Correctly fill the background of a checkbox button.
[wine] / controls / button.c
1 /* File: button.c -- Button type widgets
2  *
3  * Copyright (C) 1993 Johannes Ruscheinski
4  * Copyright (C) 1993 David Metcalfe
5  * Copyright (C) 1994 Alexandre Julliard
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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
20  */
21
22 #include <string.h>
23 #include <stdlib.h>
24
25 #include "winbase.h"
26 #include "windef.h"
27 #include "wingdi.h"
28 #include "wine/winuser16.h"
29 #include "controls.h"
30 #include "user.h"
31
32 /* GetWindowLong offsets for window extra information */
33 #define STATE_GWL_OFFSET  0
34 #define HFONT_GWL_OFFSET  (sizeof(LONG))
35 #define HIMAGE_GWL_OFFSET (2*sizeof(LONG))
36 #define NB_EXTRA_BYTES    (3*sizeof(LONG))
37
38   /* Button state values */
39 #define BUTTON_UNCHECKED       0x00
40 #define BUTTON_CHECKED         0x01
41 #define BUTTON_3STATE          0x02
42 #define BUTTON_HIGHLIGHTED     0x04
43 #define BUTTON_HASFOCUS        0x08
44 #define BUTTON_NSTATES         0x0F
45   /* undocumented flags */
46 #define BUTTON_BTNPRESSED      0x40
47 #define BUTTON_UNKNOWN2        0x20
48 #define BUTTON_UNKNOWN3        0x10
49
50 static UINT BUTTON_CalcLabelRect( HWND hwnd, HDC hdc, RECT *rc );
51 static void PB_Paint( HWND hwnd, HDC hDC, UINT action );
52 static void CB_Paint( HWND hwnd, HDC hDC, UINT action );
53 static void GB_Paint( HWND hwnd, HDC hDC, UINT action );
54 static void UB_Paint( HWND hwnd, HDC hDC, UINT action );
55 static void OB_Paint( HWND hwnd, HDC hDC, UINT action );
56 static void BUTTON_CheckAutoRadioButton( HWND hwnd );
57 static LRESULT WINAPI ButtonWndProcA( HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam );
58 static LRESULT WINAPI ButtonWndProcW( HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam );
59
60 #define MAX_BTN_TYPE  12
61
62 static const WORD maxCheckState[MAX_BTN_TYPE] =
63 {
64     BUTTON_UNCHECKED,   /* BS_PUSHBUTTON */
65     BUTTON_UNCHECKED,   /* BS_DEFPUSHBUTTON */
66     BUTTON_CHECKED,     /* BS_CHECKBOX */
67     BUTTON_CHECKED,     /* BS_AUTOCHECKBOX */
68     BUTTON_CHECKED,     /* BS_RADIOBUTTON */
69     BUTTON_3STATE,      /* BS_3STATE */
70     BUTTON_3STATE,      /* BS_AUTO3STATE */
71     BUTTON_UNCHECKED,   /* BS_GROUPBOX */
72     BUTTON_UNCHECKED,   /* BS_USERBUTTON */
73     BUTTON_CHECKED,     /* BS_AUTORADIOBUTTON */
74     BUTTON_UNCHECKED,   /* Not defined */
75     BUTTON_UNCHECKED    /* BS_OWNERDRAW */
76 };
77
78 typedef void (*pfPaint)( HWND hwnd, HDC hdc, UINT action );
79
80 static const pfPaint btnPaintFunc[MAX_BTN_TYPE] =
81 {
82     PB_Paint,    /* BS_PUSHBUTTON */
83     PB_Paint,    /* BS_DEFPUSHBUTTON */
84     CB_Paint,    /* BS_CHECKBOX */
85     CB_Paint,    /* BS_AUTOCHECKBOX */
86     CB_Paint,    /* BS_RADIOBUTTON */
87     CB_Paint,    /* BS_3STATE */
88     CB_Paint,    /* BS_AUTO3STATE */
89     GB_Paint,    /* BS_GROUPBOX */
90     UB_Paint,    /* BS_USERBUTTON */
91     CB_Paint,    /* BS_AUTORADIOBUTTON */
92     NULL,        /* Not defined */
93     OB_Paint     /* BS_OWNERDRAW */
94 };
95
96 static HBITMAP hbitmapCheckBoxes = 0;
97 static WORD checkBoxWidth = 0, checkBoxHeight = 0;
98
99
100 /*********************************************************************
101  * button class descriptor
102  */
103 const struct builtin_class_descr BUTTON_builtin_class =
104 {
105     "Button",            /* name */
106     CS_GLOBALCLASS | CS_DBLCLKS | CS_VREDRAW | CS_HREDRAW | CS_PARENTDC, /* style  */
107     ButtonWndProcA,      /* procA */
108     ButtonWndProcW,      /* procW */
109     NB_EXTRA_BYTES,      /* extra */
110     IDC_ARROWA,          /* cursor */
111     0                    /* brush */
112 };
113
114
115 inline static LONG get_button_state( HWND hwnd )
116 {
117     return GetWindowLongA( hwnd, STATE_GWL_OFFSET );
118 }
119
120 inline static void set_button_state( HWND hwnd, LONG state )
121 {
122     SetWindowLongA( hwnd, STATE_GWL_OFFSET, state );
123 }
124
125 inline static HFONT get_button_font( HWND hwnd )
126 {
127     return (HFONT)GetWindowLongA( hwnd, HFONT_GWL_OFFSET );
128 }
129
130 inline static void set_button_font( HWND hwnd, HFONT font )
131 {
132     SetWindowLongA( hwnd, HFONT_GWL_OFFSET, (LONG)font );
133 }
134
135 inline static UINT get_button_type( LONG window_style )
136 {
137     return (window_style & 0x0f);
138 }
139
140 /* paint a button of any type */
141 inline static void paint_button( HWND hwnd, LONG style, UINT action )
142 {
143     if (btnPaintFunc[style] && IsWindowVisible(hwnd))
144     {
145         HDC hdc = GetDC( hwnd );
146         btnPaintFunc[style]( hwnd, hdc, action );
147         ReleaseDC( hwnd, hdc );
148     }
149 }
150
151 /* retrieve the button text; returned buffer must be freed by caller */
152 inline static WCHAR *get_button_text( HWND hwnd )
153 {
154     INT len = GetWindowTextLengthW( hwnd );
155     WCHAR *buffer = HeapAlloc( GetProcessHeap(), 0, (len + 1) * sizeof(WCHAR) );
156     if (buffer) GetWindowTextW( hwnd, buffer, len + 1 );
157     return buffer;
158 }
159
160 /***********************************************************************
161  *           ButtonWndProc_common
162  */
163 static LRESULT WINAPI ButtonWndProc_common(HWND hWnd, UINT uMsg,
164                                            WPARAM wParam, LPARAM lParam, BOOL unicode )
165 {
166     RECT rect;
167     POINT pt;
168     LONG style = GetWindowLongA( hWnd, GWL_STYLE );
169     UINT btn_type = get_button_type( style );
170     LONG state;
171     HANDLE oldHbitmap;
172
173     pt.x = LOWORD(lParam);
174     pt.y = HIWORD(lParam);
175
176     switch (uMsg)
177     {
178     case WM_GETDLGCODE:
179         switch(btn_type)
180         {
181         case BS_PUSHBUTTON:      return DLGC_BUTTON | DLGC_UNDEFPUSHBUTTON;
182         case BS_DEFPUSHBUTTON:   return DLGC_BUTTON | DLGC_DEFPUSHBUTTON;
183         case BS_RADIOBUTTON:
184         case BS_AUTORADIOBUTTON: return DLGC_BUTTON | DLGC_RADIOBUTTON;
185         default:                 return DLGC_BUTTON;
186         }
187
188     case WM_ENABLE:
189         paint_button( hWnd, btn_type, ODA_DRAWENTIRE );
190         break;
191
192     case WM_CREATE:
193         if (!hbitmapCheckBoxes)
194         {
195             BITMAP bmp;
196             hbitmapCheckBoxes = LoadBitmapW(0, MAKEINTRESOURCEW(OBM_CHECKBOXES));
197             GetObjectW( hbitmapCheckBoxes, sizeof(bmp), &bmp );
198             checkBoxWidth  = bmp.bmWidth / 4;
199             checkBoxHeight = bmp.bmHeight / 3;
200         }
201         if (btn_type >= MAX_BTN_TYPE)
202             return -1; /* abort */
203         set_button_state( hWnd, BUTTON_UNCHECKED );
204         return 0;
205
206     case WM_ERASEBKGND:
207         return 1;
208
209     case WM_PAINT:
210         if (btnPaintFunc[btn_type])
211         {
212             PAINTSTRUCT ps;
213             HDC hdc = wParam ? (HDC)wParam : BeginPaint( hWnd, &ps );
214             int nOldMode = SetBkMode( hdc, OPAQUE );
215             (btnPaintFunc[btn_type])( hWnd, hdc, ODA_DRAWENTIRE );
216             SetBkMode(hdc, nOldMode); /*  reset painting mode */
217             if( !wParam ) EndPaint( hWnd, &ps );
218         }
219         break;
220
221     case WM_KEYDOWN:
222         if (wParam == VK_SPACE)
223         {
224             SendMessageW( hWnd, BM_SETSTATE, TRUE, 0 );
225             set_button_state( hWnd, get_button_state( hWnd ) | BUTTON_BTNPRESSED );
226         }
227         break;
228
229     case WM_LBUTTONDBLCLK:
230         if(style & BS_NOTIFY ||
231            btn_type == BS_RADIOBUTTON ||
232            btn_type == BS_USERBUTTON ||
233            btn_type == BS_OWNERDRAW)
234         {
235             SendMessageW( GetParent(hWnd), WM_COMMAND,
236                           MAKEWPARAM( GetWindowLongA(hWnd,GWL_ID), BN_DOUBLECLICKED ),
237                           (LPARAM)hWnd);
238             break;
239         }
240         /* fall through */
241     case WM_LBUTTONDOWN:
242         SetCapture( hWnd );
243         SetFocus( hWnd );
244         SendMessageW( hWnd, BM_SETSTATE, TRUE, 0 );
245         set_button_state( hWnd, get_button_state( hWnd ) | BUTTON_BTNPRESSED );
246         break;
247
248     case WM_KEYUP:
249         if (wParam != VK_SPACE)
250             break;
251         /* fall through */
252     case WM_LBUTTONUP:
253         state = get_button_state( hWnd );
254         if (!(state & BUTTON_BTNPRESSED)) break;
255         state &= BUTTON_NSTATES;
256         set_button_state( hWnd, state );
257         if (!(state & BUTTON_HIGHLIGHTED))
258         {
259             ReleaseCapture();
260             break;
261         }
262         SendMessageW( hWnd, BM_SETSTATE, FALSE, 0 );
263         ReleaseCapture();
264         GetClientRect( hWnd, &rect );
265         if (uMsg == WM_KEYUP || PtInRect( &rect, pt ))
266         {
267             state = get_button_state( hWnd );
268             switch(btn_type)
269             {
270             case BS_AUTOCHECKBOX:
271                 SendMessageW( hWnd, BM_SETCHECK, !(state & BUTTON_CHECKED), 0 );
272                 break;
273             case BS_AUTORADIOBUTTON:
274                 SendMessageW( hWnd, BM_SETCHECK, TRUE, 0 );
275                 break;
276             case BS_AUTO3STATE:
277                 SendMessageW( hWnd, BM_SETCHECK,
278                                 (state & BUTTON_3STATE) ? 0 : ((state & 3) + 1), 0 );
279                 break;
280             }
281             SendMessageW( GetParent(hWnd), WM_COMMAND,
282                           MAKEWPARAM( GetWindowLongA(hWnd,GWL_ID), BN_CLICKED ), (LPARAM)hWnd);
283         }
284         break;
285
286     case WM_CAPTURECHANGED:
287         state = get_button_state( hWnd );
288         if (state & BUTTON_BTNPRESSED)
289         {
290             state &= BUTTON_NSTATES;
291             set_button_state( hWnd, state );
292             if (state & BUTTON_HIGHLIGHTED) SendMessageW( hWnd, BM_SETSTATE, FALSE, 0 );
293         }
294         break;
295
296     case WM_MOUSEMOVE:
297         if ((wParam & MK_LBUTTON) && GetCapture() == hWnd)
298         {
299             GetClientRect( hWnd, &rect );
300             SendMessageW( hWnd, BM_SETSTATE, PtInRect(&rect, pt), 0 );
301         }
302         break;
303
304     case WM_SETTEXT:
305     {
306         /* Clear an old text here as Windows does */
307         HDC hdc = GetDC(hWnd);
308         HBRUSH hbrush;
309         RECT client, rc;
310
311         hbrush = (HBRUSH)SendMessageW(GetParent(hWnd), WM_CTLCOLORSTATIC,
312                                       (WPARAM)hdc, (LPARAM)hWnd);
313         if (!hbrush) /* did the app forget to call DefWindowProc ? */
314             hbrush = (HBRUSH)DefWindowProcW(GetParent(hWnd), WM_CTLCOLORSTATIC,
315                                             (WPARAM)hdc, (LPARAM)hWnd);
316
317         GetClientRect(hWnd, &client);
318         rc = client;
319         BUTTON_CalcLabelRect(hWnd, hdc, &rc);
320         /* Clip by client rect bounds */
321         if (rc.right > client.right) rc.right = client.right;
322         if (rc.bottom > client.bottom) rc.bottom = client.bottom;
323         FillRect(hdc, &rc, hbrush);
324         ReleaseDC(hWnd, hdc);
325
326         if (unicode) DefWindowProcW( hWnd, WM_SETTEXT, wParam, lParam );
327         else DefWindowProcA( hWnd, WM_SETTEXT, wParam, lParam );
328         if (btn_type == BS_GROUPBOX) /* Yes, only for BS_GROUPBOX */
329             InvalidateRect( hWnd, NULL, TRUE );
330         else
331             paint_button( hWnd, btn_type, ODA_DRAWENTIRE );
332         return 1; /* success. FIXME: check text length */
333     }
334
335     case WM_SETFONT:
336         set_button_font( hWnd, (HFONT)wParam );
337         if (lParam) paint_button( hWnd, btn_type, ODA_DRAWENTIRE );
338         break;
339
340     case WM_GETFONT:
341         return (LRESULT)get_button_font( hWnd );
342
343     case WM_SETFOCUS:
344         if ((btn_type == BS_RADIOBUTTON || btn_type == BS_AUTORADIOBUTTON) && (GetCapture() != hWnd) &&
345             !(SendMessageW(hWnd, BM_GETCHECK, 0, 0) & BST_CHECKED))
346         {
347             /* The notification is sent when the button (BS_AUTORADIOBUTTON)
348                is unchecked and the focus was not given by a mouse click. */
349             if (btn_type == BS_AUTORADIOBUTTON)
350                 SendMessageW( hWnd, BM_SETCHECK, BUTTON_CHECKED, 0 );
351             SendMessageW( GetParent(hWnd), WM_COMMAND,
352                           MAKEWPARAM( GetWindowLongA(hWnd,GWL_ID), BN_CLICKED ), (LPARAM)hWnd);
353         }
354         set_button_state( hWnd, get_button_state(hWnd) | BUTTON_HASFOCUS );
355         paint_button( hWnd, btn_type, ODA_FOCUS );
356         break;
357
358     case WM_KILLFOCUS:
359         set_button_state( hWnd, get_button_state(hWnd) & ~BUTTON_HASFOCUS );
360         paint_button( hWnd, btn_type, ODA_FOCUS );
361         InvalidateRect( hWnd, NULL, TRUE );
362         break;
363
364     case WM_SYSCOLORCHANGE:
365         InvalidateRect( hWnd, NULL, FALSE );
366         break;
367
368     case BM_SETSTYLE16:
369     case BM_SETSTYLE:
370         if ((wParam & 0x0f) >= MAX_BTN_TYPE) break;
371         btn_type = wParam & 0x0f;
372         style = (style & ~0x0f) | btn_type;
373         SetWindowLongA( hWnd, GWL_STYLE, style );
374
375         /* Only redraw if lParam flag is set.*/
376         if (lParam)
377            paint_button( hWnd, btn_type, ODA_DRAWENTIRE );
378
379         break;
380
381     case BM_CLICK:
382         SendMessageW( hWnd, WM_LBUTTONDOWN, 0, 0 );
383         SendMessageW( hWnd, WM_LBUTTONUP, 0, 0 );
384         break;
385
386     case BM_SETIMAGE:
387         /* Check that image format matches button style */
388         switch (style & (BS_BITMAP|BS_ICON))
389         {
390         case BS_BITMAP:
391             if (wParam != IMAGE_BITMAP) return 0;
392             break;
393         case BS_ICON:
394             if (wParam != IMAGE_ICON) return 0;
395             break;
396         default:
397             return 0;
398         }
399         oldHbitmap = (HBITMAP)SetWindowLongA( hWnd, HIMAGE_GWL_OFFSET, lParam );
400         InvalidateRect( hWnd, NULL, FALSE );
401         return (LRESULT)oldHbitmap;
402
403     case BM_GETIMAGE:
404         return GetWindowLongA( hWnd, HIMAGE_GWL_OFFSET );
405
406     case BM_GETCHECK16:
407     case BM_GETCHECK:
408         return get_button_state( hWnd ) & 3;
409
410     case BM_SETCHECK16:
411     case BM_SETCHECK:
412         if (wParam > maxCheckState[btn_type]) wParam = maxCheckState[btn_type];
413         state = get_button_state( hWnd );
414         if ((btn_type == BS_RADIOBUTTON) || (btn_type == BS_AUTORADIOBUTTON))
415         {
416             if (wParam) style |= WS_TABSTOP;
417             else style &= ~WS_TABSTOP;
418             SetWindowLongA( hWnd, GWL_STYLE, style );
419         }
420         if ((state & 3) != wParam)
421         {
422             set_button_state( hWnd, (state & ~3) | wParam );
423             paint_button( hWnd, btn_type, ODA_SELECT );
424         }
425         if ((btn_type == BS_AUTORADIOBUTTON) && (wParam == BUTTON_CHECKED) && (style & WS_CHILD))
426             BUTTON_CheckAutoRadioButton( hWnd );
427         break;
428
429     case BM_GETSTATE16:
430     case BM_GETSTATE:
431         return get_button_state( hWnd );
432
433     case BM_SETSTATE16:
434     case BM_SETSTATE:
435         state = get_button_state( hWnd );
436         if (wParam)
437         {
438             if (state & BUTTON_HIGHLIGHTED) break;
439             set_button_state( hWnd, state | BUTTON_HIGHLIGHTED );
440         }
441         else
442         {
443             if (!(state & BUTTON_HIGHLIGHTED)) break;
444             set_button_state( hWnd, state & ~BUTTON_HIGHLIGHTED );
445         }
446         paint_button( hWnd, btn_type, ODA_SELECT );
447         break;
448
449     case WM_NCHITTEST:
450         if(btn_type == BS_GROUPBOX) return HTTRANSPARENT;
451         /* fall through */
452     default:
453         return unicode ? DefWindowProcW(hWnd, uMsg, wParam, lParam) :
454                          DefWindowProcA(hWnd, uMsg, wParam, lParam);
455     }
456     return 0;
457 }
458
459 /***********************************************************************
460  *           ButtonWndProcW
461  * The button window procedure. This is just a wrapper which locks
462  * the passed HWND and calls the real window procedure (with a WND*
463  * pointer pointing to the locked windowstructure).
464  */
465 static LRESULT WINAPI ButtonWndProcW( HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam )
466 {
467     if (!IsWindow( hWnd )) return 0;
468     return ButtonWndProc_common( hWnd, uMsg, wParam, lParam, TRUE );
469 }
470
471
472 /***********************************************************************
473  *           ButtonWndProcA
474  */
475 static LRESULT WINAPI ButtonWndProcA( HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam )
476 {
477     if (!IsWindow( hWnd )) return 0;
478     return ButtonWndProc_common( hWnd, uMsg, wParam, lParam, FALSE );
479 }
480
481
482 /**********************************************************************
483  * Convert button styles to flags used by DrawText.
484  * TODO: handle WS_EX_RIGHT extended style.
485  */
486 static UINT BUTTON_BStoDT(DWORD style)
487 {
488    UINT dtStyle = DT_NOCLIP;  /* We use SelectClipRgn to limit output */
489
490    /* "Convert" pushlike buttons to pushbuttons */
491    if (style & BS_PUSHLIKE)
492       style &= ~0x0F;
493
494    if (!(style & BS_MULTILINE))
495       dtStyle |= DT_SINGLELINE;
496    else
497       dtStyle |= DT_WORDBREAK;
498
499    switch (style & BS_CENTER)
500    {
501       case BS_LEFT:   /* DT_LEFT is 0 */    break;
502       case BS_RIGHT:  dtStyle |= DT_RIGHT;  break;
503       case BS_CENTER: dtStyle |= DT_CENTER; break;
504       default:
505          /* Pushbutton's text is centered by default */
506          if (get_button_type(style) <= BS_DEFPUSHBUTTON) dtStyle |= DT_CENTER;
507          /* all other flavours have left aligned text */
508    }
509
510    /* DrawText ignores vertical alignment for multiline text,
511     * but we use these flags to align label manualy.
512     */
513    if (get_button_type(style) != BS_GROUPBOX)
514    {
515       switch (style & BS_VCENTER)
516       {
517          case BS_TOP:     /* DT_TOP is 0 */      break;
518          case BS_BOTTOM:  dtStyle |= DT_BOTTOM;  break;
519          case BS_VCENTER: /* fall through */
520          default:         dtStyle |= DT_VCENTER; break;
521       }
522    }
523    else
524       /* GroupBox's text is always single line and is top aligned. */
525       dtStyle |= DT_SINGLELINE;
526
527    return dtStyle;
528 }
529
530 /**********************************************************************
531  *       BUTTON_CalcLabelRect
532  *
533  *   Calculates label's rectangle depending on button style.
534  *
535  * Returns flags to be passed to DrawText.
536  * Calculated rectangle doesn't take into account button state
537  * (pushed, etc.). If there is nothing to draw (no text/image) output
538  * rectangle is empty, and return value is (UINT)-1.
539  */
540 static UINT BUTTON_CalcLabelRect(HWND hwnd, HDC hdc, RECT *rc)
541 {
542    LONG style = GetWindowLongA( hwnd, GWL_STYLE );
543    WCHAR *text;
544    ICONINFO    iconInfo;
545    BITMAP      bm;
546    UINT        dtStyle = BUTTON_BStoDT(style);
547    RECT        r = *rc;
548    INT         n;
549
550    /* Calculate label rectangle according to label type */
551    switch (style & (BS_ICON|BS_BITMAP))
552    {
553       case BS_TEXT:
554           if (!(text = get_button_text( hwnd ))) goto empty_rect;
555           if (!text[0])
556           {
557               HeapFree( GetProcessHeap(), 0, text );
558               goto empty_rect;
559           }
560           DrawTextW(hdc, text, -1, &r, dtStyle | DT_CALCRECT);
561           HeapFree( GetProcessHeap(), 0, text );
562           break;
563
564       case BS_ICON:
565          if (!GetIconInfo((HICON)GetWindowLongA( hwnd, HIMAGE_GWL_OFFSET ), &iconInfo))
566             goto empty_rect;
567
568          GetObjectW (iconInfo.hbmColor, sizeof(BITMAP), &bm);
569
570          r.right  = r.left + bm.bmWidth;
571          r.bottom = r.top  + bm.bmHeight;
572
573          DeleteObject(iconInfo.hbmColor);
574          DeleteObject(iconInfo.hbmMask);
575          break;
576
577       case BS_BITMAP:
578          if (!GetObjectW( (HANDLE)GetWindowLongA( hwnd, HIMAGE_GWL_OFFSET ), sizeof(BITMAP), &bm))
579             goto empty_rect;
580
581          r.right  = r.left + bm.bmWidth;
582          r.bottom = r.top  + bm.bmHeight;
583          break;
584
585       default:
586       empty_rect:
587          r.right = r.left;
588          r.bottom = r.top;
589          return (UINT)(LONG)-1;
590    }
591
592    /* Position label inside bounding rectangle according to
593     * alignment flags. (calculated rect is always left-top aligned).
594     * If label is aligned to any side - shift label in opposite
595     * direction to leave extra space for focus rectangle.
596     */
597    switch (dtStyle & (DT_CENTER|DT_RIGHT))
598    {
599       case DT_LEFT:    r.left++;  r.right++;  break;
600       case DT_CENTER:  n = r.right - r.left;
601                        r.left   = rc->left + ((rc->right - rc->left) - n) / 2;
602                        r.right  = r.left + n; break;
603       case DT_RIGHT:   n = r.right - r.left;
604                        r.right  = rc->right - 1;
605                        r.left   = r.right - n;
606                        break;
607    }
608
609    switch (dtStyle & (DT_VCENTER|DT_BOTTOM))
610    {
611       case DT_TOP:     r.top++;  r.bottom++;  break;
612       case DT_VCENTER: n = r.bottom - r.top;
613                        r.top    = rc->top + ((rc->bottom - rc->top) - n) / 2;
614                        r.bottom = r.top + n;  break;
615       case DT_BOTTOM:  n = r.bottom - r.top;
616                        r.bottom = rc->bottom - 1;
617                        r.top    = r.bottom - n;
618                        break;
619    }
620
621    *rc = r;
622    return dtStyle;
623 }
624
625
626 /**********************************************************************
627  *       BUTTON_DrawTextCallback
628  *
629  *   Callback function used by DrawStateW function.
630  */
631 static BOOL CALLBACK BUTTON_DrawTextCallback(HDC hdc, LPARAM lp, WPARAM wp, int cx, int cy)
632 {
633    RECT rc;
634    rc.left = 0;
635    rc.top = 0;
636    rc.right = cx;
637    rc.bottom = cy;
638
639    DrawTextW(hdc, (LPCWSTR)lp, -1, &rc, (UINT)wp);
640    return TRUE;
641 }
642
643
644 /**********************************************************************
645  *       BUTTON_DrawLabel
646  *
647  *   Common function for drawing button label.
648  */
649 static void BUTTON_DrawLabel(HWND hwnd, HDC hdc, UINT dtFlags, RECT *rc)
650 {
651    DRAWSTATEPROC lpOutputProc = NULL;
652    LPARAM lp;
653    WPARAM wp = 0;
654    HBRUSH hbr = 0;
655    UINT flags = IsWindowEnabled(hwnd) ? DSS_NORMAL : DSS_DISABLED;
656    LONG state = get_button_state( hwnd );
657    LONG style = GetWindowLongA( hwnd, GWL_STYLE );
658    WCHAR *text = NULL;
659
660    /* FIXME: To draw disabled label in Win31 look-and-feel, we probably
661     * must use DSS_MONO flag and COLOR_GRAYTEXT brush (or maybe DSS_UNION).
662     * I don't have Win31 on hand to verify that, so I leave it as is.
663     */
664
665    if ((style & BS_PUSHLIKE) && (state & BUTTON_3STATE))
666    {
667       hbr = GetSysColorBrush(COLOR_GRAYTEXT);
668       flags |= DSS_MONO;
669    }
670
671    switch (style & (BS_ICON|BS_BITMAP))
672    {
673       case BS_TEXT:
674          /* DST_COMPLEX -- is 0 */
675          lpOutputProc = BUTTON_DrawTextCallback;
676          if (!(text = get_button_text( hwnd ))) return;
677          lp = (LPARAM)text;
678          wp = (WPARAM)dtFlags;
679          break;
680
681       case BS_ICON:
682          flags |= DST_ICON;
683          lp = GetWindowLongA( hwnd, HIMAGE_GWL_OFFSET );
684          break;
685
686       case BS_BITMAP:
687          flags |= DST_BITMAP;
688          lp = GetWindowLongA( hwnd, HIMAGE_GWL_OFFSET );
689          break;
690
691       default:
692          return;
693    }
694
695    DrawStateW(hdc, hbr, lpOutputProc, lp, wp, rc->left, rc->top,
696               rc->right - rc->left, rc->bottom - rc->top, flags);
697    if (text) HeapFree( GetProcessHeap(), 0, text );
698 }
699
700 /**********************************************************************
701  *       Push Button Functions
702  */
703 static void PB_Paint( HWND hwnd, HDC hDC, UINT action )
704 {
705     RECT     rc, focus_rect, r;
706     UINT     dtFlags;
707     HRGN     hRgn;
708     HPEN     hOldPen;
709     HBRUSH   hOldBrush;
710     INT      oldBkMode;
711     COLORREF oldTxtColor;
712     HFONT hFont;
713     LONG state = get_button_state( hwnd );
714     LONG style = GetWindowLongA( hwnd, GWL_STYLE );
715     BOOL pushedState = (state & BUTTON_HIGHLIGHTED);
716
717     GetClientRect( hwnd, &rc );
718
719     /* Send WM_CTLCOLOR to allow changing the font (the colors are fixed) */
720     if ((hFont = get_button_font( hwnd ))) SelectObject( hDC, hFont );
721     SendMessageW( GetParent(hwnd), WM_CTLCOLORBTN, (WPARAM)hDC, (LPARAM)hwnd );
722     hOldPen = (HPEN)SelectObject(hDC, SYSCOLOR_GetPen(COLOR_WINDOWFRAME));
723     hOldBrush =(HBRUSH)SelectObject(hDC,GetSysColorBrush(COLOR_BTNFACE));
724     oldBkMode = SetBkMode(hDC, TRANSPARENT);
725
726     if ( TWEAK_WineLook == WIN31_LOOK)
727     {
728         COLORREF clr_wnd = GetSysColor(COLOR_WINDOW);
729         Rectangle(hDC, rc.left, rc.top, rc.right, rc.bottom);
730
731         SetPixel( hDC, rc.left, rc.top, clr_wnd);
732         SetPixel( hDC, rc.left, rc.bottom-1, clr_wnd);
733         SetPixel( hDC, rc.right-1, rc.top, clr_wnd);
734         SetPixel( hDC, rc.right-1, rc.bottom-1, clr_wnd);
735         InflateRect( &rc, -1, -1 );
736     }
737
738     if (get_button_type(style) == BS_DEFPUSHBUTTON)
739     {
740         Rectangle(hDC, rc.left, rc.top, rc.right, rc.bottom);
741         InflateRect( &rc, -1, -1 );
742     }
743
744     if (TWEAK_WineLook == WIN31_LOOK)
745     {
746         if (pushedState)
747         {
748             /* draw button shadow: */
749             SelectObject(hDC, GetSysColorBrush(COLOR_BTNSHADOW));
750             PatBlt(hDC, rc.left, rc.top, 1, rc.bottom-rc.top, PATCOPY );
751             PatBlt(hDC, rc.left, rc.top, rc.right-rc.left, 1, PATCOPY );
752         } else {
753            rc.right++, rc.bottom++;
754            DrawEdge( hDC, &rc, EDGE_RAISED, BF_RECT );
755            rc.right--, rc.bottom--;
756         }
757     }
758     else
759     {
760         UINT uState = DFCS_BUTTONPUSH | DFCS_ADJUSTRECT;
761
762         if (style & BS_FLAT)
763             uState |= DFCS_MONO;
764         else if (pushedState)
765         {
766             if (get_button_type(style) == BS_DEFPUSHBUTTON )
767                 uState |= DFCS_FLAT;
768             else
769                 uState |= DFCS_PUSHED;
770         }
771
772         if (state & (BUTTON_CHECKED | BUTTON_3STATE))
773             uState |= DFCS_CHECKED;
774
775         DrawFrameControl( hDC, &rc, DFC_BUTTON, uState );
776
777         focus_rect = rc;
778     }
779
780     /* draw button label */
781     r = rc;
782     dtFlags = BUTTON_CalcLabelRect(hwnd, hDC, &r);
783
784     if (dtFlags == (UINT)-1L)
785        goto cleanup;
786
787     if (pushedState)
788        OffsetRect(&r, 1, 1);
789
790     if(TWEAK_WineLook == WIN31_LOOK)
791     {
792        focus_rect = r;
793        InflateRect(&focus_rect, 2, 0);
794     }
795
796     hRgn = CreateRectRgn(rc.left, rc.top, rc.right, rc.bottom);
797     SelectClipRgn(hDC, hRgn);
798
799     oldTxtColor = SetTextColor( hDC, GetSysColor(COLOR_BTNTEXT) );
800
801     BUTTON_DrawLabel(hwnd, hDC, dtFlags, &r);
802
803     SetTextColor( hDC, oldTxtColor );
804     SelectClipRgn(hDC, 0);
805     DeleteObject(hRgn);
806
807     if (state & BUTTON_HASFOCUS)
808     {
809         InflateRect( &focus_rect, -1, -1 );
810         IntersectRect(&focus_rect, &focus_rect, &rc);
811         DrawFocusRect( hDC, &focus_rect );
812     }
813
814  cleanup:
815     SelectObject( hDC, hOldPen );
816     SelectObject( hDC, hOldBrush );
817     SetBkMode(hDC, oldBkMode);
818 }
819
820 /**********************************************************************
821  *       Check Box & Radio Button Functions
822  */
823
824 static void CB_Paint( HWND hwnd, HDC hDC, UINT action )
825 {
826     RECT rbox, rtext, client;
827     HBRUSH hBrush;
828     int delta;
829     UINT dtFlags;
830     HRGN hRgn;
831     HFONT hFont;
832     LONG state = get_button_state( hwnd );
833     LONG style = GetWindowLongA( hwnd, GWL_STYLE );
834
835     if (style & BS_PUSHLIKE)
836     {
837         PB_Paint( hwnd, hDC, action );
838         return;
839     }
840
841     GetClientRect(hwnd, &client);
842     rbox = rtext = client;
843
844     if ((hFont = get_button_font( hwnd ))) SelectObject( hDC, hFont );
845
846     hBrush = (HBRUSH)SendMessageW(GetParent(hwnd), WM_CTLCOLORSTATIC,
847                                   (WPARAM)hDC, (LPARAM)hwnd);
848     if (!hBrush) /* did the app forget to call defwindowproc ? */
849         hBrush = (HBRUSH)DefWindowProcW(GetParent(hwnd), WM_CTLCOLORSTATIC,
850                                         (WPARAM)hDC, (LPARAM)hwnd );
851
852     if (style & BS_LEFTTEXT)
853     {
854         /* magic +4 is what CTL3D expects */
855
856         rtext.right -= checkBoxWidth + 4;
857         rbox.left = rbox.right - checkBoxWidth;
858     }
859     else
860     {
861         rtext.left += checkBoxWidth + 4;
862         rbox.right = checkBoxWidth;
863     }
864  
865     /* Since WM_ERASEBKGND does nothing, first prepare background */
866     if (action == ODA_SELECT) FillRect( hDC, &rbox, hBrush );
867     if (action == ODA_DRAWENTIRE) FillRect( hDC, &client, hBrush );
868
869     /* Draw label */
870     client = rtext;
871     dtFlags = BUTTON_CalcLabelRect(hwnd, hDC, &rtext);
872     
873     rbox.top = rtext.top;
874     rbox.bottom = rtext.bottom;
875     /* Draw the check-box bitmap */
876     if (action == ODA_DRAWENTIRE || action == ODA_SELECT)
877     {
878         if( TWEAK_WineLook == WIN31_LOOK )
879         {
880             HDC hMemDC = CreateCompatibleDC( hDC );
881             int x = 0, y = 0;
882             delta = (rbox.bottom - rbox.top - checkBoxHeight) / 2;
883             
884             /* Check in case the client area is smaller than the checkbox bitmap */
885             if (delta < 0) delta = 0;
886             
887             if (state & BUTTON_HIGHLIGHTED) x += 2 * checkBoxWidth;
888             if (state & (BUTTON_CHECKED | BUTTON_3STATE)) x += checkBoxWidth;
889             if ((get_button_type(style) == BS_RADIOBUTTON) ||
890                 (get_button_type(style) == BS_AUTORADIOBUTTON)) y += checkBoxHeight;
891             else if (state & BUTTON_3STATE) y += 2 * checkBoxHeight;
892             
893             /* The bitmap for the radio button is not aligned with the
894              * left of the window, it is 1 pixel off. */
895             if ((get_button_type(style) == BS_RADIOBUTTON) ||
896                 (get_button_type(style) == BS_AUTORADIOBUTTON))
897               rbox.left += 1;
898             
899             SelectObject( hMemDC, hbitmapCheckBoxes );
900             BitBlt( hDC, rbox.left, rbox.top + delta, checkBoxWidth,
901                     checkBoxHeight, hMemDC, x, y, SRCCOPY );
902             DeleteDC( hMemDC );
903         }
904         else
905         {
906             UINT flags;
907
908             if ((get_button_type(style) == BS_RADIOBUTTON) ||
909                 (get_button_type(style) == BS_AUTORADIOBUTTON)) flags = DFCS_BUTTONRADIO;
910             else if (state & BUTTON_3STATE) flags = DFCS_BUTTON3STATE;
911             else flags = DFCS_BUTTONCHECK;
912
913             if (state & (BUTTON_CHECKED | BUTTON_3STATE)) flags |= DFCS_CHECKED;
914             if (state & BUTTON_HIGHLIGHTED) flags |= DFCS_PUSHED;
915
916             if (style & WS_DISABLED) flags |= DFCS_INACTIVE;
917
918             /* rbox must have the correct height */
919             delta = rbox.bottom - rbox.top - checkBoxHeight;
920             
921             if (style & BS_TOP) {
922               if (delta > 0) {
923                 rbox.bottom = rbox.top + checkBoxHeight;
924               } else {
925                 rbox.top -= -delta/2 + 1;
926                 rbox.bottom += rbox.top + checkBoxHeight;
927               }
928             } else if (style & BS_BOTTOM) {
929               if (delta > 0) {
930                 rbox.top = rbox.bottom - checkBoxHeight;
931               } else {
932                 rbox.bottom += -delta/2 + 1;
933                 rbox.top = rbox.bottom -= checkBoxHeight;
934               }
935             } else { /* Default */
936               if (delta > 0)
937                 {
938                   int ofs = (delta / 2);
939                   rbox.bottom -= ofs + 1;
940                   rbox.top = rbox.bottom - checkBoxHeight;
941                 }
942               else if (delta < 0)
943                 {
944                   int ofs = (-delta / 2);
945                   rbox.top -= ofs + 1;
946                   rbox.bottom = rbox.top + checkBoxHeight;
947                 }
948             }
949
950             DrawFrameControl( hDC, &rbox, DFC_BUTTON, flags );
951         }
952     }
953
954     if (dtFlags == (UINT)-1L) /* Noting to draw */
955         return;
956     hRgn = CreateRectRgn(client.left, client.top, client.right, client.bottom);
957     SelectClipRgn(hDC, hRgn);
958     DeleteObject(hRgn);
959
960     if (action == ODA_DRAWENTIRE)
961         BUTTON_DrawLabel(hwnd, hDC, dtFlags, &rtext);
962
963     /* ... and focus */
964     if ((action == ODA_FOCUS) ||
965         ((action == ODA_DRAWENTIRE) && (state & BUTTON_HASFOCUS)))
966     {
967         rtext.left--;
968         rtext.right++;
969         IntersectRect(&rtext, &rtext, &client);
970         DrawFocusRect( hDC, &rtext );
971     }
972     SelectClipRgn(hDC, 0);
973 }
974
975
976 /**********************************************************************
977  *       BUTTON_CheckAutoRadioButton
978  *
979  * hwnd is checked, uncheck every other auto radio button in group
980  */
981 static void BUTTON_CheckAutoRadioButton( HWND hwnd )
982 {
983     HWND parent, sibling, start;
984
985     parent = GetParent(hwnd);
986     /* make sure that starting control is not disabled or invisible */
987     start = sibling = GetNextDlgGroupItem( parent, hwnd, TRUE );
988     do
989     {
990         if (!sibling) break;
991         if ((hwnd != sibling) &&
992             ((GetWindowLongA( sibling, GWL_STYLE) & 0x0f) == BS_AUTORADIOBUTTON))
993             SendMessageW( sibling, BM_SETCHECK, BUTTON_UNCHECKED, 0 );
994         sibling = GetNextDlgGroupItem( parent, sibling, FALSE );
995     } while (sibling != start);
996 }
997
998
999 /**********************************************************************
1000  *       Group Box Functions
1001  */
1002
1003 static void GB_Paint( HWND hwnd, HDC hDC, UINT action )
1004 {
1005     RECT rc, rcFrame;
1006     HBRUSH hbr;
1007     HFONT hFont;
1008     UINT dtFlags;
1009     LONG style = GetWindowLongA( hwnd, GWL_STYLE );
1010
1011     if (action != ODA_DRAWENTIRE) return;
1012
1013     if ((hFont = get_button_font( hwnd ))) SelectObject( hDC, hFont );
1014     /* GroupBox acts like static control, so it sends CTLCOLORSTATIC */
1015     hbr = (HBRUSH)SendMessageW(GetParent(hwnd), WM_CTLCOLORSTATIC, (WPARAM)hDC, (LPARAM)hwnd);
1016     if (!hbr) /* did the app forget to call defwindowproc ? */
1017         hbr = (HBRUSH)DefWindowProcW(GetParent(hwnd), WM_CTLCOLORSTATIC,
1018                                      (WPARAM)hDC, (LPARAM)hwnd);
1019
1020     GetClientRect( hwnd, &rc);
1021     if (TWEAK_WineLook == WIN31_LOOK) {
1022         HPEN hPrevPen = SelectObject( hDC,
1023                                           SYSCOLOR_GetPen(COLOR_WINDOWFRAME));
1024         HBRUSH hPrevBrush = SelectObject( hDC,
1025                                               GetStockObject(NULL_BRUSH) );
1026
1027         Rectangle( hDC, rc.left, rc.top + 2, rc.right - 1, rc.bottom - 1 );
1028         SelectObject( hDC, hPrevBrush );
1029         SelectObject( hDC, hPrevPen );
1030     } else {
1031         TEXTMETRICW tm;
1032         rcFrame = rc;
1033
1034         GetTextMetricsW (hDC, &tm);
1035         rcFrame.top += (tm.tmHeight / 2) - 1;
1036         DrawEdge (hDC, &rcFrame, EDGE_ETCHED, BF_RECT | ((style & BS_FLAT) ? BF_FLAT : 0));
1037     }
1038
1039     InflateRect(&rc, -7, 1);
1040     dtFlags = BUTTON_CalcLabelRect(hwnd, hDC, &rc);
1041
1042     if (dtFlags == (UINT)-1L)
1043        return;
1044
1045     /* Because buttons have CS_PARENTDC class style, there is a chance
1046      * that label will be drawn out of client rect.
1047      * But Windows doesn't clip label's rect, so do I.
1048      */
1049
1050     /* There is 1-pixel marging at the left, right, and bottom */
1051     rc.left--; rc.right++; rc.bottom++;
1052     FillRect(hDC, &rc, hbr);
1053     rc.left++; rc.right--; rc.bottom--;
1054
1055     BUTTON_DrawLabel(hwnd, hDC, dtFlags, &rc);
1056 }
1057
1058
1059 /**********************************************************************
1060  *       User Button Functions
1061  */
1062
1063 static void UB_Paint( HWND hwnd, HDC hDC, UINT action )
1064 {
1065     RECT rc;
1066     HBRUSH hBrush;
1067     HFONT hFont;
1068     LONG state = get_button_state( hwnd );
1069
1070     if (action == ODA_SELECT) return;
1071
1072     GetClientRect( hwnd, &rc);
1073
1074     if ((hFont = get_button_font( hwnd ))) SelectObject( hDC, hFont );
1075
1076     hBrush = (HBRUSH)SendMessageW(GetParent(hwnd), WM_CTLCOLORBTN, (WPARAM)hDC, (LPARAM)hwnd);
1077     if (!hBrush) /* did the app forget to call defwindowproc ? */
1078         hBrush = (HBRUSH)DefWindowProcW(GetParent(hwnd), WM_CTLCOLORBTN,
1079                                         (WPARAM)hDC, (LPARAM)hwnd);
1080
1081     FillRect( hDC, &rc, hBrush );
1082     if ((action == ODA_FOCUS) ||
1083         ((action == ODA_DRAWENTIRE) && (state & BUTTON_HASFOCUS)))
1084         DrawFocusRect( hDC, &rc );
1085 }
1086
1087
1088 /**********************************************************************
1089  *       Ownerdrawn Button Functions
1090  */
1091
1092 static void OB_Paint( HWND hwnd, HDC hDC, UINT action )
1093 {
1094     LONG state = get_button_state( hwnd );
1095     DRAWITEMSTRUCT dis;
1096     HRGN clipRegion;
1097     RECT clipRect;
1098     UINT id = GetWindowLongA( hwnd, GWL_ID );
1099
1100     dis.CtlType    = ODT_BUTTON;
1101     dis.CtlID      = id;
1102     dis.itemID     = 0;
1103     dis.itemAction = action;
1104     dis.itemState  = ((state & BUTTON_HASFOCUS) ? ODS_FOCUS : 0) |
1105                      ((state & BUTTON_HIGHLIGHTED) ? ODS_SELECTED : 0) |
1106                      (IsWindowEnabled(hwnd) ? 0: ODS_DISABLED);
1107     dis.hwndItem   = hwnd;
1108     dis.hDC        = hDC;
1109     dis.itemData   = 0;
1110     GetClientRect( hwnd, &dis.rcItem );
1111
1112     clipRegion = CreateRectRgnIndirect(&dis.rcItem);
1113     if (GetClipRgn(hDC, clipRegion) != 1)
1114     {
1115         DeleteObject(clipRegion);
1116         clipRegion=NULL;
1117     }
1118     clipRect = dis.rcItem;
1119     DPtoLP(hDC, (LPPOINT) &clipRect, 2);
1120     IntersectClipRect(hDC, clipRect.left,  clipRect.top, clipRect.right, clipRect.bottom);
1121
1122     SetBkColor( hDC, GetSysColor( COLOR_BTNFACE ) );
1123     SendMessageW( GetParent(hwnd), WM_DRAWITEM, id, (LPARAM)&dis );
1124     SelectClipRgn(hDC, clipRegion);
1125 }