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
 *
 * 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
 *  
 *  Notifications
 *  - BN_DISABLE
 *  - BN_PUSHED/BN_HILITE
35
 *  + BN_KILLFOCUS: is it OK?
36
 *  - BN_PAINT
37
 *  + BN_SETFOCUS: is it OK?
38
 *  - BN_UNPUSHED/BN_UNHILITE
Alexandre Julliard's avatar
Alexandre Julliard committed
39
 */
Alexandre Julliard's avatar
Alexandre Julliard committed
40

41
#include <stdarg.h>
42
#include <string.h>
43 44
#include <stdlib.h>

45 46
#define OEMRESOURCE

47
#include "windef.h"
48
#include "winbase.h"
49
#include "wingdi.h"
50
#include "controls.h"
51
#include "win.h"
52
#include "user_private.h"
53 54 55
#include "wine/debug.h"

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

57 58 59
/* GetWindowLong offsets for window extra information */
#define STATE_GWL_OFFSET  0
#define HFONT_GWL_OFFSET  (sizeof(LONG))
60 61
#define HIMAGE_GWL_OFFSET (HFONT_GWL_OFFSET+sizeof(HFONT))
#define NB_EXTRA_BYTES    (HIMAGE_GWL_OFFSET+sizeof(HANDLE))
62

63
/* undocumented flags */
64 65 66 67 68
#define BUTTON_NSTATES         0x0F
#define BUTTON_BTNPRESSED      0x40
#define BUTTON_UNKNOWN2        0x20
#define BUTTON_UNKNOWN3        0x10

69 70 71 72 73 74 75 76
#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)

77
static UINT BUTTON_CalcLabelRect( HWND hwnd, HDC hdc, RECT *rc );
78 79 80 81 82 83
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
84

85
#define MAX_BTN_TYPE  16
Alexandre Julliard's avatar
Alexandre Julliard committed
86

Alexandre Julliard's avatar
Alexandre Julliard committed
87
static const WORD maxCheckState[MAX_BTN_TYPE] =
Alexandre Julliard's avatar
Alexandre Julliard committed
88
{
89 90 91 92 93 94 95 96 97 98 99 100
    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
101 102
};

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

Alexandre Julliard's avatar
Alexandre Julliard committed
105
static const pfPaint btnPaintFunc[MAX_BTN_TYPE] =
Alexandre Julliard's avatar
Alexandre Julliard committed
106 107 108 109 110 111 112 113 114 115 116
{
    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 */
117
    NULL,        /* BS_PUSHBOX */
Alexandre Julliard's avatar
Alexandre Julliard committed
118
    OB_Paint     /* BS_OWNERDRAW */
Alexandre Julliard's avatar
Alexandre Julliard committed
119 120
};

121 122 123
/*********************************************************************
 * button class descriptor
 */
124
static const WCHAR buttonW[] = {'B','u','t','t','o','n',0};
125 126
const struct builtin_class_descr BUTTON_builtin_class =
{
127
    buttonW,             /* name */
128
    CS_DBLCLKS | CS_VREDRAW | CS_HREDRAW | CS_PARENTDC, /* style  */
129
    WINPROC_BUTTON,      /* proc */
130
    NB_EXTRA_BYTES,      /* extra */
131
    IDC_ARROW,           /* cursor */
132 133 134 135
    0                    /* brush */
};


136
static inline LONG get_button_state( HWND hwnd )
137
{
138
    return GetWindowLongW( hwnd, STATE_GWL_OFFSET );
139 140
}

141
static inline void set_button_state( HWND hwnd, LONG state )
142
{
143
    SetWindowLongW( hwnd, STATE_GWL_OFFSET, state );
144 145
}

146
static inline HFONT get_button_font( HWND hwnd )
147
{
148
    return (HFONT)GetWindowLongPtrW( hwnd, HFONT_GWL_OFFSET );
149 150
}

151
static inline void set_button_font( HWND hwnd, HFONT font )
152
{
153
    SetWindowLongPtrW( hwnd, HFONT_GWL_OFFSET, (LONG_PTR)font );
154 155
}

156
static inline UINT get_button_type( LONG window_style )
157
{
158
    return (window_style & BS_TYPEMASK);
159 160 161
}

