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