Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
wine-mirror
GitHub Repository: wine-mirror/wine
Path: blob/master/dlls/comctl32_v6/button.c
5968 views
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
6
*
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.
11
*
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.
16
*
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
20
*
21
* TODO
22
* Styles
23
* - BS_NOTIFY: is it complete?
24
* - BS_RIGHTBUTTON: same as BS_LEFTTEXT
25
*
26
* Messages
27
* - WM_CHAR: Checks a (manual or automatic) check box on '+' or '=', clears it on '-' key.
28
* - WM_NCCREATE: Turns any BS_OWNERDRAW button into a BS_PUSHBUTTON button.
29
* - WM_SYSKEYUP
30
*
31
* Notifications
32
* - BCN_HOTITEMCHANGE
33
* - BN_DISABLE
34
* - BN_PUSHED/BN_HILITE
35
* + BN_KILLFOCUS: is it OK?
36
* - BN_PAINT
37
* + BN_SETFOCUS: is it OK?
38
* - BN_UNPUSHED/BN_UNHILITE
39
*
40
* Structures/Macros/Definitions
41
* - NMBCHOTITEM
42
*/
43
44
#include <stdarg.h>
45
#include <string.h>
46
#include <stdlib.h>
47
48
#define OEMRESOURCE
49
50
#include "windef.h"
51
#include "winbase.h"
52
#include "wingdi.h"
53
#include "winuser.h"
54
#include "wine/debug.h"
55
56
#include "comctl32.h"
57
#include "uiautomationclient.h"
58
59
WINE_DEFAULT_DEBUG_CHANNEL(button);
60
61
/* undocumented flags */
62
#define BUTTON_NSTATES 0x0F
63
#define BUTTON_BTNPRESSED 0x40
64
#define BUTTON_UNKNOWN2 0x20
65
#define BUTTON_UNKNOWN3 0x10
66
67
#define BUTTON_NOTIFY_PARENT(hWnd, code) \
68
do { /* Notify parent which has created this button control */ \
69
TRACE("notification " #code " sent to hwnd=%p\n", GetParent(hWnd)); \
70
SendMessageW(GetParent(hWnd), WM_COMMAND, \
71
MAKEWPARAM(GetWindowLongPtrW((hWnd),GWLP_ID), (code)), \
72
(LPARAM)(hWnd)); \
73
} while(0)
74
75
typedef struct _BUTTON_INFO
76
{
77
HWND hwnd;
78
HWND parent;
79
LONG style;
80
LONG state;
81
HFONT font;
82
WCHAR *note;
83
INT note_length;
84
DWORD image_type; /* IMAGE_BITMAP or IMAGE_ICON */
85
BUTTON_IMAGELIST imagelist;
86
UINT split_style;
87
HIMAGELIST glyph; /* this is a font character code when split_style doesn't have BCSS_IMAGE */
88
SIZE glyph_size;
89
RECT text_margin;
90
HANDLE image; /* Original handle set with BM_SETIMAGE and returned with BM_GETIMAGE. */
91
union
92
{
93
HICON icon;
94
HBITMAP bitmap;
95
HANDLE image; /* Duplicated handle used for drawing. */
96
} u;
97
} BUTTON_INFO;
98
99
static UINT BUTTON_CalcLayoutRects( const BUTTON_INFO *infoPtr, HDC hdc, RECT *labelRc, RECT *imageRc, RECT *textRc );
100
static void PB_Paint( const BUTTON_INFO *infoPtr, HDC hDC, UINT action );
101
static void CB_Paint( const BUTTON_INFO *infoPtr, HDC hDC, UINT action );
102
static void GB_Paint( const BUTTON_INFO *infoPtr, HDC hDC, UINT action );
103
static void UB_Paint( const BUTTON_INFO *infoPtr, HDC hDC, UINT action );
104
static void OB_Paint( const BUTTON_INFO *infoPtr, HDC hDC, UINT action );
105
static void SB_Paint( const BUTTON_INFO *infoPtr, HDC hDC, UINT action );
106
static void CL_Paint( const BUTTON_INFO *infoPtr, HDC hDC, UINT action );
107
static void BUTTON_CheckAutoRadioButton( HWND hwnd );
108
static void get_split_button_rects(const BUTTON_INFO*, const RECT*, RECT*, RECT*);
109
static BOOL notify_split_button_dropdown(const BUTTON_INFO*, const POINT*, HWND);
110
static void draw_split_button_dropdown_glyph(const BUTTON_INFO*, HDC, RECT*);
111
112
#define MAX_BTN_TYPE 16
113
114
static const WORD maxCheckState[MAX_BTN_TYPE] =
115
{
116
BST_UNCHECKED, /* BS_PUSHBUTTON */
117
BST_UNCHECKED, /* BS_DEFPUSHBUTTON */
118
BST_CHECKED, /* BS_CHECKBOX */
119
BST_CHECKED, /* BS_AUTOCHECKBOX */
120
BST_CHECKED, /* BS_RADIOBUTTON */
121
BST_INDETERMINATE, /* BS_3STATE */
122
BST_INDETERMINATE, /* BS_AUTO3STATE */
123
BST_UNCHECKED, /* BS_GROUPBOX */
124
BST_UNCHECKED, /* BS_USERBUTTON */
125
BST_CHECKED, /* BS_AUTORADIOBUTTON */
126
BST_UNCHECKED, /* BS_PUSHBOX */
127
BST_UNCHECKED, /* BS_OWNERDRAW */
128
BST_UNCHECKED, /* BS_SPLITBUTTON */
129
BST_UNCHECKED, /* BS_DEFSPLITBUTTON */
130
BST_UNCHECKED, /* BS_COMMANDLINK */
131
BST_UNCHECKED /* BS_DEFCOMMANDLINK */
132
};
133
134
/* Generic draw states, use get_draw_state() to get specific state for button type */
135
enum draw_state
136
{
137
STATE_NORMAL,
138
STATE_DISABLED,
139
STATE_HOT,
140
STATE_PRESSED,
141
STATE_DEFAULTED,
142
DRAW_STATE_COUNT
143
};
144
145
typedef void (*pfPaint)( const BUTTON_INFO *infoPtr, HDC hdc, UINT action );
146
147
static const pfPaint btnPaintFunc[MAX_BTN_TYPE] =
148
{
149
PB_Paint, /* BS_PUSHBUTTON */
150
PB_Paint, /* BS_DEFPUSHBUTTON */
151
CB_Paint, /* BS_CHECKBOX */
152
CB_Paint, /* BS_AUTOCHECKBOX */
153
CB_Paint, /* BS_RADIOBUTTON */
154
CB_Paint, /* BS_3STATE */
155
CB_Paint, /* BS_AUTO3STATE */
156
GB_Paint, /* BS_GROUPBOX */
157
UB_Paint, /* BS_USERBUTTON */
158
CB_Paint, /* BS_AUTORADIOBUTTON */
159
NULL, /* BS_PUSHBOX */
160
OB_Paint, /* BS_OWNERDRAW */
161
SB_Paint, /* BS_SPLITBUTTON */
162
SB_Paint, /* BS_DEFSPLITBUTTON */
163
CL_Paint, /* BS_COMMANDLINK */
164
CL_Paint /* BS_DEFCOMMANDLINK */
165
};
166
167
typedef void (*pfThemedPaint)( HTHEME theme, const BUTTON_INFO *infoPtr, HDC hdc, int drawState, UINT dtflags, BOOL focused);
168
169
static void PB_ThemedPaint( HTHEME theme, const BUTTON_INFO *infoPtr, HDC hdc, int drawState, UINT dtflags, BOOL focused);
170
static void CB_ThemedPaint( HTHEME theme, const BUTTON_INFO *infoPtr, HDC hdc, int drawState, UINT dtflags, BOOL focused);
171
static void GB_ThemedPaint( HTHEME theme, const BUTTON_INFO *infoPtr, HDC hdc, int drawState, UINT dtflags, BOOL focused);
172
static void SB_ThemedPaint( HTHEME theme, const BUTTON_INFO *infoPtr, HDC hdc, int drawState, UINT dtflags, BOOL focused);
173
static void CL_ThemedPaint( HTHEME theme, const BUTTON_INFO *infoPtr, HDC hdc, int drawState, UINT dtflags, BOOL focused);
174
175
static const pfThemedPaint btnThemedPaintFunc[MAX_BTN_TYPE] =
176
{
177
PB_ThemedPaint, /* BS_PUSHBUTTON */
178
PB_ThemedPaint, /* BS_DEFPUSHBUTTON */
179
CB_ThemedPaint, /* BS_CHECKBOX */
180
CB_ThemedPaint, /* BS_AUTOCHECKBOX */
181
CB_ThemedPaint, /* BS_RADIOBUTTON */
182
CB_ThemedPaint, /* BS_3STATE */
183
CB_ThemedPaint, /* BS_AUTO3STATE */
184
GB_ThemedPaint, /* BS_GROUPBOX */
185
NULL, /* BS_USERBUTTON */
186
CB_ThemedPaint, /* BS_AUTORADIOBUTTON */
187
NULL, /* BS_PUSHBOX */
188
NULL, /* BS_OWNERDRAW */
189
SB_ThemedPaint, /* BS_SPLITBUTTON */
190
SB_ThemedPaint, /* BS_DEFSPLITBUTTON */
191
CL_ThemedPaint, /* BS_COMMANDLINK */
192
CL_ThemedPaint /* BS_DEFCOMMANDLINK */
193
};
194
195
typedef BOOL (*pfGetIdealSize)(BUTTON_INFO *infoPtr, SIZE *size);
196
197
static BOOL PB_GetIdealSize(BUTTON_INFO *infoPtr, SIZE *size);
198
static BOOL CB_GetIdealSize(BUTTON_INFO *infoPtr, SIZE *size);
199
static BOOL GB_GetIdealSize(BUTTON_INFO *infoPtr, SIZE *size);
200
static BOOL SB_GetIdealSize(BUTTON_INFO *infoPtr, SIZE *size);
201
static BOOL CL_GetIdealSize(BUTTON_INFO *infoPtr, SIZE *size);
202
203
static const pfGetIdealSize btnGetIdealSizeFunc[MAX_BTN_TYPE] = {
204
PB_GetIdealSize, /* BS_PUSHBUTTON */
205
PB_GetIdealSize, /* BS_DEFPUSHBUTTON */
206
CB_GetIdealSize, /* BS_CHECKBOX */
207
CB_GetIdealSize, /* BS_AUTOCHECKBOX */
208
CB_GetIdealSize, /* BS_RADIOBUTTON */
209
GB_GetIdealSize, /* BS_3STATE */
210
GB_GetIdealSize, /* BS_AUTO3STATE */
211
GB_GetIdealSize, /* BS_GROUPBOX */
212
PB_GetIdealSize, /* BS_USERBUTTON */
213
CB_GetIdealSize, /* BS_AUTORADIOBUTTON */
214
GB_GetIdealSize, /* BS_PUSHBOX */
215
GB_GetIdealSize, /* BS_OWNERDRAW */
216
SB_GetIdealSize, /* BS_SPLITBUTTON */
217
SB_GetIdealSize, /* BS_DEFSPLITBUTTON */
218
CL_GetIdealSize, /* BS_COMMANDLINK */
219
CL_GetIdealSize /* BS_DEFCOMMANDLINK */
220
};
221
222
/* Fixed margin for command links, regardless of DPI (based on tests done on Windows) */
223
enum { command_link_margin = 6 };
224
225
/* The width and height for the default command link glyph (when there's no image) */
226
enum { command_link_defglyph_size = 17 };
227
228
static inline UINT get_button_type( LONG window_style )
229
{
230
return (window_style & BS_TYPEMASK);
231
}
232
233
static inline BOOL button_centers_text( LONG window_style )
234
{
235
/* Push button's text is centered by default, same for split buttons */
236
UINT type = get_button_type(window_style);
237
return type <= BS_DEFPUSHBUTTON || type == BS_SPLITBUTTON || type == BS_DEFSPLITBUTTON;
238
}
239
240
/* paint a button of any type */
241
static inline void paint_button( BUTTON_INFO *infoPtr, LONG style, UINT action )
242
{
243
if (btnPaintFunc[style] && IsWindowVisible(infoPtr->hwnd))
244
{
245
HDC hdc = GetDC( infoPtr->hwnd );
246
btnPaintFunc[style]( infoPtr, hdc, action );
247
ReleaseDC( infoPtr->hwnd, hdc );
248
}
249
}
250
251
/* retrieve the button text; returned buffer must be freed by caller */
252
static inline WCHAR *get_button_text( const BUTTON_INFO *infoPtr )
253
{
254
INT len = GetWindowTextLengthW( infoPtr->hwnd );
255
WCHAR *buffer = Alloc( (len + 1) * sizeof(WCHAR) );
256
if (buffer)
257
GetWindowTextW( infoPtr->hwnd, buffer, len + 1 );
258
return buffer;
259
}
260
261
/* get the default glyph size for split buttons */
262
static LONG get_default_glyph_size(const BUTTON_INFO *infoPtr)
263
{
264
if (infoPtr->split_style & BCSS_IMAGE)
265
{
266
/* Size it to fit, including the left and right edges */
267
int w, h;
268
if (!ImageList_GetIconSize(infoPtr->glyph, &w, &h)) w = 0;
269
return w + GetSystemMetrics(SM_CXEDGE) * 2;
270
}
271
272
/* The glyph size relies on the default menu font's cell height */
273
return GetSystemMetrics(SM_CYMENUCHECK);
274
}
275
276
static BOOL is_themed_paint_supported(HTHEME theme, UINT btn_type)
277
{
278
if (!theme || !btnThemedPaintFunc[btn_type])
279
return FALSE;
280
281
if (btn_type == BS_COMMANDLINK || btn_type == BS_DEFCOMMANDLINK)
282
{
283
if (!IsThemePartDefined(theme, BP_COMMANDLINK, 0))
284
return FALSE;
285
}
286
287
return TRUE;
288
}
289
290
static void init_custom_draw(NMCUSTOMDRAW *nmcd, const BUTTON_INFO *infoPtr, HDC hdc, const RECT *rc)
291
{
292
nmcd->hdr.hwndFrom = infoPtr->hwnd;
293
nmcd->hdr.idFrom = GetWindowLongPtrW(infoPtr->hwnd, GWLP_ID);
294
nmcd->hdr.code = NM_CUSTOMDRAW;
295
nmcd->hdc = hdc;
296
nmcd->rc = *rc;
297
nmcd->dwDrawStage = CDDS_PREERASE;
298
nmcd->dwItemSpec = 0;
299
nmcd->lItemlParam = 0;
300
nmcd->uItemState = IsWindowEnabled(infoPtr->hwnd) ? 0 : CDIS_DISABLED;
301
if (infoPtr->state & BST_PUSHED) nmcd->uItemState |= CDIS_SELECTED;
302
if (infoPtr->state & BST_FOCUS) nmcd->uItemState |= CDIS_FOCUS;
303
if (infoPtr->state & BST_HOT) nmcd->uItemState |= CDIS_HOT;
304
if (infoPtr->state & BST_INDETERMINATE)
305
nmcd->uItemState |= CDIS_INDETERMINATE;
306
307
/* Windows doesn't seem to send CDIS_CHECKED (it fails the tests) */
308
/* CDIS_SHOWKEYBOARDCUES is misleading, as the meaning is reversed */
309
/* FIXME: Handle it properly when we support keyboard cues? */
310
}
311
312
HRGN set_control_clipping( HDC hdc, const RECT *rect )
313
{
314
RECT rc = *rect;
315
HRGN hrgn = CreateRectRgn( 0, 0, 0, 0 );
316
317
if (GetClipRgn( hdc, hrgn ) != 1)
318
{
319
DeleteObject( hrgn );
320
hrgn = 0;
321
}
322
DPtoLP( hdc, (POINT *)&rc, 2 );
323
if (GetLayout( hdc ) & LAYOUT_RTL) /* compensate for the shifting done by IntersectClipRect */
324
{
325
rc.left++;
326
rc.right++;
327
}
328
IntersectClipRect( hdc, rc.left, rc.top, rc.right, rc.bottom );
329
return hrgn;
330
}
331
332
static WCHAR *heap_strndupW(const WCHAR *src, size_t length)
333
{
334
size_t size = (length + 1) * sizeof(WCHAR);
335
WCHAR *dst = Alloc(size);
336
if (dst) memcpy(dst, src, size);
337
return dst;
338
}
339
340
/**********************************************************************
341
* Convert button styles to flags used by DrawText.
342
*/
343
static UINT BUTTON_BStoDT( DWORD style, DWORD ex_style )
344
{
345
UINT dtStyle = DT_NOCLIP; /* We use SelectClipRgn to limit output */
346
347
/* "Convert" pushlike buttons to pushbuttons */
348
if (style & BS_PUSHLIKE)
349
style &= ~BS_TYPEMASK;
350
351
if (!(style & BS_MULTILINE))
352
dtStyle |= DT_SINGLELINE;
353
else
354
dtStyle |= DT_WORDBREAK;
355
356
switch (style & BS_CENTER)
357
{
358
case BS_LEFT: /* DT_LEFT is 0 */ break;
359
case BS_RIGHT: dtStyle |= DT_RIGHT; break;
360
case BS_CENTER: dtStyle |= DT_CENTER; break;
361
default:
362
if (button_centers_text(style)) dtStyle |= DT_CENTER;
363
}
364
365
if (ex_style & WS_EX_RIGHT) dtStyle = DT_RIGHT | (dtStyle & ~(DT_LEFT | DT_CENTER));
366
367
/* DrawText ignores vertical alignment for multiline text,
368
* but we use these flags to align label manually.
369
*/
370
if (get_button_type(style) != BS_GROUPBOX)
371
{
372
switch (style & BS_VCENTER)
373
{
374
case BS_TOP: /* DT_TOP is 0 */ break;
375
case BS_BOTTOM: dtStyle |= DT_BOTTOM; break;
376
case BS_VCENTER: /* fall through */
377
default: dtStyle |= DT_VCENTER; break;
378
}
379
}
380
381
return dtStyle;
382
}
383
384
static int get_draw_state(const BUTTON_INFO *infoPtr)
385
{
386
static const int pb_states[DRAW_STATE_COUNT] = { PBS_NORMAL, PBS_DISABLED, PBS_HOT, PBS_PRESSED, PBS_DEFAULTED };
387
static const int cb_states[3][DRAW_STATE_COUNT] =
388
{
389
{ CBS_UNCHECKEDNORMAL, CBS_UNCHECKEDDISABLED, CBS_UNCHECKEDHOT, CBS_UNCHECKEDPRESSED, CBS_UNCHECKEDNORMAL },
390
{ CBS_CHECKEDNORMAL, CBS_CHECKEDDISABLED, CBS_CHECKEDHOT, CBS_CHECKEDPRESSED, CBS_CHECKEDNORMAL },
391
{ CBS_MIXEDNORMAL, CBS_MIXEDDISABLED, CBS_MIXEDHOT, CBS_MIXEDPRESSED, CBS_MIXEDNORMAL }
392
};
393
static const int pushlike_cb_states[3][DRAW_STATE_COUNT] =
394
{
395
{ PBS_NORMAL, PBS_DISABLED, PBS_HOT, PBS_PRESSED, PBS_NORMAL },
396
{ PBS_PRESSED, PBS_PRESSED, PBS_PRESSED, PBS_PRESSED, PBS_PRESSED },
397
{ PBS_NORMAL, PBS_DISABLED, PBS_HOT, PBS_PRESSED, PBS_NORMAL }
398
};
399
static const int rb_states[2][DRAW_STATE_COUNT] =
400
{
401
{ RBS_UNCHECKEDNORMAL, RBS_UNCHECKEDDISABLED, RBS_UNCHECKEDHOT, RBS_UNCHECKEDPRESSED, RBS_UNCHECKEDNORMAL },
402
{ RBS_CHECKEDNORMAL, RBS_CHECKEDDISABLED, RBS_CHECKEDHOT, RBS_CHECKEDPRESSED, RBS_CHECKEDNORMAL }
403
};
404
static const int pushlike_rb_states[2][DRAW_STATE_COUNT] =
405
{
406
{ PBS_NORMAL, PBS_DISABLED, PBS_HOT, PBS_PRESSED, PBS_NORMAL },
407
{ PBS_PRESSED, PBS_PRESSED, PBS_PRESSED, PBS_PRESSED, PBS_PRESSED }
408
};
409
static const int gb_states[DRAW_STATE_COUNT] = { GBS_NORMAL, GBS_DISABLED, GBS_NORMAL, GBS_NORMAL, GBS_NORMAL };
410
LONG style = GetWindowLongW(infoPtr->hwnd, GWL_STYLE);
411
UINT type = get_button_type(style);
412
int check_state = infoPtr->state & 3;
413
enum draw_state state;
414
415
if (!IsWindowEnabled(infoPtr->hwnd))
416
state = STATE_DISABLED;
417
else if (infoPtr->state & BST_PUSHED)
418
state = STATE_PRESSED;
419
else if (infoPtr->state & BST_HOT)
420
state = STATE_HOT;
421
else if (infoPtr->state & BST_FOCUS || type == BS_DEFPUSHBUTTON || type == BS_DEFSPLITBUTTON
422
|| (type == BS_DEFCOMMANDLINK && !(style & BS_PUSHLIKE)))
423
state = STATE_DEFAULTED;
424
else
425
state = STATE_NORMAL;
426
427
switch (type)
428
{
429
case BS_PUSHBUTTON:
430
case BS_DEFPUSHBUTTON:
431
case BS_USERBUTTON:
432
case BS_SPLITBUTTON:
433
case BS_DEFSPLITBUTTON:
434
case BS_COMMANDLINK:
435
case BS_DEFCOMMANDLINK:
436
return pb_states[state];
437
case BS_CHECKBOX:
438
case BS_AUTOCHECKBOX:
439
case BS_3STATE:
440
case BS_AUTO3STATE:
441
return style & BS_PUSHLIKE ? pushlike_cb_states[check_state][state]
442
: cb_states[check_state][state];
443
case BS_RADIOBUTTON:
444
case BS_AUTORADIOBUTTON:
445
return style & BS_PUSHLIKE ? pushlike_rb_states[check_state][state]
446
: rb_states[check_state][state];
447
case BS_GROUPBOX:
448
return style & BS_PUSHLIKE ? pb_states[state] : gb_states[state];
449
default:
450
WARN("Unsupported button type 0x%08x\n", type);
451
return PBS_NORMAL;
452
}
453
}
454
455
static LRESULT CALLBACK BUTTON_WindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
456
{
457
BUTTON_INFO *infoPtr = (BUTTON_INFO *)GetWindowLongPtrW(hWnd, 0);
458
RECT rect;
459
POINT pt;
460
LONG style = GetWindowLongW( hWnd, GWL_STYLE );
461
UINT btn_type = get_button_type( style );
462
LONG state, new_state;
463
HANDLE oldHbitmap;
464
HTHEME theme;
465
466
if (!IsWindow( hWnd )) return 0;
467
468
if (!infoPtr && (uMsg != WM_NCCREATE))
469
return DefWindowProcW(hWnd, uMsg, wParam, lParam);
470
471
pt.x = (short)LOWORD(lParam);
472
pt.y = (short)HIWORD(lParam);
473
474
switch (uMsg)
475
{
476
case WM_GETDLGCODE:
477
switch(btn_type)
478
{
479
case BS_COMMANDLINK:
480
case BS_USERBUTTON:
481
case BS_PUSHBUTTON: return DLGC_BUTTON | DLGC_UNDEFPUSHBUTTON;
482
case BS_DEFCOMMANDLINK:
483
case BS_DEFPUSHBUTTON: return DLGC_BUTTON | DLGC_DEFPUSHBUTTON;
484
case BS_RADIOBUTTON:
485
case BS_AUTORADIOBUTTON: return DLGC_BUTTON | DLGC_RADIOBUTTON;
486
case BS_GROUPBOX: return DLGC_STATIC;
487
case BS_SPLITBUTTON: return DLGC_BUTTON | DLGC_UNDEFPUSHBUTTON | DLGC_WANTARROWS;
488
case BS_DEFSPLITBUTTON: return DLGC_BUTTON | DLGC_DEFPUSHBUTTON | DLGC_WANTARROWS;
489
default: return DLGC_BUTTON;
490
}
491
492
case WM_ENABLE:
493
theme = GetWindowTheme( hWnd );
494
if (theme)
495
RedrawWindow( hWnd, NULL, NULL, RDW_FRAME | RDW_INVALIDATE | RDW_UPDATENOW );
496
else
497
paint_button( infoPtr, btn_type, ODA_DRAWENTIRE );
498
break;
499
500
case WM_NCCREATE:
501
{
502
CREATESTRUCTW *cs = (CREATESTRUCTW *)lParam;
503
504
infoPtr = Alloc( sizeof(*infoPtr) );
505
SetWindowLongPtrW( hWnd, 0, (LONG_PTR)infoPtr );
506
infoPtr->hwnd = hWnd;
507
infoPtr->parent = cs->hwndParent;
508
infoPtr->style = cs->style;
509
infoPtr->split_style = BCSS_STRETCH;
510
infoPtr->glyph = (HIMAGELIST)0x36; /* Marlett down arrow char code */
511
infoPtr->glyph_size.cx = get_default_glyph_size(infoPtr);
512
return DefWindowProcW(hWnd, uMsg, wParam, lParam);
513
}
514
515
case WM_NCDESTROY:
516
SetWindowLongPtrW( hWnd, 0, 0 );
517
if (infoPtr->image_type == IMAGE_BITMAP)
518
DeleteObject(infoPtr->u.bitmap);
519
else if (infoPtr->image_type == IMAGE_ICON)
520
DestroyIcon(infoPtr->u.icon);
521
Free(infoPtr->note);
522
Free(infoPtr);
523
break;
524
525
case WM_CREATE:
526
{
527
HWND parent;
528
529
if (btn_type >= MAX_BTN_TYPE)
530
return -1; /* abort */
531
532
/* XP turns a BS_USERBUTTON into BS_PUSHBUTTON */
533
if (btn_type == BS_USERBUTTON )
534
{
535
style = (style & ~BS_TYPEMASK) | BS_PUSHBUTTON;
536
SetWindowLongW( hWnd, GWL_STYLE, style );
537
}
538
infoPtr->state = BST_UNCHECKED;
539
COMCTL32_OpenThemeForWindow( hWnd, WC_BUTTONW );
540
541
parent = GetParent( hWnd );
542
if (parent)
543
EnableThemeDialogTexture( parent, ETDT_ENABLE );
544
return 0;
545
}
546
547
case WM_DESTROY:
548
COMCTL32_CloseThemeForWindow( hWnd );
549
break;
550
551
case WM_THEMECHANGED:
552
return COMCTL32_ThemeChanged( hWnd, WC_BUTTONW, TRUE, TRUE );
553
554
case WM_ERASEBKGND:
555
if (btn_type == BS_OWNERDRAW)
556
{
557
HDC hdc = (HDC)wParam;
558
RECT rc;
559
HBRUSH hBrush;
560
HWND parent = GetParent(hWnd);
561
if (!parent) parent = hWnd;
562
hBrush = (HBRUSH)SendMessageW(parent, WM_CTLCOLORBTN, (WPARAM)hdc, (LPARAM)hWnd);
563
if (!hBrush) /* did the app forget to call defwindowproc ? */
564
hBrush = (HBRUSH)DefWindowProcW(parent, WM_CTLCOLORBTN,
565
(WPARAM)hdc, (LPARAM)hWnd);
566
GetClientRect(hWnd, &rc);
567
FillRect(hdc, &rc, hBrush);
568
}
569
return 1;
570
571
case WM_PRINTCLIENT:
572
case WM_PAINT:
573
{
574
PAINTSTRUCT ps;
575
HDC hdc;
576
577
theme = GetWindowTheme( hWnd );
578
hdc = wParam ? (HDC)wParam : BeginPaint( hWnd, &ps );
579
580
if (is_themed_paint_supported(theme, btn_type))
581
{
582
int drawState = get_draw_state(infoPtr);
583
UINT dtflags = BUTTON_BStoDT(style, GetWindowLongW(hWnd, GWL_EXSTYLE));
584
585
btnThemedPaintFunc[btn_type](theme, infoPtr, hdc, drawState, dtflags, infoPtr->state & BST_FOCUS);
586
}
587
else if (btnPaintFunc[btn_type])
588
{
589
int nOldMode = SetBkMode( hdc, OPAQUE );
590
btnPaintFunc[btn_type]( infoPtr, hdc, ODA_DRAWENTIRE );
591
SetBkMode(hdc, nOldMode); /* reset painting mode */
592
}
593
594
if ( !wParam ) EndPaint( hWnd, &ps );
595
break;
596
}
597
598
case WM_KEYDOWN:
599
if (wParam == VK_SPACE)
600
{
601
SendMessageW( hWnd, BM_SETSTATE, TRUE, 0 );
602
infoPtr->state |= BUTTON_BTNPRESSED;
603
SetCapture( hWnd );
604
}
605
else if (wParam == VK_UP || wParam == VK_DOWN)
606
{
607
/* Up and down arrows work on every button, and even with BCSS_NOSPLIT */
608
notify_split_button_dropdown(infoPtr, NULL, hWnd);
609
}
610
break;
611
612
case WM_LBUTTONDBLCLK:
613
if(style & BS_NOTIFY ||
614
btn_type == BS_RADIOBUTTON ||
615
btn_type == BS_USERBUTTON ||
616
btn_type == BS_OWNERDRAW)
617
{
618
BUTTON_NOTIFY_PARENT(hWnd, BN_DOUBLECLICKED);
619
break;
620
}
621
/* fall through */
622
case WM_LBUTTONDOWN:
623
infoPtr->state |= BUTTON_BTNPRESSED;
624
SetFocus( hWnd );
625
626
if ((btn_type == BS_SPLITBUTTON || btn_type == BS_DEFSPLITBUTTON) &&
627
!(infoPtr->split_style & BCSS_NOSPLIT) &&
628
notify_split_button_dropdown(infoPtr, &pt, hWnd))
629
{
630
infoPtr->state &= ~BUTTON_BTNPRESSED;
631
break;
632
}
633
634
SetCapture( hWnd );
635
SendMessageW( hWnd, BM_SETSTATE, TRUE, 0 );
636
break;
637
638
case WM_KEYUP:
639
if (wParam != VK_SPACE)
640
break;
641
/* fall through */
642
case WM_LBUTTONUP:
643
state = infoPtr->state;
644
if (state & BST_DROPDOWNPUSHED)
645
SendMessageW(hWnd, BCM_SETDROPDOWNSTATE, FALSE, 0);
646
if (!(state & BUTTON_BTNPRESSED)) break;
647
infoPtr->state &= BUTTON_NSTATES | BST_HOT;
648
if (!(state & BST_PUSHED))
649
{
650
ReleaseCapture();
651
break;
652
}
653
SendMessageW( hWnd, BM_SETSTATE, FALSE, 0 );
654
GetClientRect( hWnd, &rect );
655
if (uMsg == WM_KEYUP || PtInRect( &rect, pt ))
656
{
657
switch(btn_type)
658
{
659
case BS_AUTOCHECKBOX:
660
SendMessageW( hWnd, BM_SETCHECK, !(infoPtr->state & BST_CHECKED), 0 );
661
break;
662
case BS_AUTORADIOBUTTON:
663
SendMessageW( hWnd, BM_SETCHECK, TRUE, 0 );
664
break;
665
case BS_AUTO3STATE:
666
SendMessageW( hWnd, BM_SETCHECK, (infoPtr->state & BST_INDETERMINATE) ? 0 :
667
((infoPtr->state & 3) + 1), 0 );
668
break;
669
}
670
ReleaseCapture();
671
BUTTON_NOTIFY_PARENT(hWnd, BN_CLICKED);
672
}
673
else
674
{
675
ReleaseCapture();
676
}
677
break;
678
679
case WM_CAPTURECHANGED:
680
TRACE("WM_CAPTURECHANGED %p\n", hWnd);
681
if (hWnd == (HWND)lParam) break;
682
if (infoPtr->state & BUTTON_BTNPRESSED)
683
{
684
infoPtr->state &= BUTTON_NSTATES;
685
if (infoPtr->state & BST_PUSHED)
686
SendMessageW( hWnd, BM_SETSTATE, FALSE, 0 );
687
}
688
break;
689
690
case WM_MOUSEMOVE:
691
{
692
TRACKMOUSEEVENT mouse_event;
693
694
mouse_event.cbSize = sizeof(TRACKMOUSEEVENT);
695
mouse_event.dwFlags = TME_QUERY;
696
if (!TrackMouseEvent(&mouse_event) || !(mouse_event.dwFlags & (TME_HOVER | TME_LEAVE)))
697
{
698
mouse_event.dwFlags = TME_HOVER | TME_LEAVE;
699
mouse_event.hwndTrack = hWnd;
700
mouse_event.dwHoverTime = 1;
701
TrackMouseEvent(&mouse_event);
702
}
703
704
if ((wParam & MK_LBUTTON) && GetCapture() == hWnd)
705
{
706
GetClientRect( hWnd, &rect );
707
SendMessageW( hWnd, BM_SETSTATE, PtInRect(&rect, pt), 0 );
708
}
709
break;
710
}
711
712
case WM_MOUSEHOVER:
713
{
714
infoPtr->state |= BST_HOT;
715
InvalidateRect( hWnd, NULL, FALSE );
716
break;
717
}
718
719
case WM_MOUSELEAVE:
720
{
721
infoPtr->state &= ~BST_HOT;
722
InvalidateRect( hWnd, NULL, FALSE );
723
break;
724
}
725
726
case WM_SETTEXT:
727
{
728
/* Clear an old text here as Windows does */
729
if (IsWindowVisible(hWnd))
730
{
731
HDC hdc = GetDC(hWnd);
732
HBRUSH hbrush;
733
RECT client, rc;
734
HWND parent = GetParent(hWnd);
735
UINT message = (btn_type == BS_PUSHBUTTON ||
736
btn_type == BS_DEFPUSHBUTTON ||
737
btn_type == BS_USERBUTTON ||
738
btn_type == BS_OWNERDRAW) ?
739
WM_CTLCOLORBTN : WM_CTLCOLORSTATIC;
740
741
if (!parent) parent = hWnd;
742
hbrush = (HBRUSH)SendMessageW(parent, message,
743
(WPARAM)hdc, (LPARAM)hWnd);
744
if (!hbrush) /* did the app forget to call DefWindowProc ? */
745
hbrush = (HBRUSH)DefWindowProcW(parent, message,
746
(WPARAM)hdc, (LPARAM)hWnd);
747
748
GetClientRect(hWnd, &client);
749
rc = client;
750
/* FIXME: check other BS_* handlers */
751
if (btn_type == BS_GROUPBOX)
752
InflateRect(&rc, -7, 1); /* GB_Paint does this */
753
BUTTON_CalcLayoutRects(infoPtr, hdc, &rc, NULL, NULL);
754
/* Clip by client rect bounds */
755
if (rc.right > client.right) rc.right = client.right;
756
if (rc.bottom > client.bottom) rc.bottom = client.bottom;
757
FillRect(hdc, &rc, hbrush);
758
ReleaseDC(hWnd, hdc);
759
}
760
761
DefWindowProcW( hWnd, WM_SETTEXT, wParam, lParam );
762
if (btn_type == BS_GROUPBOX) /* Yes, only for BS_GROUPBOX */
763
InvalidateRect( hWnd, NULL, TRUE );
764
else if (GetWindowTheme( hWnd ))
765
RedrawWindow( hWnd, NULL, NULL, RDW_INVALIDATE | RDW_UPDATENOW );
766
else
767
paint_button( infoPtr, btn_type, ODA_DRAWENTIRE );
768
return 1; /* success. FIXME: check text length */
769
}
770
771
case BCM_SETNOTE:
772
{
773
WCHAR *note = (WCHAR *)lParam;
774
if (btn_type != BS_COMMANDLINK && btn_type != BS_DEFCOMMANDLINK)
775
{
776
SetLastError(ERROR_NOT_SUPPORTED);
777
return FALSE;
778
}
779
780
Free(infoPtr->note);
781
if (note)
782
{
783
infoPtr->note_length = lstrlenW(note);
784
infoPtr->note = heap_strndupW(note, infoPtr->note_length);
785
}
786
787
if (!note || !infoPtr->note)
788
{
789
infoPtr->note_length = 0;
790
infoPtr->note = Alloc(sizeof(WCHAR));
791
}
792
793
SetLastError(NO_ERROR);
794
return TRUE;
795
}
796
797
case BCM_GETNOTE:
798
{
799
DWORD *size = (DWORD *)wParam;
800
WCHAR *buffer = (WCHAR *)lParam;
801
INT length = 0;
802
803
if (btn_type != BS_COMMANDLINK && btn_type != BS_DEFCOMMANDLINK)
804
{
805
SetLastError(ERROR_NOT_SUPPORTED);
806
return FALSE;
807
}
808
809
if (!buffer || !size || !infoPtr->note)
810
{
811
SetLastError(ERROR_INVALID_PARAMETER);
812
return FALSE;
813
}
814
815
if (*size > 0)
816
{
817
length = min(*size - 1, infoPtr->note_length);
818
memcpy(buffer, infoPtr->note, length * sizeof(WCHAR));
819
buffer[length] = '\0';
820
}
821
822
if (*size < infoPtr->note_length + 1)
823
{
824
*size = infoPtr->note_length + 1;
825
SetLastError(ERROR_INSUFFICIENT_BUFFER);
826
return FALSE;
827
}
828
else
829
{
830
SetLastError(NO_ERROR);
831
return TRUE;
832
}
833
}
834
835
case BCM_GETNOTELENGTH:
836
{
837
if (btn_type != BS_COMMANDLINK && btn_type != BS_DEFCOMMANDLINK)
838
{
839
SetLastError(ERROR_NOT_SUPPORTED);
840
return 0;
841
}
842
843
return infoPtr->note_length;
844
}
845
846
case WM_SETFONT:
847
infoPtr->font = (HFONT)wParam;
848
if (lParam) InvalidateRect(hWnd, NULL, TRUE);
849
break;
850
851
case WM_GETFONT:
852
return (LRESULT)infoPtr->font;
853
854
case WM_SETFOCUS:
855
TRACE("WM_SETFOCUS %p\n",hWnd);
856
infoPtr->state |= BST_FOCUS;
857
858
if (btn_type == BS_OWNERDRAW)
859
paint_button( infoPtr, btn_type, ODA_FOCUS );
860
else
861
InvalidateRect(hWnd, NULL, FALSE);
862
863
if (style & BS_NOTIFY)
864
BUTTON_NOTIFY_PARENT(hWnd, BN_SETFOCUS);
865
866
if (((btn_type == BS_RADIOBUTTON) || (btn_type == BS_AUTORADIOBUTTON)) &&
867
!(infoPtr->state & (BST_CHECKED | BUTTON_BTNPRESSED)))
868
{
869
BUTTON_NOTIFY_PARENT(hWnd, BN_CLICKED);
870
}
871
break;
872
873
case WM_KILLFOCUS:
874
TRACE("WM_KILLFOCUS %p\n",hWnd);
875
infoPtr->state &= ~BST_FOCUS;
876
877
if ((infoPtr->state & BUTTON_BTNPRESSED) && GetCapture() == hWnd)
878
ReleaseCapture();
879
if (style & BS_NOTIFY)
880
BUTTON_NOTIFY_PARENT(hWnd, BN_KILLFOCUS);
881
882
InvalidateRect( hWnd, NULL, FALSE );
883
break;
884
885
case WM_SYSCOLORCHANGE:
886
InvalidateRect( hWnd, NULL, FALSE );
887
break;
888
889
case WM_GETOBJECT:
890
if ((LONG)lParam == OBJID_QUERYCLASSNAMEIDX)
891
return 0x10002;
892
break;
893
894
case BM_SETSTYLE:
895
{
896
DWORD new_btn_type;
897
898
new_btn_type= wParam & BS_TYPEMASK;
899
if (btn_type >= BS_SPLITBUTTON && new_btn_type <= BS_DEFPUSHBUTTON)
900
new_btn_type = (btn_type & ~BS_DEFPUSHBUTTON) | new_btn_type;
901
902
style = (style & ~BS_TYPEMASK) | new_btn_type;
903
SetWindowLongW( hWnd, GWL_STYLE, style );
904
905
NotifyWinEvent( EVENT_OBJECT_STATECHANGE, hWnd, OBJID_CLIENT, 0 );
906
907
/* Only redraw if lParam flag is set.*/
908
if (lParam)
909
InvalidateRect( hWnd, NULL, TRUE );
910
911
break;
912
}
913
case BM_CLICK:
914
SendMessageW( hWnd, WM_LBUTTONDOWN, 0, 0 );
915
SendMessageW( hWnd, WM_LBUTTONUP, 0, 0 );
916
break;
917
918
case BM_SETIMAGE:
919
infoPtr->image_type = (DWORD)wParam;
920
oldHbitmap = infoPtr->image;
921
infoPtr->u.image = CopyImage((HANDLE)lParam, infoPtr->image_type, 0, 0, 0);
922
infoPtr->image = (HANDLE)lParam;
923
InvalidateRect( hWnd, NULL, FALSE );
924
return (LRESULT)oldHbitmap;
925
926
case BM_GETIMAGE:
927
return (LRESULT)infoPtr->image;
928
929
case BCM_SETIMAGELIST:
930
{
931
BUTTON_IMAGELIST *imagelist = (BUTTON_IMAGELIST *)lParam;
932
933
if (!imagelist) return FALSE;
934
935
infoPtr->imagelist = *imagelist;
936
return TRUE;
937
}
938
939
case BCM_GETIMAGELIST:
940
{
941
BUTTON_IMAGELIST *imagelist = (BUTTON_IMAGELIST *)lParam;
942
943
if (!imagelist) return FALSE;
944
945
*imagelist = infoPtr->imagelist;
946
return TRUE;
947
}
948
949
case BCM_SETSPLITINFO:
950
{
951
BUTTON_SPLITINFO *info = (BUTTON_SPLITINFO*)lParam;
952
953
if (!info) return TRUE;
954
955
if (info->mask & (BCSIF_GLYPH | BCSIF_IMAGE))
956
{
957
infoPtr->split_style &= ~BCSS_IMAGE;
958
if (!(info->mask & BCSIF_GLYPH))
959
infoPtr->split_style |= BCSS_IMAGE;
960
infoPtr->glyph = info->himlGlyph;
961
infoPtr->glyph_size.cx = infoPtr->glyph_size.cy = 0;
962
}
963
964
if (info->mask & BCSIF_STYLE)
965
infoPtr->split_style = info->uSplitStyle;
966
if (info->mask & BCSIF_SIZE)
967
infoPtr->glyph_size = info->size;
968
969
/* Calculate fitting value for cx if invalid (cy is untouched) */
970
if (infoPtr->glyph_size.cx <= 0)
971
infoPtr->glyph_size.cx = get_default_glyph_size(infoPtr);
972
973
/* Windows doesn't invalidate or redraw it, so we don't, either */
974
return TRUE;
975
}
976
977
case BCM_GETSPLITINFO:
978
{
979
BUTTON_SPLITINFO *info = (BUTTON_SPLITINFO*)lParam;
980
981
if (!info) return FALSE;
982
983
if (info->mask & BCSIF_STYLE)
984
info->uSplitStyle = infoPtr->split_style;
985
if (info->mask & (BCSIF_GLYPH | BCSIF_IMAGE))
986
info->himlGlyph = infoPtr->glyph;
987
if (info->mask & BCSIF_SIZE)
988
info->size = infoPtr->glyph_size;
989
990
return TRUE;
991
}
992
993
case BM_GETCHECK:
994
return infoPtr->state & 3;
995
996
case BM_SETCHECK:
997
if (wParam > maxCheckState[btn_type]) wParam = maxCheckState[btn_type];
998
if ((btn_type == BS_RADIOBUTTON) || (btn_type == BS_AUTORADIOBUTTON))
999
{
1000
style = wParam ? style | WS_TABSTOP : style & ~WS_TABSTOP;
1001
SetWindowLongW( hWnd, GWL_STYLE, style );
1002
}
1003
if ((infoPtr->state & 3) != wParam)
1004
{
1005
infoPtr->state = (infoPtr->state & ~3) | wParam;
1006
InvalidateRect( hWnd, NULL, FALSE );
1007
NotifyWinEvent( UIA_ToggleToggleStatePropertyId, hWnd, OBJID_CLIENT, 0 );
1008
NotifyWinEvent( EVENT_OBJECT_STATECHANGE, hWnd, OBJID_CLIENT, 0 );
1009
}
1010
if ((btn_type == BS_AUTORADIOBUTTON) && (wParam == BST_CHECKED) && (style & WS_CHILD))
1011
BUTTON_CheckAutoRadioButton( hWnd );
1012
break;
1013
1014
case BM_GETSTATE:
1015
return infoPtr->state;
1016
1017
case BM_SETSTATE:
1018
state = infoPtr->state;
1019
new_state = wParam ? BST_PUSHED : 0;
1020
1021
if ((state ^ new_state) & BST_PUSHED)
1022
{
1023
if (wParam)
1024
state |= BST_PUSHED;
1025
else
1026
state &= ~BST_PUSHED;
1027
1028
if (btn_type == BS_USERBUTTON)
1029
BUTTON_NOTIFY_PARENT( hWnd, (state & BST_PUSHED) ? BN_HILITE : BN_UNHILITE );
1030
infoPtr->state = state;
1031
1032
NotifyWinEvent( EVENT_OBJECT_STATECHANGE, hWnd, OBJID_CLIENT, 0 );
1033
1034
InvalidateRect( hWnd, NULL, FALSE );
1035
}
1036
break;
1037
1038
case BCM_SETDROPDOWNSTATE:
1039
new_state = wParam ? BST_DROPDOWNPUSHED : 0;
1040
1041
if ((infoPtr->state ^ new_state) & BST_DROPDOWNPUSHED)
1042
{
1043
infoPtr->state &= ~BST_DROPDOWNPUSHED;
1044
infoPtr->state |= new_state;
1045
NotifyWinEvent( EVENT_OBJECT_STATECHANGE, hWnd, OBJID_CLIENT, 0 );
1046
NotifyWinEvent( UIA_ExpandCollapseExpandCollapseStatePropertyId, hWnd, OBJID_CLIENT, 0 );
1047
1048
/* Windows sends this twice for some reason */
1049
NotifyWinEvent( EVENT_OBJECT_STATECHANGE, hWnd, OBJID_CLIENT, 0 );
1050
NotifyWinEvent( UIA_ExpandCollapseExpandCollapseStatePropertyId, hWnd, OBJID_CLIENT, 0 );
1051
1052
InvalidateRect(hWnd, NULL, FALSE);
1053
}
1054
break;
1055
1056
case BCM_SETTEXTMARGIN:
1057
{
1058
RECT *text_margin = (RECT *)lParam;
1059
1060
if (!text_margin) return FALSE;
1061
1062
infoPtr->text_margin = *text_margin;
1063
return TRUE;
1064
}
1065
1066
case BCM_GETTEXTMARGIN:
1067
{
1068
RECT *text_margin = (RECT *)lParam;
1069
1070
if (!text_margin) return FALSE;
1071
1072
*text_margin = infoPtr->text_margin;
1073
return TRUE;
1074
}
1075
1076
case BCM_GETIDEALSIZE:
1077
{
1078
SIZE *size = (SIZE *)lParam;
1079
1080
if (!size) return FALSE;
1081
1082
return btnGetIdealSizeFunc[btn_type](infoPtr, size);
1083
}
1084
1085
case WM_NCHITTEST:
1086
if(btn_type == BS_GROUPBOX) return HTTRANSPARENT;
1087
/* fall through */
1088
default:
1089
return DefWindowProcW(hWnd, uMsg, wParam, lParam);
1090
}
1091
return 0;
1092
}
1093
1094
/* If maxWidth is zero, rectangle width is unlimited */
1095
static RECT BUTTON_GetTextRect(const BUTTON_INFO *infoPtr, HDC hdc, const WCHAR *text, LONG maxWidth)
1096
{
1097
LONG style = GetWindowLongW(infoPtr->hwnd, GWL_STYLE);
1098
LONG exStyle = GetWindowLongW(infoPtr->hwnd, GWL_EXSTYLE);
1099
UINT dtStyle = BUTTON_BStoDT(style, exStyle);
1100
HFONT hPrevFont;
1101
RECT rect = {0};
1102
1103
rect.right = maxWidth;
1104
hPrevFont = SelectObject(hdc, infoPtr->font);
1105
/* Calculate height without DT_VCENTER and DT_BOTTOM to get the correct height */
1106
DrawTextW(hdc, text, -1, &rect, (dtStyle & ~(DT_VCENTER | DT_BOTTOM)) | DT_CALCRECT);
1107
if (hPrevFont) SelectObject(hdc, hPrevFont);
1108
1109
return rect;
1110
}
1111
1112
static BOOL show_image_only(const BUTTON_INFO *infoPtr)
1113
{
1114
LONG style = GetWindowLongW(infoPtr->hwnd, GWL_STYLE);
1115
return (style & (BS_ICON | BS_BITMAP)) && (infoPtr->u.image || infoPtr->imagelist.himl);
1116
}
1117
1118
static BOOL show_image_and_text(const BUTTON_INFO *infoPtr)
1119
{
1120
LONG style = GetWindowLongW(infoPtr->hwnd, GWL_STYLE);
1121
UINT type = get_button_type(style);
1122
return !(style & (BS_ICON | BS_BITMAP))
1123
&& ((infoPtr->u.image
1124
&& (type == BS_PUSHBUTTON || type == BS_DEFPUSHBUTTON || type == BS_USERBUTTON || type == BS_SPLITBUTTON
1125
|| type == BS_DEFSPLITBUTTON || type == BS_COMMANDLINK || type == BS_DEFCOMMANDLINK))
1126
|| (infoPtr->imagelist.himl && type != BS_GROUPBOX));
1127
}
1128
1129
static BOOL show_image(const BUTTON_INFO *infoPtr)
1130
{
1131
return show_image_only(infoPtr) || show_image_and_text(infoPtr);
1132
}
1133
1134
/* Get a bounding rectangle that is large enough to contain a image and a text side by side.
1135
* Note: (left,top) of the result rectangle may not be (0,0), offset it by yourself if needed */
1136
static RECT BUTTON_GetBoundingLabelRect(LONG style, const RECT *textRect, const RECT *imageRect)
1137
{
1138
RECT labelRect;
1139
RECT rect = *imageRect;
1140
INT textWidth = textRect->right - textRect->left;
1141
INT textHeight = textRect->bottom - textRect->top;
1142
INT imageWidth = imageRect->right - imageRect->left;
1143
INT imageHeight = imageRect->bottom - imageRect->top;
1144
1145
if ((style & BS_CENTER) == BS_RIGHT)
1146
OffsetRect(&rect, textWidth, 0);
1147
else if ((style & BS_CENTER) == BS_LEFT)
1148
OffsetRect(&rect, -imageWidth, 0);
1149
else if ((style & BS_VCENTER) == BS_BOTTOM)
1150
OffsetRect(&rect, 0, textHeight);
1151
else if ((style & BS_VCENTER) == BS_TOP)
1152
OffsetRect(&rect, 0, -imageHeight);
1153
else
1154
OffsetRect(&rect, -imageWidth, 0);
1155
1156
UnionRect(&labelRect, textRect, &rect);
1157
return labelRect;
1158
}
1159
1160
/* Position a rectangle inside a bounding rectangle according to button alignment flags */
1161
static void BUTTON_PositionRect(LONG style, const RECT *outerRect, RECT *innerRect, const RECT *margin)
1162
{
1163
INT width = innerRect->right - innerRect->left;
1164
INT height = innerRect->bottom - innerRect->top;
1165
1166
if ((style & BS_PUSHLIKE) && !(style & BS_CENTER)) style |= BS_CENTER;
1167
1168
if (!(style & BS_CENTER))
1169
{
1170
if (button_centers_text(style))
1171
style |= BS_CENTER;
1172
else
1173
style |= BS_LEFT;
1174
}
1175
1176
if (!(style & BS_VCENTER))
1177
{
1178
/* Group box's text is top aligned by default */
1179
if (get_button_type(style) == BS_GROUPBOX)
1180
style |= BS_TOP;
1181
}
1182
1183
switch (style & BS_CENTER)
1184
{
1185
case BS_CENTER:
1186
/* The left and right margins are added to the inner rectangle to get a new rectangle. Then
1187
* the new rectangle is adjusted to be in the horizontal center */
1188
innerRect->left = outerRect->left + (outerRect->right - outerRect->left - width
1189
+ margin->left - margin->right) / 2;
1190
innerRect->right = innerRect->left + width;
1191
break;
1192
case BS_RIGHT:
1193
innerRect->right = outerRect->right - margin->right;
1194
innerRect->left = innerRect->right - width;
1195
break;
1196
case BS_LEFT:
1197
default:
1198
innerRect->left = outerRect->left + margin->left;
1199
innerRect->right = innerRect->left + width;
1200
break;
1201
}
1202
1203
switch (style & BS_VCENTER)
1204
{
1205
case BS_TOP:
1206
innerRect->top = outerRect->top + margin->top;
1207
innerRect->bottom = innerRect->top + height;
1208
break;
1209
case BS_BOTTOM:
1210
innerRect->bottom = outerRect->bottom - margin->bottom;
1211
innerRect->top = innerRect->bottom - height;
1212
break;
1213
case BS_VCENTER:
1214
default:
1215
/* The top and bottom margins are added to the inner rectangle to get a new rectangle. Then
1216
* the new rectangle is adjusted to be in the vertical center */
1217
innerRect->top = outerRect->top + (outerRect->bottom - outerRect->top - height
1218
+ margin->top - margin->bottom) / 2;
1219
innerRect->bottom = innerRect->top + height;
1220
break;
1221
}
1222
}
1223
1224
/* Convert imagelist align style to button align style */
1225
static UINT BUTTON_ILStoBS(UINT align)
1226
{
1227
switch (align)
1228
{
1229
case BUTTON_IMAGELIST_ALIGN_TOP:
1230
return BS_CENTER | BS_TOP;
1231
case BUTTON_IMAGELIST_ALIGN_BOTTOM:
1232
return BS_CENTER | BS_BOTTOM;
1233
case BUTTON_IMAGELIST_ALIGN_CENTER:
1234
return BS_CENTER | BS_VCENTER;
1235
case BUTTON_IMAGELIST_ALIGN_RIGHT:
1236
return BS_RIGHT | BS_VCENTER;
1237
case BUTTON_IMAGELIST_ALIGN_LEFT:
1238
default:
1239
return BS_LEFT | BS_VCENTER;
1240
}
1241
}
1242
1243
static SIZE BUTTON_GetImageSize(const BUTTON_INFO *infoPtr)
1244
{
1245
ICONINFO iconInfo;
1246
BITMAP bm = {0};
1247
SIZE size = {0};
1248
1249
/* ImageList has priority over image */
1250
if (infoPtr->imagelist.himl)
1251
{
1252
int scx, scy;
1253
ImageList_GetIconSize(infoPtr->imagelist.himl, &scx, &scy);
1254
size.cx = scx;
1255
size.cy = scy;
1256
}
1257
else if (infoPtr->u.image)
1258
{
1259
if (infoPtr->image_type == IMAGE_ICON)
1260
{
1261
GetIconInfo(infoPtr->u.icon, &iconInfo);
1262
GetObjectW(iconInfo.hbmColor, sizeof(bm), &bm);
1263
DeleteObject(iconInfo.hbmColor);
1264
DeleteObject(iconInfo.hbmMask);
1265
}
1266
else if (infoPtr->image_type == IMAGE_BITMAP)
1267
GetObjectW(infoPtr->u.bitmap, sizeof(bm), &bm);
1268
1269
size.cx = bm.bmWidth;
1270
size.cy = bm.bmHeight;
1271
}
1272
1273
return size;
1274
}
1275
1276
static const RECT *BUTTON_GetTextMargin(const BUTTON_INFO *infoPtr)
1277
{
1278
static const RECT oneMargin = {1, 1, 1, 1};
1279
1280
/* Use text margin only when showing both image and text, and image is not imagelist */
1281
if (show_image_and_text(infoPtr) && !infoPtr->imagelist.himl)
1282
return &infoPtr->text_margin;
1283
else
1284
return &oneMargin;
1285
}
1286
1287
static void BUTTON_GetClientRectSize(BUTTON_INFO *infoPtr, SIZE *size)
1288
{
1289
RECT rect;
1290
GetClientRect(infoPtr->hwnd, &rect);
1291
size->cx = rect.right - rect.left;
1292
size->cy = rect.bottom - rect.top;
1293
}
1294
1295
static void BUTTON_GetTextIdealSize(BUTTON_INFO *infoPtr, LONG maxWidth, SIZE *size)
1296
{
1297
WCHAR *text = get_button_text(infoPtr);
1298
HDC hdc;
1299
RECT rect;
1300
const RECT *margin = BUTTON_GetTextMargin(infoPtr);
1301
1302
if (maxWidth != 0)
1303
{
1304
maxWidth -= margin->right + margin->right;
1305
if (maxWidth <= 0) maxWidth = 1;
1306
}
1307
1308
hdc = GetDC(infoPtr->hwnd);
1309
rect = BUTTON_GetTextRect(infoPtr, hdc, text, maxWidth);
1310
ReleaseDC(infoPtr->hwnd, hdc);
1311
Free(text);
1312
1313
size->cx = rect.right - rect.left + margin->left + margin->right;
1314
size->cy = rect.bottom - rect.top + margin->top + margin->bottom;
1315
}
1316
1317
static void BUTTON_GetLabelIdealSize(BUTTON_INFO *infoPtr, LONG maxWidth, SIZE *size)
1318
{
1319
LONG style = GetWindowLongW(infoPtr->hwnd, GWL_STYLE);
1320
SIZE imageSize;
1321
SIZE textSize;
1322
BOOL horizontal;
1323
1324
imageSize = BUTTON_GetImageSize(infoPtr);
1325
if (infoPtr->imagelist.himl)
1326
{
1327
imageSize.cx += infoPtr->imagelist.margin.left + infoPtr->imagelist.margin.right;
1328
imageSize.cy += infoPtr->imagelist.margin.top + infoPtr->imagelist.margin.bottom;
1329
if (infoPtr->imagelist.uAlign == BUTTON_IMAGELIST_ALIGN_TOP
1330
|| infoPtr->imagelist.uAlign == BUTTON_IMAGELIST_ALIGN_BOTTOM)
1331
horizontal = FALSE;
1332
else
1333
horizontal = TRUE;
1334
}
1335
else
1336
{
1337
/* horizontal alignment flags has priority over vertical ones if both are specified */
1338
if (!(style & (BS_CENTER | BS_VCENTER)) || ((style & BS_CENTER) && (style & BS_CENTER) != BS_CENTER)
1339
|| !(style & BS_VCENTER) || (style & BS_VCENTER) == BS_VCENTER)
1340
horizontal = TRUE;
1341
else
1342
horizontal = FALSE;
1343
}
1344
1345
if (horizontal)
1346
{
1347
if (maxWidth != 0)
1348
{
1349
maxWidth -= imageSize.cx;
1350
if (maxWidth <= 0) maxWidth = 1;
1351
}
1352
BUTTON_GetTextIdealSize(infoPtr, maxWidth, &textSize);
1353
size->cx = textSize.cx + imageSize.cx;
1354
size->cy = max(textSize.cy, imageSize.cy);
1355
}
1356
else
1357
{
1358
BUTTON_GetTextIdealSize(infoPtr, maxWidth, &textSize);
1359
size->cx = max(textSize.cx, imageSize.cx);
1360
size->cy = textSize.cy + imageSize.cy;
1361
}
1362
}
1363
1364
static BOOL GB_GetIdealSize(BUTTON_INFO *infoPtr, SIZE *size)
1365
{
1366
BUTTON_GetClientRectSize(infoPtr, size);
1367
return TRUE;
1368
}
1369
1370
static BOOL CB_GetIdealSize(BUTTON_INFO *infoPtr, SIZE *size)
1371
{
1372
LONG style = GetWindowLongW(infoPtr->hwnd, GWL_STYLE);
1373
HDC hdc;
1374
HFONT hfont;
1375
SIZE labelSize;
1376
INT textOffset;
1377
double scaleX;
1378
double scaleY;
1379
LONG checkboxWidth, checkboxHeight;
1380
LONG maxWidth = 0;
1381
1382
if (SendMessageW(infoPtr->hwnd, WM_GETTEXTLENGTH, 0, 0) == 0)
1383
{
1384
BUTTON_GetClientRectSize(infoPtr, size);
1385
return TRUE;
1386
}
1387
1388
hdc = GetDC(infoPtr->hwnd);
1389
scaleX = GetDeviceCaps(hdc, LOGPIXELSX) / 96.0;
1390
scaleY = GetDeviceCaps(hdc, LOGPIXELSY) / 96.0;
1391
if ((hfont = infoPtr->font)) SelectObject(hdc, hfont);
1392
GetCharWidthW(hdc, '0', '0', &textOffset);
1393
textOffset /= 2;
1394
ReleaseDC(infoPtr->hwnd, hdc);
1395
1396
checkboxWidth = 12 * scaleX + 1;
1397
checkboxHeight = 12 * scaleY + 1;
1398
if (size->cx)
1399
{
1400
maxWidth = size->cx - checkboxWidth - textOffset;
1401
if (maxWidth <= 0) maxWidth = 1;
1402
}
1403
1404
/* Checkbox doesn't support both image(but not image list) and text */
1405
if (!(style & (BS_ICON | BS_BITMAP)) && infoPtr->u.image)
1406
BUTTON_GetTextIdealSize(infoPtr, maxWidth, &labelSize);
1407
else
1408
BUTTON_GetLabelIdealSize(infoPtr, maxWidth, &labelSize);
1409
1410
size->cx = labelSize.cx + checkboxWidth + textOffset;
1411
size->cy = max(labelSize.cy, checkboxHeight);
1412
1413
return TRUE;
1414
}
1415
1416
static BOOL PB_GetIdealSize(BUTTON_INFO *infoPtr, SIZE *size)
1417
{
1418
SIZE labelSize;
1419
1420
if (SendMessageW(infoPtr->hwnd, WM_GETTEXTLENGTH, 0, 0) == 0)
1421
BUTTON_GetClientRectSize(infoPtr, size);
1422
else
1423
{
1424
/* Ideal size include text size even if image only flags(BS_ICON, BS_BITMAP) are specified */
1425
BUTTON_GetLabelIdealSize(infoPtr, size->cx, &labelSize);
1426
1427
size->cx = labelSize.cx;
1428
size->cy = labelSize.cy;
1429
}
1430
return TRUE;
1431
}
1432
1433
static BOOL SB_GetIdealSize(BUTTON_INFO *infoPtr, SIZE *size)
1434
{
1435
LONG extra_width = infoPtr->glyph_size.cx * 2 + GetSystemMetrics(SM_CXEDGE);
1436
SIZE label_size;
1437
1438
if (SendMessageW(infoPtr->hwnd, WM_GETTEXTLENGTH, 0, 0) == 0)
1439
{
1440
BUTTON_GetClientRectSize(infoPtr, size);
1441
size->cx = max(size->cx, extra_width);
1442
}
1443
else
1444
{
1445
BUTTON_GetLabelIdealSize(infoPtr, size->cx, &label_size);
1446
size->cx = label_size.cx + ((size->cx == 0) ? extra_width : 0);
1447
size->cy = label_size.cy;
1448
}
1449
return TRUE;
1450
}
1451
1452
static BOOL CL_GetIdealSize(BUTTON_INFO *infoPtr, SIZE *size)
1453
{
1454
HTHEME theme = GetWindowTheme(infoPtr->hwnd);
1455
HDC hdc = GetDC(infoPtr->hwnd);
1456
LONG w, text_w = 0, text_h = 0;
1457
UINT flags = DT_TOP | DT_LEFT;
1458
HFONT font, old_font = NULL;
1459
RECT text_bound = { 0 };
1460
SIZE img_size;
1461
RECT margin;
1462
WCHAR *text;
1463
1464
/* Get the image size */
1465
if (infoPtr->u.image || infoPtr->imagelist.himl)
1466
img_size = BUTTON_GetImageSize(infoPtr);
1467
else
1468
{
1469
if (theme)
1470
GetThemePartSize(theme, NULL, BP_COMMANDLINKGLYPH, CMDLS_NORMAL, NULL, TS_DRAW, &img_size);
1471
else
1472
img_size.cx = img_size.cy = command_link_defglyph_size;
1473
}
1474
1475
/* Get the content margins */
1476
if (theme)
1477
{
1478
RECT r = { 0, 0, 0xffff, 0xffff };
1479
GetThemeBackgroundContentRect(theme, hdc, BP_COMMANDLINK, CMDLS_NORMAL, &r, &margin);
1480
margin.left -= r.left;
1481
margin.top -= r.top;
1482
margin.right = r.right - margin.right;
1483
margin.bottom = r.bottom - margin.bottom;
1484
}
1485
else
1486
{
1487
margin.left = margin.right = command_link_margin;
1488
margin.top = margin.bottom = command_link_margin;
1489
}
1490
1491
/* Account for the border margins and the margin between image and text */
1492
w = margin.left + margin.right + (img_size.cx ? (img_size.cx + command_link_margin) : 0);
1493
1494
/* If a rectangle with a specific width was requested, bound the text to it */
1495
if (size->cx > w)
1496
{
1497
text_bound.right = size->cx - w;
1498
flags |= DT_WORDBREAK;
1499
}
1500
1501
if (theme)
1502
{
1503
if (infoPtr->font) old_font = SelectObject(hdc, infoPtr->font);
1504
1505
/* Find the text's rect */
1506
if ((text = get_button_text(infoPtr)))
1507
{
1508
RECT r;
1509
GetThemeTextExtent(theme, hdc, BP_COMMANDLINK, CMDLS_NORMAL,
1510
text, -1, flags, &text_bound, &r);
1511
Free(text);
1512
text_w = r.right - r.left;
1513
text_h = r.bottom - r.top;
1514
}
1515
1516
/* Find the note's rect */
1517
if (infoPtr->note)
1518
{
1519
DTTOPTS opts;
1520
1521
opts.dwSize = sizeof(opts);
1522
opts.dwFlags = DTT_FONTPROP | DTT_CALCRECT;
1523
opts.iFontPropId = TMT_BODYFONT;
1524
DrawThemeTextEx(theme, hdc, BP_COMMANDLINK, CMDLS_NORMAL,
1525
infoPtr->note, infoPtr->note_length,
1526
flags | DT_NOPREFIX | DT_CALCRECT, &text_bound, &opts);
1527
text_w = max(text_w, text_bound.right - text_bound.left);
1528
text_h += text_bound.bottom - text_bound.top;
1529
}
1530
}
1531
else
1532
{
1533
NONCLIENTMETRICSW ncm;
1534
1535
ncm.cbSize = sizeof(ncm);
1536
if (SystemParametersInfoW(SPI_GETNONCLIENTMETRICS, sizeof(ncm), &ncm, 0))
1537
{
1538
LONG note_weight = ncm.lfMessageFont.lfWeight;
1539
1540
/* Find the text's rect */
1541
ncm.lfMessageFont.lfWeight = FW_BOLD;
1542
if ((font = CreateFontIndirectW(&ncm.lfMessageFont)))
1543
{
1544
if ((text = get_button_text(infoPtr)))
1545
{
1546
RECT r = text_bound;
1547
old_font = SelectObject(hdc, font);
1548
DrawTextW(hdc, text, -1, &r, flags | DT_CALCRECT);
1549
Free(text);
1550
1551
text_w = r.right - r.left;
1552
text_h = r.bottom - r.top;
1553
}
1554
DeleteObject(font);
1555
}
1556
1557
/* Find the note's rect */
1558
ncm.lfMessageFont.lfWeight = note_weight;
1559
if (infoPtr->note && (font = CreateFontIndirectW(&ncm.lfMessageFont)))
1560
{
1561
HFONT tmp = SelectObject(hdc, font);
1562
if (!old_font) old_font = tmp;
1563
1564
DrawTextW(hdc, infoPtr->note, infoPtr->note_length, &text_bound,
1565
flags | DT_NOPREFIX | DT_CALCRECT);
1566
DeleteObject(font);
1567
1568
text_w = max(text_w, text_bound.right - text_bound.left);
1569
text_h += text_bound.bottom - text_bound.top + 2;
1570
}
1571
}
1572
}
1573
w += text_w;
1574
1575
size->cx = min(size->cx, w);
1576
size->cy = max(text_h, img_size.cy) + margin.top + margin.bottom;
1577
1578
if (old_font) SelectObject(hdc, old_font);
1579
ReleaseDC(infoPtr->hwnd, hdc);
1580
return TRUE;
1581
}
1582
1583
/**********************************************************************
1584
* BUTTON_CalcLayoutRects
1585
*
1586
* Calculates the rectangles of the button label(image and text) and its parts depending on a button's style.
1587
*
1588
* Returns flags to be passed to DrawText.
1589
* Calculated rectangle doesn't take into account button state
1590
* (pushed, etc.). If there is nothing to draw (no text/image) output
1591
* rectangle is empty, and return value is (UINT)-1.
1592
*
1593
* PARAMS:
1594
* infoPtr [I] Button pointer
1595
* hdc [I] Handle to device context to draw to
1596
* labelRc [I/O] Input the rect the label to be positioned in, and output the label rect
1597
* imageRc [O] Optional, output the image rect
1598
* textRc [O] Optional, output the text rect
1599
*/
1600
static UINT BUTTON_CalcLayoutRects(const BUTTON_INFO *infoPtr, HDC hdc, RECT *labelRc, RECT *imageRc, RECT *textRc)
1601
{
1602
WCHAR *text = get_button_text(infoPtr);
1603
SIZE imageSize = BUTTON_GetImageSize(infoPtr);
1604
RECT labelRect, imageRect, imageRectWithMargin, textRect;
1605
LONG imageMarginWidth, imageMarginHeight;
1606
const RECT *textMargin = BUTTON_GetTextMargin(infoPtr);
1607
LONG style, ex_style, split_style;
1608
RECT emptyMargin = {0};
1609
LONG maxTextWidth;
1610
UINT dtStyle;
1611
1612
/* Calculate label rectangle according to label type */
1613
if ((imageSize.cx == 0 && imageSize.cy == 0) && (text == NULL || text[0] == '\0'))
1614
{
1615
SetRectEmpty(labelRc);
1616
SetRectEmpty(imageRc);
1617
SetRectEmpty(textRc);
1618
Free(text);
1619
return (UINT)-1;
1620
}
1621
1622
style = GetWindowLongW(infoPtr->hwnd, GWL_STYLE);
1623
ex_style = GetWindowLongW(infoPtr->hwnd, GWL_EXSTYLE);
1624
/* Add BS_RIGHT directly. When both WS_EX_RIGHT and BS_LEFT are present, it becomes BS_CENTER */
1625
if (ex_style & WS_EX_RIGHT)
1626
style |= BS_RIGHT;
1627
split_style = infoPtr->imagelist.himl ? BUTTON_ILStoBS(infoPtr->imagelist.uAlign) : style;
1628
dtStyle = BUTTON_BStoDT(style, ex_style);
1629
1630
/* Group boxes are top aligned unless BS_PUSHLIKE is set and it's not themed */
1631
if (get_button_type(style) == BS_GROUPBOX
1632
&& (!(style & BS_PUSHLIKE) || GetWindowTheme(infoPtr->hwnd)))
1633
style &= ~BS_VCENTER | BS_TOP;
1634
1635
SetRect(&imageRect, 0, 0, imageSize.cx, imageSize.cy);
1636
imageRectWithMargin = imageRect;
1637
if (infoPtr->imagelist.himl)
1638
{
1639
imageRectWithMargin.top -= infoPtr->imagelist.margin.top;
1640
imageRectWithMargin.bottom += infoPtr->imagelist.margin.bottom;
1641
imageRectWithMargin.left -= infoPtr->imagelist.margin.left;
1642
imageRectWithMargin.right += infoPtr->imagelist.margin.right;
1643
}
1644
1645
/* Show image only */
1646
if (show_image_only(infoPtr))
1647
{
1648
BUTTON_PositionRect(style, labelRc, &imageRect,
1649
infoPtr->imagelist.himl ? &infoPtr->imagelist.margin : &emptyMargin);
1650
labelRect = imageRect;
1651
SetRectEmpty(&textRect);
1652
}
1653
else
1654
{
1655
/* Get text rect */
1656
maxTextWidth = labelRc->right - labelRc->left;
1657
textRect = BUTTON_GetTextRect(infoPtr, hdc, text, maxTextWidth);
1658
1659
/* Show image and text */
1660
if (show_image_and_text(infoPtr))
1661
{
1662
RECT boundingLabelRect, boundingImageRect, boundingTextRect;
1663
1664
/* Get label rect */
1665
/* Image list may have different alignment than the button, use the whole rect for label in this case */
1666
if (infoPtr->imagelist.himl)
1667
labelRect = *labelRc;
1668
else
1669
{
1670
/* Get a label bounding rectangle to position the label in the user specified label rectangle because
1671
* text and image need to align together. */
1672
boundingLabelRect = BUTTON_GetBoundingLabelRect(split_style, &textRect, &imageRectWithMargin);
1673
BUTTON_PositionRect(split_style, labelRc, &boundingLabelRect, &emptyMargin);
1674
labelRect = boundingLabelRect;
1675
}
1676
1677
/* When imagelist has center align, use the whole rect for imagelist and text */
1678
if(infoPtr->imagelist.himl && infoPtr->imagelist.uAlign == BUTTON_IMAGELIST_ALIGN_CENTER)
1679
{
1680
boundingImageRect = labelRect;
1681
boundingTextRect = labelRect;
1682
BUTTON_PositionRect(split_style, &boundingImageRect, &imageRect,
1683
infoPtr->imagelist.himl ? &infoPtr->imagelist.margin : &emptyMargin);
1684
/* Text doesn't use imagelist align */
1685
BUTTON_PositionRect(style, &boundingTextRect, &textRect, textMargin);
1686
}
1687
else
1688
{
1689
/* Get image rect */
1690
/* Split the label rect to two halves as two bounding rectangles for image and text */
1691
boundingImageRect = labelRect;
1692
imageMarginWidth = imageRectWithMargin.right - imageRectWithMargin.left;
1693
imageMarginHeight = imageRectWithMargin.bottom - imageRectWithMargin.top;
1694
if ((split_style & BS_CENTER) == BS_RIGHT)
1695
boundingImageRect.left = boundingImageRect.right - imageMarginWidth;
1696
else if ((split_style & BS_CENTER) == BS_LEFT)
1697
boundingImageRect.right = boundingImageRect.left + imageMarginWidth;
1698
else if ((split_style & BS_VCENTER) == BS_BOTTOM)
1699
boundingImageRect.top = boundingImageRect.bottom - imageMarginHeight;
1700
else if ((split_style & BS_VCENTER) == BS_TOP)
1701
boundingImageRect.bottom = boundingImageRect.top + imageMarginHeight;
1702
else
1703
boundingImageRect.right = boundingImageRect.left + imageMarginWidth;
1704
BUTTON_PositionRect(split_style, &boundingImageRect, &imageRect,
1705
infoPtr->imagelist.himl ? &infoPtr->imagelist.margin : &emptyMargin);
1706
1707
/* Get text rect */
1708
SubtractRect(&boundingTextRect, &labelRect, &boundingImageRect);
1709
/* Text doesn't use imagelist align */
1710
BUTTON_PositionRect(style, &boundingTextRect, &textRect, textMargin);
1711
}
1712
}
1713
/* Show text only */
1714
else
1715
{
1716
BUTTON_PositionRect(style, labelRc, &textRect, textMargin);
1717
labelRect = textRect;
1718
SetRectEmpty(&imageRect);
1719
}
1720
}
1721
Free(text);
1722
1723
CopyRect(labelRc, &labelRect);
1724
CopyRect(imageRc, &imageRect);
1725
CopyRect(textRc, &textRect);
1726
1727
return dtStyle;
1728
}
1729
1730
1731
/**********************************************************************
1732
* BUTTON_DrawImage
1733
*
1734
* Draw the button's image into the specified rectangle.
1735
*/
1736
static void BUTTON_DrawImage(const BUTTON_INFO *infoPtr, HDC hdc, HBRUSH hbr, UINT flags, const RECT *rect)
1737
{
1738
if (infoPtr->imagelist.himl)
1739
{
1740
int i = (ImageList_GetImageCount(infoPtr->imagelist.himl) == 1) ? 0 : get_draw_state(infoPtr) - 1;
1741
1742
ImageList_Draw(infoPtr->imagelist.himl, i, hdc, rect->left, rect->top, ILD_NORMAL);
1743
}
1744
else
1745
{
1746
switch (infoPtr->image_type)
1747
{
1748
case IMAGE_ICON:
1749
flags |= DST_ICON;
1750
break;
1751
case IMAGE_BITMAP:
1752
flags |= DST_BITMAP;
1753
break;
1754
default:
1755
return;
1756
}
1757
1758
DrawStateW(hdc, hbr, NULL, (LPARAM)infoPtr->u.image, 0, rect->left, rect->top,
1759
rect->right - rect->left, rect->bottom - rect->top, flags);
1760
}
1761
}
1762
1763
1764
/**********************************************************************
1765
* BUTTON_DrawTextCallback
1766
*
1767
* Callback function used by DrawStateW function.
1768
*/
1769
static BOOL CALLBACK BUTTON_DrawTextCallback(HDC hdc, LPARAM lp, WPARAM wp, int cx, int cy)
1770
{
1771
RECT rc;
1772
1773
SetRect(&rc, 0, 0, cx, cy);
1774
DrawTextW(hdc, (LPCWSTR)lp, -1, &rc, (UINT)wp);
1775
return TRUE;
1776
}
1777
1778
/**********************************************************************
1779
* BUTTON_DrawLabel
1780
*
1781
* Common function for drawing button label.
1782
*
1783
* FIXME:
1784
* 1. When BS_SINGLELINE is specified and text contains '\t', '\n' or '\r' in the middle, they are rendered as
1785
* squares now whereas they should be ignored.
1786
* 2. When BS_MULTILINE is specified and text contains space in the middle, the space mistakenly be rendered as newline.
1787
*/
1788
static void BUTTON_DrawLabel(const BUTTON_INFO *infoPtr, HDC hdc, UINT dtFlags, const RECT *imageRect,
1789
const RECT *textRect)
1790
{
1791
HBRUSH hbr = 0;
1792
UINT flags = IsWindowEnabled(infoPtr->hwnd) ? DSS_NORMAL : DSS_DISABLED;
1793
LONG style = GetWindowLongW( infoPtr->hwnd, GWL_STYLE );
1794
WCHAR *text;
1795
1796
/* FIXME: To draw disabled label in Win31 look-and-feel, we probably
1797
* must use DSS_MONO flag and COLOR_GRAYTEXT brush (or maybe DSS_UNION).
1798
* I don't have Win31 on hand to verify that, so I leave it as is.
1799
*/
1800
1801
if ((style & BS_PUSHLIKE) && (infoPtr->state & BST_INDETERMINATE))
1802
{
1803
hbr = GetSysColorBrush(COLOR_GRAYTEXT);
1804
flags |= DSS_MONO;
1805
}
1806
1807
if (show_image(infoPtr)) BUTTON_DrawImage(infoPtr, hdc, hbr, flags, imageRect);
1808
if (show_image_only(infoPtr)) return;
1809
1810
/* DST_COMPLEX -- is 0 */
1811
if (!(text = get_button_text(infoPtr))) return;
1812
DrawStateW(hdc, hbr, BUTTON_DrawTextCallback, (LPARAM)text, dtFlags, textRect->left, textRect->top,
1813
textRect->right - textRect->left, textRect->bottom - textRect->top, flags);
1814
Free(text);
1815
}
1816
1817
static void BUTTON_DrawThemedLabel(const BUTTON_INFO *info, HDC hdc, UINT text_flags,
1818
const RECT *image_rect, const RECT *text_rect, HTHEME theme,
1819
int part, int state)
1820
{
1821
HBRUSH brush = NULL;
1822
UINT image_flags;
1823
WCHAR *text;
1824
1825
if (show_image(info))
1826
{
1827
image_flags = IsWindowEnabled(info->hwnd) ? DSS_NORMAL : DSS_DISABLED;
1828
1829
if ((GetWindowLongW(info->hwnd, GWL_STYLE) & BS_PUSHLIKE)
1830
&& (info->state & BST_INDETERMINATE))
1831
{
1832
brush = GetSysColorBrush(COLOR_GRAYTEXT);
1833
image_flags |= DSS_MONO;
1834
}
1835
1836
BUTTON_DrawImage(info, hdc, brush, image_flags, image_rect);
1837
}
1838
1839
if (show_image_only(info))
1840
return;
1841
1842
if (!(text = get_button_text(info)))
1843
return;
1844
1845
DrawThemeText(theme, hdc, part, state, text, lstrlenW(text), text_flags, 0, text_rect);
1846
Free(text);
1847
}
1848
1849
/**********************************************************************
1850
* Push Button Functions
1851
*/
1852
static void PB_Paint( const BUTTON_INFO *infoPtr, HDC hDC, UINT action )
1853
{
1854
RECT rc, labelRect, imageRect, textRect;
1855
UINT dtFlags = (UINT)-1, uState;
1856
HPEN hOldPen, hpen;
1857
HBRUSH hOldBrush;
1858
INT oldBkMode;
1859
COLORREF oldTxtColor;
1860
LRESULT cdrf;
1861
HFONT hFont;
1862
NMCUSTOMDRAW nmcd;
1863
LONG state = infoPtr->state;
1864
LONG style = GetWindowLongW( infoPtr->hwnd, GWL_STYLE );
1865
BOOL pushedState = (state & BST_PUSHED);
1866
HWND parent;
1867
HRGN hrgn;
1868
1869
GetClientRect( infoPtr->hwnd, &rc );
1870
1871
/* Send WM_CTLCOLOR to allow changing the font (the colors are fixed) */
1872
if ((hFont = infoPtr->font)) SelectObject( hDC, hFont );
1873
parent = GetParent(infoPtr->hwnd);
1874
if (!parent) parent = infoPtr->hwnd;
1875
SendMessageW( parent, WM_CTLCOLORBTN, (WPARAM)hDC, (LPARAM)infoPtr->hwnd );
1876
1877
hrgn = set_control_clipping( hDC, &rc );
1878
1879
hpen = CreatePen( PS_SOLID, 1, GetSysColor(COLOR_WINDOWFRAME));
1880
hOldPen = SelectObject(hDC, hpen);
1881
hOldBrush = SelectObject(hDC,GetSysColorBrush(COLOR_BTNFACE));
1882
oldBkMode = SetBkMode(hDC, TRANSPARENT);
1883
1884
init_custom_draw(&nmcd, infoPtr, hDC, &rc);
1885
1886
/* Send erase notifications */
1887
cdrf = SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
1888
if (cdrf & CDRF_SKIPDEFAULT) goto cleanup;
1889
1890
if (get_button_type(style) == BS_DEFPUSHBUTTON)
1891
{
1892
if (action != ODA_FOCUS)
1893
Rectangle(hDC, rc.left, rc.top, rc.right, rc.bottom);
1894
InflateRect( &rc, -1, -1 );
1895
}
1896
1897
/* Skip the frame drawing if only focus has changed */
1898
if (action != ODA_FOCUS)
1899
{
1900
uState = DFCS_BUTTONPUSH;
1901
1902
if (style & BS_FLAT)
1903
uState |= DFCS_MONO;
1904
else if (pushedState)
1905
{
1906
if (get_button_type(style) == BS_DEFPUSHBUTTON )
1907
uState |= DFCS_FLAT;
1908
else
1909
uState |= DFCS_PUSHED;
1910
}
1911
1912
if (state & (BST_CHECKED | BST_INDETERMINATE))
1913
uState |= DFCS_CHECKED;
1914
1915
DrawFrameControl( hDC, &rc, DFC_BUTTON, uState );
1916
}
1917
1918
if (cdrf & CDRF_NOTIFYPOSTERASE)
1919
{
1920
nmcd.dwDrawStage = CDDS_POSTERASE;
1921
SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
1922
}
1923
1924
/* Send paint notifications */
1925
nmcd.dwDrawStage = CDDS_PREPAINT;
1926
cdrf = SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
1927
if (cdrf & CDRF_SKIPDEFAULT) goto cleanup;
1928
1929
if (!(cdrf & CDRF_DOERASE) && action != ODA_FOCUS)
1930
{
1931
/* draw button label */
1932
labelRect = rc;
1933
/* Shrink label rect at all sides by 2 so that the content won't touch the surrounding frame */
1934
InflateRect(&labelRect, -2, -2);
1935
dtFlags = BUTTON_CalcLayoutRects(infoPtr, hDC, &labelRect, &imageRect, &textRect);
1936
1937
if (dtFlags != (UINT)-1L)
1938
{
1939
if (pushedState) OffsetRect(&labelRect, 1, 1);
1940
1941
oldTxtColor = SetTextColor( hDC, GetSysColor(COLOR_BTNTEXT) );
1942
1943
BUTTON_DrawLabel(infoPtr, hDC, dtFlags, &imageRect, &textRect);
1944
1945
SetTextColor( hDC, oldTxtColor );
1946
}
1947
}
1948
1949
if (cdrf & CDRF_NOTIFYPOSTPAINT)
1950
{
1951
nmcd.dwDrawStage = CDDS_POSTPAINT;
1952
SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
1953
}
1954
if ((cdrf & CDRF_SKIPPOSTPAINT) || dtFlags == (UINT)-1L) goto cleanup;
1955
1956
if (action == ODA_FOCUS || (state & BST_FOCUS))
1957
{
1958
InflateRect( &rc, -2, -2 );
1959
DrawFocusRect( hDC, &rc );
1960
}
1961
1962
cleanup:
1963
SelectObject( hDC, hOldPen );
1964
SelectObject( hDC, hOldBrush );
1965
SetBkMode(hDC, oldBkMode);
1966
SelectClipRgn( hDC, hrgn );
1967
if (hrgn) DeleteObject( hrgn );
1968
DeleteObject( hpen );
1969
}
1970
1971
/**********************************************************************
1972
* Check Box & Radio Button Functions
1973
*/
1974
1975
/* Get adjusted check box or radio box rectangle */
1976
static RECT get_box_rect(LONG style, LONG ex_style, const RECT *content_rect,
1977
const RECT *label_rect, BOOL has_label, SIZE box_size)
1978
{
1979
RECT rect;
1980
int delta;
1981
1982
rect = *content_rect;
1983
1984
if (style & BS_LEFTTEXT || ex_style & WS_EX_RIGHT)
1985
rect.left = rect.right - box_size.cx;
1986
else
1987
rect.right = rect.left + box_size.cx;
1988
1989
/* Adjust box when label is valid */
1990
if (has_label)
1991
{
1992
rect.top = label_rect->top;
1993
rect.bottom = label_rect->bottom;
1994
}
1995
1996
/* Box must have the correct height */
1997
delta = rect.bottom - rect.top - box_size.cy;
1998
if ((style & BS_VCENTER) == BS_TOP)
1999
{
2000
if (delta <= 0)
2001
rect.top -= -delta / 2 + 1;
2002
2003
rect.bottom = rect.top + box_size.cy;
2004
}
2005
else if ((style & BS_VCENTER) == BS_BOTTOM)
2006
{
2007
if (delta <= 0)
2008
rect.bottom += -delta / 2 + 1;
2009
2010
rect.top = rect.bottom - box_size.cy;
2011
}
2012
else
2013
{
2014
if (delta > 0)
2015
{
2016
rect.bottom -= delta / 2 + 1;
2017
rect.top = rect.bottom - box_size.cy;
2018
}
2019
else if (delta < 0)
2020
{
2021
rect.top -= -delta / 2 + 1;
2022
rect.bottom = rect.top + box_size.cy;
2023
}
2024
}
2025
2026
return rect;
2027
}
2028
2029
static void CB_Paint( const BUTTON_INFO *infoPtr, HDC hDC, UINT action )
2030
{
2031
RECT rbox, labelRect, oldLabelRect, imageRect, textRect, client;
2032
HBRUSH hBrush;
2033
int text_offset;
2034
UINT dtFlags;
2035
LRESULT cdrf;
2036
HFONT hFont;
2037
NMCUSTOMDRAW nmcd;
2038
LONG state = infoPtr->state;
2039
LONG style = GetWindowLongW( infoPtr->hwnd, GWL_STYLE );
2040
LONG ex_style = GetWindowLongW( infoPtr->hwnd, GWL_EXSTYLE );
2041
SIZE box_size;
2042
HWND parent;
2043
HRGN hrgn;
2044
2045
if (style & BS_PUSHLIKE)
2046
{
2047
PB_Paint( infoPtr, hDC, action );
2048
return;
2049
}
2050
2051
GetClientRect(infoPtr->hwnd, &client);
2052
labelRect = client;
2053
2054
box_size.cx = 12 * GetDpiForWindow(infoPtr->hwnd) / 96 + 1;
2055
box_size.cy = box_size.cx;
2056
2057
if ((hFont = infoPtr->font)) SelectObject( hDC, hFont );
2058
GetCharWidthW( hDC, '0', '0', &text_offset );
2059
text_offset /= 2;
2060
2061
parent = GetParent(infoPtr->hwnd);
2062
if (!parent) parent = infoPtr->hwnd;
2063
hBrush = (HBRUSH)SendMessageW(parent, WM_CTLCOLORSTATIC, (WPARAM)hDC, (LPARAM)infoPtr->hwnd);
2064
if (!hBrush) /* did the app forget to call defwindowproc ? */
2065
hBrush = (HBRUSH)DefWindowProcW(parent, WM_CTLCOLORSTATIC, (WPARAM)hDC, (LPARAM)infoPtr->hwnd);
2066
hrgn = set_control_clipping( hDC, &client );
2067
2068
if (style & BS_LEFTTEXT || ex_style & WS_EX_RIGHT)
2069
labelRect.right -= box_size.cx + text_offset;
2070
else
2071
labelRect.left += box_size.cx + text_offset;
2072
2073
oldLabelRect = labelRect;
2074
dtFlags = BUTTON_CalcLayoutRects(infoPtr, hDC, &labelRect, &imageRect, &textRect);
2075
rbox = get_box_rect(style, ex_style, &client, &labelRect, dtFlags != (UINT)-1L, box_size);
2076
2077
init_custom_draw(&nmcd, infoPtr, hDC, &client);
2078
2079
/* Send erase notifications */
2080
cdrf = SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
2081
if (cdrf & CDRF_SKIPDEFAULT) goto cleanup;
2082
2083
/* Since WM_ERASEBKGND does nothing, first prepare background */
2084
if (action == ODA_SELECT) FillRect( hDC, &rbox, hBrush );
2085
if (action == ODA_DRAWENTIRE) FillRect( hDC, &client, hBrush );
2086
if (cdrf & CDRF_NOTIFYPOSTERASE)
2087
{
2088
nmcd.dwDrawStage = CDDS_POSTERASE;
2089
SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
2090
}
2091
2092
/* Draw label */
2093
/* Send paint notifications */
2094
nmcd.dwDrawStage = CDDS_PREPAINT;
2095
cdrf = SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
2096
if (cdrf & CDRF_SKIPDEFAULT) goto cleanup;
2097
2098
/* Draw the check-box bitmap */
2099
if (!(cdrf & CDRF_DOERASE))
2100
{
2101
if (action == ODA_DRAWENTIRE || action == ODA_SELECT)
2102
{
2103
UINT flags;
2104
2105
if ((get_button_type(style) == BS_RADIOBUTTON) ||
2106
(get_button_type(style) == BS_AUTORADIOBUTTON)) flags = DFCS_BUTTONRADIO;
2107
else if (state & BST_INDETERMINATE) flags = DFCS_BUTTON3STATE;
2108
else flags = DFCS_BUTTONCHECK;
2109
2110
if (state & (BST_CHECKED | BST_INDETERMINATE)) flags |= DFCS_CHECKED;
2111
if (state & BST_PUSHED) flags |= DFCS_PUSHED;
2112
if (style & WS_DISABLED) flags |= DFCS_INACTIVE;
2113
2114
DrawFrameControl(hDC, &rbox, DFC_BUTTON, flags);
2115
}
2116
2117
if (dtFlags != (UINT)-1L) /* Something to draw */
2118
if (action == ODA_DRAWENTIRE) BUTTON_DrawLabel(infoPtr, hDC, dtFlags, &imageRect, &textRect);
2119
}
2120
2121
if (cdrf & CDRF_NOTIFYPOSTPAINT)
2122
{
2123
nmcd.dwDrawStage = CDDS_POSTPAINT;
2124
SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
2125
}
2126
if ((cdrf & CDRF_SKIPPOSTPAINT) || dtFlags == (UINT)-1L) goto cleanup;
2127
2128
/* ... and focus */
2129
if (action == ODA_FOCUS || (state & BST_FOCUS))
2130
{
2131
labelRect.left--;
2132
labelRect.right++;
2133
IntersectRect(&labelRect, &labelRect, &oldLabelRect);
2134
DrawFocusRect(hDC, &labelRect);
2135
}
2136
2137
cleanup:
2138
SelectClipRgn( hDC, hrgn );
2139
if (hrgn) DeleteObject( hrgn );
2140
}
2141
2142
2143
/**********************************************************************
2144
* BUTTON_CheckAutoRadioButton
2145
*
2146
* hwnd is checked, uncheck every other auto radio button in group
2147
*/
2148
static void BUTTON_CheckAutoRadioButton( HWND hwnd )
2149
{
2150
HWND parent, sibling, start;
2151
2152
parent = GetParent(hwnd);
2153
/* make sure that starting control is not disabled or invisible */
2154
start = sibling = GetNextDlgGroupItem( parent, hwnd, TRUE );
2155
do
2156
{
2157
if (!sibling) break;
2158
if ((hwnd != sibling) &&
2159
((GetWindowLongW( sibling, GWL_STYLE) & BS_TYPEMASK) == BS_AUTORADIOBUTTON))
2160
SendMessageW( sibling, BM_SETCHECK, BST_UNCHECKED, 0 );
2161
sibling = GetNextDlgGroupItem( parent, sibling, FALSE );
2162
} while (sibling != start);
2163
}
2164
2165
2166
/**********************************************************************
2167
* Group Box Functions
2168
*/
2169
2170
static void GB_Paint( const BUTTON_INFO *infoPtr, HDC hDC, UINT action )
2171
{
2172
RECT labelRect, imageRect, textRect, rcFrame;
2173
HBRUSH hbr;
2174
HFONT hFont;
2175
UINT dtFlags;
2176
TEXTMETRICW tm;
2177
LONG style = GetWindowLongW( infoPtr->hwnd, GWL_STYLE );
2178
HWND parent;
2179
HRGN hrgn;
2180
2181
if ((hFont = infoPtr->font)) SelectObject( hDC, hFont );
2182
/* GroupBox acts like static control, so it sends CTLCOLORSTATIC */
2183
parent = GetParent(infoPtr->hwnd);
2184
if (!parent) parent = infoPtr->hwnd;
2185
hbr = (HBRUSH)SendMessageW(parent, WM_CTLCOLORSTATIC, (WPARAM)hDC, (LPARAM)infoPtr->hwnd);
2186
if (!hbr) /* did the app forget to call defwindowproc ? */
2187
hbr = (HBRUSH)DefWindowProcW(parent, WM_CTLCOLORSTATIC, (WPARAM)hDC, (LPARAM)infoPtr->hwnd);
2188
GetClientRect(infoPtr->hwnd, &labelRect);
2189
rcFrame = labelRect;
2190
hrgn = set_control_clipping(hDC, &labelRect);
2191
2192
GetTextMetricsW (hDC, &tm);
2193
rcFrame.top += (tm.tmHeight / 2) - 1;
2194
DrawEdge (hDC, &rcFrame, EDGE_ETCHED, BF_RECT | ((style & BS_FLAT) ? BF_FLAT : 0));
2195
2196
InflateRect(&labelRect, -7, 1);
2197
dtFlags = BUTTON_CalcLayoutRects(infoPtr, hDC, &labelRect, &imageRect, &textRect);
2198
2199
if (dtFlags != (UINT)-1)
2200
{
2201
/* Because buttons have CS_PARENTDC class style, there is a chance
2202
* that label will be drawn out of client rect.
2203
* But Windows doesn't clip label's rect, so do I.
2204
*/
2205
2206
/* There is 1-pixel margin at the left, right, and bottom */
2207
labelRect.left--;
2208
labelRect.right++;
2209
labelRect.bottom++;
2210
FillRect(hDC, &labelRect, hbr);
2211
BUTTON_DrawLabel(infoPtr, hDC, dtFlags, &imageRect, &textRect);
2212
}
2213
SelectClipRgn( hDC, hrgn );
2214
if (hrgn) DeleteObject( hrgn );
2215
}
2216
2217
2218
/**********************************************************************
2219
* User Button Functions
2220
*/
2221
2222
static void UB_Paint( const BUTTON_INFO *infoPtr, HDC hDC, UINT action )
2223
{
2224
RECT rc;
2225
HBRUSH hBrush;
2226
LRESULT cdrf;
2227
HFONT hFont;
2228
NMCUSTOMDRAW nmcd;
2229
LONG state = infoPtr->state;
2230
HWND parent;
2231
2232
GetClientRect( infoPtr->hwnd, &rc);
2233
2234
if ((hFont = infoPtr->font)) SelectObject( hDC, hFont );
2235
2236
parent = GetParent(infoPtr->hwnd);
2237
if (!parent) parent = infoPtr->hwnd;
2238
hBrush = (HBRUSH)SendMessageW(parent, WM_CTLCOLORBTN, (WPARAM)hDC, (LPARAM)infoPtr->hwnd);
2239
if (!hBrush) /* did the app forget to call defwindowproc ? */
2240
hBrush = (HBRUSH)DefWindowProcW(parent, WM_CTLCOLORBTN, (WPARAM)hDC, (LPARAM)infoPtr->hwnd);
2241
2242
if (action == ODA_FOCUS || (state & BST_FOCUS))
2243
{
2244
init_custom_draw(&nmcd, infoPtr, hDC, &rc);
2245
2246
/* Send erase notifications */
2247
cdrf = SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
2248
if (cdrf & CDRF_SKIPDEFAULT) goto notify;
2249
}
2250
2251
FillRect( hDC, &rc, hBrush );
2252
if (action == ODA_FOCUS || (state & BST_FOCUS))
2253
{
2254
if (cdrf & CDRF_NOTIFYPOSTERASE)
2255
{
2256
nmcd.dwDrawStage = CDDS_POSTERASE;
2257
SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
2258
}
2259
2260
/* Send paint notifications */
2261
nmcd.dwDrawStage = CDDS_PREPAINT;
2262
cdrf = SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
2263
if (cdrf & CDRF_SKIPDEFAULT) goto notify;
2264
if (cdrf & CDRF_NOTIFYPOSTPAINT)
2265
{
2266
nmcd.dwDrawStage = CDDS_POSTPAINT;
2267
SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
2268
}
2269
2270
if (!(cdrf & CDRF_SKIPPOSTPAINT))
2271
DrawFocusRect( hDC, &rc );
2272
}
2273
2274
notify:
2275
switch (action)
2276
{
2277
case ODA_FOCUS:
2278
BUTTON_NOTIFY_PARENT( infoPtr->hwnd, (state & BST_FOCUS) ? BN_SETFOCUS : BN_KILLFOCUS );
2279
break;
2280
2281
case ODA_SELECT:
2282
BUTTON_NOTIFY_PARENT( infoPtr->hwnd, (state & BST_PUSHED) ? BN_HILITE : BN_UNHILITE );
2283
break;
2284
2285
default:
2286
break;
2287
}
2288
}
2289
2290
2291
/**********************************************************************
2292
* Ownerdrawn Button Functions
2293
*/
2294
2295
static void OB_Paint( const BUTTON_INFO *infoPtr, HDC hDC, UINT action )
2296
{
2297
LONG state = infoPtr->state;
2298
DRAWITEMSTRUCT dis;
2299
LONG_PTR id = GetWindowLongPtrW( infoPtr->hwnd, GWLP_ID );
2300
HWND parent;
2301
HFONT hFont;
2302
HRGN hrgn;
2303
2304
dis.CtlType = ODT_BUTTON;
2305
dis.CtlID = id;
2306
dis.itemID = 0;
2307
dis.itemAction = action;
2308
dis.itemState = ((state & BST_FOCUS) ? ODS_FOCUS : 0) |
2309
((state & BST_PUSHED) ? ODS_SELECTED : 0) |
2310
(IsWindowEnabled(infoPtr->hwnd) ? 0: ODS_DISABLED);
2311
dis.hwndItem = infoPtr->hwnd;
2312
dis.hDC = hDC;
2313
dis.itemData = 0;
2314
GetClientRect( infoPtr->hwnd, &dis.rcItem );
2315
2316
if ((hFont = infoPtr->font)) SelectObject( hDC, hFont );
2317
parent = GetParent(infoPtr->hwnd);
2318
if (!parent) parent = infoPtr->hwnd;
2319
SendMessageW( parent, WM_CTLCOLORBTN, (WPARAM)hDC, (LPARAM)infoPtr->hwnd );
2320
2321
hrgn = set_control_clipping( hDC, &dis.rcItem );
2322
2323
SendMessageW( GetParent(infoPtr->hwnd), WM_DRAWITEM, id, (LPARAM)&dis );
2324
SelectClipRgn( hDC, hrgn );
2325
if (hrgn) DeleteObject( hrgn );
2326
}
2327
2328
2329
/**********************************************************************
2330
* Split Button Functions
2331
*/
2332
static void SB_Paint( const BUTTON_INFO *infoPtr, HDC hDC, UINT action )
2333
{
2334
LONG style = GetWindowLongW(infoPtr->hwnd, GWL_STYLE);
2335
LONG state = infoPtr->state;
2336
UINT dtFlags = (UINT)-1L;
2337
2338
RECT rc, push_rect, dropdown_rect;
2339
NMCUSTOMDRAW nmcd;
2340
HPEN pen, old_pen;
2341
HBRUSH old_brush;
2342
INT old_bk_mode;
2343
LRESULT cdrf;
2344
HWND parent;
2345
HRGN hrgn;
2346
2347
GetClientRect(infoPtr->hwnd, &rc);
2348
2349
/* Send WM_CTLCOLOR to allow changing the font (the colors are fixed) */
2350
if (infoPtr->font) SelectObject(hDC, infoPtr->font);
2351
if (!(parent = GetParent(infoPtr->hwnd))) parent = infoPtr->hwnd;
2352
SendMessageW(parent, WM_CTLCOLORBTN, (WPARAM)hDC, (LPARAM)infoPtr->hwnd);
2353
2354
hrgn = set_control_clipping(hDC, &rc);
2355
2356
pen = CreatePen(PS_SOLID, 1, GetSysColor(COLOR_WINDOWFRAME));
2357
old_pen = SelectObject(hDC, pen);
2358
old_brush = SelectObject(hDC, GetSysColorBrush(COLOR_BTNFACE));
2359
old_bk_mode = SetBkMode(hDC, TRANSPARENT);
2360
2361
init_custom_draw(&nmcd, infoPtr, hDC, &rc);
2362
2363
/* Send erase notifications */
2364
cdrf = SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
2365
if (cdrf & CDRF_SKIPDEFAULT) goto cleanup;
2366
2367
if (get_button_type(style) == BS_DEFSPLITBUTTON)
2368
{
2369
if (action != ODA_FOCUS)
2370
Rectangle(hDC, rc.left, rc.top, rc.right, rc.bottom);
2371
InflateRect(&rc, -1, -1);
2372
/* The split will now be off by 1 pixel, but
2373
that's exactly what Windows does as well */
2374
}
2375
2376
get_split_button_rects(infoPtr, &rc, &push_rect, &dropdown_rect);
2377
if (infoPtr->split_style & BCSS_NOSPLIT)
2378
push_rect = rc;
2379
2380
/* Skip the frame drawing if only focus has changed */
2381
if (action != ODA_FOCUS)
2382
{
2383
UINT flags = DFCS_BUTTONPUSH;
2384
2385
if (style & BS_FLAT) flags |= DFCS_MONO;
2386
else if (state & BST_PUSHED)
2387
flags |= (get_button_type(style) == BS_DEFSPLITBUTTON)
2388
? DFCS_FLAT : DFCS_PUSHED;
2389
2390
if (state & (BST_CHECKED | BST_INDETERMINATE))
2391
flags |= DFCS_CHECKED;
2392
2393
if (infoPtr->split_style & BCSS_NOSPLIT)
2394
DrawFrameControl(hDC, &push_rect, DFC_BUTTON, flags);
2395
else
2396
{
2397
UINT dropdown_flags = flags & ~DFCS_CHECKED;
2398
2399
if (state & BST_DROPDOWNPUSHED)
2400
dropdown_flags = (dropdown_flags & ~DFCS_FLAT) | DFCS_PUSHED;
2401
2402
/* Adjust for shadow and draw order so it looks properly */
2403
if (infoPtr->split_style & BCSS_ALIGNLEFT)
2404
{
2405
dropdown_rect.right++;
2406
DrawFrameControl(hDC, &dropdown_rect, DFC_BUTTON, dropdown_flags);
2407
dropdown_rect.right--;
2408
DrawFrameControl(hDC, &push_rect, DFC_BUTTON, flags);
2409
}
2410
else
2411
{
2412
push_rect.right++;
2413
DrawFrameControl(hDC, &push_rect, DFC_BUTTON, flags);
2414
push_rect.right--;
2415
DrawFrameControl(hDC, &dropdown_rect, DFC_BUTTON, dropdown_flags);
2416
}
2417
}
2418
}
2419
2420
if (cdrf & CDRF_NOTIFYPOSTERASE)
2421
{
2422
nmcd.dwDrawStage = CDDS_POSTERASE;
2423
SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
2424
}
2425
2426
/* Send paint notifications */
2427
nmcd.dwDrawStage = CDDS_PREPAINT;
2428
cdrf = SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
2429
if (cdrf & CDRF_SKIPDEFAULT) goto cleanup;
2430
2431
/* Shrink push button rect so that the content won't touch the surrounding frame */
2432
InflateRect(&push_rect, -2, -2);
2433
2434
if (!(cdrf & CDRF_DOERASE) && action != ODA_FOCUS)
2435
{
2436
COLORREF old_color = SetTextColor(hDC, GetSysColor(COLOR_BTNTEXT));
2437
RECT label_rect = push_rect, image_rect, text_rect;
2438
2439
dtFlags = BUTTON_CalcLayoutRects(infoPtr, hDC, &label_rect, &image_rect, &text_rect);
2440
2441
if (dtFlags != (UINT)-1L)
2442
BUTTON_DrawLabel(infoPtr, hDC, dtFlags, &image_rect, &text_rect);
2443
2444
draw_split_button_dropdown_glyph(infoPtr, hDC, &dropdown_rect);
2445
SetTextColor(hDC, old_color);
2446
}
2447
2448
if (cdrf & CDRF_NOTIFYPOSTPAINT)
2449
{
2450
nmcd.dwDrawStage = CDDS_POSTPAINT;
2451
SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
2452
}
2453
if ((cdrf & CDRF_SKIPPOSTPAINT) || dtFlags == (UINT)-1L) goto cleanup;
2454
2455
if (action == ODA_FOCUS || (state & BST_FOCUS))
2456
DrawFocusRect(hDC, &push_rect);
2457
2458
cleanup:
2459
SelectObject(hDC, old_pen);
2460
SelectObject(hDC, old_brush);
2461
SetBkMode(hDC, old_bk_mode);
2462
SelectClipRgn(hDC, hrgn);
2463
if (hrgn) DeleteObject(hrgn);
2464
DeleteObject(pen);
2465
}
2466
2467
/* Given the full button rect of the split button, retrieve the push part and the dropdown part */
2468
static inline void get_split_button_rects(const BUTTON_INFO *infoPtr, const RECT *button_rect,
2469
RECT *push_rect, RECT *dropdown_rect)
2470
{
2471
*push_rect = *dropdown_rect = *button_rect;
2472
2473
/* The dropdown takes priority if the client rect is too small, it will only have a dropdown */
2474
if (infoPtr->split_style & BCSS_ALIGNLEFT)
2475
{
2476
dropdown_rect->right = min(button_rect->left + infoPtr->glyph_size.cx, button_rect->right);
2477
push_rect->left = dropdown_rect->right;
2478
}
2479
else
2480
{
2481
dropdown_rect->left = max(button_rect->right - infoPtr->glyph_size.cx, button_rect->left);
2482
push_rect->right = dropdown_rect->left;
2483
}
2484
}
2485
2486
/* Notify the parent if the point is within the dropdown and return TRUE (always notify if NULL) */
2487
static BOOL notify_split_button_dropdown(const BUTTON_INFO *infoPtr, const POINT *pt, HWND hwnd)
2488
{
2489
NMBCDROPDOWN nmbcd;
2490
2491
GetClientRect(hwnd, &nmbcd.rcButton);
2492
if (pt)
2493
{
2494
RECT push_rect, dropdown_rect;
2495
2496
get_split_button_rects(infoPtr, &nmbcd.rcButton, &push_rect, &dropdown_rect);
2497
if (!PtInRect(&dropdown_rect, *pt))
2498
return FALSE;
2499
2500
/* If it's already down (set manually via BCM_SETDROPDOWNSTATE), fake the notify */
2501
if (infoPtr->state & BST_DROPDOWNPUSHED)
2502
return TRUE;
2503
}
2504
SendMessageW(hwnd, BCM_SETDROPDOWNSTATE, TRUE, 0);
2505
2506
nmbcd.hdr.hwndFrom = hwnd;
2507
nmbcd.hdr.idFrom = GetWindowLongPtrW(hwnd, GWLP_ID);
2508
nmbcd.hdr.code = BCN_DROPDOWN;
2509
SendMessageW(GetParent(hwnd), WM_NOTIFY, nmbcd.hdr.idFrom, (LPARAM)&nmbcd);
2510
2511
SendMessageW(hwnd, BCM_SETDROPDOWNSTATE, FALSE, 0);
2512
return TRUE;
2513
}
2514
2515
/* Draw the split button dropdown glyph or image */
2516
static void draw_split_button_dropdown_glyph(const BUTTON_INFO *infoPtr, HDC hdc, RECT *rect)
2517
{
2518
if (infoPtr->split_style & BCSS_IMAGE)
2519
{
2520
int w, h;
2521
2522
/* When the glyph is an image list, Windows is very buggy with BCSS_STRETCH,
2523
positions it weirdly and doesn't even stretch it, but instead extends the
2524
image, leaking into other images in the list (or black if none). Instead,
2525
we'll ignore this and just position it at center as without BCSS_STRETCH. */
2526
if (!ImageList_GetIconSize(infoPtr->glyph, &w, &h)) return;
2527
2528
ImageList_Draw(infoPtr->glyph,
2529
(ImageList_GetImageCount(infoPtr->glyph) == 1) ? 0 : get_draw_state(infoPtr) - 1,
2530
hdc, rect->left + (rect->right - rect->left - w) / 2,
2531
rect->top + (rect->bottom - rect->top - h) / 2, ILD_NORMAL);
2532
}
2533
else if (infoPtr->glyph_size.cy >= 0)
2534
{
2535
/* infoPtr->glyph is a character code from Marlett */
2536
HFONT font, old_font;
2537
LOGFONTW logfont = { 0, 0, 0, 0, FW_NORMAL, 0, 0, 0, SYMBOL_CHARSET, 0, 0, 0, 0,
2538
L"Marlett" };
2539
if (infoPtr->glyph_size.cy)
2540
{
2541
/* BCSS_STRETCH preserves aspect ratio, uses minimum as size */
2542
if (infoPtr->split_style & BCSS_STRETCH)
2543
logfont.lfHeight = min(infoPtr->glyph_size.cx, infoPtr->glyph_size.cy);
2544
else
2545
{
2546
logfont.lfWidth = infoPtr->glyph_size.cx;
2547
logfont.lfHeight = infoPtr->glyph_size.cy;
2548
}
2549
}
2550
else logfont.lfHeight = infoPtr->glyph_size.cx;
2551
2552
if ((font = CreateFontIndirectW(&logfont)))
2553
{
2554
old_font = SelectObject(hdc, font);
2555
DrawTextW(hdc, (const WCHAR*)&infoPtr->glyph, 1, rect,
2556
DT_CENTER | DT_VCENTER | DT_SINGLELINE | DT_NOCLIP | DT_NOPREFIX);
2557
SelectObject(hdc, old_font);
2558
DeleteObject(font);
2559
}
2560
}
2561
}
2562
2563
2564
/**********************************************************************
2565
* Command Link Functions
2566
*/
2567
static void CL_Paint( const BUTTON_INFO *infoPtr, HDC hDC, UINT action )
2568
{
2569
LONG style = GetWindowLongW(infoPtr->hwnd, GWL_STYLE);
2570
LONG state = infoPtr->state;
2571
2572
RECT rc, content_rect;
2573
NMCUSTOMDRAW nmcd;
2574
HPEN pen, old_pen;
2575
HBRUSH old_brush;
2576
INT old_bk_mode;
2577
LRESULT cdrf;
2578
HWND parent;
2579
HRGN hrgn;
2580
2581
GetClientRect(infoPtr->hwnd, &rc);
2582
2583
/* Command Links are not affected by the button's font, and are based
2584
on the default message font. Furthermore, they are not affected by
2585
any of the alignment styles (and always align with the top-left). */
2586
if (!(parent = GetParent(infoPtr->hwnd))) parent = infoPtr->hwnd;
2587
SendMessageW(parent, WM_CTLCOLORBTN, (WPARAM)hDC, (LPARAM)infoPtr->hwnd);
2588
2589
hrgn = set_control_clipping(hDC, &rc);
2590
2591
pen = CreatePen(PS_SOLID, 1, GetSysColor(COLOR_WINDOWFRAME));
2592
old_pen = SelectObject(hDC, pen);
2593
old_brush = SelectObject(hDC, GetSysColorBrush(COLOR_BTNFACE));
2594
old_bk_mode = SetBkMode(hDC, TRANSPARENT);
2595
2596
init_custom_draw(&nmcd, infoPtr, hDC, &rc);
2597
2598
/* Send erase notifications */
2599
cdrf = SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
2600
if (cdrf & CDRF_SKIPDEFAULT) goto cleanup;
2601
content_rect = rc;
2602
2603
if (get_button_type(style) == BS_DEFCOMMANDLINK)
2604
{
2605
if (action != ODA_FOCUS)
2606
Rectangle(hDC, rc.left, rc.top, rc.right, rc.bottom);
2607
InflateRect(&rc, -1, -1);
2608
}
2609
2610
/* Skip the frame drawing if only focus has changed */
2611
if (action != ODA_FOCUS)
2612
{
2613
if (!(state & (BST_HOT | BST_PUSHED | BST_CHECKED | BST_INDETERMINATE)))
2614
FillRect(hDC, &rc, GetSysColorBrush(COLOR_BTNFACE));
2615
else
2616
{
2617
UINT flags = DFCS_BUTTONPUSH;
2618
2619
if (style & BS_FLAT) flags |= DFCS_MONO;
2620
else if (state & BST_PUSHED) flags |= DFCS_PUSHED;
2621
2622
if (state & (BST_CHECKED | BST_INDETERMINATE))
2623
flags |= DFCS_CHECKED;
2624
DrawFrameControl(hDC, &rc, DFC_BUTTON, flags);
2625
}
2626
}
2627
2628
if (cdrf & CDRF_NOTIFYPOSTERASE)
2629
{
2630
nmcd.dwDrawStage = CDDS_POSTERASE;
2631
SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
2632
}
2633
2634
/* Send paint notifications */
2635
nmcd.dwDrawStage = CDDS_PREPAINT;
2636
cdrf = SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
2637
if (cdrf & CDRF_SKIPDEFAULT) goto cleanup;
2638
2639
if (!(cdrf & CDRF_DOERASE) && action != ODA_FOCUS)
2640
{
2641
UINT flags = IsWindowEnabled(infoPtr->hwnd) ? DSS_NORMAL : DSS_DISABLED;
2642
COLORREF old_color = SetTextColor(hDC, GetSysColor(flags == DSS_NORMAL ?
2643
COLOR_BTNTEXT : COLOR_GRAYTEXT));
2644
HIMAGELIST defimg = NULL;
2645
NONCLIENTMETRICSW ncm;
2646
UINT txt_h = 0;
2647
SIZE img_size;
2648
2649
/* Command Links ignore the margins of the image list or its alignment */
2650
if (infoPtr->u.image || infoPtr->imagelist.himl)
2651
img_size = BUTTON_GetImageSize(infoPtr);
2652
else
2653
{
2654
img_size.cx = img_size.cy = command_link_defglyph_size;
2655
defimg = ImageList_LoadImageW(COMCTL32_hModule, (LPCWSTR)MAKEINTRESOURCE(IDB_CMDLINK),
2656
img_size.cx, 3, CLR_NONE, IMAGE_BITMAP, LR_CREATEDIBSECTION);
2657
}
2658
2659
/* Shrink rect by the command link margin, except on bottom (just the frame) */
2660
InflateRect(&content_rect, -command_link_margin, -command_link_margin);
2661
content_rect.bottom += command_link_margin - 2;
2662
2663
ncm.cbSize = sizeof(ncm);
2664
if (SystemParametersInfoW(SPI_GETNONCLIENTMETRICS, sizeof(ncm), &ncm, 0))
2665
{
2666
LONG note_weight = ncm.lfMessageFont.lfWeight;
2667
RECT r = content_rect;
2668
WCHAR *text;
2669
HFONT font;
2670
2671
if (img_size.cx) r.left += img_size.cx + command_link_margin;
2672
2673
/* Draw the text */
2674
ncm.lfMessageFont.lfWeight = FW_BOLD;
2675
if ((font = CreateFontIndirectW(&ncm.lfMessageFont)))
2676
{
2677
if ((text = get_button_text(infoPtr)))
2678
{
2679
SelectObject(hDC, font);
2680
txt_h = DrawTextW(hDC, text, -1, &r,
2681
DT_TOP | DT_LEFT | DT_WORDBREAK | DT_END_ELLIPSIS);
2682
Free(text);
2683
}
2684
DeleteObject(font);
2685
}
2686
2687
/* Draw the note */
2688
ncm.lfMessageFont.lfWeight = note_weight;
2689
if (infoPtr->note && (font = CreateFontIndirectW(&ncm.lfMessageFont)))
2690
{
2691
r.top += txt_h + 2;
2692
SelectObject(hDC, font);
2693
DrawTextW(hDC, infoPtr->note, infoPtr->note_length, &r,
2694
DT_TOP | DT_LEFT | DT_WORDBREAK | DT_NOPREFIX);
2695
DeleteObject(font);
2696
}
2697
}
2698
2699
/* Position the image at the vertical center of the drawn text (not note) */
2700
txt_h = min(txt_h, content_rect.bottom - content_rect.top);
2701
if (img_size.cy < txt_h) content_rect.top += (txt_h - img_size.cy) / 2;
2702
2703
content_rect.right = content_rect.left + img_size.cx;
2704
content_rect.bottom = content_rect.top + img_size.cy;
2705
2706
if (defimg)
2707
{
2708
int i = 0;
2709
if (flags == DSS_DISABLED) i = 2;
2710
else if (state & BST_HOT) i = 1;
2711
2712
ImageList_Draw(defimg, i, hDC, content_rect.left, content_rect.top, ILD_NORMAL);
2713
ImageList_Destroy(defimg);
2714
}
2715
else
2716
BUTTON_DrawImage(infoPtr, hDC, NULL, flags, &content_rect);
2717
2718
SetTextColor(hDC, old_color);
2719
}
2720
2721
if (cdrf & CDRF_NOTIFYPOSTPAINT)
2722
{
2723
nmcd.dwDrawStage = CDDS_POSTPAINT;
2724
SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
2725
}
2726
if (cdrf & CDRF_SKIPPOSTPAINT) goto cleanup;
2727
2728
if (action == ODA_FOCUS || (state & BST_FOCUS))
2729
{
2730
InflateRect(&rc, -2, -2);
2731
DrawFocusRect(hDC, &rc);
2732
}
2733
2734
cleanup:
2735
SelectObject(hDC, old_pen);
2736
SelectObject(hDC, old_brush);
2737
SetBkMode(hDC, old_bk_mode);
2738
SelectClipRgn(hDC, hrgn);
2739
if (hrgn) DeleteObject(hrgn);
2740
DeleteObject(pen);
2741
}
2742
2743
2744
/**********************************************************************
2745
* Themed Paint Functions
2746
*/
2747
static void PB_ThemedPaint(HTHEME theme, const BUTTON_INFO *infoPtr, HDC hDC, int state, UINT dtFlags, BOOL focused)
2748
{
2749
RECT bgRect, labelRect, imageRect, textRect, focusRect;
2750
NMCUSTOMDRAW nmcd;
2751
HBRUSH brush;
2752
LRESULT cdrf;
2753
HWND parent;
2754
2755
if (infoPtr->font) SelectObject(hDC, infoPtr->font);
2756
2757
GetClientRect(infoPtr->hwnd, &bgRect);
2758
GetThemeBackgroundContentRect(theme, hDC, BP_PUSHBUTTON, state, &bgRect, &labelRect);
2759
focusRect = labelRect;
2760
2761
init_custom_draw(&nmcd, infoPtr, hDC, &bgRect);
2762
2763
parent = GetParent(infoPtr->hwnd);
2764
if (!parent) parent = infoPtr->hwnd;
2765
2766
/* Send erase notifications */
2767
cdrf = SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
2768
if (cdrf & CDRF_SKIPDEFAULT) return;
2769
2770
if (IsThemeBackgroundPartiallyTransparent(theme, BP_PUSHBUTTON, state))
2771
{
2772
DrawThemeParentBackground(infoPtr->hwnd, hDC, NULL);
2773
/* Tests show that the brush from WM_CTLCOLORBTN is used for filling background after a
2774
* DrawThemeParentBackground() call */
2775
brush = (HBRUSH)SendMessageW(parent, WM_CTLCOLORBTN, (WPARAM)hDC, (LPARAM)infoPtr->hwnd);
2776
FillRect(hDC, &bgRect, brush ? brush : GetSysColorBrush(COLOR_BTNFACE));
2777
}
2778
DrawThemeBackground(theme, hDC, BP_PUSHBUTTON, state, &bgRect, NULL);
2779
2780
if (cdrf & CDRF_NOTIFYPOSTERASE)
2781
{
2782
nmcd.dwDrawStage = CDDS_POSTERASE;
2783
SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
2784
}
2785
2786
/* Send paint notifications */
2787
nmcd.dwDrawStage = CDDS_PREPAINT;
2788
cdrf = SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
2789
if (cdrf & CDRF_SKIPDEFAULT) return;
2790
2791
if (!(cdrf & CDRF_DOERASE))
2792
{
2793
dtFlags = BUTTON_CalcLayoutRects(infoPtr, hDC, &labelRect, &imageRect, &textRect);
2794
if (dtFlags != (UINT)-1L)
2795
BUTTON_DrawThemedLabel(infoPtr, hDC, dtFlags, &imageRect, &textRect, theme,
2796
BP_PUSHBUTTON, state);
2797
}
2798
2799
if (cdrf & CDRF_NOTIFYPOSTPAINT)
2800
{
2801
nmcd.dwDrawStage = CDDS_POSTPAINT;
2802
SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
2803
}
2804
if (cdrf & CDRF_SKIPPOSTPAINT) return;
2805
2806
if (focused) DrawFocusRect(hDC, &focusRect);
2807
}
2808
2809
static void CB_ThemedPaint(HTHEME theme, const BUTTON_INFO *infoPtr, HDC hDC, int state, UINT dtFlags, BOOL focused)
2810
{
2811
RECT client_rect, content_rect, old_label_rect, label_rect, box_rect, image_rect, text_rect;
2812
HFONT font, hPrevFont = NULL;
2813
DWORD dwStyle = GetWindowLongW(infoPtr->hwnd, GWL_STYLE);
2814
LONG ex_style = GetWindowLongW(infoPtr->hwnd, GWL_EXSTYLE);
2815
UINT btn_type = get_button_type( dwStyle );
2816
int part = (btn_type == BS_RADIOBUTTON) || (btn_type == BS_AUTORADIOBUTTON) ? BP_RADIOBUTTON : BP_CHECKBOX;
2817
NMCUSTOMDRAW nmcd;
2818
HBRUSH brush;
2819
LRESULT cdrf;
2820
LOGFONTW lf;
2821
HWND parent;
2822
BOOL created_font = FALSE;
2823
int text_offset;
2824
SIZE box_size;
2825
HRGN region;
2826
HRESULT hr;
2827
2828
if (dwStyle & BS_PUSHLIKE)
2829
{
2830
PB_ThemedPaint(theme, infoPtr, hDC, state, dtFlags, focused);
2831
return;
2832
}
2833
2834
hr = GetThemeFont(theme, hDC, part, state, TMT_FONT, &lf);
2835
if (SUCCEEDED(hr)) {
2836
font = CreateFontIndirectW(&lf);
2837
if (!font)
2838
TRACE("Failed to create font\n");
2839
else {
2840
TRACE("font = %s\n", debugstr_w(lf.lfFaceName));
2841
hPrevFont = SelectObject(hDC, font);
2842
created_font = TRUE;
2843
}
2844
} else {
2845
if (infoPtr->font) SelectObject(hDC, infoPtr->font);
2846
}
2847
2848
GetClientRect(infoPtr->hwnd, &client_rect);
2849
GetThemeBackgroundContentRect(theme, hDC, part, state, &client_rect, &content_rect);
2850
region = set_control_clipping(hDC, &client_rect);
2851
2852
if (FAILED(GetThemePartSize(theme, hDC, part, state, &content_rect, TS_DRAW, &box_size)))
2853
{
2854
box_size.cx = 12 * GetDpiForWindow(infoPtr->hwnd) / 96 + 1;
2855
box_size.cy = box_size.cx;
2856
}
2857
2858
GetCharWidthW(hDC, '0', '0', &text_offset);
2859
text_offset /= 2;
2860
2861
label_rect = content_rect;
2862
if (dwStyle & BS_LEFTTEXT || ex_style & WS_EX_RIGHT)
2863
label_rect.right -= box_size.cx + text_offset;
2864
else
2865
label_rect.left += box_size.cx + text_offset;
2866
2867
old_label_rect = label_rect;
2868
dtFlags = BUTTON_CalcLayoutRects(infoPtr, hDC, &label_rect, &image_rect, &text_rect);
2869
box_rect = get_box_rect(dwStyle, ex_style, &content_rect, &label_rect, dtFlags != (UINT)-1L,
2870
box_size);
2871
2872
init_custom_draw(&nmcd, infoPtr, hDC, &client_rect);
2873
2874
parent = GetParent(infoPtr->hwnd);
2875
if (!parent) parent = infoPtr->hwnd;
2876
2877
/* Send erase notifications */
2878
cdrf = SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
2879
if (cdrf & CDRF_SKIPDEFAULT) goto cleanup;
2880
2881
DrawThemeParentBackground(infoPtr->hwnd, hDC, NULL);
2882
/* Tests show that the brush from WM_CTLCOLORSTATIC is used for filling background after a
2883
* DrawThemeParentBackground() call */
2884
brush = (HBRUSH)SendMessageW(parent, WM_CTLCOLORSTATIC, (WPARAM)hDC, (LPARAM)infoPtr->hwnd);
2885
FillRect(hDC, &client_rect, brush ? brush : GetSysColorBrush(COLOR_BTNFACE));
2886
2887
if (cdrf & CDRF_NOTIFYPOSTERASE)
2888
{
2889
nmcd.dwDrawStage = CDDS_POSTERASE;
2890
SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
2891
}
2892
2893
/* Send paint notifications */
2894
nmcd.dwDrawStage = CDDS_PREPAINT;
2895
cdrf = SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
2896
if (cdrf & CDRF_SKIPDEFAULT) goto cleanup;
2897
2898
/* Draw label */
2899
if (!(cdrf & CDRF_DOERASE))
2900
{
2901
DrawThemeBackground(theme, hDC, part, state, &box_rect, NULL);
2902
if (dtFlags != (UINT)-1L)
2903
BUTTON_DrawThemedLabel(infoPtr, hDC, dtFlags, &image_rect, &text_rect, theme, part, state);
2904
}
2905
2906
if (cdrf & CDRF_NOTIFYPOSTPAINT)
2907
{
2908
nmcd.dwDrawStage = CDDS_POSTPAINT;
2909
SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
2910
}
2911
if ((cdrf & CDRF_SKIPPOSTPAINT) || dtFlags == (UINT)-1L) goto cleanup;
2912
2913
if (focused)
2914
{
2915
label_rect.left--;
2916
label_rect.right++;
2917
IntersectRect(&label_rect, &label_rect, &old_label_rect);
2918
DrawFocusRect(hDC, &label_rect);
2919
}
2920
2921
cleanup:
2922
SelectClipRgn(hDC, region);
2923
if (region) DeleteObject(region);
2924
if (created_font) DeleteObject(font);
2925
if (hPrevFont) SelectObject(hDC, hPrevFont);
2926
}
2927
2928
static void GB_ThemedPaint(HTHEME theme, const BUTTON_INFO *infoPtr, HDC hDC, int state, UINT dtFlags, BOOL focused)
2929
{
2930
RECT clientRect, contentRect, labelRect, imageRect, textRect, bgRect;
2931
HRGN region, textRegion = NULL;
2932
LOGFONTW lf;
2933
HFONT font, hPrevFont = NULL;
2934
BOOL created_font = FALSE;
2935
TEXTMETRICW textMetric;
2936
HBRUSH brush;
2937
HWND parent;
2938
HRESULT hr;
2939
LONG style;
2940
int part;
2941
2942
/* DrawThemeParentBackground() is used for filling content background. The brush from
2943
* WM_CTLCOLORSTATIC is used for filling text background */
2944
parent = GetParent(infoPtr->hwnd);
2945
if (!parent)
2946
parent = infoPtr->hwnd;
2947
brush = (HBRUSH)SendMessageW(parent, WM_CTLCOLORSTATIC, (WPARAM)hDC, (LPARAM)infoPtr->hwnd);
2948
2949
hr = GetThemeFont(theme, hDC, BP_GROUPBOX, state, TMT_FONT, &lf);
2950
if (SUCCEEDED(hr)) {
2951
font = CreateFontIndirectW(&lf);
2952
if (!font)
2953
TRACE("Failed to create font\n");
2954
else {
2955
hPrevFont = SelectObject(hDC, font);
2956
created_font = TRUE;
2957
}
2958
} else {
2959
if (infoPtr->font)
2960
SelectObject(hDC, infoPtr->font);
2961
}
2962
2963
GetClientRect(infoPtr->hwnd, &clientRect);
2964
region = set_control_clipping(hDC, &clientRect);
2965
2966
bgRect = clientRect;
2967
GetTextMetricsW(hDC, &textMetric);
2968
bgRect.top += (textMetric.tmHeight / 2) - 1;
2969
2970
labelRect = clientRect;
2971
InflateRect(&labelRect, -7, 1);
2972
dtFlags = BUTTON_CalcLayoutRects(infoPtr, hDC, &labelRect, &imageRect, &textRect);
2973
if (dtFlags != (UINT)-1 && !show_image_only(infoPtr))
2974
{
2975
textRegion = CreateRectRgnIndirect(&textRect);
2976
ExtSelectClipRgn(hDC, textRegion, RGN_DIFF);
2977
}
2978
2979
style = GetWindowLongW(infoPtr->hwnd, GWL_STYLE);
2980
if (style & BS_PUSHLIKE)
2981
{
2982
part = BP_PUSHBUTTON;
2983
}
2984
else
2985
{
2986
part = BP_GROUPBOX;
2987
GetThemeBackgroundContentRect(theme, hDC, part, state, &bgRect, &contentRect);
2988
ExcludeClipRect(hDC, contentRect.left, contentRect.top, contentRect.right, contentRect.bottom);
2989
}
2990
if (IsThemeBackgroundPartiallyTransparent(theme, part, state))
2991
DrawThemeParentBackground(infoPtr->hwnd, hDC, NULL);
2992
DrawThemeBackground(theme, hDC, part, state, &bgRect, NULL);
2993
2994
if (dtFlags != (UINT)-1)
2995
{
2996
if (textRegion)
2997
{
2998
SelectClipRgn(hDC, textRegion);
2999
DeleteObject(textRegion);
3000
}
3001
FillRect(hDC, &textRect, brush ? brush : GetSysColorBrush(COLOR_BTNFACE));
3002
BUTTON_DrawThemedLabel(infoPtr, hDC, dtFlags, &imageRect, &textRect, theme, part, state);
3003
}
3004
3005
SelectClipRgn(hDC, region);
3006
if (region) DeleteObject(region);
3007
if (created_font) DeleteObject(font);
3008
if (hPrevFont) SelectObject(hDC, hPrevFont);
3009
}
3010
3011
static void SB_ThemedPaint(HTHEME theme, const BUTTON_INFO *infoPtr, HDC hDC, int state, UINT dtFlags, BOOL focused)
3012
{
3013
RECT rc, content_rect, push_rect, dropdown_rect, focus_rect, label_rect, image_rect, text_rect;
3014
NMCUSTOMDRAW nmcd;
3015
LRESULT cdrf;
3016
HWND parent;
3017
3018
if (infoPtr->font) SelectObject(hDC, infoPtr->font);
3019
3020
GetClientRect(infoPtr->hwnd, &rc);
3021
init_custom_draw(&nmcd, infoPtr, hDC, &rc);
3022
3023
parent = GetParent(infoPtr->hwnd);
3024
if (!parent) parent = infoPtr->hwnd;
3025
3026
/* Send erase notifications */
3027
cdrf = SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
3028
if (cdrf & CDRF_SKIPDEFAULT) return;
3029
3030
if (IsThemeBackgroundPartiallyTransparent(theme, BP_PUSHBUTTON, state))
3031
DrawThemeParentBackground(infoPtr->hwnd, hDC, NULL);
3032
3033
/* The zone outside the content is ignored for the dropdown (draws over) */
3034
GetThemeBackgroundContentRect(theme, hDC, BP_PUSHBUTTON, state, &rc, &content_rect);
3035
get_split_button_rects(infoPtr, &rc, &push_rect, &dropdown_rect);
3036
3037
if (infoPtr->split_style & BCSS_NOSPLIT)
3038
{
3039
push_rect = rc;
3040
DrawThemeBackground(theme, hDC, BP_PUSHBUTTON, state, &rc, NULL);
3041
GetThemeBackgroundContentRect(theme, hDC, BP_PUSHBUTTON, state, &push_rect, &focus_rect);
3042
}
3043
else
3044
{
3045
RECT r = { dropdown_rect.left, content_rect.top, dropdown_rect.right, content_rect.bottom };
3046
UINT edge = (infoPtr->split_style & BCSS_ALIGNLEFT) ? BF_RIGHT : BF_LEFT;
3047
const RECT *clip = NULL;
3048
3049
/* If only the dropdown is pressed, we need to draw it separately */
3050
if (state != PBS_PRESSED && (infoPtr->state & BST_DROPDOWNPUSHED))
3051
{
3052
DrawThemeBackground(theme, hDC, BP_PUSHBUTTON, PBS_PRESSED, &rc, &dropdown_rect);
3053
clip = &push_rect;
3054
}
3055
DrawThemeBackground(theme, hDC, BP_PUSHBUTTON, state, &rc, clip);
3056
3057
/* Draw the separator */
3058
DrawThemeEdge(theme, hDC, BP_PUSHBUTTON, state, &r, EDGE_ETCHED, edge, NULL);
3059
3060
/* The content rect should be the content area of the push button */
3061
GetThemeBackgroundContentRect(theme, hDC, BP_PUSHBUTTON, state, &push_rect, &content_rect);
3062
focus_rect = content_rect;
3063
}
3064
3065
if (cdrf & CDRF_NOTIFYPOSTERASE)
3066
{
3067
nmcd.dwDrawStage = CDDS_POSTERASE;
3068
SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
3069
}
3070
3071
/* Send paint notifications */
3072
nmcd.dwDrawStage = CDDS_PREPAINT;
3073
cdrf = SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
3074
if (cdrf & CDRF_SKIPDEFAULT) return;
3075
3076
if (!(cdrf & CDRF_DOERASE))
3077
{
3078
COLORREF old_color, color;
3079
INT old_bk_mode;
3080
3081
label_rect = content_rect;
3082
dtFlags = BUTTON_CalcLayoutRects(infoPtr, hDC, &label_rect, &image_rect, &text_rect);
3083
if (dtFlags != (UINT)-1L)
3084
BUTTON_DrawThemedLabel(infoPtr, hDC, dtFlags, &image_rect, &text_rect, theme,
3085
BP_PUSHBUTTON, state);
3086
3087
GetThemeColor(theme, BP_PUSHBUTTON, state, TMT_TEXTCOLOR, &color);
3088
old_bk_mode = SetBkMode(hDC, TRANSPARENT);
3089
old_color = SetTextColor(hDC, color);
3090
3091
draw_split_button_dropdown_glyph(infoPtr, hDC, &dropdown_rect);
3092
3093
SetTextColor(hDC, old_color);
3094
SetBkMode(hDC, old_bk_mode);
3095
}
3096
3097
if (cdrf & CDRF_NOTIFYPOSTPAINT)
3098
{
3099
nmcd.dwDrawStage = CDDS_POSTPAINT;
3100
SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
3101
}
3102
if (cdrf & CDRF_SKIPPOSTPAINT) return;
3103
3104
if (focused) DrawFocusRect(hDC, &focus_rect);
3105
}
3106
3107
static void CL_ThemedPaint(HTHEME theme, const BUTTON_INFO *infoPtr, HDC hDC, int state, UINT dtFlags, BOOL focused)
3108
{
3109
NMCUSTOMDRAW nmcd;
3110
LRESULT cdrf;
3111
HWND parent;
3112
int part;
3113
RECT rc;
3114
3115
if (infoPtr->font) SelectObject(hDC, infoPtr->font);
3116
3117
GetClientRect(infoPtr->hwnd, &rc);
3118
init_custom_draw(&nmcd, infoPtr, hDC, &rc);
3119
3120
parent = GetParent(infoPtr->hwnd);
3121
if (!parent) parent = infoPtr->hwnd;
3122
3123
/* Send erase notifications */
3124
cdrf = SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
3125
if (cdrf & CDRF_SKIPDEFAULT) return;
3126
3127
part = GetWindowLongW(infoPtr->hwnd, GWL_STYLE) & BS_PUSHLIKE ? BP_PUSHBUTTON : BP_COMMANDLINK;
3128
if (IsThemeBackgroundPartiallyTransparent(theme, part, state))
3129
DrawThemeParentBackground(infoPtr->hwnd, hDC, NULL);
3130
DrawThemeBackground(theme, hDC, part, state, &rc, NULL);
3131
3132
if (cdrf & CDRF_NOTIFYPOSTERASE)
3133
{
3134
nmcd.dwDrawStage = CDDS_POSTERASE;
3135
SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
3136
}
3137
3138
/* Send paint notifications */
3139
nmcd.dwDrawStage = CDDS_PREPAINT;
3140
cdrf = SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
3141
if (cdrf & CDRF_SKIPDEFAULT) return;
3142
3143
if (!(cdrf & CDRF_DOERASE))
3144
{
3145
RECT r, img_rect;
3146
UINT txt_h = 0;
3147
SIZE img_size;
3148
WCHAR *text;
3149
3150
GetThemeBackgroundContentRect(theme, hDC, part, state, &rc, &r);
3151
3152
/* The text alignment and styles are fixed and don't depend on button styles */
3153
dtFlags = DT_TOP | DT_LEFT | DT_WORDBREAK;
3154
3155
/* Command Links ignore the margins of the image list or its alignment */
3156
if (infoPtr->u.image || infoPtr->imagelist.himl)
3157
img_size = BUTTON_GetImageSize(infoPtr);
3158
else
3159
GetThemePartSize(theme, NULL, BP_COMMANDLINKGLYPH, state, NULL, TS_DRAW, &img_size);
3160
3161
img_rect = r;
3162
if (img_size.cx) r.left += img_size.cx + command_link_margin;
3163
3164
/* Draw the text */
3165
if ((text = get_button_text(infoPtr)))
3166
{
3167
UINT len = lstrlenW(text);
3168
RECT text_rect;
3169
3170
GetThemeTextExtent(theme, hDC, part, state, text, len, dtFlags | DT_END_ELLIPSIS, &r,
3171
&text_rect);
3172
DrawThemeText(theme, hDC, part, state, text, len, dtFlags | DT_END_ELLIPSIS, 0, &r);
3173
3174
txt_h = text_rect.bottom - text_rect.top;
3175
Free(text);
3176
}
3177
3178
/* Draw the note */
3179
if (infoPtr->note)
3180
{
3181
DTTOPTS opts;
3182
3183
r.top += txt_h;
3184
opts.dwSize = sizeof(opts);
3185
opts.dwFlags = DTT_FONTPROP;
3186
opts.iFontPropId = TMT_BODYFONT;
3187
DrawThemeTextEx(theme, hDC, part, state, infoPtr->note, infoPtr->note_length,
3188
dtFlags | DT_NOPREFIX, &r, &opts);
3189
}
3190
3191
/* Position the image at the vertical center of the drawn text (not note) */
3192
txt_h = min(txt_h, img_rect.bottom - img_rect.top);
3193
if (img_size.cy < txt_h) img_rect.top += (txt_h - img_size.cy) / 2;
3194
3195
img_rect.right = img_rect.left + img_size.cx;
3196
img_rect.bottom = img_rect.top + img_size.cy;
3197
3198
if (infoPtr->u.image || infoPtr->imagelist.himl)
3199
BUTTON_DrawImage(infoPtr, hDC, NULL,
3200
(state == CMDLS_DISABLED) ? DSS_DISABLED : DSS_NORMAL,
3201
&img_rect);
3202
else
3203
DrawThemeBackground(theme, hDC, BP_COMMANDLINKGLYPH, state, &img_rect, NULL);
3204
}
3205
3206
if (cdrf & CDRF_NOTIFYPOSTPAINT)
3207
{
3208
nmcd.dwDrawStage = CDDS_POSTPAINT;
3209
SendMessageW(parent, WM_NOTIFY, nmcd.hdr.idFrom, (LPARAM)&nmcd);
3210
}
3211
if (cdrf & CDRF_SKIPPOSTPAINT) return;
3212
3213
if (focused)
3214
{
3215
MARGINS margins;
3216
3217
/* The focus rect has margins of a push button rather than command link... */
3218
GetThemeMargins(theme, hDC, BP_PUSHBUTTON, state, TMT_CONTENTMARGINS, NULL, &margins);
3219
3220
rc.left += margins.cxLeftWidth;
3221
rc.top += margins.cyTopHeight;
3222
rc.right -= margins.cxRightWidth;
3223
rc.bottom -= margins.cyBottomHeight;
3224
DrawFocusRect(hDC, &rc);
3225
}
3226
}
3227
3228
void BUTTON_Register(void)
3229
{
3230
WNDCLASSW wndClass;
3231
3232
memset(&wndClass, 0, sizeof(wndClass));
3233
wndClass.style = CS_GLOBALCLASS | CS_DBLCLKS | CS_VREDRAW | CS_HREDRAW | CS_PARENTDC;
3234
wndClass.lpfnWndProc = BUTTON_WindowProc;
3235
wndClass.cbClsExtra = 0;
3236
wndClass.cbWndExtra = sizeof(BUTTON_INFO *);
3237
wndClass.hCursor = LoadCursorW(0, (LPWSTR)IDC_ARROW);
3238
wndClass.hbrBackground = NULL;
3239
wndClass.lpszClassName = WC_BUTTONW;
3240
RegisterClassW(&wndClass);
3241
}
3242
3243