/* paint a button of any type */
162
static inline void paint_button( HWND hwnd, LONG style, UINT action )
163 164 165 166 167 168 169 170 171 172
{
    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 */
173
static inline WCHAR *get_button_text( HWND hwnd )
174
{
175
    static const INT len = 512;
176
    WCHAR *buffer = HeapAlloc( GetProcessHeap(), 0, (len + 1) * sizeof(WCHAR) );
177
    if (buffer) InternalGetWindowText( hwnd, buffer, len + 1 );
178 179 180
    return buffer;
}

Alexandre Julliard's avatar
Alexandre Julliard committed
181
/***********************************************************************
182
 *           ButtonWndProc_common
Alexandre Julliard's avatar
Alexandre Julliard committed
183
 */
184
LRESULT ButtonWndProc_common(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL unicode )
Alexandre Julliard's avatar
Alexandre Julliard committed
185
{
186
    RECT rect;
187
    POINT pt;
188
    LONG style = GetWindowLongW( hWnd, GWL_STYLE );
189 190
    UINT btn_type = get_button_type( style );
    LONG state;
191
    HANDLE oldHbitmap;
Alexandre Julliard's avatar
Alexandre Julliard committed
192

193 194
    if (!IsWindow( hWnd )) return 0;

195 196
    pt.x = (short)LOWORD(lParam);
    pt.y = (short)HIWORD(lParam);
197

Alexandre Julliard's avatar
Alexandre Julliard committed
198 199 200
    switch (uMsg)
    {
    case WM_GETDLGCODE:
201
        switch(btn_type)
Alexandre Julliard's avatar
Alexandre Julliard committed
202
        {
203 204 205 206 207
        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;
208
        case BS_GROUPBOX:        return DLGC_STATIC;
Alexandre Julliard's avatar
Alexandre Julliard committed
209 210
        default:                 return DLGC_BUTTON;
        }
Alexandre Julliard's avatar
Alexandre Julliard committed
211

Alexandre Julliard's avatar
Alexandre Julliard committed
212
    case WM_ENABLE:
213
        paint_button( hWnd, btn_type, ODA_DRAWENTIRE );
Alexandre Julliard's avatar
Alexandre Julliard committed
214
        break;
Alexandre Julliard's avatar
Alexandre Julliard committed
215

Alexandre Julliard's avatar
Alexandre Julliard committed
216
    case WM_CREATE:
217
        if (btn_type >= MAX_BTN_TYPE)
218
            return -1; /* abort */
219 220 221 222

        /* XP turns a BS_USERBUTTON into BS_PUSHBUTTON */
        if (btn_type == BS_USERBUTTON )
        {
223 224
            style = (style & ~BS_TYPEMASK) | BS_PUSHBUTTON;
            WIN_SetStyle( hWnd, style, BS_TYPEMASK & ~style );
225
        }
226
        set_button_state( hWnd, BST_UNCHECKED );
Alexandre Julliard's avatar
Alexandre Julliard committed
227
        return 0;
Alexandre Julliard's avatar
Alexandre Julliard committed
228

Alexandre Julliard's avatar
Alexandre Julliard committed
229
    case WM_ERASEBKGND:
230 231 232 233
        if (btn_type == BS_OWNERDRAW)
        {
            HDC hdc = (HDC)wParam;
            RECT rc;
234 235 236 237
            HBRUSH hBrush;
            HWND parent = GetParent(hWnd);
            if (!parent) parent = hWnd;
            hBrush = (HBRUSH)SendMessageW(parent, WM_CTLCOLORBTN, (WPARAM)hdc, (LPARAM)hWnd);
238
            if (!hBrush) /* did the app forget to call defwindowproc ? */
239
                hBrush = (HBRUSH)DefWindowProcW(parent, WM_CTLCOLORBTN,
240 241 242 243
                                                (WPARAM)hdc, (LPARAM)hWnd);
            GetClientRect(hWnd, &rc);
            FillRect(hdc, &rc, hBrush);
        }
Alexandre Julliard's avatar
Alexandre Julliard committed
244
        return 1;
Alexandre Julliard's avatar
Alexandre Julliard committed
245

246
    case WM_PRINTCLIENT:
Alexandre Julliard's avatar
Alexandre Julliard committed
247
    case WM_PAINT:
248 249 250
    {
        PAINTSTRUCT ps;
        HDC hdc = wParam ? (HDC)wParam : BeginPaint( hWnd, &ps );
251
        if (btnPaintFunc[btn_type])
Alexandre Julliard's avatar
Alexandre Julliard committed
252
        {
253
            int nOldMode = SetBkMode( hdc, OPAQUE );
254
            (btnPaintFunc[btn_type])( hWnd, hdc, ODA_DRAWENTIRE );
255
            SetBkMode(hdc, nOldMode); /*  reset painting mode */
Alexandre Julliard's avatar
Alexandre Julliard committed
256
        }
257
        if ( !wParam ) EndPaint( hWnd, &ps );
Alexandre Julliard's avatar
Alexandre Julliard committed
258
        break;
259
    }
Alexandre Julliard's avatar
Alexandre Julliard committed
260

261 262 263
    case WM_KEYDOWN:
	if (wParam == VK_SPACE)
	{
264
	    SendMessageW( hWnd, BM_SETSTATE, TRUE, 0 );
265
            set_button_state( hWnd, get_button_state( hWnd ) | BUTTON_BTNPRESSED );
266
            SetCapture( hWnd );
267 268
	}
	break;
269

270
    case WM_LBUTTONDBLCLK:
271 272 273 274 275
        if(style & BS_NOTIFY ||
           btn_type == BS_RADIOBUTTON ||
           btn_type == BS_USERBUTTON ||
           btn_type == BS_OWNERDRAW)
        {
276
            BUTTON_NOTIFY_PARENT(hWnd, BN_DOUBLECLICKED);
277 278 279
            break;
        }
        /* fall through */
280
    case WM_LBUTTONDOWN:
281
        SetCapture( hWnd );
282
        SetFocus( hWnd );
283
        set_button_state( hWnd, get_button_state( hWnd ) | BUTTON_BTNPRESSED );
284
        SendMessageW( hWnd, BM_SETSTATE, TRUE, 0 );
Alexandre Julliard's avatar
Alexandre Julliard committed
285 286
        break;

287 288 289 290
    case WM_KEYUP:
	if (wParam != VK_SPACE)
	    break;
	/* fall through */
Alexandre Julliard's avatar
Alexandre Julliard committed
291
    case WM_LBUTTONUP:
292 293 294 295
        state = get_button_state( hWnd );
        if (!(state & BUTTON_BTNPRESSED)) break;
        state &= BUTTON_NSTATES;
        set_button_state( hWnd, state );
296
        if (!(state & BST_PUSHED))
297
        {
298 299 300
            ReleaseCapture();
            break;
        }
301
        SendMessageW( hWnd, BM_SETSTATE, FALSE, 0 );
302
        GetClientRect( hWnd, &rect );
303
	if (uMsg == WM_KEYUP || PtInRect( &rect, pt ))
Alexandre Julliard's avatar
Alexandre Julliard committed
304
        {
305 306
            state = get_button_state( hWnd );
            switch(btn_type)
Alexandre Julliard's avatar
Alexandre Julliard committed
307 308
            {
            case BS_AUTOCHECKBOX:
309
                SendMessageW( hWnd, BM_SETCHECK, !(state & BST_CHECKED), 0 );
Alexandre Julliard's avatar
Alexandre Julliard committed
310
                break;
Alexandre Julliard's avatar
Alexandre Julliard committed
311
            case BS_AUTORADIOBUTTON:
312
                SendMessageW( hWnd, BM_SETCHECK, TRUE, 0 );
Alexandre Julliard's avatar
Alexandre Julliard committed
313 314
                break;
            case BS_AUTO3STATE:
315
                SendMessageW( hWnd, BM_SETCHECK,
316
                                (state & BST_INDETERMINATE) ? 0 : ((state & 3) + 1), 0 );
Alexandre Julliard's avatar
Alexandre Julliard committed
317
                break;
Alexandre Julliard's avatar
Alexandre Julliard committed
318
            }
319
            ReleaseCapture();
320
            BUTTON_NOTIFY_PARENT(hWnd, BN_CLICKED);
Alexandre Julliard's avatar
Alexandre Julliard committed
321
        }
322 323 324 325
        else
        {
            ReleaseCapture();
        }
Alexandre Julliard's avatar
Alexandre Julliard committed
326 327
        break;

328
    case WM_CAPTURECHANGED:
329
        TRACE("WM_CAPTURECHANGED %p\n", hWnd);
330
        if (hWnd == (HWND)lParam) break;
331 332 333 334 335
        state = get_button_state( hWnd );
        if (state & BUTTON_BTNPRESSED)
        {
            state &= BUTTON_NSTATES;
            set_button_state( hWnd, state );
336
            if (state & BST_PUSHED) SendMessageW( hWnd, BM_SETSTATE, FALSE, 0 );
337 338 339
        }
        break;

Alexandre Julliard's avatar
Alexandre Julliard committed
340
    case WM_MOUSEMOVE:
341
        if ((wParam & MK_LBUTTON) && GetCapture() == hWnd)
Alexandre Julliard's avatar
Alexandre Julliard committed
342
        {
343
            GetClientRect( hWnd, &rect );
344
            SendMessageW( hWnd, BM_SETSTATE, PtInRect(&rect, pt), 0 );
Alexandre Julliard's avatar
Alexandre Julliard committed
345 346 347 348
        }
        break;

    case WM_SETTEXT:
349 350
    {
        /* Clear an old text here as Windows does */
351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371
        if (IsWindowVisible(hWnd))
        {
            HDC hdc = GetDC(hWnd);
            HBRUSH hbrush;
            RECT client, rc;
            HWND parent = GetParent(hWnd);
            UINT message = (btn_type == BS_PUSHBUTTON ||
                            btn_type == BS_DEFPUSHBUTTON ||
                            btn_type == BS_USERBUTTON ||
                            btn_type == BS_OWNERDRAW) ?
                            WM_CTLCOLORBTN : WM_CTLCOLORSTATIC;

            if (!parent) parent = hWnd;
            hbrush = (HBRUSH)SendMessageW(parent, message,
                                          (WPARAM)hdc, (LPARAM)hWnd);
            if (!hbrush) /* did the app forget to call DefWindowProc ? */
                hbrush = (HBRUSH)DefWindowProcW(parent, message,
                                                (WPARAM)hdc, (LPARAM)hWnd);

            GetClientRect(hWnd, &client);
            rc = client;
372 373 374
            /* FIXME: check other BS_* handlers */
            if (btn_type == BS_GROUPBOX)
                InflateRect(&rc, -7, 1); /* GB_Paint does this */
375 376 377 378 379 380 381
            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);
        }
382

383 384
        if (unicode) DefWindowProcW( hWnd, WM_SETTEXT, wParam, lParam );
        else DefWindowProcA( hWnd, WM_SETTEXT, wParam, lParam );
385 386 387 388
        if (btn_type == BS_GROUPBOX) /* Yes, only for BS_GROUPBOX */
            InvalidateRect( hWnd, NULL, TRUE );
        else
            paint_button( hWnd, btn_type, ODA_DRAWENTIRE );
389
        return 1; /* success. FIXME: check text length */
390
    }
Alexandre Julliard's avatar
Alexandre Julliard committed
391 392

    case WM_SETFONT:
393
        set_button_font( hWnd, (HFONT)wParam );
394
        if (lParam) InvalidateRect(hWnd, NULL, TRUE);
Alexandre Julliard's avatar
Alexandre Julliard committed
395 396 397
        break;

    case WM_GETFONT:
398
        return (LRESULT)get_button_font( hWnd );
Alexandre Julliard's avatar
Alexandre Julliard committed
399 400

    case WM_SETFOCUS:
401
        TRACE("WM_SETFOCUS %p\n",hWnd);
402
        set_button_state( hWnd, get_button_state(hWnd) | BST_FOCUS );
403
        paint_button( hWnd, btn_type, ODA_FOCUS );
404 405
        if (style & BS_NOTIFY)
            BUTTON_NOTIFY_PARENT(hWnd, BN_SETFOCUS);
Alexandre Julliard's avatar
Alexandre Julliard committed
406 407 408
        break;

    case WM_KILLFOCUS:
409
        TRACE("WM_KILLFOCUS %p\n",hWnd);
410
        state = get_button_state( hWnd );
411
        set_button_state( hWnd, state & ~BST_FOCUS );
412
	paint_button( hWnd, btn_type, ODA_FOCUS );
413 414 415

        if ((state & BUTTON_BTNPRESSED) && GetCapture() == hWnd)
            ReleaseCapture();
416 417 418
        if (style & BS_NOTIFY)
            BUTTON_NOTIFY_PARENT(hWnd, BN_KILLFOCUS);

419
        InvalidateRect( hWnd, NULL, FALSE );
Alexandre Julliard's avatar
Alexandre Julliard committed
420 421 422
        break;

    case WM_SYSCOLORCHANGE:
423
        InvalidateRect( hWnd, NULL, FALSE );
Alexandre Julliard's avatar
Alexandre Julliard committed
424 425
        break;

426
    case BM_SETSTYLE:
427 428 429
        btn_type = wParam & BS_TYPEMASK;
        style = (style & ~BS_TYPEMASK) | btn_type;
        WIN_SetStyle( hWnd, style, BS_TYPEMASK & ~style );
430 431 432

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

Alexandre Julliard's avatar
Alexandre Julliard committed
435 436
        break;

437
    case BM_CLICK:
438 439
	SendMessageW( hWnd, WM_LBUTTONDOWN, 0, 0 );
	SendMessageW( hWnd, WM_LBUTTONUP, 0, 0 );
440 441
	break;

442
    case BM_SETIMAGE:
443 444 445 446 447 448 449 450 451 452 453 454
        /* 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;
        }
455
        oldHbitmap = (HBITMAP)SetWindowLongPtrW( hWnd, HIMAGE_GWL_OFFSET, lParam );
456
	InvalidateRect( hWnd, NULL, FALSE );
457
	return (LRESULT)oldHbitmap;
458 459

    case BM_GETIMAGE:
460
        return GetWindowLongPtrW( hWnd, HIMAGE_GWL_OFFSET );
461

462
    case BM_GETCHECK:
463
        return get_button_state( hWnd ) & 3;
Alexandre Julliard's avatar
Alexandre Julliard committed
464

465
    case BM_SETCHECK:
466 467
        if (wParam > maxCheckState[btn_type]) wParam = maxCheckState[btn_type];
        state = get_button_state( hWnd );
468 469
        if ((btn_type == BS_RADIOBUTTON) || (btn_type == BS_AUTORADIOBUTTON))
        {
470 471
            if (wParam) WIN_SetStyle( hWnd, WS_TABSTOP, 0 );
            else WIN_SetStyle( hWnd, 0, WS_TABSTOP );
472
        }
473
        if ((state & 3) != wParam)
Alexandre Julliard's avatar
Alexandre Julliard committed
474
        {
475 476
            set_button_state( hWnd, (state & ~3) | wParam );
            paint_button( hWnd, btn_type, ODA_SELECT );
Alexandre Julliard's avatar
Alexandre Julliard committed
477
        }
478
        if ((btn_type == BS_AUTORADIOBUTTON) && (wParam == BST_CHECKED) && (style & WS_CHILD))
479
            BUTTON_CheckAutoRadioButton( hWnd );
Alexandre Julliard's avatar
Alexandre Julliard committed
480
        break;
Alexandre Julliard's avatar
Alexandre Julliard committed
481

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

485
    case BM_SETSTATE:
486
        state = get_button_state( hWnd );
Alexandre Julliard's avatar
Alexandre Julliard committed
487
        if (wParam)
488
            set_button_state( hWnd, state | BST_PUSHED );
Alexandre Julliard's avatar
Alexandre Julliard committed
489
        else
490
            set_button_state( hWnd, state & ~BST_PUSHED );
491

492
        paint_button( hWnd, btn_type, ODA_SELECT );
Alexandre Julliard's avatar
Alexandre Julliard committed
493 494
        break;

495
    case WM_NCHITTEST:
496
        if(btn_type == BS_GROUPBOX) return HTTRANSPARENT;
497
        /* fall through */
Alexandre Julliard's avatar
Alexandre Julliard committed
498
    default:
499 500
        return unicode ? DefWindowProcW(hWnd, uMsg, wParam, lParam) :
                         DefWindowProcA(hWnd, uMsg, wParam, lParam);
Alexandre Julliard's avatar
Alexandre Julliard committed
501 502
    }
    return 0;
Alexandre Julliard's avatar
Alexandre Julliard committed
503 504
}

