button.c 33.3 KB
Newer Older
Alexandre Julliard's avatar
Alexandre Julliard committed
1
/* File: button.c -- Button type widgets
Alexandre Julliard's avatar
Alexandre Julliard committed
2
 *
Alexandre Julliard's avatar
Alexandre Julliard committed
3 4
 * Copyright (C) 1993 Johannes Ruscheinski
 * Copyright (C) 1993 David Metcalfe
Alexandre Julliard's avatar
Alexandre Julliard committed
5
 * Copyright (C) 1994 Alexandre Julliard
6 7 8 9 10 11 12 13 14 15 16 17 18
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * 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 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49
 *
 * NOTES
 *
 * This code was audited for completeness against the documented features
 * of Comctl32.dll version 6.0 on Oct. 3, 2004, by Dimitrie O. Paun.
 * 
 * Unless otherwise noted, we believe this code to be complete, as per
 * the specification mentioned above.
 * If you discover missing features, or bugs, please note them below.
 * 
 * TODO
 *  Styles
 *  - BS_NOTIFY: is it complete?
 *  - BS_RIGHTBUTTON: same as BS_LEFTTEXT
 *
 *  Messages
 *  - WM_CHAR: Checks a (manual or automatic) check box on '+' or '=', clears it on '-' key.
 *  - WM_SETFOCUS: For (manual or automatic) radio buttons, send the parent window BN_CLICKED
 *  - WM_NCCREATE: Turns any BS_OWNERDRAW button into a BS_PUSHBUTTON button.
 *  - WM_SYSKEYUP
 *  - BCM_GETIDEALSIZE
 *  - BCM_GETIMAGELIST
 *  - BCM_GETTEXTMARGIN
 *  - BCM_SETIMAGELIST
 *  - BCM_SETTEXTMARGIN
 *  
 *  Notifications
 *  - BCN_HOTITEMCHANGE
 *  - BN_DISABLE
 *  - BN_PUSHED/BN_HILITE
50
 *  + BN_KILLFOCUS: is it OK?
51
 *  - BN_PAINT
52
 *  + BN_SETFOCUS: is it OK?
53 54 55 56 57 58 59 60 61 62 63
 *  - BN_UNPUSHED/BN_UNHILITE
 *  - NM_CUSTOMDRAW
 *
 *  Structures/Macros/Definitions
 *  - BUTTON_IMAGELIST
 *  - NMBCHOTITEM
 *  - Button_GetIdealSize
 *  - Button_GetImageList
 *  - Button_GetTextMargin
 *  - Button_SetImageList
 *  - Button_SetTextMargin
Alexandre Julliard's avatar
Alexandre Julliard committed
64
 */
Alexandre Julliard's avatar
Alexandre Julliard committed
65

66
#include <stdarg.h>
67
#include <string.h>
68 69
#include <stdlib.h>

70 71
#define OEMRESOURCE

72
#include "windef.h"
73
#include "winbase.h"
74
#include "wingdi.h"
75
#include "controls.h"
76
#include "win.h"
77
#include "user_private.h"
78 79 80
#include "wine/debug.h"

WINE_DEFAULT_DEBUG_CHANNEL(button);
Alexandre Julliard's avatar
Alexandre Julliard committed
81

82 83 84
/* GetWindowLong offsets for window extra information */
#define STATE_GWL_OFFSET  0
#define HFONT_GWL_OFFSET  (sizeof(LONG))
85 86
#define HIMAGE_GWL_OFFSET (HFONT_GWL_OFFSET+sizeof(HFONT))
#define NB_EXTRA_BYTES    (HIMAGE_GWL_OFFSET+sizeof(HANDLE))
87

88
/* undocumented flags */
89 90 91 92 93
#define BUTTON_NSTATES         0x0F
#define BUTTON_BTNPRESSED      0x40
#define BUTTON_UNKNOWN2        0x20
#define BUTTON_UNKNOWN3        0x10

