d3d8: Stop setting the device state in d3d8_device_SetPixelShaderConstant().
[wine.git] / dlls / comctl32 / button.c
blob0078bd0fc616d537ca2bea18ac965f3d5d012599
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 CL_Paint( const BUTTON_INFO *infoPtr, HDC hDC, UINT action );
109 static void BUTTON_CheckAutoRadioButton( HWND hwnd );
110 static void get_split_button_rects(const BUTTON_INFO*, const RECT*, RECT*, RECT*);
111 static BOOL notify_split_button_dropdown(const BUTTON_INFO*, const POINT*, HWND);
112 static void draw_split_button_dropdown_glyph(const BUTTON_INFO*, HDC, RECT*);
114 #define MAX_BTN_TYPE 16
116 static const WORD maxCheckState[MAX_BTN_TYPE] =
118 BST_UNCHECKED, /* BS_PUSHBUTTON */
119 BST_UNCHECKED, /* BS_DEFPUSHBUTTON */
120 BST_CHECKED, /* BS_CHECKBOX */
121 BST_CHECKED, /* BS_AUTOCHECKBOX */
122 BST_CHECKED, /* BS_RADIOBUTTON */
123 BST_INDETERMINATE, /* BS_3STATE */
124 BST_INDETERMINATE, /* BS_AUTO3STATE */
125 BST_UNCHECKED, /* BS_GROUPBOX */
126 BST_UNCHECKED, /* BS_USERBUTTON */
127 BST_CHECKED, /* BS_AUTORADIOBUTTON */
128 BST_UNCHECKED, /* BS_PUSHBOX */
129 BST_UNCHECKED, /* BS_OWNERDRAW */
130 BST_UNCHECKED, /* BS_SPLITBUTTON */
131 BST_UNCHECKED, /* BS_DEFSPLITBUTTON */
132 BST_UNCHECKED, /* BS_COMMANDLINK */
133 BST_UNCHECKED /* BS_DEFCOMMANDLINK */
136 /* Generic draw states, use get_draw_state() to get specific state for button type */
137 enum draw_state
139 STATE_NORMAL,
140 STATE_DISABLED,
141 STATE_HOT,
142 STATE_PRESSED,
143 STATE_DEFAULTED,
144 DRAW_STATE_COUNT
147 typedef void (*pfPaint)( const BUTTON_INFO *infoPtr, HDC hdc, UINT action );
149 static const pfPaint btnPaintFunc[MAX_BTN_TYPE] =
151 PB_Paint, /* BS_PUSHBUTTON */
152 PB_Paint, /* BS_DEFPUSHBUTTON */
153 CB_Paint, /* BS_CHECKBOX */
154 CB_Paint, /* BS_AUTOCHECKBOX */
155 CB_Paint, /* BS_RADIOBUTTON */
156 CB_Paint, /* BS_3STATE */
157 CB_Paint, /* BS_AUTO3STATE */
158 GB_Paint, /* BS_GROUPBOX */
159 UB_Paint, /* BS_USERBUTTON */
160 CB_Paint, /* BS_AUTORADIOBUTTON */
161 NULL, /* BS_PUSHBOX */
162 OB_Paint, /* BS_OWNERDRAW */
163 SB_Paint, /* BS_SPLITBUTTON */
164 SB_Paint, /* BS_DEFSPLITBUTTON */
165 CL_Paint, /* BS_COMMANDLINK */
166 CL_Paint /* BS_DEFCOMMANDLINK */
169 typedef void (*pfThemedPaint)( HTHEME theme, const BUTTON_INFO *infoPtr, HDC hdc, int drawState, UINT dtflags, BOOL focused);
171 static void PB_ThemedPaint( HTHEME theme, const BUTTON_INFO *infoPtr, HDC hdc, int drawState, UINT dtflags, BOOL focused);
172 static void CB_ThemedPaint( HTHEME theme, const BUTTON_INFO *infoPtr, HDC hdc, int drawState, UINT dtflags, BOOL focused);
173 static void GB_ThemedPaint( HTHEME theme, const BUTTON_INFO *infoPtr, HDC hdc, int drawState, UINT dtflags, BOOL focused);
174 static void SB_ThemedPaint( HTHEME theme, const BUTTON_INFO *infoPtr, HDC hdc, int drawState, UINT dtflags, BOOL focused);
175 static void CL_ThemedPaint( HTHEME theme, const BUTTON_INFO *infoPtr, HDC hdc, int drawState, UINT dtflags, BOOL focused);
177 static const pfThemedPaint btnThemedPaintFunc[MAX_BTN_TYPE] =
179 PB_ThemedPaint, /* BS_PUSHBUTTON */
180 PB_ThemedPaint, /* BS_DEFPUSHBUTTON */
181 CB_ThemedPaint, /* BS_CHECKBOX */
182 CB_ThemedPaint, /* BS_AUTOCHECKBOX */
183 CB_ThemedPaint, /* BS_RADIOBUTTON */
184 CB_ThemedPaint, /* BS_3STATE */
185 CB_ThemedPaint, /* BS_AUTO3STATE */
186 GB_ThemedPaint, /* BS_GROUPBOX */
187 NULL, /* BS_USERBUTTON */
188 CB_ThemedPaint, /* BS_AUTORADIOBUTTON */
189 NULL, /* BS_PUSHBOX */
190 NULL, /* BS_OWNERDRAW */
191 SB_ThemedPaint, /* BS_SPLITBUTTON */
192 SB_ThemedPaint, /* BS_DEFSPLITBUTTON */
193 CL_ThemedPaint, /* BS_COMMANDLINK */
194 CL_ThemedPaint /* BS_DEFCOMMANDLINK */
197 typedef BOOL (*pfGetIdealSize)(BUTTON_INFO *infoPtr, SIZE *size);
199 static BOOL PB_GetIdealSize(BUTTON_INFO *infoPtr, SIZE *size);
200 static BOOL CB_GetIdealSize(BUTTON_INFO *infoPtr, SIZE *size);
201 static BOOL GB_GetIdealSize(BUTTON_INFO *infoPtr, SIZE *size);
202 static BOOL SB_GetIdealSize(BUTTON_INFO *infoPtr, SIZE *size);
203 static BOOL CL_GetIdealSize(BUTTON_INFO *infoPtr, SIZE *size);
205 static const pfGetIdealSize btnGetIdealSizeFunc[MAX_BTN_TYPE] = {
206 PB_GetIdealSize, /* BS_PUSHBUTTON */
207 PB_GetIdealSize, /* BS_DEFPUSHBUTTON */
208 CB_GetIdealSize, /* BS_CHECKBOX */
209 CB_GetIdealSize, /* BS_AUTOCHECKBOX */
210 CB_GetIdealSize, /* BS_RADIOBUTTON */
211 GB_GetIdealSize, /* BS_3STATE */
212 GB_GetIdealSize, /* BS_AUTO3STATE */
213 GB_GetIdealSize, /* BS_GROUPBOX */
214 PB_GetIdealSize, /* BS_USERBUTTON */
215 CB_GetIdealSize, /* BS_AUTORADIOBUTTON */
216 GB_GetIdealSize, /* BS_PUSHBOX */
217 GB_GetIdealSize, /* BS_OWNERDRAW */
218 SB_GetIdealSize, /* BS_SPLITBUTTON */
219 SB_GetIdealSize, /* BS_DEFSPLITBUTTON */
220 CL_GetIdealSize, /* BS_COMMANDLINK */
221 CL_GetIdealSize /* BS_DEFCOMMANDLINK */
224 /* Fixed margin for command links, regardless of DPI (based on tests done on Windows) */
225 enum { command_link_margin = 6 };
227 /* The width and height for the default command link glyph (when there's no image) */
228 enum { command_link_defglyph_size = 17 };
230 static inline UINT get_button_type( LONG window_style )
232 return (window_style & BS_TYPEMASK);
235 static inline BOOL button_centers_text( LONG window_style )
237 /* Push button's text is centered by default, same for split buttons */
238 UINT type = get_button_type(window_style);
239 return type <= BS_DEFPUSHBUTTON || type == BS_SPLITBUTTON || type == BS_DEFSPLITBUTTON;
242 /* paint a button of any type */
243 static inline void paint_button( BUTTON_INFO *infoPtr, LONG style, UINT action )
245 if (btnPaintFunc[style] && IsWindowVisible(infoPtr->hwnd))
247 HDC hdc = GetDC( infoPtr->hwnd );
248 btnPaintFunc[style]( infoPtr, hdc, action );
249 ReleaseDC( infoPtr->hwnd, hdc );
253 /* retrieve the button text; returned buffer must be freed by caller */
254 static inline WCHAR *get_button_text( const BUTTON_INFO *infoPtr )
256 INT len = GetWindowTextLengthW( infoPtr->hwnd );
257 WCHAR *buffer = heap_alloc( (len + 1) * sizeof(WCHAR) );
258 if (buffer)
259 GetWindowTextW( infoPtr->hwnd, buffer, len + 1 );
260 return buffer;
263 /* get the default glyph size for split buttons */
264 static LONG get_default_glyph_size(const BUTTON_INFO *infoPtr)
266 if (infoPtr->split_style & BCSS_IMAGE)
268 /* Size it to fit, including the left and right edges */
269 int w, h;
270 if (!ImageList_GetIconSize(infoPtr->glyph, &w, &h)) w = 0;
271 return w + GetSystemMetrics(SM_CXEDGE) * 2;
274 /* The glyph size relies on the default menu font's cell height */
275 return GetSystemMetrics(SM_CYMENUCHECK);
278 static void init_custom_draw(NMCUSTOMDRAW *nmcd, const BUTTON_INFO *infoPtr, HDC hdc, const RECT *rc)
280 nmcd->hdr.hwndFrom = infoPtr->hwnd;
281 nmcd->hdr.idFrom = GetWindowLongPtrW(infoPtr->hwnd, GWLP_ID);
282 nmcd->hdr.code = NM_CUSTOMDRAW;
283 nmcd->hdc = hdc;
284 nmcd->rc = *rc;
285 nmcd->dwDrawStage = CDDS_PREERASE;
286 nmcd->dwItemSpec = 0;
287 nmcd->lItemlParam = 0;
288 nmcd->uItemState = IsWindowEnabled(infoPtr->hwnd) ? 0 : CDIS_DISABLED;
289 if (infoPtr->state & BST_PUSHED) nmcd->uItemState |= CDIS_SELECTED;
290 if (infoPtr->state & BST_FOCUS) nmcd->uItemState |= CDIS_FOCUS;
291 if (infoPtr->state & BST_HOT) nmcd->uItemState |= CDIS_HOT;
292 if (infoPtr->state & BST_INDETERMINATE)
293 nmcd->uItemState |= CDIS_INDETERMINATE;
295 /* Windows doesn't seem to send CDIS_CHECKED (it fails the tests) */
296 /* CDIS_SHOWKEYBOARDCUES is misleading, as the meaning is reversed */
297 /* FIXME: Handle it properly when we support keyboard cues? */
300 HRGN set_control_clipping( HDC hdc, const RECT *rect )
302 RECT rc = *rect;
303 HRGN hrgn = CreateRectRgn( 0, 0, 0, 0 );
305 if (GetClipRgn( hdc, hrgn ) != 1)
307 DeleteObject( hrgn );
308 hrgn = 0;
310 DPtoLP( hdc, (POINT *)&rc, 2 );
311 if (GetLayout( hdc ) & LAYOUT_RTL) /* compensate for the shifting done by IntersectClipRect */
313 rc.left++;
314 rc.right++;
316 IntersectClipRect( hdc, rc.left, rc.top, rc.right, rc.bottom );
317 return hrgn;
320 static WCHAR *heap_strndupW(const WCHAR *src, size_t length)
322 size_t size = (length + 1) * sizeof(WCHAR);
323 WCHAR *dst = heap_alloc(size);
324 if (dst) memcpy(dst, src, size);
325 return dst;
328 /**********************************************************************
329 * Convert button styles to flags used by DrawText.
331 static UINT BUTTON_BStoDT( DWORD style, DWORD ex_style )
333 UINT dtStyle = DT_NOCLIP; /* We use SelectClipRgn to limit output */
335 /* "Convert" pushlike buttons to pushbuttons */
336 if (style & BS_PUSHLIKE)
337 style &= ~BS_TYPEMASK;
339 if (!(style & BS_MULTILINE))
340 dtStyle |= DT_SINGLELINE;
341 else
342 dtStyle |= DT_WORDBREAK;
344 switch (style & BS_CENTER)
346 case BS_LEFT: /* DT_LEFT is 0 */ break;
347 case BS_RIGHT: dtStyle |= DT_RIGHT; break;
348 case BS_CENTER: dtStyle |= DT_CENTER; break;
349 default:
350 if (button_centers_text(style)) dtStyle |= DT_CENTER;
353 if (ex_style & WS_EX_RIGHT) dtStyle = DT_RIGHT | (dtStyle & ~(DT_LEFT | DT_CENTER));
355 /* DrawText ignores vertical alignment for multiline text,
356 * but we use these flags to align label manually.
358 if (get_button_type(style) != BS_GROUPBOX)
360 switch (style & BS_VCENTER)
362 case BS_TOP: /* DT_TOP is 0 */ break;
363 case BS_BOTTOM: dtStyle |= DT_BOTTOM; break;
364 case BS_VCENTER: /* fall through */
365 default: dtStyle |= DT_VCENTER; break;
369 return dtStyle;
372 static int get_draw_state(const BUTTON_INFO *infoPtr)
374 static const int pb_states[DRAW_STATE_COUNT] = { PBS_NORMAL, PBS_DISABLED, PBS_HOT, PBS_PRESSED, PBS_DEFAULTED };
375 static const int cb_states[3][DRAW_STATE_COUNT] =
377 { CBS_UNCHECKEDNORMAL, CBS_UNCHECKEDDISABLED, CBS_UNCHECKEDHOT, CBS_UNCHECKEDPRESSED, CBS_UNCHECKEDNORMAL },
378 { CBS_CHECKEDNORMAL, CBS_CHECKEDDISABLED, CBS_CHECKEDHOT, CBS_CHECKEDPRESSED, CBS_CHECKEDNORMAL },
379 { CBS_MIXEDNORMAL, CBS_MIXEDDISABLED, CBS_MIXEDHOT, CBS_MIXEDPRESSED, CBS_MIXEDNORMAL }
381 static const int rb_states[2][DRAW_STATE_COUNT] =
383 { RBS_UNCHECKEDNORMAL, RBS_UNCHECKEDDISABLED, RBS_UNCHECKEDHOT, RBS_UNCHECKEDPRESSED, RBS_UNCHECKEDNORMAL },
384 { RBS_CHECKEDNORMAL, RBS_CHECKEDDISABLED, RBS_CHECKEDHOT, RBS_CHECKEDPRESSED, RBS_CHECKEDNORMAL }
386 static const int gb_states[DRAW_STATE_COUNT] = { GBS_NORMAL, GBS_DISABLED, GBS_NORMAL, GBS_NORMAL, GBS_NORMAL };
387 LONG style = GetWindowLongW(infoPtr->hwnd, GWL_STYLE);
388 UINT type = get_button_type(style);
389 int check_state = infoPtr->state & 3;
390 enum draw_state state;
392 if (!IsWindowEnabled(infoPtr->hwnd))
393 state = STATE_DISABLED;
394 else if (infoPtr->state & BST_PUSHED)
395 state = STATE_PRESSED;
396 else if (infoPtr->state & BST_HOT)
397 state = STATE_HOT;
398 else if (infoPtr->state & BST_FOCUS)
399 state = STATE_DEFAULTED;
400 else
401 state = STATE_NORMAL;
403 switch (type)
405 case BS_PUSHBUTTON:
406 case BS_DEFPUSHBUTTON:
407 case BS_USERBUTTON:
408 case BS_SPLITBUTTON:
409 case BS_DEFSPLITBUTTON:
410 case BS_COMMANDLINK:
411 case BS_DEFCOMMANDLINK:
412 return pb_states[state];
413 case BS_CHECKBOX:
414 case BS_AUTOCHECKBOX:
415 return cb_states[check_state][state];
416 case BS_RADIOBUTTON:
417 case BS_3STATE:
418 case BS_AUTO3STATE:
419 case BS_AUTORADIOBUTTON:
420 return rb_states[check_state][state];
421 case BS_GROUPBOX:
422 return gb_states[state];
423 default:
424 WARN("Unsupported button type 0x%08x\n", type);
425 return PBS_NORMAL;
429 static LRESULT CALLBACK BUTTON_WindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
431 BUTTON_INFO *infoPtr = (BUTTON_INFO *)GetWindowLongPtrW(hWnd, 0);
432 RECT rect;
433 POINT pt;
434 LONG style = GetWindowLongW( hWnd, GWL_STYLE );
435 UINT btn_type = get_button_type( style );
436 LONG state, new_state;
437 HANDLE oldHbitmap;
438 HTHEME theme;
440 if (!IsWindow( hWnd )) return 0;
442 if (!infoPtr && (uMsg != WM_NCCREATE))
443 return DefWindowProcW(hWnd, uMsg, wParam, lParam);
445 pt.x = (short)LOWORD(lParam);
446 pt.y = (short)HIWORD(lParam);
448 switch (uMsg)
450 case WM_GETDLGCODE:
451 switch(btn_type)
453 case BS_COMMANDLINK:
454 case BS_USERBUTTON:
455 case BS_PUSHBUTTON: return DLGC_BUTTON | DLGC_UNDEFPUSHBUTTON;
456 case BS_DEFCOMMANDLINK:
457 case BS_DEFPUSHBUTTON: return DLGC_BUTTON | DLGC_DEFPUSHBUTTON;
458 case BS_RADIOBUTTON:
459 case BS_AUTORADIOBUTTON: return DLGC_BUTTON | DLGC_RADIOBUTTON;
460 case BS_GROUPBOX: return DLGC_STATIC;
461 case BS_SPLITBUTTON: return DLGC_BUTTON | DLGC_UNDEFPUSHBUTTON | DLGC_WANTARROWS;
462 case BS_DEFSPLITBUTTON: return DLGC_BUTTON | DLGC_DEFPUSHBUTTON | DLGC_WANTARROWS;
463 default: return DLGC_BUTTON;
466 case WM_ENABLE:
467 theme = GetWindowTheme( hWnd );
468 if (theme)
469 RedrawWindow( hWnd, NULL, NULL, RDW_FRAME | RDW_INVALIDATE | RDW_UPDATENOW );
470 else
471 paint_button( infoPtr, btn_type, ODA_DRAWENTIRE );
472 break;
474 case WM_NCCREATE:
476 CREATESTRUCTW *cs = (CREATESTRUCTW *)lParam;
478 infoPtr = heap_alloc_zero( sizeof(*infoPtr) );
479 SetWindowLongPtrW( hWnd, 0, (LONG_PTR)infoPtr );
480 infoPtr->hwnd = hWnd;
481 infoPtr->parent = cs->hwndParent;
482 infoPtr->style = cs->style;
483 infoPtr->split_style = BCSS_STRETCH;
484 infoPtr->glyph = (HIMAGELIST)0x36; /* Marlett down arrow char code */
485 infoPtr->glyph_size.cx = get_default_glyph_size(infoPtr);
486 return DefWindowProcW(hWnd, uMsg, wParam, lParam);
489 case WM_NCDESTROY:
490 SetWindowLongPtrW( hWnd, 0, 0 );
491 heap_free(infoPtr->note);
492 heap_free(infoPtr);
493 break;
495 case WM_CREATE:
496 if (btn_type >= MAX_BTN_TYPE)
497 return -1; /* abort */
499 /* XP turns a BS_USERBUTTON into BS_PUSHBUTTON */
500 if (btn_type == BS_USERBUTTON )
502 style = (style & ~BS_TYPEMASK) | BS_PUSHBUTTON;
503 SetWindowLongW( hWnd, GWL_STYLE, style );
505 infoPtr->state = BST_UNCHECKED;
506 OpenThemeData( hWnd, WC_BUTTONW );
507 return 0;
509 case WM_DESTROY:
510 theme = GetWindowTheme( hWnd );
511 CloseThemeData( theme );
512 break;
514 case WM_THEMECHANGED:
515 theme = GetWindowTheme( hWnd );
516 CloseThemeData( theme );
517 OpenThemeData( hWnd, WC_BUTTONW );
518 break;
520 case WM_ERASEBKGND:
521 if (btn_type == BS_OWNERDRAW)
523 HDC hdc = (HDC)wParam;
524 RECT rc;
525 HBRUSH hBrush;
526 HWND parent = GetParent(hWnd);
527 if (!parent) parent = hWnd;
528 hBrush = (HBRUSH)SendMessageW(parent, WM_CTLCOLORBTN, (WPARAM)hdc, (LPARAM)hWnd);
529 if (!hBrush) /* did the app forget to call defwindowproc ? */
530 hBrush = (HBRUSH)DefWindowProcW(parent, WM_CTLCOLORBTN,
531 (WPARAM)hdc, (LPARAM)hWnd);
532 GetClientRect(hWnd, &rc);
533 FillRect(hdc, &rc, hBrush);
535 return 1;
537 case WM_PRINTCLIENT:
538 case WM_PAINT:
540 PAINTSTRUCT ps;
541 HDC hdc;
543 theme = GetWindowTheme( hWnd );
544 hdc = wParam ? (HDC)wParam : BeginPaint( hWnd, &ps );
546 if (theme && btnThemedPaintFunc[btn_type])
548 int drawState = get_draw_state(infoPtr);
549 UINT dtflags = BUTTON_BStoDT(style, GetWindowLongW(hWnd, GWL_EXSTYLE));
551 btnThemedPaintFunc[btn_type](theme, infoPtr, hdc, drawState, dtflags, infoPtr->state & BST_FOCUS);
553 else if (btnPaintFunc[btn_type])
555 int nOldMode = SetBkMode( hdc, OPAQUE );
556 btnPaintFunc[btn_type]( infoPtr, hdc, ODA_DRAWENTIRE );
557 SetBkMode(hdc, nOldMode); /* reset painting mode */
560 if ( !wParam ) EndPaint( hWnd, &ps );
561 break;
564 case WM_KEYDOWN:
565 if (wParam == VK_SPACE)
567 SendMessageW( hWnd, BM_SETSTATE, TRUE, 0 );
568 infoPtr->state |= BUTTON_BTNPRESSED;
569 SetCapture( hWnd );
571 else if (wParam == VK_UP || wParam == VK_DOWN)
573 /* Up and down arrows work on every button, and even with BCSS_NOSPLIT */
574 notify_split_button_dropdown(infoPtr, NULL, hWnd);
576 break;
578 case WM_LBUTTONDBLCLK:
579 if(style & BS_NOTIFY ||
580 btn_type == BS_RADIOBUTTON ||
581 btn_type == BS_USERBUTTON ||
582 btn_type == BS_OWNERDRAW)
584 BUTTON_NOTIFY_PARENT(hWnd, BN_DOUBLECLICKED);
585 break;
587 /* fall through */
588 case WM_LBUTTONDOWN:
589 SetFocus( hWnd );
591 if ((btn_type == BS_SPLITBUTTON || btn_type == BS_DEFSPLITBUTTON) &&
592 !(infoPtr->split_style & BCSS_NOSPLIT) &&
593 notify_split_button_dropdown(infoPtr, &pt, hWnd))
594 break;
596 SetCapture( hWnd );
597 infoPtr->state |= BUTTON_BTNPRESSED;
598 SendMessageW( hWnd, BM_SETSTATE, TRUE, 0 );
599 break;
601 case WM_KEYUP:
602 if (wParam != VK_SPACE)
603 break;
604 /* fall through */
605 case WM_LBUTTONUP:
606 state = infoPtr->state;
607 if (state & BST_DROPDOWNPUSHED)
608 SendMessageW(hWnd, BCM_SETDROPDOWNSTATE, FALSE, 0);
609 if (!(state & BUTTON_BTNPRESSED)) break;
610 infoPtr->state &= BUTTON_NSTATES | BST_HOT;
611 if (!(state & BST_PUSHED))
613 ReleaseCapture();
614 break;
616 SendMessageW( hWnd, BM_SETSTATE, FALSE, 0 );
617 GetClientRect( hWnd, &rect );
618 if (uMsg == WM_KEYUP || PtInRect( &rect, pt ))
620 switch(btn_type)
622 case BS_AUTOCHECKBOX:
623 SendMessageW( hWnd, BM_SETCHECK, !(infoPtr->state & BST_CHECKED), 0 );
624 break;
625 case BS_AUTORADIOBUTTON:
626 SendMessageW( hWnd, BM_SETCHECK, TRUE, 0 );
627 break;
628 case BS_AUTO3STATE:
629 SendMessageW( hWnd, BM_SETCHECK, (infoPtr->state & BST_INDETERMINATE) ? 0 :
630 ((infoPtr->state & 3) + 1), 0 );
631 break;
633 ReleaseCapture();
634 BUTTON_NOTIFY_PARENT(hWnd, BN_CLICKED);
636 else
638 ReleaseCapture();
640 break;
642 case WM_CAPTURECHANGED:
643 TRACE("WM_CAPTURECHANGED %p\n", hWnd);
644 if (hWnd == (HWND)lParam) break;
645 if (infoPtr->state & BUTTON_BTNPRESSED)
647 infoPtr->state &= BUTTON_NSTATES;
648 if (infoPtr->state & BST_PUSHED)
649 SendMessageW( hWnd, BM_SETSTATE, FALSE, 0 );
651 break;
653 case WM_MOUSEMOVE:
655 TRACKMOUSEEVENT mouse_event;
657 mouse_event.cbSize = sizeof(TRACKMOUSEEVENT);
658 mouse_event.dwFlags = TME_QUERY;
659 if (!TrackMouseEvent(&mouse_event) || !(mouse_event.dwFlags & (TME_HOVER | TME_LEAVE)))
661 mouse_event.dwFlags = TME_HOVER | TME_LEAVE;
662 mouse_event.hwndTrack = hWnd;
663 mouse_event.dwHoverTime = 1;
664 TrackMouseEvent(&mouse_event);
667 if ((wParam & MK_LBUTTON) && GetCapture() == hWnd)
669 GetClientRect( hWnd, &rect );
670 SendMessageW( hWnd, BM_SETSTATE, PtInRect(&rect, pt), 0 );
672 break;
675 case WM_MOUSEHOVER:
677 infoPtr->state |= BST_HOT;
678 InvalidateRect( hWnd, NULL, FALSE );
679 break;
682 case WM_MOUSELEAVE:
684 infoPtr->state &= ~BST_HOT;
685 InvalidateRect( hWnd, NULL, FALSE );
686 break;
689 case WM_SETTEXT:
691 /* Clear an old text here as Windows does */
692 if (IsWindowVisible(hWnd))
694 HDC hdc = GetDC(hWnd);
695 HBRUSH hbrush;
696 RECT client, rc;
697 HWND parent = GetParent(hWnd);
698 UINT message = (btn_type == BS_PUSHBUTTON ||
699 btn_type == BS_DEFPUSHBUTTON ||
700 btn_type == BS_USERBUTTON ||
701 btn_type == BS_OWNERDRAW) ?
702 WM_CTLCOLORBTN : WM_CTLCOLORSTATIC;
704 if (!parent) parent = hWnd;
705 hbrush = (HBRUSH)SendMessageW(parent, message,
706 (WPARAM)hdc, (LPARAM)hWnd);
707 if (!hbrush) /* did the app forget to call DefWindowProc ? */
708 hbrush = (HBRUSH)DefWindowProcW(parent, message,
709 (WPARAM)hdc, (LPARAM)hWnd);
711 GetClientRect(hWnd, &client);
712 rc = client;
713 /* FIXME: check other BS_* handlers */
714 if (btn_type == BS_GROUPBOX)
715 InflateRect(&rc, -7, 1); /* GB_Paint does this */
716 BUTTON_CalcLayoutRects(infoPtr, hdc, &rc, NULL, NULL);
717 /* Clip by client rect bounds */
718 if (rc.right > client.right) rc.right = client.right;
719 if (rc.bottom > client.bottom) rc.bottom = client.bottom;
720 FillRect(hdc, &rc, hbrush);
721 ReleaseDC(hWnd, hdc);
724 DefWindowProcW( hWnd, WM_SETTEXT, wParam, lParam );
725 if (btn_type == BS_GROUPBOX) /* Yes, only for BS_GROUPBOX */
726 InvalidateRect( hWnd, NULL, TRUE );
727 else
728 paint_button( infoPtr, btn_type, ODA_DRAWENTIRE );
729 return 1; /* success. FIXME: check text length */
732 case BCM_SETNOTE:
734 WCHAR *note = (WCHAR *)lParam;
735 if (btn_type != BS_COMMANDLINK && btn_type != BS_DEFCOMMANDLINK)
737 SetLastError(ERROR_NOT_SUPPORTED);
738 return FALSE;
741 heap_free(infoPtr->note);
742 if (note)
744 infoPtr->note_length = lstrlenW(note);
745 infoPtr->note = heap_strndupW(note, infoPtr->note_length);
748 if (!note || !infoPtr->note)
750 infoPtr->note_length = 0;
751 infoPtr->note = heap_alloc_zero(sizeof(WCHAR));
754 SetLastError(NO_ERROR);
755 return TRUE;
758 case BCM_GETNOTE:
760 DWORD *size = (DWORD *)wParam;
761 WCHAR *buffer = (WCHAR *)lParam;
762 INT length = 0;
764 if (btn_type != BS_COMMANDLINK && btn_type != BS_DEFCOMMANDLINK)
766 SetLastError(ERROR_NOT_SUPPORTED);
767 return FALSE;
770 if (!buffer || !size || !infoPtr->note)
772 SetLastError(ERROR_INVALID_PARAMETER);
773 return FALSE;
776 if (*size > 0)
778 length = min(*size - 1, infoPtr->note_length);
779 memcpy(buffer, infoPtr->note, length * sizeof(WCHAR));
780 buffer[length] = '\0';
783 if (*size < infoPtr->note_length + 1)
785 *size = infoPtr->note_length + 1;
786 SetLastError(ERROR_INSUFFICIENT_BUFFER);
787 return FALSE;
789 else
791 SetLastError(NO_ERROR);
792 return TRUE;
796 case BCM_GETNOTELENGTH:
798 if (btn_type != BS_COMMANDLINK && btn_type != BS_DEFCOMMANDLINK)
800 SetLastError(ERROR_NOT_SUPPORTED);
801 return 0;
804 return infoPtr->note_length;
807 case WM_SETFONT:
808 infoPtr->font = (HFONT)wParam;
809 if (lParam) InvalidateRect(hWnd, NULL, TRUE);
810 break;
812 case WM_GETFONT:
813 return (LRESULT)infoPtr->font;
815 case WM_SETFOCUS:
816 TRACE("WM_SETFOCUS %p\n",hWnd);
817 infoPtr->state |= BST_FOCUS;
819 if (btn_type == BS_OWNERDRAW)
820 paint_button( infoPtr, btn_type, ODA_FOCUS );
821 else
822 InvalidateRect(hWnd, NULL, FALSE);
824 if (style & BS_NOTIFY)
825 BUTTON_NOTIFY_PARENT(hWnd, BN_SETFOCUS);
826 break;
828 case WM_KILLFOCUS:
829 TRACE("WM_KILLFOCUS %p\n",hWnd);
830 infoPtr->state &= ~BST_FOCUS;
832 if ((infoPtr->state & BUTTON_BTNPRESSED) && GetCapture() == hWnd)
833 ReleaseCapture();
834 if (style & BS_NOTIFY)
835 BUTTON_NOTIFY_PARENT(hWnd, BN_KILLFOCUS);
837 InvalidateRect( hWnd, NULL, FALSE );
838 break;
840 case WM_SYSCOLORCHANGE:
841 InvalidateRect( hWnd, NULL, FALSE );
842 break;
844 case BM_SETSTYLE:
845 btn_type = wParam & BS_TYPEMASK;
846 style = (style & ~BS_TYPEMASK) | btn_type;
847 SetWindowLongW( hWnd, GWL_STYLE, style );
849 /* Only redraw if lParam flag is set.*/
850 if (lParam)
851 InvalidateRect( hWnd, NULL, TRUE );
853 break;
855 case BM_CLICK:
856 SendMessageW( hWnd, WM_LBUTTONDOWN, 0, 0 );
857 SendMessageW( hWnd, WM_LBUTTONUP, 0, 0 );
858 break;
860 case BM_SETIMAGE:
861 infoPtr->image_type = (DWORD)wParam;
862 oldHbitmap = infoPtr->u.image;
863 infoPtr->u.image = (HANDLE)lParam;
864 InvalidateRect( hWnd, NULL, FALSE );
865 return (LRESULT)oldHbitmap;
867 case BM_GETIMAGE:
868 return (LRESULT)infoPtr->u.image;
870 case BCM_SETIMAGELIST:
872 BUTTON_IMAGELIST *imagelist = (BUTTON_IMAGELIST *)lParam;
874 if (!imagelist) return FALSE;
876 infoPtr->imagelist = *imagelist;
877 return TRUE;
880 case BCM_GETIMAGELIST:
882 BUTTON_IMAGELIST *imagelist = (BUTTON_IMAGELIST *)lParam;
884 if (!imagelist) return FALSE;
886 *imagelist = infoPtr->imagelist;
887 return TRUE;
890 case BCM_SETSPLITINFO:
892 BUTTON_SPLITINFO *info = (BUTTON_SPLITINFO*)lParam;
894 if (!info) return TRUE;
896 if (info->mask & (BCSIF_GLYPH | BCSIF_IMAGE))
898 infoPtr->split_style &= ~BCSS_IMAGE;
899 if (!(info->mask & BCSIF_GLYPH))
900 infoPtr->split_style |= BCSS_IMAGE;
901 infoPtr->glyph = info->himlGlyph;
902 infoPtr->glyph_size.cx = infoPtr->glyph_size.cy = 0;
905 if (info->mask & BCSIF_STYLE)
906 infoPtr->split_style = info->uSplitStyle;
907 if (info->mask & BCSIF_SIZE)
908 infoPtr->glyph_size = info->size;
910 /* Calculate fitting value for cx if invalid (cy is untouched) */
911 if (infoPtr->glyph_size.cx <= 0)
912 infoPtr->glyph_size.cx = get_default_glyph_size(infoPtr);
914 /* Windows doesn't invalidate or redraw it, so we don't, either */
915 return TRUE;
918 case BCM_GETSPLITINFO:
920 BUTTON_SPLITINFO *info = (BUTTON_SPLITINFO*)lParam;
922 if (!info) return FALSE;
924 if (info->mask & BCSIF_STYLE)
925 info->uSplitStyle = infoPtr->split_style;
926 if (info->mask & (BCSIF_GLYPH | BCSIF_IMAGE))
927 info->himlGlyph = infoPtr->glyph;
928 if (info->mask & BCSIF_SIZE)
929 info->size = infoPtr->glyph_size;
931 return TRUE;
934 case BM_GETCHECK:
935 return infoPtr->state & 3;
937 case BM_SETCHECK:
938 if (wParam > maxCheckState[btn_type]) wParam = maxCheckState[btn_type];
939 if ((btn_type == BS_RADIOBUTTON) || (btn_type == BS_AUTORADIOBUTTON))
941 style = wParam ? style | WS_TABSTOP : style & ~WS_TABSTOP;
942 SetWindowLongW( hWnd, GWL_STYLE, style );
944 if ((infoPtr->state & 3) != wParam)
946 infoPtr->state = (infoPtr->state & ~3) | wParam;
947 InvalidateRect( hWnd, NULL, FALSE );
949 if ((btn_type == BS_AUTORADIOBUTTON) && (wParam == BST_CHECKED) && (style & WS_CHILD))
950 BUTTON_CheckAutoRadioButton( hWnd );
951 break;
953 case BM_GETSTATE:
954 return infoPtr->state;
956 case BM_SETSTATE:
957 state = infoPtr->state;
958 new_state = wParam ? BST_PUSHED : 0;
960 if ((state ^ new_state) & BST_PUSHED)
962 if (wParam)
963 state |= BST_PUSHED;
964 else
965 state &= ~BST_PUSHED;
967 if (btn_type == BS_USERBUTTON)
968 BUTTON_NOTIFY_PARENT( hWnd, (state & BST_PUSHED) ? BN_HILITE : BN_UNHILITE );
969 infoPtr->state = state;
971 InvalidateRect( hWnd, NULL, FALSE );
973 break;
975 case BCM_SETDROPDOWNSTATE:
976 new_state = wParam ? BST_DROPDOWNPUSHED : 0;
978 if ((infoPtr->state ^ new_state) & BST_DROPDOWNPUSHED)
980 infoPtr->state &= ~BST_DROPDOWNPUSHED;
981 infoPtr->state |= new_state;
982 InvalidateRect(hWnd, NULL, FALSE);
984 break;
986 case BCM_SETTEXTMARGIN:
988 RECT *text_margin = (RECT *)lParam;
990 if (!text_margin) return FALSE;
992 infoPtr->text_margin = *text_margin;
993 return TRUE;
996 case BCM_GETTEXTMARGIN:
998 RECT *text_margin = (RECT *)lParam;
1000 if (!text_margin) return FALSE;
1002 *text_margin = infoPtr->text_margin;
1003 return TRUE;
1006 case BCM_GETIDEALSIZE:
1008 SIZE *size = (SIZE *)lParam;
1010 if (!size) return FALSE;
1012 return btnGetIdealSizeFunc[btn_type](infoPtr, size);
1015 case WM_NCHITTEST:
1016 if(btn_type == BS_GROUPBOX) return HTTRANSPARENT;
1017 /* fall through */
1018 default:
1019 return DefWindowProcW(hWnd, uMsg, wParam, lParam);
1021 return 0;
1024 /* If maxWidth is zero, rectangle width is unlimited */
1025 static RECT BUTTON_GetTextRect(const BUTTON_INFO *infoPtr, HDC hdc, const WCHAR *text, LONG maxWidth)
1027 LONG style = GetWindowLongW(infoPtr->hwnd, GWL_STYLE);
1028 LONG exStyle = GetWindowLongW(infoPtr->hwnd, GWL_EXSTYLE);
1029 UINT dtStyle = BUTTON_BStoDT(style, exStyle);
1030 HFONT hPrevFont;
1031 RECT rect = {0};
1033 rect.right = maxWidth;
1034 hPrevFont = SelectObject(hdc, infoPtr->font);
1035 /* Calculate height without DT_VCENTER and DT_BOTTOM to get the correct height */
1036 DrawTextW(hdc, text, -1, &rect, (dtStyle & ~(DT_VCENTER | DT_BOTTOM)) | DT_CALCRECT);
1037 if (hPrevFont) SelectObject(hdc, hPrevFont);
1039 return rect;
1042 static BOOL show_image_only(const BUTTON_INFO *infoPtr)
1044 LONG style = GetWindowLongW(infoPtr->hwnd, GWL_STYLE);
1045 return (style & (BS_ICON | BS_BITMAP)) && (infoPtr->u.image || infoPtr->imagelist.himl);
1048 static BOOL show_image_and_text(const BUTTON_INFO *infoPtr)
1050 LONG style = GetWindowLongW(infoPtr->hwnd, GWL_STYLE);
1051 UINT type = get_button_type(style);
1052 return !(style & (BS_ICON | BS_BITMAP))
1053 && ((infoPtr->u.image
1054 && (type == BS_PUSHBUTTON || type == BS_DEFPUSHBUTTON || type == BS_USERBUTTON || type == BS_SPLITBUTTON
1055 || type == BS_DEFSPLITBUTTON || type == BS_COMMANDLINK || type == BS_DEFCOMMANDLINK))
1056 || (infoPtr->imagelist.himl && type != BS_GROUPBOX));
1059 static BOOL show_image(const BUTTON_INFO *infoPtr)
1061 return show_image_only(infoPtr) || show_image_and_text(infoPtr);
1064 /* Get a bounding rectangle that is large enough to contain a image and a text side by side.
1065 * Note: (left,top) of the result rectangle may not be (0,0), offset it by yourself if needed */
1066 static RECT BUTTON_GetBoundingLabelRect(LONG style, const RECT *textRect, const RECT *imageRect)
1068 RECT labelRect;
1069 RECT rect = *imageRect;
1070 INT textWidth = textRect->right - textRect->left;
1071 INT textHeight = textRect->bottom - textRect->top;
1072 INT imageWidth = imageRect->right - imageRect->left;
1073 INT imageHeight = imageRect->bottom - imageRect->top;
1075 if ((style & BS_CENTER) == BS_RIGHT)
1076 OffsetRect(&rect, textWidth, 0);
1077 else if ((style & BS_CENTER) == BS_LEFT)
1078 OffsetRect(&rect, -imageWidth, 0);
1079 else if ((style & BS_VCENTER) == BS_BOTTOM)
1080 OffsetRect(&rect, 0, textHeight);
1081 else if ((style & BS_VCENTER) == BS_TOP)
1082 OffsetRect(&rect, 0, -imageHeight);
1083 else
1084 OffsetRect(&rect, -imageWidth, 0);
1086 UnionRect(&labelRect, textRect, &rect);
1087 return labelRect;
1090 /* Position a rectangle inside a bounding rectangle according to button alignment flags */
1091 static void BUTTON_PositionRect(LONG style, const RECT *outerRect, RECT *innerRect, const RECT *margin)
1093 INT width = innerRect->right - innerRect->left;
1094 INT height = innerRect->bottom - innerRect->top;
1096 if ((style & WS_EX_RIGHT) && !(style & BS_CENTER)) style |= BS_CENTER;
1098 if (!(style & BS_CENTER))
1100 if (button_centers_text(style))
1101 style |= BS_CENTER;
1102 else
1103 style |= BS_LEFT;
1106 if (!(style & BS_VCENTER))
1108 /* Group box's text is top aligned by default */
1109 if (get_button_type(style) == BS_GROUPBOX)
1110 style |= BS_TOP;
1113 switch (style & BS_CENTER)
1115 case BS_CENTER:
1116 innerRect->left = outerRect->left + (outerRect->right - outerRect->left - width) / 2;
1117 innerRect->right = innerRect->left + width;
1118 break;
1119 case BS_RIGHT:
1120 innerRect->right = outerRect->right - margin->right;
1121 innerRect->left = innerRect->right - width;
1122 break;
1123 case BS_LEFT:
1124 default:
1125 innerRect->left = outerRect->left + margin->left;
1126 innerRect->right = innerRect->left + width;
1127 break;
1130 switch (style & BS_VCENTER)
1132 case BS_TOP:
1133 innerRect->top = outerRect->top + margin->top;
1134 innerRect->bottom = innerRect->top + height;
1135 break;
1136 case BS_BOTTOM:
1137 innerRect->bottom = outerRect->bottom - margin->bottom;
1138 innerRect->top = innerRect->bottom - height;
1139 break;
1140 case BS_VCENTER:
1141 default:
1142 innerRect->top = outerRect->top + (outerRect->bottom - outerRect->top - height) / 2;
1143 innerRect->bottom = innerRect->top + height;
1144 break;
1148 /* Convert imagelist align style to button align style */
1149 static UINT BUTTON_ILStoBS(UINT align)
1151 switch (align)
1153 case BUTTON_IMAGELIST_ALIGN_TOP:
1154 return BS_CENTER | BS_TOP;
1155 case BUTTON_IMAGELIST_ALIGN_BOTTOM:
1156 return BS_CENTER | BS_BOTTOM;
1157 case BUTTON_IMAGELIST_ALIGN_CENTER:
1158 return BS_CENTER | BS_VCENTER;
1159 case BUTTON_IMAGELIST_ALIGN_RIGHT:
1160 return BS_RIGHT | BS_VCENTER;
1161 case BUTTON_IMAGELIST_ALIGN_LEFT:
1162 default:
1163 return BS_LEFT | BS_VCENTER;
1167 static SIZE BUTTON_GetImageSize(const BUTTON_INFO *infoPtr)
1169 ICONINFO iconInfo;
1170 BITMAP bm = {0};
1171 SIZE size = {0};
1173 /* ImageList has priority over image */
1174 if (infoPtr->imagelist.himl)
1175 ImageList_GetIconSize(infoPtr->imagelist.himl, &size.cx, &size.cy);
1176 else if (infoPtr->u.image)
1178 if (infoPtr->image_type == IMAGE_ICON)
1180 GetIconInfo(infoPtr->u.icon, &iconInfo);
1181 GetObjectW(iconInfo.hbmColor, sizeof(bm), &bm);
1182 DeleteObject(iconInfo.hbmColor);
1183 DeleteObject(iconInfo.hbmMask);
1185 else if (infoPtr->image_type == IMAGE_BITMAP)
1186 GetObjectW(infoPtr->u.bitmap, sizeof(bm), &bm);
1188 size.cx = bm.bmWidth;
1189 size.cy = bm.bmHeight;
1192 return size;
1195 static const RECT *BUTTON_GetTextMargin(const BUTTON_INFO *infoPtr)
1197 static const RECT oneMargin = {1, 1, 1, 1};
1199 /* Use text margin only when showing both image and text, and image is not imagelist */
1200 if (show_image_and_text(infoPtr) && !infoPtr->imagelist.himl)
1201 return &infoPtr->text_margin;
1202 else
1203 return &oneMargin;
1206 static void BUTTON_GetClientRectSize(BUTTON_INFO *infoPtr, SIZE *size)
1208 RECT rect;
1209 GetClientRect(infoPtr->hwnd, &rect);
1210 size->cx = rect.right - rect.left;
1211 size->cy = rect.bottom - rect.top;
1214 static void BUTTON_GetTextIdealSize(BUTTON_INFO *infoPtr, LONG maxWidth, SIZE *size)
1216 WCHAR *text = get_button_text(infoPtr);
1217 HDC hdc;
1218 RECT rect;
1219 const RECT *margin = BUTTON_GetTextMargin(infoPtr);
1221 if (maxWidth != 0)
1223 maxWidth -= margin->right + margin->right;
1224 if (maxWidth <= 0) maxWidth = 1;
1227 hdc = GetDC(infoPtr->hwnd);
1228 rect = BUTTON_GetTextRect(infoPtr, hdc, text, maxWidth);
1229 ReleaseDC(infoPtr->hwnd, hdc);
1230 heap_free(text);
1232 size->cx = rect.right - rect.left + margin->left + margin->right;
1233 size->cy = rect.bottom - rect.top + margin->top + margin->bottom;
1236 static void BUTTON_GetLabelIdealSize(BUTTON_INFO *infoPtr, LONG maxWidth, SIZE *size)
1238 LONG style = GetWindowLongW(infoPtr->hwnd, GWL_STYLE);
1239 SIZE imageSize;
1240 SIZE textSize;
1241 BOOL horizontal;
1243 imageSize = BUTTON_GetImageSize(infoPtr);
1244 if (infoPtr->imagelist.himl)
1246 imageSize.cx += infoPtr->imagelist.margin.left + infoPtr->imagelist.margin.right;
1247 imageSize.cy += infoPtr->imagelist.margin.top + infoPtr->imagelist.margin.bottom;
1248 if (infoPtr->imagelist.uAlign == BUTTON_IMAGELIST_ALIGN_TOP
1249 || infoPtr->imagelist.uAlign == BUTTON_IMAGELIST_ALIGN_BOTTOM)
1250 horizontal = FALSE;
1251 else
1252 horizontal = TRUE;
1254 else
1256 /* horizontal alignment flags has priority over vertical ones if both are specified */
1257 if (!(style & (BS_CENTER | BS_VCENTER)) || ((style & BS_CENTER) && (style & BS_CENTER) != BS_CENTER)
1258 || !(style & BS_VCENTER) || (style & BS_VCENTER) == BS_VCENTER)
1259 horizontal = TRUE;
1260 else
1261 horizontal = FALSE;
1264 if (horizontal)
1266 if (maxWidth != 0)
1268 maxWidth -= imageSize.cx;
1269 if (maxWidth <= 0) maxWidth = 1;
1271 BUTTON_GetTextIdealSize(infoPtr, maxWidth, &textSize);
1272 size->cx = textSize.cx + imageSize.cx;
1273 size->cy = max(textSize.cy, imageSize.cy);
1275 else
1277 BUTTON_GetTextIdealSize(infoPtr, maxWidth, &textSize);
1278 size->cx = max(textSize.cx, imageSize.cx);
1279 size->cy = textSize.cy + imageSize.cy;
1283 static BOOL GB_GetIdealSize(BUTTON_INFO *infoPtr, SIZE *size)
1285 BUTTON_GetClientRectSize(infoPtr, size);
1286 return TRUE;
1289 static BOOL CB_GetIdealSize(BUTTON_INFO *infoPtr, SIZE *size)
1291 LONG style = GetWindowLongW(infoPtr->hwnd, GWL_STYLE);
1292 HDC hdc;
1293 HFONT hfont;
1294 SIZE labelSize;
1295 INT textOffset;
1296 double scaleX;
1297 double scaleY;
1298 LONG checkboxWidth, checkboxHeight;
1299 LONG maxWidth = 0;
1301 if (SendMessageW(infoPtr->hwnd, WM_GETTEXTLENGTH, 0, 0) == 0)
1303 BUTTON_GetClientRectSize(infoPtr, size);
1304 return TRUE;
1307 hdc = GetDC(infoPtr->hwnd);
1308 scaleX = GetDeviceCaps(hdc, LOGPIXELSX) / 96.0;
1309 scaleY = GetDeviceCaps(hdc, LOGPIXELSY) / 96.0;
1310 if ((hfont = infoPtr->font)) SelectObject(hdc, hfont);
1311 GetCharWidthW(hdc, '0', '0', &textOffset);
1312 textOffset /= 2;
1313 ReleaseDC(infoPtr->hwnd, hdc);
1315 checkboxWidth = 12 * scaleX + 1;
1316 checkboxHeight = 12 * scaleY + 1;
1317 if (size->cx)
1319 maxWidth = size->cx - checkboxWidth - textOffset;
1320 if (maxWidth <= 0) maxWidth = 1;
1323 /* Checkbox doesn't support both image(but not image list) and text */
1324 if (!(style & (BS_ICON | BS_BITMAP)) && infoPtr->u.image)
1325 BUTTON_GetTextIdealSize(infoPtr, maxWidth, &labelSize);
1326 else
1327 BUTTON_GetLabelIdealSize(infoPtr, maxWidth, &labelSize);
1329 size->cx = labelSize.cx + checkboxWidth + textOffset;
1330 size->cy = max(labelSize.cy, checkboxHeight);
1332 return TRUE;
1335 static BOOL PB_GetIdealSize(BUTTON_INFO *infoPtr, SIZE *size)
1337 SIZE labelSize;
1339 if (SendMessageW(infoPtr->hwnd, WM_GETTEXTLENGTH, 0, 0) == 0)
1340 BUTTON_GetClientRectSize(infoPtr, size);
1341 else
1343 /* Ideal size include text size even if image only flags(BS_ICON, BS_BITMAP) are specified */
1344 BUTTON_GetLabelIdealSize(infoPtr, size->cx, &labelSize);
1346 size->cx = labelSize.cx;
1347 size->cy = labelSize.cy;
1349 return TRUE;
1352 static BOOL SB_GetIdealSize(BUTTON_INFO *infoPtr, SIZE *size)
1354 LONG extra_width = infoPtr->glyph_size.cx * 2 + GetSystemMetrics(SM_CXEDGE);
1355 SIZE label_size;
1357 if (SendMessageW(infoPtr->hwnd, WM_GETTEXTLENGTH, 0, 0) == 0)
1359 BUTTON_GetClientRectSize(infoPtr, size);
1360 size->cx = max(size->cx, extra_width);
1362 else
1364 BUTTON_GetLabelIdealSize(infoPtr, size->cx, &label_size);
1365 size->cx = label_size.cx + ((size->cx == 0) ? extra_width : 0);
1366 size->cy = label_size.cy;
1368 return TRUE;
1371 static BOOL CL_GetIdealSize(BUTTON_INFO *infoPtr, SIZE *size)
1373 HTHEME theme = GetWindowTheme(infoPtr->hwnd);
1374 HDC hdc = GetDC(infoPtr->hwnd);
1375 LONG w, text_w = 0, text_h = 0;
1376 UINT flags = DT_TOP | DT_LEFT;
1377 HFONT font, old_font = NULL;
1378 RECT text_bound = { 0 };
1379 SIZE img_size;
1380 RECT margin;
1381 WCHAR *text;
1383 /* Get the image size */
1384 if (infoPtr->u.image || infoPtr->imagelist.himl)
1385 img_size = BUTTON_GetImageSize(infoPtr);
1386 else
1388 if (theme)
1389 GetThemePartSize(theme, NULL, BP_COMMANDLINKGLYPH, CMDLS_NORMAL, NULL, TS_DRAW, &img_size);
1390 else
1391 img_size.cx = img_size.cy = command_link_defglyph_size;
1394 /* Get the content margins */
1395 if (theme)
1397 RECT r = { 0, 0, 0xffff, 0xffff };
1398 GetThemeBackgroundContentRect(theme, hdc, BP_COMMANDLINK, CMDLS_NORMAL, &r, &margin);
1399 margin.left -= r.left;
1400 margin.top -= r.top;
1401 margin.right = r.right - margin.right;
1402 margin.bottom = r.bottom - margin.bottom;
1404 else
1406 margin.left = margin.right = command_link_margin;
1407 margin.top = margin.bottom = command_link_margin;
1410 /* Account for the border margins and the margin between image and text */
1411 w = margin.left + margin.right + (img_size.cx ? (img_size.cx + command_link_margin) : 0);
1413 /* If a rectangle with a specific width was requested, bound the text to it */
1414 if (size->cx > w)
1416 text_bound.right = size->cx - w;
1417 flags |= DT_WORDBREAK;
1420 if (theme)
1422 if (infoPtr->font) old_font = SelectObject(hdc, infoPtr->font);
1424 /* Find the text's rect */
1425 if ((text = get_button_text(infoPtr)))
1427 RECT r;
1428 GetThemeTextExtent(theme, hdc, BP_COMMANDLINK, CMDLS_NORMAL,
1429 text, -1, flags, &text_bound, &r);
1430 heap_free(text);
1431 text_w = r.right - r.left;
1432 text_h = r.bottom - r.top;
1435 /* Find the note's rect */
1436 if (infoPtr->note)
1438 DTTOPTS opts;
1440 opts.dwSize = sizeof(opts);
1441 opts.dwFlags = DTT_FONTPROP | DTT_CALCRECT;
1442 opts.iFontPropId = TMT_BODYFONT;
1443 DrawThemeTextEx(theme, hdc, BP_COMMANDLINK, CMDLS_NORMAL,
1444 infoPtr->note, infoPtr->note_length,
1445 flags | DT_NOPREFIX | DT_CALCRECT, &text_bound, &opts);
1446 text_w = max(text_w, text_bound.right - text_bound.left);
1447 text_h += text_bound.bottom - text_bound.top;
1450 else
1452 NONCLIENTMETRICSW ncm;
1454 ncm.cbSize = sizeof(ncm);
1455 if (SystemParametersInfoW(SPI_GETNONCLIENTMETRICS, sizeof(ncm), &ncm, 0))
1457 LONG note_weight = ncm.lfMessageFont.lfWeight;
1459 /* Find the text's rect */
1460 ncm.lfMessageFont.lfWeight = FW_BOLD;
1461 if ((font = CreateFontIndirectW(&ncm.lfMessageFont)))
1463 if ((text = get_button_text(infoPtr)))
1465 RECT r = text_bound;
1466 old_font = SelectObject(hdc, font);
1467 DrawTextW(hdc, text, -1, &r, flags | DT_CALCRECT);
1468 heap_free(text);
1470 text_w = r.right - r.left;
1471 text_h = r.bottom - r.top;
1473 DeleteObject(font);
1476 /* Find the note's rect */
1477 ncm.lfMessageFont.lfWeight = note_weight;
1478 if (infoPtr->note && (font = CreateFontIndirectW(&ncm.lfMessageFont)))
1480 HFONT tmp = SelectObject(hdc, font);
1481 if (!old_font) old_font = tmp;
1483 DrawTextW(hdc, infoPtr->note, infoPtr->note_length, &text_bound,
1484 flags | DT_NOPREFIX | DT_CALCRECT);
1485 DeleteObject(font);
1487 text_w = max(text_w, text_bound.right - text_bound.left);
1488 text_h += text_bound.bottom - text_bound.top + 2;
1492 w += text_w;
1494 size->cx = min(size->cx, w);
1495 size->cy = max(text_h, img_size.cy) + margin.top + margin.bottom;
1497 if (old_font) SelectObject(hdc, old_font);
1498 ReleaseDC(infoPtr->hwnd, hdc);
1499 return TRUE;
1502 /**********************************************************************
1503 * BUTTON_CalcLayoutRects
1505 * Calculates the rectangles of the button label(image and text) and its parts depending on a button's style.
1507 * Returns flags to be passed to DrawText.
1508 * Calculated rectangle doesn't take into account button state
1509 * (pushed, etc.). If there is nothing to draw (no text/image) output
1510 * rectangle is empty, and return value is (UINT)-1.
1512 * PARAMS:
1513 * infoPtr [I] Button pointer
1514 * hdc [I] Handle to device context to draw to
1515 * labelRc [I/O] Input the rect the label to be positioned in, and output the label rect
1516 * imageRc [O] Optional, output the image rect
1517 * textRc [O] Optional, output the text rect
1519 static UINT BUTTON_CalcLayoutRects(const BUTTON_INFO *infoPtr, HDC hdc, RECT *labelRc, RECT *imageRc, RECT *textRc)
1521 LONG style = GetWindowLongW( infoPtr->hwnd, GWL_STYLE );
1522 LONG ex_style = GetWindowLongW( infoPtr->hwnd, GWL_EXSTYLE );
1523 LONG split_style = infoPtr->imagelist.himl ? BUTTON_ILStoBS(infoPtr->imagelist.uAlign) : style;
1524 WCHAR *text = get_button_text(infoPtr);
1525 SIZE imageSize = BUTTON_GetImageSize(infoPtr);
1526 UINT dtStyle = BUTTON_BStoDT(style, ex_style);
1527 RECT labelRect, imageRect, imageRectWithMargin, textRect;
1528 LONG imageMarginWidth, imageMarginHeight;
1529 const RECT *textMargin = BUTTON_GetTextMargin(infoPtr);
1530 RECT emptyMargin = {0};
1531 LONG maxTextWidth;
1533 /* Calculate label rectangle according to label type */
1534 if ((imageSize.cx == 0 && imageSize.cy == 0) && (text == NULL || text[0] == '\0'))
1536 SetRectEmpty(labelRc);
1537 SetRectEmpty(imageRc);
1538 SetRectEmpty(textRc);
1539 heap_free(text);
1540 return (UINT)-1;
1543 SetRect(&imageRect, 0, 0, imageSize.cx, imageSize.cy);
1544 imageRectWithMargin = imageRect;
1545 if (infoPtr->imagelist.himl)
1547 imageRectWithMargin.top -= infoPtr->imagelist.margin.top;
1548 imageRectWithMargin.bottom += infoPtr->imagelist.margin.bottom;
1549 imageRectWithMargin.left -= infoPtr->imagelist.margin.left;
1550 imageRectWithMargin.right += infoPtr->imagelist.margin.right;
1553 /* Show image only */
1554 if (show_image_only(infoPtr))
1556 BUTTON_PositionRect(style, labelRc, &imageRect,
1557 infoPtr->imagelist.himl ? &infoPtr->imagelist.margin : &emptyMargin);
1558 labelRect = imageRect;
1559 SetRectEmpty(&textRect);
1561 else
1563 /* Get text rect */
1564 maxTextWidth = labelRc->right - labelRc->left;
1565 textRect = BUTTON_GetTextRect(infoPtr, hdc, text, maxTextWidth);
1567 /* Show image and text */
1568 if (show_image_and_text(infoPtr))
1570 RECT boundingLabelRect, boundingImageRect, boundingTextRect;
1572 /* Get label rect */
1573 /* Image list may have different alignment than the button, use the whole rect for label in this case */
1574 if (infoPtr->imagelist.himl)
1575 labelRect = *labelRc;
1576 else
1578 /* Get a label bounding rectangle to position the label in the user specified label rectangle because
1579 * text and image need to align together. */
1580 boundingLabelRect = BUTTON_GetBoundingLabelRect(split_style, &textRect, &imageRectWithMargin);
1581 BUTTON_PositionRect(split_style, labelRc, &boundingLabelRect, &emptyMargin);
1582 labelRect = boundingLabelRect;
1585 /* When imagelist has center align, use the whole rect for imagelist and text */
1586 if(infoPtr->imagelist.himl && infoPtr->imagelist.uAlign == BUTTON_IMAGELIST_ALIGN_CENTER)
1588 boundingImageRect = labelRect;
1589 boundingTextRect = labelRect;
1590 BUTTON_PositionRect(split_style, &boundingImageRect, &imageRect,
1591 infoPtr->imagelist.himl ? &infoPtr->imagelist.margin : &emptyMargin);
1592 /* Text doesn't use imagelist align */
1593 BUTTON_PositionRect(style, &boundingTextRect, &textRect, textMargin);
1595 else
1597 /* Get image rect */
1598 /* Split the label rect to two halves as two bounding rectangles for image and text */
1599 boundingImageRect = labelRect;
1600 imageMarginWidth = imageRectWithMargin.right - imageRectWithMargin.left;
1601 imageMarginHeight = imageRectWithMargin.bottom - imageRectWithMargin.top;
1602 if ((split_style & BS_CENTER) == BS_RIGHT)
1603 boundingImageRect.left = boundingImageRect.right - imageMarginWidth;
1604 else if ((split_style & BS_CENTER) == BS_LEFT)
1605 boundingImageRect.right = boundingImageRect.left + imageMarginWidth;
1606 else if ((split_style & BS_VCENTER) == BS_BOTTOM)
1607 boundingImageRect.top = boundingImageRect.bottom - imageMarginHeight;
1608 else if ((split_style & BS_VCENTER) == BS_TOP)
1609 boundingImageRect.bottom = boundingImageRect.top + imageMarginHeight;
1610 else
1611 boundingImageRect.right = boundingImageRect.left + imageMarginWidth;
1612 BUTTON_PositionRect(split_style, &boundingImageRect, &imageRect,
1613 infoPtr->imagelist.himl ? &infoPtr->imagelist.margin : &emptyMargin);
1615 /* Get text rect */
1616 SubtractRect(&boundingTextRect, &labelRect, &boundingImageRect);
1617 /* Text doesn't use imagelist align */
1618 BUTTON_PositionRect(style, &boundingTextRect, &textRect, textMargin);
1621 /* Show text only */
1622 else
1624 if (get_button_type(style) != BS_GROUPBOX)
1625 BUTTON_PositionRect(style, labelRc, &textRect, textMargin);
1626 else
1627 /* GroupBox is always top aligned */
1628 BUTTON_PositionRect((style & ~BS_VCENTER) | BS_TOP, labelRc, &textRect, textMargin);
1629 labelRect = textRect;
1630 SetRectEmpty(&imageRect);
1633 heap_free(text);
1635 CopyRect(labelRc, &labelRect);
1636 CopyRect(imageRc, &imageRect);
1637 CopyRect(textRc, &textRect);
1639 return dtStyle;
1643 /**********************************************************************
1644 * BUTTON_DrawImage
1646 * Draw the button's image into the specified rectangle.
1648 static void BUTTON_DrawImage(const BUTTON_INFO *infoPtr, HDC hdc, HBRUSH hbr, UINT flags, const RECT *rect)
1650 if (infoPtr->imagelist.himl)
1652 int i = (ImageList_GetImageCount(infoPtr->imagelist.himl) == 1) ? 0 : get_draw_state(infoPtr) - 1;
1654 ImageList_Draw(infoPtr->imagelist.himl, i, hdc, rect->left, rect->top, ILD_NORMAL);
1656 else
1658 switch (infoPtr->image_type)
1660 case IMAGE_ICON:
1661 flags |= DST_ICON;
1662 break;
1663 case IMAGE_BITMAP:
1664 flags |= DST_BITMAP;
1665 break;
1666 default:
1667 return;
1670 DrawStateW(hdc, hbr, NULL, (LPARAM)infoPtr->u.image, 0, rect->left, rect->top,
1671 rect->right - rect->left, rect->bottom - rect->top, flags);
1676 /**********************************************************************
1677 * BUTTON_DrawTextCallback
1679 * Callback function used by DrawStateW function.
1681 static BOOL CALLBACK BUTTON_DrawTextCallback(HDC hdc, LPARAM lp, WPARAM wp, int cx, int cy)
1683 RECT rc;
1685 SetRect(&rc, 0, 0, cx, cy);
1686 DrawTextW(hdc, (LPCWSTR)lp, -1, &rc, (UINT)wp);
1687 return TRUE;
1690 /**********************************************************************
1691 * BUTTON_DrawLabel
1693 * Common function for drawing button label.
1695 * FIXME:
1696 * 1. When BS_SINGLELINE is specified and text contains '\t', '\n' or '\r' in the middle, they are rendered as
1697 * squares now whereas they should be ignored.
1698 * 2. When BS_MULTILINE is specified and text contains space in the middle, the space mistakenly be rendered as newline.
1700 static void BUTTON_DrawLabel(const BUTTON_INFO *infoPtr, HDC hdc, UINT dtFlags, const RECT *imageRect,
1701 const RECT *textRect)
1703 HBRUSH hbr = 0;
1704 UINT flags = IsWindowEnabled(infoPtr->hwnd) ? DSS_NORMAL : DSS_DISABLED;
1705 LONG style = GetWindowLongW( infoPtr->hwnd, GWL_STYLE );
1706 WCHAR *text;
1708 /* FIXME: To draw disabled label in Win31 look-and-feel, we probably
1709 * must use DSS_MONO flag and COLOR_GRAYTEXT brush (or maybe DSS_UNION).
1710 * I don't have Win31 on hand to verify that, so I leave it as is.
1713 if ((style & BS_PUSHLIKE) && (infoPtr->state & BST_INDETERMINATE))
1715 hbr = GetSysColorBrush(COLOR_GRAYTEXT);
1716 flags |= DSS_MONO;
1719 if (show_image(infoPtr)) BUTTON_DrawImage(infoPtr, hdc, hbr, flags, imageRect);
1720 if (show_image_only(infoPtr)) return;
1722 /* DST_COMPLEX -- is 0 */
1723 if (!(text = get_button_text(infoPtr))) return;
1724 DrawStateW(hdc, hbr, BUTTON_DrawTextCallback, (LPARAM)text, dtFlags, textRect->left, textRect->top,
1725 textRect->right - textRect->left, textRect->bottom - textRect->top, flags);
1726 heap_free(text);
1729 /**********************************************************************
1730 * Push Button Functions
1732 static void PB_Paint( const BUTTON_INFO *infoPtr, HDC hDC, UINT action )
1734 RECT rc, labelRect, imageRect, textRect;
1735 UINT dtFlags, uState;
1736 HPEN hOldPen, hpen;
1737 HBRUSH hOldBrush;
1738 INT oldBkMode;
1739 COLORREF oldTxtColor;
1740 LRESULT cdrf;
1741 HFONT hFont;
1742 NMCUSTOMDRAW nmcd;
1743 LONG state = infoPtr->state;
1744 LONG style = GetWindowLongW( infoPtr->hwnd, GWL_STYLE );
1745 BOOL pushedState = (state & BST_PUSHED);
1746 HWND parent;
1747 HRGN hrgn;
1749 GetClientRect( infoPtr->hwnd, &rc );
1751 /* Send WM_CTLCOLOR to allow changing the font (the colors are fixed) */
1752 if ((hFont = infoPtr->font)) SelectObject( hDC, hFont );
1753 parent = GetParent(infoPtr->hwnd);
1754 if (!parent) parent = infoPtr->hwnd;
1755 SendMessageW( parent, WM_CTLCOLORBTN, (WPARAM)hDC, (LPARAM)infoPtr->hwnd );
1757 hrgn = set_control_clipping( hDC, &rc );
1759 hpen = CreatePen( PS_SOLID, 1, GetSysColor(COLOR_WINDOWFRAME));
1760 hOldPen = SelectObject(hDC, hpen);
1761 hOldBrush = SelectObject(hDC,GetSysColorBrush(COLOR_BTNFACE));
1762 oldBkMode = SetBkMode(hDC, TRANSPARENT);
1764 init_custom_draw(&nmcd, infoPtr, hDC, &rc);
1766 /* Send erase notifications */
1767 cdrf = SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
1768 if (cdrf & CDRF_SKIPDEFAULT) goto cleanup;
1770 if (get_button_type(style) == BS_DEFPUSHBUTTON)
1772 if (action != ODA_FOCUS)
1773 Rectangle(hDC, rc.left, rc.top, rc.right, rc.bottom);
1774 InflateRect( &rc, -1, -1 );
1777 /* Skip the frame drawing if only focus has changed */
1778 if (action != ODA_FOCUS)
1780 uState = DFCS_BUTTONPUSH;
1782 if (style & BS_FLAT)
1783 uState |= DFCS_MONO;
1784 else if (pushedState)
1786 if (get_button_type(style) == BS_DEFPUSHBUTTON )
1787 uState |= DFCS_FLAT;
1788 else
1789 uState |= DFCS_PUSHED;
1792 if (state & (BST_CHECKED | BST_INDETERMINATE))
1793 uState |= DFCS_CHECKED;
1795 DrawFrameControl( hDC, &rc, DFC_BUTTON, uState );
1798 if (cdrf & CDRF_NOTIFYPOSTERASE)
1800 nmcd.dwDrawStage = CDDS_POSTERASE;
1801 SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
1804 /* Send paint notifications */
1805 nmcd.dwDrawStage = CDDS_PREPAINT;
1806 cdrf = SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
1807 if (cdrf & CDRF_SKIPDEFAULT) goto cleanup;
1809 if (!(cdrf & CDRF_DOERASE) && action != ODA_FOCUS)
1811 /* draw button label */
1812 labelRect = rc;
1813 /* Shrink label rect at all sides by 2 so that the content won't touch the surrounding frame */
1814 InflateRect(&labelRect, -2, -2);
1815 dtFlags = BUTTON_CalcLayoutRects(infoPtr, hDC, &labelRect, &imageRect, &textRect);
1817 if (dtFlags != (UINT)-1L)
1819 if (pushedState) OffsetRect(&labelRect, 1, 1);
1821 oldTxtColor = SetTextColor( hDC, GetSysColor(COLOR_BTNTEXT) );
1823 BUTTON_DrawLabel(infoPtr, hDC, dtFlags, &imageRect, &textRect);
1825 SetTextColor( hDC, oldTxtColor );
1829 if (cdrf & CDRF_NOTIFYPOSTPAINT)
1831 nmcd.dwDrawStage = CDDS_POSTPAINT;
1832 SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
1834 if ((cdrf & CDRF_SKIPPOSTPAINT) || dtFlags == (UINT)-1L) goto cleanup;
1836 if (action == ODA_FOCUS || (state & BST_FOCUS))
1838 InflateRect( &rc, -2, -2 );
1839 DrawFocusRect( hDC, &rc );
1842 cleanup:
1843 SelectObject( hDC, hOldPen );
1844 SelectObject( hDC, hOldBrush );
1845 SetBkMode(hDC, oldBkMode);
1846 SelectClipRgn( hDC, hrgn );
1847 if (hrgn) DeleteObject( hrgn );
1848 DeleteObject( hpen );
1851 /**********************************************************************
1852 * Check Box & Radio Button Functions
1855 static void CB_Paint( const BUTTON_INFO *infoPtr, HDC hDC, UINT action )
1857 RECT rbox, labelRect, imageRect, textRect, client;
1858 HBRUSH hBrush;
1859 int delta, text_offset, checkBoxWidth, checkBoxHeight;
1860 UINT dtFlags;
1861 LRESULT cdrf;
1862 HFONT hFont;
1863 NMCUSTOMDRAW nmcd;
1864 LONG state = infoPtr->state;
1865 LONG style = GetWindowLongW( infoPtr->hwnd, GWL_STYLE );
1866 LONG ex_style = GetWindowLongW( infoPtr->hwnd, GWL_EXSTYLE );
1867 HWND parent;
1868 HRGN hrgn;
1870 if (style & BS_PUSHLIKE)
1872 PB_Paint( infoPtr, hDC, action );
1873 return;
1876 GetClientRect(infoPtr->hwnd, &client);
1877 rbox = labelRect = client;
1879 checkBoxWidth = 12 * GetDpiForWindow( infoPtr->hwnd ) / 96 + 1;
1880 checkBoxHeight = 12 * GetDpiForWindow( infoPtr->hwnd ) / 96 + 1;
1882 if ((hFont = infoPtr->font)) SelectObject( hDC, hFont );
1883 GetCharWidthW( hDC, '0', '0', &text_offset );
1884 text_offset /= 2;
1886 parent = GetParent(infoPtr->hwnd);
1887 if (!parent) parent = infoPtr->hwnd;
1888 hBrush = (HBRUSH)SendMessageW(parent, WM_CTLCOLORSTATIC, (WPARAM)hDC, (LPARAM)infoPtr->hwnd);
1889 if (!hBrush) /* did the app forget to call defwindowproc ? */
1890 hBrush = (HBRUSH)DefWindowProcW(parent, WM_CTLCOLORSTATIC, (WPARAM)hDC, (LPARAM)infoPtr->hwnd);
1891 hrgn = set_control_clipping( hDC, &client );
1893 if (style & BS_LEFTTEXT || ex_style & WS_EX_RIGHT)
1895 labelRect.right -= checkBoxWidth + text_offset;
1896 rbox.left = rbox.right - checkBoxWidth;
1898 else
1900 labelRect.left += checkBoxWidth + text_offset;
1901 rbox.right = checkBoxWidth;
1904 init_custom_draw(&nmcd, infoPtr, hDC, &client);
1906 /* Send erase notifications */
1907 cdrf = SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
1908 if (cdrf & CDRF_SKIPDEFAULT) goto cleanup;
1910 /* Since WM_ERASEBKGND does nothing, first prepare background */
1911 if (action == ODA_SELECT) FillRect( hDC, &rbox, hBrush );
1912 if (action == ODA_DRAWENTIRE) FillRect( hDC, &client, hBrush );
1913 if (cdrf & CDRF_NOTIFYPOSTERASE)
1915 nmcd.dwDrawStage = CDDS_POSTERASE;
1916 SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
1919 /* Draw label */
1920 client = labelRect;
1921 dtFlags = BUTTON_CalcLayoutRects(infoPtr, hDC, &labelRect, &imageRect, &textRect);
1923 /* Only adjust rbox when rtext is valid */
1924 if (dtFlags != (UINT)-1L)
1926 rbox.top = labelRect.top;
1927 rbox.bottom = labelRect.bottom;
1930 /* Send paint notifications */
1931 nmcd.dwDrawStage = CDDS_PREPAINT;
1932 cdrf = SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
1933 if (cdrf & CDRF_SKIPDEFAULT) goto cleanup;
1935 /* Draw the check-box bitmap */
1936 if (!(cdrf & CDRF_DOERASE))
1938 if (action == ODA_DRAWENTIRE || action == ODA_SELECT)
1940 UINT flags;
1942 if ((get_button_type(style) == BS_RADIOBUTTON) ||
1943 (get_button_type(style) == BS_AUTORADIOBUTTON)) flags = DFCS_BUTTONRADIO;
1944 else if (state & BST_INDETERMINATE) flags = DFCS_BUTTON3STATE;
1945 else flags = DFCS_BUTTONCHECK;
1947 if (state & (BST_CHECKED | BST_INDETERMINATE)) flags |= DFCS_CHECKED;
1948 if (state & BST_PUSHED) flags |= DFCS_PUSHED;
1949 if (style & WS_DISABLED) flags |= DFCS_INACTIVE;
1951 /* rbox must have the correct height */
1952 delta = rbox.bottom - rbox.top - checkBoxHeight;
1954 if ((style & BS_VCENTER) == BS_TOP)
1956 if (delta > 0)
1957 rbox.bottom = rbox.top + checkBoxHeight;
1958 else
1960 rbox.top -= -delta / 2 + 1;
1961 rbox.bottom = rbox.top + checkBoxHeight;
1964 else if ((style & BS_VCENTER) == BS_BOTTOM)
1966 if (delta > 0)
1967 rbox.top = rbox.bottom - checkBoxHeight;
1968 else
1970 rbox.bottom += -delta / 2 + 1;
1971 rbox.top = rbox.bottom - checkBoxHeight;
1974 else /* Default */
1976 if (delta > 0)
1978 int ofs = delta / 2;
1979 rbox.bottom -= ofs + 1;
1980 rbox.top = rbox.bottom - checkBoxHeight;
1982 else if (delta < 0)
1984 int ofs = -delta / 2;
1985 rbox.top -= ofs + 1;
1986 rbox.bottom = rbox.top + checkBoxHeight;
1990 DrawFrameControl(hDC, &rbox, DFC_BUTTON, flags);
1993 if (dtFlags != (UINT)-1L) /* Something to draw */
1994 if (action == ODA_DRAWENTIRE) BUTTON_DrawLabel(infoPtr, hDC, dtFlags, &imageRect, &textRect);
1997 if (cdrf & CDRF_NOTIFYPOSTPAINT)
1999 nmcd.dwDrawStage = CDDS_POSTPAINT;
2000 SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
2002 if ((cdrf & CDRF_SKIPPOSTPAINT) || dtFlags == (UINT)-1L) goto cleanup;
2004 /* ... and focus */
2005 if (action == ODA_FOCUS || (state & BST_FOCUS))
2007 labelRect.left--;
2008 labelRect.right++;
2009 IntersectRect(&labelRect, &labelRect, &client);
2010 DrawFocusRect(hDC, &labelRect);
2013 cleanup:
2014 SelectClipRgn( hDC, hrgn );
2015 if (hrgn) DeleteObject( hrgn );
2019 /**********************************************************************
2020 * BUTTON_CheckAutoRadioButton
2022 * hwnd is checked, uncheck every other auto radio button in group
2024 static void BUTTON_CheckAutoRadioButton( HWND hwnd )
2026 HWND parent, sibling, start;
2028 parent = GetParent(hwnd);
2029 /* make sure that starting control is not disabled or invisible */
2030 start = sibling = GetNextDlgGroupItem( parent, hwnd, TRUE );
2033 if (!sibling) break;
2034 if ((hwnd != sibling) &&
2035 ((GetWindowLongW( sibling, GWL_STYLE) & BS_TYPEMASK) == BS_AUTORADIOBUTTON))
2036 SendMessageW( sibling, BM_SETCHECK, BST_UNCHECKED, 0 );
2037 sibling = GetNextDlgGroupItem( parent, sibling, FALSE );
2038 } while (sibling != start);
2042 /**********************************************************************
2043 * Group Box Functions
2046 static void GB_Paint( const BUTTON_INFO *infoPtr, HDC hDC, UINT action )
2048 RECT labelRect, imageRect, textRect, rcFrame;
2049 HBRUSH hbr;
2050 HFONT hFont;
2051 UINT dtFlags;
2052 TEXTMETRICW tm;
2053 LONG style = GetWindowLongW( infoPtr->hwnd, GWL_STYLE );
2054 HWND parent;
2055 HRGN hrgn;
2057 if ((hFont = infoPtr->font)) SelectObject( hDC, hFont );
2058 /* GroupBox acts like static control, so it sends CTLCOLORSTATIC */
2059 parent = GetParent(infoPtr->hwnd);
2060 if (!parent) parent = infoPtr->hwnd;
2061 hbr = (HBRUSH)SendMessageW(parent, WM_CTLCOLORSTATIC, (WPARAM)hDC, (LPARAM)infoPtr->hwnd);
2062 if (!hbr) /* did the app forget to call defwindowproc ? */
2063 hbr = (HBRUSH)DefWindowProcW(parent, WM_CTLCOLORSTATIC, (WPARAM)hDC, (LPARAM)infoPtr->hwnd);
2064 GetClientRect(infoPtr->hwnd, &labelRect);
2065 rcFrame = labelRect;
2066 hrgn = set_control_clipping(hDC, &labelRect);
2068 GetTextMetricsW (hDC, &tm);
2069 rcFrame.top += (tm.tmHeight / 2) - 1;
2070 DrawEdge (hDC, &rcFrame, EDGE_ETCHED, BF_RECT | ((style & BS_FLAT) ? BF_FLAT : 0));
2072 InflateRect(&labelRect, -7, 1);
2073 dtFlags = BUTTON_CalcLayoutRects(infoPtr, hDC, &labelRect, &imageRect, &textRect);
2075 if (dtFlags != (UINT)-1)
2077 /* Because buttons have CS_PARENTDC class style, there is a chance
2078 * that label will be drawn out of client rect.
2079 * But Windows doesn't clip label's rect, so do I.
2082 /* There is 1-pixel margin at the left, right, and bottom */
2083 labelRect.left--;
2084 labelRect.right++;
2085 labelRect.bottom++;
2086 FillRect(hDC, &labelRect, hbr);
2087 labelRect.left++;
2088 labelRect.right--;
2089 labelRect.bottom--;
2091 BUTTON_DrawLabel(infoPtr, hDC, dtFlags, &imageRect, &textRect);
2093 SelectClipRgn( hDC, hrgn );
2094 if (hrgn) DeleteObject( hrgn );
2098 /**********************************************************************
2099 * User Button Functions
2102 static void UB_Paint( const BUTTON_INFO *infoPtr, HDC hDC, UINT action )
2104 RECT rc;
2105 HBRUSH hBrush;
2106 LRESULT cdrf;
2107 HFONT hFont;
2108 NMCUSTOMDRAW nmcd;
2109 LONG state = infoPtr->state;
2110 HWND parent;
2112 GetClientRect( infoPtr->hwnd, &rc);
2114 if ((hFont = infoPtr->font)) SelectObject( hDC, hFont );
2116 parent = GetParent(infoPtr->hwnd);
2117 if (!parent) parent = infoPtr->hwnd;
2118 hBrush = (HBRUSH)SendMessageW(parent, WM_CTLCOLORBTN, (WPARAM)hDC, (LPARAM)infoPtr->hwnd);
2119 if (!hBrush) /* did the app forget to call defwindowproc ? */
2120 hBrush = (HBRUSH)DefWindowProcW(parent, WM_CTLCOLORBTN, (WPARAM)hDC, (LPARAM)infoPtr->hwnd);
2122 if (action == ODA_FOCUS || (state & BST_FOCUS))
2124 init_custom_draw(&nmcd, infoPtr, hDC, &rc);
2126 /* Send erase notifications */
2127 cdrf = SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
2128 if (cdrf & CDRF_SKIPDEFAULT) goto notify;
2131 FillRect( hDC, &rc, hBrush );
2132 if (action == ODA_FOCUS || (state & BST_FOCUS))
2134 if (cdrf & CDRF_NOTIFYPOSTERASE)
2136 nmcd.dwDrawStage = CDDS_POSTERASE;
2137 SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
2140 /* Send paint notifications */
2141 nmcd.dwDrawStage = CDDS_PREPAINT;
2142 cdrf = SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
2143 if (cdrf & CDRF_SKIPDEFAULT) goto notify;
2144 if (cdrf & CDRF_NOTIFYPOSTPAINT)
2146 nmcd.dwDrawStage = CDDS_POSTPAINT;
2147 SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
2150 if (!(cdrf & CDRF_SKIPPOSTPAINT))
2151 DrawFocusRect( hDC, &rc );
2154 notify:
2155 switch (action)
2157 case ODA_FOCUS:
2158 BUTTON_NOTIFY_PARENT( infoPtr->hwnd, (state & BST_FOCUS) ? BN_SETFOCUS : BN_KILLFOCUS );
2159 break;
2161 case ODA_SELECT:
2162 BUTTON_NOTIFY_PARENT( infoPtr->hwnd, (state & BST_PUSHED) ? BN_HILITE : BN_UNHILITE );
2163 break;
2165 default:
2166 break;
2171 /**********************************************************************
2172 * Ownerdrawn Button Functions
2175 static void OB_Paint( const BUTTON_INFO *infoPtr, HDC hDC, UINT action )
2177 LONG state = infoPtr->state;
2178 DRAWITEMSTRUCT dis;
2179 LONG_PTR id = GetWindowLongPtrW( infoPtr->hwnd, GWLP_ID );
2180 HWND parent;
2181 HFONT hFont;
2182 HRGN hrgn;
2184 dis.CtlType = ODT_BUTTON;
2185 dis.CtlID = id;
2186 dis.itemID = 0;
2187 dis.itemAction = action;
2188 dis.itemState = ((state & BST_FOCUS) ? ODS_FOCUS : 0) |
2189 ((state & BST_PUSHED) ? ODS_SELECTED : 0) |
2190 (IsWindowEnabled(infoPtr->hwnd) ? 0: ODS_DISABLED);
2191 dis.hwndItem = infoPtr->hwnd;
2192 dis.hDC = hDC;
2193 dis.itemData = 0;
2194 GetClientRect( infoPtr->hwnd, &dis.rcItem );
2196 if ((hFont = infoPtr->font)) SelectObject( hDC, hFont );
2197 parent = GetParent(infoPtr->hwnd);
2198 if (!parent) parent = infoPtr->hwnd;
2199 SendMessageW( parent, WM_CTLCOLORBTN, (WPARAM)hDC, (LPARAM)infoPtr->hwnd );
2201 hrgn = set_control_clipping( hDC, &dis.rcItem );
2203 SendMessageW( GetParent(infoPtr->hwnd), WM_DRAWITEM, id, (LPARAM)&dis );
2204 SelectClipRgn( hDC, hrgn );
2205 if (hrgn) DeleteObject( hrgn );
2209 /**********************************************************************
2210 * Split Button Functions
2212 static void SB_Paint( const BUTTON_INFO *infoPtr, HDC hDC, UINT action )
2214 LONG style = GetWindowLongW(infoPtr->hwnd, GWL_STYLE);
2215 LONG state = infoPtr->state;
2216 UINT dtFlags = (UINT)-1L;
2218 RECT rc, push_rect, dropdown_rect;
2219 NMCUSTOMDRAW nmcd;
2220 HPEN pen, old_pen;
2221 HBRUSH old_brush;
2222 INT old_bk_mode;
2223 LRESULT cdrf;
2224 HWND parent;
2225 HRGN hrgn;
2227 GetClientRect(infoPtr->hwnd, &rc);
2229 /* Send WM_CTLCOLOR to allow changing the font (the colors are fixed) */
2230 if (infoPtr->font) SelectObject(hDC, infoPtr->font);
2231 if (!(parent = GetParent(infoPtr->hwnd))) parent = infoPtr->hwnd;
2232 SendMessageW(parent, WM_CTLCOLORBTN, (WPARAM)hDC, (LPARAM)infoPtr->hwnd);
2234 hrgn = set_control_clipping(hDC, &rc);
2236 pen = CreatePen(PS_SOLID, 1, GetSysColor(COLOR_WINDOWFRAME));
2237 old_pen = SelectObject(hDC, pen);
2238 old_brush = SelectObject(hDC, GetSysColorBrush(COLOR_BTNFACE));
2239 old_bk_mode = SetBkMode(hDC, TRANSPARENT);
2241 init_custom_draw(&nmcd, infoPtr, hDC, &rc);
2243 /* Send erase notifications */
2244 cdrf = SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
2245 if (cdrf & CDRF_SKIPDEFAULT) goto cleanup;
2247 if (get_button_type(style) == BS_DEFSPLITBUTTON)
2249 if (action != ODA_FOCUS)
2250 Rectangle(hDC, rc.left, rc.top, rc.right, rc.bottom);
2251 InflateRect(&rc, -1, -1);
2252 /* The split will now be off by 1 pixel, but
2253 that's exactly what Windows does as well */
2256 get_split_button_rects(infoPtr, &rc, &push_rect, &dropdown_rect);
2257 if (infoPtr->split_style & BCSS_NOSPLIT)
2258 push_rect = rc;
2260 /* Skip the frame drawing if only focus has changed */
2261 if (action != ODA_FOCUS)
2263 UINT flags = DFCS_BUTTONPUSH;
2265 if (style & BS_FLAT) flags |= DFCS_MONO;
2266 else if (state & BST_PUSHED)
2267 flags |= (get_button_type(style) == BS_DEFSPLITBUTTON)
2268 ? DFCS_FLAT : DFCS_PUSHED;
2270 if (state & (BST_CHECKED | BST_INDETERMINATE))
2271 flags |= DFCS_CHECKED;
2273 if (infoPtr->split_style & BCSS_NOSPLIT)
2274 DrawFrameControl(hDC, &push_rect, DFC_BUTTON, flags);
2275 else
2277 UINT dropdown_flags = flags & ~DFCS_CHECKED;
2279 if (state & BST_DROPDOWNPUSHED)
2280 dropdown_flags = (dropdown_flags & ~DFCS_FLAT) | DFCS_PUSHED;
2282 /* Adjust for shadow and draw order so it looks properly */
2283 if (infoPtr->split_style & BCSS_ALIGNLEFT)
2285 dropdown_rect.right++;
2286 DrawFrameControl(hDC, &dropdown_rect, DFC_BUTTON, dropdown_flags);
2287 dropdown_rect.right--;
2288 DrawFrameControl(hDC, &push_rect, DFC_BUTTON, flags);
2290 else
2292 push_rect.right++;
2293 DrawFrameControl(hDC, &push_rect, DFC_BUTTON, flags);
2294 push_rect.right--;
2295 DrawFrameControl(hDC, &dropdown_rect, DFC_BUTTON, dropdown_flags);
2300 if (cdrf & CDRF_NOTIFYPOSTERASE)
2302 nmcd.dwDrawStage = CDDS_POSTERASE;
2303 SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
2306 /* Send paint notifications */
2307 nmcd.dwDrawStage = CDDS_PREPAINT;
2308 cdrf = SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
2309 if (cdrf & CDRF_SKIPDEFAULT) goto cleanup;
2311 /* Shrink push button rect so that the content won't touch the surrounding frame */
2312 InflateRect(&push_rect, -2, -2);
2314 if (!(cdrf & CDRF_DOERASE) && action != ODA_FOCUS)
2316 COLORREF old_color = SetTextColor(hDC, GetSysColor(COLOR_BTNTEXT));
2317 RECT label_rect = push_rect, image_rect, text_rect;
2319 dtFlags = BUTTON_CalcLayoutRects(infoPtr, hDC, &label_rect, &image_rect, &text_rect);
2321 if (dtFlags != (UINT)-1L)
2322 BUTTON_DrawLabel(infoPtr, hDC, dtFlags, &image_rect, &text_rect);
2324 draw_split_button_dropdown_glyph(infoPtr, hDC, &dropdown_rect);
2325 SetTextColor(hDC, old_color);
2328 if (cdrf & CDRF_NOTIFYPOSTPAINT)
2330 nmcd.dwDrawStage = CDDS_POSTPAINT;
2331 SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
2333 if ((cdrf & CDRF_SKIPPOSTPAINT) || dtFlags == (UINT)-1L) goto cleanup;
2335 if (action == ODA_FOCUS || (state & BST_FOCUS))
2336 DrawFocusRect(hDC, &push_rect);
2338 cleanup:
2339 SelectObject(hDC, old_pen);
2340 SelectObject(hDC, old_brush);
2341 SetBkMode(hDC, old_bk_mode);
2342 SelectClipRgn(hDC, hrgn);
2343 if (hrgn) DeleteObject(hrgn);
2344 DeleteObject(pen);
2347 /* Given the full button rect of the split button, retrieve the push part and the dropdown part */
2348 static inline void get_split_button_rects(const BUTTON_INFO *infoPtr, const RECT *button_rect,
2349 RECT *push_rect, RECT *dropdown_rect)
2351 *push_rect = *dropdown_rect = *button_rect;
2353 /* The dropdown takes priority if the client rect is too small, it will only have a dropdown */
2354 if (infoPtr->split_style & BCSS_ALIGNLEFT)
2356 dropdown_rect->right = min(button_rect->left + infoPtr->glyph_size.cx, button_rect->right);
2357 push_rect->left = dropdown_rect->right;
2359 else
2361 dropdown_rect->left = max(button_rect->right - infoPtr->glyph_size.cx, button_rect->left);
2362 push_rect->right = dropdown_rect->left;
2366 /* Notify the parent if the point is within the dropdown and return TRUE (always notify if NULL) */
2367 static BOOL notify_split_button_dropdown(const BUTTON_INFO *infoPtr, const POINT *pt, HWND hwnd)
2369 NMBCDROPDOWN nmbcd;
2371 GetClientRect(hwnd, &nmbcd.rcButton);
2372 if (pt)
2374 RECT push_rect, dropdown_rect;
2376 get_split_button_rects(infoPtr, &nmbcd.rcButton, &push_rect, &dropdown_rect);
2377 if (!PtInRect(&dropdown_rect, *pt))
2378 return FALSE;
2380 /* If it's already down (set manually via BCM_SETDROPDOWNSTATE), fake the notify */
2381 if (infoPtr->state & BST_DROPDOWNPUSHED)
2382 return TRUE;
2384 SendMessageW(hwnd, BCM_SETDROPDOWNSTATE, TRUE, 0);
2386 nmbcd.hdr.hwndFrom = hwnd;
2387 nmbcd.hdr.idFrom = GetWindowLongPtrW(hwnd, GWLP_ID);
2388 nmbcd.hdr.code = BCN_DROPDOWN;
2389 SendMessageW(GetParent(hwnd), WM_NOTIFY, nmbcd.hdr.idFrom, (LPARAM)&nmbcd);
2391 SendMessageW(hwnd, BCM_SETDROPDOWNSTATE, FALSE, 0);
2392 return TRUE;
2395 /* Draw the split button dropdown glyph or image */
2396 static void draw_split_button_dropdown_glyph(const BUTTON_INFO *infoPtr, HDC hdc, RECT *rect)
2398 if (infoPtr->split_style & BCSS_IMAGE)
2400 int w, h;
2402 /* When the glyph is an image list, Windows is very buggy with BCSS_STRETCH,
2403 positions it weirdly and doesn't even stretch it, but instead extends the
2404 image, leaking into other images in the list (or black if none). Instead,
2405 we'll ignore this and just position it at center as without BCSS_STRETCH. */
2406 if (!ImageList_GetIconSize(infoPtr->glyph, &w, &h)) return;
2408 ImageList_Draw(infoPtr->glyph,
2409 (ImageList_GetImageCount(infoPtr->glyph) == 1) ? 0 : get_draw_state(infoPtr) - 1,
2410 hdc, rect->left + (rect->right - rect->left - w) / 2,
2411 rect->top + (rect->bottom - rect->top - h) / 2, ILD_NORMAL);
2413 else if (infoPtr->glyph_size.cy >= 0)
2415 /* infoPtr->glyph is a character code from Marlett */
2416 HFONT font, old_font;
2417 LOGFONTW logfont = { 0, 0, 0, 0, FW_NORMAL, 0, 0, 0, SYMBOL_CHARSET, 0, 0, 0, 0,
2418 { 'M','a','r','l','e','t','t',0 } };
2419 if (infoPtr->glyph_size.cy)
2421 /* BCSS_STRETCH preserves aspect ratio, uses minimum as size */
2422 if (infoPtr->split_style & BCSS_STRETCH)
2423 logfont.lfHeight = min(infoPtr->glyph_size.cx, infoPtr->glyph_size.cy);
2424 else
2426 logfont.lfWidth = infoPtr->glyph_size.cx;
2427 logfont.lfHeight = infoPtr->glyph_size.cy;
2430 else logfont.lfHeight = infoPtr->glyph_size.cx;
2432 if ((font = CreateFontIndirectW(&logfont)))
2434 old_font = SelectObject(hdc, font);
2435 DrawTextW(hdc, (const WCHAR*)&infoPtr->glyph, 1, rect,
2436 DT_CENTER | DT_VCENTER | DT_SINGLELINE | DT_NOCLIP | DT_NOPREFIX);
2437 SelectObject(hdc, old_font);
2438 DeleteObject(font);
2444 /**********************************************************************
2445 * Command Link Functions
2447 static void CL_Paint( const BUTTON_INFO *infoPtr, HDC hDC, UINT action )
2449 LONG style = GetWindowLongW(infoPtr->hwnd, GWL_STYLE);
2450 LONG state = infoPtr->state;
2452 RECT rc, content_rect;
2453 NMCUSTOMDRAW nmcd;
2454 HPEN pen, old_pen;
2455 HBRUSH old_brush;
2456 INT old_bk_mode;
2457 LRESULT cdrf;
2458 HWND parent;
2459 HRGN hrgn;
2461 GetClientRect(infoPtr->hwnd, &rc);
2463 /* Command Links are not affected by the button's font, and are based
2464 on the default message font. Furthermore, they are not affected by
2465 any of the alignment styles (and always align with the top-left). */
2466 if (!(parent = GetParent(infoPtr->hwnd))) parent = infoPtr->hwnd;
2467 SendMessageW(parent, WM_CTLCOLORBTN, (WPARAM)hDC, (LPARAM)infoPtr->hwnd);
2469 hrgn = set_control_clipping(hDC, &rc);
2471 pen = CreatePen(PS_SOLID, 1, GetSysColor(COLOR_WINDOWFRAME));
2472 old_pen = SelectObject(hDC, pen);
2473 old_brush = SelectObject(hDC, GetSysColorBrush(COLOR_BTNFACE));
2474 old_bk_mode = SetBkMode(hDC, TRANSPARENT);
2476 init_custom_draw(&nmcd, infoPtr, hDC, &rc);
2478 /* Send erase notifications */
2479 cdrf = SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
2480 if (cdrf & CDRF_SKIPDEFAULT) goto cleanup;
2481 content_rect = rc;
2483 if (get_button_type(style) == BS_DEFCOMMANDLINK)
2485 if (action != ODA_FOCUS)
2486 Rectangle(hDC, rc.left, rc.top, rc.right, rc.bottom);
2487 InflateRect(&rc, -1, -1);
2490 /* Skip the frame drawing if only focus has changed */
2491 if (action != ODA_FOCUS)
2493 if (!(state & (BST_HOT | BST_PUSHED | BST_CHECKED | BST_INDETERMINATE)))
2494 FillRect(hDC, &rc, GetSysColorBrush(COLOR_BTNFACE));
2495 else
2497 UINT flags = DFCS_BUTTONPUSH;
2499 if (style & BS_FLAT) flags |= DFCS_MONO;
2500 else if (state & BST_PUSHED) flags |= DFCS_PUSHED;
2502 if (state & (BST_CHECKED | BST_INDETERMINATE))
2503 flags |= DFCS_CHECKED;
2504 DrawFrameControl(hDC, &rc, DFC_BUTTON, flags);
2508 if (cdrf & CDRF_NOTIFYPOSTERASE)
2510 nmcd.dwDrawStage = CDDS_POSTERASE;
2511 SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
2514 /* Send paint notifications */
2515 nmcd.dwDrawStage = CDDS_PREPAINT;
2516 cdrf = SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
2517 if (cdrf & CDRF_SKIPDEFAULT) goto cleanup;
2519 if (!(cdrf & CDRF_DOERASE) && action != ODA_FOCUS)
2521 UINT flags = IsWindowEnabled(infoPtr->hwnd) ? DSS_NORMAL : DSS_DISABLED;
2522 COLORREF old_color = SetTextColor(hDC, GetSysColor(flags == DSS_NORMAL ?
2523 COLOR_BTNTEXT : COLOR_GRAYTEXT));
2524 HIMAGELIST defimg = NULL;
2525 NONCLIENTMETRICSW ncm;
2526 UINT txt_h = 0;
2527 SIZE img_size;
2529 /* Command Links ignore the margins of the image list or its alignment */
2530 if (infoPtr->u.image || infoPtr->imagelist.himl)
2531 img_size = BUTTON_GetImageSize(infoPtr);
2532 else
2534 img_size.cx = img_size.cy = command_link_defglyph_size;
2535 defimg = ImageList_LoadImageW(COMCTL32_hModule, (LPCWSTR)MAKEINTRESOURCE(IDB_CMDLINK),
2536 img_size.cx, 3, CLR_NONE, IMAGE_BITMAP, LR_CREATEDIBSECTION);
2539 /* Shrink rect by the command link margin, except on bottom (just the frame) */
2540 InflateRect(&content_rect, -command_link_margin, -command_link_margin);
2541 content_rect.bottom += command_link_margin - 2;
2543 ncm.cbSize = sizeof(ncm);
2544 if (SystemParametersInfoW(SPI_GETNONCLIENTMETRICS, sizeof(ncm), &ncm, 0))
2546 LONG note_weight = ncm.lfMessageFont.lfWeight;
2547 RECT r = content_rect;
2548 WCHAR *text;
2549 HFONT font;
2551 if (img_size.cx) r.left += img_size.cx + command_link_margin;
2553 /* Draw the text */
2554 ncm.lfMessageFont.lfWeight = FW_BOLD;
2555 if ((font = CreateFontIndirectW(&ncm.lfMessageFont)))
2557 if ((text = get_button_text(infoPtr)))
2559 SelectObject(hDC, font);
2560 txt_h = DrawTextW(hDC, text, -1, &r,
2561 DT_TOP | DT_LEFT | DT_WORDBREAK | DT_END_ELLIPSIS);
2562 heap_free(text);
2564 DeleteObject(font);
2567 /* Draw the note */
2568 ncm.lfMessageFont.lfWeight = note_weight;
2569 if (infoPtr->note && (font = CreateFontIndirectW(&ncm.lfMessageFont)))
2571 r.top += txt_h + 2;
2572 SelectObject(hDC, font);
2573 DrawTextW(hDC, infoPtr->note, infoPtr->note_length, &r,
2574 DT_TOP | DT_LEFT | DT_WORDBREAK | DT_NOPREFIX);
2575 DeleteObject(font);
2579 /* Position the image at the vertical center of the drawn text (not note) */
2580 txt_h = min(txt_h, content_rect.bottom - content_rect.top);
2581 if (img_size.cy < txt_h) content_rect.top += (txt_h - img_size.cy) / 2;
2583 content_rect.right = content_rect.left + img_size.cx;
2584 content_rect.bottom = content_rect.top + img_size.cy;
2586 if (defimg)
2588 int i = 0;
2589 if (flags == DSS_DISABLED) i = 2;
2590 else if (state & BST_HOT) i = 1;
2592 ImageList_Draw(defimg, i, hDC, content_rect.left, content_rect.top, ILD_NORMAL);
2593 ImageList_Destroy(defimg);
2595 else
2596 BUTTON_DrawImage(infoPtr, hDC, NULL, flags, &content_rect);
2598 SetTextColor(hDC, old_color);
2601 if (cdrf & CDRF_NOTIFYPOSTPAINT)
2603 nmcd.dwDrawStage = CDDS_POSTPAINT;
2604 SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
2606 if (cdrf & CDRF_SKIPPOSTPAINT) goto cleanup;
2608 if (action == ODA_FOCUS || (state & BST_FOCUS))
2610 InflateRect(&rc, -2, -2);
2611 DrawFocusRect(hDC, &rc);
2614 cleanup:
2615 SelectObject(hDC, old_pen);
2616 SelectObject(hDC, old_brush);
2617 SetBkMode(hDC, old_bk_mode);
2618 SelectClipRgn(hDC, hrgn);
2619 if (hrgn) DeleteObject(hrgn);
2620 DeleteObject(pen);
2624 /**********************************************************************
2625 * Themed Paint Functions
2627 static void PB_ThemedPaint(HTHEME theme, const BUTTON_INFO *infoPtr, HDC hDC, int state, UINT dtFlags, BOOL focused)
2629 RECT bgRect, textRect;
2630 HFONT font = infoPtr->font;
2631 HFONT hPrevFont = font ? SelectObject(hDC, font) : NULL;
2632 NMCUSTOMDRAW nmcd;
2633 LRESULT cdrf;
2634 HWND parent;
2635 WCHAR *text;
2637 GetClientRect(infoPtr->hwnd, &bgRect);
2638 GetThemeBackgroundContentRect(theme, hDC, BP_PUSHBUTTON, state, &bgRect, &textRect);
2639 init_custom_draw(&nmcd, infoPtr, hDC, &bgRect);
2641 parent = GetParent(infoPtr->hwnd);
2642 if (!parent) parent = infoPtr->hwnd;
2644 /* Send erase notifications */
2645 cdrf = SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
2646 if (cdrf & CDRF_SKIPDEFAULT) goto cleanup;
2648 if (IsThemeBackgroundPartiallyTransparent(theme, BP_PUSHBUTTON, state))
2649 DrawThemeParentBackground(infoPtr->hwnd, hDC, NULL);
2650 DrawThemeBackground(theme, hDC, BP_PUSHBUTTON, state, &bgRect, NULL);
2652 if (cdrf & CDRF_NOTIFYPOSTERASE)
2654 nmcd.dwDrawStage = CDDS_POSTERASE;
2655 SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
2658 /* Send paint notifications */
2659 nmcd.dwDrawStage = CDDS_PREPAINT;
2660 cdrf = SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
2661 if (cdrf & CDRF_SKIPDEFAULT) goto cleanup;
2663 if (!(cdrf & CDRF_DOERASE) && (text = get_button_text(infoPtr)))
2665 DrawThemeText(theme, hDC, BP_PUSHBUTTON, state, text, lstrlenW(text), dtFlags, 0, &textRect);
2666 heap_free(text);
2669 if (cdrf & CDRF_NOTIFYPOSTPAINT)
2671 nmcd.dwDrawStage = CDDS_POSTPAINT;
2672 SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
2674 if (cdrf & CDRF_SKIPPOSTPAINT) goto cleanup;
2676 if (focused)
2678 MARGINS margins;
2679 RECT focusRect = bgRect;
2681 GetThemeMargins(theme, hDC, BP_PUSHBUTTON, state, TMT_CONTENTMARGINS, NULL, &margins);
2683 focusRect.left += margins.cxLeftWidth;
2684 focusRect.top += margins.cyTopHeight;
2685 focusRect.right -= margins.cxRightWidth;
2686 focusRect.bottom -= margins.cyBottomHeight;
2688 DrawFocusRect( hDC, &focusRect );
2691 cleanup:
2692 if (hPrevFont) SelectObject(hDC, hPrevFont);
2695 static void CB_ThemedPaint(HTHEME theme, const BUTTON_INFO *infoPtr, HDC hDC, int state, UINT dtFlags, BOOL focused)
2697 SIZE sz;
2698 RECT bgRect, textRect;
2699 HFONT font, hPrevFont = NULL;
2700 DWORD dwStyle = GetWindowLongW(infoPtr->hwnd, GWL_STYLE);
2701 UINT btn_type = get_button_type( dwStyle );
2702 int part = (btn_type == BS_RADIOBUTTON) || (btn_type == BS_AUTORADIOBUTTON) ? BP_RADIOBUTTON : BP_CHECKBOX;
2703 NMCUSTOMDRAW nmcd;
2704 LRESULT cdrf;
2705 LOGFONTW lf;
2706 HWND parent;
2707 WCHAR *text;
2708 BOOL created_font = FALSE;
2710 HRESULT hr = GetThemeFont(theme, hDC, part, state, TMT_FONT, &lf);
2711 if (SUCCEEDED(hr)) {
2712 font = CreateFontIndirectW(&lf);
2713 if (!font)
2714 TRACE("Failed to create font\n");
2715 else {
2716 TRACE("font = %s\n", debugstr_w(lf.lfFaceName));
2717 hPrevFont = SelectObject(hDC, font);
2718 created_font = TRUE;
2720 } else {
2721 font = (HFONT)SendMessageW(infoPtr->hwnd, WM_GETFONT, 0, 0);
2722 hPrevFont = SelectObject(hDC, font);
2725 if (FAILED(GetThemePartSize(theme, hDC, part, state, NULL, TS_DRAW, &sz)))
2726 sz.cx = sz.cy = 13;
2728 GetClientRect(infoPtr->hwnd, &bgRect);
2729 GetThemeBackgroundContentRect(theme, hDC, part, state, &bgRect, &textRect);
2730 init_custom_draw(&nmcd, infoPtr, hDC, &bgRect);
2732 if (dtFlags & DT_SINGLELINE) /* Center the checkbox / radio button to the text. */
2733 bgRect.top = bgRect.top + (textRect.bottom - textRect.top - sz.cy) / 2;
2735 /* adjust for the check/radio marker */
2736 bgRect.bottom = bgRect.top + sz.cy;
2737 bgRect.right = bgRect.left + sz.cx;
2738 textRect.left = bgRect.right + 6;
2740 parent = GetParent(infoPtr->hwnd);
2741 if (!parent) parent = infoPtr->hwnd;
2743 /* Send erase notifications */
2744 cdrf = SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
2745 if (cdrf & CDRF_SKIPDEFAULT) goto cleanup;
2747 DrawThemeParentBackground(infoPtr->hwnd, hDC, NULL);
2748 DrawThemeBackground(theme, hDC, part, state, &bgRect, NULL);
2750 if (cdrf & CDRF_NOTIFYPOSTERASE)
2752 nmcd.dwDrawStage = CDDS_POSTERASE;
2753 SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
2756 /* Send paint notifications */
2757 nmcd.dwDrawStage = CDDS_PREPAINT;
2758 cdrf = SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
2759 if (cdrf & CDRF_SKIPDEFAULT) goto cleanup;
2761 text = get_button_text(infoPtr);
2762 if (!(cdrf & CDRF_DOERASE) && text)
2763 DrawThemeText(theme, hDC, part, state, text, lstrlenW(text), dtFlags, 0, &textRect);
2765 if (cdrf & CDRF_NOTIFYPOSTPAINT)
2767 nmcd.dwDrawStage = CDDS_POSTPAINT;
2768 SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
2771 if (text)
2773 if (!(cdrf & CDRF_SKIPPOSTPAINT) && focused)
2775 RECT focusRect;
2777 focusRect = textRect;
2779 DrawTextW(hDC, text, lstrlenW(text), &focusRect, dtFlags | DT_CALCRECT);
2781 if (focusRect.right < textRect.right) focusRect.right++;
2782 focusRect.bottom = textRect.bottom;
2784 DrawFocusRect( hDC, &focusRect );
2787 heap_free(text);
2790 cleanup:
2791 if (created_font) DeleteObject(font);
2792 if (hPrevFont) SelectObject(hDC, hPrevFont);
2795 static void GB_ThemedPaint(HTHEME theme, const BUTTON_INFO *infoPtr, HDC hDC, int state, UINT dtFlags, BOOL focused)
2797 RECT bgRect, textRect, contentRect;
2798 WCHAR *text = get_button_text(infoPtr);
2799 LOGFONTW lf;
2800 HFONT font, hPrevFont = NULL;
2801 BOOL created_font = FALSE;
2803 HRESULT hr = GetThemeFont(theme, hDC, BP_GROUPBOX, state, TMT_FONT, &lf);
2804 if (SUCCEEDED(hr)) {
2805 font = CreateFontIndirectW(&lf);
2806 if (!font)
2807 TRACE("Failed to create font\n");
2808 else {
2809 hPrevFont = SelectObject(hDC, font);
2810 created_font = TRUE;
2812 } else {
2813 font = (HFONT)SendMessageW(infoPtr->hwnd, WM_GETFONT, 0, 0);
2814 hPrevFont = SelectObject(hDC, font);
2817 GetClientRect(infoPtr->hwnd, &bgRect);
2818 textRect = bgRect;
2820 if (text)
2822 SIZE textExtent;
2823 GetTextExtentPoint32W(hDC, text, lstrlenW(text), &textExtent);
2824 bgRect.top += (textExtent.cy / 2);
2825 textRect.left += 10;
2826 textRect.bottom = textRect.top + textExtent.cy;
2827 textRect.right = textRect.left + textExtent.cx + 4;
2829 ExcludeClipRect(hDC, textRect.left, textRect.top, textRect.right, textRect.bottom);
2832 GetThemeBackgroundContentRect(theme, hDC, BP_GROUPBOX, state, &bgRect, &contentRect);
2833 ExcludeClipRect(hDC, contentRect.left, contentRect.top, contentRect.right, contentRect.bottom);
2835 if (IsThemeBackgroundPartiallyTransparent(theme, BP_GROUPBOX, state))
2836 DrawThemeParentBackground(infoPtr->hwnd, hDC, NULL);
2837 DrawThemeBackground(theme, hDC, BP_GROUPBOX, state, &bgRect, NULL);
2839 SelectClipRgn(hDC, NULL);
2841 if (text)
2843 InflateRect(&textRect, -2, 0);
2844 DrawThemeText(theme, hDC, BP_GROUPBOX, state, text, lstrlenW(text), 0, 0, &textRect);
2845 heap_free(text);
2848 if (created_font) DeleteObject(font);
2849 if (hPrevFont) SelectObject(hDC, hPrevFont);
2852 static void SB_ThemedPaint(HTHEME theme, const BUTTON_INFO *infoPtr, HDC hDC, int state, UINT dtFlags, BOOL focused)
2854 HFONT old_font = infoPtr->font ? SelectObject(hDC, infoPtr->font) : NULL;
2855 RECT rc, content_rect, push_rect, dropdown_rect;
2856 NMCUSTOMDRAW nmcd;
2857 LRESULT cdrf;
2858 HWND parent;
2860 GetClientRect(infoPtr->hwnd, &rc);
2861 init_custom_draw(&nmcd, infoPtr, hDC, &rc);
2863 parent = GetParent(infoPtr->hwnd);
2864 if (!parent) parent = infoPtr->hwnd;
2866 /* Send erase notifications */
2867 cdrf = SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
2868 if (cdrf & CDRF_SKIPDEFAULT) goto cleanup;
2870 if (IsThemeBackgroundPartiallyTransparent(theme, BP_PUSHBUTTON, state))
2871 DrawThemeParentBackground(infoPtr->hwnd, hDC, NULL);
2873 /* The zone outside the content is ignored for the dropdown (draws over) */
2874 GetThemeBackgroundContentRect(theme, hDC, BP_PUSHBUTTON, state, &rc, &content_rect);
2875 get_split_button_rects(infoPtr, &rc, &push_rect, &dropdown_rect);
2877 if (infoPtr->split_style & BCSS_NOSPLIT)
2879 push_rect = rc;
2880 DrawThemeBackground(theme, hDC, BP_PUSHBUTTON, state, &rc, NULL);
2882 else
2884 RECT r = { dropdown_rect.left, content_rect.top, dropdown_rect.right, content_rect.bottom };
2885 UINT edge = (infoPtr->split_style & BCSS_ALIGNLEFT) ? BF_RIGHT : BF_LEFT;
2886 const RECT *clip = NULL;
2888 /* If only the dropdown is pressed, we need to draw it separately */
2889 if (state != PBS_PRESSED && (infoPtr->state & BST_DROPDOWNPUSHED))
2891 DrawThemeBackground(theme, hDC, BP_PUSHBUTTON, PBS_PRESSED, &rc, &dropdown_rect);
2892 clip = &push_rect;
2894 DrawThemeBackground(theme, hDC, BP_PUSHBUTTON, state, &rc, clip);
2896 /* Draw the separator */
2897 DrawThemeEdge(theme, hDC, BP_PUSHBUTTON, state, &r, EDGE_ETCHED, edge, NULL);
2899 /* The content rect should be the content area of the push button */
2900 GetThemeBackgroundContentRect(theme, hDC, BP_PUSHBUTTON, state, &push_rect, &content_rect);
2903 if (cdrf & CDRF_NOTIFYPOSTERASE)
2905 nmcd.dwDrawStage = CDDS_POSTERASE;
2906 SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
2909 /* Send paint notifications */
2910 nmcd.dwDrawStage = CDDS_PREPAINT;
2911 cdrf = SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
2912 if (cdrf & CDRF_SKIPDEFAULT) goto cleanup;
2914 if (!(cdrf & CDRF_DOERASE))
2916 COLORREF old_color, color;
2917 INT old_bk_mode;
2918 WCHAR *text;
2920 if ((text = get_button_text(infoPtr)))
2922 DrawThemeText(theme, hDC, BP_PUSHBUTTON, state, text, lstrlenW(text), dtFlags, 0, &content_rect);
2923 heap_free(text);
2926 GetThemeColor(theme, BP_PUSHBUTTON, state, TMT_TEXTCOLOR, &color);
2927 old_bk_mode = SetBkMode(hDC, TRANSPARENT);
2928 old_color = SetTextColor(hDC, color);
2930 draw_split_button_dropdown_glyph(infoPtr, hDC, &dropdown_rect);
2932 SetTextColor(hDC, old_color);
2933 SetBkMode(hDC, old_bk_mode);
2936 if (cdrf & CDRF_NOTIFYPOSTPAINT)
2938 nmcd.dwDrawStage = CDDS_POSTPAINT;
2939 SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
2941 if (cdrf & CDRF_SKIPPOSTPAINT) goto cleanup;
2943 if (focused)
2945 MARGINS margins;
2947 GetThemeMargins(theme, hDC, BP_PUSHBUTTON, state, TMT_CONTENTMARGINS, NULL, &margins);
2949 push_rect.left += margins.cxLeftWidth;
2950 push_rect.top += margins.cyTopHeight;
2951 push_rect.right -= margins.cxRightWidth;
2952 push_rect.bottom -= margins.cyBottomHeight;
2953 DrawFocusRect(hDC, &push_rect);
2956 cleanup:
2957 if (old_font) SelectObject(hDC, old_font);
2960 static void CL_ThemedPaint(HTHEME theme, const BUTTON_INFO *infoPtr, HDC hDC, int state, UINT dtFlags, BOOL focused)
2962 HFONT old_font = infoPtr->font ? SelectObject(hDC, infoPtr->font) : NULL;
2963 NMCUSTOMDRAW nmcd;
2964 LRESULT cdrf;
2965 HWND parent;
2966 RECT rc;
2968 GetClientRect(infoPtr->hwnd, &rc);
2969 init_custom_draw(&nmcd, infoPtr, hDC, &rc);
2971 parent = GetParent(infoPtr->hwnd);
2972 if (!parent) parent = infoPtr->hwnd;
2974 /* Send erase notifications */
2975 cdrf = SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
2976 if (cdrf & CDRF_SKIPDEFAULT) goto cleanup;
2978 if (IsThemeBackgroundPartiallyTransparent(theme, BP_COMMANDLINK, state))
2979 DrawThemeParentBackground(infoPtr->hwnd, hDC, NULL);
2980 DrawThemeBackground(theme, hDC, BP_COMMANDLINK, state, &rc, NULL);
2982 if (cdrf & CDRF_NOTIFYPOSTERASE)
2984 nmcd.dwDrawStage = CDDS_POSTERASE;
2985 SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
2988 /* Send paint notifications */
2989 nmcd.dwDrawStage = CDDS_PREPAINT;
2990 cdrf = SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
2991 if (cdrf & CDRF_SKIPDEFAULT) goto cleanup;
2993 if (!(cdrf & CDRF_DOERASE))
2995 RECT r, img_rect;
2996 UINT txt_h = 0;
2997 SIZE img_size;
2998 WCHAR *text;
3000 GetThemeBackgroundContentRect(theme, hDC, BP_COMMANDLINK, state, &rc, &r);
3002 /* The text alignment and styles are fixed and don't depend on button styles */
3003 dtFlags = DT_TOP | DT_LEFT | DT_WORDBREAK;
3005 /* Command Links ignore the margins of the image list or its alignment */
3006 if (infoPtr->u.image || infoPtr->imagelist.himl)
3007 img_size = BUTTON_GetImageSize(infoPtr);
3008 else
3009 GetThemePartSize(theme, NULL, BP_COMMANDLINKGLYPH, state, NULL, TS_DRAW, &img_size);
3011 img_rect = r;
3012 if (img_size.cx) r.left += img_size.cx + command_link_margin;
3014 /* Draw the text */
3015 if ((text = get_button_text(infoPtr)))
3017 UINT len = lstrlenW(text);
3018 RECT text_rect;
3020 GetThemeTextExtent(theme, hDC, BP_COMMANDLINK, state, text, len,
3021 dtFlags | DT_END_ELLIPSIS, &r, &text_rect);
3022 DrawThemeText(theme, hDC, BP_COMMANDLINK, state, text, len,
3023 dtFlags | DT_END_ELLIPSIS, 0, &r);
3025 txt_h = text_rect.bottom - text_rect.top;
3026 heap_free(text);
3029 /* Draw the note */
3030 if (infoPtr->note)
3032 DTTOPTS opts;
3034 r.top += txt_h;
3035 opts.dwSize = sizeof(opts);
3036 opts.dwFlags = DTT_FONTPROP;
3037 opts.iFontPropId = TMT_BODYFONT;
3038 DrawThemeTextEx(theme, hDC, BP_COMMANDLINK, state,
3039 infoPtr->note, infoPtr->note_length,
3040 dtFlags | DT_NOPREFIX, &r, &opts);
3043 /* Position the image at the vertical center of the drawn text (not note) */
3044 txt_h = min(txt_h, img_rect.bottom - img_rect.top);
3045 if (img_size.cy < txt_h) img_rect.top += (txt_h - img_size.cy) / 2;
3047 img_rect.right = img_rect.left + img_size.cx;
3048 img_rect.bottom = img_rect.top + img_size.cy;
3050 if (infoPtr->u.image || infoPtr->imagelist.himl)
3051 BUTTON_DrawImage(infoPtr, hDC, NULL,
3052 (state == CMDLS_DISABLED) ? DSS_DISABLED : DSS_NORMAL,
3053 &img_rect);
3054 else
3055 DrawThemeBackground(theme, hDC, BP_COMMANDLINKGLYPH, state, &img_rect, NULL);
3058 if (cdrf & CDRF_NOTIFYPOSTPAINT)
3060 nmcd.dwDrawStage = CDDS_POSTPAINT;
3061 SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
3063 if (cdrf & CDRF_SKIPPOSTPAINT) goto cleanup;
3065 if (focused)
3067 MARGINS margins;
3069 /* The focus rect has margins of a push button rather than command link... */
3070 GetThemeMargins(theme, hDC, BP_PUSHBUTTON, state, TMT_CONTENTMARGINS, NULL, &margins);
3072 rc.left += margins.cxLeftWidth;
3073 rc.top += margins.cyTopHeight;
3074 rc.right -= margins.cxRightWidth;
3075 rc.bottom -= margins.cyBottomHeight;
3076 DrawFocusRect(hDC, &rc);
3079 cleanup:
3080 if (old_font) SelectObject(hDC, old_font);
3083 void BUTTON_Register(void)
3085 WNDCLASSW wndClass;
3087 memset(&wndClass, 0, sizeof(wndClass));
3088 wndClass.style = CS_GLOBALCLASS | CS_DBLCLKS | CS_VREDRAW | CS_HREDRAW | CS_PARENTDC;
3089 wndClass.lpfnWndProc = BUTTON_WindowProc;
3090 wndClass.cbClsExtra = 0;
3091 wndClass.cbWndExtra = sizeof(BUTTON_INFO *);
3092 wndClass.hCursor = LoadCursorW(0, (LPWSTR)IDC_ARROW);
3093 wndClass.hbrBackground = NULL;
3094 wndClass.lpszClassName = WC_BUTTONW;
3095 RegisterClassW(&wndClass);