505 506 507
/**********************************************************************
 * Convert button styles to flags used by DrawText.
 */
508
static UINT BUTTON_BStoDT( DWORD style, DWORD ex_style )
509 510 511 512 513
{
   UINT dtStyle = DT_NOCLIP;  /* We use SelectClipRgn to limit output */

   /* "Convert" pushlike buttons to pushbuttons */
   if (style & BS_PUSHLIKE)
514
      style &= ~BS_TYPEMASK;
515 516 517 518 519 520 521 522 523 524 525 526 527

   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 */
528
         if (get_button_type(style) <= BS_DEFPUSHBUTTON) dtStyle |= DT_CENTER;
529 530 531
         /* all other flavours have left aligned text */
   }

532 533
   if (ex_style & WS_EX_RIGHT) dtStyle = DT_RIGHT | (dtStyle & ~(DT_LEFT | DT_CENTER));

534
   /* DrawText ignores vertical alignment for multiline text,
535
    * but we use these flags to align label manually.
536
    */
537
   if (get_button_type(style) != BS_GROUPBOX)
538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563
   {
      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.
 */
564
static UINT BUTTON_CalcLabelRect(HWND hwnd, HDC hdc, RECT *rc)
565
{
566
   LONG style = GetWindowLongW( hwnd, GWL_STYLE );
567
   LONG ex_style = GetWindowLongW( hwnd, GWL_EXSTYLE );
568
   WCHAR *text;
569 570
   ICONINFO    iconInfo;
   BITMAP      bm;
571
   UINT        dtStyle = BUTTON_BStoDT( style, ex_style );
572 573 574 575
   RECT        r = *rc;
   INT         n;

   /* Calculate label rectangle according to label type */
576
   switch (style & (BS_ICON|BS_BITMAP))
577 578
   {
      case BS_TEXT:
579 580 581
      {
          HFONT hFont, hPrevFont = 0;

582 583 584 585 586 587
          if (!(text = get_button_text( hwnd ))) goto empty_rect;
          if (!text[0])
          {
              HeapFree( GetProcessHeap(), 0, text );
              goto empty_rect;
          }
588 589

          if ((hFont = get_button_font( hwnd ))) hPrevFont = SelectObject( hdc, hFont );
590
          DrawTextW(hdc, text, -1, &r, dtStyle | DT_CALCRECT);
591
          if (hPrevFont) SelectObject( hdc, hPrevFont );
592 593
          HeapFree( GetProcessHeap(), 0, text );
          break;
594
      }
595 596

      case BS_ICON:
597
         if (!GetIconInfo((HICON)GetWindowLongPtrW( hwnd, HIMAGE_GWL_OFFSET ), &iconInfo))
598 599
            goto empty_rect;

600
         GetObjectW (iconInfo.hbmColor, sizeof(BITMAP), &bm);
601 602 603 604 605 606 607 608 609

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

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

      case BS_BITMAP:
610
         if (!GetObjectW( (HANDLE)GetWindowLongPtrW( hwnd, HIMAGE_GWL_OFFSET ), sizeof(BITMAP), &bm))
611 612 613 614 615 616 617
            goto empty_rect;

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

      default:
618
      empty_rect:
Felix Nawothnig's avatar
Felix Nawothnig committed
619 620
         rc->right = r.left;
         rc->bottom = r.top;
621
         return (UINT)-1;
622 623 624 625 626 627 628 629 630 631 632 633 634 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
   }

   /* 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
 *
661
 *   Callback function used by DrawStateW function.
662 663 664
 */
static BOOL CALLBACK BUTTON_DrawTextCallback(HDC hdc, LPARAM lp, WPARAM wp, int cx, int cy)
{
665
   RECT rc;
666

667
   SetRect(&rc, 0, 0, cx, cy);
668 669 670 671 672 673 674 675 676 677
   DrawTextW(hdc, (LPCWSTR)lp, -1, &rc, (UINT)wp);
   return TRUE;
}


/**********************************************************************
 *       BUTTON_DrawLabel
 *
 *   Common function for drawing button label.
 */
678
static void BUTTON_DrawLabel(HWND hwnd, HDC hdc, UINT dtFlags, const RECT *rc)
679 680 681 682 683
{
   DRAWSTATEPROC lpOutputProc = NULL;
   LPARAM lp;
   WPARAM wp = 0;
   HBRUSH hbr = 0;
684 685
   UINT flags = IsWindowEnabled(hwnd) ? DSS_NORMAL : DSS_DISABLED;
   LONG state = get_button_state( hwnd );
686
   LONG style = GetWindowLongW( hwnd, GWL_STYLE );
687
   WCHAR *text = NULL;
688

689
   /* FIXME: To draw disabled label in Win31 look-and-feel, we probably
690 691 692 693
    * 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.
    */

694
   if ((style & BS_PUSHLIKE) && (state & BST_INDETERMINATE))
695 696 697 698 699
   {
      hbr = GetSysColorBrush(COLOR_GRAYTEXT);
      flags |= DSS_MONO;
   }

700
   switch (style & (BS_ICON|BS_BITMAP))
701 702 703 704
   {
      case BS_TEXT:
         /* DST_COMPLEX -- is 0 */
         lpOutputProc = BUTTON_DrawTextCallback;
705 706
         if (!(text = get_button_text( hwnd ))) return;
         lp = (LPARAM)text;
707
         wp = dtFlags;
708 709 710 711
         break;

      case BS_ICON:
         flags |= DST_ICON;
712
         lp = GetWindowLongPtrW( hwnd, HIMAGE_GWL_OFFSET );
713 714 715 716
         break;

      case BS_BITMAP:
         flags |= DST_BITMAP;
717
         lp = GetWindowLongPtrW( hwnd, HIMAGE_GWL_OFFSET );
718 719 720 721 722 723 724 725
         break;

      default:
         return;
   }

   DrawStateW(hdc, hbr, lpOutputProc, lp, wp, rc->left, rc->top,
              rc->right - rc->left, rc->bottom - rc->top, flags);
726
   HeapFree( GetProcessHeap(), 0, text );
727 728
}

729
/**********************************************************************
730
 *       Push Button Functions
731
 */
732
static void PB_Paint( HWND hwnd, HDC hDC, UINT action )
Alexandre Julliard's avatar
Alexandre Julliard committed
733
{
734
    RECT     rc, r;
735
    UINT     dtFlags, uState;
736 737 738 739
    HPEN     hOldPen;
    HBRUSH   hOldBrush;
    INT      oldBkMode;
    COLORREF oldTxtColor;
740 741
    HFONT hFont;
    LONG state = get_button_state( hwnd );
742
    LONG style = GetWindowLongW( hwnd, GWL_STYLE );
743
    BOOL pushedState = (state & BST_PUSHED);
744
    HWND parent;
745
    HRGN hrgn;
Alexandre Julliard's avatar
Alexandre Julliard committed
746

747
    GetClientRect( hwnd, &rc );
Alexandre Julliard's avatar
Alexandre Julliard committed
748

749
    /* Send WM_CTLCOLOR to allow changing the font (the colors are fixed) */
750
    if ((hFont = get_button_font( hwnd ))) SelectObject( hDC, hFont );
751 752 753
    parent = GetParent(hwnd);
    if (!parent) parent = hwnd;
    SendMessageW( parent, WM_CTLCOLORBTN, (WPARAM)hDC, (LPARAM)hwnd );
754

755
    hrgn = set_control_clipping( hDC, &rc );
756

757 758
    hOldPen = SelectObject(hDC, SYSCOLOR_GetPen(COLOR_WINDOWFRAME));
    hOldBrush = SelectObject(hDC,GetSysColorBrush(COLOR_BTNFACE));
759
    oldBkMode = SetBkMode(hDC, TRANSPARENT);
760

761
    if (get_button_type(style) == BS_DEFPUSHBUTTON)
Alexandre Julliard's avatar
Alexandre Julliard committed
762
    {
763 764
        if (action != ODA_FOCUS)
            Rectangle(hDC, rc.left, rc.top, rc.right, rc.bottom);
765
	InflateRect( &rc, -1, -1 );
Alexandre Julliard's avatar
Alexandre Julliard committed
766 767
    }

768 769 770
    /* completely skip the drawing if only focus has changed */
    if (action == ODA_FOCUS) goto draw_focus;

771
    uState = DFCS_BUTTONPUSH;
772 773 774 775

    if (style & BS_FLAT)
        uState |= DFCS_MONO;
    else if (pushedState)
Alexandre Julliard's avatar
Alexandre Julliard committed
776
    {
777 778 779 780
	if (get_button_type(style) == BS_DEFPUSHBUTTON )
	    uState |= DFCS_FLAT;
	else
	    uState |= DFCS_PUSHED;
781 782
    }

783
    if (state & (BST_CHECKED | BST_INDETERMINATE))
784
        uState |= DFCS_CHECKED;
785

786
    DrawFrameControl( hDC, &rc, DFC_BUTTON, uState );
787

788 789
    /* draw button label */
    r = rc;
790
    dtFlags = BUTTON_CalcLabelRect(hwnd, hDC, &r);
791

792 793
    if (dtFlags == (UINT)-1L)
       goto cleanup;
794

795 796
    if (pushedState)
       OffsetRect(&r, 1, 1);
797

798
    oldTxtColor = SetTextColor( hDC, GetSysColor(COLOR_BTNTEXT) );
799

800
    BUTTON_DrawLabel(hwnd, hDC, dtFlags, &r);
801

802
    SetTextColor( hDC, oldTxtColor );
803

804
draw_focus:
805
    if (action == ODA_FOCUS || (state & BST_FOCUS))
806
    {
807 808
        InflateRect( &rc, -2, -2 );
        DrawFocusRect( hDC, &rc );
809 810
    }

811
 cleanup:
812 813
    SelectObject( hDC, hOldPen );
    SelectObject( hDC, hOldBrush );
814
    SetBkMode(hDC, oldBkMode);
815 816
    SelectClipRgn( hDC, hrgn );
    if (hrgn) DeleteObject( hrgn );
Alexandre Julliard's avatar
Alexandre Julliard committed
817 818 819
}

/**********************************************************************
Alexandre Julliard's avatar
Alexandre Julliard committed
820
 *       Check Box & Radio Button Functions
Alexandre Julliard's avatar
Alexandre Julliard committed
821
 */
Alexandre Julliard's avatar
Alexandre Julliard committed
822

823
static void CB_Paint( HWND hwnd, HDC hDC, UINT action )
Alexandre Julliard's avatar
Alexandre Julliard committed
824
{
825 826
    RECT rbox, rtext, client;
    HBRUSH hBrush;
827
    int delta, text_offset, checkBoxWidth, checkBoxHeight;
828
    UINT dtFlags;
829 830
    HFONT hFont;
    LONG state = get_button_state( hwnd );
831
    LONG style = GetWindowLongW( hwnd, GWL_STYLE );
832
    LONG ex_style = GetWindowLongW( hwnd, GWL_EXSTYLE );
833
    HWND parent;
834
    HRGN hrgn;
Alexandre Julliard's avatar
Alexandre Julliard committed
835

836
    if (style & BS_PUSHLIKE)
837
    {
838
        PB_Paint( hwnd, hDC, action );
839
	return;
840 841
    }

842
    GetClientRect(hwnd, &client);
Alexandre Julliard's avatar
Alexandre Julliard committed
843
    rbox = rtext = client;
Alexandre Julliard's avatar
Alexandre Julliard committed
844

845 846
    checkBoxWidth  = 12 * GetDpiForWindow( hwnd ) / 96 + 1;
    checkBoxHeight = 12 * GetDpiForWindow( hwnd ) / 96 + 1;
847

848
    if ((hFont = get_button_font( hwnd ))) SelectObject( hDC, hFont );
849 850
    GetCharWidthW( hDC, '0', '0', &text_offset );
    text_offset /= 2;
Alexandre Julliard's avatar
Alexandre Julliard committed
851

852 853 854
    parent = GetParent(hwnd);
    if (!parent) parent = hwnd;
    hBrush = (HBRUSH)SendMessageW(parent, WM_CTLCOLORSTATIC,
855
				  (WPARAM)hDC, (LPARAM)hwnd);
856
    if (!hBrush) /* did the app forget to call defwindowproc ? */
857
        hBrush = (HBRUSH)DefWindowProcW(parent, WM_CTLCOLORSTATIC,
858
					(WPARAM)hDC, (LPARAM)hwnd );
859
    hrgn = set_control_clipping( hDC, &client );
Alexandre Julliard's avatar
Alexandre Julliard committed
860

861
    if (style & BS_LEFTTEXT || ex_style & WS_EX_RIGHT)
Alexandre Julliard's avatar
Alexandre Julliard committed
862
    {
863
        rtext.right -= checkBoxWidth + text_offset;
Alexandre Julliard's avatar
Alexandre Julliard committed
864 865
        rbox.left = rbox.right - checkBoxWidth;
    }
866
    else
Alexandre Julliard's avatar
Alexandre Julliard committed
867
    {
868
        rtext.left += checkBoxWidth + text_offset;
Alexandre Julliard's avatar
Alexandre Julliard committed
869 870
        rbox.right = checkBoxWidth;
    }
871

872 873 874
    /* 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
875

876 877 878 879
    /* Draw label */
    client = rtext;
    dtFlags = BUTTON_CalcLabelRect(hwnd, hDC, &rtext);
    
880 881 882 883 884 885 886
    /* Only adjust rbox when rtext is valid */
    if (dtFlags != (UINT)-1L)
    {
	rbox.top = rtext.top;
	rbox.bottom = rtext.bottom;
    }

887
    /* Draw the check-box bitmap */
Alexandre Julliard's avatar
Alexandre Julliard committed
888
    if (action == ODA_DRAWENTIRE || action == ODA_SELECT)
889
    {
890
	UINT flags;
891

892 893
	if ((get_button_type(style) == BS_RADIOBUTTON) ||
	    (get_button_type(style) == BS_AUTORADIOBUTTON)) flags = DFCS_BUTTONRADIO;
894
	else if (state & BST_INDETERMINATE) flags = DFCS_BUTTON3STATE;
895
	else flags = DFCS_BUTTONCHECK;
896

897 898
	if (state & (BST_CHECKED | BST_INDETERMINATE)) flags |= DFCS_CHECKED;
	if (state & BST_PUSHED) flags |= DFCS_PUSHED;
899

900
	if (style & WS_DISABLED) flags |= DFCS_INACTIVE;
901

902 903 904
	/* rbox must have the correct height */
	delta = rbox.bottom - rbox.top - checkBoxHeight;
	
905
	if ((style & BS_VCENTER) == BS_TOP) {
906
	    if (delta > 0) {
907
		rbox.bottom = rbox.top + checkBoxHeight;
908
	    } else { 
909
		rbox.top -= -delta/2 + 1;
910
		rbox.bottom = rbox.top + checkBoxHeight;
911
	    }
912
	} else if ((style & BS_VCENTER) == BS_BOTTOM) {
913
	    if (delta > 0) {
914
		rbox.top = rbox.bottom - checkBoxHeight;
915
	    } else {
916
		rbox.bottom += -delta/2 + 1;
917
		rbox.top = rbox.bottom - checkBoxHeight;
918
	    }
919 920 921 922 923 924 925 926 927 928 929
	} 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;
	    }
	}
930

931
	DrawFrameControl( hDC, &rbox, DFC_BUTTON, flags );
Alexandre Julliard's avatar
Alexandre Julliard committed
932
    }
Alexandre Julliard's avatar
Alexandre Julliard committed
933

934 935
    if (dtFlags == (UINT)-1L) /* Noting to draw */
	return;
936

937
    if (action == ODA_DRAWENTIRE)
938
	BUTTON_DrawLabel(hwnd, hDC, dtFlags, &rtext);
939 940

    /* ... and focus */
941
    if (action == ODA_FOCUS || (state & BST_FOCUS))
Alexandre Julliard's avatar
Alexandre Julliard committed
942
    {
943 944 945 946
	rtext.left--;
	rtext.right++;
	IntersectRect(&rtext, &rtext, &client);
	DrawFocusRect( hDC, &rtext );
Alexandre Julliard's avatar
Alexandre Julliard committed
947
    }
948 949
    SelectClipRgn( hDC, hrgn );
    if (hrgn) DeleteObject( hrgn );
Alexandre Julliard's avatar
Alexandre Julliard committed
950 951 952
}


