Removed trailing whitespace.
[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 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, 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 < 0L || 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 = SendMessageW(GetParent(hWnd), WM_CTLCOLORSTATIC, hdc, (LPARAM)hWnd);
312         if (!hbrush) /* did the app forget to call DefWindowProc ? */
313             hbrush = DefWindowProcW(GetParent(hWnd), WM_CTLCOLORSTATIC, hdc, (LPARAM)hWnd);
314
315         GetClientRect(hWnd, &client);
316         rc = client;
317         BUTTON_CalcLabelRect(hWnd, hdc, &rc);
318         /* Clip by client rect bounds */
319         if (rc.right > client.right) rc.right = client.right;
320         if (rc.bottom > client.bottom) rc.bottom = client.bottom;
321         FillRect(hdc, &rc, hbrush);
322         ReleaseDC(hWnd, hdc);
323
324         if (unicode) DefWindowProcW( hWnd, WM_SETTEXT, wParam, lParam );
325         else DefWindowProcA( hWnd, WM_SETTEXT, wParam, lParam );
326         if (btn_type == BS_GROUPBOX) /* Yes, only for BS_GROUPBOX */
327             InvalidateRect( hWnd, NULL, TRUE );
328         else
329             paint_button( hWnd, btn_type, ODA_DRAWENTIRE );
330         return 1; /* success. FIXME: check text length */
331     }
332
333     case WM_SETFONT:
334         set_button_font( hWnd, wParam );
335         if (lParam) paint_button( hWnd, btn_type, ODA_DRAWENTIRE );
336         break;
337
338     case WM_GETFONT:
339         return get_button_font( hWnd );
340
341     case WM_SETFOCUS:
342         if ((btn_type == BS_RADIOBUTTON || btn_type == BS_AUTORADIOBUTTON) && (GetCapture() != hWnd) &&
343             !(SendMessageW(hWnd, BM_GETCHECK, 0, 0) & BST_CHECKED))
344         {
345             /* The notification is sent when the button (BS_AUTORADIOBUTTON)
346                is unchecked and the focus was not given by a mouse click. */
347             if (btn_type == BS_AUTORADIOBUTTON)
348                 SendMessageW( hWnd, BM_SETCHECK, BUTTON_CHECKED, 0 );
349             SendMessageW( GetParent(hWnd), WM_COMMAND,
350                           MAKEWPARAM( GetWindowLongA(hWnd,GWL_ID), BN_CLICKED ), (LPARAM)hWnd);
351         }
352         set_button_state( hWnd, get_button_state(hWnd) | BUTTON_HASFOCUS );
353         paint_button( hWnd, btn_type, ODA_FOCUS );
354         break;
355
356     case WM_KILLFOCUS:
357         set_button_state( hWnd, get_button_state(hWnd) & ~BUTTON_HASFOCUS );
358         paint_button( hWnd, btn_type, ODA_FOCUS );
359         InvalidateRect( hWnd, NULL, TRUE );
360         break;
361
362     case WM_SYSCOLORCHANGE:
363         InvalidateRect( hWnd, NULL, FALSE );
364         break;
365
366     case BM_SETSTYLE16:
367     case BM_SETSTYLE:
368         if ((wParam & 0x0f) >= MAX_BTN_TYPE) break;
369         btn_type = wParam & 0x0f;
370         style = (style & ~0x0f) | btn_type;
371         SetWindowLongA( hWnd, GWL_STYLE, style );
372
373         /* Only redraw if lParam flag is set.*/
374         if (lParam)
375            paint_button( hWnd, btn_type, ODA_DRAWENTIRE );
376
377         break;
378
379     case BM_CLICK:
380         SendMessageW( hWnd, WM_LBUTTONDOWN, 0, 0 );
381         SendMessageW( hWnd, WM_LBUTTONUP, 0, 0 );
382         break;
383
384     case BM_SETIMAGE:
385         /* Check that image format matches button style */
386         switch (style & (BS_BITMAP|BS_ICON))
387         {
388         case BS_BITMAP:
389             if (wParam != IMAGE_BITMAP) return 0;
390             break;
391         case BS_ICON:
392             if (wParam != IMAGE_ICON) return 0;
393             break;
394         default:
395             return 0;
396         }
397         oldHbitmap = SetWindowLongA( hWnd, HIMAGE_GWL_OFFSET, lParam );
398         InvalidateRect( hWnd, NULL, FALSE );
399         return oldHbitmap;
400
401     case BM_GETIMAGE:
402         return GetWindowLongA( hWnd, HIMAGE_GWL_OFFSET );
403
404     case BM_GETCHECK16:
405     case BM_GETCHECK:
406         return get_button_state( hWnd ) & 3;
407
408     case BM_SETCHECK16:
409     case BM_SETCHECK:
410         if (wParam > maxCheckState[btn_type]) wParam = maxCheckState[btn_type];
411         state = get_button_state( hWnd );
412         if ((btn_type == BS_RADIOBUTTON) || (btn_type == BS_AUTORADIOBUTTON))
413         {
414             if (wParam) style |= WS_TABSTOP;
415             else style &= ~WS_TABSTOP;
416             SetWindowLongA( hWnd, GWL_STYLE, style );
417         }
418         if ((state & 3) != wParam)
419         {
420             set_button_state( hWnd, (state & ~3) | wParam );
421             paint_button( hWnd, btn_type, ODA_SELECT );
422         }
423         if ((btn_type == BS_AUTORADIOBUTTON) && (wParam == BUTTON_CHECKED) && (style & WS_CHILD))
424             BUTTON_CheckAutoRadioButton( hWnd );
425         break;
426
427     case BM_GETSTATE16:
428     case BM_GETSTATE:
429         return get_button_state( hWnd );
430
431     case BM_SETSTATE16:
432     case BM_SETSTATE:
433         state = get_button_state( hWnd );
434         if (wParam)
435         {
436             if (state & BUTTON_HIGHLIGHTED) break;
437             set_button_state( hWnd, state | BUTTON_HIGHLIGHTED );
438         }
439         else
440         {
441             if (!(state & BUTTON_HIGHLIGHTED)) break;
442             set_button_state( hWnd, state & ~BUTTON_HIGHLIGHTED );
443         }
444         paint_button( hWnd, btn_type, ODA_SELECT );
445         break;
446
447     case WM_NCHITTEST:
448         if(btn_type == BS_GROUPBOX) return HTTRANSPARENT;
449         /* fall through */
450     default:
451         return unicode ? DefWindowProcW(hWnd, uMsg, wParam, lParam) :
452                          DefWindowProcA(hWnd, uMsg, wParam, lParam);
453     }
454     return 0;
455 }
456
457 /***********************************************************************
458  *           ButtonWndProcW
459  * The button window procedure. This is just a wrapper which locks
460  * the passed HWND and calls the real window procedure (with a WND*
461  * pointer pointing to the locked windowstructure).
462  */
463 static LRESULT WINAPI ButtonWndProcW( HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam )
464 {
465     if (!IsWindow( hWnd )) return 0;
466     return ButtonWndProc_common( hWnd, uMsg, wParam, lParam, TRUE );
467 }
468
469
470 /***********************************************************************
471  *           ButtonWndProcA
472  */
473 static LRESULT WINAPI ButtonWndProcA( HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam )
474 {
475     if (!IsWindow( hWnd )) return 0;
476     return ButtonWndProc_common( hWnd, uMsg, wParam, lParam, FALSE );
477 }
478
479
480 /**********************************************************************
481  * Convert button styles to flags used by DrawText.
482  * TODO: handle WS_EX_RIGHT extended style.
483  */
484 static UINT BUTTON_BStoDT(DWORD style)
485 {
486    UINT dtStyle = DT_NOCLIP;  /* We use SelectClipRgn to limit output */
487
488    /* "Convert" pushlike buttons to pushbuttons */
489    if (style & BS_PUSHLIKE)
490       style &= ~0x0F;
491
492    if (!(style & BS_MULTILINE))
493       dtStyle |= DT_SINGLELINE;
494    else
495       dtStyle |= DT_WORDBREAK;
496
497    switch (style & BS_CENTER)
498    {
499       case BS_LEFT:   /* DT_LEFT is 0 */    break;
500       case BS_RIGHT:  dtStyle |= DT_RIGHT;  break;
501       case BS_CENTER: dtStyle |= DT_CENTER; break;
502       default:
503          /* Pushbutton's text is centered by default */
504          if (get_button_type(style) <= BS_DEFPUSHBUTTON) dtStyle |= DT_CENTER;
505          /* all other flavours have left aligned text */
506    }
507
508    /* DrawText ignores vertical alignment for multiline text,
509     * but we use these flags to align label manualy.
510     */
511    if (get_button_type(style) != BS_GROUPBOX)
512    {
513       switch (style & BS_VCENTER)
514       {
515          case BS_TOP:     /* DT_TOP is 0 */      break;
516          case BS_BOTTOM:  dtStyle |= DT_BOTTOM;  break;
517          case BS_VCENTER: /* fall through */
518          default:         dtStyle |= DT_VCENTER; break;
519       }
520    }
521    else
522       /* GroupBox's text is always single line and is top aligned. */
523       dtStyle |= DT_SINGLELINE;
524
525    return dtStyle;
526 }
527
528 /**********************************************************************
529  *       BUTTON_CalcLabelRect
530  *
531  *   Calculates label's rectangle depending on button style.
532  *
533  * Returns flags to be passed to DrawText.
534  * Calculated rectangle doesn't take into account button state
535  * (pushed, etc.). If there is nothing to draw (no text/image) output
536  * rectangle is empty, and return value is (UINT)-1.
537  */
538 static UINT BUTTON_CalcLabelRect(HWND hwnd, HDC hdc, RECT *rc)
539 {
540    LONG style = GetWindowLongA( hwnd, GWL_STYLE );
541    WCHAR *text;
542    ICONINFO    iconInfo;
543    BITMAP      bm;
544    UINT        dtStyle = BUTTON_BStoDT(style);
545    RECT        r = *rc;
546    INT         n;
547
548    /* Calculate label rectangle according to label type */
549    switch (style & (BS_ICON|BS_BITMAP))
550    {
551       case BS_TEXT:
552           if (!(text = get_button_text( hwnd ))) goto empty_rect;
553           if (!text[0])
554           {
555               HeapFree( GetProcessHeap(), 0, text );
556               goto empty_rect;
557           }
558           DrawTextW(hdc, text, -1, &r, dtStyle | DT_CALCRECT);
559           HeapFree( GetProcessHeap(), 0, text );
560           break;
561
562       case BS_ICON:
563          if (!GetIconInfo((HICON)GetWindowLongA( hwnd, HIMAGE_GWL_OFFSET ), &iconInfo))
564             goto empty_rect;
565
566          GetObjectW (iconInfo.hbmColor, sizeof(BITMAP), &bm);
567
568          r.right  = r.left + bm.bmWidth;
569          r.bottom = r.top  + bm.bmHeight;
570
571          DeleteObject(iconInfo.hbmColor);
572          DeleteObject(iconInfo.hbmMask);
573          break;
574
575       case BS_BITMAP:
576          if (!GetObjectW( (HANDLE)GetWindowLongA( hwnd, HIMAGE_GWL_OFFSET ), sizeof(BITMAP), &bm))
577             goto empty_rect;
578
579          r.right  = r.left + bm.bmWidth;
580          r.bottom = r.top  + bm.bmHeight;
581          break;
582
583       default:
584       empty_rect:
585          r.right = r.left;
586          r.bottom = r.top;
587          return (UINT)(LONG)-1;
588    }
589
590    /* Position label inside bounding rectangle according to
591     * alignment flags. (calculated rect is always left-top aligned).
592     * If label is aligned to any side - shift label in opposite
593     * direction to leave extra space for focus rectangle.
594     */
595    switch (dtStyle & (DT_CENTER|DT_RIGHT))
596    {
597       case DT_LEFT:    r.left++;  r.right++;  break;
598       case DT_CENTER:  n = r.right - r.left;
599                        r.left   = rc->left + ((rc->right - rc->left) - n) / 2;
600                        r.right  = r.left + n; break;
601       case DT_RIGHT:   n = r.right - r.left;
602                        r.right  = rc->right - 1;
603                        r.left   = r.right - n;
604                        break;
605    }
606
607    switch (dtStyle & (DT_VCENTER|DT_BOTTOM))
608    {
609       case DT_TOP:     r.top++;  r.bottom++;  break;
610       case DT_VCENTER: n = r.bottom - r.top;
611                        r.top    = rc->top + ((rc->bottom - rc->top) - n) / 2;
612                        r.bottom = r.top + n;  break;
613       case DT_BOTTOM:  n = r.bottom - r.top;
614                        r.bottom = rc->bottom - 1;
615                        r.top    = r.bottom - n;
616                        break;
617    }
618
619    *rc = r;
620    return dtStyle;
621 }
622
623
624 /**********************************************************************
625  *       BUTTON_DrawTextCallback
626  *
627  *   Callback function used by DrawStateW function.
628  */
629 static BOOL CALLBACK BUTTON_DrawTextCallback(HDC hdc, LPARAM lp, WPARAM wp, int cx, int cy)
630 {
631    RECT rc;
632    rc.left = 0;
633    rc.top = 0;
634    rc.right = cx;
635    rc.bottom = cy;
636
637    DrawTextW(hdc, (LPCWSTR)lp, -1, &rc, (UINT)wp);
638    return TRUE;
639 }
640
641
642 /**********************************************************************
643  *       BUTTON_DrawLabel
644  *
645  *   Common function for drawing button label.
646  */
647 static void BUTTON_DrawLabel(HWND hwnd, HDC hdc, UINT dtFlags, RECT *rc)
648 {
649    DRAWSTATEPROC lpOutputProc = NULL;
650    LPARAM lp;
651    WPARAM wp = 0;
652    HBRUSH hbr = 0;
653    UINT flags = IsWindowEnabled(hwnd) ? DSS_NORMAL : DSS_DISABLED;
654    LONG state = get_button_state( hwnd );
655    LONG style = GetWindowLongA( hwnd, GWL_STYLE );
656    WCHAR *text = NULL;
657
658    /* FIXME: To draw disabled label in Win31 look-and-feel, we probably
659     * must use DSS_MONO flag and COLOR_GRAYTEXT brush (or maybe DSS_UNION).
660     * I don't have Win31 on hand to verify that, so I leave it as is.
661     */
662
663    if ((style & BS_PUSHLIKE) && (state & BUTTON_3STATE))
664    {
665       hbr = GetSysColorBrush(COLOR_GRAYTEXT);
666       flags |= DSS_MONO;
667    }
668
669    switch (style & (BS_ICON|BS_BITMAP))
670    {
671       case BS_TEXT:
672          /* DST_COMPLEX -- is 0 */
673          lpOutputProc = BUTTON_DrawTextCallback;
674          if (!(text = get_button_text( hwnd ))) return;
675          lp = (LPARAM)text;
676          wp = (WPARAM)dtFlags;
677          break;
678
679       case BS_ICON:
680          flags |= DST_ICON;
681          lp = GetWindowLongA( hwnd, HIMAGE_GWL_OFFSET );
682          break;
683
684       case BS_BITMAP:
685          flags |= DST_BITMAP;
686          lp = GetWindowLongA( hwnd, HIMAGE_GWL_OFFSET );
687          break;
688
689       default:
690          return;
691    }
692
693    DrawStateW(hdc, hbr, lpOutputProc, lp, wp, rc->left, rc->top,
694               rc->right - rc->left, rc->bottom - rc->top, flags);
695    if (text) HeapFree( GetProcessHeap(), 0, text );
696 }
697
698 /**********************************************************************
699  *       Push Button Functions
700  */
701 static void PB_Paint( HWND hwnd, HDC hDC, UINT action )
702 {
703     RECT     rc, focus_rect, r;
704     UINT     dtFlags;
705     HRGN     hRgn;
706     HPEN     hOldPen;
707     HBRUSH   hOldBrush;
708     INT      oldBkMode;
709     COLORREF oldTxtColor;
710     HFONT hFont;
711     LONG state = get_button_state( hwnd );
712     LONG style = GetWindowLongA( hwnd, GWL_STYLE );
713     BOOL pushedState = (state & BUTTON_HIGHLIGHTED);
714
715     GetClientRect( hwnd, &rc );
716
717     /* Send WM_CTLCOLOR to allow changing the font (the colors are fixed) */
718     if ((hFont = get_button_font( hwnd ))) SelectObject( hDC, hFont );
719     SendMessageW( GetParent(hwnd), WM_CTLCOLORBTN, hDC, (LPARAM)hwnd );
720     hOldPen = (HPEN)SelectObject(hDC, SYSCOLOR_GetPen(COLOR_WINDOWFRAME));
721     hOldBrush =(HBRUSH)SelectObject(hDC,GetSysColorBrush(COLOR_BTNFACE));
722     oldBkMode = SetBkMode(hDC, TRANSPARENT);
723
724     if ( TWEAK_WineLook == WIN31_LOOK)
725     {
726         COLORREF clr_wnd = GetSysColor(COLOR_WINDOW);
727         Rectangle(hDC, rc.left, rc.top, rc.right, rc.bottom);
728
729         SetPixel( hDC, rc.left, rc.top, clr_wnd);
730         SetPixel( hDC, rc.left, rc.bottom-1, clr_wnd);
731         SetPixel( hDC, rc.right-1, rc.top, clr_wnd);
732         SetPixel( hDC, rc.right-1, rc.bottom-1, clr_wnd);
733         InflateRect( &rc, -1, -1 );
734     }
735
736     if (get_button_type(style) == BS_DEFPUSHBUTTON)
737     {
738         Rectangle(hDC, rc.left, rc.top, rc.right, rc.bottom);
739         InflateRect( &rc, -1, -1 );
740     }
741
742     if (TWEAK_WineLook == WIN31_LOOK)
743     {
744         if (pushedState)
745         {
746             /* draw button shadow: */
747             SelectObject(hDC, GetSysColorBrush(COLOR_BTNSHADOW));
748             PatBlt(hDC, rc.left, rc.top, 1, rc.bottom-rc.top, PATCOPY );
749             PatBlt(hDC, rc.left, rc.top, rc.right-rc.left, 1, PATCOPY );
750         } else {
751            rc.right++, rc.bottom++;
752            DrawEdge( hDC, &rc, EDGE_RAISED, BF_RECT );
753            rc.right--, rc.bottom--;
754         }
755     }
756     else
757     {
758         UINT 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
778     /* draw button label */
779     r = rc;
780     dtFlags = BUTTON_CalcLabelRect(hwnd, hDC, &r);
781
782     if (dtFlags == (UINT)-1L)
783        goto cleanup;
784
785     if (pushedState)
786        OffsetRect(&r, 1, 1);
787
788     if(TWEAK_WineLook == WIN31_LOOK)
789     {
790        focus_rect = r;
791        InflateRect(&focus_rect, 2, 0);
792     }
793
794     hRgn = CreateRectRgn(rc.left, rc.top, rc.right, rc.bottom);
795     SelectClipRgn(hDC, hRgn);
796
797     oldTxtColor = SetTextColor( hDC, GetSysColor(COLOR_BTNTEXT) );
798
799     BUTTON_DrawLabel(hwnd, hDC, dtFlags, &r);
800
801     SetTextColor( hDC, oldTxtColor );
802     SelectClipRgn(hDC, 0);
803     DeleteObject(hRgn);
804
805     if (state & BUTTON_HASFOCUS)
806     {
807         InflateRect( &focus_rect, -1, -1 );
808         IntersectRect(&focus_rect, &focus_rect, &rc);
809         DrawFocusRect( hDC, &focus_rect );
810     }
811
812  cleanup:
813     SelectObject( hDC, hOldPen );
814     SelectObject( hDC, hOldBrush );
815     SetBkMode(hDC, oldBkMode);
816 }
817
818 /**********************************************************************
819  *       Check Box & Radio Button Functions
820  */
821
822 static void CB_Paint( HWND hwnd, HDC hDC, UINT action )
823 {
824     RECT rbox, rtext, client;
825     HBRUSH hBrush;
826     int delta;
827     UINT dtFlags;
828     HRGN hRgn;
829     HFONT hFont;
830     LONG state = get_button_state( hwnd );
831     LONG style = GetWindowLongA( hwnd, GWL_STYLE );
832
833     if (style & BS_PUSHLIKE)
834     {
835         PB_Paint( hwnd, hDC, action );
836         return;
837     }
838
839     GetClientRect(hwnd, &client);
840     rbox = rtext = client;
841
842     if ((hFont = get_button_font( hwnd ))) SelectObject( hDC, hFont );
843
844     hBrush = SendMessageW( GetParent(hwnd), WM_CTLCOLORSTATIC, hDC, (LPARAM)hwnd );
845     if (!hBrush) /* did the app forget to call defwindowproc ? */
846         hBrush = DefWindowProcW( GetParent(hwnd), WM_CTLCOLORSTATIC, hDC, (LPARAM)hwnd );
847
848     if (style & BS_LEFTTEXT)
849     {
850         /* magic +4 is what CTL3D expects */
851
852         rtext.right -= checkBoxWidth + 4;
853         rbox.left = rbox.right - checkBoxWidth;
854     }
855     else
856     {
857         rtext.left += checkBoxWidth + 4;
858         rbox.right = checkBoxWidth;
859     }
860
861     /* Draw the check-box bitmap */
862     if (action == ODA_DRAWENTIRE || action == ODA_SELECT)
863     {
864         /* Since WM_ERASEBKGND does nothing, first prepare background */
865         if (action == ODA_SELECT) FillRect( hDC, &rbox, hBrush );
866         else FillRect( hDC, &client, hBrush );
867
868         if( TWEAK_WineLook == WIN31_LOOK )
869         {
870         HDC hMemDC = CreateCompatibleDC( hDC );
871         int x = 0, y = 0;
872         delta = (rbox.bottom - rbox.top - checkBoxHeight) / 2;
873
874         /* Check in case the client area is smaller than the checkbox bitmap */
875         if (delta < 0) delta = 0;
876
877         if (state & BUTTON_HIGHLIGHTED) x += 2 * checkBoxWidth;
878         if (state & (BUTTON_CHECKED | BUTTON_3STATE)) x += checkBoxWidth;
879         if ((get_button_type(style) == BS_RADIOBUTTON) ||
880             (get_button_type(style) == BS_AUTORADIOBUTTON)) y += checkBoxHeight;
881         else if (state & BUTTON_3STATE) y += 2 * checkBoxHeight;
882
883         /* The bitmap for the radio button is not aligned with the
884          * left of the window, it is 1 pixel off. */
885         if ((get_button_type(style) == BS_RADIOBUTTON) ||
886             (get_button_type(style) == BS_AUTORADIOBUTTON))
887           rbox.left += 1;
888
889         SelectObject( hMemDC, hbitmapCheckBoxes );
890         BitBlt( hDC, rbox.left, rbox.top + delta, checkBoxWidth,
891                   checkBoxHeight, hMemDC, x, y, SRCCOPY );
892         DeleteDC( hMemDC );
893         }
894         else
895         {
896             UINT flags;
897
898             if ((get_button_type(style) == BS_RADIOBUTTON) ||
899                 (get_button_type(style) == BS_AUTORADIOBUTTON)) flags = DFCS_BUTTONRADIO;
900             else if (state & BUTTON_3STATE) flags = DFCS_BUTTON3STATE;
901             else flags = DFCS_BUTTONCHECK;
902
903             if (state & (BUTTON_CHECKED | BUTTON_3STATE)) flags |= DFCS_CHECKED;
904             if (state & BUTTON_HIGHLIGHTED) flags |= DFCS_PUSHED;
905
906             if (style & WS_DISABLED) flags |= DFCS_INACTIVE;
907
908             /* rbox must have the correct height */
909             delta = rbox.bottom - rbox.top - checkBoxHeight;
910             if (delta > 0)
911             {
912                 int ofs = (abs(delta) / 2);
913                 rbox.bottom -= ofs + 1;
914                 rbox.top = rbox.bottom - checkBoxHeight;
915             }
916             else if (delta < 0)
917             {
918                 int ofs = (abs(delta) / 2);
919                 rbox.top -= ofs + 1;
920                 rbox.bottom = rbox.top + checkBoxHeight;
921             }
922
923             DrawFrameControl( hDC, &rbox, DFC_BUTTON, flags );
924         }
925     }
926
927     /* Draw label */
928     client = rtext;
929     dtFlags = BUTTON_CalcLabelRect(hwnd, hDC, &rtext);
930
931     if (dtFlags == (UINT)-1L) /* Noting to draw */
932         return;
933     hRgn = CreateRectRgn(client.left, client.top, client.right, client.bottom);
934     SelectClipRgn(hDC, hRgn);
935     DeleteObject(hRgn);
936
937     if (action == ODA_DRAWENTIRE)
938         BUTTON_DrawLabel(hwnd, hDC, dtFlags, &rtext);
939
940     /* ... and focus */
941     if ((action == ODA_FOCUS) ||
942         ((action == ODA_DRAWENTIRE) && (state & BUTTON_HASFOCUS)))
943     {
944         rtext.left--;
945         rtext.right++;
946         IntersectRect(&rtext, &rtext, &client);
947         DrawFocusRect( hDC, &rtext );
948     }
949     SelectClipRgn(hDC, 0);
950 }
951
952
953 /**********************************************************************
954  *       BUTTON_CheckAutoRadioButton
955  *
956  * hwnd is checked, uncheck every other auto radio button in group
957  */
958 static void BUTTON_CheckAutoRadioButton( HWND hwnd )
959 {
960     HWND parent, sibling, start;
961
962     parent = GetParent(hwnd);
963     /* make sure that starting control is not disabled or invisible */
964     start = sibling = GetNextDlgGroupItem( parent, hwnd, TRUE );
965     do
966     {
967         if (!sibling) break;
968         if ((hwnd != sibling) &&
969             ((GetWindowLongA( sibling, GWL_STYLE) & 0x0f) == BS_AUTORADIOBUTTON))
970             SendMessageW( sibling, BM_SETCHECK, BUTTON_UNCHECKED, 0 );
971         sibling = GetNextDlgGroupItem( parent, sibling, FALSE );
972     } while (sibling != start);
973 }
974
975
976 /**********************************************************************
977  *       Group Box Functions
978  */
979
980 static void GB_Paint( HWND hwnd, HDC hDC, UINT action )
981 {
982     RECT rc, rcFrame;
983     HBRUSH hbr;
984     HFONT hFont;
985     UINT dtFlags;
986     LONG style = GetWindowLongA( hwnd, GWL_STYLE );
987
988     if (action != ODA_DRAWENTIRE) return;
989
990     if ((hFont = get_button_font( hwnd ))) SelectObject( hDC, hFont );
991     /* GroupBox acts like static control, so it sends CTLCOLORSTATIC */
992     hbr = SendMessageW( GetParent(hwnd), WM_CTLCOLORSTATIC, hDC, (LPARAM)hwnd );
993     if (!hbr) /* did the app forget to call defwindowproc ? */
994         hbr = DefWindowProcW( GetParent(hwnd), WM_CTLCOLORSTATIC, hDC, (LPARAM)hwnd );
995
996     GetClientRect( hwnd, &rc);
997     if (TWEAK_WineLook == WIN31_LOOK) {
998         HPEN hPrevPen = SelectObject( hDC,
999                                           SYSCOLOR_GetPen(COLOR_WINDOWFRAME));
1000         HBRUSH hPrevBrush = SelectObject( hDC,
1001                                               GetStockObject(NULL_BRUSH) );
1002
1003         Rectangle( hDC, rc.left, rc.top + 2, rc.right - 1, rc.bottom - 1 );
1004         SelectObject( hDC, hPrevBrush );
1005         SelectObject( hDC, hPrevPen );
1006     } else {
1007         TEXTMETRICW tm;
1008         rcFrame = rc;
1009
1010         GetTextMetricsW (hDC, &tm);
1011         rcFrame.top += (tm.tmHeight / 2) - 1;
1012         DrawEdge (hDC, &rcFrame, EDGE_ETCHED, BF_RECT | ((style & BS_FLAT) ? BF_FLAT : 0));
1013     }
1014
1015     InflateRect(&rc, -7, 1);
1016     dtFlags = BUTTON_CalcLabelRect(hwnd, hDC, &rc);
1017
1018     if (dtFlags == (UINT)-1L)
1019        return;
1020
1021     /* Because buttons have CS_PARENTDC class style, there is a chance
1022      * that label will be drawn out of client rect.
1023      * But Windows doesn't clip label's rect, so do I.
1024      */
1025
1026     /* There is 1-pixel marging at the left, right, and bottom */
1027     rc.left--; rc.right++; rc.bottom++;
1028     FillRect(hDC, &rc, hbr);
1029     rc.left++; rc.right--; rc.bottom--;
1030
1031     BUTTON_DrawLabel(hwnd, hDC, dtFlags, &rc);
1032 }
1033
1034
1035 /**********************************************************************
1036  *       User Button Functions
1037  */
1038
1039 static void UB_Paint( HWND hwnd, HDC hDC, UINT action )
1040 {
1041     RECT rc;
1042     HBRUSH hBrush;
1043     HFONT hFont;
1044     LONG state = get_button_state( hwnd );
1045
1046     if (action == ODA_SELECT) return;
1047
1048     GetClientRect( hwnd, &rc);
1049
1050     if ((hFont = get_button_font( hwnd ))) SelectObject( hDC, hFont );
1051
1052     hBrush = SendMessageW( GetParent(hwnd), WM_CTLCOLORBTN, hDC, (LPARAM)hwnd );
1053     if (!hBrush) /* did the app forget to call defwindowproc ? */
1054         hBrush = DefWindowProcW( GetParent(hwnd), WM_CTLCOLORBTN, hDC, (LPARAM)hwnd );
1055
1056     FillRect( hDC, &rc, hBrush );
1057     if ((action == ODA_FOCUS) ||
1058         ((action == ODA_DRAWENTIRE) && (state & BUTTON_HASFOCUS)))
1059         DrawFocusRect( hDC, &rc );
1060 }
1061
1062
1063 /**********************************************************************
1064  *       Ownerdrawn Button Functions
1065  */
1066
1067 static void OB_Paint( HWND hwnd, HDC hDC, UINT action )
1068 {
1069     LONG state = get_button_state( hwnd );
1070     DRAWITEMSTRUCT dis;
1071     HRGN clipRegion;
1072     RECT clipRect;
1073     UINT id = GetWindowLongA( hwnd, GWL_ID );
1074
1075     dis.CtlType    = ODT_BUTTON;
1076     dis.CtlID      = id;
1077     dis.itemID     = 0;
1078     dis.itemAction = action;
1079     dis.itemState  = ((state & BUTTON_HASFOCUS) ? ODS_FOCUS : 0) |
1080                      ((state & BUTTON_HIGHLIGHTED) ? ODS_SELECTED : 0) |
1081                      (IsWindowEnabled(hwnd) ? 0: ODS_DISABLED);
1082     dis.hwndItem   = hwnd;
1083     dis.hDC        = hDC;
1084     dis.itemData   = 0;
1085     GetClientRect( hwnd, &dis.rcItem );
1086
1087     clipRegion = CreateRectRgnIndirect(&dis.rcItem);
1088     if (GetClipRgn(hDC, clipRegion) != 1)
1089     {
1090         DeleteObject(clipRegion);
1091         clipRegion=(HRGN)NULL;
1092     }
1093     clipRect = dis.rcItem;
1094     DPtoLP(hDC, (LPPOINT) &clipRect, 2);
1095     IntersectClipRect(hDC, clipRect.left,  clipRect.top, clipRect.right, clipRect.bottom);
1096
1097     SetBkColor( hDC, GetSysColor( COLOR_BTNFACE ) );
1098     SendMessageW( GetParent(hwnd), WM_DRAWITEM, id, (LPARAM)&dis );
1099     SelectClipRgn(hDC, clipRegion);
1100 }
1101