ntdll: Translate signal to trap when trap code is 0 on ARM.
[wine.git] / dlls / comctl32 / button.c
blob5338ef8a8a637f42b946c33f22e097c17c58a27f
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 union
95 HICON icon;
96 HBITMAP bitmap;
97 HANDLE image;
98 } u;
99 } BUTTON_INFO;
101 static UINT BUTTON_CalcLayoutRects( const BUTTON_INFO *infoPtr, HDC hdc, RECT *labelRc, RECT *imageRc, RECT *textRc );
102 static void PB_Paint( const BUTTON_INFO *infoPtr, HDC hDC, UINT action );
103 static void CB_Paint( const BUTTON_INFO *infoPtr, HDC hDC, UINT action );
104 static void GB_Paint( const BUTTON_INFO *infoPtr, HDC hDC, UINT action );
105 static void UB_Paint( const BUTTON_INFO *infoPtr, HDC hDC, UINT action );
106 static void OB_Paint( const BUTTON_INFO *infoPtr, HDC hDC, UINT action );
107 static void SB_Paint( const BUTTON_INFO *infoPtr, HDC hDC, UINT action );
108 static void BUTTON_CheckAutoRadioButton( HWND hwnd );
109 static void get_split_button_rects(const BUTTON_INFO*, const RECT*, RECT*, RECT*);
110 static BOOL notify_split_button_dropdown(const BUTTON_INFO*, const POINT*, HWND);
111 static void draw_split_button_dropdown_glyph(const BUTTON_INFO*, HDC, RECT*);
113 #define MAX_BTN_TYPE 16
115 static const WORD maxCheckState[MAX_BTN_TYPE] =
117 BST_UNCHECKED, /* BS_PUSHBUTTON */
118 BST_UNCHECKED, /* BS_DEFPUSHBUTTON */
119 BST_CHECKED, /* BS_CHECKBOX */
120 BST_CHECKED, /* BS_AUTOCHECKBOX */
121 BST_CHECKED, /* BS_RADIOBUTTON */
122 BST_INDETERMINATE, /* BS_3STATE */
123 BST_INDETERMINATE, /* BS_AUTO3STATE */
124 BST_UNCHECKED, /* BS_GROUPBOX */
125 BST_UNCHECKED, /* BS_USERBUTTON */
126 BST_CHECKED, /* BS_AUTORADIOBUTTON */
127 BST_UNCHECKED, /* BS_PUSHBOX */
128 BST_UNCHECKED, /* BS_OWNERDRAW */
129 BST_UNCHECKED, /* BS_SPLITBUTTON */
130 BST_UNCHECKED, /* BS_DEFSPLITBUTTON */
131 BST_UNCHECKED, /* BS_COMMANDLINK */
132 BST_UNCHECKED /* BS_DEFCOMMANDLINK */
135 /* Generic draw states, use get_draw_state() to get specific state for button type */
136 enum draw_state
138 STATE_NORMAL,
139 STATE_DISABLED,
140 STATE_HOT,
141 STATE_PRESSED,
142 STATE_DEFAULTED,
143 DRAW_STATE_COUNT
146 typedef void (*pfPaint)( const BUTTON_INFO *infoPtr, HDC hdc, UINT action );
148 static const pfPaint btnPaintFunc[MAX_BTN_TYPE] =
150 PB_Paint, /* BS_PUSHBUTTON */
151 PB_Paint, /* BS_DEFPUSHBUTTON */
152 CB_Paint, /* BS_CHECKBOX */
153 CB_Paint, /* BS_AUTOCHECKBOX */
154 CB_Paint, /* BS_RADIOBUTTON */
155 CB_Paint, /* BS_3STATE */
156 CB_Paint, /* BS_AUTO3STATE */
157 GB_Paint, /* BS_GROUPBOX */
158 UB_Paint, /* BS_USERBUTTON */
159 CB_Paint, /* BS_AUTORADIOBUTTON */
160 NULL, /* BS_PUSHBOX */
161 OB_Paint, /* BS_OWNERDRAW */
162 SB_Paint, /* BS_SPLITBUTTON */
163 SB_Paint, /* BS_DEFSPLITBUTTON */
164 PB_Paint, /* BS_COMMANDLINK */
165 PB_Paint /* BS_DEFCOMMANDLINK */
168 typedef void (*pfThemedPaint)( HTHEME theme, const BUTTON_INFO *infoPtr, HDC hdc, int drawState, UINT dtflags, BOOL focused);
170 static void PB_ThemedPaint( HTHEME theme, const BUTTON_INFO *infoPtr, HDC hdc, int drawState, UINT dtflags, BOOL focused);
171 static void CB_ThemedPaint( HTHEME theme, const BUTTON_INFO *infoPtr, HDC hdc, int drawState, UINT dtflags, BOOL focused);
172 static void GB_ThemedPaint( HTHEME theme, const BUTTON_INFO *infoPtr, HDC hdc, int drawState, UINT dtflags, BOOL focused);
173 static void SB_ThemedPaint( HTHEME theme, const BUTTON_INFO *infoPtr, HDC hdc, int drawState, UINT dtflags, BOOL focused);
175 static const pfThemedPaint btnThemedPaintFunc[MAX_BTN_TYPE] =
177 PB_ThemedPaint, /* BS_PUSHBUTTON */
178 PB_ThemedPaint, /* BS_DEFPUSHBUTTON */
179 CB_ThemedPaint, /* BS_CHECKBOX */
180 CB_ThemedPaint, /* BS_AUTOCHECKBOX */
181 CB_ThemedPaint, /* BS_RADIOBUTTON */
182 CB_ThemedPaint, /* BS_3STATE */
183 CB_ThemedPaint, /* BS_AUTO3STATE */
184 GB_ThemedPaint, /* BS_GROUPBOX */
185 NULL, /* BS_USERBUTTON */
186 CB_ThemedPaint, /* BS_AUTORADIOBUTTON */
187 NULL, /* BS_PUSHBOX */
188 NULL, /* BS_OWNERDRAW */
189 SB_ThemedPaint, /* BS_SPLITBUTTON */
190 SB_ThemedPaint, /* BS_DEFSPLITBUTTON */
191 NULL, /* BS_COMMANDLINK */
192 NULL, /* BS_DEFCOMMANDLINK */
195 typedef BOOL (*pfGetIdealSize)(BUTTON_INFO *infoPtr, SIZE *size);
197 static BOOL PB_GetIdealSize(BUTTON_INFO *infoPtr, SIZE *size);
198 static BOOL CB_GetIdealSize(BUTTON_INFO *infoPtr, SIZE *size);
199 static BOOL GB_GetIdealSize(BUTTON_INFO *infoPtr, SIZE *size);
200 static BOOL SB_GetIdealSize(BUTTON_INFO *infoPtr, SIZE *size);
202 static const pfGetIdealSize btnGetIdealSizeFunc[MAX_BTN_TYPE] = {
203 PB_GetIdealSize, /* BS_PUSHBUTTON */
204 PB_GetIdealSize, /* BS_DEFPUSHBUTTON */
205 CB_GetIdealSize, /* BS_CHECKBOX */
206 CB_GetIdealSize, /* BS_AUTOCHECKBOX */
207 CB_GetIdealSize, /* BS_RADIOBUTTON */
208 GB_GetIdealSize, /* BS_3STATE */
209 GB_GetIdealSize, /* BS_AUTO3STATE */
210 GB_GetIdealSize, /* BS_GROUPBOX */
211 PB_GetIdealSize, /* BS_USERBUTTON */
212 CB_GetIdealSize, /* BS_AUTORADIOBUTTON */
213 GB_GetIdealSize, /* BS_PUSHBOX */
214 GB_GetIdealSize, /* BS_OWNERDRAW */
215 SB_GetIdealSize, /* BS_SPLITBUTTON */
216 SB_GetIdealSize, /* BS_DEFSPLITBUTTON */
217 /* GetIdealSize() for following types are unimplemented, use BS_PUSHBUTTON's for now */
218 PB_GetIdealSize, /* BS_COMMANDLINK */
219 PB_GetIdealSize /* BS_DEFCOMMANDLINK */
222 static inline UINT get_button_type( LONG window_style )
224 return (window_style & BS_TYPEMASK);
227 static inline BOOL button_centers_text( LONG window_style )
229 /* Push button's text is centered by default, same for split buttons */
230 UINT type = get_button_type(window_style);
231 return type <= BS_DEFPUSHBUTTON || type == BS_SPLITBUTTON || type == BS_DEFSPLITBUTTON;
234 /* paint a button of any type */
235 static inline void paint_button( BUTTON_INFO *infoPtr, LONG style, UINT action )
237 if (btnPaintFunc[style] && IsWindowVisible(infoPtr->hwnd))
239 HDC hdc = GetDC( infoPtr->hwnd );
240 btnPaintFunc[style]( infoPtr, hdc, action );
241 ReleaseDC( infoPtr->hwnd, hdc );
245 /* retrieve the button text; returned buffer must be freed by caller */
246 static inline WCHAR *get_button_text( const BUTTON_INFO *infoPtr )
248 INT len = GetWindowTextLengthW( infoPtr->hwnd );
249 WCHAR *buffer = heap_alloc( (len + 1) * sizeof(WCHAR) );
250 if (buffer)
251 GetWindowTextW( infoPtr->hwnd, buffer, len + 1 );
252 return buffer;
255 /* get the default glyph size for split buttons */
256 static LONG get_default_glyph_size(const BUTTON_INFO *infoPtr)
258 if (infoPtr->split_style & BCSS_IMAGE)
260 /* Size it to fit, including the left and right edges */
261 int w, h;
262 if (!ImageList_GetIconSize(infoPtr->glyph, &w, &h)) w = 0;
263 return w + GetSystemMetrics(SM_CXEDGE) * 2;
266 /* The glyph size relies on the default menu font's cell height */
267 return GetSystemMetrics(SM_CYMENUCHECK);
270 static void init_custom_draw(NMCUSTOMDRAW *nmcd, const BUTTON_INFO *infoPtr, HDC hdc, const RECT *rc)
272 nmcd->hdr.hwndFrom = infoPtr->hwnd;
273 nmcd->hdr.idFrom = GetWindowLongPtrW(infoPtr->hwnd, GWLP_ID);
274 nmcd->hdr.code = NM_CUSTOMDRAW;
275 nmcd->hdc = hdc;
276 nmcd->rc = *rc;
277 nmcd->dwDrawStage = CDDS_PREERASE;
278 nmcd->dwItemSpec = 0;
279 nmcd->lItemlParam = 0;
280 nmcd->uItemState = IsWindowEnabled(infoPtr->hwnd) ? 0 : CDIS_DISABLED;
281 if (infoPtr->state & BST_PUSHED) nmcd->uItemState |= CDIS_SELECTED;
282 if (infoPtr->state & BST_FOCUS) nmcd->uItemState |= CDIS_FOCUS;
283 if (infoPtr->state & BST_HOT) nmcd->uItemState |= CDIS_HOT;
284 if (infoPtr->state & BST_INDETERMINATE)
285 nmcd->uItemState |= CDIS_INDETERMINATE;
287 /* Windows doesn't seem to send CDIS_CHECKED (it fails the tests) */
288 /* CDIS_SHOWKEYBOARDCUES is misleading, as the meaning is reversed */
289 /* FIXME: Handle it properly when we support keyboard cues? */
292 HRGN set_control_clipping( HDC hdc, const RECT *rect )
294 RECT rc = *rect;
295 HRGN hrgn = CreateRectRgn( 0, 0, 0, 0 );
297 if (GetClipRgn( hdc, hrgn ) != 1)
299 DeleteObject( hrgn );
300 hrgn = 0;
302 DPtoLP( hdc, (POINT *)&rc, 2 );
303 if (GetLayout( hdc ) & LAYOUT_RTL) /* compensate for the shifting done by IntersectClipRect */
305 rc.left++;
306 rc.right++;
308 IntersectClipRect( hdc, rc.left, rc.top, rc.right, rc.bottom );
309 return hrgn;
312 static WCHAR *heap_strndupW(const WCHAR *src, size_t length)
314 size_t size = (length + 1) * sizeof(WCHAR);
315 WCHAR *dst = heap_alloc(size);
316 if (dst) memcpy(dst, src, size);
317 return dst;
320 /**********************************************************************
321 * Convert button styles to flags used by DrawText.
323 static UINT BUTTON_BStoDT( DWORD style, DWORD ex_style )
325 UINT dtStyle = DT_NOCLIP; /* We use SelectClipRgn to limit output */
327 /* "Convert" pushlike buttons to pushbuttons */
328 if (style & BS_PUSHLIKE)
329 style &= ~BS_TYPEMASK;
331 if (!(style & BS_MULTILINE))
332 dtStyle |= DT_SINGLELINE;
333 else
334 dtStyle |= DT_WORDBREAK;
336 switch (style & BS_CENTER)
338 case BS_LEFT: /* DT_LEFT is 0 */ break;
339 case BS_RIGHT: dtStyle |= DT_RIGHT; break;
340 case BS_CENTER: dtStyle |= DT_CENTER; break;
341 default:
342 if (button_centers_text(style)) dtStyle |= DT_CENTER;
345 if (ex_style & WS_EX_RIGHT) dtStyle = DT_RIGHT | (dtStyle & ~(DT_LEFT | DT_CENTER));
347 /* DrawText ignores vertical alignment for multiline text,
348 * but we use these flags to align label manually.
350 if (get_button_type(style) != BS_GROUPBOX)
352 switch (style & BS_VCENTER)
354 case BS_TOP: /* DT_TOP is 0 */ break;
355 case BS_BOTTOM: dtStyle |= DT_BOTTOM; break;
356 case BS_VCENTER: /* fall through */
357 default: dtStyle |= DT_VCENTER; break;
361 return dtStyle;
364 static int get_draw_state(const BUTTON_INFO *infoPtr)
366 static const int pb_states[DRAW_STATE_COUNT] = { PBS_NORMAL, PBS_DISABLED, PBS_HOT, PBS_PRESSED, PBS_DEFAULTED };
367 static const int cb_states[3][DRAW_STATE_COUNT] =
369 { CBS_UNCHECKEDNORMAL, CBS_UNCHECKEDDISABLED, CBS_UNCHECKEDHOT, CBS_UNCHECKEDPRESSED, CBS_UNCHECKEDNORMAL },
370 { CBS_CHECKEDNORMAL, CBS_CHECKEDDISABLED, CBS_CHECKEDHOT, CBS_CHECKEDPRESSED, CBS_CHECKEDNORMAL },
371 { CBS_MIXEDNORMAL, CBS_MIXEDDISABLED, CBS_MIXEDHOT, CBS_MIXEDPRESSED, CBS_MIXEDNORMAL }
373 static const int rb_states[2][DRAW_STATE_COUNT] =
375 { RBS_UNCHECKEDNORMAL, RBS_UNCHECKEDDISABLED, RBS_UNCHECKEDHOT, RBS_UNCHECKEDPRESSED, RBS_UNCHECKEDNORMAL },
376 { RBS_CHECKEDNORMAL, RBS_CHECKEDDISABLED, RBS_CHECKEDHOT, RBS_CHECKEDPRESSED, RBS_CHECKEDNORMAL }
378 static const int gb_states[DRAW_STATE_COUNT] = { GBS_NORMAL, GBS_DISABLED, GBS_NORMAL, GBS_NORMAL, GBS_NORMAL };
379 LONG style = GetWindowLongW(infoPtr->hwnd, GWL_STYLE);
380 UINT type = get_button_type(style);
381 int check_state = infoPtr->state & 3;
382 enum draw_state state;
384 if (!IsWindowEnabled(infoPtr->hwnd))
385 state = STATE_DISABLED;
386 else if (infoPtr->state & BST_PUSHED)
387 state = STATE_PRESSED;
388 else if (infoPtr->state & BST_HOT)
389 state = STATE_HOT;
390 else if (infoPtr->state & BST_FOCUS)
391 state = STATE_DEFAULTED;
392 else
393 state = STATE_NORMAL;
395 switch (type)
397 case BS_PUSHBUTTON:
398 case BS_DEFPUSHBUTTON:
399 case BS_USERBUTTON:
400 case BS_SPLITBUTTON:
401 case BS_DEFSPLITBUTTON:
402 case BS_COMMANDLINK:
403 case BS_DEFCOMMANDLINK:
404 return pb_states[state];
405 case BS_CHECKBOX:
406 case BS_AUTOCHECKBOX:
407 return cb_states[check_state][state];
408 case BS_RADIOBUTTON:
409 case BS_3STATE:
410 case BS_AUTO3STATE:
411 case BS_AUTORADIOBUTTON:
412 return rb_states[check_state][state];
413 case BS_GROUPBOX:
414 return gb_states[state];
415 default:
416 WARN("Unsupported button type 0x%08x\n", type);
417 return PBS_NORMAL;
421 static LRESULT CALLBACK BUTTON_WindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
423 BUTTON_INFO *infoPtr = (BUTTON_INFO *)GetWindowLongPtrW(hWnd, 0);
424 RECT rect;
425 POINT pt;
426 LONG style = GetWindowLongW( hWnd, GWL_STYLE );
427 UINT btn_type = get_button_type( style );
428 LONG state, new_state;
429 HANDLE oldHbitmap;
430 HTHEME theme;
432 if (!IsWindow( hWnd )) return 0;
434 if (!infoPtr && (uMsg != WM_NCCREATE))
435 return DefWindowProcW(hWnd, uMsg, wParam, lParam);
437 pt.x = (short)LOWORD(lParam);
438 pt.y = (short)HIWORD(lParam);
440 switch (uMsg)
442 case WM_GETDLGCODE:
443 switch(btn_type)
445 case BS_COMMANDLINK:
446 case BS_USERBUTTON:
447 case BS_PUSHBUTTON: return DLGC_BUTTON | DLGC_UNDEFPUSHBUTTON;
448 case BS_DEFCOMMANDLINK:
449 case BS_DEFPUSHBUTTON: return DLGC_BUTTON | DLGC_DEFPUSHBUTTON;
450 case BS_RADIOBUTTON:
451 case BS_AUTORADIOBUTTON: return DLGC_BUTTON | DLGC_RADIOBUTTON;
452 case BS_GROUPBOX: return DLGC_STATIC;
453 case BS_SPLITBUTTON: return DLGC_BUTTON | DLGC_UNDEFPUSHBUTTON | DLGC_WANTARROWS;
454 case BS_DEFSPLITBUTTON: return DLGC_BUTTON | DLGC_DEFPUSHBUTTON | DLGC_WANTARROWS;
455 default: return DLGC_BUTTON;
458 case WM_ENABLE:
459 theme = GetWindowTheme( hWnd );
460 if (theme)
461 RedrawWindow( hWnd, NULL, NULL, RDW_FRAME | RDW_INVALIDATE | RDW_UPDATENOW );
462 else
463 paint_button( infoPtr, btn_type, ODA_DRAWENTIRE );
464 break;
466 case WM_NCCREATE:
468 CREATESTRUCTW *cs = (CREATESTRUCTW *)lParam;
470 infoPtr = heap_alloc_zero( sizeof(*infoPtr) );
471 SetWindowLongPtrW( hWnd, 0, (LONG_PTR)infoPtr );
472 infoPtr->hwnd = hWnd;
473 infoPtr->parent = cs->hwndParent;
474 infoPtr->style = cs->style;
475 infoPtr->split_style = BCSS_STRETCH;
476 infoPtr->glyph = (HIMAGELIST)0x36; /* Marlett down arrow char code */
477 infoPtr->glyph_size.cx = get_default_glyph_size(infoPtr);
478 return DefWindowProcW(hWnd, uMsg, wParam, lParam);
481 case WM_NCDESTROY:
482 SetWindowLongPtrW( hWnd, 0, 0 );
483 heap_free(infoPtr->note);
484 heap_free(infoPtr);
485 break;
487 case WM_CREATE:
488 if (btn_type >= MAX_BTN_TYPE)
489 return -1; /* abort */
491 /* XP turns a BS_USERBUTTON into BS_PUSHBUTTON */
492 if (btn_type == BS_USERBUTTON )
494 style = (style & ~BS_TYPEMASK) | BS_PUSHBUTTON;
495 SetWindowLongW( hWnd, GWL_STYLE, style );
497 infoPtr->state = BST_UNCHECKED;
498 OpenThemeData( hWnd, WC_BUTTONW );
499 return 0;
501 case WM_DESTROY:
502 theme = GetWindowTheme( hWnd );
503 CloseThemeData( theme );
504 break;
506 case WM_THEMECHANGED:
507 theme = GetWindowTheme( hWnd );
508 CloseThemeData( theme );
509 OpenThemeData( hWnd, WC_BUTTONW );
510 break;
512 case WM_ERASEBKGND:
513 if (btn_type == BS_OWNERDRAW)
515 HDC hdc = (HDC)wParam;
516 RECT rc;
517 HBRUSH hBrush;
518 HWND parent = GetParent(hWnd);
519 if (!parent) parent = hWnd;
520 hBrush = (HBRUSH)SendMessageW(parent, WM_CTLCOLORBTN, (WPARAM)hdc, (LPARAM)hWnd);
521 if (!hBrush) /* did the app forget to call defwindowproc ? */
522 hBrush = (HBRUSH)DefWindowProcW(parent, WM_CTLCOLORBTN,
523 (WPARAM)hdc, (LPARAM)hWnd);
524 GetClientRect(hWnd, &rc);
525 FillRect(hdc, &rc, hBrush);
527 return 1;
529 case WM_PRINTCLIENT:
530 case WM_PAINT:
532 PAINTSTRUCT ps;
533 HDC hdc;
535 theme = GetWindowTheme( hWnd );
536 hdc = wParam ? (HDC)wParam : BeginPaint( hWnd, &ps );
538 if (theme && btnThemedPaintFunc[btn_type])
540 int drawState = get_draw_state(infoPtr);
541 UINT dtflags = BUTTON_BStoDT(style, GetWindowLongW(hWnd, GWL_EXSTYLE));
543 btnThemedPaintFunc[btn_type](theme, infoPtr, hdc, drawState, dtflags, infoPtr->state & BST_FOCUS);
545 else if (btnPaintFunc[btn_type])
547 int nOldMode = SetBkMode( hdc, OPAQUE );
548 btnPaintFunc[btn_type]( infoPtr, hdc, ODA_DRAWENTIRE );
549 SetBkMode(hdc, nOldMode); /* reset painting mode */
552 if ( !wParam ) EndPaint( hWnd, &ps );
553 break;
556 case WM_KEYDOWN:
557 if (wParam == VK_SPACE)
559 SendMessageW( hWnd, BM_SETSTATE, TRUE, 0 );
560 infoPtr->state |= BUTTON_BTNPRESSED;
561 SetCapture( hWnd );
563 else if (wParam == VK_UP || wParam == VK_DOWN)
565 /* Up and down arrows work on every button, and even with BCSS_NOSPLIT */
566 notify_split_button_dropdown(infoPtr, NULL, hWnd);
568 break;
570 case WM_LBUTTONDBLCLK:
571 if(style & BS_NOTIFY ||
572 btn_type == BS_RADIOBUTTON ||
573 btn_type == BS_USERBUTTON ||
574 btn_type == BS_OWNERDRAW)
576 BUTTON_NOTIFY_PARENT(hWnd, BN_DOUBLECLICKED);
577 break;
579 /* fall through */
580 case WM_LBUTTONDOWN:
581 SetFocus( hWnd );
583 if ((btn_type == BS_SPLITBUTTON || btn_type == BS_DEFSPLITBUTTON) &&
584 !(infoPtr->split_style & BCSS_NOSPLIT) &&
585 notify_split_button_dropdown(infoPtr, &pt, hWnd))
586 break;
588 SetCapture( hWnd );
589 infoPtr->state |= BUTTON_BTNPRESSED;
590 SendMessageW( hWnd, BM_SETSTATE, TRUE, 0 );
591 break;
593 case WM_KEYUP:
594 if (wParam != VK_SPACE)
595 break;
596 /* fall through */
597 case WM_LBUTTONUP:
598 state = infoPtr->state;
599 if (state & BST_DROPDOWNPUSHED)
600 SendMessageW(hWnd, BCM_SETDROPDOWNSTATE, FALSE, 0);
601 if (!(state & BUTTON_BTNPRESSED)) break;
602 infoPtr->state &= BUTTON_NSTATES;
603 if (!(state & BST_PUSHED))
605 ReleaseCapture();
606 break;
608 SendMessageW( hWnd, BM_SETSTATE, FALSE, 0 );
609 GetClientRect( hWnd, &rect );
610 if (uMsg == WM_KEYUP || PtInRect( &rect, pt ))
612 switch(btn_type)
614 case BS_AUTOCHECKBOX:
615 SendMessageW( hWnd, BM_SETCHECK, !(infoPtr->state & BST_CHECKED), 0 );
616 break;
617 case BS_AUTORADIOBUTTON:
618 SendMessageW( hWnd, BM_SETCHECK, TRUE, 0 );
619 break;
620 case BS_AUTO3STATE:
621 SendMessageW( hWnd, BM_SETCHECK, (infoPtr->state & BST_INDETERMINATE) ? 0 :
622 ((infoPtr->state & 3) + 1), 0 );
623 break;
625 ReleaseCapture();
626 BUTTON_NOTIFY_PARENT(hWnd, BN_CLICKED);
628 else
630 ReleaseCapture();
632 break;
634 case WM_CAPTURECHANGED:
635 TRACE("WM_CAPTURECHANGED %p\n", hWnd);
636 if (hWnd == (HWND)lParam) break;
637 if (infoPtr->state & BUTTON_BTNPRESSED)
639 infoPtr->state &= BUTTON_NSTATES;
640 if (infoPtr->state & BST_PUSHED)
641 SendMessageW( hWnd, BM_SETSTATE, FALSE, 0 );
643 break;
645 case WM_MOUSEMOVE:
647 TRACKMOUSEEVENT mouse_event;
649 mouse_event.cbSize = sizeof(TRACKMOUSEEVENT);
650 mouse_event.dwFlags = TME_QUERY;
651 if (!TrackMouseEvent(&mouse_event) || !(mouse_event.dwFlags & (TME_HOVER | TME_LEAVE)))
653 mouse_event.dwFlags = TME_HOVER | TME_LEAVE;
654 mouse_event.hwndTrack = hWnd;
655 mouse_event.dwHoverTime = 1;
656 TrackMouseEvent(&mouse_event);
659 if ((wParam & MK_LBUTTON) && GetCapture() == hWnd)
661 GetClientRect( hWnd, &rect );
662 SendMessageW( hWnd, BM_SETSTATE, PtInRect(&rect, pt), 0 );
664 break;
667 case WM_MOUSEHOVER:
669 infoPtr->state |= BST_HOT;
670 InvalidateRect( hWnd, NULL, FALSE );
671 break;
674 case WM_MOUSELEAVE:
676 infoPtr->state &= ~BST_HOT;
677 InvalidateRect( hWnd, NULL, FALSE );
678 break;
681 case WM_SETTEXT:
683 /* Clear an old text here as Windows does */
684 if (IsWindowVisible(hWnd))
686 HDC hdc = GetDC(hWnd);
687 HBRUSH hbrush;
688 RECT client, rc;
689 HWND parent = GetParent(hWnd);
690 UINT message = (btn_type == BS_PUSHBUTTON ||
691 btn_type == BS_DEFPUSHBUTTON ||
692 btn_type == BS_USERBUTTON ||
693 btn_type == BS_OWNERDRAW) ?
694 WM_CTLCOLORBTN : WM_CTLCOLORSTATIC;
696 if (!parent) parent = hWnd;
697 hbrush = (HBRUSH)SendMessageW(parent, message,
698 (WPARAM)hdc, (LPARAM)hWnd);
699 if (!hbrush) /* did the app forget to call DefWindowProc ? */
700 hbrush = (HBRUSH)DefWindowProcW(parent, message,
701 (WPARAM)hdc, (LPARAM)hWnd);
703 GetClientRect(hWnd, &client);
704 rc = client;
705 /* FIXME: check other BS_* handlers */
706 if (btn_type == BS_GROUPBOX)
707 InflateRect(&rc, -7, 1); /* GB_Paint does this */
708 BUTTON_CalcLayoutRects(infoPtr, hdc, &rc, NULL, NULL);
709 /* Clip by client rect bounds */
710 if (rc.right > client.right) rc.right = client.right;
711 if (rc.bottom > client.bottom) rc.bottom = client.bottom;
712 FillRect(hdc, &rc, hbrush);
713 ReleaseDC(hWnd, hdc);
716 DefWindowProcW( hWnd, WM_SETTEXT, wParam, lParam );
717 if (btn_type == BS_GROUPBOX) /* Yes, only for BS_GROUPBOX */
718 InvalidateRect( hWnd, NULL, TRUE );
719 else
720 paint_button( infoPtr, btn_type, ODA_DRAWENTIRE );
721 return 1; /* success. FIXME: check text length */
724 case BCM_SETNOTE:
726 WCHAR *note = (WCHAR *)lParam;
727 if (btn_type != BS_COMMANDLINK && btn_type != BS_DEFCOMMANDLINK)
729 SetLastError(ERROR_NOT_SUPPORTED);
730 return FALSE;
733 heap_free(infoPtr->note);
734 if (note)
736 infoPtr->note_length = lstrlenW(note);
737 infoPtr->note = heap_strndupW(note, infoPtr->note_length);
740 if (!note || !infoPtr->note)
742 infoPtr->note_length = 0;
743 infoPtr->note = heap_alloc_zero(sizeof(WCHAR));
746 SetLastError(NO_ERROR);
747 return TRUE;
750 case BCM_GETNOTE:
752 DWORD *size = (DWORD *)wParam;
753 WCHAR *buffer = (WCHAR *)lParam;
754 INT length = 0;
756 if (btn_type != BS_COMMANDLINK && btn_type != BS_DEFCOMMANDLINK)
758 SetLastError(ERROR_NOT_SUPPORTED);
759 return FALSE;
762 if (!buffer || !size || !infoPtr->note)
764 SetLastError(ERROR_INVALID_PARAMETER);
765 return FALSE;
768 if (*size > 0)
770 length = min(*size - 1, infoPtr->note_length);
771 memcpy(buffer, infoPtr->note, length * sizeof(WCHAR));
772 buffer[length] = '\0';
775 if (*size < infoPtr->note_length + 1)
777 *size = infoPtr->note_length + 1;
778 SetLastError(ERROR_INSUFFICIENT_BUFFER);
779 return FALSE;
781 else
783 SetLastError(NO_ERROR);
784 return TRUE;
788 case BCM_GETNOTELENGTH:
790 if (btn_type != BS_COMMANDLINK && btn_type != BS_DEFCOMMANDLINK)
792 SetLastError(ERROR_NOT_SUPPORTED);
793 return 0;
796 return infoPtr->note_length;
799 case WM_SETFONT:
800 infoPtr->font = (HFONT)wParam;
801 if (lParam) InvalidateRect(hWnd, NULL, TRUE);
802 break;
804 case WM_GETFONT:
805 return (LRESULT)infoPtr->font;
807 case WM_SETFOCUS:
808 TRACE("WM_SETFOCUS %p\n",hWnd);
809 infoPtr->state |= BST_FOCUS;
810 paint_button( infoPtr, btn_type, ODA_FOCUS );
811 if (style & BS_NOTIFY)
812 BUTTON_NOTIFY_PARENT(hWnd, BN_SETFOCUS);
813 break;
815 case WM_KILLFOCUS:
816 TRACE("WM_KILLFOCUS %p\n",hWnd);
817 infoPtr->state &= ~BST_FOCUS;
819 if ((infoPtr->state & BUTTON_BTNPRESSED) && GetCapture() == hWnd)
820 ReleaseCapture();
821 if (style & BS_NOTIFY)
822 BUTTON_NOTIFY_PARENT(hWnd, BN_KILLFOCUS);
824 InvalidateRect( hWnd, NULL, FALSE );
825 break;
827 case WM_SYSCOLORCHANGE:
828 InvalidateRect( hWnd, NULL, FALSE );
829 break;
831 case BM_SETSTYLE:
832 btn_type = wParam & BS_TYPEMASK;
833 style = (style & ~BS_TYPEMASK) | btn_type;
834 SetWindowLongW( hWnd, GWL_STYLE, style );
836 /* Only redraw if lParam flag is set.*/
837 if (lParam)
838 InvalidateRect( hWnd, NULL, TRUE );
840 break;
842 case BM_CLICK:
843 SendMessageW( hWnd, WM_LBUTTONDOWN, 0, 0 );
844 SendMessageW( hWnd, WM_LBUTTONUP, 0, 0 );
845 break;
847 case BM_SETIMAGE:
848 infoPtr->image_type = (DWORD)wParam;
849 oldHbitmap = infoPtr->u.image;
850 infoPtr->u.image = (HANDLE)lParam;
851 InvalidateRect( hWnd, NULL, FALSE );
852 return (LRESULT)oldHbitmap;
854 case BM_GETIMAGE:
855 return (LRESULT)infoPtr->u.image;
857 case BCM_SETIMAGELIST:
859 BUTTON_IMAGELIST *imagelist = (BUTTON_IMAGELIST *)lParam;
861 if (!imagelist) return FALSE;
863 infoPtr->imagelist = *imagelist;
864 return TRUE;
867 case BCM_GETIMAGELIST:
869 BUTTON_IMAGELIST *imagelist = (BUTTON_IMAGELIST *)lParam;
871 if (!imagelist) return FALSE;
873 *imagelist = infoPtr->imagelist;
874 return TRUE;
877 case BCM_SETSPLITINFO:
879 BUTTON_SPLITINFO *info = (BUTTON_SPLITINFO*)lParam;
881 if (!info) return TRUE;
883 if (info->mask & (BCSIF_GLYPH | BCSIF_IMAGE))
885 infoPtr->split_style &= ~BCSS_IMAGE;
886 if (!(info->mask & BCSIF_GLYPH))
887 infoPtr->split_style |= BCSS_IMAGE;
888 infoPtr->glyph = info->himlGlyph;
889 infoPtr->glyph_size.cx = infoPtr->glyph_size.cy = 0;
892 if (info->mask & BCSIF_STYLE)
893 infoPtr->split_style = info->uSplitStyle;
894 if (info->mask & BCSIF_SIZE)
895 infoPtr->glyph_size = info->size;
897 /* Calculate fitting value for cx if invalid (cy is untouched) */
898 if (infoPtr->glyph_size.cx <= 0)
899 infoPtr->glyph_size.cx = get_default_glyph_size(infoPtr);
901 /* Windows doesn't invalidate or redraw it, so we don't, either */
902 return TRUE;
905 case BCM_GETSPLITINFO:
907 BUTTON_SPLITINFO *info = (BUTTON_SPLITINFO*)lParam;
909 if (!info) return FALSE;
911 if (info->mask & BCSIF_STYLE)
912 info->uSplitStyle = infoPtr->split_style;
913 if (info->mask & (BCSIF_GLYPH | BCSIF_IMAGE))
914 info->himlGlyph = infoPtr->glyph;
915 if (info->mask & BCSIF_SIZE)
916 info->size = infoPtr->glyph_size;
918 return TRUE;
921 case BM_GETCHECK:
922 return infoPtr->state & 3;
924 case BM_SETCHECK:
925 if (wParam > maxCheckState[btn_type]) wParam = maxCheckState[btn_type];
926 if ((btn_type == BS_RADIOBUTTON) || (btn_type == BS_AUTORADIOBUTTON))
928 style = wParam ? style | WS_TABSTOP : style & ~WS_TABSTOP;
929 SetWindowLongW( hWnd, GWL_STYLE, style );
931 if ((infoPtr->state & 3) != wParam)
933 infoPtr->state = (infoPtr->state & ~3) | wParam;
934 InvalidateRect( hWnd, NULL, FALSE );
936 if ((btn_type == BS_AUTORADIOBUTTON) && (wParam == BST_CHECKED) && (style & WS_CHILD))
937 BUTTON_CheckAutoRadioButton( hWnd );
938 break;
940 case BM_GETSTATE:
941 return infoPtr->state;
943 case BM_SETSTATE:
944 state = infoPtr->state;
945 new_state = wParam ? BST_PUSHED : 0;
947 if ((state ^ new_state) & BST_PUSHED)
949 if (wParam)
950 state |= BST_PUSHED;
951 else
952 state &= ~BST_PUSHED;
954 if (btn_type == BS_USERBUTTON)
955 BUTTON_NOTIFY_PARENT( hWnd, (state & BST_PUSHED) ? BN_HILITE : BN_UNHILITE );
956 infoPtr->state = state;
958 InvalidateRect( hWnd, NULL, FALSE );
960 break;
962 case BCM_SETDROPDOWNSTATE:
963 new_state = wParam ? BST_DROPDOWNPUSHED : 0;
965 if ((infoPtr->state ^ new_state) & BST_DROPDOWNPUSHED)
967 infoPtr->state &= ~BST_DROPDOWNPUSHED;
968 infoPtr->state |= new_state;
969 InvalidateRect(hWnd, NULL, FALSE);
971 break;
973 case BCM_SETTEXTMARGIN:
975 RECT *text_margin = (RECT *)lParam;
977 if (!text_margin) return FALSE;
979 infoPtr->text_margin = *text_margin;
980 return TRUE;
983 case BCM_GETTEXTMARGIN:
985 RECT *text_margin = (RECT *)lParam;
987 if (!text_margin) return FALSE;
989 *text_margin = infoPtr->text_margin;
990 return TRUE;
993 case BCM_GETIDEALSIZE:
995 SIZE *size = (SIZE *)lParam;
997 if (!size) return FALSE;
999 return btnGetIdealSizeFunc[btn_type](infoPtr, size);
1002 case WM_NCHITTEST:
1003 if(btn_type == BS_GROUPBOX) return HTTRANSPARENT;
1004 /* fall through */
1005 default:
1006 return DefWindowProcW(hWnd, uMsg, wParam, lParam);
1008 return 0;
1011 /* If maxWidth is zero, rectangle width is unlimited */
1012 static RECT BUTTON_GetTextRect(const BUTTON_INFO *infoPtr, HDC hdc, const WCHAR *text, LONG maxWidth)
1014 LONG style = GetWindowLongW(infoPtr->hwnd, GWL_STYLE);
1015 LONG exStyle = GetWindowLongW(infoPtr->hwnd, GWL_EXSTYLE);
1016 UINT dtStyle = BUTTON_BStoDT(style, exStyle);
1017 HFONT hPrevFont;
1018 RECT rect = {0};
1020 rect.right = maxWidth;
1021 hPrevFont = SelectObject(hdc, infoPtr->font);
1022 /* Calculate height without DT_VCENTER and DT_BOTTOM to get the correct height */
1023 DrawTextW(hdc, text, -1, &rect, (dtStyle & ~(DT_VCENTER | DT_BOTTOM)) | DT_CALCRECT);
1024 if (hPrevFont) SelectObject(hdc, hPrevFont);
1026 return rect;
1029 static BOOL show_image_only(const BUTTON_INFO *infoPtr)
1031 LONG style = GetWindowLongW(infoPtr->hwnd, GWL_STYLE);
1032 return (style & (BS_ICON | BS_BITMAP)) && (infoPtr->u.image || infoPtr->imagelist.himl);
1035 static BOOL show_image_and_text(const BUTTON_INFO *infoPtr)
1037 LONG style = GetWindowLongW(infoPtr->hwnd, GWL_STYLE);
1038 UINT type = get_button_type(style);
1039 return !(style & (BS_ICON | BS_BITMAP))
1040 && ((infoPtr->u.image
1041 && (type == BS_PUSHBUTTON || type == BS_DEFPUSHBUTTON || type == BS_USERBUTTON || type == BS_SPLITBUTTON
1042 || type == BS_DEFSPLITBUTTON || type == BS_COMMANDLINK || type == BS_DEFCOMMANDLINK))
1043 || (infoPtr->imagelist.himl && type != BS_GROUPBOX));
1046 static BOOL show_image(const BUTTON_INFO *infoPtr)
1048 return show_image_only(infoPtr) || show_image_and_text(infoPtr);
1051 /* Get a bounding rectangle that is large enough to contain a image and a text side by side.
1052 * Note: (left,top) of the result rectangle may not be (0,0), offset it by yourself if needed */
1053 static RECT BUTTON_GetBoundingLabelRect(LONG style, const RECT *textRect, const RECT *imageRect)
1055 RECT labelRect;
1056 RECT rect = *imageRect;
1057 INT textWidth = textRect->right - textRect->left;
1058 INT textHeight = textRect->bottom - textRect->top;
1059 INT imageWidth = imageRect->right - imageRect->left;
1060 INT imageHeight = imageRect->bottom - imageRect->top;
1062 if ((style & BS_CENTER) == BS_RIGHT)
1063 OffsetRect(&rect, textWidth, 0);
1064 else if ((style & BS_CENTER) == BS_LEFT)
1065 OffsetRect(&rect, -imageWidth, 0);
1066 else if ((style & BS_VCENTER) == BS_BOTTOM)
1067 OffsetRect(&rect, 0, textHeight);
1068 else if ((style & BS_VCENTER) == BS_TOP)
1069 OffsetRect(&rect, 0, -imageHeight);
1070 else
1071 OffsetRect(&rect, -imageWidth, 0);
1073 UnionRect(&labelRect, textRect, &rect);
1074 return labelRect;
1077 /* Position a rectangle inside a bounding rectangle according to button alignment flags */
1078 static void BUTTON_PositionRect(LONG style, const RECT *outerRect, RECT *innerRect, const RECT *margin)
1080 INT width = innerRect->right - innerRect->left;
1081 INT height = innerRect->bottom - innerRect->top;
1083 if ((style & WS_EX_RIGHT) && !(style & BS_CENTER)) style |= BS_CENTER;
1085 if (!(style & BS_CENTER))
1087 if (button_centers_text(style))
1088 style |= BS_CENTER;
1089 else
1090 style |= BS_LEFT;
1093 if (!(style & BS_VCENTER))
1095 /* Group box's text is top aligned by default */
1096 if (get_button_type(style) == BS_GROUPBOX)
1097 style |= BS_TOP;
1100 switch (style & BS_CENTER)
1102 case BS_CENTER:
1103 innerRect->left = outerRect->left + (outerRect->right - outerRect->left - width) / 2;
1104 innerRect->right = innerRect->left + width;
1105 break;
1106 case BS_RIGHT:
1107 innerRect->right = outerRect->right - margin->right;
1108 innerRect->left = innerRect->right - width;
1109 break;
1110 case BS_LEFT:
1111 default:
1112 innerRect->left = outerRect->left + margin->left;
1113 innerRect->right = innerRect->left + width;
1114 break;
1117 switch (style & BS_VCENTER)
1119 case BS_TOP:
1120 innerRect->top = outerRect->top + margin->top;
1121 innerRect->bottom = innerRect->top + height;
1122 break;
1123 case BS_BOTTOM:
1124 innerRect->bottom = outerRect->bottom - margin->bottom;
1125 innerRect->top = innerRect->bottom - height;
1126 break;
1127 case BS_VCENTER:
1128 default:
1129 innerRect->top = outerRect->top + (outerRect->bottom - outerRect->top - height) / 2;
1130 innerRect->bottom = innerRect->top + height;
1131 break;
1135 /* Convert imagelist align style to button align style */
1136 static UINT BUTTON_ILStoBS(UINT align)
1138 switch (align)
1140 case BUTTON_IMAGELIST_ALIGN_TOP:
1141 return BS_CENTER | BS_TOP;
1142 case BUTTON_IMAGELIST_ALIGN_BOTTOM:
1143 return BS_CENTER | BS_BOTTOM;
1144 case BUTTON_IMAGELIST_ALIGN_CENTER:
1145 return BS_CENTER | BS_VCENTER;
1146 case BUTTON_IMAGELIST_ALIGN_RIGHT:
1147 return BS_RIGHT | BS_VCENTER;
1148 case BUTTON_IMAGELIST_ALIGN_LEFT:
1149 default:
1150 return BS_LEFT | BS_VCENTER;
1154 static SIZE BUTTON_GetImageSize(const BUTTON_INFO *infoPtr)
1156 ICONINFO iconInfo;
1157 BITMAP bm = {0};
1158 SIZE size = {0};
1160 /* ImageList has priority over image */
1161 if (infoPtr->imagelist.himl)
1162 ImageList_GetIconSize(infoPtr->imagelist.himl, &size.cx, &size.cy);
1163 else if (infoPtr->u.image)
1165 if (infoPtr->image_type == IMAGE_ICON)
1167 GetIconInfo(infoPtr->u.icon, &iconInfo);
1168 GetObjectW(iconInfo.hbmColor, sizeof(bm), &bm);
1169 DeleteObject(iconInfo.hbmColor);
1170 DeleteObject(iconInfo.hbmMask);
1172 else if (infoPtr->image_type == IMAGE_BITMAP)
1173 GetObjectW(infoPtr->u.bitmap, sizeof(bm), &bm);
1175 size.cx = bm.bmWidth;
1176 size.cy = bm.bmHeight;
1179 return size;
1182 static const RECT *BUTTON_GetTextMargin(const BUTTON_INFO *infoPtr)
1184 static const RECT oneMargin = {1, 1, 1, 1};
1186 /* Use text margin only when showing both image and text, and image is not imagelist */
1187 if (show_image_and_text(infoPtr) && !infoPtr->imagelist.himl)
1188 return &infoPtr->text_margin;
1189 else
1190 return &oneMargin;
1193 static void BUTTON_GetClientRectSize(BUTTON_INFO *infoPtr, SIZE *size)
1195 RECT rect;
1196 GetClientRect(infoPtr->hwnd, &rect);
1197 size->cx = rect.right - rect.left;
1198 size->cy = rect.bottom - rect.top;
1201 static void BUTTON_GetTextIdealSize(BUTTON_INFO *infoPtr, LONG maxWidth, SIZE *size)
1203 WCHAR *text = get_button_text(infoPtr);
1204 HDC hdc;
1205 RECT rect;
1206 const RECT *margin = BUTTON_GetTextMargin(infoPtr);
1208 if (maxWidth != 0)
1210 maxWidth -= margin->right + margin->right;
1211 if (maxWidth <= 0) maxWidth = 1;
1214 hdc = GetDC(infoPtr->hwnd);
1215 rect = BUTTON_GetTextRect(infoPtr, hdc, text, maxWidth);
1216 ReleaseDC(infoPtr->hwnd, hdc);
1217 heap_free(text);
1219 size->cx = rect.right - rect.left + margin->left + margin->right;
1220 size->cy = rect.bottom - rect.top + margin->top + margin->bottom;
1223 static void BUTTON_GetLabelIdealSize(BUTTON_INFO *infoPtr, LONG maxWidth, SIZE *size)
1225 LONG style = GetWindowLongW(infoPtr->hwnd, GWL_STYLE);
1226 SIZE imageSize;
1227 SIZE textSize;
1228 BOOL horizontal;
1230 imageSize = BUTTON_GetImageSize(infoPtr);
1231 if (infoPtr->imagelist.himl)
1233 imageSize.cx += infoPtr->imagelist.margin.left + infoPtr->imagelist.margin.right;
1234 imageSize.cy += infoPtr->imagelist.margin.top + infoPtr->imagelist.margin.bottom;
1235 if (infoPtr->imagelist.uAlign == BUTTON_IMAGELIST_ALIGN_TOP
1236 || infoPtr->imagelist.uAlign == BUTTON_IMAGELIST_ALIGN_BOTTOM)
1237 horizontal = FALSE;
1238 else
1239 horizontal = TRUE;
1241 else
1243 /* horizontal alignment flags has priority over vertical ones if both are specified */
1244 if (!(style & (BS_CENTER | BS_VCENTER)) || ((style & BS_CENTER) && (style & BS_CENTER) != BS_CENTER)
1245 || !(style & BS_VCENTER) || (style & BS_VCENTER) == BS_VCENTER)
1246 horizontal = TRUE;
1247 else
1248 horizontal = FALSE;
1251 if (horizontal)
1253 if (maxWidth != 0)
1255 maxWidth -= imageSize.cx;
1256 if (maxWidth <= 0) maxWidth = 1;
1258 BUTTON_GetTextIdealSize(infoPtr, maxWidth, &textSize);
1259 size->cx = textSize.cx + imageSize.cx;
1260 size->cy = max(textSize.cy, imageSize.cy);
1262 else
1264 BUTTON_GetTextIdealSize(infoPtr, maxWidth, &textSize);
1265 size->cx = max(textSize.cx, imageSize.cx);
1266 size->cy = textSize.cy + imageSize.cy;
1270 static BOOL GB_GetIdealSize(BUTTON_INFO *infoPtr, SIZE *size)
1272 BUTTON_GetClientRectSize(infoPtr, size);
1273 return TRUE;
1276 static BOOL CB_GetIdealSize(BUTTON_INFO *infoPtr, SIZE *size)
1278 LONG style = GetWindowLongW(infoPtr->hwnd, GWL_STYLE);
1279 HDC hdc;
1280 HFONT hfont;
1281 SIZE labelSize;
1282 INT textOffset;
1283 double scaleX;
1284 double scaleY;
1285 LONG checkboxWidth, checkboxHeight;
1286 LONG maxWidth = 0;
1288 if (SendMessageW(infoPtr->hwnd, WM_GETTEXTLENGTH, 0, 0) == 0)
1290 BUTTON_GetClientRectSize(infoPtr, size);
1291 return TRUE;
1294 hdc = GetDC(infoPtr->hwnd);
1295 scaleX = GetDeviceCaps(hdc, LOGPIXELSX) / 96.0;
1296 scaleY = GetDeviceCaps(hdc, LOGPIXELSY) / 96.0;
1297 if ((hfont = infoPtr->font)) SelectObject(hdc, hfont);
1298 GetCharWidthW(hdc, '0', '0', &textOffset);
1299 textOffset /= 2;
1300 ReleaseDC(infoPtr->hwnd, hdc);
1302 checkboxWidth = 12 * scaleX + 1;
1303 checkboxHeight = 12 * scaleY + 1;
1304 if (size->cx)
1306 maxWidth = size->cx - checkboxWidth - textOffset;
1307 if (maxWidth <= 0) maxWidth = 1;
1310 /* Checkbox doesn't support both image(but not image list) and text */
1311 if (!(style & (BS_ICON | BS_BITMAP)) && infoPtr->u.image)
1312 BUTTON_GetTextIdealSize(infoPtr, maxWidth, &labelSize);
1313 else
1314 BUTTON_GetLabelIdealSize(infoPtr, maxWidth, &labelSize);
1316 size->cx = labelSize.cx + checkboxWidth + textOffset;
1317 size->cy = max(labelSize.cy, checkboxHeight);
1319 return TRUE;
1322 static BOOL PB_GetIdealSize(BUTTON_INFO *infoPtr, SIZE *size)
1324 SIZE labelSize;
1326 if (SendMessageW(infoPtr->hwnd, WM_GETTEXTLENGTH, 0, 0) == 0)
1327 BUTTON_GetClientRectSize(infoPtr, size);
1328 else
1330 /* Ideal size include text size even if image only flags(BS_ICON, BS_BITMAP) are specified */
1331 BUTTON_GetLabelIdealSize(infoPtr, size->cx, &labelSize);
1333 size->cx = labelSize.cx;
1334 size->cy = labelSize.cy;
1336 return TRUE;
1339 static BOOL SB_GetIdealSize(BUTTON_INFO *infoPtr, SIZE *size)
1341 LONG extra_width = infoPtr->glyph_size.cx * 2 + GetSystemMetrics(SM_CXEDGE);
1342 SIZE label_size;
1344 if (SendMessageW(infoPtr->hwnd, WM_GETTEXTLENGTH, 0, 0) == 0)
1346 BUTTON_GetClientRectSize(infoPtr, size);
1347 size->cx = max(size->cx, extra_width);
1349 else
1351 BUTTON_GetLabelIdealSize(infoPtr, size->cx, &label_size);
1352 size->cx = label_size.cx + ((size->cx == 0) ? extra_width : 0);
1353 size->cy = label_size.cy;
1355 return TRUE;
1358 /**********************************************************************
1359 * BUTTON_CalcLayoutRects
1361 * Calculates the rectangles of the button label(image and text) and its parts depending on a button's style.
1363 * Returns flags to be passed to DrawText.
1364 * Calculated rectangle doesn't take into account button state
1365 * (pushed, etc.). If there is nothing to draw (no text/image) output
1366 * rectangle is empty, and return value is (UINT)-1.
1368 * PARAMS:
1369 * infoPtr [I] Button pointer
1370 * hdc [I] Handle to device context to draw to
1371 * labelRc [I/O] Input the rect the label to be positioned in, and output the label rect
1372 * imageRc [O] Optional, output the image rect
1373 * textRc [O] Optional, output the text rect
1375 static UINT BUTTON_CalcLayoutRects(const BUTTON_INFO *infoPtr, HDC hdc, RECT *labelRc, RECT *imageRc, RECT *textRc)
1377 LONG style = GetWindowLongW( infoPtr->hwnd, GWL_STYLE );
1378 LONG ex_style = GetWindowLongW( infoPtr->hwnd, GWL_EXSTYLE );
1379 LONG split_style = infoPtr->imagelist.himl ? BUTTON_ILStoBS(infoPtr->imagelist.uAlign) : style;
1380 WCHAR *text = get_button_text(infoPtr);
1381 SIZE imageSize = BUTTON_GetImageSize(infoPtr);
1382 UINT dtStyle = BUTTON_BStoDT(style, ex_style);
1383 RECT labelRect, imageRect, imageRectWithMargin, textRect;
1384 LONG imageMarginWidth, imageMarginHeight;
1385 const RECT *textMargin = BUTTON_GetTextMargin(infoPtr);
1386 RECT emptyMargin = {0};
1387 LONG maxTextWidth;
1389 /* Calculate label rectangle according to label type */
1390 if ((imageSize.cx == 0 && imageSize.cy == 0) && (text == NULL || text[0] == '\0'))
1392 SetRectEmpty(labelRc);
1393 SetRectEmpty(imageRc);
1394 SetRectEmpty(textRc);
1395 heap_free(text);
1396 return (UINT)-1;
1399 SetRect(&imageRect, 0, 0, imageSize.cx, imageSize.cy);
1400 imageRectWithMargin = imageRect;
1401 if (infoPtr->imagelist.himl)
1403 imageRectWithMargin.top -= infoPtr->imagelist.margin.top;
1404 imageRectWithMargin.bottom += infoPtr->imagelist.margin.bottom;
1405 imageRectWithMargin.left -= infoPtr->imagelist.margin.left;
1406 imageRectWithMargin.right += infoPtr->imagelist.margin.right;
1409 /* Show image only */
1410 if (show_image_only(infoPtr))
1412 BUTTON_PositionRect(style, labelRc, &imageRect,
1413 infoPtr->imagelist.himl ? &infoPtr->imagelist.margin : &emptyMargin);
1414 labelRect = imageRect;
1415 SetRectEmpty(&textRect);
1417 else
1419 /* Get text rect */
1420 maxTextWidth = labelRc->right - labelRc->left;
1421 textRect = BUTTON_GetTextRect(infoPtr, hdc, text, maxTextWidth);
1423 /* Show image and text */
1424 if (show_image_and_text(infoPtr))
1426 RECT boundingLabelRect, boundingImageRect, boundingTextRect;
1428 /* Get label rect */
1429 /* Image list may have different alignment than the button, use the whole rect for label in this case */
1430 if (infoPtr->imagelist.himl)
1431 labelRect = *labelRc;
1432 else
1434 /* Get a label bounding rectangle to position the label in the user specified label rectangle because
1435 * text and image need to align together. */
1436 boundingLabelRect = BUTTON_GetBoundingLabelRect(split_style, &textRect, &imageRectWithMargin);
1437 BUTTON_PositionRect(split_style, labelRc, &boundingLabelRect, &emptyMargin);
1438 labelRect = boundingLabelRect;
1441 /* When imagelist has center align, use the whole rect for imagelist and text */
1442 if(infoPtr->imagelist.himl && infoPtr->imagelist.uAlign == BUTTON_IMAGELIST_ALIGN_CENTER)
1444 boundingImageRect = labelRect;
1445 boundingTextRect = labelRect;
1446 BUTTON_PositionRect(split_style, &boundingImageRect, &imageRect,
1447 infoPtr->imagelist.himl ? &infoPtr->imagelist.margin : &emptyMargin);
1448 /* Text doesn't use imagelist align */
1449 BUTTON_PositionRect(style, &boundingTextRect, &textRect, textMargin);
1451 else
1453 /* Get image rect */
1454 /* Split the label rect to two halves as two bounding rectangles for image and text */
1455 boundingImageRect = labelRect;
1456 imageMarginWidth = imageRectWithMargin.right - imageRectWithMargin.left;
1457 imageMarginHeight = imageRectWithMargin.bottom - imageRectWithMargin.top;
1458 if ((split_style & BS_CENTER) == BS_RIGHT)
1459 boundingImageRect.left = boundingImageRect.right - imageMarginWidth;
1460 else if ((split_style & BS_CENTER) == BS_LEFT)
1461 boundingImageRect.right = boundingImageRect.left + imageMarginWidth;
1462 else if ((split_style & BS_VCENTER) == BS_BOTTOM)
1463 boundingImageRect.top = boundingImageRect.bottom - imageMarginHeight;
1464 else if ((split_style & BS_VCENTER) == BS_TOP)
1465 boundingImageRect.bottom = boundingImageRect.top + imageMarginHeight;
1466 else
1467 boundingImageRect.right = boundingImageRect.left + imageMarginWidth;
1468 BUTTON_PositionRect(split_style, &boundingImageRect, &imageRect,
1469 infoPtr->imagelist.himl ? &infoPtr->imagelist.margin : &emptyMargin);
1471 /* Get text rect */
1472 SubtractRect(&boundingTextRect, &labelRect, &boundingImageRect);
1473 /* Text doesn't use imagelist align */
1474 BUTTON_PositionRect(style, &boundingTextRect, &textRect, textMargin);
1477 /* Show text only */
1478 else
1480 if (get_button_type(style) != BS_GROUPBOX)
1481 BUTTON_PositionRect(style, labelRc, &textRect, textMargin);
1482 else
1483 /* GroupBox is always top aligned */
1484 BUTTON_PositionRect((style & ~BS_VCENTER) | BS_TOP, labelRc, &textRect, textMargin);
1485 labelRect = textRect;
1486 SetRectEmpty(&imageRect);
1489 heap_free(text);
1491 CopyRect(labelRc, &labelRect);
1492 CopyRect(imageRc, &imageRect);
1493 CopyRect(textRc, &textRect);
1495 return dtStyle;
1499 /**********************************************************************
1500 * BUTTON_DrawTextCallback
1502 * Callback function used by DrawStateW function.
1504 static BOOL CALLBACK BUTTON_DrawTextCallback(HDC hdc, LPARAM lp, WPARAM wp, int cx, int cy)
1506 RECT rc;
1508 SetRect(&rc, 0, 0, cx, cy);
1509 DrawTextW(hdc, (LPCWSTR)lp, -1, &rc, (UINT)wp);
1510 return TRUE;
1513 /**********************************************************************
1514 * BUTTON_DrawLabel
1516 * Common function for drawing button label.
1518 * FIXME:
1519 * 1. When BS_SINGLELINE is specified and text contains '\t', '\n' or '\r' in the middle, they are rendered as
1520 * squares now whereas they should be ignored.
1521 * 2. When BS_MULTILINE is specified and text contains space in the middle, the space mistakenly be rendered as newline.
1523 static void BUTTON_DrawLabel(const BUTTON_INFO *infoPtr, HDC hdc, UINT dtFlags, const RECT *imageRect,
1524 const RECT *textRect)
1526 DRAWSTATEPROC lpOutputProc = NULL;
1527 LPARAM lp;
1528 WPARAM wp = 0;
1529 HBRUSH hbr = 0;
1530 UINT flags = IsWindowEnabled(infoPtr->hwnd) ? DSS_NORMAL : DSS_DISABLED;
1531 UINT imageFlags;
1532 LONG state = infoPtr->state;
1533 LONG draw_state;
1534 LONG style = GetWindowLongW( infoPtr->hwnd, GWL_STYLE );
1535 WCHAR *text = NULL;
1537 /* FIXME: To draw disabled label in Win31 look-and-feel, we probably
1538 * must use DSS_MONO flag and COLOR_GRAYTEXT brush (or maybe DSS_UNION).
1539 * I don't have Win31 on hand to verify that, so I leave it as is.
1542 if ((style & BS_PUSHLIKE) && (state & BST_INDETERMINATE))
1544 hbr = GetSysColorBrush(COLOR_GRAYTEXT);
1545 flags |= DSS_MONO;
1548 if (show_image(infoPtr))
1550 if (infoPtr->imagelist.himl)
1552 if (ImageList_GetImageCount(infoPtr->imagelist.himl) == 1)
1553 ImageList_Draw(infoPtr->imagelist.himl, 0, hdc, imageRect->left, imageRect->top, ILD_NORMAL);
1554 else
1556 draw_state = get_draw_state(infoPtr);
1557 ImageList_Draw(infoPtr->imagelist.himl, draw_state - 1, hdc, imageRect->left, imageRect->top,
1558 ILD_NORMAL);
1561 else
1563 switch (infoPtr->image_type)
1565 case IMAGE_ICON:
1566 imageFlags = flags | DST_ICON;
1567 lp = (LPARAM)infoPtr->u.icon;
1568 break;
1569 case IMAGE_BITMAP:
1570 imageFlags = flags | DST_BITMAP;
1571 lp = (LPARAM)infoPtr->u.bitmap;
1572 break;
1573 default:
1574 return;
1577 DrawStateW(hdc, hbr, lpOutputProc, lp, wp, imageRect->left, imageRect->top,
1578 imageRect->right - imageRect->left, imageRect->bottom - imageRect->top, imageFlags);
1582 if (show_image_only(infoPtr)) return;
1584 /* DST_COMPLEX -- is 0 */
1585 lpOutputProc = BUTTON_DrawTextCallback;
1586 if (!(text = get_button_text(infoPtr))) return;
1587 lp = (LPARAM)text;
1588 wp = dtFlags;
1589 DrawStateW(hdc, hbr, lpOutputProc, lp, wp, textRect->left, textRect->top, textRect->right - textRect->left,
1590 textRect->bottom - textRect->top, flags);
1591 heap_free(text);
1594 /**********************************************************************
1595 * Push Button Functions
1597 static void PB_Paint( const BUTTON_INFO *infoPtr, HDC hDC, UINT action )
1599 RECT rc, labelRect, imageRect, textRect;
1600 UINT dtFlags, uState;
1601 HPEN hOldPen, hpen;
1602 HBRUSH hOldBrush;
1603 INT oldBkMode;
1604 COLORREF oldTxtColor;
1605 LRESULT cdrf;
1606 HFONT hFont;
1607 NMCUSTOMDRAW nmcd;
1608 LONG state = infoPtr->state;
1609 LONG style = GetWindowLongW( infoPtr->hwnd, GWL_STYLE );
1610 BOOL pushedState = (state & BST_PUSHED);
1611 HWND parent;
1612 HRGN hrgn;
1614 GetClientRect( infoPtr->hwnd, &rc );
1616 /* Send WM_CTLCOLOR to allow changing the font (the colors are fixed) */
1617 if ((hFont = infoPtr->font)) SelectObject( hDC, hFont );
1618 parent = GetParent(infoPtr->hwnd);
1619 if (!parent) parent = infoPtr->hwnd;
1620 SendMessageW( parent, WM_CTLCOLORBTN, (WPARAM)hDC, (LPARAM)infoPtr->hwnd );
1622 hrgn = set_control_clipping( hDC, &rc );
1624 hpen = CreatePen( PS_SOLID, 1, GetSysColor(COLOR_WINDOWFRAME));
1625 hOldPen = SelectObject(hDC, hpen);
1626 hOldBrush = SelectObject(hDC,GetSysColorBrush(COLOR_BTNFACE));
1627 oldBkMode = SetBkMode(hDC, TRANSPARENT);
1629 init_custom_draw(&nmcd, infoPtr, hDC, &rc);
1631 /* Send erase notifications */
1632 cdrf = SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
1633 if (cdrf & CDRF_SKIPDEFAULT) goto cleanup;
1635 if (get_button_type(style) == BS_DEFPUSHBUTTON)
1637 if (action != ODA_FOCUS)
1638 Rectangle(hDC, rc.left, rc.top, rc.right, rc.bottom);
1639 InflateRect( &rc, -1, -1 );
1642 /* Skip the frame drawing if only focus has changed */
1643 if (action != ODA_FOCUS)
1645 uState = DFCS_BUTTONPUSH;
1647 if (style & BS_FLAT)
1648 uState |= DFCS_MONO;
1649 else if (pushedState)
1651 if (get_button_type(style) == BS_DEFPUSHBUTTON )
1652 uState |= DFCS_FLAT;
1653 else
1654 uState |= DFCS_PUSHED;
1657 if (state & (BST_CHECKED | BST_INDETERMINATE))
1658 uState |= DFCS_CHECKED;
1660 DrawFrameControl( hDC, &rc, DFC_BUTTON, uState );
1663 if (cdrf & CDRF_NOTIFYPOSTERASE)
1665 nmcd.dwDrawStage = CDDS_POSTERASE;
1666 SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
1669 /* Send paint notifications */
1670 nmcd.dwDrawStage = CDDS_PREPAINT;
1671 cdrf = SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
1672 if (cdrf & CDRF_SKIPDEFAULT) goto cleanup;
1674 if (!(cdrf & CDRF_DOERASE) && action != ODA_FOCUS)
1676 /* draw button label */
1677 labelRect = rc;
1678 /* Shrink label rect at all sides by 2 so that the content won't touch the surrounding frame */
1679 InflateRect(&labelRect, -2, -2);
1680 dtFlags = BUTTON_CalcLayoutRects(infoPtr, hDC, &labelRect, &imageRect, &textRect);
1682 if (dtFlags != (UINT)-1L)
1684 if (pushedState) OffsetRect(&labelRect, 1, 1);
1686 oldTxtColor = SetTextColor( hDC, GetSysColor(COLOR_BTNTEXT) );
1688 BUTTON_DrawLabel(infoPtr, hDC, dtFlags, &imageRect, &textRect);
1690 SetTextColor( hDC, oldTxtColor );
1694 if (cdrf & CDRF_NOTIFYPOSTPAINT)
1696 nmcd.dwDrawStage = CDDS_POSTPAINT;
1697 SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
1699 if ((cdrf & CDRF_SKIPPOSTPAINT) || dtFlags == (UINT)-1L) goto cleanup;
1701 if (action == ODA_FOCUS || (state & BST_FOCUS))
1703 InflateRect( &rc, -2, -2 );
1704 DrawFocusRect( hDC, &rc );
1707 cleanup:
1708 SelectObject( hDC, hOldPen );
1709 SelectObject( hDC, hOldBrush );
1710 SetBkMode(hDC, oldBkMode);
1711 SelectClipRgn( hDC, hrgn );
1712 if (hrgn) DeleteObject( hrgn );
1713 DeleteObject( hpen );
1716 /**********************************************************************
1717 * Check Box & Radio Button Functions
1720 static void CB_Paint( const BUTTON_INFO *infoPtr, HDC hDC, UINT action )
1722 RECT rbox, labelRect, imageRect, textRect, client;
1723 HBRUSH hBrush;
1724 int delta, text_offset, checkBoxWidth, checkBoxHeight;
1725 UINT dtFlags;
1726 LRESULT cdrf;
1727 HFONT hFont;
1728 NMCUSTOMDRAW nmcd;
1729 LONG state = infoPtr->state;
1730 LONG style = GetWindowLongW( infoPtr->hwnd, GWL_STYLE );
1731 LONG ex_style = GetWindowLongW( infoPtr->hwnd, GWL_EXSTYLE );
1732 HWND parent;
1733 HRGN hrgn;
1735 if (style & BS_PUSHLIKE)
1737 PB_Paint( infoPtr, hDC, action );
1738 return;
1741 GetClientRect(infoPtr->hwnd, &client);
1742 rbox = labelRect = client;
1744 checkBoxWidth = 12 * GetDpiForWindow( infoPtr->hwnd ) / 96 + 1;
1745 checkBoxHeight = 12 * GetDpiForWindow( infoPtr->hwnd ) / 96 + 1;
1747 if ((hFont = infoPtr->font)) SelectObject( hDC, hFont );
1748 GetCharWidthW( hDC, '0', '0', &text_offset );
1749 text_offset /= 2;
1751 parent = GetParent(infoPtr->hwnd);
1752 if (!parent) parent = infoPtr->hwnd;
1753 hBrush = (HBRUSH)SendMessageW(parent, WM_CTLCOLORSTATIC, (WPARAM)hDC, (LPARAM)infoPtr->hwnd);
1754 if (!hBrush) /* did the app forget to call defwindowproc ? */
1755 hBrush = (HBRUSH)DefWindowProcW(parent, WM_CTLCOLORSTATIC, (WPARAM)hDC, (LPARAM)infoPtr->hwnd);
1756 hrgn = set_control_clipping( hDC, &client );
1758 if (style & BS_LEFTTEXT || ex_style & WS_EX_RIGHT)
1760 labelRect.right -= checkBoxWidth + text_offset;
1761 rbox.left = rbox.right - checkBoxWidth;
1763 else
1765 labelRect.left += checkBoxWidth + text_offset;
1766 rbox.right = checkBoxWidth;
1769 init_custom_draw(&nmcd, infoPtr, hDC, &client);
1771 /* Send erase notifications */
1772 cdrf = SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
1773 if (cdrf & CDRF_SKIPDEFAULT) goto cleanup;
1775 /* Since WM_ERASEBKGND does nothing, first prepare background */
1776 if (action == ODA_SELECT) FillRect( hDC, &rbox, hBrush );
1777 if (action == ODA_DRAWENTIRE) FillRect( hDC, &client, hBrush );
1778 if (cdrf & CDRF_NOTIFYPOSTERASE)
1780 nmcd.dwDrawStage = CDDS_POSTERASE;
1781 SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
1784 /* Draw label */
1785 client = labelRect;
1786 dtFlags = BUTTON_CalcLayoutRects(infoPtr, hDC, &labelRect, &imageRect, &textRect);
1788 /* Only adjust rbox when rtext is valid */
1789 if (dtFlags != (UINT)-1L)
1791 rbox.top = labelRect.top;
1792 rbox.bottom = labelRect.bottom;
1795 /* Send paint notifications */
1796 nmcd.dwDrawStage = CDDS_PREPAINT;
1797 cdrf = SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
1798 if (cdrf & CDRF_SKIPDEFAULT) goto cleanup;
1800 /* Draw the check-box bitmap */
1801 if (!(cdrf & CDRF_DOERASE))
1803 if (action == ODA_DRAWENTIRE || action == ODA_SELECT)
1805 UINT flags;
1807 if ((get_button_type(style) == BS_RADIOBUTTON) ||
1808 (get_button_type(style) == BS_AUTORADIOBUTTON)) flags = DFCS_BUTTONRADIO;
1809 else if (state & BST_INDETERMINATE) flags = DFCS_BUTTON3STATE;
1810 else flags = DFCS_BUTTONCHECK;
1812 if (state & (BST_CHECKED | BST_INDETERMINATE)) flags |= DFCS_CHECKED;
1813 if (state & BST_PUSHED) flags |= DFCS_PUSHED;
1814 if (style & WS_DISABLED) flags |= DFCS_INACTIVE;
1816 /* rbox must have the correct height */
1817 delta = rbox.bottom - rbox.top - checkBoxHeight;
1819 if ((style & BS_VCENTER) == BS_TOP)
1821 if (delta > 0)
1822 rbox.bottom = rbox.top + checkBoxHeight;
1823 else
1825 rbox.top -= -delta / 2 + 1;
1826 rbox.bottom = rbox.top + checkBoxHeight;
1829 else if ((style & BS_VCENTER) == BS_BOTTOM)
1831 if (delta > 0)
1832 rbox.top = rbox.bottom - checkBoxHeight;
1833 else
1835 rbox.bottom += -delta / 2 + 1;
1836 rbox.top = rbox.bottom - checkBoxHeight;
1839 else /* Default */
1841 if (delta > 0)
1843 int ofs = delta / 2;
1844 rbox.bottom -= ofs + 1;
1845 rbox.top = rbox.bottom - checkBoxHeight;
1847 else if (delta < 0)
1849 int ofs = -delta / 2;
1850 rbox.top -= ofs + 1;
1851 rbox.bottom = rbox.top + checkBoxHeight;
1855 DrawFrameControl(hDC, &rbox, DFC_BUTTON, flags);
1858 if (dtFlags != (UINT)-1L) /* Something to draw */
1859 if (action == ODA_DRAWENTIRE) BUTTON_DrawLabel(infoPtr, hDC, dtFlags, &imageRect, &textRect);
1862 if (cdrf & CDRF_NOTIFYPOSTPAINT)
1864 nmcd.dwDrawStage = CDDS_POSTPAINT;
1865 SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
1867 if ((cdrf & CDRF_SKIPPOSTPAINT) || dtFlags == (UINT)-1L) goto cleanup;
1869 /* ... and focus */
1870 if (action == ODA_FOCUS || (state & BST_FOCUS))
1872 labelRect.left--;
1873 labelRect.right++;
1874 IntersectRect(&labelRect, &labelRect, &client);
1875 DrawFocusRect(hDC, &labelRect);
1878 cleanup:
1879 SelectClipRgn( hDC, hrgn );
1880 if (hrgn) DeleteObject( hrgn );
1884 /**********************************************************************
1885 * BUTTON_CheckAutoRadioButton
1887 * hwnd is checked, uncheck every other auto radio button in group
1889 static void BUTTON_CheckAutoRadioButton( HWND hwnd )
1891 HWND parent, sibling, start;
1893 parent = GetParent(hwnd);
1894 /* make sure that starting control is not disabled or invisible */
1895 start = sibling = GetNextDlgGroupItem( parent, hwnd, TRUE );
1898 if (!sibling) break;
1899 if ((hwnd != sibling) &&
1900 ((GetWindowLongW( sibling, GWL_STYLE) & BS_TYPEMASK) == BS_AUTORADIOBUTTON))
1901 SendMessageW( sibling, BM_SETCHECK, BST_UNCHECKED, 0 );
1902 sibling = GetNextDlgGroupItem( parent, sibling, FALSE );
1903 } while (sibling != start);
1907 /**********************************************************************
1908 * Group Box Functions
1911 static void GB_Paint( const BUTTON_INFO *infoPtr, HDC hDC, UINT action )
1913 RECT labelRect, imageRect, textRect, rcFrame;
1914 HBRUSH hbr;
1915 HFONT hFont;
1916 UINT dtFlags;
1917 TEXTMETRICW tm;
1918 LONG style = GetWindowLongW( infoPtr->hwnd, GWL_STYLE );
1919 HWND parent;
1920 HRGN hrgn;
1922 if ((hFont = infoPtr->font)) SelectObject( hDC, hFont );
1923 /* GroupBox acts like static control, so it sends CTLCOLORSTATIC */
1924 parent = GetParent(infoPtr->hwnd);
1925 if (!parent) parent = infoPtr->hwnd;
1926 hbr = (HBRUSH)SendMessageW(parent, WM_CTLCOLORSTATIC, (WPARAM)hDC, (LPARAM)infoPtr->hwnd);
1927 if (!hbr) /* did the app forget to call defwindowproc ? */
1928 hbr = (HBRUSH)DefWindowProcW(parent, WM_CTLCOLORSTATIC, (WPARAM)hDC, (LPARAM)infoPtr->hwnd);
1929 GetClientRect(infoPtr->hwnd, &labelRect);
1930 rcFrame = labelRect;
1931 hrgn = set_control_clipping(hDC, &labelRect);
1933 GetTextMetricsW (hDC, &tm);
1934 rcFrame.top += (tm.tmHeight / 2) - 1;
1935 DrawEdge (hDC, &rcFrame, EDGE_ETCHED, BF_RECT | ((style & BS_FLAT) ? BF_FLAT : 0));
1937 InflateRect(&labelRect, -7, 1);
1938 dtFlags = BUTTON_CalcLayoutRects(infoPtr, hDC, &labelRect, &imageRect, &textRect);
1940 if (dtFlags != (UINT)-1)
1942 /* Because buttons have CS_PARENTDC class style, there is a chance
1943 * that label will be drawn out of client rect.
1944 * But Windows doesn't clip label's rect, so do I.
1947 /* There is 1-pixel margin at the left, right, and bottom */
1948 labelRect.left--;
1949 labelRect.right++;
1950 labelRect.bottom++;
1951 FillRect(hDC, &labelRect, hbr);
1952 labelRect.left++;
1953 labelRect.right--;
1954 labelRect.bottom--;
1956 BUTTON_DrawLabel(infoPtr, hDC, dtFlags, &imageRect, &textRect);
1958 SelectClipRgn( hDC, hrgn );
1959 if (hrgn) DeleteObject( hrgn );
1963 /**********************************************************************
1964 * User Button Functions
1967 static void UB_Paint( const BUTTON_INFO *infoPtr, HDC hDC, UINT action )
1969 RECT rc;
1970 HBRUSH hBrush;
1971 LRESULT cdrf;
1972 HFONT hFont;
1973 NMCUSTOMDRAW nmcd;
1974 LONG state = infoPtr->state;
1975 HWND parent;
1977 GetClientRect( infoPtr->hwnd, &rc);
1979 if ((hFont = infoPtr->font)) SelectObject( hDC, hFont );
1981 parent = GetParent(infoPtr->hwnd);
1982 if (!parent) parent = infoPtr->hwnd;
1983 hBrush = (HBRUSH)SendMessageW(parent, WM_CTLCOLORBTN, (WPARAM)hDC, (LPARAM)infoPtr->hwnd);
1984 if (!hBrush) /* did the app forget to call defwindowproc ? */
1985 hBrush = (HBRUSH)DefWindowProcW(parent, WM_CTLCOLORBTN, (WPARAM)hDC, (LPARAM)infoPtr->hwnd);
1987 if (action == ODA_FOCUS || (state & BST_FOCUS))
1989 init_custom_draw(&nmcd, infoPtr, hDC, &rc);
1991 /* Send erase notifications */
1992 cdrf = SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
1993 if (cdrf & CDRF_SKIPDEFAULT) goto notify;
1996 FillRect( hDC, &rc, hBrush );
1997 if (action == ODA_FOCUS || (state & BST_FOCUS))
1999 if (cdrf & CDRF_NOTIFYPOSTERASE)
2001 nmcd.dwDrawStage = CDDS_POSTERASE;
2002 SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
2005 /* Send paint notifications */
2006 nmcd.dwDrawStage = CDDS_PREPAINT;
2007 cdrf = SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
2008 if (cdrf & CDRF_SKIPDEFAULT) goto notify;
2009 if (cdrf & CDRF_NOTIFYPOSTPAINT)
2011 nmcd.dwDrawStage = CDDS_POSTPAINT;
2012 SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
2015 if (!(cdrf & CDRF_SKIPPOSTPAINT))
2016 DrawFocusRect( hDC, &rc );
2019 notify:
2020 switch (action)
2022 case ODA_FOCUS:
2023 BUTTON_NOTIFY_PARENT( infoPtr->hwnd, (state & BST_FOCUS) ? BN_SETFOCUS : BN_KILLFOCUS );
2024 break;
2026 case ODA_SELECT:
2027 BUTTON_NOTIFY_PARENT( infoPtr->hwnd, (state & BST_PUSHED) ? BN_HILITE : BN_UNHILITE );
2028 break;
2030 default:
2031 break;
2036 /**********************************************************************
2037 * Ownerdrawn Button Functions
2040 static void OB_Paint( const BUTTON_INFO *infoPtr, HDC hDC, UINT action )
2042 LONG state = infoPtr->state;
2043 DRAWITEMSTRUCT dis;
2044 LONG_PTR id = GetWindowLongPtrW( infoPtr->hwnd, GWLP_ID );
2045 HWND parent;
2046 HFONT hFont;
2047 HRGN hrgn;
2049 dis.CtlType = ODT_BUTTON;
2050 dis.CtlID = id;
2051 dis.itemID = 0;
2052 dis.itemAction = action;
2053 dis.itemState = ((state & BST_FOCUS) ? ODS_FOCUS : 0) |
2054 ((state & BST_PUSHED) ? ODS_SELECTED : 0) |
2055 (IsWindowEnabled(infoPtr->hwnd) ? 0: ODS_DISABLED);
2056 dis.hwndItem = infoPtr->hwnd;
2057 dis.hDC = hDC;
2058 dis.itemData = 0;
2059 GetClientRect( infoPtr->hwnd, &dis.rcItem );
2061 if ((hFont = infoPtr->font)) SelectObject( hDC, hFont );
2062 parent = GetParent(infoPtr->hwnd);
2063 if (!parent) parent = infoPtr->hwnd;
2064 SendMessageW( parent, WM_CTLCOLORBTN, (WPARAM)hDC, (LPARAM)infoPtr->hwnd );
2066 hrgn = set_control_clipping( hDC, &dis.rcItem );
2068 SendMessageW( GetParent(infoPtr->hwnd), WM_DRAWITEM, id, (LPARAM)&dis );
2069 SelectClipRgn( hDC, hrgn );
2070 if (hrgn) DeleteObject( hrgn );
2074 /**********************************************************************
2075 * Split Button Functions
2077 static void SB_Paint( const BUTTON_INFO *infoPtr, HDC hDC, UINT action )
2079 LONG style = GetWindowLongW(infoPtr->hwnd, GWL_STYLE);
2080 LONG state = infoPtr->state;
2081 UINT dtFlags = (UINT)-1L;
2083 RECT rc, push_rect, dropdown_rect;
2084 NMCUSTOMDRAW nmcd;
2085 HPEN pen, old_pen;
2086 HBRUSH old_brush;
2087 INT old_bk_mode;
2088 LRESULT cdrf;
2089 HWND parent;
2090 HRGN hrgn;
2092 GetClientRect(infoPtr->hwnd, &rc);
2094 /* Send WM_CTLCOLOR to allow changing the font (the colors are fixed) */
2095 if (infoPtr->font) SelectObject(hDC, infoPtr->font);
2096 if (!(parent = GetParent(infoPtr->hwnd))) parent = infoPtr->hwnd;
2097 SendMessageW(parent, WM_CTLCOLORBTN, (WPARAM)hDC, (LPARAM)infoPtr->hwnd);
2099 hrgn = set_control_clipping(hDC, &rc);
2101 pen = CreatePen(PS_SOLID, 1, GetSysColor(COLOR_WINDOWFRAME));
2102 old_pen = SelectObject(hDC, pen);
2103 old_brush = SelectObject(hDC, GetSysColorBrush(COLOR_BTNFACE));
2104 old_bk_mode = SetBkMode(hDC, TRANSPARENT);
2106 init_custom_draw(&nmcd, infoPtr, hDC, &rc);
2108 /* Send erase notifications */
2109 cdrf = SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
2110 if (cdrf & CDRF_SKIPDEFAULT) goto cleanup;
2112 if (get_button_type(style) == BS_DEFSPLITBUTTON)
2114 if (action != ODA_FOCUS)
2115 Rectangle(hDC, rc.left, rc.top, rc.right, rc.bottom);
2116 InflateRect(&rc, -1, -1);
2117 /* The split will now be off by 1 pixel, but
2118 that's exactly what Windows does as well */
2121 get_split_button_rects(infoPtr, &rc, &push_rect, &dropdown_rect);
2122 if (infoPtr->split_style & BCSS_NOSPLIT)
2123 push_rect = rc;
2125 /* Skip the frame drawing if only focus has changed */
2126 if (action != ODA_FOCUS)
2128 UINT flags = DFCS_BUTTONPUSH;
2130 if (style & BS_FLAT) flags |= DFCS_MONO;
2131 else if (state & BST_PUSHED)
2132 flags |= (get_button_type(style) == BS_DEFSPLITBUTTON)
2133 ? DFCS_FLAT : DFCS_PUSHED;
2135 if (state & (BST_CHECKED | BST_INDETERMINATE))
2136 flags |= DFCS_CHECKED;
2138 if (infoPtr->split_style & BCSS_NOSPLIT)
2139 DrawFrameControl(hDC, &push_rect, DFC_BUTTON, flags);
2140 else
2142 UINT dropdown_flags = flags & ~DFCS_CHECKED;
2144 if (state & BST_DROPDOWNPUSHED)
2145 dropdown_flags = (dropdown_flags & ~DFCS_FLAT) | DFCS_PUSHED;
2147 /* Adjust for shadow and draw order so it looks properly */
2148 if (infoPtr->split_style & BCSS_ALIGNLEFT)
2150 dropdown_rect.right++;
2151 DrawFrameControl(hDC, &dropdown_rect, DFC_BUTTON, dropdown_flags);
2152 dropdown_rect.right--;
2153 DrawFrameControl(hDC, &push_rect, DFC_BUTTON, flags);
2155 else
2157 push_rect.right++;
2158 DrawFrameControl(hDC, &push_rect, DFC_BUTTON, flags);
2159 push_rect.right--;
2160 DrawFrameControl(hDC, &dropdown_rect, DFC_BUTTON, dropdown_flags);
2165 if (cdrf & CDRF_NOTIFYPOSTERASE)
2167 nmcd.dwDrawStage = CDDS_POSTERASE;
2168 SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
2171 /* Send paint notifications */
2172 nmcd.dwDrawStage = CDDS_PREPAINT;
2173 cdrf = SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
2174 if (cdrf & CDRF_SKIPDEFAULT) goto cleanup;
2176 /* Shrink push button rect so that the content won't touch the surrounding frame */
2177 InflateRect(&push_rect, -2, -2);
2179 if (!(cdrf & CDRF_DOERASE) && action != ODA_FOCUS)
2181 COLORREF old_color = SetTextColor(hDC, GetSysColor(COLOR_BTNTEXT));
2182 RECT label_rect = push_rect, image_rect, text_rect;
2184 dtFlags = BUTTON_CalcLayoutRects(infoPtr, hDC, &label_rect, &image_rect, &text_rect);
2186 if (dtFlags != (UINT)-1L)
2187 BUTTON_DrawLabel(infoPtr, hDC, dtFlags, &image_rect, &text_rect);
2189 draw_split_button_dropdown_glyph(infoPtr, hDC, &dropdown_rect);
2190 SetTextColor(hDC, old_color);
2193 if (cdrf & CDRF_NOTIFYPOSTPAINT)
2195 nmcd.dwDrawStage = CDDS_POSTPAINT;
2196 SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
2198 if ((cdrf & CDRF_SKIPPOSTPAINT) || dtFlags == (UINT)-1L) goto cleanup;
2200 if (action == ODA_FOCUS || (state & BST_FOCUS))
2201 DrawFocusRect(hDC, &push_rect);
2203 cleanup:
2204 SelectObject(hDC, old_pen);
2205 SelectObject(hDC, old_brush);
2206 SetBkMode(hDC, old_bk_mode);
2207 SelectClipRgn(hDC, hrgn);
2208 if (hrgn) DeleteObject(hrgn);
2209 DeleteObject(pen);
2212 /* Given the full button rect of the split button, retrieve the push part and the dropdown part */
2213 static inline void get_split_button_rects(const BUTTON_INFO *infoPtr, const RECT *button_rect,
2214 RECT *push_rect, RECT *dropdown_rect)
2216 *push_rect = *dropdown_rect = *button_rect;
2218 /* The dropdown takes priority if the client rect is too small, it will only have a dropdown */
2219 if (infoPtr->split_style & BCSS_ALIGNLEFT)
2221 dropdown_rect->right = min(button_rect->left + infoPtr->glyph_size.cx, button_rect->right);
2222 push_rect->left = dropdown_rect->right;
2224 else
2226 dropdown_rect->left = max(button_rect->right - infoPtr->glyph_size.cx, button_rect->left);
2227 push_rect->right = dropdown_rect->left;
2231 /* Notify the parent if the point is within the dropdown and return TRUE (always notify if NULL) */
2232 static BOOL notify_split_button_dropdown(const BUTTON_INFO *infoPtr, const POINT *pt, HWND hwnd)
2234 NMBCDROPDOWN nmbcd;
2236 GetClientRect(hwnd, &nmbcd.rcButton);
2237 if (pt)
2239 RECT push_rect, dropdown_rect;
2241 get_split_button_rects(infoPtr, &nmbcd.rcButton, &push_rect, &dropdown_rect);
2242 if (!PtInRect(&dropdown_rect, *pt))
2243 return FALSE;
2245 /* If it's already down (set manually via BCM_SETDROPDOWNSTATE), fake the notify */
2246 if (infoPtr->state & BST_DROPDOWNPUSHED)
2247 return TRUE;
2249 SendMessageW(hwnd, BCM_SETDROPDOWNSTATE, TRUE, 0);
2251 nmbcd.hdr.hwndFrom = hwnd;
2252 nmbcd.hdr.idFrom = GetWindowLongPtrW(hwnd, GWLP_ID);
2253 nmbcd.hdr.code = BCN_DROPDOWN;
2254 SendMessageW(GetParent(hwnd), WM_NOTIFY, nmbcd.hdr.idFrom, (LPARAM)&nmbcd);
2256 SendMessageW(hwnd, BCM_SETDROPDOWNSTATE, FALSE, 0);
2257 return TRUE;
2260 /* Draw the split button dropdown glyph or image */
2261 static void draw_split_button_dropdown_glyph(const BUTTON_INFO *infoPtr, HDC hdc, RECT *rect)
2263 if (infoPtr->split_style & BCSS_IMAGE)
2265 int w, h;
2267 /* When the glyph is an image list, Windows is very buggy with BCSS_STRETCH,
2268 positions it weirdly and doesn't even stretch it, but instead extends the
2269 image, leaking into other images in the list (or black if none). Instead,
2270 we'll ignore this and just position it at center as without BCSS_STRETCH. */
2271 if (!ImageList_GetIconSize(infoPtr->glyph, &w, &h)) return;
2273 ImageList_Draw(infoPtr->glyph,
2274 (ImageList_GetImageCount(infoPtr->glyph) == 1) ? 0 : get_draw_state(infoPtr) - 1,
2275 hdc, rect->left + (rect->right - rect->left - w) / 2,
2276 rect->top + (rect->bottom - rect->top - h) / 2, ILD_NORMAL);
2278 else if (infoPtr->glyph_size.cy >= 0)
2280 /* infoPtr->glyph is a character code from Marlett */
2281 HFONT font, old_font;
2282 LOGFONTW logfont = { 0, 0, 0, 0, FW_NORMAL, 0, 0, 0, SYMBOL_CHARSET, 0, 0, 0, 0,
2283 { 'M','a','r','l','e','t','t',0 } };
2284 if (infoPtr->glyph_size.cy)
2286 /* BCSS_STRETCH preserves aspect ratio, uses minimum as size */
2287 if (infoPtr->split_style & BCSS_STRETCH)
2288 logfont.lfHeight = min(infoPtr->glyph_size.cx, infoPtr->glyph_size.cy);
2289 else
2291 logfont.lfWidth = infoPtr->glyph_size.cx;
2292 logfont.lfHeight = infoPtr->glyph_size.cy;
2295 else logfont.lfHeight = infoPtr->glyph_size.cx;
2297 if ((font = CreateFontIndirectW(&logfont)))
2299 old_font = SelectObject(hdc, font);
2300 DrawTextW(hdc, (const WCHAR*)&infoPtr->glyph, 1, rect,
2301 DT_CENTER | DT_VCENTER | DT_SINGLELINE | DT_NOCLIP | DT_NOPREFIX);
2302 SelectObject(hdc, old_font);
2303 DeleteObject(font);
2309 /**********************************************************************
2310 * Themed Paint Functions
2312 static void PB_ThemedPaint(HTHEME theme, const BUTTON_INFO *infoPtr, HDC hDC, int state, UINT dtFlags, BOOL focused)
2314 RECT bgRect, textRect;
2315 HFONT font = infoPtr->font;
2316 HFONT hPrevFont = font ? SelectObject(hDC, font) : NULL;
2317 NMCUSTOMDRAW nmcd;
2318 LRESULT cdrf;
2319 HWND parent;
2320 WCHAR *text;
2322 GetClientRect(infoPtr->hwnd, &bgRect);
2323 GetThemeBackgroundContentRect(theme, hDC, BP_PUSHBUTTON, state, &bgRect, &textRect);
2324 init_custom_draw(&nmcd, infoPtr, hDC, &bgRect);
2326 parent = GetParent(infoPtr->hwnd);
2327 if (!parent) parent = infoPtr->hwnd;
2329 /* Send erase notifications */
2330 cdrf = SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
2331 if (cdrf & CDRF_SKIPDEFAULT) goto cleanup;
2333 if (IsThemeBackgroundPartiallyTransparent(theme, BP_PUSHBUTTON, state))
2334 DrawThemeParentBackground(infoPtr->hwnd, hDC, NULL);
2335 DrawThemeBackground(theme, hDC, BP_PUSHBUTTON, state, &bgRect, NULL);
2337 if (cdrf & CDRF_NOTIFYPOSTERASE)
2339 nmcd.dwDrawStage = CDDS_POSTERASE;
2340 SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
2343 /* Send paint notifications */
2344 nmcd.dwDrawStage = CDDS_PREPAINT;
2345 cdrf = SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
2346 if (cdrf & CDRF_SKIPDEFAULT) goto cleanup;
2348 if (!(cdrf & CDRF_DOERASE) && (text = get_button_text(infoPtr)))
2350 DrawThemeText(theme, hDC, BP_PUSHBUTTON, state, text, lstrlenW(text), dtFlags, 0, &textRect);
2351 heap_free(text);
2354 if (cdrf & CDRF_NOTIFYPOSTPAINT)
2356 nmcd.dwDrawStage = CDDS_POSTPAINT;
2357 SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
2359 if (cdrf & CDRF_SKIPPOSTPAINT) goto cleanup;
2361 if (focused)
2363 MARGINS margins;
2364 RECT focusRect = bgRect;
2366 GetThemeMargins(theme, hDC, BP_PUSHBUTTON, state, TMT_CONTENTMARGINS, NULL, &margins);
2368 focusRect.left += margins.cxLeftWidth;
2369 focusRect.top += margins.cyTopHeight;
2370 focusRect.right -= margins.cxRightWidth;
2371 focusRect.bottom -= margins.cyBottomHeight;
2373 DrawFocusRect( hDC, &focusRect );
2376 cleanup:
2377 if (hPrevFont) SelectObject(hDC, hPrevFont);
2380 static void CB_ThemedPaint(HTHEME theme, const BUTTON_INFO *infoPtr, HDC hDC, int state, UINT dtFlags, BOOL focused)
2382 SIZE sz;
2383 RECT bgRect, textRect;
2384 HFONT font, hPrevFont = NULL;
2385 DWORD dwStyle = GetWindowLongW(infoPtr->hwnd, GWL_STYLE);
2386 UINT btn_type = get_button_type( dwStyle );
2387 int part = (btn_type == BS_RADIOBUTTON) || (btn_type == BS_AUTORADIOBUTTON) ? BP_RADIOBUTTON : BP_CHECKBOX;
2388 NMCUSTOMDRAW nmcd;
2389 LRESULT cdrf;
2390 LOGFONTW lf;
2391 HWND parent;
2392 WCHAR *text;
2393 BOOL created_font = FALSE;
2395 HRESULT hr = GetThemeFont(theme, hDC, part, state, TMT_FONT, &lf);
2396 if (SUCCEEDED(hr)) {
2397 font = CreateFontIndirectW(&lf);
2398 if (!font)
2399 TRACE("Failed to create font\n");
2400 else {
2401 TRACE("font = %s\n", debugstr_w(lf.lfFaceName));
2402 hPrevFont = SelectObject(hDC, font);
2403 created_font = TRUE;
2405 } else {
2406 font = (HFONT)SendMessageW(infoPtr->hwnd, WM_GETFONT, 0, 0);
2407 hPrevFont = SelectObject(hDC, font);
2410 if (FAILED(GetThemePartSize(theme, hDC, part, state, NULL, TS_DRAW, &sz)))
2411 sz.cx = sz.cy = 13;
2413 GetClientRect(infoPtr->hwnd, &bgRect);
2414 GetThemeBackgroundContentRect(theme, hDC, part, state, &bgRect, &textRect);
2415 init_custom_draw(&nmcd, infoPtr, hDC, &bgRect);
2417 if (dtFlags & DT_SINGLELINE) /* Center the checkbox / radio button to the text. */
2418 bgRect.top = bgRect.top + (textRect.bottom - textRect.top - sz.cy) / 2;
2420 /* adjust for the check/radio marker */
2421 bgRect.bottom = bgRect.top + sz.cy;
2422 bgRect.right = bgRect.left + sz.cx;
2423 textRect.left = bgRect.right + 6;
2425 parent = GetParent(infoPtr->hwnd);
2426 if (!parent) parent = infoPtr->hwnd;
2428 /* Send erase notifications */
2429 cdrf = SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
2430 if (cdrf & CDRF_SKIPDEFAULT) goto cleanup;
2432 DrawThemeParentBackground(infoPtr->hwnd, hDC, NULL);
2433 DrawThemeBackground(theme, hDC, part, state, &bgRect, NULL);
2435 if (cdrf & CDRF_NOTIFYPOSTERASE)
2437 nmcd.dwDrawStage = CDDS_POSTERASE;
2438 SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
2441 /* Send paint notifications */
2442 nmcd.dwDrawStage = CDDS_PREPAINT;
2443 cdrf = SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
2444 if (cdrf & CDRF_SKIPDEFAULT) goto cleanup;
2446 text = get_button_text(infoPtr);
2447 if (!(cdrf & CDRF_DOERASE) && text)
2448 DrawThemeText(theme, hDC, part, state, text, lstrlenW(text), dtFlags, 0, &textRect);
2450 if (cdrf & CDRF_NOTIFYPOSTPAINT)
2452 nmcd.dwDrawStage = CDDS_POSTPAINT;
2453 SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
2456 if (text)
2458 if (!(cdrf & CDRF_SKIPPOSTPAINT) && focused)
2460 RECT focusRect;
2462 focusRect = textRect;
2464 DrawTextW(hDC, text, lstrlenW(text), &focusRect, dtFlags | DT_CALCRECT);
2466 if (focusRect.right < textRect.right) focusRect.right++;
2467 focusRect.bottom = textRect.bottom;
2469 DrawFocusRect( hDC, &focusRect );
2472 heap_free(text);
2475 cleanup:
2476 if (created_font) DeleteObject(font);
2477 if (hPrevFont) SelectObject(hDC, hPrevFont);
2480 static void GB_ThemedPaint(HTHEME theme, const BUTTON_INFO *infoPtr, HDC hDC, int state, UINT dtFlags, BOOL focused)
2482 RECT bgRect, textRect, contentRect;
2483 WCHAR *text = get_button_text(infoPtr);
2484 LOGFONTW lf;
2485 HFONT font, hPrevFont = NULL;
2486 BOOL created_font = FALSE;
2488 HRESULT hr = GetThemeFont(theme, hDC, BP_GROUPBOX, state, TMT_FONT, &lf);
2489 if (SUCCEEDED(hr)) {
2490 font = CreateFontIndirectW(&lf);
2491 if (!font)
2492 TRACE("Failed to create font\n");
2493 else {
2494 hPrevFont = SelectObject(hDC, font);
2495 created_font = TRUE;
2497 } else {
2498 font = (HFONT)SendMessageW(infoPtr->hwnd, WM_GETFONT, 0, 0);
2499 hPrevFont = SelectObject(hDC, font);
2502 GetClientRect(infoPtr->hwnd, &bgRect);
2503 textRect = bgRect;
2505 if (text)
2507 SIZE textExtent;
2508 GetTextExtentPoint32W(hDC, text, lstrlenW(text), &textExtent);
2509 bgRect.top += (textExtent.cy / 2);
2510 textRect.left += 10;
2511 textRect.bottom = textRect.top + textExtent.cy;
2512 textRect.right = textRect.left + textExtent.cx + 4;
2514 ExcludeClipRect(hDC, textRect.left, textRect.top, textRect.right, textRect.bottom);
2517 GetThemeBackgroundContentRect(theme, hDC, BP_GROUPBOX, state, &bgRect, &contentRect);
2518 ExcludeClipRect(hDC, contentRect.left, contentRect.top, contentRect.right, contentRect.bottom);
2520 if (IsThemeBackgroundPartiallyTransparent(theme, BP_GROUPBOX, state))
2521 DrawThemeParentBackground(infoPtr->hwnd, hDC, NULL);
2522 DrawThemeBackground(theme, hDC, BP_GROUPBOX, state, &bgRect, NULL);
2524 SelectClipRgn(hDC, NULL);
2526 if (text)
2528 InflateRect(&textRect, -2, 0);
2529 DrawThemeText(theme, hDC, BP_GROUPBOX, state, text, lstrlenW(text), 0, 0, &textRect);
2530 heap_free(text);
2533 if (created_font) DeleteObject(font);
2534 if (hPrevFont) SelectObject(hDC, hPrevFont);
2537 static void SB_ThemedPaint(HTHEME theme, const BUTTON_INFO *infoPtr, HDC hDC, int state, UINT dtFlags, BOOL focused)
2539 HFONT old_font = infoPtr->font ? SelectObject(hDC, infoPtr->font) : NULL;
2540 RECT rc, content_rect, push_rect, dropdown_rect;
2541 NMCUSTOMDRAW nmcd;
2542 LRESULT cdrf;
2543 HWND parent;
2545 GetClientRect(infoPtr->hwnd, &rc);
2546 init_custom_draw(&nmcd, infoPtr, hDC, &rc);
2548 parent = GetParent(infoPtr->hwnd);
2549 if (!parent) parent = infoPtr->hwnd;
2551 /* Send erase notifications */
2552 cdrf = SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
2553 if (cdrf & CDRF_SKIPDEFAULT) goto cleanup;
2555 if (IsThemeBackgroundPartiallyTransparent(theme, BP_PUSHBUTTON, state))
2556 DrawThemeParentBackground(infoPtr->hwnd, hDC, NULL);
2558 /* The zone outside the content is ignored for the dropdown (draws over) */
2559 GetThemeBackgroundContentRect(theme, hDC, BP_PUSHBUTTON, state, &rc, &content_rect);
2560 get_split_button_rects(infoPtr, &rc, &push_rect, &dropdown_rect);
2562 if (infoPtr->split_style & BCSS_NOSPLIT)
2564 push_rect = rc;
2565 DrawThemeBackground(theme, hDC, BP_PUSHBUTTON, state, &rc, NULL);
2567 else
2569 RECT r = { dropdown_rect.left, content_rect.top, dropdown_rect.right, content_rect.bottom };
2570 UINT edge = (infoPtr->split_style & BCSS_ALIGNLEFT) ? BF_RIGHT : BF_LEFT;
2571 const RECT *clip = NULL;
2573 /* If only the dropdown is pressed, we need to draw it separately */
2574 if (state != PBS_PRESSED && (infoPtr->state & BST_DROPDOWNPUSHED))
2576 DrawThemeBackground(theme, hDC, BP_PUSHBUTTON, PBS_PRESSED, &rc, &dropdown_rect);
2577 clip = &push_rect;
2579 DrawThemeBackground(theme, hDC, BP_PUSHBUTTON, state, &rc, clip);
2581 /* Draw the separator */
2582 DrawThemeEdge(theme, hDC, BP_PUSHBUTTON, state, &r, EDGE_ETCHED, edge, NULL);
2584 /* The content rect should be the content area of the push button */
2585 GetThemeBackgroundContentRect(theme, hDC, BP_PUSHBUTTON, state, &push_rect, &content_rect);
2588 if (cdrf & CDRF_NOTIFYPOSTERASE)
2590 nmcd.dwDrawStage = CDDS_POSTERASE;
2591 SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
2594 /* Send paint notifications */
2595 nmcd.dwDrawStage = CDDS_PREPAINT;
2596 cdrf = SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
2597 if (cdrf & CDRF_SKIPDEFAULT) goto cleanup;
2599 if (!(cdrf & CDRF_DOERASE))
2601 COLORREF old_color, color;
2602 INT old_bk_mode;
2603 WCHAR *text;
2605 if ((text = get_button_text(infoPtr)))
2607 DrawThemeText(theme, hDC, BP_PUSHBUTTON, state, text, lstrlenW(text), dtFlags, 0, &content_rect);
2608 heap_free(text);
2611 GetThemeColor(theme, BP_PUSHBUTTON, state, TMT_TEXTCOLOR, &color);
2612 old_bk_mode = SetBkMode(hDC, TRANSPARENT);
2613 old_color = SetTextColor(hDC, color);
2615 draw_split_button_dropdown_glyph(infoPtr, hDC, &dropdown_rect);
2617 SetTextColor(hDC, old_color);
2618 SetBkMode(hDC, old_bk_mode);
2621 if (cdrf & CDRF_NOTIFYPOSTPAINT)
2623 nmcd.dwDrawStage = CDDS_POSTPAINT;
2624 SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
2626 if (cdrf & CDRF_SKIPPOSTPAINT) goto cleanup;
2628 if (focused)
2630 MARGINS margins;
2632 GetThemeMargins(theme, hDC, BP_PUSHBUTTON, state, TMT_CONTENTMARGINS, NULL, &margins);
2634 push_rect.left += margins.cxLeftWidth;
2635 push_rect.top += margins.cyTopHeight;
2636 push_rect.right -= margins.cxRightWidth;
2637 push_rect.bottom -= margins.cyBottomHeight;
2638 DrawFocusRect(hDC, &push_rect);
2641 cleanup:
2642 if (old_font) SelectObject(hDC, old_font);
2645 void BUTTON_Register(void)
2647 WNDCLASSW wndClass;
2649 memset(&wndClass, 0, sizeof(wndClass));
2650 wndClass.style = CS_GLOBALCLASS | CS_DBLCLKS | CS_VREDRAW | CS_HREDRAW | CS_PARENTDC;
2651 wndClass.lpfnWndProc = BUTTON_WindowProc;
2652 wndClass.cbClsExtra = 0;
2653 wndClass.cbWndExtra = sizeof(BUTTON_INFO *);
2654 wndClass.hCursor = LoadCursorW(0, (LPWSTR)IDC_ARROW);
2655 wndClass.hbrBackground = NULL;
2656 wndClass.lpszClassName = WC_BUTTONW;
2657 RegisterClassW(&wndClass);