Alexandre Julliard's avatar
Alexandre Julliard committed
953 954 955
/**********************************************************************
 *       BUTTON_CheckAutoRadioButton
 *
956
 * hwnd is checked, uncheck every other auto radio button in group
Alexandre Julliard's avatar
Alexandre Julliard committed
957
 */
958
static void BUTTON_CheckAutoRadioButton( HWND hwnd )
Alexandre Julliard's avatar
Alexandre Julliard committed
959
{
960
    HWND parent, sibling, start;
961 962 963 964

    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
965 966 967
    do
    {
        if (!sibling) break;
968
        if ((hwnd != sibling) &&
969
            ((GetWindowLongW( sibling, GWL_STYLE) & BS_TYPEMASK) == BS_AUTORADIOBUTTON))
970
            SendMessageW( sibling, BM_SETCHECK, BST_UNCHECKED, 0 );
971
        sibling = GetNextDlgGroupItem( parent, sibling, FALSE );
Alexandre Julliard's avatar
Alexandre Julliard committed
972
    } while (sibling != start);
Alexandre Julliard's avatar
Alexandre Julliard committed
973 974 975
}


Alexandre Julliard's avatar
Alexandre Julliard committed
976 977 978 979
/**********************************************************************
 *       Group Box Functions
 */

980
static void GB_Paint( HWND hwnd, HDC hDC, UINT action )
Alexandre Julliard's avatar
Alexandre Julliard committed
981
{
982
    RECT rc, rcFrame;
983
    HBRUSH hbr;
984
    HFONT hFont;
985
    UINT dtFlags;
986
    TEXTMETRICW tm;
987
    LONG style = GetWindowLongW( hwnd, GWL_STYLE );
988
    HWND parent;
989
    HRGN hrgn;
Alexandre Julliard's avatar
Alexandre Julliard committed
990

991
    if ((hFont = get_button_font( hwnd ))) SelectObject( hDC, hFont );
992
    /* GroupBox acts like static control, so it sends CTLCOLORSTATIC */
993 994 995
    parent = GetParent(hwnd);
    if (!parent) parent = hwnd;
    hbr = (HBRUSH)SendMessageW(parent, WM_CTLCOLORSTATIC, (WPARAM)hDC, (LPARAM)hwnd);
996
    if (!hbr) /* did the app forget to call defwindowproc ? */
997
        hbr = (HBRUSH)DefWindowProcW(parent, WM_CTLCOLORSTATIC,
998
				     (WPARAM)hDC, (LPARAM)hwnd);
999
    GetClientRect( hwnd, &rc);
1000
    rcFrame = rc;
1001
    hrgn = set_control_clipping( hDC, &rc );
1002 1003 1004 1005

    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
1006

1007
    InflateRect(&rc, -7, 1);
1008
    dtFlags = BUTTON_CalcLabelRect(hwnd, hDC, &rc);
1009

1010 1011 1012 1013 1014 1015
    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.
         */
1016

1017 1018 1019 1020
        /* 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--;
1021

1022 1023 1024 1025
        BUTTON_DrawLabel(hwnd, hDC, dtFlags, &rc);
    }
    SelectClipRgn( hDC, hrgn );
    if (hrgn) DeleteObject( hrgn );
Alexandre Julliard's avatar
Alexandre Julliard committed
1026 1027 1028 1029 1030 1031 1032
}


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

1033
static void UB_Paint( HWND hwnd, HDC hDC, UINT action )
Alexandre Julliard's avatar
Alexandre Julliard committed
1034
{
1035 1036
    RECT rc;
    HBRUSH hBrush;
1037 1038
    HFONT hFont;
    LONG state = get_button_state( hwnd );
1039
    HWND parent;
Alexandre Julliard's avatar
Alexandre Julliard committed
1040

1041
    GetClientRect( hwnd, &rc);
Alexandre Julliard's avatar
Alexandre Julliard committed
1042

1043
    if ((hFont = get_button_font( hwnd ))) SelectObject( hDC, hFont );
1044

1045 1046 1047
    parent = GetParent(hwnd);
    if (!parent) parent = hwnd;
    hBrush = (HBRUSH)SendMessageW(parent, WM_CTLCOLORBTN, (WPARAM)hDC, (LPARAM)hwnd);
1048
    if (!hBrush) /* did the app forget to call defwindowproc ? */
1049
        hBrush = (HBRUSH)DefWindowProcW(parent, WM_CTLCOLORBTN,
1050
					(WPARAM)hDC, (LPARAM)hwnd);
Alexandre Julliard's avatar
Alexandre Julliard committed
1051

1052
    FillRect( hDC, &rc, hBrush );
1053
    if (action == ODA_FOCUS || (state & BST_FOCUS))
1054
        DrawFocusRect( hDC, &rc );
1055

1056 1057 1058
    switch (action)
    {
    case ODA_FOCUS:
1059
        BUTTON_NOTIFY_PARENT( hwnd, (state & BST_FOCUS) ? BN_SETFOCUS : BN_KILLFOCUS );
1060 1061 1062
        break;

    case ODA_SELECT:
1063
        BUTTON_NOTIFY_PARENT( hwnd, (state & BST_PUSHED) ? BN_HILITE : BN_UNHILITE );
1064 1065 1066 1067 1068 1069
        break;

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

Alexandre Julliard's avatar
Alexandre Julliard committed
1072 1073 1074 1075 1076

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

1077
static void OB_Paint( HWND hwnd, HDC hDC, UINT action )
Alexandre Julliard's avatar
Alexandre Julliard committed
1078
{
1079
    LONG state = get_button_state( hwnd );
1080
    DRAWITEMSTRUCT dis;
1081
    LONG_PTR id = GetWindowLongPtrW( hwnd, GWLP_ID );
1082
    HWND parent;
1083
    HFONT hFont;
1084
    HRGN hrgn;
Alexandre Julliard's avatar
Alexandre Julliard committed
1085

Alexandre Julliard's avatar
Alexandre Julliard committed
1086
    dis.CtlType    = ODT_BUTTON;
1087
    dis.CtlID      = id;
Alexandre Julliard's avatar
Alexandre Julliard committed
1088 1089
    dis.itemID     = 0;
    dis.itemAction = action;
1090 1091
    dis.itemState  = ((state & BST_FOCUS) ? ODS_FOCUS : 0) |
                     ((state & BST_PUSHED) ? ODS_SELECTED : 0) |
1092 1093
                     (IsWindowEnabled(hwnd) ? 0: ODS_DISABLED);
    dis.hwndItem   = hwnd;
Alexandre Julliard's avatar
Alexandre Julliard committed
1094 1095
    dis.hDC        = hDC;
    dis.itemData   = 0;
1096
    GetClientRect( hwnd, &dis.rcItem );
1097

1098
    if ((hFont = get_button_font( hwnd ))) SelectObject( hDC, hFont );
1099 1100 1101
    parent = GetParent(hwnd);
    if (!parent) parent = hwnd;
    SendMessageW( parent, WM_CTLCOLORBTN, (WPARAM)hDC, (LPARAM)hwnd );
1102

1103
    hrgn = set_control_clipping( hDC, &dis.rcItem );
1104

1105
    SendMessageW( GetParent(hwnd), WM_DRAWITEM, id, (LPARAM)&dis );
1106 1107
    SelectClipRgn( hDC, hrgn );
    if (hrgn) DeleteObject( hrgn );
Alexandre Julliard's avatar
Alexandre Julliard committed
1108
}