1 /*******************************************************************************
2 * Copyright (c) 2000, 2019 IBM Corporation and others.
3 *
4 * This program and the accompanying materials
5 * are made available under the terms of the Eclipse Public License 2.0
6 * which accompanies this distribution, and is available at
7 * https://www.eclipse.org/legal/epl-2.0/
8 *
9 * SPDX-License-Identifier: EPL-2.0
10 *
11 * Contributors:
12 * IBM Corporation - initial API and implementation
13 * Conrad Groth - Bug 23837 [FEEP] Button, do not respect foreground and background color on Windows
14 *******************************************************************************/
15 package org.eclipse.swt.widgets;
16
17
18 import org.eclipse.swt.*;
19 import org.eclipse.swt.events.*;
20 import org.eclipse.swt.graphics.*;
21 import org.eclipse.swt.internal.*;
22 import org.eclipse.swt.internal.win32.*;
23
24 /**
25 * Instances of this class represent a selectable user interface object that
26 * issues notification when pressed and released.
27 * <dl>
28 * <dt><b>Styles:</b></dt>
29 * <dd>ARROW, CHECK, PUSH, RADIO, TOGGLE, FLAT, WRAP</dd>
30 * <dd>UP, DOWN, LEFT, RIGHT, CENTER</dd>
31 * <dt><b>Events:</b></dt>
32 * <dd>Selection</dd>
33 * </dl>
34 * <p>
35 * Note: Only one of the styles ARROW, CHECK, PUSH, RADIO, and TOGGLE
36 * may be specified.
37 * </p><p>
38 * Note: Only one of the styles LEFT, RIGHT, and CENTER may be specified.
39 * </p><p>
40 * Note: Only one of the styles UP, DOWN, LEFT, and RIGHT may be specified
41 * when the ARROW style is specified.
42 * </p><p>
43 * IMPORTANT: This class is <em>not</em> intended to be subclassed.
44 * </p>
45 *
46 * @see <a href="http://www.eclipse.org/swt/snippets/#button">Button snippets</a>
47 * @see <a href="http://www.eclipse.org/swt/examples.php">SWT Example: ControlExample</a>
48 * @see <a href="http://www.eclipse.org/swt/">Sample code and further information</a>
49 * @noextend This class is not intended to be subclassed by clients.
50 */
51 public class Button extends Control {
52 String text = "", message = "";
53 Image image, image2, disabledImage;
54 ImageList imageList;
55 boolean ignoreMouse, grayed;
56 int buttonBackground = -1;
57 // we need our own field, because setting Control.background causes two colored pixels around the button.
58 int buttonBackgroundAlpha = 255;
59 static final int MARGIN = 4;
60 static final int CHECK_WIDTH, CHECK_HEIGHT;
61 static final int ICON_WIDTH = 128, ICON_HEIGHT = 128;
62 static /*final*/ boolean COMMAND_LINK = false;
63 static final long ButtonProc;
64 static final TCHAR ButtonClass = new TCHAR (0, "BUTTON", true);
65 static {
66 long hBitmap = OS.LoadBitmap (0, OS.OBM_CHECKBOXES);
67 if (hBitmap == 0) {
68 CHECK_WIDTH = OS.GetSystemMetrics (OS.SM_CXVSCROLL);
69 CHECK_HEIGHT = OS.GetSystemMetrics (OS.SM_CYVSCROLL);
70 } else {
71 BITMAP bitmap = new BITMAP ();
OS.GetObject(hBitmap, BITMAP.sizeof, bitmap)72 OS.GetObject (hBitmap, BITMAP.sizeof, bitmap);
73 OS.DeleteObject (hBitmap);
74 CHECK_WIDTH = bitmap.bmWidth / 4;
75 CHECK_HEIGHT = bitmap.bmHeight / 3;
76 }
77 WNDCLASS lpWndClass = new WNDCLASS ();
78 OS.GetClassInfo (0, ButtonClass, lpWndClass);
79 ButtonProc = lpWndClass.lpfnWndProc;
80 }
81
82 /**
83 * Constructs a new instance of this class given its parent
84 * and a style value describing its behavior and appearance.
85 * <p>
86 * The style value is either one of the style constants defined in
87 * class <code>SWT</code> which is applicable to instances of this
88 * class, or must be built by <em>bitwise OR</em>'ing together
89 * (that is, using the <code>int</code> "|" operator) two or more
90 * of those <code>SWT</code> style constants. The class description
91 * lists the style constants that are applicable to the class.
92 * Style bits are also inherited from superclasses.
93 * </p>
94 *
95 * @param parent a composite control which will be the parent of the new instance (cannot be null)
96 * @param style the style of control to construct
97 *
98 * @exception IllegalArgumentException <ul>
99 * <li>ERROR_NULL_ARGUMENT - if the parent is null</li>
100 * </ul>
101 * @exception SWTException <ul>
102 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the parent</li>
103 * <li>ERROR_INVALID_SUBCLASS - if this class is not an allowed subclass</li>
104 * </ul>
105 *
106 * @see SWT#ARROW
107 * @see SWT#CHECK
108 * @see SWT#PUSH
109 * @see SWT#RADIO
110 * @see SWT#TOGGLE
111 * @see SWT#FLAT
112 * @see SWT#UP
113 * @see SWT#DOWN
114 * @see SWT#LEFT
115 * @see SWT#RIGHT
116 * @see SWT#CENTER
117 * @see Widget#checkSubclass
118 * @see Widget#getStyle
119 */
Button(Composite parent, int style)120 public Button (Composite parent, int style) {
121 super (parent, checkStyle (style));
122 }
123
_setImage(Image image)124 void _setImage (Image image) {
125 if ((style & SWT.COMMAND) != 0) return;
126 if (imageList != null) imageList.dispose ();
127 imageList = null;
128 if (image != null) {
129 imageList = new ImageList (style & SWT.RIGHT_TO_LEFT);
130 if (OS.IsWindowEnabled (handle)) {
131 imageList.add (image);
132 } else {
133 if (disabledImage != null) disabledImage.dispose ();
134 disabledImage = new Image (display, image, SWT.IMAGE_DISABLE);
135 imageList.add (disabledImage);
136 }
137 BUTTON_IMAGELIST buttonImageList = new BUTTON_IMAGELIST ();
138 buttonImageList.himl = imageList.getHandle ();
139 int oldBits = OS.GetWindowLong (handle, OS.GWL_STYLE), newBits = oldBits;
140 newBits &= ~(OS.BS_LEFT | OS.BS_CENTER | OS.BS_RIGHT);
141 if ((style & SWT.LEFT) != 0) newBits |= OS.BS_LEFT;
142 if ((style & SWT.CENTER) != 0) newBits |= OS.BS_CENTER;
143 if ((style & SWT.RIGHT) != 0) newBits |= OS.BS_RIGHT;
144 if (text.length () == 0) {
145 if ((style & SWT.LEFT) != 0) buttonImageList.uAlign = OS.BUTTON_IMAGELIST_ALIGN_LEFT;
146 if ((style & SWT.CENTER) != 0) buttonImageList.uAlign = OS.BUTTON_IMAGELIST_ALIGN_CENTER;
147 if ((style & SWT.RIGHT) != 0) buttonImageList.uAlign = OS.BUTTON_IMAGELIST_ALIGN_RIGHT;
148 } else {
149 buttonImageList.uAlign = OS.BUTTON_IMAGELIST_ALIGN_LEFT;
150 buttonImageList.margin_left = computeLeftMargin ();
151 buttonImageList.margin_right = MARGIN;
152 newBits &= ~(OS.BS_CENTER | OS.BS_RIGHT);
153 newBits |= OS.BS_LEFT;
154 }
155 if (newBits != oldBits) {
156 OS.SetWindowLong (handle, OS.GWL_STYLE, newBits);
157 OS.InvalidateRect (handle, null, true);
158 }
159 OS.SendMessage (handle, OS.BCM_SETIMAGELIST, 0, buttonImageList);
160 } else {
161 OS.SendMessage (handle, OS.BCM_SETIMAGELIST, 0, 0);
162 }
163 /*
164 * Bug in Windows. Under certain cirumstances yet to be
165 * isolated, BCM_SETIMAGELIST does not redraw the control
166 * when a new image is set. The fix is to force a redraw.
167 */
168 OS.InvalidateRect (handle, null, true);
169 }
170
_setText(String text)171 void _setText (String text) {
172 int oldBits = OS.GetWindowLong (handle, OS.GWL_STYLE), newBits = oldBits;
173 newBits &= ~(OS.BS_LEFT | OS.BS_CENTER | OS.BS_RIGHT);
174 if ((style & SWT.LEFT) != 0) newBits |= OS.BS_LEFT;
175 if ((style & SWT.CENTER) != 0) newBits |= OS.BS_CENTER;
176 if ((style & SWT.RIGHT) != 0) newBits |= OS.BS_RIGHT;
177 if (imageList != null) {
178 BUTTON_IMAGELIST buttonImageList = new BUTTON_IMAGELIST ();
179 buttonImageList.himl = imageList.getHandle ();
180 if (text.length () == 0) {
181 if ((style & SWT.LEFT) != 0) buttonImageList.uAlign = OS.BUTTON_IMAGELIST_ALIGN_LEFT;
182 if ((style & SWT.CENTER) != 0) buttonImageList.uAlign = OS.BUTTON_IMAGELIST_ALIGN_CENTER;
183 if ((style & SWT.RIGHT) != 0) buttonImageList.uAlign = OS.BUTTON_IMAGELIST_ALIGN_RIGHT;
184 } else {
185 buttonImageList.uAlign = OS.BUTTON_IMAGELIST_ALIGN_LEFT;
186 buttonImageList.margin_left = computeLeftMargin ();
187 buttonImageList.margin_right = MARGIN;
188 newBits &= ~(OS.BS_CENTER | OS.BS_RIGHT);
189 newBits |= OS.BS_LEFT;
190 }
191 OS.SendMessage (handle, OS.BCM_SETIMAGELIST, 0, buttonImageList);
192 }
193 if (newBits != oldBits) {
194 OS.SetWindowLong (handle, OS.GWL_STYLE, newBits);
195 OS.InvalidateRect (handle, null, true);
196 }
197 /*
198 * Bug in Windows. When a Button control is right-to-left and
199 * is disabled, the first pixel of the text is clipped. The fix
200 * is to append a space to the text.
201 */
202 if ((style & SWT.RIGHT_TO_LEFT) != 0) {
203 if (!OS.IsAppThemed ()) {
204 text = OS.IsWindowEnabled (handle) ? text : text + " ";
205 }
206 }
207 TCHAR buffer = new TCHAR (getCodePage (), text, true);
208 OS.SetWindowText (handle, buffer);
209 if ((state & HAS_AUTO_DIRECTION) != 0) {
210 updateTextDirection (AUTO_TEXT_DIRECTION);
211 }
212 }
213
214 /**
215 * Adds the listener to the collection of listeners who will
216 * be notified when the control is selected by the user, by sending
217 * it one of the messages defined in the <code>SelectionListener</code>
218 * interface.
219 * <p>
220 * <code>widgetSelected</code> is called when the control is selected by the user.
221 * <code>widgetDefaultSelected</code> is not called.
222 * </p>
223 * <p>
224 * When the <code>SWT.RADIO</code> style bit is set, the <code>widgetSelected</code> method is
225 * also called when the receiver loses selection because another item in the same radio group
226 * was selected by the user. During <code>widgetSelected</code> the application can use
227 * <code>getSelection()</code> to determine the current selected state of the receiver.
228 * </p>
229 *
230 * @param listener the listener which should be notified
231 *
232 * @exception IllegalArgumentException <ul>
233 * <li>ERROR_NULL_ARGUMENT - if the listener is null</li>
234 * </ul>
235 * @exception SWTException <ul>
236 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
237 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
238 * </ul>
239 *
240 * @see SelectionListener
241 * @see #removeSelectionListener
242 * @see SelectionEvent
243 */
addSelectionListener(SelectionListener listener)244 public void addSelectionListener (SelectionListener listener) {
245 checkWidget ();
246 if (listener == null) error (SWT.ERROR_NULL_ARGUMENT);
247 TypedListener typedListener = new TypedListener (listener);
248 addListener (SWT.Selection,typedListener);
249 addListener (SWT.DefaultSelection,typedListener);
250 }
251
252 @Override
callWindowProc(long hwnd, int msg, long wParam, long lParam)253 long callWindowProc (long hwnd, int msg, long wParam, long lParam) {
254 if (handle == 0) return 0;
255 return OS.CallWindowProc (ButtonProc, hwnd, msg, wParam, lParam);
256 }
257
checkStyle(int style)258 static int checkStyle (int style) {
259 style = checkBits (style, SWT.PUSH, SWT.ARROW, SWT.CHECK, SWT.RADIO, SWT.TOGGLE, COMMAND_LINK ? SWT.COMMAND : 0);
260 if ((style & (SWT.PUSH | SWT.TOGGLE)) != 0) {
261 return checkBits (style, SWT.CENTER, SWT.LEFT, SWT.RIGHT, 0, 0, 0);
262 }
263 if ((style & (SWT.CHECK | SWT.RADIO)) != 0) {
264 return checkBits (style, SWT.LEFT, SWT.RIGHT, SWT.CENTER, 0, 0, 0);
265 }
266 if ((style & SWT.ARROW) != 0) {
267 style |= SWT.NO_FOCUS;
268 return checkBits (style, SWT.UP, SWT.DOWN, SWT.LEFT, SWT.RIGHT, 0, 0);
269 }
270 return style;
271 }
272
click()273 void click () {
274 /*
275 * Feature in Windows. BM_CLICK sends a fake WM_LBUTTONDOWN and
276 * WM_LBUTTONUP in order to click the button. This causes the
277 * application to get unexpected mouse events. The fix is to
278 * ignore mouse events when they are caused by BM_CLICK.
279 */
280 ignoreMouse = true;
281 OS.SendMessage (handle, OS.BM_CLICK, 0, 0);
282 ignoreMouse = false;
283 }
284
285 // TODO: this method ignores the style LEFT, CENTER or RIGHT
computeLeftMargin()286 int computeLeftMargin () {
287 if ((style & (SWT.PUSH | SWT.TOGGLE)) == 0) return MARGIN;
288 int margin = 0;
289 if (image != null && text.length () != 0) {
290 Rectangle bounds = image.getBoundsInPixels ();
291 margin += bounds.width + MARGIN * 2;
292 long oldFont = 0;
293 long hDC = OS.GetDC (handle);
294 long newFont = OS.SendMessage (handle, OS.WM_GETFONT, 0, 0);
295 if (newFont != 0) oldFont = OS.SelectObject (hDC, newFont);
296 char [] buffer = text.toCharArray ();
297 RECT rect = new RECT ();
298 int flags = OS.DT_CALCRECT | OS.DT_SINGLELINE;
299 OS.DrawText (hDC, buffer, buffer.length, rect, flags);
300 margin += rect.right - rect.left;
301 if (newFont != 0) OS.SelectObject (hDC, oldFont);
302 OS.ReleaseDC (handle, hDC);
303 OS.GetClientRect (handle, rect);
304 margin = Math.max (MARGIN, (rect.right - rect.left - margin) / 2);
305 }
306 return margin;
307 }
308
computeSizeInPixels(int wHint, int hHint, boolean changed)309 @Override Point computeSizeInPixels (int wHint, int hHint, boolean changed) {
310 checkWidget ();
311 int width = 0, height = 0, border = getBorderWidthInPixels ();
312 if ((style & SWT.ARROW) != 0) {
313 if ((style & (SWT.UP | SWT.DOWN)) != 0) {
314 width += OS.GetSystemMetrics (OS.SM_CXVSCROLL);
315 height += OS.GetSystemMetrics (OS.SM_CYVSCROLL);
316 } else {
317 width += OS.GetSystemMetrics (OS.SM_CXHSCROLL);
318 height += OS.GetSystemMetrics (OS.SM_CYHSCROLL);
319 }
320 } else {
321 if ((style & SWT.COMMAND) != 0) {
322 SIZE size = new SIZE ();
323 if (wHint != SWT.DEFAULT) {
324 size.cx = wHint;
325 OS.SendMessage (handle, OS.BCM_GETIDEALSIZE, 0, size);
326 width = size.cx;
327 height = size.cy;
328 } else {
329 OS.SendMessage (handle, OS.BCM_GETIDEALSIZE, 0, size);
330 width = size.cy;
331 height = size.cy;
332 size.cy = 0;
333 while (size.cy != height) {
334 size.cx = width++;
335 size.cy = 0;
336 OS.SendMessage (handle, OS.BCM_GETIDEALSIZE, 0, size);
337 }
338 }
339 } else {
340 int extra = 0;
341 boolean hasImage = image != null, hasText = true;
342 if (hasImage) {
343 if (image != null) {
344 Rectangle rect = image.getBoundsInPixels ();
345 width = rect.width;
346 if (hasText && text.length () != 0) {
347 width += MARGIN * 2;
348 }
349 height = rect.height;
350 extra = MARGIN * 2;
351 }
352 }
353 if (hasText) {
354 long oldFont = 0;
355 long hDC = OS.GetDC (handle);
356 long newFont = OS.SendMessage (handle, OS.WM_GETFONT, 0, 0);
357 if (newFont != 0) oldFont = OS.SelectObject (hDC, newFont);
358 TEXTMETRIC lptm = new TEXTMETRIC ();
359 OS.GetTextMetrics (hDC, lptm);
360 int length = text.length ();
361 if (length == 0) {
362 height = Math.max (height, lptm.tmHeight);
363 } else {
364 extra = Math.max (MARGIN * 2, lptm.tmAveCharWidth);
365 char [] buffer = text.toCharArray ();
366 RECT rect = new RECT ();
367 int flags = OS.DT_CALCRECT | OS.DT_SINGLELINE;
368 if ((style & SWT.WRAP) != 0 && wHint != SWT.DEFAULT) {
369 flags = OS.DT_CALCRECT | OS.DT_WORDBREAK;
370 rect.right = wHint - width - 2 * border;
371 if (isRadioOrCheck()) {
372 rect.right -= CHECK_WIDTH + 3;
373 } else {
374 rect.right -= 6;
375 }
376 if (!OS.IsAppThemed ()) {
377 rect.right -= 2;
378 if (isRadioOrCheck()) {
379 rect.right -= 2;
380 }
381 }
382 }
383 OS.DrawText (hDC, buffer, buffer.length, rect, flags);
384 width += rect.right - rect.left;
385 height = Math.max (height, rect.bottom - rect.top);
386 }
387 if (newFont != 0) OS.SelectObject (hDC, oldFont);
388 OS.ReleaseDC (handle, hDC);
389 }
390 if (isRadioOrCheck()) {
391 width += CHECK_WIDTH + extra;
392 height = Math.max (height, CHECK_HEIGHT + 3);
393 }
394 if ((style & (SWT.PUSH | SWT.TOGGLE)) != 0) {
395 width += 12; height += 10;
396 }
397 }
398 }
399 if (wHint != SWT.DEFAULT) width = wHint;
400 if (hHint != SWT.DEFAULT) height = hHint;
401 width += border * 2;
402 height += border * 2;
403 return new Point (width, height);
404 }
405
406 @Override
createHandle()407 void createHandle () {
408 /*
409 * Feature in Windows. When a button is created,
410 * it clears the UI state for all controls in the
411 * shell by sending WM_CHANGEUISTATE with UIS_SET,
412 * UISF_HIDEACCEL and UISF_HIDEFOCUS to the parent.
413 * This is undocumented and unexpected. The fix
414 * is to ignore the WM_CHANGEUISTATE, when sent
415 * from CreateWindowEx().
416 */
417 parent.state |= IGNORE_WM_CHANGEUISTATE;
418 super.createHandle ();
419 parent.state &= ~IGNORE_WM_CHANGEUISTATE;
420
421 if (OS.IsAppThemed ()) {
422 /* Set the theme background.
423 *
424 * NOTE: On Vista this causes problems when the tab
425 * key is pressed for push buttons so disable the
426 * theme background drawing for these widgets for
427 * now.
428 */
429 if ((style & (SWT.PUSH | SWT.TOGGLE)) == 0) {
430 state |= THEME_BACKGROUND;
431 }
432
433 /*
434 * Bug in Windows. For some reason, the HBRUSH that
435 * is returned from WM_CTRLCOLOR is misaligned when
436 * the button uses it to draw. If the brush is a solid
437 * color, this does not matter. However, if the brush
438 * contains an image, the image is misaligned. The
439 * fix is to draw the background in WM_CTRLCOLOR.
440 *
441 * NOTE: For comctl32.dll 6.0 with themes disabled,
442 * drawing in WM_ERASEBKGND will draw on top of the
443 * text of the control.
444 */
445 if ((style & SWT.RADIO) != 0) {
446 state |= DRAW_BACKGROUND;
447 }
448 }
449 }
450
customBackgroundDrawing()451 private boolean customBackgroundDrawing() {
452 return buttonBackground != -1 && !isRadioOrCheck();
453 }
454
customDrawing()455 private boolean customDrawing() {
456 return customBackgroundDrawing() || customForegroundDrawing();
457 }
458
customForegroundDrawing()459 private boolean customForegroundDrawing() {
460 return foreground != -1 && !text.isEmpty() && OS.IsWindowEnabled(handle);
461 }
462
463 @Override
defaultBackground()464 int defaultBackground () {
465 if ((style & (SWT.PUSH | SWT.TOGGLE)) != 0) {
466 return OS.GetSysColor (OS.COLOR_BTNFACE);
467 }
468 return super.defaultBackground ();
469 }
470
471 @Override
defaultForeground()472 int defaultForeground () {
473 return OS.GetSysColor (OS.COLOR_BTNTEXT);
474 }
475
476 @Override
enableWidget(boolean enabled)477 void enableWidget (boolean enabled) {
478 super.enableWidget (enabled);
479 /*
480 * Bug in Windows. When a Button control is right-to-left and
481 * is disabled, the first pixel of the text is clipped. The fix
482 * is to append a space to the text.
483 */
484 if ((style & SWT.RIGHT_TO_LEFT) != 0) {
485 if (!OS.IsAppThemed ()) {
486 int bits = OS.GetWindowLong (handle, OS.GWL_STYLE);
487 boolean hasImage = (bits & (OS.BS_BITMAP | OS.BS_ICON)) != 0;
488 if (!hasImage) {
489 String string = enabled ? text : text + " ";
490 TCHAR buffer = new TCHAR (getCodePage (), string, true);
491 OS.SetWindowText (handle, buffer);
492 }
493 }
494 }
495 /*
496 * Bug in Windows. When a button has the style BS_CHECKBOX
497 * or BS_RADIOBUTTON, is checked, and is displaying both an
498 * image and some text, when BCM_SETIMAGELIST is used to
499 * assign an image list for each of the button states, the
500 * button does not draw properly. When the user drags the
501 * mouse in and out of the button, it draws using a blank
502 * image. The fix is to set the complete image list only
503 * when the button is disabled.
504 */
505 updateImageList ();
506 }
507
508 /**
509 * Returns a value which describes the position of the
510 * text or image in the receiver. The value will be one of
511 * <code>LEFT</code>, <code>RIGHT</code> or <code>CENTER</code>
512 * unless the receiver is an <code>ARROW</code> button, in
513 * which case, the alignment will indicate the direction of
514 * the arrow (one of <code>LEFT</code>, <code>RIGHT</code>,
515 * <code>UP</code> or <code>DOWN</code>).
516 *
517 * @return the alignment
518 *
519 * @exception SWTException <ul>
520 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
521 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
522 * </ul>
523 */
getAlignment()524 public int getAlignment () {
525 checkWidget ();
526 if ((style & SWT.ARROW) != 0) {
527 if ((style & SWT.UP) != 0) return SWT.UP;
528 if ((style & SWT.DOWN) != 0) return SWT.DOWN;
529 if ((style & SWT.LEFT) != 0) return SWT.LEFT;
530 if ((style & SWT.RIGHT) != 0) return SWT.RIGHT;
531 return SWT.UP;
532 }
533 if ((style & SWT.LEFT) != 0) return SWT.LEFT;
534 if ((style & SWT.CENTER) != 0) return SWT.CENTER;
535 if ((style & SWT.RIGHT) != 0) return SWT.RIGHT;
536 return SWT.LEFT;
537 }
538
539 @Override
getBackground()540 public Color getBackground () {
541 if (isRadioOrCheck()) {
542 return super.getBackground();
543 }
544 checkWidget ();
545 if (buttonBackground != -1) {
546 return Color.win32_new (display, buttonBackground, buttonBackgroundAlpha);
547 }
548 return Color.win32_new (display, defaultBackground());
549 }
550
getDefault()551 boolean getDefault () {
552 if ((style & SWT.PUSH) == 0) return false;
553 int bits = OS.GetWindowLong (handle, OS.GWL_STYLE);
554 return (bits & OS.BS_DEFPUSHBUTTON) != 0;
555 }
556
557 /**
558 * Returns <code>true</code> if the receiver is grayed,
559 * and false otherwise. When the widget does not have
560 * the <code>CHECK</code> style, return false.
561 *
562 * @return the grayed state of the checkbox
563 *
564 * @exception SWTException <ul>
565 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
566 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
567 * </ul>
568 *
569 * @since 3.4
570 */
getGrayed()571 public boolean getGrayed () {
572 checkWidget();
573 if ((style & SWT.CHECK) == 0) return false;
574 return grayed;
575 }
576
577 /**
578 * Returns the receiver's image if it has one, or null
579 * if it does not.
580 *
581 * @return the receiver's image
582 *
583 * @exception SWTException <ul>
584 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
585 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
586 * </ul>
587 */
getImage()588 public Image getImage () {
589 checkWidget ();
590 return image;
591 }
592
593 /**
594 * Returns the widget message. When the widget is created
595 * with the style <code>SWT.COMMAND</code>, the message text
596 * is displayed to provide further information for the user.
597 *
598 * @return the widget message
599 *
600 * @exception SWTException <ul>
601 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
602 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
603 * </ul>
604 *
605 * @since 3.3
606 */
getMessage()607 /*public*/ String getMessage () {
608 checkWidget ();
609 return message;
610 }
611
612 @Override
getNameText()613 String getNameText () {
614 return getText ();
615 }
616
617 /**
618 * Returns <code>true</code> if the receiver is selected,
619 * and false otherwise.
620 * <p>
621 * When the receiver is of type <code>CHECK</code> or <code>RADIO</code>,
622 * it is selected when it is checked. When it is of type <code>TOGGLE</code>,
623 * it is selected when it is pushed in. If the receiver is of any other type,
624 * this method returns false.
625 *
626 * @return the selection state
627 *
628 * @exception SWTException <ul>
629 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
630 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
631 * </ul>
632 */
getSelection()633 public boolean getSelection () {
634 checkWidget ();
635 if ((style & (SWT.CHECK | SWT.RADIO | SWT.TOGGLE)) == 0) return false;
636 return isChecked();
637 }
638
639 /**
640 * Returns the receiver's text, which will be an empty
641 * string if it has never been set or if the receiver is
642 * an <code>ARROW</code> button.
643 *
644 * @return the receiver's text
645 *
646 * @exception SWTException <ul>
647 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
648 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
649 * </ul>
650 */
getText()651 public String getText () {
652 checkWidget ();
653 if ((style & SWT.ARROW) != 0) return "";
654 return text;
655 }
656
isChecked()657 private boolean isChecked() {
658 long flags = OS.SendMessage (handle, OS.BM_GETCHECK, 0, 0);
659 return flags != OS.BST_UNCHECKED;
660 }
661
isRadioOrCheck()662 private boolean isRadioOrCheck() {
663 return (style & (SWT.RADIO | SWT.CHECK)) != 0;
664 }
665
666 @Override
isTabItem()667 boolean isTabItem () {
668 if ((style & SWT.PUSH) != 0) return isTabGroup ();
669 return super.isTabItem ();
670 }
671
672 @Override
mnemonicHit(char ch)673 boolean mnemonicHit (char ch) {
674 if (!setFocus ()) return false;
675 /*
676 * Feature in Windows. When a radio button gets focus,
677 * it selects the button in WM_SETFOCUS. Therefore, it
678 * is not necessary to click the button or send events
679 * because this has already happened in WM_SETFOCUS.
680 */
681 if ((style & SWT.RADIO) == 0) click ();
682 return true;
683 }
684
685 @Override
mnemonicMatch(char key)686 boolean mnemonicMatch (char key) {
687 char mnemonic = findMnemonic (getText ());
688 if (mnemonic == '\0') return false;
689 return Character.toUpperCase (key) == Character.toUpperCase (mnemonic);
690 }
691
692 @Override
releaseWidget()693 void releaseWidget () {
694 super.releaseWidget ();
695 if (imageList != null) imageList.dispose ();
696 imageList = null;
697 if (disabledImage != null) disabledImage.dispose ();
698 disabledImage = null;
699 if (image2 != null) image2.dispose ();
700 image2 = null;
701 text = null;
702 image = null;
703 }
704
705 /**
706 * Removes the listener from the collection of listeners who will
707 * be notified when the control is selected by the user.
708 *
709 * @param listener the listener which should no longer be notified
710 *
711 * @exception IllegalArgumentException <ul>
712 * <li>ERROR_NULL_ARGUMENT - if the listener is null</li>
713 * </ul>
714 * @exception SWTException <ul>
715 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
716 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
717 * </ul>
718 *
719 * @see SelectionListener
720 * @see #addSelectionListener
721 */
removeSelectionListener(SelectionListener listener)722 public void removeSelectionListener (SelectionListener listener) {
723 checkWidget ();
724 if (listener == null) error (SWT.ERROR_NULL_ARGUMENT);
725 if (eventTable == null) return;
726 eventTable.unhook (SWT.Selection, listener);
727 eventTable.unhook (SWT.DefaultSelection,listener);
728 }
729
730 @Override
resolveTextDirection()731 int resolveTextDirection() {
732 return (style & SWT.ARROW) != 0 ? SWT.NONE : BidiUtil.resolveTextDirection(text);
733 }
734
selectRadio()735 void selectRadio () {
736 for (Control child : parent._getChildren ()) {
737 if (this != child) child.setRadioSelection (false);
738 }
739 setSelection (true);
740 }
741
742 /**
743 * Controls how text, images and arrows will be displayed
744 * in the receiver. The argument should be one of
745 * <code>LEFT</code>, <code>RIGHT</code> or <code>CENTER</code>
746 * unless the receiver is an <code>ARROW</code> button, in
747 * which case, the argument indicates the direction of
748 * the arrow (one of <code>LEFT</code>, <code>RIGHT</code>,
749 * <code>UP</code> or <code>DOWN</code>).
750 *
751 * @param alignment the new alignment
752 *
753 * @exception SWTException <ul>
754 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
755 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
756 * </ul>
757 */
setAlignment(int alignment)758 public void setAlignment (int alignment) {
759 checkWidget ();
760 if ((style & SWT.ARROW) != 0) {
761 if ((style & (SWT.UP | SWT.DOWN | SWT.LEFT | SWT.RIGHT)) == 0) return;
762 style &= ~(SWT.UP | SWT.DOWN | SWT.LEFT | SWT.RIGHT);
763 style |= alignment & (SWT.UP | SWT.DOWN | SWT.LEFT | SWT.RIGHT);
764 OS.InvalidateRect (handle, null, true);
765 return;
766 }
767 if ((alignment & (SWT.LEFT | SWT.RIGHT | SWT.CENTER)) == 0) return;
768 style &= ~(SWT.LEFT | SWT.RIGHT | SWT.CENTER);
769 style |= alignment & (SWT.LEFT | SWT.RIGHT | SWT.CENTER);
770 int oldBits = OS.GetWindowLong (handle, OS.GWL_STYLE), newBits = oldBits;
771 newBits &= ~(OS.BS_LEFT | OS.BS_CENTER | OS.BS_RIGHT);
772 if ((style & SWT.LEFT) != 0) newBits |= OS.BS_LEFT;
773 if ((style & SWT.CENTER) != 0) newBits |= OS.BS_CENTER;
774 if ((style & SWT.RIGHT) != 0) newBits |= OS.BS_RIGHT;
775 if (imageList != null) {
776 BUTTON_IMAGELIST buttonImageList = new BUTTON_IMAGELIST ();
777 buttonImageList.himl = imageList.getHandle ();
778 if (text.length () == 0) {
779 if ((style & SWT.LEFT) != 0) buttonImageList.uAlign = OS.BUTTON_IMAGELIST_ALIGN_LEFT;
780 if ((style & SWT.CENTER) != 0) buttonImageList.uAlign = OS.BUTTON_IMAGELIST_ALIGN_CENTER;
781 if ((style & SWT.RIGHT) != 0) buttonImageList.uAlign = OS.BUTTON_IMAGELIST_ALIGN_RIGHT;
782 } else {
783 buttonImageList.uAlign = OS.BUTTON_IMAGELIST_ALIGN_LEFT;
784 buttonImageList.margin_left = computeLeftMargin ();
785 buttonImageList.margin_right = MARGIN;
786 newBits &= ~(OS.BS_CENTER | OS.BS_RIGHT);
787 newBits |= OS.BS_LEFT;
788 }
789 OS.SendMessage (handle, OS.BCM_SETIMAGELIST, 0, buttonImageList);
790 }
791 if (newBits != oldBits) {
792 OS.SetWindowLong (handle, OS.GWL_STYLE, newBits);
793 OS.InvalidateRect (handle, null, true);
794 }
795 }
796
797 /**
798 * Sets the button's background color to the color specified
799 * by the argument, or to the default system color for the control
800 * if the argument is null.
801 * <p>
802 * Note: This is custom paint operation and only affects {@link SWT#PUSH} and {@link SWT#TOGGLE} buttons. If the native button
803 * has a 3D look an feel (e.g. Windows 7), this method will cause the button to look FLAT irrespective of the state of the
804 * {@link SWT#FLAT} style.
805 * For {@link SWT#CHECK} and {@link SWT#RADIO} buttons, this method delegates to {@link Control#setBackground(Color)}.
806 * </p>
807 * @param color the new color (or null)
808 *
809 * @exception IllegalArgumentException <ul>
810 * <li>ERROR_INVALID_ARGUMENT - if the argument has been disposed</li>
811 * </ul>
812 * @exception SWTException <ul>
813 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
814 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
815 * </ul>
816 */
817 @Override
setBackground(Color color)818 public void setBackground (Color color) {
819 checkWidget ();
820 if (isRadioOrCheck()) {
821 super.setBackground(color);
822 } else {
823 setButtonBackground (color);
824 }
825 }
826
setButtonBackground(Color color)827 private void setButtonBackground (Color color) {
828 int pixel = -1;
829 int alpha = 255;
830 if (color != null) {
831 if (color.isDisposed ()) error (SWT.ERROR_INVALID_ARGUMENT);
832 pixel = color.handle;
833 alpha = color.getAlpha();
834 }
835 if (pixel == buttonBackground && alpha == buttonBackgroundAlpha) return;
836 buttonBackground = pixel;
837 buttonBackgroundAlpha = alpha;
838 updateBackgroundColor ();
839 }
840
setDefault(boolean value)841 void setDefault (boolean value) {
842 if ((style & SWT.PUSH) == 0) return;
843 long hwndShell = menuShell ().handle;
844 int bits = OS.GetWindowLong (handle, OS.GWL_STYLE);
845 if (value) {
846 bits |= OS.BS_DEFPUSHBUTTON;
847 OS.SendMessage (hwndShell, OS.DM_SETDEFID, handle, 0);
848 } else {
849 bits &= ~OS.BS_DEFPUSHBUTTON;
850 OS.SendMessage (hwndShell, OS.DM_SETDEFID, 0, 0);
851 }
852 OS.SendMessage (handle, OS.BM_SETSTYLE, bits, 1);
853 }
854
855 @Override
setFocus()856 public boolean setFocus () {
857 checkWidget ();
858 /*
859 * Feature in Windows. When a radio button gets focus,
860 * it selects the button in WM_SETFOCUS. The fix is to
861 * not assign focus to an unselected radio button.
862 */
863 if ((style & SWT.RADIO) != 0 && !isChecked () && display.fixFocus) return false;
864 return super.setFocus ();
865 }
866
867 /**
868 * Sets the receiver's image to the argument, which may be
869 * <code>null</code> indicating that no image should be displayed.
870 * <p>
871 * Note that a Button can display an image and text simultaneously
872 * on Windows (starting with XP), GTK+ and OSX. On other platforms,
873 * a Button that has an image and text set into it will display the
874 * image or text that was set most recently.
875 * </p>
876 * @param image the image to display on the receiver (may be <code>null</code>)
877 *
878 * @exception IllegalArgumentException <ul>
879 * <li>ERROR_INVALID_ARGUMENT - if the image has been disposed</li>
880 * </ul>
881 * @exception SWTException <ul>
882 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
883 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
884 * </ul>
885 */
setImage(Image image)886 public void setImage (Image image) {
887 checkWidget ();
888 if (image != null && image.isDisposed()) error(SWT.ERROR_INVALID_ARGUMENT);
889 if ((style & SWT.ARROW) != 0) return;
890 this.image = image;
891 _setImage (image);
892 }
893
894 /**
895 * Sets the grayed state of the receiver. This state change
896 * only applies if the control was created with the SWT.CHECK
897 * style.
898 *
899 * @param grayed the new grayed state
900 *
901 * @exception SWTException <ul>
902 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
903 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
904 * </ul>
905 *
906 * @since 3.4
907 */
setGrayed(boolean grayed)908 public void setGrayed (boolean grayed) {
909 checkWidget ();
910 if ((style & SWT.CHECK) == 0) return;
911 this.grayed = grayed;
912 long flags = OS.SendMessage (handle, OS.BM_GETCHECK, 0, 0);
913 if (grayed) {
914 if (flags == OS.BST_CHECKED) updateSelection (OS.BST_INDETERMINATE);
915 } else {
916 if (flags == OS.BST_INDETERMINATE) updateSelection (OS.BST_CHECKED);
917 }
918 }
919
920 /**
921 * Sets the widget message. When the widget is created
922 * with the style <code>SWT.COMMAND</code>, the message text
923 * is displayed to provide further information for the user.
924 *
925 * @param message the new message
926 *
927 * @exception IllegalArgumentException <ul>
928 * <li>ERROR_NULL_ARGUMENT - if the string is null</li>
929 * </ul>
930 * @exception SWTException <ul>
931 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
932 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
933 * </ul>
934 *
935 * @since 3.3
936 */
setMessage(String message)937 /*public*/ void setMessage (String message) {
938 checkWidget ();
939 if (message == null) error (SWT.ERROR_NULL_ARGUMENT);
940 this.message = message;
941 if ((style & SWT.COMMAND) != 0) {
942 int length = message.length ();
943 char [] chars = new char [length + 1];
944 message.getChars(0, length, chars, 0);
945 OS.SendMessage (handle, OS.BCM_SETNOTE, 0, chars);
946 }
947 }
948
949 @Override
setRadioFocus(boolean tabbing)950 boolean setRadioFocus (boolean tabbing) {
951 if ((style & SWT.RADIO) == 0 || !getSelection ()) return false;
952 return tabbing ? setTabItemFocus () : setFocus ();
953 }
954
955 @Override
setRadioSelection(boolean value)956 boolean setRadioSelection (boolean value) {
957 if ((style & SWT.RADIO) == 0) return false;
958 if (getSelection () != value) {
959 setSelection (value);
960 sendSelectionEvent (SWT.Selection);
961 }
962 return true;
963 }
964
965 /**
966 * Sets the selection state of the receiver, if it is of type <code>CHECK</code>,
967 * <code>RADIO</code>, or <code>TOGGLE</code>.
968 *
969 * <p>
970 * When the receiver is of type <code>CHECK</code> or <code>RADIO</code>,
971 * it is selected when it is checked. When it is of type <code>TOGGLE</code>,
972 * it is selected when it is pushed in.
973 *
974 * @param selected the new selection state
975 *
976 * @exception SWTException <ul>
977 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
978 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
979 * </ul>
980 */
setSelection(boolean selected)981 public void setSelection (boolean selected) {
982 checkWidget ();
983 if ((style & (SWT.CHECK | SWT.RADIO | SWT.TOGGLE)) == 0) return;
984 int flags = selected ? OS.BST_CHECKED : OS.BST_UNCHECKED;
985 if ((style & SWT.CHECK) != 0) {
986 if (selected && grayed) flags = OS.BST_INDETERMINATE;
987 }
988 updateSelection (flags);
989 }
990
991 /**
992 * Sets the receiver's text.
993 * <p>
994 * This method sets the button label. The label may include
995 * the mnemonic character but must not contain line delimiters.
996 * </p>
997 * <p>
998 * Mnemonics are indicated by an '&' that causes the next
999 * character to be the mnemonic. When the user presses a
1000 * key sequence that matches the mnemonic, a selection
1001 * event occurs. On most platforms, the mnemonic appears
1002 * underlined but may be emphasized in a platform specific
1003 * manner. The mnemonic indicator character '&' can be
1004 * escaped by doubling it in the string, causing a single
1005 * '&' to be displayed.
1006 * </p><p>
1007 * Note that a Button can display an image and text simultaneously
1008 * on Windows (starting with XP), GTK+ and OSX. On other platforms,
1009 * a Button that has an image and text set into it will display the
1010 * image or text that was set most recently.
1011 * </p><p>
1012 * Also note, if control characters like '\n', '\t' etc. are used
1013 * in the string, then the behavior is platform dependent.
1014 * </p>
1015 * @param string the new text
1016 *
1017 * @exception IllegalArgumentException <ul>
1018 * <li>ERROR_NULL_ARGUMENT - if the text is null</li>
1019 * </ul>
1020 * @exception SWTException <ul>
1021 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
1022 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
1023 * </ul>
1024 */
setText(String string)1025 public void setText (String string) {
1026 checkWidget ();
1027 if (string == null) error (SWT.ERROR_NULL_ARGUMENT);
1028 if ((style & SWT.ARROW) != 0) return;
1029 text = string;
1030 _setText (string);
1031 }
1032
1033 @Override
updateTextDirection(int textDirection)1034 boolean updateTextDirection(int textDirection) {
1035 if (super.updateTextDirection(textDirection)) {
1036 // TODO: Keep for now, to follow up
1037 // int flags = SWT.RIGHT_TO_LEFT | SWT.LEFT_TO_RIGHT;
1038 // style &= ~SWT.MIRRORED;
1039 // style &= ~flags;
1040 // style |= textDirection & flags;
1041 // updateOrientation ();
1042 // checkMirrored ();
1043 return true;
1044 }
1045 return false;
1046 }
1047
updateImageList()1048 void updateImageList () {
1049 if (imageList != null) {
1050 BUTTON_IMAGELIST buttonImageList = new BUTTON_IMAGELIST ();
1051 OS.SendMessage (handle, OS.BCM_GETIMAGELIST, 0, buttonImageList);
1052 if (imageList != null) imageList.dispose ();
1053 imageList = new ImageList (style & SWT.RIGHT_TO_LEFT);
1054 if (OS.IsWindowEnabled (handle)) {
1055 imageList.add (image);
1056 } else {
1057 if (disabledImage != null) disabledImage.dispose ();
1058 disabledImage = new Image (display, image, SWT.IMAGE_DISABLE);
1059 imageList.add (disabledImage);
1060 }
1061 buttonImageList.himl = imageList.getHandle ();
1062 OS.SendMessage (handle, OS.BCM_SETIMAGELIST, 0, buttonImageList);
1063 /*
1064 * Bug in Windows. Under certain cirumstances yet to be
1065 * isolated, BCM_SETIMAGELIST does not redraw the control
1066 * when an image is set. The fix is to force a redraw.
1067 */
1068 OS.InvalidateRect (handle, null, true);
1069 }
1070 }
1071
1072 @Override
updateOrientation()1073 void updateOrientation () {
1074 super.updateOrientation ();
1075 updateImageList ();
1076 }
1077
updateSelection(int flags)1078 void updateSelection (int flags) {
1079 if (flags != OS.SendMessage (handle, OS.BM_GETCHECK, 0, 0)) {
1080 /*
1081 * Feature in Windows. When BM_SETCHECK is used
1082 * to set the checked state of a radio or check
1083 * button, it sets the WS_TABSTOP style. This
1084 * is undocumented and unwanted. The fix is
1085 * to save and restore the window style bits.
1086 */
1087 int bits = OS.GetWindowLong (handle, OS.GWL_STYLE);
1088 if ((style & SWT.CHECK) != 0) {
1089 if (flags == OS.BST_INDETERMINATE) {
1090 bits &= ~OS.BS_CHECKBOX;
1091 bits |= OS.BS_3STATE;
1092 } else {
1093 bits |= OS.BS_CHECKBOX;
1094 bits &= ~OS.BS_3STATE;
1095 }
1096 if (bits != OS.GetWindowLong (handle, OS.GWL_STYLE)) {
1097 OS.SetWindowLong (handle, OS.GWL_STYLE, bits);
1098 }
1099 }
1100 OS.SendMessage (handle, OS.BM_SETCHECK, flags, 0);
1101 if (bits != OS.GetWindowLong (handle, OS.GWL_STYLE)) {
1102 OS.SetWindowLong (handle, OS.GWL_STYLE, bits);
1103 }
1104 }
1105 }
1106
1107 @Override
widgetStyle()1108 int widgetStyle () {
1109 int bits = super.widgetStyle ();
1110 if ((style & SWT.FLAT) != 0) bits |= OS.BS_FLAT;
1111 if ((style & SWT.ARROW) != 0) return bits | OS.BS_OWNERDRAW;
1112 if ((style & SWT.LEFT) != 0) bits |= OS.BS_LEFT;
1113 if ((style & SWT.CENTER) != 0) bits |= OS.BS_CENTER;
1114 if ((style & SWT.RIGHT) != 0) bits |= OS.BS_RIGHT;
1115 if ((style & SWT.WRAP) != 0) bits |= OS.BS_MULTILINE;
1116 if ((style & SWT.PUSH) != 0) return bits | OS.BS_PUSHBUTTON | OS.WS_TABSTOP;
1117 if ((style & SWT.CHECK) != 0) return bits | OS.BS_CHECKBOX | OS.WS_TABSTOP;
1118 if ((style & SWT.RADIO) != 0) return bits | OS.BS_RADIOBUTTON;
1119 if ((style & SWT.TOGGLE) != 0) return bits | OS.BS_PUSHLIKE | OS.BS_CHECKBOX | OS.WS_TABSTOP;
1120 if ((style & SWT.COMMAND) != 0) return bits | OS.BS_COMMANDLINK | OS.WS_TABSTOP;
1121 return bits | OS.BS_PUSHBUTTON | OS.WS_TABSTOP;
1122 }
1123
1124 @Override
windowClass()1125 TCHAR windowClass () {
1126 return ButtonClass;
1127 }
1128
1129 @Override
windowProc()1130 long windowProc () {
1131 return ButtonProc;
1132 }
1133
1134 @Override
WM_GETDLGCODE(long wParam, long lParam)1135 LRESULT WM_GETDLGCODE (long wParam, long lParam) {
1136 LRESULT result = super.WM_GETDLGCODE (wParam, lParam);
1137 if (result != null) return result;
1138 if ((style & SWT.ARROW) != 0) {
1139 return new LRESULT (OS.DLGC_STATIC);
1140 }
1141 return result;
1142 }
1143
1144 @Override
WM_GETOBJECT(long wParam, long lParam)1145 LRESULT WM_GETOBJECT (long wParam, long lParam) {
1146 /*
1147 * Ensure that there is an accessible object created for this
1148 * control because support for radio button position in group
1149 * accessibility is implemented in the accessibility package.
1150 */
1151 if ((style & SWT.RADIO) != 0) {
1152 if (accessible == null) accessible = new_Accessible (this);
1153 }
1154 return super.WM_GETOBJECT (wParam, lParam);
1155 }
1156
1157 @Override
WM_KILLFOCUS(long wParam, long lParam)1158 LRESULT WM_KILLFOCUS (long wParam, long lParam) {
1159 LRESULT result = super.WM_KILLFOCUS (wParam, lParam);
1160 if ((style & SWT.PUSH) != 0 && getDefault ()) {
1161 menuShell ().setDefaultButton (null, false);
1162 }
1163 return result;
1164 }
1165
1166 @Override
WM_LBUTTONDOWN(long wParam, long lParam)1167 LRESULT WM_LBUTTONDOWN (long wParam, long lParam) {
1168 if (ignoreMouse) return null;
1169 return super.WM_LBUTTONDOWN (wParam, lParam);
1170 }
1171
1172 @Override
WM_LBUTTONUP(long wParam, long lParam)1173 LRESULT WM_LBUTTONUP (long wParam, long lParam) {
1174 if (ignoreMouse) return null;
1175 return super.WM_LBUTTONUP (wParam, lParam);
1176 }
1177
1178 @Override
WM_SETFOCUS(long wParam, long lParam)1179 LRESULT WM_SETFOCUS (long wParam, long lParam) {
1180 /*
1181 * Feature in Windows. When Windows sets focus to
1182 * a radio button, it sets the WS_TABSTOP style.
1183 * This is undocumented and unwanted. The fix is
1184 * to save and restore the window style bits.
1185 */
1186 int bits = 0;
1187 if ((style & SWT.RADIO) != 0) {
1188 bits = OS.GetWindowLong (handle, OS.GWL_STYLE);
1189 }
1190 LRESULT result = super.WM_SETFOCUS (wParam, lParam);
1191 if ((style & SWT.RADIO) != 0) {
1192 OS.SetWindowLong (handle, OS.GWL_STYLE, bits);
1193 }
1194 if ((style & SWT.PUSH) != 0) {
1195 menuShell ().setDefaultButton (this, false);
1196 }
1197 return result;
1198 }
1199
1200 @Override
WM_SIZE(long wParam, long lParam)1201 LRESULT WM_SIZE (long wParam, long lParam) {
1202 LRESULT result = super.WM_SIZE (wParam, lParam);
1203 if (result != null) return result;
1204 if ((style & (SWT.PUSH | SWT.TOGGLE)) != 0) {
1205 if (imageList != null && text.length () != 0) {
1206 BUTTON_IMAGELIST buttonImageList = new BUTTON_IMAGELIST ();
1207 OS.SendMessage (handle, OS.BCM_GETIMAGELIST, 0, buttonImageList);
1208 buttonImageList.uAlign = OS.BUTTON_IMAGELIST_ALIGN_LEFT;
1209 buttonImageList.margin_left = computeLeftMargin ();
1210 buttonImageList.margin_right = MARGIN;
1211 OS.SendMessage (handle, OS.BCM_SETIMAGELIST, 0, buttonImageList);
1212 }
1213 }
1214 return result;
1215 }
1216
1217 @Override
WM_SYSCOLORCHANGE(long wParam, long lParam)1218 LRESULT WM_SYSCOLORCHANGE (long wParam, long lParam) {
1219 LRESULT result = super.WM_SYSCOLORCHANGE (wParam, lParam);
1220 if (result != null) return result;
1221 if (image2 != null) _setImage (image);
1222 return result;
1223 }
1224
1225 @Override
WM_UPDATEUISTATE(long wParam, long lParam)1226 LRESULT WM_UPDATEUISTATE (long wParam, long lParam) {
1227 LRESULT result = super.WM_UPDATEUISTATE (wParam, lParam);
1228 if (result != null) return result;
1229 /*
1230 * Feature in Windows. When WM_UPDATEUISTATE is sent to
1231 * a button, it sends WM_CTLCOLORBTN to get the foreground
1232 * and background. If drawing happens in WM_CTLCOLORBTN,
1233 * it will overwrite the contents of the control. The
1234 * fix is draw the button without drawing the background
1235 * and avoid the button window proc.
1236 *
1237 * NOTE: This only happens for radio, check and toggle
1238 * buttons.
1239 */
1240 if ((style & (SWT.RADIO | SWT.CHECK | SWT.TOGGLE)) != 0) {
1241 boolean redraw = findImageControl () != null;
1242 if (!redraw) {
1243 if ((state & THEME_BACKGROUND) != 0) {
1244 if (OS.IsAppThemed ()) {
1245 redraw = findThemeControl () != null;
1246 }
1247 }
1248 if (!redraw) redraw = findBackgroundControl () != null;
1249 }
1250 if (redraw) {
1251 OS.InvalidateRect (handle, null, false);
1252 long code = OS.DefWindowProc (handle, OS.WM_UPDATEUISTATE, wParam, lParam);
1253 return new LRESULT (code);
1254 }
1255 }
1256 /*
1257 * Feature in Windows. Push and toggle buttons draw directly
1258 * in WM_UPDATEUISTATE rather than damaging and drawing later
1259 * in WM_PAINT. This means that clients who hook WM_PAINT
1260 * expecting to get all the drawing will not. The fix is to
1261 * redraw the control when paint events are hooked.
1262 */
1263 if ((style & (SWT.PUSH | SWT.TOGGLE)) != 0) {
1264 if (hooks (SWT.Paint) || filters (SWT.Paint) || customDrawing()) {
1265 OS.InvalidateRect (handle, null, true);
1266 }
1267 }
1268 return result;
1269 }
1270
1271 @Override
wmCommandChild(long wParam, long lParam)1272 LRESULT wmCommandChild (long wParam, long lParam) {
1273 int code = OS.HIWORD (wParam);
1274 switch (code) {
1275 case OS.BN_CLICKED:
1276 case OS.BN_DOUBLECLICKED:
1277 if ((style & (SWT.CHECK | SWT.TOGGLE)) != 0) {
1278 setSelection (!getSelection ());
1279 } else {
1280 if ((style & SWT.RADIO) != 0) {
1281 if ((parent.getStyle () & SWT.NO_RADIO_GROUP) != 0) {
1282 setSelection (!getSelection ());
1283 } else {
1284 selectRadio ();
1285 }
1286 }
1287 }
1288 sendSelectionEvent (SWT.Selection);
1289 }
1290 return super.wmCommandChild (wParam, lParam);
1291 }
1292
1293 @Override
wmNotifyChild(NMHDR hdr, long wParam, long lParam)1294 LRESULT wmNotifyChild (NMHDR hdr, long wParam, long lParam) {
1295 switch (hdr.code) {
1296 case OS.NM_CUSTOMDRAW:
1297 // this message will not appear for owner-draw buttons (currently the ARROW button).
1298
1299 NMCUSTOMDRAW nmcd = new NMCUSTOMDRAW ();
1300 OS.MoveMemory (nmcd, lParam, NMCUSTOMDRAW.sizeof);
1301
1302 switch (nmcd.dwDrawStage) {
1303 case OS.CDDS_PREPAINT: {
1304 // buttons are ignoring SetBkColor, SetBkMode and SetTextColor
1305 if (customBackgroundDrawing()) {
1306 int pixel = buttonBackground;
1307 if ((nmcd.uItemState & OS.CDIS_SELECTED) != 0) {
1308 pixel = getDifferentColor(buttonBackground);
1309 } else if ((nmcd.uItemState & OS.CDIS_HOT) != 0) {
1310 pixel = getSlightlyDifferentColor(buttonBackground);
1311 }
1312 if ((style & SWT.TOGGLE) != 0 && isChecked()) {
1313 pixel = getDifferentColor(buttonBackground);
1314 }
1315 RECT rect = new RECT ();
1316 OS.SetRect (rect, nmcd.left+2, nmcd.top+2, nmcd.right-2, nmcd.bottom-2);
1317 long brush = OS.CreateSolidBrush(pixel);
1318 OS.FillRect(nmcd.hdc, rect, brush);
1319 OS.DeleteObject(brush);
1320 }
1321 if (customForegroundDrawing()) {
1322 /*
1323 * Check-box/Radio buttons are native widget which honors
1324 * the Win OS zoom level for both 'Square' and 'Text' part
1325 * [Note: By-design SWT doesn't control native auto-scaling]
1326 * Hence, custom fore-ground draw logic should auto-scale
1327 * text-padding as per OS Native DPI level to fix bug 506371
1328 */
1329 int radioOrCheckTextPadding = DPIUtil.autoScaleUpUsingNativeDPI(16);
1330 int border = isRadioOrCheck() ? 0 : 3;
1331 int left = nmcd.left + border;
1332 int right = nmcd.right - border;
1333 if (image != null) {
1334 GCData data = new GCData();
1335 data.device = display;
1336 GC gc = GC.win32_new (nmcd.hdc, data);
1337
1338 int margin = computeLeftMargin();
1339 int imageWidth = image.getBoundsInPixels().width;
1340 left += (imageWidth + (isRadioOrCheck() ? 2 * MARGIN : MARGIN)); // for SWT.RIGHT_TO_LEFT right and left are inverted
1341
1342 int x = margin + (isRadioOrCheck() ? radioOrCheckTextPadding : 3);
1343 int y = Math.max (0, (nmcd.bottom - image.getBoundsInPixels().height) / 2);
1344 gc.drawImage (image, DPIUtil.autoScaleDown(x), DPIUtil.autoScaleDown(y));
1345 gc.dispose ();
1346 }
1347
1348 left += isRadioOrCheck() ? radioOrCheckTextPadding : 0;
1349 RECT textRect = new RECT ();
1350 OS.SetRect (textRect, left, nmcd.top + border, right, nmcd.bottom - border);
1351
1352 // draw text
1353 char [] buffer = text.toCharArray ();
1354 int flags = 0;
1355 if ((style & SWT.WRAP) != 0) {
1356 flags |= OS.DT_WORDBREAK;
1357 if (!isRadioOrCheck() && image != null) {
1358 textRect.right -= MARGIN;
1359 }
1360 } else {
1361 flags |= OS.DT_SINGLELINE; // TODO: this always draws the prefix
1362 }
1363 OS.DrawText(nmcd.hdc, buffer, buffer.length, textRect, flags | OS.DT_CALCRECT);
1364 OS.OffsetRect(textRect, 0, Math.max(0, (nmcd.bottom - textRect.bottom - border) / 2));
1365 if (image != null) {
1366 // The default button with an image doesn't respect the text alignment. So we do the same for styled buttons.
1367 flags |= OS.DT_LEFT;
1368 if (!isRadioOrCheck()) {
1369 OS.OffsetRect(textRect, Math.max(MARGIN, (right - textRect.right) / 2 + 1), 0);
1370 }
1371 } else if ((style & SWT.LEFT) != 0) {
1372 flags |= OS.DT_LEFT;
1373 } else if ((style & SWT.RIGHT) != 0) {
1374 flags |= OS.DT_RIGHT;
1375 OS.OffsetRect(textRect, right - textRect.right, 0);
1376 } else {
1377 flags |= OS.DT_CENTER;
1378 OS.OffsetRect(textRect, (right - textRect.right) / 2, 0);
1379 }
1380 OS.SetBkMode(nmcd.hdc, OS.TRANSPARENT);
1381 OS.SetTextColor(nmcd.hdc, foreground);
1382 OS.DrawText(nmcd.hdc, buffer, buffer.length, textRect, flags);
1383
1384 // draw focus rect
1385 if ((nmcd.uItemState & OS.CDIS_FOCUS) != 0) {
1386 RECT focusRect = new RECT ();
1387 if (isRadioOrCheck()) {
1388 if (text.length() > 0) {
1389 OS.SetRect(focusRect, textRect.left-1, textRect.top, Math.min(nmcd.right, textRect.right+1), Math.min(nmcd.bottom, textRect.bottom+1));
1390 } else {
1391 /*
1392 * With custom foreground, draw focus rectangle for CheckBox
1393 * and Radio buttons considering the native text padding
1394 * value(which is DPI aware). See bug 508141 for details.
1395 */
1396 OS.SetRect (focusRect, nmcd.left+1+radioOrCheckTextPadding, nmcd.top, nmcd.right-2, nmcd.bottom-1);
1397 }
1398 } else {
1399 OS.SetRect (focusRect, nmcd.left+2, nmcd.top+3, nmcd.right-2, nmcd.bottom-3);
1400 }
1401 OS.DrawFocusRect(nmcd.hdc, focusRect);
1402 }
1403 return new LRESULT (OS.CDRF_SKIPDEFAULT);
1404 }
1405 return new LRESULT (OS.CDRF_DODEFAULT);
1406 }
1407 }
1408 break;
1409 }
1410 return super.wmNotifyChild (hdr, wParam, lParam);
1411 }
1412
1413 @Override
wmDrawChild(long wParam, long lParam)1414 LRESULT wmDrawChild (long wParam, long lParam) {
1415 if ((style & SWT.ARROW) == 0) return super.wmDrawChild (wParam, lParam);
1416 DRAWITEMSTRUCT struct = new DRAWITEMSTRUCT ();
1417 OS.MoveMemory (struct, lParam, DRAWITEMSTRUCT.sizeof);
1418 RECT rect = new RECT ();
1419 OS.SetRect (rect, struct.left, struct.top, struct.right, struct.bottom);
1420 if (OS.IsAppThemed ()) {
1421 int iStateId = OS.ABS_LEFTNORMAL;
1422 switch (style & (SWT.UP | SWT.DOWN | SWT.LEFT | SWT.RIGHT)) {
1423 case SWT.UP: iStateId = OS.ABS_UPNORMAL; break;
1424 case SWT.DOWN: iStateId = OS.ABS_DOWNNORMAL; break;
1425 case SWT.LEFT: iStateId = OS.ABS_LEFTNORMAL; break;
1426 case SWT.RIGHT: iStateId = OS.ABS_RIGHTNORMAL; break;
1427 }
1428 /*
1429 * Feature in Windows. DrawThemeBackground() does not mirror the drawing.
1430 * The fix is switch left to right and right to left.
1431 */
1432 if ((style & SWT.MIRRORED) != 0) {
1433 if ((style & (SWT.LEFT | SWT.RIGHT)) != 0) {
1434 iStateId = iStateId == OS.ABS_RIGHTNORMAL ? OS.ABS_LEFTNORMAL : OS.ABS_RIGHTNORMAL;
1435 }
1436 }
1437 /*
1438 * NOTE: The normal, hot, pressed and disabled state is
1439 * computed relying on the fact that the increment between
1440 * the direction states is invariant (always separated by 4).
1441 */
1442 if (!getEnabled ()) iStateId += OS.ABS_UPDISABLED - OS.ABS_UPNORMAL;
1443 if ((struct.itemState & OS.ODS_SELECTED) != 0) iStateId += OS.ABS_UPPRESSED - OS.ABS_UPNORMAL;
1444 OS.DrawThemeBackground (display.hScrollBarTheme (), struct.hDC, OS.SBP_ARROWBTN, iStateId, rect, null);
1445 } else {
1446 int uState = OS.DFCS_SCROLLLEFT;
1447 switch (style & (SWT.UP | SWT.DOWN | SWT.LEFT | SWT.RIGHT)) {
1448 case SWT.UP: uState = OS.DFCS_SCROLLUP; break;
1449 case SWT.DOWN: uState = OS.DFCS_SCROLLDOWN; break;
1450 case SWT.LEFT: uState = OS.DFCS_SCROLLLEFT; break;
1451 case SWT.RIGHT: uState = OS.DFCS_SCROLLRIGHT; break;
1452 }
1453 if (!getEnabled ()) uState |= OS.DFCS_INACTIVE;
1454 if ((style & SWT.FLAT) == SWT.FLAT) uState |= OS.DFCS_FLAT;
1455 if ((struct.itemState & OS.ODS_SELECTED) != 0) uState |= OS.DFCS_PUSHED;
1456 OS.DrawFrameControl (struct.hDC, rect, OS.DFC_SCROLL, uState);
1457 }
1458 return null;
1459 }
1460
1461 }
1462