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