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