820e56f8291eab5e8da3db24c42dea6831dd2a77
[reactos.git] / reactos / dll / win32 / comctl32 / 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
20 *
21 * NOTES
22 *
23 * This code was audited for completeness against the documented features
24 * of Comctl32.dll version 6.0 on Oct. 3, 2004, by Dimitrie O. Paun.
25 *
26 * Unless otherwise noted, we believe this code to be complete, as per
27 * the specification mentioned above.
28 * If you discover missing features, or bugs, please note them below.
29 *
30 * TODO
31 * Styles
32 * - BS_NOTIFY: is it complete?
33 * - BS_RIGHTBUTTON: same as BS_LEFTTEXT
34 *
35 * Messages
36 * - WM_CHAR: Checks a (manual or automatic) check box on '+' or '=', clears it on '-' key.
37 * - WM_SETFOCUS: For (manual or automatic) radio buttons, send the parent window BN_CLICKED
38 * - WM_NCCREATE: Turns any BS_OWNERDRAW button into a BS_PUSHBUTTON button.
39 * - WM_SYSKEYUP
40 * - BCM_GETIDEALSIZE
41 * - BCM_GETIMAGELIST
42 * - BCM_GETTEXTMARGIN
43 * - BCM_SETIMAGELIST
44 * - BCM_SETTEXTMARGIN
45 *
46 * Notifications
47 * - BCN_HOTITEMCHANGE
48 * - BN_DISABLE
49 * - BN_PUSHED/BN_HILITE
50 * + BN_KILLFOCUS: is it OK?
51 * - BN_PAINT
52 * + BN_SETFOCUS: is it OK?
53 * - BN_UNPUSHED/BN_UNHILITE
54 * - NM_CUSTOMDRAW
55 *
56 * Structures/Macros/Definitions
57 * - BUTTON_IMAGELIST
58 * - NMBCHOTITEM
59 * - Button_GetIdealSize
60 * - Button_GetImageList
61 * - Button_GetTextMargin
62 * - Button_SetImageList
63 * - Button_SetTextMargin
64 */
65 #include "comctl32.h"
66
67 #include <wine/debug.h>
68 WINE_DEFAULT_DEBUG_CHANNEL(button);
69
70 /* GetWindowLong offsets for window extra information */
71 #define STATE_GWL_OFFSET 0
72 #define HFONT_GWL_OFFSET (sizeof(LONG))
73 #define HIMAGE_GWL_OFFSET (HFONT_GWL_OFFSET+sizeof(HFONT))
74 #define UISTATE_GWL_OFFSET (HIMAGE_GWL_OFFSET+sizeof(HFONT))
75 #define NB_EXTRA_BYTES (UISTATE_GWL_OFFSET+sizeof(LONG))
76
77 /* undocumented flags */
78 #define BUTTON_NSTATES 0x0F
79 #define BUTTON_BTNPRESSED 0x40
80 #define BUTTON_UNKNOWN2 0x20
81 #define BUTTON_UNKNOWN3 0x10
82 #ifdef __REACTOS__
83 #define BUTTON_BMCLICK 0x100 // ReactOS Need to up to wine!
84 #endif
85
86 #define BUTTON_NOTIFY_PARENT(hWnd, code) \
87 do { /* Notify parent which has created this button control */ \
88 TRACE("notification " #code " sent to hwnd=%p\n", GetParent(hWnd)); \
89 SendMessageW(GetParent(hWnd), WM_COMMAND, \
90 MAKEWPARAM(GetWindowLongPtrW((hWnd),GWLP_ID), (code)), \
91 (LPARAM)(hWnd)); \
92 } while(0)
93
94 static UINT BUTTON_CalcLabelRect( HWND hwnd, HDC hdc, RECT *rc );
95 static void PB_Paint( HWND hwnd, HDC hDC, UINT action );
96 static void CB_Paint( HWND hwnd, HDC hDC, UINT action );
97 static void GB_Paint( HWND hwnd, HDC hDC, UINT action );
98 static void UB_Paint( HWND hwnd, HDC hDC, UINT action );
99 static void OB_Paint( HWND hwnd, HDC hDC, UINT action );
100 static void BUTTON_CheckAutoRadioButton( HWND hwnd );
101
102 #define MAX_BTN_TYPE 16
103
104 static const WORD maxCheckState[MAX_BTN_TYPE] =
105 {
106 BST_UNCHECKED, /* BS_PUSHBUTTON */
107 BST_UNCHECKED, /* BS_DEFPUSHBUTTON */
108 BST_CHECKED, /* BS_CHECKBOX */
109 BST_CHECKED, /* BS_AUTOCHECKBOX */
110 BST_CHECKED, /* BS_RADIOBUTTON */
111 BST_INDETERMINATE, /* BS_3STATE */
112 BST_INDETERMINATE, /* BS_AUTO3STATE */
113 BST_UNCHECKED, /* BS_GROUPBOX */
114 BST_UNCHECKED, /* BS_USERBUTTON */
115 BST_CHECKED, /* BS_AUTORADIOBUTTON */
116 BST_UNCHECKED, /* BS_PUSHBOX */
117 BST_UNCHECKED /* BS_OWNERDRAW */
118 };
119
120 typedef void (*pfPaint)( HWND hwnd, HDC hdc, UINT action );
121
122 static const pfPaint btnPaintFunc[MAX_BTN_TYPE] =
123 {
124 PB_Paint, /* BS_PUSHBUTTON */
125 PB_Paint, /* BS_DEFPUSHBUTTON */
126 CB_Paint, /* BS_CHECKBOX */
127 CB_Paint, /* BS_AUTOCHECKBOX */
128 CB_Paint, /* BS_RADIOBUTTON */
129 CB_Paint, /* BS_3STATE */
130 CB_Paint, /* BS_AUTO3STATE */
131 GB_Paint, /* BS_GROUPBOX */
132 UB_Paint, /* BS_USERBUTTON */
133 CB_Paint, /* BS_AUTORADIOBUTTON */
134 NULL, /* BS_PUSHBOX */
135 OB_Paint /* BS_OWNERDRAW */
136 };
137
138 /* The original code from user32 was kept in order to make it easier to bring changes from user32 */
139 #ifdef _USER32_
140 /*********************************************************************
141 * button class descriptor
142 */
143 static const WCHAR buttonW[] = {'B','u','t','t','o','n',0};
144 const struct builtin_class_descr BUTTON_builtin_class =
145 {
146 buttonW, /* name */
147 CS_DBLCLKS | CS_VREDRAW | CS_HREDRAW | CS_PARENTDC, /* style */
148 #ifdef __REACTOS__
149 ButtonWndProcA, /* procA */
150 ButtonWndProcW, /* procW */
151 #else
152 WINPROC_BUTTON, /* proc */
153 #endif
154 NB_EXTRA_BYTES, /* extra */
155 IDC_ARROW, /* cursor */
156 0 /* brush */
157 };
158
159
160 static inline LONG get_button_state( HWND hwnd )
161 {
162 return GetWindowLongPtrW( hwnd, STATE_GWL_OFFSET );
163 }
164
165 static inline void set_button_state( HWND hwnd, LONG state )
166 {
167 SetWindowLongPtrW( hwnd, STATE_GWL_OFFSET, state );
168 }
169
170 #ifdef __REACTOS__
171
172 static __inline void set_ui_state( HWND hwnd, LONG flags )
173 {
174 SetWindowLongPtrW( hwnd, UISTATE_GWL_OFFSET, flags );
175 }
176
177 static __inline LONG get_ui_state( HWND hwnd )
178 {
179 return GetWindowLongPtrW( hwnd, UISTATE_GWL_OFFSET );
180 }
181
182 #endif /* __REACTOS__ */
183
184 static inline HFONT get_button_font( HWND hwnd )
185 {
186 return (HFONT)GetWindowLongPtrW( hwnd, HFONT_GWL_OFFSET );
187 }
188
189 static inline void set_button_font( HWND hwnd, HFONT font )
190 {
191 SetWindowLongPtrW( hwnd, HFONT_GWL_OFFSET, (LONG_PTR)font );
192 }
193
194 static inline UINT get_button_type( LONG window_style )
195 {
196 return (window_style & BS_TYPEMASK);
197 }
198
199 /* paint a button of any type */
200 static inline void paint_button( HWND hwnd, LONG style, UINT action )
201 {
202 if (btnPaintFunc[style] && IsWindowVisible(hwnd))
203 {
204 HDC hdc = GetDC( hwnd );
205 btnPaintFunc[style]( hwnd, hdc, action );
206 ReleaseDC( hwnd, hdc );
207 }
208 }
209
210 #else
211
212 #define NtUserAlterWindowStyle SetWindowLongPtrW
213
214 static inline void _SetButtonData(HWND hwnd, PBUTTON_DATA data)
215 {
216 SetWindowLongPtrW( hwnd, 0, (LONG)data );
217 }
218
219 HRGN set_control_clipping( HDC hdc, const RECT *rect )
220 {
221 RECT rc = *rect;
222 HRGN hrgn = CreateRectRgn( 0, 0, 0, 0 );
223
224 if (GetClipRgn( hdc, hrgn ) != 1)
225 {
226 DeleteObject( hrgn );
227 hrgn = 0;
228 }
229 DPtoLP( hdc, (POINT *)&rc, 2 );
230 if (GetLayout( hdc ) & LAYOUT_RTL) /* compensate for the shifting done by IntersectClipRect */
231 {
232 rc.left++;
233 rc.right++;
234 }
235 IntersectClipRect( hdc, rc.left, rc.top, rc.right, rc.bottom );
236 return hrgn;
237 }
238
239 BOOL BUTTON_PaintWithTheme(HTHEME theme, HWND hwnd, HDC hParamDC, LPARAM prfFlag);
240
241 static inline LONG_PTR get_button_image(HWND hwnd)
242 {
243 return _GetButtonData(hwnd)->image;
244 }
245
246 static inline LONG_PTR set_button_image(HWND hwnd, LONG_PTR image)
247 {
248 PBUTTON_DATA data = _GetButtonData(hwnd);
249 LONG_PTR ret = data->image;
250 data->image = image;
251 return ret;
252 }
253
254 static inline LONG get_button_state( HWND hwnd )
255 {
256 return _GetButtonData(hwnd)->state;
257 }
258
259 static inline void set_button_state( HWND hwnd, LONG state )
260 {
261 _GetButtonData(hwnd)->state = state;
262 }
263
264 static __inline void set_ui_state( HWND hwnd, LONG flags )
265 {
266 _GetButtonData(hwnd)->ui_state = flags;
267 }
268
269 static __inline LONG get_ui_state( HWND hwnd )
270 {
271 return _GetButtonData(hwnd)->ui_state;
272 }
273
274 static inline HFONT get_button_font( HWND hwnd )
275 {
276 return (HFONT)_GetButtonData(hwnd)->font;
277 }
278
279 static inline void set_button_font( HWND hwnd, HFONT font )
280 {
281 _GetButtonData(hwnd)->font = font;
282 }
283
284 static inline UINT get_button_type( LONG window_style )
285 {
286 return (window_style & BS_TYPEMASK);
287 }
288
289 /* paint a button of any type */
290 static inline void paint_button( HWND hwnd, LONG style, UINT action )
291 {
292 HTHEME theme = GetWindowTheme(hwnd);
293 RECT rc;
294 HDC hdc = GetDC( hwnd );
295 /* GetDC appears to give a dc with a clip rect that includes the whoe parent, not sure if it is correct or not. */
296 GetClientRect(hwnd, &rc);
297 IntersectClipRect (hdc, rc.left, rc. top, rc.right, rc.bottom);
298 if (theme && BUTTON_PaintWithTheme(theme, hwnd, hdc, 0))
299 {
300 ReleaseDC( hwnd, hdc );
301 return;
302 }
303 if (btnPaintFunc[style] && IsWindowVisible(hwnd))
304 {
305 btnPaintFunc[style]( hwnd, hdc, action );
306 }
307 ReleaseDC( hwnd, hdc );
308 }
309
310 #endif
311
312
313 /* retrieve the button text; returned buffer must be freed by caller */
314 static inline WCHAR *get_button_text( HWND hwnd )
315 {
316 INT len = 512;
317 WCHAR *buffer = HeapAlloc( GetProcessHeap(), 0, (len + 1) * sizeof(WCHAR) );
318 if (buffer) InternalGetWindowText( hwnd, buffer, len + 1 );
319 return buffer;
320 }
321
322 #ifdef __REACTOS__
323 /* Retrieve the UI state for the control */
324 static BOOL button_update_uistate(HWND hwnd, BOOL unicode)
325 {
326 LONG flags, prevflags;
327
328 if (unicode)
329 flags = DefWindowProcW(hwnd, WM_QUERYUISTATE, 0, 0);
330 else
331 flags = DefWindowProcA(hwnd, WM_QUERYUISTATE, 0, 0);
332
333 prevflags = get_ui_state(hwnd);
334
335 if (prevflags != flags)
336 {
337 set_ui_state(hwnd, flags);
338 return TRUE;
339 }
340
341 return FALSE;
342 }
343 #endif
344
345 /***********************************************************************
346 * ButtonWndProc_common
347 */
348 LRESULT WINAPI ButtonWndProc_common(HWND hWnd, UINT uMsg,
349 WPARAM wParam, LPARAM lParam, BOOL unicode )
350 {
351 RECT rect;
352 POINT pt;
353 LONG style = GetWindowLongPtrW( hWnd, GWL_STYLE );
354 UINT btn_type = get_button_type( style );
355 LONG state;
356 HANDLE oldHbitmap;
357 #if defined(__REACTOS__) && defined(_USER32_)
358 PWND pWnd;
359
360 pWnd = ValidateHwnd(hWnd);
361 if (pWnd)
362 {
363 if (!pWnd->fnid)
364 {
365 NtUserSetWindowFNID(hWnd, FNID_BUTTON);
366 }
367 else
368 {
369 if (pWnd->fnid != FNID_BUTTON)
370 {
371 ERR("Wrong window class for Button! fnId 0x%x\n",pWnd->fnid);
372 return 0;
373 }
374 }
375 }
376 else
377 return 0;
378 #else
379 if (!IsWindow( hWnd )) return 0;
380 #endif
381
382 pt.x = (short)LOWORD(lParam);
383 pt.y = (short)HIWORD(lParam);
384
385 #ifndef _USER32_
386 switch (uMsg)
387 {
388 case WM_NCCREATE:
389 {
390 PBUTTON_DATA data = HeapAlloc( GetProcessHeap(), 0, sizeof(BUTTON_DATA) );
391 if (!data)
392 {
393 ERR("Failed to alloc internal button data\n");
394 return -1;
395 }
396
397 memset(data, 0, sizeof(BUTTON_DATA));
398 _SetButtonData(hWnd, data);
399 break;
400 }
401 case WM_NCDESTROY:
402 {
403 PBUTTON_DATA data = _GetButtonData(hWnd);
404 if (!data)
405 {
406 ERR("No data");
407 return 0;
408 }
409 HeapFree( GetProcessHeap(), 0, data );
410 _SetButtonData(hWnd, NULL);
411 }
412 case WM_CREATE:
413 OpenThemeData(hWnd, WC_BUTTONW);
414 break;
415 case WM_DESTROY:
416 CloseThemeData (GetWindowTheme(hWnd));
417 break;
418 case WM_THEMECHANGED:
419 CloseThemeData (GetWindowTheme(hWnd));
420 OpenThemeData(hWnd, WC_BUTTONW);
421 InvalidateRect(hWnd, NULL, FALSE);
422 break;
423 case WM_MOUSEHOVER:
424 {
425 int state = (int)SendMessageW(hWnd, BM_GETSTATE, 0, 0);
426 set_button_state(hWnd, state|BST_HOT);
427 InvalidateRect(hWnd, NULL, FALSE);
428 break;
429 }
430 case WM_MOUSELEAVE:
431 {
432 int state = (int)SendMessageW(hWnd, BM_GETSTATE, 0, 0);
433 set_button_state(hWnd, state&(~BST_HOT));
434 InvalidateRect(hWnd, NULL, FALSE);
435 break;
436 }
437 case WM_MOUSEMOVE:
438 {
439 TRACKMOUSEEVENT mouse_event;
440 mouse_event.cbSize = sizeof(TRACKMOUSEEVENT);
441 mouse_event.dwFlags = TME_QUERY;
442 if(!TrackMouseEvent(&mouse_event) || !(mouse_event.dwFlags&(TME_HOVER|TME_LEAVE)))
443 {
444 mouse_event.dwFlags = TME_HOVER|TME_LEAVE;
445 mouse_event.hwndTrack = hWnd;
446 mouse_event.dwHoverTime = 1;
447 TrackMouseEvent(&mouse_event);
448 }
449 break;
450 }
451 }
452
453 if (!_GetButtonData(hWnd))
454 {
455 ERR("no data!\n");
456 return unicode ? DefWindowProcW(hWnd, uMsg, wParam, lParam) :
457 DefWindowProcA(hWnd, uMsg, wParam, lParam);
458 }
459
460 #endif
461
462 switch (uMsg)
463 {
464 case WM_GETDLGCODE:
465 switch(btn_type)
466 {
467 case BS_USERBUTTON:
468 case BS_PUSHBUTTON: return DLGC_BUTTON | DLGC_UNDEFPUSHBUTTON;
469 case BS_DEFPUSHBUTTON: return DLGC_BUTTON | DLGC_DEFPUSHBUTTON;
470 case BS_RADIOBUTTON:
471 case BS_AUTORADIOBUTTON: return DLGC_BUTTON | DLGC_RADIOBUTTON;
472 case BS_GROUPBOX: return DLGC_STATIC;
473 default: return DLGC_BUTTON;
474 }
475
476 case WM_ENABLE:
477 paint_button( hWnd, btn_type, ODA_DRAWENTIRE );
478 break;
479
480 case WM_CREATE:
481 if (btn_type >= MAX_BTN_TYPE)
482 return -1; /* abort */
483
484 /* XP turns a BS_USERBUTTON into BS_PUSHBUTTON */
485 if (btn_type == BS_USERBUTTON )
486 {
487 style = (style & ~BS_TYPEMASK) | BS_PUSHBUTTON;
488 #ifdef __REACTOS__
489 NtUserAlterWindowStyle(hWnd, GWL_STYLE, style );
490 #else
491 WIN_SetStyle( hWnd, style, BS_TYPEMASK & ~style );
492 #endif
493 }
494 set_button_state( hWnd, BST_UNCHECKED );
495 #ifdef __REACTOS__
496 button_update_uistate( hWnd, unicode );
497 #endif
498 return 0;
499
500 #if defined(__REACTOS__) && defined(_USER32_)
501 case WM_NCDESTROY:
502 NtUserSetWindowFNID(hWnd, FNID_DESTROY);
503 case WM_DESTROY:
504 break;
505 #endif
506 case WM_ERASEBKGND:
507 if (btn_type == BS_OWNERDRAW)
508 {
509 HDC hdc = (HDC)wParam;
510 RECT rc;
511 HBRUSH hBrush;
512 HWND parent = GetParent(hWnd);
513 if (!parent) parent = hWnd;
514 #if defined(__REACTOS__) && defined(_USER32_)
515 hBrush = GetControlColor( parent, hWnd, hdc, WM_CTLCOLORBTN);
516 #else
517 hBrush = (HBRUSH)SendMessageW(parent, WM_CTLCOLORBTN, (WPARAM)hdc, (LPARAM)hWnd);
518 if (!hBrush) /* did the app forget to call defwindowproc ? */
519 hBrush = (HBRUSH)DefWindowProcW(parent, WM_CTLCOLORBTN,
520 (WPARAM)hdc, (LPARAM)hWnd);
521 #endif
522 GetClientRect(hWnd, &rc);
523 FillRect(hdc, &rc, hBrush);
524 }
525 return 1;
526
527 case WM_PRINTCLIENT:
528 case WM_PAINT:
529 {
530 PAINTSTRUCT ps;
531 HDC hdc = wParam ? (HDC)wParam : BeginPaint( hWnd, &ps );
532 #ifndef _USER32_
533 HTHEME theme = GetWindowTheme(hWnd);
534 if (theme && BUTTON_PaintWithTheme(theme, hWnd, hdc, uMsg == WM_PRINTCLIENT ? lParam : 0))
535 {
536 if ( !wParam ) EndPaint( hWnd, &ps );
537 return 0;
538 }
539 #endif
540 if (btnPaintFunc[btn_type])
541 {
542 int nOldMode = SetBkMode( hdc, OPAQUE );
543 (btnPaintFunc[btn_type])( hWnd, hdc, ODA_DRAWENTIRE );
544 SetBkMode(hdc, nOldMode); /* reset painting mode */
545 }
546 if ( !wParam ) EndPaint( hWnd, &ps );
547 break;
548 }
549
550 case WM_KEYDOWN:
551 if (wParam == VK_SPACE)
552 {
553 SendMessageW( hWnd, BM_SETSTATE, TRUE, 0 );
554 set_button_state( hWnd, get_button_state( hWnd ) | BUTTON_BTNPRESSED );
555 SetCapture( hWnd );
556 }
557 break;
558
559 case WM_LBUTTONDBLCLK:
560 if(style & BS_NOTIFY ||
561 btn_type == BS_RADIOBUTTON ||
562 btn_type == BS_USERBUTTON ||
563 btn_type == BS_OWNERDRAW)
564 {
565 BUTTON_NOTIFY_PARENT(hWnd, BN_DOUBLECLICKED);
566 break;
567 }
568 /* fall through */
569 case WM_LBUTTONDOWN:
570 SetCapture( hWnd );
571 SetFocus( hWnd );
572 set_button_state( hWnd, get_button_state( hWnd ) | BUTTON_BTNPRESSED );
573 SendMessageW( hWnd, BM_SETSTATE, TRUE, 0 );
574 break;
575
576 case WM_KEYUP:
577 if (wParam != VK_SPACE)
578 break;
579 /* fall through */
580 case WM_LBUTTONUP:
581 #ifdef _REACTOS_
582 BOOL TellParent = FALSE; //// ReactOS see note below.
583 #endif
584 state = get_button_state( hWnd );
585 if (!(state & BUTTON_BTNPRESSED)) break;
586 state &= BUTTON_NSTATES;
587 set_button_state( hWnd, state );
588 if (!(state & BST_PUSHED))
589 {
590 ReleaseCapture();
591 break;
592 }
593 SendMessageW( hWnd, BM_SETSTATE, FALSE, 0 );
594 GetClientRect( hWnd, &rect );
595 if (uMsg == WM_KEYUP || PtInRect( &rect, pt ))
596 {
597 state = get_button_state( hWnd );
598 switch(btn_type)
599 {
600 case BS_AUTOCHECKBOX:
601 SendMessageW( hWnd, BM_SETCHECK, !(state & BST_CHECKED), 0 );
602 break;
603 case BS_AUTORADIOBUTTON:
604 SendMessageW( hWnd, BM_SETCHECK, TRUE, 0 );
605 break;
606 case BS_AUTO3STATE:
607 SendMessageW( hWnd, BM_SETCHECK,
608 (state & BST_INDETERMINATE) ? 0 : ((state & 3) + 1), 0 );
609 break;
610 }
611 #ifdef _REACTOS_
612 TellParent = TRUE; // <---- Fix CORE-10194, Notify parent after capture is released.
613 #else
614 ReleaseCapture();
615 BUTTON_NOTIFY_PARENT(hWnd, BN_CLICKED);
616 #endif
617 }
618 #ifdef _REACTOS_
619 ReleaseCapture();
620 if (TellParent) BUTTON_NOTIFY_PARENT(hWnd, BN_CLICKED);
621 #else
622 else
623 {
624 ReleaseCapture();
625 }
626 #endif
627 break;
628
629 case WM_CAPTURECHANGED:
630 TRACE("WM_CAPTURECHANGED %p\n", hWnd);
631 if (hWnd == (HWND)lParam) break;
632 state = get_button_state( hWnd );
633 if (state & BUTTON_BTNPRESSED)
634 {
635 state &= BUTTON_NSTATES;
636 set_button_state( hWnd, state );
637 if (state & BST_PUSHED) SendMessageW( hWnd, BM_SETSTATE, FALSE, 0 );
638 }
639 break;
640
641 case WM_MOUSEMOVE:
642 if ((wParam & MK_LBUTTON) && GetCapture() == hWnd)
643 {
644 GetClientRect( hWnd, &rect );
645 SendMessageW( hWnd, BM_SETSTATE, PtInRect(&rect, pt), 0 );
646 }
647 break;
648
649 case WM_SETTEXT:
650 {
651 /* Clear an old text here as Windows does */
652 //
653 // ReactOS Note :
654 // wine Bug: http://bugs.winehq.org/show_bug.cgi?id=25790
655 // Patch: http://source.winehq.org/patches/data/70889
656 // By: Alexander LAW, Replicate Windows behavior of WM_SETTEXT handler regarding WM_CTLCOLOR*
657 //
658 #ifdef __REACTOS__
659 if (style & WS_VISIBLE)
660 #else
661 if (IsWindowVisible(hWnd))
662 #endif
663 {
664 HDC hdc = GetDC(hWnd);
665 HBRUSH hbrush;
666 RECT client, rc;
667 HWND parent = GetParent(hWnd);
668 UINT message = (btn_type == BS_PUSHBUTTON ||
669 btn_type == BS_DEFPUSHBUTTON ||
670 btn_type == BS_PUSHLIKE ||
671 btn_type == BS_USERBUTTON ||
672 btn_type == BS_OWNERDRAW) ?
673 WM_CTLCOLORBTN : WM_CTLCOLORSTATIC;
674
675 if (!parent) parent = hWnd;
676 #if defined(__REACTOS__) && defined(_USER32_)
677 hbrush = GetControlColor(parent, hWnd, hdc, message);
678 #else
679 hbrush = (HBRUSH)SendMessageW(parent, message,
680 (WPARAM)hdc, (LPARAM)hWnd);
681 if (!hbrush) /* did the app forget to call DefWindowProc ? */
682 hbrush = (HBRUSH)DefWindowProcW(parent, message,
683 (WPARAM)hdc, (LPARAM)hWnd);
684 #endif
685
686 GetClientRect(hWnd, &client);
687 rc = client;
688 /* FIXME: check other BS_* handlers */
689 if (btn_type == BS_GROUPBOX)
690 InflateRect(&rc, -7, 1); /* GB_Paint does this */
691 BUTTON_CalcLabelRect(hWnd, hdc, &rc);
692 /* Clip by client rect bounds */
693 if (rc.right > client.right) rc.right = client.right;
694 if (rc.bottom > client.bottom) rc.bottom = client.bottom;
695 FillRect(hdc, &rc, hbrush);
696 ReleaseDC(hWnd, hdc);
697 }
698
699 if (unicode) DefWindowProcW( hWnd, WM_SETTEXT, wParam, lParam );
700 else DefWindowProcA( hWnd, WM_SETTEXT, wParam, lParam );
701 if (btn_type == BS_GROUPBOX) /* Yes, only for BS_GROUPBOX */
702 InvalidateRect( hWnd, NULL, TRUE );
703 else
704 paint_button( hWnd, btn_type, ODA_DRAWENTIRE );
705 return 1; /* success. FIXME: check text length */
706 }
707
708 case WM_SETFONT:
709 set_button_font( hWnd, (HFONT)wParam );
710 if (lParam) InvalidateRect(hWnd, NULL, TRUE);
711 break;
712
713 case WM_GETFONT:
714 return (LRESULT)get_button_font( hWnd );
715
716 case WM_SETFOCUS:
717 TRACE("WM_SETFOCUS %p\n",hWnd);
718 set_button_state( hWnd, get_button_state(hWnd) | BST_FOCUS );
719 #ifndef _USER32_
720 if (btn_type != BS_OWNERDRAW)
721 InvalidateRect(hWnd, NULL, FALSE);
722 else
723 #endif
724 paint_button( hWnd, btn_type, ODA_FOCUS );
725 if (style & BS_NOTIFY)
726 BUTTON_NOTIFY_PARENT(hWnd, BN_SETFOCUS);
727 break;
728
729 case WM_KILLFOCUS:
730 TRACE("WM_KILLFOCUS %p\n",hWnd);
731 state = get_button_state( hWnd );
732 set_button_state( hWnd, state & ~BST_FOCUS );
733 #ifdef _USER32_
734 paint_button( hWnd, btn_type, ODA_FOCUS );
735 #endif
736
737 if ((state & BUTTON_BTNPRESSED) && GetCapture() == hWnd)
738 ReleaseCapture();
739 if (style & BS_NOTIFY)
740 BUTTON_NOTIFY_PARENT(hWnd, BN_KILLFOCUS);
741
742 InvalidateRect( hWnd, NULL, FALSE );
743 break;
744
745 case WM_SYSCOLORCHANGE:
746 InvalidateRect( hWnd, NULL, FALSE );
747 break;
748
749 case BM_SETSTYLE:
750 btn_type = wParam & BS_TYPEMASK;
751 style = (style & ~BS_TYPEMASK) | btn_type;
752 #ifdef __REACTOS__
753 NtUserAlterWindowStyle(hWnd, GWL_STYLE, style);
754 #else
755 WIN_SetStyle( hWnd, style, BS_TYPEMASK & ~style );
756 #endif
757
758 /* Only redraw if lParam flag is set.*/
759 if (lParam)
760 InvalidateRect( hWnd, NULL, TRUE );
761
762 break;
763
764 case BM_CLICK:
765 #ifdef __REACTOS__
766 state = get_button_state(hWnd);
767 if (state & BUTTON_BMCLICK)
768 break;
769 set_button_state(hWnd, state | BUTTON_BMCLICK); // Tracked in STATE_GWL_OFFSET.
770 #endif
771 SendMessageW( hWnd, WM_LBUTTONDOWN, 0, 0 );
772 SendMessageW( hWnd, WM_LBUTTONUP, 0, 0 );
773 #ifdef __REACTOS__
774 state = get_button_state(hWnd);
775 if (!(state & BUTTON_BMCLICK)) break;
776 state &= ~BUTTON_BMCLICK;
777 set_button_state(hWnd, state);
778 #endif
779 break;
780
781 case BM_SETIMAGE:
782 /* Check that image format matches button style */
783 switch (style & (BS_BITMAP|BS_ICON))
784 {
785 case BS_BITMAP:
786 if (wParam != IMAGE_BITMAP) return 0;
787 break;
788 case BS_ICON:
789 if (wParam != IMAGE_ICON) return 0;
790 break;
791 default:
792 return 0;
793 }
794 #ifdef _USER32_
795 oldHbitmap = (HBITMAP)SetWindowLongPtrW( hWnd, HIMAGE_GWL_OFFSET, lParam );
796 #else
797 oldHbitmap = (HBITMAP)set_button_image(hWnd, lParam );
798 #endif
799 InvalidateRect( hWnd, NULL, FALSE );
800 return (LRESULT)oldHbitmap;
801
802 case BM_GETIMAGE:
803 #ifdef _USER32_
804 return GetWindowLongPtrW( hWnd, HIMAGE_GWL_OFFSET );
805 #else
806 return get_button_image(hWnd);
807 #endif
808
809 case BM_GETCHECK:
810 return get_button_state( hWnd ) & 3;
811
812 case BM_SETCHECK:
813 if (wParam > maxCheckState[btn_type]) wParam = maxCheckState[btn_type];
814 state = get_button_state( hWnd );
815 if ((btn_type == BS_RADIOBUTTON) || (btn_type == BS_AUTORADIOBUTTON))
816 {
817 #ifdef __REACTOS__
818 if (wParam) style |= WS_TABSTOP;
819 else style &= ~WS_TABSTOP;
820 NtUserAlterWindowStyle(hWnd, GWL_STYLE, style);
821 #else
822 if (wParam) WIN_SetStyle( hWnd, WS_TABSTOP, 0 );
823 else WIN_SetStyle( hWnd, 0, WS_TABSTOP );
824 #endif
825 }
826 if ((state & 3) != wParam)
827 {
828 set_button_state( hWnd, (state & ~3) | wParam );
829 #ifdef _USER32
830 paint_button( hWnd, btn_type, ODA_SELECT );
831 #else
832 InvalidateRect(hWnd, NULL, FALSE);
833 #endif
834 }
835 if ((btn_type == BS_AUTORADIOBUTTON) && (wParam == BST_CHECKED) && (style & WS_CHILD))
836 BUTTON_CheckAutoRadioButton( hWnd );
837 break;
838
839 case BM_GETSTATE:
840 return get_button_state( hWnd );
841
842 case BM_SETSTATE:
843 state = get_button_state( hWnd );
844 if (wParam)
845 set_button_state( hWnd, state | BST_PUSHED );
846 else
847 set_button_state( hWnd, state & ~BST_PUSHED );
848
849 #ifdef _USER32_
850 paint_button( hWnd, btn_type, ODA_SELECT );
851 #else
852 InvalidateRect(hWnd, NULL, FALSE);
853 #endif
854 break;
855
856 #ifdef __REACTOS__
857 case WM_UPDATEUISTATE:
858 if (unicode)
859 DefWindowProcW(hWnd, uMsg, wParam, lParam);
860 else
861 DefWindowProcA(hWnd, uMsg, wParam, lParam);
862
863 if (button_update_uistate(hWnd, unicode))
864 paint_button( hWnd, btn_type, ODA_DRAWENTIRE );
865 break;
866 #endif
867
868 case WM_NCHITTEST:
869 if(btn_type == BS_GROUPBOX) return HTTRANSPARENT;
870 /* fall through */
871 default:
872 return unicode ? DefWindowProcW(hWnd, uMsg, wParam, lParam) :
873 DefWindowProcA(hWnd, uMsg, wParam, lParam);
874 }
875 return 0;
876 }
877
878 #ifdef __REACTOS__
879
880 /***********************************************************************
881 * ButtonWndProcW
882 * The button window procedure. This is just a wrapper which locks
883 * the passed HWND and calls the real window procedure (with a WND*
884 * pointer pointing to the locked windowstructure).
885 */
886 LRESULT WINAPI ButtonWndProcW(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
887 {
888 if (!IsWindow(hWnd)) return 0;
889 return ButtonWndProc_common(hWnd, uMsg, wParam, lParam, TRUE);
890 }
891
892 /***********************************************************************
893 * ButtonWndProcA
894 */
895 LRESULT WINAPI ButtonWndProcA(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
896 {
897 if (!IsWindow(hWnd)) return 0;
898 return ButtonWndProc_common(hWnd, uMsg, wParam, lParam, FALSE);
899 }
900
901 #endif /* __REACTOS__ */
902
903 /**********************************************************************
904 * Convert button styles to flags used by DrawText.
905 */
906 static UINT BUTTON_BStoDT( DWORD style, DWORD ex_style )
907 {
908 UINT dtStyle = DT_NOCLIP; /* We use SelectClipRgn to limit output */
909
910 /* "Convert" pushlike buttons to pushbuttons */
911 if (style & BS_PUSHLIKE)
912 style &= ~BS_TYPEMASK;
913
914 if (!(style & BS_MULTILINE))
915 dtStyle |= DT_SINGLELINE;
916 else
917 dtStyle |= DT_WORDBREAK;
918
919 switch (style & BS_CENTER)
920 {
921 case BS_LEFT: /* DT_LEFT is 0 */ break;
922 case BS_RIGHT: dtStyle |= DT_RIGHT; break;
923 case BS_CENTER: dtStyle |= DT_CENTER; break;
924 default:
925 /* Pushbutton's text is centered by default */
926 if (get_button_type(style) <= BS_DEFPUSHBUTTON) dtStyle |= DT_CENTER;
927 /* all other flavours have left aligned text */
928 }
929
930 if (ex_style & WS_EX_RIGHT) dtStyle = DT_RIGHT | (dtStyle & ~(DT_LEFT | DT_CENTER));
931
932 /* DrawText ignores vertical alignment for multiline text,
933 * but we use these flags to align label manually.
934 */
935 if (get_button_type(style) != BS_GROUPBOX)
936 {
937 switch (style & BS_VCENTER)
938 {
939 case BS_TOP: /* DT_TOP is 0 */ break;
940 case BS_BOTTOM: dtStyle |= DT_BOTTOM; break;
941 case BS_VCENTER: /* fall through */
942 default: dtStyle |= DT_VCENTER; break;
943 }
944 }
945 else
946 /* GroupBox's text is always single line and is top aligned. */
947 dtStyle |= DT_SINGLELINE;
948
949 return dtStyle;
950 }
951
952 /**********************************************************************
953 * BUTTON_CalcLabelRect
954 *
955 * Calculates label's rectangle depending on button style.
956 *
957 * Returns flags to be passed to DrawText.
958 * Calculated rectangle doesn't take into account button state
959 * (pushed, etc.). If there is nothing to draw (no text/image) output
960 * rectangle is empty, and return value is (UINT)-1.
961 */
962 static UINT BUTTON_CalcLabelRect(HWND hwnd, HDC hdc, RECT *rc)
963 {
964 LONG style = GetWindowLongPtrW( hwnd, GWL_STYLE );
965 LONG ex_style = GetWindowLongPtrW( hwnd, GWL_EXSTYLE );
966 WCHAR *text;
967 ICONINFO iconInfo;
968 BITMAP bm;
969 UINT dtStyle = BUTTON_BStoDT( style, ex_style );
970 RECT r = *rc;
971 INT n;
972
973 /* Calculate label rectangle according to label type */
974 switch (style & (BS_ICON|BS_BITMAP))
975 {
976 case BS_TEXT:
977 {
978 HFONT hFont, hPrevFont = 0;
979
980 if (!(text = get_button_text( hwnd ))) goto empty_rect;
981 if (!text[0])
982 {
983 HeapFree( GetProcessHeap(), 0, text );
984 goto empty_rect;
985 }
986
987 if ((hFont = get_button_font( hwnd ))) hPrevFont = SelectObject( hdc, hFont );
988 DrawTextW(hdc, text, -1, &r, dtStyle | DT_CALCRECT);
989 if (hPrevFont) SelectObject( hdc, hPrevFont );
990 HeapFree( GetProcessHeap(), 0, text );
991 #ifdef __REACTOS__
992 if (get_ui_state(hwnd) & UISF_HIDEACCEL)
993 dtStyle |= DT_HIDEPREFIX;
994 #endif
995 break;
996 }
997
998 case BS_ICON:
999 #ifdef _USER32_
1000 if (!GetIconInfo((HICON)GetWindowLongPtrW( hwnd, HIMAGE_GWL_OFFSET ), &iconInfo))
1001 #else
1002 if (!GetIconInfo((HICON)get_button_image(hwnd), &iconInfo))
1003 #endif
1004 goto empty_rect;
1005
1006 GetObjectW (iconInfo.hbmColor, sizeof(BITMAP), &bm);
1007
1008 r.right = r.left + bm.bmWidth;
1009 r.bottom = r.top + bm.bmHeight;
1010
1011 DeleteObject(iconInfo.hbmColor);
1012 DeleteObject(iconInfo.hbmMask);
1013 break;
1014
1015 case BS_BITMAP:
1016 #ifdef _USER32_
1017 if (!GetObjectW( (HANDLE)GetWindowLongPtrW( hwnd, HIMAGE_GWL_OFFSET ), sizeof(BITMAP), &bm))
1018 #else
1019 if (!GetObjectW( (HANDLE)get_button_image(hwnd), sizeof(BITMAP), &bm))
1020 #endif
1021 goto empty_rect;
1022
1023 r.right = r.left + bm.bmWidth;
1024 r.bottom = r.top + bm.bmHeight;
1025 break;
1026
1027 default:
1028 empty_rect:
1029 rc->right = r.left;
1030 rc->bottom = r.top;
1031 return (UINT)-1;
1032 }
1033
1034 /* Position label inside bounding rectangle according to
1035 * alignment flags. (calculated rect is always left-top aligned).
1036 * If label is aligned to any side - shift label in opposite
1037 * direction to leave extra space for focus rectangle.
1038 */
1039 switch (dtStyle & (DT_CENTER|DT_RIGHT))
1040 {
1041 case DT_LEFT: r.left++; r.right++; break;
1042 case DT_CENTER: n = r.right - r.left;
1043 r.left = rc->left + ((rc->right - rc->left) - n) / 2;
1044 r.right = r.left + n; break;
1045 case DT_RIGHT: n = r.right - r.left;
1046 r.right = rc->right - 1;
1047 r.left = r.right - n;
1048 break;
1049 }
1050
1051 switch (dtStyle & (DT_VCENTER|DT_BOTTOM))
1052 {
1053 case DT_TOP: r.top++; r.bottom++; break;
1054 case DT_VCENTER: n = r.bottom - r.top;
1055 r.top = rc->top + ((rc->bottom - rc->top) - n) / 2;
1056 r.bottom = r.top + n; break;
1057 case DT_BOTTOM: n = r.bottom - r.top;
1058 r.bottom = rc->bottom - 1;
1059 r.top = r.bottom - n;
1060 break;
1061 }
1062
1063 *rc = r;
1064 return dtStyle;
1065 }
1066
1067
1068 /**********************************************************************
1069 * BUTTON_DrawTextCallback
1070 *
1071 * Callback function used by DrawStateW function.
1072 */
1073 static BOOL CALLBACK BUTTON_DrawTextCallback(HDC hdc, LPARAM lp, WPARAM wp, int cx, int cy)
1074 {
1075 RECT rc;
1076
1077 SetRect(&rc, 0, 0, cx, cy);
1078 DrawTextW(hdc, (LPCWSTR)lp, -1, &rc, (UINT)wp);
1079 return TRUE;
1080 }
1081
1082
1083 /**********************************************************************
1084 * BUTTON_DrawLabel
1085 *
1086 * Common function for drawing button label.
1087 */
1088 static void BUTTON_DrawLabel(HWND hwnd, HDC hdc, UINT dtFlags, const RECT *rc)
1089 {
1090 DRAWSTATEPROC lpOutputProc = NULL;
1091 LPARAM lp;
1092 WPARAM wp = 0;
1093 HBRUSH hbr = 0;
1094 UINT flags = IsWindowEnabled(hwnd) ? DSS_NORMAL : DSS_DISABLED;
1095 LONG state = get_button_state( hwnd );
1096 LONG style = GetWindowLongPtrW( hwnd, GWL_STYLE );
1097 WCHAR *text = NULL;
1098
1099 /* FIXME: To draw disabled label in Win31 look-and-feel, we probably
1100 * must use DSS_MONO flag and COLOR_GRAYTEXT brush (or maybe DSS_UNION).
1101 * I don't have Win31 on hand to verify that, so I leave it as is.
1102 */
1103
1104 if ((style & BS_PUSHLIKE) && (state & BST_INDETERMINATE))
1105 {
1106 hbr = GetSysColorBrush(COLOR_GRAYTEXT);
1107 flags |= DSS_MONO;
1108 }
1109
1110 switch (style & (BS_ICON|BS_BITMAP))
1111 {
1112 case BS_TEXT:
1113 /* DST_COMPLEX -- is 0 */
1114 lpOutputProc = BUTTON_DrawTextCallback;
1115 if (!(text = get_button_text( hwnd ))) return;
1116 lp = (LPARAM)text;
1117 wp = (WPARAM)dtFlags;
1118
1119 #ifdef __REACTOS__
1120 if (dtFlags & DT_HIDEPREFIX)
1121 flags |= DSS_HIDEPREFIX;
1122 #endif
1123 break;
1124
1125 case BS_ICON:
1126 flags |= DST_ICON;
1127 #ifdef _USER32_
1128 lp = GetWindowLongPtrW( hwnd, HIMAGE_GWL_OFFSET );
1129 #else
1130 lp = get_button_image(hwnd);
1131 #endif
1132 break;
1133
1134 case BS_BITMAP:
1135 flags |= DST_BITMAP;
1136 #ifdef _USER32_
1137 lp = GetWindowLongPtrW( hwnd, HIMAGE_GWL_OFFSET );
1138 #else
1139 lp = get_button_image(hwnd);
1140 #endif
1141 break;
1142
1143 default:
1144 return;
1145 }
1146
1147 DrawStateW(hdc, hbr, lpOutputProc, lp, wp, rc->left, rc->top,
1148 rc->right - rc->left, rc->bottom - rc->top, flags);
1149 HeapFree( GetProcessHeap(), 0, text );
1150 }
1151
1152 /**********************************************************************
1153 * Push Button Functions
1154 */
1155 static void PB_Paint( HWND hwnd, HDC hDC, UINT action )
1156 {
1157 RECT rc, r;
1158 UINT dtFlags, uState;
1159 HPEN hOldPen;
1160 HBRUSH hOldBrush;
1161 INT oldBkMode;
1162 COLORREF oldTxtColor;
1163 HFONT hFont;
1164 LONG state = get_button_state( hwnd );
1165 LONG style = GetWindowLongPtrW( hwnd, GWL_STYLE );
1166 BOOL pushedState = (state & BST_PUSHED);
1167 HWND parent;
1168 HRGN hrgn;
1169
1170 GetClientRect( hwnd, &rc );
1171
1172 /* Send WM_CTLCOLOR to allow changing the font (the colors are fixed) */
1173 if ((hFont = get_button_font( hwnd ))) SelectObject( hDC, hFont );
1174 parent = GetParent(hwnd);
1175 if (!parent) parent = hwnd;
1176 #if defined(__REACTOS__) && defined(_USER32_)
1177 GetControlColor( parent, hwnd, hDC, WM_CTLCOLORBTN);
1178 #else
1179 SendMessageW( parent, WM_CTLCOLORBTN, (WPARAM)hDC, (LPARAM)hwnd );
1180 #endif
1181
1182 hrgn = set_control_clipping( hDC, &rc );
1183 #ifdef __REACTOS__
1184 hOldPen = SelectObject(hDC, GetStockObject(DC_PEN));
1185 SetDCPenColor(hDC, GetSysColor(COLOR_WINDOWFRAME));
1186 #else
1187 hOldPen = SelectObject(hDC, SYSCOLOR_GetPen(COLOR_WINDOWFRAME));
1188 #endif
1189 hOldBrush = SelectObject(hDC,GetSysColorBrush(COLOR_BTNFACE));
1190 oldBkMode = SetBkMode(hDC, TRANSPARENT);
1191
1192 if (get_button_type(style) == BS_DEFPUSHBUTTON)
1193 {
1194 if (action != ODA_FOCUS)
1195 Rectangle(hDC, rc.left, rc.top, rc.right, rc.bottom);
1196 InflateRect( &rc, -1, -1 );
1197 }
1198
1199 /* completely skip the drawing if only focus has changed */
1200 if (action == ODA_FOCUS) goto draw_focus;
1201
1202 uState = DFCS_BUTTONPUSH;
1203
1204 if (style & BS_FLAT)
1205 uState |= DFCS_MONO;
1206 else if (pushedState)
1207 {
1208 if (get_button_type(style) == BS_DEFPUSHBUTTON )
1209 uState |= DFCS_FLAT;
1210 else
1211 uState |= DFCS_PUSHED;
1212 }
1213
1214 if (state & (BST_CHECKED | BST_INDETERMINATE))
1215 uState |= DFCS_CHECKED;
1216
1217 DrawFrameControl( hDC, &rc, DFC_BUTTON, uState );
1218
1219 /* draw button label */
1220 r = rc;
1221 dtFlags = BUTTON_CalcLabelRect(hwnd, hDC, &r);
1222
1223 if (dtFlags == (UINT)-1L)
1224 goto cleanup;
1225
1226 if (pushedState)
1227 OffsetRect(&r, 1, 1);
1228
1229 oldTxtColor = SetTextColor( hDC, GetSysColor(COLOR_BTNTEXT) );
1230
1231 BUTTON_DrawLabel(hwnd, hDC, dtFlags, &r);
1232
1233 SetTextColor( hDC, oldTxtColor );
1234
1235 draw_focus:
1236 if (action == ODA_FOCUS || (state & BST_FOCUS))
1237 {
1238 #ifdef __REACTOS__
1239 if (!(get_ui_state(hwnd) & UISF_HIDEFOCUS))
1240 {
1241 #endif
1242 InflateRect( &rc, -2, -2 );
1243 DrawFocusRect( hDC, &rc );
1244 #ifdef __REACTOS__
1245 }
1246 #endif
1247 }
1248
1249 cleanup:
1250 SelectObject( hDC, hOldPen );
1251 SelectObject( hDC, hOldBrush );
1252 SetBkMode(hDC, oldBkMode);
1253 SelectClipRgn( hDC, hrgn );
1254 if (hrgn) DeleteObject( hrgn );
1255 }
1256
1257 /**********************************************************************
1258 * Check Box & Radio Button Functions
1259 */
1260
1261 static void CB_Paint( HWND hwnd, HDC hDC, UINT action )
1262 {
1263 RECT rbox, rtext, client;
1264 HBRUSH hBrush;
1265 int delta, text_offset, checkBoxWidth, checkBoxHeight;
1266 UINT dtFlags;
1267 HFONT hFont;
1268 LONG state = get_button_state( hwnd );
1269 LONG style = GetWindowLongPtrW( hwnd, GWL_STYLE );
1270 LONG ex_style = GetWindowLongW( hwnd, GWL_EXSTYLE );
1271 HWND parent;
1272 HRGN hrgn;
1273
1274 if (style & BS_PUSHLIKE)
1275 {
1276 PB_Paint( hwnd, hDC, action );
1277 return;
1278 }
1279
1280 GetClientRect(hwnd, &client);
1281 rbox = rtext = client;
1282
1283 checkBoxWidth = 12 * GetDeviceCaps( hDC, LOGPIXELSX ) / 96 + 1;
1284 checkBoxHeight = 12 * GetDeviceCaps( hDC, LOGPIXELSY ) / 96 + 1;
1285
1286 if ((hFont = get_button_font( hwnd ))) SelectObject( hDC, hFont );
1287 GetCharWidthW( hDC, '0', '0', &text_offset );
1288 text_offset /= 2;
1289
1290 parent = GetParent(hwnd);
1291 if (!parent) parent = hwnd;
1292 #if defined(__REACTOS__) && defined(_USER32_)
1293 hBrush = GetControlColor(parent, hwnd, hDC, WM_CTLCOLORSTATIC);
1294 #else
1295 hBrush = (HBRUSH)SendMessageW(parent, WM_CTLCOLORSTATIC,
1296 (WPARAM)hDC, (LPARAM)hwnd);
1297 if (!hBrush) /* did the app forget to call defwindowproc ? */
1298 hBrush = (HBRUSH)DefWindowProcW(parent, WM_CTLCOLORSTATIC,
1299 (WPARAM)hDC, (LPARAM)hwnd );
1300 #endif
1301 hrgn = set_control_clipping( hDC, &client );
1302
1303 if (style & BS_LEFTTEXT || ex_style & WS_EX_RIGHT)
1304 {
1305 /* magic +4 is what CTL3D expects */
1306
1307 rtext.right -= checkBoxWidth + text_offset;;
1308 rbox.left = rbox.right - checkBoxWidth;
1309 }
1310 else
1311 {
1312 rtext.left += checkBoxWidth + text_offset;;
1313 rbox.right = checkBoxWidth;
1314 }
1315
1316 /* Since WM_ERASEBKGND does nothing, first prepare background */
1317 if (action == ODA_SELECT) FillRect( hDC, &rbox, hBrush );
1318 if (action == ODA_DRAWENTIRE) FillRect( hDC, &client, hBrush );
1319
1320 /* Draw label */
1321 client = rtext;
1322 dtFlags = BUTTON_CalcLabelRect(hwnd, hDC, &rtext);
1323
1324 /* Only adjust rbox when rtext is valid */
1325 if (dtFlags != (UINT)-1L)
1326 {
1327 rbox.top = rtext.top;
1328 rbox.bottom = rtext.bottom;
1329 }
1330
1331 /* Draw the check-box bitmap */
1332 if (action == ODA_DRAWENTIRE || action == ODA_SELECT)
1333 {
1334 UINT flags;
1335
1336 if ((get_button_type(style) == BS_RADIOBUTTON) ||
1337 (get_button_type(style) == BS_AUTORADIOBUTTON)) flags = DFCS_BUTTONRADIO;
1338 else if (state & BST_INDETERMINATE) flags = DFCS_BUTTON3STATE;
1339 else flags = DFCS_BUTTONCHECK;
1340
1341 if (state & (BST_CHECKED | BST_INDETERMINATE)) flags |= DFCS_CHECKED;
1342 if (state & BST_PUSHED) flags |= DFCS_PUSHED;
1343
1344 if (style & WS_DISABLED) flags |= DFCS_INACTIVE;
1345
1346 /* rbox must have the correct height */
1347 delta = rbox.bottom - rbox.top - checkBoxHeight;
1348
1349 if (style & BS_TOP) {
1350 if (delta > 0) {
1351 rbox.bottom = rbox.top + checkBoxHeight;
1352 } else {
1353 rbox.top -= -delta/2 + 1;
1354 rbox.bottom = rbox.top + checkBoxHeight;
1355 }
1356 } else if (style & BS_BOTTOM) {
1357 if (delta > 0) {
1358 rbox.top = rbox.bottom - checkBoxHeight;
1359 } else {
1360 rbox.bottom += -delta/2 + 1;
1361 rbox.top = rbox.bottom - checkBoxHeight;
1362 }
1363 } else { /* Default */
1364 if (delta > 0) {
1365 int ofs = (delta / 2);
1366 rbox.bottom -= ofs + 1;
1367 rbox.top = rbox.bottom - checkBoxHeight;
1368 } else if (delta < 0) {
1369 int ofs = (-delta / 2);
1370 rbox.top -= ofs + 1;
1371 rbox.bottom = rbox.top + checkBoxHeight;
1372 }
1373 }
1374
1375 DrawFrameControl( hDC, &rbox, DFC_BUTTON, flags );
1376 }
1377
1378 if (dtFlags == (UINT)-1L) /* Noting to draw */
1379 return;
1380
1381 if (action == ODA_DRAWENTIRE)
1382 BUTTON_DrawLabel(hwnd, hDC, dtFlags, &rtext);
1383
1384 /* ... and focus */
1385 if (action == ODA_FOCUS || (state & BST_FOCUS))
1386 {
1387 #ifdef __REACTOS__
1388 if (!(get_ui_state(hwnd) & UISF_HIDEFOCUS))
1389 {
1390 #endif
1391 rtext.left--;
1392 rtext.right++;
1393 IntersectRect(&rtext, &rtext, &client);
1394 DrawFocusRect( hDC, &rtext );
1395 #ifdef __REACTOS__
1396 }
1397 #endif
1398 }
1399 SelectClipRgn( hDC, hrgn );
1400 if (hrgn) DeleteObject( hrgn );
1401 }
1402
1403
1404 /**********************************************************************
1405 * BUTTON_CheckAutoRadioButton
1406 *
1407 * hwnd is checked, uncheck every other auto radio button in group
1408 */
1409 static void BUTTON_CheckAutoRadioButton( HWND hwnd )
1410 {
1411 HWND parent, sibling, start;
1412
1413 parent = GetParent(hwnd);
1414 /* make sure that starting control is not disabled or invisible */
1415 start = sibling = GetNextDlgGroupItem( parent, hwnd, TRUE );
1416 do
1417 {
1418 if (!sibling) break;
1419 if ((hwnd != sibling) &&
1420 ((GetWindowLongPtrW( sibling, GWL_STYLE) & BS_TYPEMASK) == BS_AUTORADIOBUTTON))
1421 SendMessageW( sibling, BM_SETCHECK, BST_UNCHECKED, 0 );
1422 sibling = GetNextDlgGroupItem( parent, sibling, FALSE );
1423 } while (sibling != start);
1424 }
1425
1426
1427 /**********************************************************************
1428 * Group Box Functions
1429 */
1430
1431 static void GB_Paint( HWND hwnd, HDC hDC, UINT action )
1432 {
1433 RECT rc, rcFrame;
1434 HBRUSH hbr;
1435 HFONT hFont;
1436 UINT dtFlags;
1437 TEXTMETRICW tm;
1438 LONG style = GetWindowLongPtrW( hwnd, GWL_STYLE );
1439 HWND parent;
1440 HRGN hrgn;
1441
1442 if ((hFont = get_button_font( hwnd ))) SelectObject( hDC, hFont );
1443 /* GroupBox acts like static control, so it sends CTLCOLORSTATIC */
1444 parent = GetParent(hwnd);
1445 if (!parent) parent = hwnd;
1446 #if defined(__REACTOS__) && defined(_USER32_)
1447 hbr = GetControlColor(parent, hwnd, hDC, WM_CTLCOLORSTATIC);
1448 #else
1449 hbr = (HBRUSH)SendMessageW(parent, WM_CTLCOLORSTATIC, (WPARAM)hDC, (LPARAM)hwnd);
1450 if (!hbr) /* did the app forget to call defwindowproc ? */
1451 hbr = (HBRUSH)DefWindowProcW(parent, WM_CTLCOLORSTATIC,
1452 (WPARAM)hDC, (LPARAM)hwnd);
1453 #endif
1454 GetClientRect( hwnd, &rc);
1455 rcFrame = rc;
1456 hrgn = set_control_clipping( hDC, &rc );
1457
1458 GetTextMetricsW (hDC, &tm);
1459 rcFrame.top += (tm.tmHeight / 2) - 1;
1460 DrawEdge (hDC, &rcFrame, EDGE_ETCHED, BF_RECT | ((style & BS_FLAT) ? BF_FLAT : 0));
1461
1462 InflateRect(&rc, -7, 1);
1463 dtFlags = BUTTON_CalcLabelRect(hwnd, hDC, &rc);
1464
1465 if (dtFlags != (UINT)-1L)
1466 {
1467 /* Because buttons have CS_PARENTDC class style, there is a chance
1468 * that label will be drawn out of client rect.
1469 * But Windows doesn't clip label's rect, so do I.
1470 */
1471
1472 /* There is 1-pixel margin at the left, right, and bottom */
1473 rc.left--; rc.right++; rc.bottom++;
1474 FillRect(hDC, &rc, hbr);
1475 rc.left++; rc.right--; rc.bottom--;
1476
1477 BUTTON_DrawLabel(hwnd, hDC, dtFlags, &rc);
1478 }
1479 SelectClipRgn( hDC, hrgn );
1480 if (hrgn) DeleteObject( hrgn );
1481 }
1482
1483
1484 /**********************************************************************
1485 * User Button Functions
1486 */
1487
1488 static void UB_Paint( HWND hwnd, HDC hDC, UINT action )
1489 {
1490 RECT rc;
1491 HBRUSH hBrush;
1492 HFONT hFont;
1493 LONG state = get_button_state( hwnd );
1494 HWND parent;
1495
1496 GetClientRect( hwnd, &rc);
1497
1498 if ((hFont = get_button_font( hwnd ))) SelectObject( hDC, hFont );
1499
1500 parent = GetParent(hwnd);
1501 if (!parent) parent = hwnd;
1502 #if defined(__REACTOS__) && defined(_USER32_)
1503 hBrush = GetControlColor( parent, hwnd, hDC, WM_CTLCOLORBTN);
1504 #else
1505 hBrush = (HBRUSH)SendMessageW(parent, WM_CTLCOLORBTN, (WPARAM)hDC, (LPARAM)hwnd);
1506 if (!hBrush) /* did the app forget to call defwindowproc ? */
1507 hBrush = (HBRUSH)DefWindowProcW(parent, WM_CTLCOLORBTN,
1508 (WPARAM)hDC, (LPARAM)hwnd);
1509 #endif
1510
1511 FillRect( hDC, &rc, hBrush );
1512 if (action == ODA_FOCUS || (state & BST_FOCUS))
1513 #ifdef __REACTOS__
1514 {
1515 if (!(get_ui_state(hwnd) & UISF_HIDEFOCUS))
1516 #endif
1517 DrawFocusRect( hDC, &rc );
1518 #ifdef __REACTOS__
1519 }
1520 #endif
1521
1522 switch (action)
1523 {
1524 case ODA_FOCUS:
1525 BUTTON_NOTIFY_PARENT( hwnd, (state & BST_FOCUS) ? BN_SETFOCUS : BN_KILLFOCUS );
1526 break;
1527
1528 case ODA_SELECT:
1529 BUTTON_NOTIFY_PARENT( hwnd, (state & BST_PUSHED) ? BN_HILITE : BN_UNHILITE );
1530 break;
1531
1532 default:
1533 BUTTON_NOTIFY_PARENT( hwnd, BN_PAINT );
1534 break;
1535 }
1536 }
1537
1538
1539 /**********************************************************************
1540 * Ownerdrawn Button Functions
1541 */
1542
1543 static void OB_Paint( HWND hwnd, HDC hDC, UINT action )
1544 {
1545 LONG state = get_button_state( hwnd );
1546 DRAWITEMSTRUCT dis;
1547 LONG_PTR id = GetWindowLongPtrW( hwnd, GWLP_ID );
1548 HWND parent;
1549 HFONT hFont, hPrevFont = 0;
1550 HRGN hrgn;
1551
1552 dis.CtlType = ODT_BUTTON;
1553 dis.CtlID = id;
1554 dis.itemID = 0;
1555 dis.itemAction = action;
1556 dis.itemState = ((state & BST_FOCUS) ? ODS_FOCUS : 0) |
1557 ((state & BST_PUSHED) ? ODS_SELECTED : 0) |
1558 (IsWindowEnabled(hwnd) ? 0: ODS_DISABLED);
1559 dis.hwndItem = hwnd;
1560 dis.hDC = hDC;
1561 dis.itemData = 0;
1562 GetClientRect( hwnd, &dis.rcItem );
1563
1564 if ((hFont = get_button_font( hwnd ))) hPrevFont = SelectObject( hDC, hFont );
1565 parent = GetParent(hwnd);
1566 if (!parent) parent = hwnd;
1567 #if defined(__REACTOS__) && defined(_USER32_)
1568 GetControlColor( parent, hwnd, hDC, WM_CTLCOLORBTN);
1569 #else
1570 SendMessageW( parent, WM_CTLCOLORBTN, (WPARAM)hDC, (LPARAM)hwnd );
1571 #endif
1572
1573 hrgn = set_control_clipping( hDC, &dis.rcItem );
1574
1575 SendMessageW( GetParent(hwnd), WM_DRAWITEM, id, (LPARAM)&dis );
1576 if (hPrevFont) SelectObject(hDC, hPrevFont);
1577 SelectClipRgn( hDC, hrgn );
1578 if (hrgn) DeleteObject( hrgn );
1579 }
1580
1581 #ifndef _USER32_
1582 void BUTTON_Register()
1583 {
1584 WNDCLASSW wndClass;
1585
1586 ZeroMemory(&wndClass, sizeof(WNDCLASSW));
1587 wndClass.style = CS_GLOBALCLASS | CS_DBLCLKS | CS_VREDRAW | CS_HREDRAW | CS_PARENTDC;
1588 wndClass.lpfnWndProc = ButtonWndProcW;
1589 wndClass.cbClsExtra = 0;
1590 wndClass.cbWndExtra = sizeof(PBUTTON_DATA);
1591 wndClass.hCursor = LoadCursorW(0, (LPCWSTR)IDC_ARROW);
1592 wndClass.hbrBackground = (HBRUSH)(COLOR_BTNFACE + 1);
1593 wndClass.lpszClassName = WC_BUTTONW;
1594
1595 RegisterClassW(&wndClass);
1596 }
1597
1598 void BUTTON_Unregister()
1599 {
1600 UnregisterClassW(WC_BUTTONW, NULL);
1601 }
1602 #endif