94 95 96 97 98 99 100 101
#define BUTTON_NOTIFY_PARENT(hWnd, code) \
    do { /* Notify parent which has created this button control */ \
        TRACE("notification " #code " sent to hwnd=%p\n", GetParent(hWnd)); \
        SendMessageW(GetParent(hWnd), WM_COMMAND, \
                     MAKEWPARAM(GetWindowLongPtrW((hWnd),GWLP_ID), (code)), \
                     (LPARAM)(hWnd)); \
    } while(0)

102
static UINT BUTTON_CalcLabelRect( HWND hwnd, HDC hdc, RECT *rc );
103 104 105 106 107 108
static void PB_Paint( HWND hwnd, HDC hDC, UINT action );
static void CB_Paint( HWND hwnd, HDC hDC, UINT action );
static void GB_Paint( HWND hwnd, HDC hDC, UINT action );
static void UB_Paint( HWND hwnd, HDC hDC, UINT action );
static void OB_Paint( HWND hwnd, HDC hDC, UINT action );
static void BUTTON_CheckAutoRadioButton( HWND hwnd );
Alexandre Julliard's avatar
Alexandre Julliard committed
109

110
#define MAX_BTN_TYPE  16
Alexandre Julliard's avatar
Alexandre Julliard committed
111

Alexandre Julliard's avatar
Alexandre Julliard committed
112
static const WORD maxCheckState[MAX_BTN_TYPE] =
Alexandre Julliard's avatar
Alexandre Julliard committed
113
{
114 115 116 117 118 119 120 121 122 123 124 125
    BST_UNCHECKED,      /* BS_PUSHBUTTON */
    BST_UNCHECKED,      /* BS_DEFPUSHBUTTON */
    BST_CHECKED,        /* BS_CHECKBOX */
    BST_CHECKED,        /* BS_AUTOCHECKBOX */
    BST_CHECKED,        /* BS_RADIOBUTTON */
    BST_INDETERMINATE,  /* BS_3STATE */
    BST_INDETERMINATE,  /* BS_AUTO3STATE */
    BST_UNCHECKED,      /* BS_GROUPBOX */
    BST_UNCHECKED,      /* BS_USERBUTTON */
    BST_CHECKED,        /* BS_AUTORADIOBUTTON */
    BST_UNCHECKED,      /* BS_PUSHBOX */
    BST_UNCHECKED       /* BS_OWNERDRAW */
Alexandre Julliard's avatar
Alexandre Julliard committed
126 127
};

128
typedef void (*pfPaint)( HWND hwnd, HDC hdc, UINT action );
Alexandre Julliard's avatar
Alexandre Julliard committed
129

Alexandre Julliard's avatar
Alexandre Julliard committed
130
static const pfPaint btnPaintFunc[MAX_BTN_TYPE] =
Alexandre Julliard's avatar
Alexandre Julliard committed
131 132 133 134 135 136 137 138 139 140 141
{
    PB_Paint,    /* BS_PUSHBUTTON */
    PB_Paint,    /* BS_DEFPUSHBUTTON */
    CB_Paint,    /* BS_CHECKBOX */
    CB_Paint,    /* BS_AUTOCHECKBOX */
    CB_Paint,    /* BS_RADIOBUTTON */
    CB_Paint,    /* BS_3STATE */
    CB_Paint,    /* BS_AUTO3STATE */
    GB_Paint,    /* BS_GROUPBOX */
    UB_Paint,    /* BS_USERBUTTON */
    CB_Paint,    /* BS_AUTORADIOBUTTON */
142
    NULL,        /* BS_PUSHBOX */
Alexandre Julliard's avatar
Alexandre Julliard committed
143
    OB_Paint     /* BS_OWNERDRAW */
Alexandre Julliard's avatar
Alexandre Julliard committed
144 145
};

146
static HBITMAP hbitmapCheckBoxes = 0;
Alexandre Julliard's avatar
Alexandre Julliard committed
147 148
static WORD checkBoxWidth = 0, checkBoxHeight = 0;

Alexandre Julliard's avatar
Alexandre Julliard committed
149

150 151 152
/*********************************************************************
 * button class descriptor
 */
153
static const WCHAR buttonW[] = {'B','u','t','t','o','n',0};
154 155
const struct builtin_class_descr BUTTON_builtin_class =
{
156
    buttonW,             /* name */
157
    CS_DBLCLKS | CS_VREDRAW | CS_HREDRAW | CS_PARENTDC, /* style  */
158
    WINPROC_BUTTON,      /* proc */
159
    NB_EXTRA_BYTES,      /* extra */
160
    IDC_ARROW,           /* cursor */
161 162 163 164
    0                    /* brush */
};


165
static inline LONG get_button_state( HWND hwnd )
166
{
167
    return GetWindowLongW( hwnd, STATE_GWL_OFFSET );
168 169
}

170
static inline void set_button_state( HWND hwnd, LONG state )
171
{
172
    SetWindowLongW( hwnd, STATE_GWL_OFFSET, state );
173 174
}

175
static inline HFONT get_button_font( HWND hwnd )
176
{
177
    return (HFONT)GetWindowLongPtrW( hwnd, HFONT_GWL_OFFSET );
178 179
}

180
static inline void set_button_font( HWND hwnd, HFONT font )
181
{
182
    SetWindowLongPtrW( hwnd, HFONT_GWL_OFFSET, (LONG_PTR)font );
183 184
}

185
static inline UINT get_button_type( LONG window_style )
186
{
187
    return (window_style & BS_TYPEMASK);
188 189 190
}

/* paint a button of any type */
191
static inline void paint_button( HWND hwnd, LONG style, UINT action )
192 193 194 195 196 197 198 199 200 201
{
    if (btnPaintFunc[style] && IsWindowVisible(hwnd))
    {
        HDC hdc = GetDC( hwnd );
        btnPaintFunc[style]( hwnd, hdc, action );
        ReleaseDC( hwnd, hdc );
    }
}

/* retrieve the button text; returned buffer must be freed by caller */
202
static inline WCHAR *get_button_text( HWND hwnd )
203
{
204
    INT len = 512;
205
    WCHAR *buffer = HeapAlloc( GetProcessHeap(), 0, (len + 1) * sizeof(WCHAR) );
206
    if (buffer) InternalGetWindowText( hwnd, buffer, len + 1 );
207 208 209
    return buffer;
}

Alexandre Julliard's avatar
Alexandre Julliard committed
210
/***********************************************************************
211
 *           ButtonWndProc_common
Alexandre Julliard's avatar
Alexandre Julliard committed
212
 */
213
LRESULT ButtonWndProc_common(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL unicode )
Alexandre Julliard's avatar
Alexandre Julliard committed
214
{
215
    RECT rect;
216
    POINT pt;
217
    LONG style = GetWindowLongW( hWnd, GWL_STYLE );
218 219
    UINT btn_type = get_button_type( style );
    LONG state;
220
    HANDLE oldHbitmap;
Alexandre Julliard's avatar
Alexandre Julliard committed
221

222 223
    if (!IsWindow( hWnd )) return 0;

224 225
    pt.x = (short)LOWORD(lParam);
    pt.y = (short)HIWORD(lParam);
226

Alexandre Julliard's avatar
Alexandre Julliard committed
227 228 229
    switch (uMsg)
    {
    case WM_GETDLGCODE:
230
        switch(btn_type)
Alexandre Julliard's avatar
Alexandre Julliard committed
231
        {
232 233 234 235 236
        case BS_USERBUTTON:
        case BS_PUSHBUTTON:      return DLGC_BUTTON | DLGC_UNDEFPUSHBUTTON;
        case BS_DEFPUSHBUTTON:   return DLGC_BUTTON | DLGC_DEFPUSHBUTTON;
        case BS_RADIOBUTTON:
        case BS_AUTORADIOBUTTON: return DLGC_BUTTON | DLGC_RADIOBUTTON;
237
        case BS_GROUPBOX:        return DLGC_STATIC;
Alexandre Julliard's avatar
Alexandre Julliard committed
238 239
        default:                 return DLGC_BUTTON;
        }
Alexandre Julliard's avatar
Alexandre Julliard committed
240

Alexandre Julliard's avatar
Alexandre Julliard committed
241
    case WM_ENABLE:
242
        paint_button( hWnd, btn_type, ODA_DRAWENTIRE );
Alexandre Julliard's avatar
Alexandre Julliard committed
243
        break;
Alexandre Julliard's avatar
Alexandre Julliard committed
244

Alexandre Julliard's avatar
Alexandre Julliard committed
245 246 247
    case WM_CREATE:
        if (!hbitmapCheckBoxes)
        {
248
            BITMAP bmp;
249 250
            hbitmapCheckBoxes = LoadBitmapW(0, MAKEINTRESOURCEW(OBM_CHECKBOXES));
            GetObjectW( hbitmapCheckBoxes, sizeof(bmp), &bmp );
Alexandre Julliard's avatar
Alexandre Julliard committed
251 252 253
            checkBoxWidth  = bmp.bmWidth / 4;
            checkBoxHeight = bmp.bmHeight / 3;
        }
254
        if (btn_type >= MAX_BTN_TYPE)
255
            return -1; /* abort */
256 257 258 259

        /* XP turns a BS_USERBUTTON into BS_PUSHBUTTON */
        if (btn_type == BS_USERBUTTON )
        {
260 261
            style = (style & ~BS_TYPEMASK) | BS_PUSHBUTTON;
            WIN_SetStyle( hWnd, style, BS_TYPEMASK & ~style );
262
        }
263
        set_button_state( hWnd, BST_UNCHECKED );
Alexandre Julliard's avatar
Alexandre Julliard committed
264
        return 0;
Alexandre Julliard's avatar
Alexandre Julliard committed
265

Alexandre Julliard's avatar
Alexandre Julliard committed
266
    case WM_ERASEBKGND:
267 268 269 270
        if (btn_type == BS_OWNERDRAW)
        {
            HDC hdc = (HDC)wParam;
            RECT rc;
271 272 273 274
            HBRUSH hBrush;
            HWND parent = GetParent(hWnd);
            if (!parent) parent = hWnd;
            hBrush = (HBRUSH)SendMessageW(parent, WM_CTLCOLORBTN, (WPARAM)hdc, (LPARAM)hWnd);
275
            if (!hBrush) /* did the app forget to call defwindowproc ? */
276
                hBrush = (HBRUSH)DefWindowProcW(parent, WM_CTLCOLORBTN,
277 278 279 280
                                                (WPARAM)hdc, (LPARAM)hWnd);
            GetClientRect(hWnd, &rc);
            FillRect(hdc, &rc, hBrush);
        }
Alexandre Julliard's avatar
Alexandre Julliard committed
281
        return 1;
Alexandre Julliard's avatar
Alexandre Julliard committed
282

283
    case WM_PRINTCLIENT:
Alexandre Julliard's avatar
Alexandre Julliard committed
284
    case WM_PAINT:
285
        if (btnPaintFunc[btn_type])
Alexandre Julliard's avatar
Alexandre Julliard committed
286
        {
287 288
            PAINTSTRUCT ps;
            HDC hdc = wParam ? (HDC)wParam : BeginPaint( hWnd, &ps );
289
            int nOldMode = SetBkMode( hdc, OPAQUE );
290
            (btnPaintFunc[btn_type])( hWnd, hdc, ODA_DRAWENTIRE );
291
            SetBkMode(hdc, nOldMode); /*  reset painting mode */
292
            if( !wParam ) EndPaint( hWnd, &ps );
Alexandre Julliard's avatar
Alexandre Julliard committed
293 294 295
        }
        break;

296 297 298
    case WM_KEYDOWN:
	if (wParam == VK_SPACE)
	{
299
	    SendMessageW( hWnd, BM_SETSTATE, TRUE, 0 );
300
            set_button_state( hWnd, get_button_state( hWnd ) | BUTTON_BTNPRESSED );
301
            SetCapture( hWnd );
302 303
	}
	break;
304

305
    case WM_LBUTTONDBLCLK:
306 307 308 309 310
        if(style & BS_NOTIFY ||
           btn_type == BS_RADIOBUTTON ||
           btn_type == BS_USERBUTTON ||
           btn_type == BS_OWNERDRAW)
        {
311
            BUTTON_NOTIFY_PARENT(hWnd, BN_DOUBLECLICKED);
312 313 314
            break;
        }
        /* fall through */
315
    case WM_LBUTTONDOWN:
316
        SetCapture( hWnd );
317
        SetFocus( hWnd );
318
        set_button_state( hWnd, get_button_state( hWnd ) | BUTTON_BTNPRESSED );
319
        SendMessageW( hWnd, BM_SETSTATE, TRUE, 0 );
Alexandre Julliard's avatar
Alexandre Julliard committed
320 321
        break;

322 323 324 325
    case WM_KEYUP:
	if (wParam != VK_SPACE)
	    break;
	/* fall through */
Alexandre Julliard's avatar
Alexandre Julliard committed
326
    case WM_LBUTTONUP:
327 328 329 330
        state = get_button_state( hWnd );
        if (!(state & BUTTON_BTNPRESSED)) break;
        state &= BUTTON_NSTATES;
        set_button_state( hWnd, state );
331
        if (!(state & BST_PUSHED))
332
        {
333 334 335
            ReleaseCapture();
            break;
        }
336
        SendMessageW( hWnd, BM_SETSTATE, FALSE, 0 );
337
        ReleaseCapture();
338
        GetClientRect( hWnd, &rect );
339
	if (uMsg == WM_KEYUP || PtInRect( &rect, pt ))
Alexandre Julliard's avatar
Alexandre Julliard committed
340
        {
341 342
            state = get_button_state( hWnd );
            switch(btn_type)
Alexandre Julliard's avatar
Alexandre Julliard committed
343 344
            {
            case BS_AUTOCHECKBOX:
345
                SendMessageW( hWnd, BM_SETCHECK, !(state & BST_CHECKED), 0 );
Alexandre Julliard's avatar
Alexandre Julliard committed
346
                break;
Alexandre Julliard's avatar
Alexandre Julliard committed
347
            case BS_AUTORADIOBUTTON:
348
                SendMessageW( hWnd, BM_SETCHECK, TRUE, 0 );
Alexandre Julliard's avatar
Alexandre Julliard committed
349 350
                break;
            case BS_AUTO3STATE:
351
                SendMessageW( hWnd, BM_SETCHECK,
352
                                (state & BST_INDETERMINATE) ? 0 : ((state & 3) + 1), 0 );
Alexandre Julliard's avatar
Alexandre Julliard committed
353
                break;
Alexandre Julliard's avatar
Alexandre Julliard committed
354
            }
355
            BUTTON_NOTIFY_PARENT(hWnd, BN_CLICKED);
Alexandre Julliard's avatar
Alexandre Julliard committed
356 357 358
        }
        break;

359
    case WM_CAPTURECHANGED:
360
        TRACE("WM_CAPTURECHANGED %p\n", hWnd);
361 362 363 364 365
        state = get_button_state( hWnd );
        if (state & BUTTON_BTNPRESSED)
        {
            state &= BUTTON_NSTATES;
            set_button_state( hWnd, state );
366
            if (state & BST_PUSHED) SendMessageW( hWnd, BM_SETSTATE, FALSE, 0 );
367 368 369
        }
        break;

Alexandre Julliard's avatar
Alexandre Julliard committed
370
    case WM_MOUSEMOVE:
371
        if ((wParam & MK_LBUTTON) && GetCapture() == hWnd)
Alexandre Julliard's avatar
Alexandre Julliard committed
372
        {
373
            GetClientRect( hWnd, &rect );
374
            SendMessageW( hWnd, BM_SETSTATE, PtInRect(&rect, pt), 0 );
Alexandre Julliard's avatar
Alexandre Julliard committed
375 376 377 378
        }
        break;

    case WM_SETTEXT:
379 380 381 382 383
    {
        /* Clear an old text here as Windows does */
        HDC hdc = GetDC(hWnd);
        HBRUSH hbrush;
        RECT client, rc;
384
        HWND parent = GetParent(hWnd);
385

386 387
        if (!parent) parent = hWnd;
        hbrush = (HBRUSH)SendMessageW(parent, WM_CTLCOLORSTATIC,
388
				      (WPARAM)hdc, (LPARAM)hWnd);
389
        if (!hbrush) /* did the app forget to call DefWindowProc ? */
390
            hbrush = (HBRUSH)DefWindowProcW(parent, WM_CTLCOLORSTATIC,
391
					    (WPARAM)hdc, (LPARAM)hWnd);
392 393 394 395 396 397 398 399 400 401

        GetClientRect(hWnd, &client);
        rc = client;
        BUTTON_CalcLabelRect(hWnd, hdc, &rc);
        /* Clip by client rect bounds */
        if (rc.right > client.right) rc.right = client.right;
        if (rc.bottom > client.bottom) rc.bottom = client.bottom;
        FillRect(hdc, &rc, hbrush);
        ReleaseDC(hWnd, hdc);

402 403
        if (unicode) DefWindowProcW( hWnd, WM_SETTEXT, wParam, lParam );
        else DefWindowProcA( hWnd, WM_SETTEXT, wParam, lParam );
404 405 406 407
        if (btn_type == BS_GROUPBOX) /* Yes, only for BS_GROUPBOX */
            InvalidateRect( hWnd, NULL, TRUE );
        else
            paint_button( hWnd, btn_type, ODA_DRAWENTIRE );
408
        return 1; /* success. FIXME: check text length */
409
    }
Alexandre Julliard's avatar
Alexandre Julliard committed
410 411

    case WM_SETFONT:
412
        set_button_font( hWnd, (HFONT)wParam );
413
        if (lParam) InvalidateRect(hWnd, NULL, TRUE);
Alexandre Julliard's avatar
Alexandre Julliard committed
414 415 416
        break;

    case WM_GETFONT:
417
        return (LRESULT)get_button_font( hWnd );
Alexandre Julliard's avatar
Alexandre Julliard committed
418 419

    case WM_SETFOCUS:
420
        TRACE("WM_SETFOCUS %p\n",hWnd);
421
        set_button_state( hWnd, get_button_state(hWnd) | BST_FOCUS );
422
        paint_button( hWnd, btn_type, ODA_FOCUS );
423 424
        if (style & BS_NOTIFY)
            BUTTON_NOTIFY_PARENT(hWnd, BN_SETFOCUS);
Alexandre Julliard's avatar
Alexandre Julliard committed
425 426 427
        break;

    case WM_KILLFOCUS:
428
        TRACE("WM_KILLFOCUS %p\n",hWnd);
429
        state = get_button_state( hWnd );
430
        set_button_state( hWnd, state & ~BST_FOCUS );
431
	paint_button( hWnd, btn_type, ODA_FOCUS );
432 433 434

        if ((state & BUTTON_BTNPRESSED) && GetCapture() == hWnd)
            ReleaseCapture();
435 436 437
        if (style & BS_NOTIFY)
            BUTTON_NOTIFY_PARENT(hWnd, BN_KILLFOCUS);

438
        InvalidateRect( hWnd, NULL, FALSE );
Alexandre Julliard's avatar
Alexandre Julliard committed
439 440 441
        break;

    case WM_SYSCOLORCHANGE:
442
        InvalidateRect( hWnd, NULL, FALSE );
Alexandre Julliard's avatar
Alexandre Julliard committed
443 444
        break;

445
    case BM_SETSTYLE:
446 447 448 449
        if ((wParam & BS_TYPEMASK) >= MAX_BTN_TYPE) break;
        btn_type = wParam & BS_TYPEMASK;
        style = (style & ~BS_TYPEMASK) | btn_type;
        WIN_SetStyle( hWnd, style, BS_TYPEMASK & ~style );
450 451 452

        /* Only redraw if lParam flag is set.*/
        if (lParam)
453
            InvalidateRect( hWnd, NULL, TRUE );
454

Alexandre Julliard's avatar
Alexandre Julliard committed
455 456
        break;

457
    case BM_CLICK:
458 459
	SendMessageW( hWnd, WM_LBUTTONDOWN, 0, 0 );
	SendMessageW( hWnd, WM_LBUTTONUP, 0, 0 );
460 461
	break;

462
    case BM_SETIMAGE:
463 464 465 466 467 468 469 470 471 472 473 474
        /* Check that image format matches button style */
        switch (style & (BS_BITMAP|BS_ICON))
        {
        case BS_BITMAP:
            if (wParam != IMAGE_BITMAP) return 0;
            break;
        case BS_ICON:
            if (wParam != IMAGE_ICON) return 0;
            break;
        default:
            return 0;
        }
475
        oldHbitmap = (HBITMAP)SetWindowLongPtrW( hWnd, HIMAGE_GWL_OFFSET, lParam );
476
	InvalidateRect( hWnd, NULL, FALSE );
477
	return (LRESULT)oldHbitmap;
478 479

    case BM_GETIMAGE:
480
        return GetWindowLongPtrW( hWnd, HIMAGE_GWL_OFFSET );
481

482
    case BM_GETCHECK:
483
        return get_button_state( hWnd ) & 3;
Alexandre Julliard's avatar
Alexandre Julliard committed
484

485
    case BM_SETCHECK:
486 487
        if (wParam > maxCheckState[btn_type]) wParam = maxCheckState[btn_type];
        state = get_button_state( hWnd );
488 489
        if ((btn_type == BS_RADIOBUTTON) || (btn_type == BS_AUTORADIOBUTTON))
        {
490 491
            if (wParam) WIN_SetStyle( hWnd, WS_TABSTOP, 0 );
            else WIN_SetStyle( hWnd, 0, WS_TABSTOP );
492
        }
493
        if ((state & 3) != wParam)
Alexandre Julliard's avatar
Alexandre Julliard committed
494
        {
495 496
            set_button_state( hWnd, (state & ~3) | wParam );
            paint_button( hWnd, btn_type, ODA_SELECT );
Alexandre Julliard's avatar
Alexandre Julliard committed
497
        }
498
        if ((btn_type == BS_AUTORADIOBUTTON) && (wParam == BST_CHECKED) && (style & WS_CHILD))
499
            BUTTON_CheckAutoRadioButton( hWnd );
Alexandre Julliard's avatar
Alexandre Julliard committed
500
        break;
Alexandre Julliard's avatar
Alexandre Julliard committed
501

502
    case BM_GETSTATE:
503
        return get_button_state( hWnd );
Alexandre Julliard's avatar
Alexandre Julliard committed
504

505
    case BM_SETSTATE:
506
        state = get_button_state( hWnd );
Alexandre Julliard's avatar
Alexandre Julliard committed
507
        if (wParam)
508
            set_button_state( hWnd, state | BST_PUSHED );
Alexandre Julliard's avatar
Alexandre Julliard committed
509
        else
510
            set_button_state( hWnd, state & ~BST_PUSHED );
511

512
        paint_button( hWnd, btn_type, ODA_SELECT );
Alexandre Julliard's avatar
Alexandre Julliard committed
513 514
        break;

515
    case WM_NCHITTEST:
516
        if(btn_type == BS_GROUPBOX) return HTTRANSPARENT;
517
        /* fall through */
Alexandre Julliard's avatar
Alexandre Julliard committed
518
    default:
519 520
        return unicode ? DefWindowProcW(hWnd, uMsg, wParam, lParam) :
                         DefWindowProcA(hWnd, uMsg, wParam, lParam);
Alexandre Julliard's avatar
Alexandre Julliard committed
521 522
    }
    return 0;
Alexandre Julliard's avatar
Alexandre Julliard committed
523 524
}

525 526 527
/**********************************************************************
 * Convert button styles to flags used by DrawText.
 */
528
static UINT BUTTON_BStoDT( DWORD style, DWORD ex_style )
529 530 531 532 533
{
   UINT dtStyle = DT_NOCLIP;  /* We use SelectClipRgn to limit output */

   /* "Convert" pushlike buttons to pushbuttons */
   if (style & BS_PUSHLIKE)
534
      style &= ~BS_TYPEMASK;
535 536 537 538 539 540 541 542 543 544 545 546 547

   if (!(style & BS_MULTILINE))
      dtStyle |= DT_SINGLELINE;
   else
      dtStyle |= DT_WORDBREAK;

   switch (style & BS_CENTER)
   {
      case BS_LEFT:   /* DT_LEFT is 0 */    break;
      case BS_RIGHT:  dtStyle |= DT_RIGHT;  break;
      case BS_CENTER: dtStyle |= DT_CENTER; break;
      default:
         /* Pushbutton's text is centered by default */
548
         if (get_button_type(style) <= BS_DEFPUSHBUTTON) dtStyle |= DT_CENTER;
549 550 551
         /* all other flavours have left aligned text */
   }

552 553
   if (ex_style & WS_EX_RIGHT) dtStyle = DT_RIGHT | (dtStyle & ~(DT_LEFT | DT_CENTER));

554
   /* DrawText ignores vertical alignment for multiline text,
555
    * but we use these flags to align label manually.
556
    */
557
   if (get_button_type(style) != BS_GROUPBOX)
558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583
   {
      switch (style & BS_VCENTER)
      {
         case BS_TOP:     /* DT_TOP is 0 */      break;
         case BS_BOTTOM:  dtStyle |= DT_BOTTOM;  break;
         case BS_VCENTER: /* fall through */
         default:         dtStyle |= DT_VCENTER; break;
      }
   }
   else
      /* GroupBox's text is always single line and is top aligned. */
      dtStyle |= DT_SINGLELINE;

   return dtStyle;
}

/**********************************************************************
 *       BUTTON_CalcLabelRect
 *
 *   Calculates label's rectangle depending on button style.
 *
 * Returns flags to be passed to DrawText.
 * Calculated rectangle doesn't take into account button state
 * (pushed, etc.). If there is nothing to draw (no text/image) output
 * rectangle is empty, and return value is (UINT)-1.
 */
584
static UINT BUTTON_CalcLabelRect(HWND hwnd, HDC hdc, RECT *rc)
585
{
586
   LONG style = GetWindowLongW( hwnd, GWL_STYLE );
587
   LONG ex_style = GetWindowLongW( hwnd, GWL_EXSTYLE );
588
   WCHAR *text;
589 590
   ICONINFO    iconInfo;
   BITMAP      bm;
591
   UINT        dtStyle = BUTTON_BStoDT( style, ex_style );
592 593 594 595
   RECT        r = *rc;
   INT         n;

   /* Calculate label rectangle according to label type */
596
   switch (style & (BS_ICON|BS_BITMAP))
597 598
   {
      case BS_TEXT:
599 600 601 602 603 604 605 606 607
          if (!(text = get_button_text( hwnd ))) goto empty_rect;
          if (!text[0])
          {
              HeapFree( GetProcessHeap(), 0, text );
              goto empty_rect;
          }
          DrawTextW(hdc, text, -1, &r, dtStyle | DT_CALCRECT);
          HeapFree( GetProcessHeap(), 0, text );
          break;
608 609

      case BS_ICON:
610
         if (!GetIconInfo((HICON)GetWindowLongPtrW( hwnd, HIMAGE_GWL_OFFSET ), &iconInfo))
611 612
            goto empty_rect;

613
         GetObjectW (iconInfo.hbmColor, sizeof(BITMAP), &bm);
614 615 616 617 618 619 620 621 622

         r.right  = r.left + bm.bmWidth;
         r.bottom = r.top  + bm.bmHeight;

         DeleteObject(iconInfo.hbmColor);
         DeleteObject(iconInfo.hbmMask);
         break;

      case BS_BITMAP:
623
         if (!GetObjectW( (HANDLE)GetWindowLongPtrW( hwnd, HIMAGE_GWL_OFFSET ), sizeof(BITMAP), &bm))
624 625 626 627 628 629 630
            goto empty_rect;

         r.right  = r.left + bm.bmWidth;
         r.bottom = r.top  + bm.bmHeight;
         break;

      default:
631
      empty_rect:
Felix Nawothnig's avatar
Felix Nawothnig committed
632 633
         rc->right = r.left;
         rc->bottom = r.top;
634
         return (UINT)-1;
635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673
   }

   /* Position label inside bounding rectangle according to
    * alignment flags. (calculated rect is always left-top aligned).
    * If label is aligned to any side - shift label in opposite
    * direction to leave extra space for focus rectangle.
    */
   switch (dtStyle & (DT_CENTER|DT_RIGHT))
   {
      case DT_LEFT:    r.left++;  r.right++;  break;
      case DT_CENTER:  n = r.right - r.left;
                       r.left   = rc->left + ((rc->right - rc->left) - n) / 2;
                       r.right  = r.left + n; break;
      case DT_RIGHT:   n = r.right - r.left;
                       r.right  = rc->right - 1;
                       r.left   = r.right - n;
                       break;
   }

   switch (dtStyle & (DT_VCENTER|DT_BOTTOM))
   {
      case DT_TOP:     r.top++;  r.bottom++;  break;
      case DT_VCENTER: n = r.bottom - r.top;
                       r.top    = rc->top + ((rc->bottom - rc->top) - n) / 2;
                       r.bottom = r.top + n;  break;
      case DT_BOTTOM:  n = r.bottom - r.top;
                       r.bottom = rc->bottom - 1;
                       r.top    = r.bottom - n;
                       break;
   }

   *rc = r;
   return dtStyle;
}


/**********************************************************************
 *       BUTTON_DrawTextCallback
 *
674
 *   Callback function used by DrawStateW function.
675 676 677
 */
static BOOL CALLBACK BUTTON_DrawTextCallback(HDC hdc, LPARAM lp, WPARAM wp, int cx, int cy)
{
678 679 680 681 682
   RECT rc;
   rc.left = 0;
   rc.top = 0;
   rc.right = cx;
   rc.bottom = cy;
683 684 685 686 687 688 689 690 691 692 693

   DrawTextW(hdc, (LPCWSTR)lp, -1, &rc, (UINT)wp);
   return TRUE;
}


/**********************************************************************
 *       BUTTON_DrawLabel
 *
 *   Common function for drawing button label.
 */
694
static void BUTTON_DrawLabel(HWND hwnd, HDC hdc, UINT dtFlags, const RECT *rc)
695 696 697 698 699
{
   DRAWSTATEPROC lpOutputProc = NULL;
   LPARAM lp;
   WPARAM wp = 0;
   HBRUSH hbr = 0;
700 701
   UINT flags = IsWindowEnabled(hwnd) ? DSS_NORMAL : DSS_DISABLED;
   LONG state = get_button_state( hwnd );
702
   LONG style = GetWindowLongW( hwnd, GWL_STYLE );
703
   WCHAR *text = NULL;
704

705
   /* FIXME: To draw disabled label in Win31 look-and-feel, we probably
706 707 708 709
    * must use DSS_MONO flag and COLOR_GRAYTEXT brush (or maybe DSS_UNION).
    * I don't have Win31 on hand to verify that, so I leave it as is.
    */

710
   if ((style & BS_PUSHLIKE) && (state & BST_INDETERMINATE))
711 712 713 714 715
   {
      hbr = GetSysColorBrush(COLOR_GRAYTEXT);
      flags |= DSS_MONO;
   }

716
   switch (style & (BS_ICON|BS_BITMAP))
717 718 719 720
   {
      case BS_TEXT:
         /* DST_COMPLEX -- is 0 */
         lpOutputProc = BUTTON_DrawTextCallback;
721 722
         if (!(text = get_button_text( hwnd ))) return;
         lp = (LPARAM)text;
723
         wp = dtFlags;
724 725 726 727
         break;

      case BS_ICON:
         flags |= DST_ICON;
728
         lp = GetWindowLongPtrW( hwnd, HIMAGE_GWL_OFFSET );
729 730 731 732
         break;

      case BS_BITMAP:
         flags |= DST_BITMAP;
733
         lp = GetWindowLongPtrW( hwnd, HIMAGE_GWL_OFFSET );
734 735 736 737 738 739 740 741
         break;

      default:
         return;
   }

   DrawStateW(hdc, hbr, lpOutputProc, lp, wp, rc->left, rc->top,
              rc->right - rc->left, rc->bottom - rc->top, flags);
742
   HeapFree( GetProcessHeap(), 0, text );
743 744
}

745
/**********************************************************************
746
 *       Push Button Functions
747
 */
748
static void PB_Paint( HWND hwnd, HDC hDC, UINT action )
Alexandre Julliard's avatar
Alexandre Julliard committed
749
{
750
    RECT     rc, r;
751
    UINT     dtFlags, uState;
752 753 754 755
    HPEN     hOldPen;
    HBRUSH   hOldBrush;
    INT      oldBkMode;
    COLORREF oldTxtColor;
756 757
    HFONT hFont;
    LONG state = get_button_state( hwnd );
758
    LONG style = GetWindowLongW( hwnd, GWL_STYLE );
759
    BOOL pushedState = (state & BST_PUSHED);
760
    HWND parent;
761
    HRGN hrgn;
Alexandre Julliard's avatar
Alexandre Julliard committed
762

763
    GetClientRect( hwnd, &rc );
Alexandre Julliard's avatar
Alexandre Julliard committed
764

765
    /* Send WM_CTLCOLOR to allow changing the font (the colors are fixed) */
766
    if ((hFont = get_button_font( hwnd ))) SelectObject( hDC, hFont );
767 768 769
    parent = GetParent(hwnd);
    if (!parent) parent = hwnd;
    SendMessageW( parent, WM_CTLCOLORBTN, (WPARAM)hDC, (LPARAM)hwnd );
770

771
    hrgn = set_control_clipping( hDC, &rc );
772

773 774
    hOldPen = SelectObject(hDC, SYSCOLOR_GetPen(COLOR_WINDOWFRAME));
    hOldBrush = SelectObject(hDC,GetSysColorBrush(COLOR_BTNFACE));
775
    oldBkMode = SetBkMode(hDC, TRANSPARENT);
776

777
    if (get_button_type(style) == BS_DEFPUSHBUTTON)
Alexandre Julliard's avatar
Alexandre Julliard committed
778
    {
779 780
        if (action != ODA_FOCUS)
            Rectangle(hDC, rc.left, rc.top, rc.right, rc.bottom);
781
	InflateRect( &rc, -1, -1 );
Alexandre Julliard's avatar
Alexandre Julliard committed
782 783
    }

784 785 786
    /* completely skip the drawing if only focus has changed */
    if (action == ODA_FOCUS) goto draw_focus;

787
    uState = DFCS_BUTTONPUSH;
788 789 790 791

    if (style & BS_FLAT)
        uState |= DFCS_MONO;
    else if (pushedState)
Alexandre Julliard's avatar
Alexandre Julliard committed
792
    {
793 794 795 796
	if (get_button_type(style) == BS_DEFPUSHBUTTON )
	    uState |= DFCS_FLAT;
	else
	    uState |= DFCS_PUSHED;
797 798
    }

799
    if (state & (BST_CHECKED | BST_INDETERMINATE))
800
        uState |= DFCS_CHECKED;
801

802
    DrawFrameControl( hDC, &rc, DFC_BUTTON, uState );
803

804 805
    /* draw button label */
    r = rc;
806
    dtFlags = BUTTON_CalcLabelRect(hwnd, hDC, &r);
807

808 809
    if (dtFlags == (UINT)-1L)
       goto cleanup;
810

811 812
    if (pushedState)
       OffsetRect(&r, 1, 1);
813

814
    oldTxtColor = SetTextColor( hDC, GetSysColor(COLOR_BTNTEXT) );
815

816
    BUTTON_DrawLabel(hwnd, hDC, dtFlags, &r);
817

818
    SetTextColor( hDC, oldTxtColor );
819

820
draw_focus:
821
    if (action == ODA_FOCUS || (state & BST_FOCUS))
822
    {
823 824
        InflateRect( &rc, -2, -2 );
        DrawFocusRect( hDC, &rc );
825 826
    }

827
 cleanup:
828 829
    SelectObject( hDC, hOldPen );
    SelectObject( hDC, hOldBrush );
830
    SetBkMode(hDC, oldBkMode);
831 832
    SelectClipRgn( hDC, hrgn );
    if (hrgn) DeleteObject( hrgn );
Alexandre Julliard's avatar
Alexandre Julliard committed
833 834 835
}

/**********************************************************************
Alexandre Julliard's avatar
Alexandre Julliard committed
836
 *       Check Box & Radio Button Functions
Alexandre Julliard's avatar
Alexandre Julliard committed
837
 */
Alexandre Julliard's avatar
Alexandre Julliard committed
838

839
static void CB_Paint( HWND hwnd, HDC hDC, UINT action )
Alexandre Julliard's avatar
Alexandre Julliard committed
840
{
841 842
    RECT rbox, rtext, client;
    HBRUSH hBrush;
843 844
    int delta;
    UINT dtFlags;
845 846
    HFONT hFont;
    LONG state = get_button_state( hwnd );
847
    LONG style = GetWindowLongW( hwnd, GWL_STYLE );
848
    HWND parent;
849
    HRGN hrgn;
Alexandre Julliard's avatar
Alexandre Julliard committed
850

851
    if (style & BS_PUSHLIKE)
852
    {
853
        PB_Paint( hwnd, hDC, action );
854
	return;
855 856
    }

857
    GetClientRect(hwnd, &client);
Alexandre Julliard's avatar
Alexandre Julliard committed
858
    rbox = rtext = client;
Alexandre Julliard's avatar
Alexandre Julliard committed
859

860
    if ((hFont = get_button_font( hwnd ))) SelectObject( hDC, hFont );
Alexandre Julliard's avatar
Alexandre Julliard committed
861

862 863 864
    parent = GetParent(hwnd);
    if (!parent) parent = hwnd;
    hBrush = (HBRUSH)SendMessageW(parent, WM_CTLCOLORSTATIC,
865
				  (WPARAM)hDC, (LPARAM)hwnd);
866
    if (!hBrush) /* did the app forget to call defwindowproc ? */
867
        hBrush = (HBRUSH)DefWindowProcW(parent, WM_CTLCOLORSTATIC,
868
					(WPARAM)hDC, (LPARAM)hwnd );
869
    hrgn = set_control_clipping( hDC, &client );
Alexandre Julliard's avatar
Alexandre Julliard committed
870

871
    if (style & BS_LEFTTEXT)
Alexandre Julliard's avatar
Alexandre Julliard committed
872 873
    {
	/* magic +4 is what CTL3D expects */
Alexandre Julliard's avatar
Alexandre Julliard committed
874

Alexandre Julliard's avatar
Alexandre Julliard committed
875 876 877
        rtext.right -= checkBoxWidth + 4;
        rbox.left = rbox.right - checkBoxWidth;
    }
878
    else
Alexandre Julliard's avatar
Alexandre Julliard committed
879 880 881 882
    {
        rtext.left += checkBoxWidth + 4;
        rbox.right = checkBoxWidth;
    }
883

884 885 886
    /* Since WM_ERASEBKGND does nothing, first prepare background */
    if (action == ODA_SELECT) FillRect( hDC, &rbox, hBrush );
    if (action == ODA_DRAWENTIRE) FillRect( hDC, &client, hBrush );
Alexandre Julliard's avatar
Alexandre Julliard committed
887

888 889 890 891
    /* Draw label */
    client = rtext;
    dtFlags = BUTTON_CalcLabelRect(hwnd, hDC, &rtext);
    
892 893 894 895 896 897 898
    /* Only adjust rbox when rtext is valid */
    if (dtFlags != (UINT)-1L)
    {
	rbox.top = rtext.top;
	rbox.bottom = rtext.bottom;
    }

899
    /* Draw the check-box bitmap */
Alexandre Julliard's avatar
Alexandre Julliard committed
900
    if (action == ODA_DRAWENTIRE || action == ODA_SELECT)
901
    {
902
	UINT flags;
903

904 905
	if ((get_button_type(style) == BS_RADIOBUTTON) ||
	    (get_button_type(style) == BS_AUTORADIOBUTTON)) flags = DFCS_BUTTONRADIO;
906
	else if (state & BST_INDETERMINATE) flags = DFCS_BUTTON3STATE;
907
	else flags = DFCS_BUTTONCHECK;
908

909 910
	if (state & (BST_CHECKED | BST_INDETERMINATE)) flags |= DFCS_CHECKED;
	if (state & BST_PUSHED) flags |= DFCS_PUSHED;
911

912
	if (style & WS_DISABLED) flags |= DFCS_INACTIVE;
913

914 915 916 917 918
	/* rbox must have the correct height */
	delta = rbox.bottom - rbox.top - checkBoxHeight;
	
	if (style & BS_TOP) {
	    if (delta > 0) {
919
		rbox.bottom = rbox.top + checkBoxHeight;
920
	    } else { 
921
		rbox.top -= -delta/2 + 1;
922
		rbox.bottom = rbox.top + checkBoxHeight;
923 924 925
	    }
	} else if (style & BS_BOTTOM) {
	    if (delta > 0) {
926
		rbox.top = rbox.bottom - checkBoxHeight;
927
	    } else {
928
		rbox.bottom += -delta/2 + 1;
929
		rbox.top = rbox.bottom - checkBoxHeight;
930
	    }
931 932 933 934 935 936 937 938 939 940 941
	} else { /* Default */
	    if (delta > 0) {
		int ofs = (delta / 2);
		rbox.bottom -= ofs + 1;
		rbox.top = rbox.bottom - checkBoxHeight;
	    } else if (delta < 0) {
		int ofs = (-delta / 2);
		rbox.top -= ofs + 1;
		rbox.bottom = rbox.top + checkBoxHeight;
	    }
	}
942

943
	DrawFrameControl( hDC, &rbox, DFC_BUTTON, flags );
Alexandre Julliard's avatar
Alexandre Julliard committed
944
    }
Alexandre Julliard's avatar
Alexandre Julliard committed
945

946 947
    if (dtFlags == (UINT)-1L) /* Noting to draw */
	return;
948

949
    if (action == ODA_DRAWENTIRE)
950
	BUTTON_DrawLabel(hwnd, hDC, dtFlags, &rtext);
951 952

    /* ... and focus */
953
    if (action == ODA_FOCUS || (state & BST_FOCUS))
Alexandre Julliard's avatar
Alexandre Julliard committed
954
    {
955 956 957 958
	rtext.left--;
	rtext.right++;
	IntersectRect(&rtext, &rtext, &client);
	DrawFocusRect( hDC, &rtext );
Alexandre Julliard's avatar
Alexandre Julliard committed
959
    }
960 961
    SelectClipRgn( hDC, hrgn );
    if (hrgn) DeleteObject( hrgn );
Alexandre Julliard's avatar
Alexandre Julliard committed
962 963 964
}


Alexandre Julliard's avatar
Alexandre Julliard committed
965 966 967
/**********************************************************************
 *       BUTTON_CheckAutoRadioButton
 *
968
 * hwnd is checked, uncheck every other auto radio button in group
Alexandre Julliard's avatar
Alexandre Julliard committed
969
 */
970
static void BUTTON_CheckAutoRadioButton( HWND hwnd )
Alexandre Julliard's avatar
Alexandre Julliard committed
971
{
972
    HWND parent, sibling, start;
973 974 975 976

    parent = GetParent(hwnd);
    /* make sure that starting control is not disabled or invisible */
    start = sibling = GetNextDlgGroupItem( parent, hwnd, TRUE );
Alexandre Julliard's avatar
Alexandre Julliard committed
977 978 979
    do
    {
        if (!sibling) break;
980
        if ((hwnd != sibling) &&
981
            ((GetWindowLongW( sibling, GWL_STYLE) & BS_TYPEMASK) == BS_AUTORADIOBUTTON))
982
            SendMessageW( sibling, BM_SETCHECK, BST_UNCHECKED, 0 );
983
        sibling = GetNextDlgGroupItem( parent, sibling, FALSE );
Alexandre Julliard's avatar
Alexandre Julliard committed
984
    } while (sibling != start);
Alexandre Julliard's avatar
Alexandre Julliard committed
985 986 987
}


Alexandre Julliard's avatar
Alexandre Julliard committed
988 989 990 991
/**********************************************************************
 *       Group Box Functions
 */

992
static void GB_Paint( HWND hwnd, HDC hDC, UINT action )
Alexandre Julliard's avatar
Alexandre Julliard committed
993
{
994
    RECT rc, rcFrame;
995
    HBRUSH hbr;
996
    HFONT hFont;
997
    UINT dtFlags;
998
    TEXTMETRICW tm;
999
    LONG style = GetWindowLongW( hwnd, GWL_STYLE );
1000
    HWND parent;
1001
    HRGN hrgn;
Alexandre Julliard's avatar
Alexandre Julliard committed
1002

1003
    if ((hFont = get_button_font( hwnd ))) SelectObject( hDC, hFont );
1004
    /* GroupBox acts like static control, so it sends CTLCOLORSTATIC */
1005 1006 1007
    parent = GetParent(hwnd);
    if (!parent) parent = hwnd;
    hbr = (HBRUSH)SendMessageW(parent, WM_CTLCOLORSTATIC, (WPARAM)hDC, (LPARAM)hwnd);
1008
    if (!hbr) /* did the app forget to call defwindowproc ? */
1009
        hbr = (HBRUSH)DefWindowProcW(parent, WM_CTLCOLORSTATIC,
1010
				     (WPARAM)hDC, (LPARAM)hwnd);
1011
    GetClientRect( hwnd, &rc);
1012
    rcFrame = rc;
1013
    hrgn = set_control_clipping( hDC, &rc );
1014 1015 1016 1017

    GetTextMetricsW (hDC, &tm);
    rcFrame.top += (tm.tmHeight / 2) - 1;
    DrawEdge (hDC, &rcFrame, EDGE_ETCHED, BF_RECT | ((style & BS_FLAT) ? BF_FLAT : 0));
Alexandre Julliard's avatar
Alexandre Julliard committed
1018

1019
    InflateRect(&rc, -7, 1);
1020
    dtFlags = BUTTON_CalcLabelRect(hwnd, hDC, &rc);
1021

1022 1023 1024 1025 1026 1027
    if (dtFlags != (UINT)-1)
    {
        /* Because buttons have CS_PARENTDC class style, there is a chance
         * that label will be drawn out of client rect.
         * But Windows doesn't clip label's rect, so do I.
         */
1028

1029 1030 1031 1032
        /* There is 1-pixel margin at the left, right, and bottom */
        rc.left--; rc.right++; rc.bottom++;
        FillRect(hDC, &rc, hbr);
        rc.left++; rc.right--; rc.bottom--;
1033

1034 1035 1036 1037
        BUTTON_DrawLabel(hwnd, hDC, dtFlags, &rc);
    }
    SelectClipRgn( hDC, hrgn );
    if (hrgn) DeleteObject( hrgn );
Alexandre Julliard's avatar
Alexandre Julliard committed
1038 1039 1040 1041 1042 1043 1044
}


/**********************************************************************
 *       User Button Functions
 */

1045
static void UB_Paint( HWND hwnd, HDC hDC, UINT action )
Alexandre Julliard's avatar
Alexandre Julliard committed
1046
{
1047 1048
    RECT rc;
    HBRUSH hBrush;
1049 1050
    HFONT hFont;
    LONG state = get_button_state( hwnd );
1051
    HWND parent;
Alexandre Julliard's avatar
Alexandre Julliard committed
1052

1053
    GetClientRect( hwnd, &rc);
Alexandre Julliard's avatar
Alexandre Julliard committed
1054

1055
    if ((hFont = get_button_font( hwnd ))) SelectObject( hDC, hFont );
1056

1057 1058 1059
    parent = GetParent(hwnd);
    if (!parent) parent = hwnd;
    hBrush = (HBRUSH)SendMessageW(parent, WM_CTLCOLORBTN, (WPARAM)hDC, (LPARAM)hwnd);
1060
    if (!hBrush) /* did the app forget to call defwindowproc ? */
1061
        hBrush = (HBRUSH)DefWindowProcW(parent, WM_CTLCOLORBTN,
1062
					(WPARAM)hDC, (LPARAM)hwnd);
Alexandre Julliard's avatar
Alexandre Julliard committed
1063

1064
    FillRect( hDC, &rc, hBrush );
1065
    if (action == ODA_FOCUS || (state & BST_FOCUS))
1066
        DrawFocusRect( hDC, &rc );
1067

1068 1069 1070
    switch (action)
    {
    case ODA_FOCUS:
1071
        BUTTON_NOTIFY_PARENT( hwnd, (state & BST_FOCUS) ? BN_SETFOCUS : BN_KILLFOCUS );
1072 1073 1074
        break;

    case ODA_SELECT:
1075
        BUTTON_NOTIFY_PARENT( hwnd, (state & BST_PUSHED) ? BN_HILITE : BN_UNHILITE );
1076 1077 1078 1079 1080 1081
        break;

    default:
        BUTTON_NOTIFY_PARENT( hwnd, BN_PAINT );
        break;
    }
Alexandre Julliard's avatar
Alexandre Julliard committed
1082
}
Alexandre Julliard's avatar
Alexandre Julliard committed
1083

Alexandre Julliard's avatar
Alexandre Julliard committed
1084 1085 1086 1087 1088

/**********************************************************************
 *       Ownerdrawn Button Functions
 */

1089
static void OB_Paint( HWND hwnd, HDC hDC, UINT action )
Alexandre Julliard's avatar
Alexandre Julliard committed
1090
{
1091
    LONG state = get_button_state( hwnd );
1092
    DRAWITEMSTRUCT dis;
1093
    LONG_PTR id = GetWindowLongPtrW( hwnd, GWLP_ID );
1094
    HWND parent;
1095
    HFONT hFont, hPrevFont = 0;
1096
    HRGN hrgn;
Alexandre Julliard's avatar
Alexandre Julliard committed
1097

Alexandre Julliard's avatar
Alexandre Julliard committed
1098
    dis.CtlType    = ODT_BUTTON;
1099
    dis.CtlID      = id;
Alexandre Julliard's avatar
Alexandre Julliard committed
1100 1101
    dis.itemID     = 0;
    dis.itemAction = action;
1102 1103
    dis.itemState  = ((state & BST_FOCUS) ? ODS_FOCUS : 0) |
                     ((state & BST_PUSHED) ? ODS_SELECTED : 0) |
1104 1105
                     (IsWindowEnabled(hwnd) ? 0: ODS_DISABLED);
    dis.hwndItem   = hwnd;
Alexandre Julliard's avatar
Alexandre Julliard committed
1106 1107
    dis.hDC        = hDC;
    dis.itemData   = 0;
1108
    GetClientRect( hwnd, &dis.rcItem );
1109

1110
    if ((hFont = get_button_font( hwnd ))) hPrevFont = SelectObject( hDC, hFont );
1111 1112 1113
    parent = GetParent(hwnd);
    if (!parent) parent = hwnd;
    SendMessageW( parent, WM_CTLCOLORBTN, (WPARAM)hDC, (LPARAM)hwnd );
1114

1115
    hrgn = set_control_clipping( hDC, &dis.rcItem );
1116

1117
    SendMessageW( GetParent(hwnd), WM_DRAWITEM, id, (LPARAM)&dis );
1118
    if (hPrevFont) SelectObject(hDC, hPrevFont);
1119 1120
    SelectClipRgn( hDC, hrgn );
    if (hrgn) DeleteObject( hrgn );
Alexandre Julliard's avatar
Alexandre Julliard committed
1121
}