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 '&amp;' 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 '&amp;' can be
1004  * escaped by doubling it in the string, causing a single
1005  * '&amp;' 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