Janitorial. Get rid of W->A call.
[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     /* Draw the check-box bitmap */
866     if (action == ODA_DRAWENTIRE || action == ODA_SELECT)
867     {
868         /* Since WM_ERASEBKGND does nothing, first prepare background */
869         if (action == ODA_SELECT) FillRect( hDC, &rbox, hBrush );
870         else FillRect( hDC, &client, hBrush );
871
872         if( TWEAK_WineLook == WIN31_LOOK )
873         {
874         HDC hMemDC = CreateCompatibleDC( hDC );
875         int x = 0, y = 0;
876         delta = (rbox.bottom - rbox.top - checkBoxHeight) / 2;
877
878         /* Check in case the client area is smaller than the checkbox bitmap */
879         if (delta < 0) delta = 0;
880
881         if (state & BUTTON_HIGHLIGHTED) x += 2 * checkBoxWidth;
882         if (state & (BUTTON_CHECKED | BUTTON_3STATE)) x += checkBoxWidth;
883         if ((get_button_type(style) == BS_RADIOBUTTON) ||
884             (get_button_type(style) == BS_AUTORADIOBUTTON)) y += checkBoxHeight;
885         else if (state & BUTTON_3STATE) y += 2 * checkBoxHeight;
886
887         /* The bitmap for the radio button is not aligned with the
888          * left of the window, it is 1 pixel off. */
889         if ((get_button_type(style) == BS_RADIOBUTTON) ||
890             (get_button_type(style) == BS_AUTORADIOBUTTON))
891           rbox.left += 1;
892
893         SelectObject( hMemDC, hbitmapCheckBoxes );
894         BitBlt( hDC, rbox.left, rbox.top + delta, checkBoxWidth,
895                   checkBoxHeight, hMemDC, x, y, SRCCOPY );
896         DeleteDC( hMemDC );
897         }
898         else
899         {
900             UINT flags;
901
902             if ((get_button_type(style) == BS_RADIOBUTTON) ||
903                 (get_button_type(style) == BS_AUTORADIOBUTTON)) flags = DFCS_BUTTONRADIO;
904             else if (state & BUTTON_3STATE) flags = DFCS_BUTTON3STATE;
905             else flags = DFCS_BUTTONCHECK;
906
907             if (state & (BUTTON_CHECKED | BUTTON_3STATE)) flags |= DFCS_CHECKED;
908             if (state & BUTTON_HIGHLIGHTED) flags |= DFCS_PUSHED;
909
910             if (style & WS_DISABLED) flags |= DFCS_INACTIVE;
911
912             /* rbox must have the correct height */
913             delta = rbox.bottom - rbox.top - checkBoxHeight;
914             if (delta > 0)
915             {
916                 int ofs = (abs(delta) / 2);
917                 rbox.bottom -= ofs + 1;
918                 rbox.top = rbox.bottom - checkBoxHeight;
919             }
920             else if (delta < 0)
921             {
922                 int ofs = (abs(delta) / 2);
923                 rbox.top -= ofs + 1;
924                 rbox.bottom = rbox.top + checkBoxHeight;
925             }
926
927             DrawFrameControl( hDC, &rbox, DFC_BUTTON, flags );
928         }
929     }
930
931     /* Draw label */
932     client = rtext;
933     dtFlags = BUTTON_CalcLabelRect(hwnd, hDC, &rtext);
934
935     if (dtFlags == (UINT)-1L) /* Noting to draw */
936         return;
937     hRgn = CreateRectRgn(client.left, client.top, client.right, client.bottom);
938     SelectClipRgn(hDC, hRgn);
939     DeleteObject(hRgn);
940
941     if (action == ODA_DRAWENTIRE)
942         BUTTON_DrawLabel(hwnd, hDC, dtFlags, &rtext);
943
944     /* ... and focus */
945     if ((action == ODA_FOCUS) ||
946         ((action == ODA_DRAWENTIRE) && (state & BUTTON_HASFOCUS)))
947     {
948         rtext.left--;
949         rtext.right++;
950         IntersectRect(&rtext, &rtext, &client);
951         DrawFocusRect( hDC, &rtext );
952     }
953     SelectClipRgn(hDC, 0);
954 }
955
956
957 /**********************************************************************
958  *       BUTTON_CheckAutoRadioButton
959  *
960  * hwnd is checked, uncheck every other auto radio button in group
961  */
962 static void BUTTON_CheckAutoRadioButton( HWND hwnd )
963 {
964     HWND parent, sibling, start;
965
966     parent = GetParent(hwnd);
967     /* make sure that starting control is not disabled or invisible */
968     start = sibling = GetNextDlgGroupItem( parent, hwnd, TRUE );
969     do
970     {
971         if (!sibling) break;
972         if ((hwnd != sibling) &&
973             ((GetWindowLongA( sibling, GWL_STYLE) & 0x0f) == BS_AUTORADIOBUTTON))
974             SendMessageW( sibling, BM_SETCHECK, BUTTON_UNCHECKED, 0 );
975         sibling = GetNextDlgGroupItem( parent, sibling, FALSE );
976     } while (sibling != start);
977 }
978
979
980 /**********************************************************************
981  *       Group Box Functions
982  */
983
984 static void GB_Paint( HWND hwnd, HDC hDC, UINT action )
985 {
986     RECT rc, rcFrame;
987     HBRUSH hbr;
988     HFONT hFont;
989     UINT dtFlags;
990     LONG style = GetWindowLongA( hwnd, GWL_STYLE );
991
992     if (action != ODA_DRAWENTIRE) return;
993
994     if ((hFont = get_button_font( hwnd ))) SelectObject( hDC, hFont );
995     /* GroupBox acts like static control, so it sends CTLCOLORSTATIC */
996     hbr = (HBRUSH)SendMessageW(GetParent(hwnd), WM_CTLCOLORSTATIC, (WPARAM)hDC, (LPARAM)hwnd);
997     if (!hbr) /* did the app forget to call defwindowproc ? */
998         hbr = (HBRUSH)DefWindowProcW(GetParent(hwnd), WM_CTLCOLORSTATIC,
999                                      (WPARAM)hDC, (LPARAM)hwnd);
1000
1001     GetClientRect( hwnd, &rc);
1002     if (TWEAK_WineLook == WIN31_LOOK) {
1003         HPEN hPrevPen = SelectObject( hDC,
1004                                           SYSCOLOR_GetPen(COLOR_WINDOWFRAME));
1005         HBRUSH hPrevBrush = SelectObject( hDC,
1006                                               GetStockObject(NULL_BRUSH) );
1007
1008         Rectangle( hDC, rc.left, rc.top + 2, rc.right - 1, rc.bottom - 1 );
1009         SelectObject( hDC, hPrevBrush );
1010         SelectObject( hDC, hPrevPen );
1011     } else {
1012         TEXTMETRICW tm;
1013         rcFrame = rc;
1014
1015         GetTextMetricsW (hDC, &tm);
1016         rcFrame.top += (tm.tmHeight / 2) - 1;
1017         DrawEdge (hDC, &rcFrame, EDGE_ETCHED, BF_RECT | ((style & BS_FLAT) ? BF_FLAT : 0));
1018     }
1019
1020     InflateRect(&rc, -7, 1);
1021     dtFlags = BUTTON_CalcLabelRect(hwnd, hDC, &rc);
1022
1023     if (dtFlags == (UINT)-1L)
1024        return;
1025
1026     /* Because buttons have CS_PARENTDC class style, there is a chance
1027      * that label will be drawn out of client rect.
1028      * But Windows doesn't clip label's rect, so do I.
1029      */
1030
1031     /* There is 1-pixel marging at the left, right, and bottom */
1032     rc.left--; rc.right++; rc.bottom++;
1033     FillRect(hDC, &rc, hbr);
1034     rc.left++; rc.right--; rc.bottom--;
1035
1036     BUTTON_DrawLabel(hwnd, hDC, dtFlags, &rc);
1037 }
1038
1039
1040 /**********************************************************************
1041  *       User Button Functions
1042  */
1043
1044 static void UB_Paint( HWND hwnd, HDC hDC, UINT action )
1045 {
1046     RECT rc;
1047     HBRUSH hBrush;
1048     HFONT hFont;
1049     LONG state = get_button_state( hwnd );
1050
1051     if (action == ODA_SELECT) return;
1052
1053     GetClientRect( hwnd, &rc);
1054
1055     if ((hFont = get_button_font( hwnd ))) SelectObject( hDC, hFont );
1056
1057     hBrush = (HBRUSH)SendMessageW(GetParent(hwnd), WM_CTLCOLORBTN, (WPARAM)hDC, (LPARAM)hwnd);
1058     if (!hBrush) /* did the app forget to call defwindowproc ? */
1059         hBrush = (HBRUSH)DefWindowProcW(GetParent(hwnd), WM_CTLCOLORBTN,
1060                                         (WPARAM)hDC, (LPARAM)hwnd);
1061
1062     FillRect( hDC, &rc, hBrush );
1063     if ((action == ODA_FOCUS) ||
1064         ((action == ODA_DRAWENTIRE) && (state & BUTTON_HASFOCUS)))
1065         DrawFocusRect( hDC, &rc );
1066 }
1067
1068
1069 /**********************************************************************
1070  *       Ownerdrawn Button Functions
1071  */
1072
1073 static void OB_Paint( HWND hwnd, HDC hDC, UINT action )
1074 {
1075     LONG state = get_button_state( hwnd );
1076     DRAWITEMSTRUCT dis;
1077     HRGN clipRegion;
1078     RECT clipRect;
1079     UINT id = GetWindowLongA( hwnd, GWL_ID );
1080
1081     dis.CtlType    = ODT_BUTTON;
1082     dis.CtlID      = id;
1083     dis.itemID     = 0;
1084     dis.itemAction = action;
1085     dis.itemState  = ((state & BUTTON_HASFOCUS) ? ODS_FOCUS : 0) |
1086                      ((state & BUTTON_HIGHLIGHTED) ? ODS_SELECTED : 0) |
1087                      (IsWindowEnabled(hwnd) ? 0: ODS_DISABLED);
1088     dis.hwndItem   = hwnd;
1089     dis.hDC        = hDC;
1090     dis.itemData   = 0;
1091     GetClientRect( hwnd, &dis.rcItem );
1092
1093     clipRegion = CreateRectRgnIndirect(&dis.rcItem);
1094     if (GetClipRgn(hDC, clipRegion) != 1)
1095     {
1096         DeleteObject(clipRegion);
1097         clipRegion=NULL;
1098     }
1099     clipRect = dis.rcItem;
1100     DPtoLP(hDC, (LPPOINT) &clipRect, 2);
1101     IntersectClipRect(hDC, clipRect.left,  clipRect.top, clipRect.right, clipRect.bottom);
1102
1103     SetBkColor( hDC, GetSysColor( COLOR_BTNFACE ) );
1104     SendMessageW( GetParent(hwnd), WM_DRAWITEM, id, (LPARAM)&dis );
1105     SelectClipRgn(hDC, clipRegion);
1106 }