| /******************************************************************************* |
| * Copyright (c) 2011 Google, Inc. |
| * All rights reserved. This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License v1.0 |
| * which accompanies this distribution, and is available at |
| * http://www.eclipse.org/legal/epl-v10.html |
| * |
| * Contributors: |
| * Google, Inc. - initial API and implementation |
| *******************************************************************************/ |
| package org.eclipse.wb.core.controls; |
| |
| import org.eclipse.swt.SWT; |
| import org.eclipse.swt.events.FocusAdapter; |
| import org.eclipse.swt.events.FocusEvent; |
| import org.eclipse.swt.events.KeyAdapter; |
| import org.eclipse.swt.events.KeyEvent; |
| import org.eclipse.swt.events.SelectionAdapter; |
| import org.eclipse.swt.events.SelectionEvent; |
| import org.eclipse.swt.graphics.Color; |
| import org.eclipse.swt.graphics.Point; |
| import org.eclipse.swt.graphics.Rectangle; |
| import org.eclipse.swt.widgets.Button; |
| import org.eclipse.swt.widgets.Composite; |
| import org.eclipse.swt.widgets.Display; |
| import org.eclipse.swt.widgets.Event; |
| import org.eclipse.swt.widgets.Layout; |
| import org.eclipse.swt.widgets.Spinner; |
| import org.eclipse.swt.widgets.Text; |
| |
| import java.text.DecimalFormat; |
| import java.text.MessageFormat; |
| import java.text.ParseException; |
| |
| /** |
| * Custom implementation of {@link Spinner}. |
| * |
| * @author scheglov_ke |
| * @coverage core.control |
| */ |
| public class CSpinner extends Composite { |
| private static final Color COLOR_VALID = Display.getCurrent().getSystemColor( |
| SWT.COLOR_LIST_BACKGROUND); |
| private static final Color COLOR_INVALID = new Color(null, 255, 230, 230); |
| private int m_minimum = 0; |
| private int m_maximum = 100; |
| private int m_increment = 1; |
| private int m_value = 0; |
| private int m_multiplier = 1; |
| private String m_formatPattern = "0"; |
| private DecimalFormat m_format = new DecimalFormat(m_formatPattern); |
| //////////////////////////////////////////////////////////////////////////// |
| // |
| // GUI fields |
| // |
| //////////////////////////////////////////////////////////////////////////// |
| private final Button m_button; |
| private final Text m_text; |
| private final Spinner m_spinner; |
| private Composite win32Hack; |
| |
| //////////////////////////////////////////////////////////////////////////// |
| // |
| // Constructor |
| // |
| //////////////////////////////////////////////////////////////////////////// |
| public CSpinner(Composite parent, int style) { |
| super(parent, style); |
| m_button = new Button(this, SWT.ARROW | SWT.DOWN); |
| { |
| int textStyle = SWT.SINGLE | SWT.RIGHT; |
| if (IS_OS_MAC_OSX_COCOA) { |
| textStyle |= SWT.BORDER; |
| } |
| m_text = new Text(this, textStyle); |
| m_text.setText("" + m_value); |
| m_text.addKeyListener(new KeyAdapter() { |
| @Override |
| public void keyPressed(KeyEvent e) { |
| if (e.keyCode == SWT.ARROW_UP || e.keyCode == SWT.ARROW_DOWN) { |
| e.doit = false; |
| updateValue(e.keyCode); |
| } |
| } |
| |
| @Override |
| public void keyReleased(KeyEvent e) { |
| try { |
| m_value = (int) (m_format.parse(m_text.getText()).doubleValue() * m_multiplier); |
| if (m_value < m_minimum || m_value > m_maximum) { |
| m_text.setBackground(COLOR_INVALID); |
| setState(MessageFormat.format( |
| Messages.CSpinner_outOfRange, |
| m_value, |
| m_minimum, |
| m_maximum)); |
| notifySelectionListeners(false); |
| } else { |
| setState(null); |
| notifySelectionListeners(true); |
| } |
| } catch (ParseException ex) { |
| setState(MessageFormat.format( |
| Messages.CSpinner_canNotParse, |
| m_text.getText(), |
| m_formatPattern)); |
| notifySelectionListeners(false); |
| } |
| } |
| }); |
| } |
| if (!IS_OS_MAC_OSX) { |
| win32Hack = new Composite(this, SWT.NONE); |
| win32Hack.setBackground(getDisplay().getSystemColor(SWT.COLOR_WHITE)); |
| win32Hack.moveAbove(null); |
| win32Hack.moveBelow(m_text); |
| } |
| { |
| m_spinner = new Spinner(this, SWT.VERTICAL); |
| m_spinner.setMinimum(0); |
| m_spinner.setMaximum(50); |
| m_spinner.setIncrement(1); |
| m_spinner.setPageIncrement(1); |
| m_spinner.setSelection(25); |
| m_spinner.addFocusListener(new FocusAdapter() { |
| @Override |
| public void focusGained(FocusEvent e) { |
| setFocus(); |
| } |
| }); |
| m_spinner.addSelectionListener(new SelectionAdapter() { |
| @Override |
| public void widgetSelected(SelectionEvent e) { |
| m_text.forceFocus(); |
| if (m_spinner.getSelection() > 25) { |
| updateValue(SWT.ARROW_UP); |
| } else { |
| updateValue(SWT.ARROW_DOWN); |
| } |
| m_spinner.setSelection(25); |
| } |
| }); |
| setBackground(getDisplay().getSystemColor(SWT.COLOR_WHITE)); |
| if (IS_OS_WINDOWS_XP || IS_OS_WINDOWS_2003) { |
| setLayout(new WindowsXpLayout()); |
| } else if (IS_OS_WINDOWS_VISTA || IS_OS_WINDOWS_7) { |
| setLayout(new WindowsVistaLayout()); |
| } else if (IS_OS_LINUX) { |
| setLayout(new LinuxLayout()); |
| } else if (IS_OS_MAC_OSX) { |
| if (IS_OS_MAC_OSX_COCOA) { |
| setLayout(new MacCocoaLayout()); |
| } else { |
| setLayout(new MacLayout()); |
| } |
| } else { |
| setLayout(new WindowsXpLayout()); |
| } |
| } |
| } |
| |
| //////////////////////////////////////////////////////////////////////////// |
| // |
| // Access |
| // |
| //////////////////////////////////////////////////////////////////////////// |
| @Override |
| public void setEnabled(boolean enabled) { |
| super.setEnabled(enabled); |
| m_text.setEnabled(enabled); |
| m_spinner.setEnabled(enabled); |
| } |
| |
| /** |
| * Sets the number of decimal places used by the receiver. |
| * <p> |
| * See {@link Spinner#setDigits(int)}. |
| */ |
| public void setDigits(int digits) { |
| m_formatPattern = "0."; |
| m_multiplier = 1; |
| for (int i = 0; i < digits; i++) { |
| m_formatPattern += "0"; |
| m_multiplier *= 10; |
| } |
| m_format = new DecimalFormat(m_formatPattern); |
| updateText(); |
| } |
| |
| /** |
| * Sets minimum and maximum using single invocation. |
| */ |
| public void setRange(int minimum, int maximum) { |
| setMinimum(minimum); |
| setMaximum(maximum); |
| } |
| |
| /** |
| * @return the minimum value that the receiver will allow. |
| */ |
| public int getMinimum() { |
| return m_minimum; |
| } |
| |
| /** |
| * Sets the minimum value that the receiver will allow. |
| */ |
| public void setMinimum(int minimum) { |
| m_minimum = minimum; |
| setSelection(Math.max(m_value, m_minimum)); |
| } |
| |
| /** |
| * Sets the maximum value that the receiver will allow. |
| */ |
| public void setMaximum(int maximum) { |
| m_maximum = maximum; |
| setSelection(Math.min(m_value, m_maximum)); |
| } |
| |
| /** |
| * Sets the amount that the receiver's value will be modified by when the up/down arrows are |
| * pressed to the argument, which must be at least one. |
| */ |
| public void setIncrement(int increment) { |
| m_increment = increment; |
| } |
| |
| /** |
| * Sets the <em>value</em>, which is the receiver's position, to the argument. If the argument is |
| * not within the range specified by minimum and maximum, it will be adjusted to fall within this |
| * range. |
| */ |
| public void setSelection(int newValue) { |
| newValue = Math.min(Math.max(m_minimum, newValue), m_maximum); |
| if (newValue != m_value) { |
| m_value = newValue; |
| updateText(); |
| // set valid state |
| setState(null); |
| } |
| } |
| |
| private void updateText() { |
| String text = m_format.format((double) m_value / m_multiplier); |
| m_text.setText(text); |
| m_text.selectAll(); |
| } |
| |
| /** |
| * @return the <em>selection</em>, which is the receiver's position. |
| */ |
| public int getSelection() { |
| return m_value; |
| } |
| |
| //////////////////////////////////////////////////////////////////////////// |
| // |
| // Update |
| // |
| //////////////////////////////////////////////////////////////////////////// |
| /** |
| * Updates {@link #m_value} into given direction. |
| */ |
| private void updateValue(int direction) { |
| // prepare new value |
| int newValue; |
| { |
| newValue = m_value; |
| if (direction == SWT.ARROW_UP) { |
| newValue += m_increment; |
| } |
| if (direction == SWT.ARROW_DOWN) { |
| newValue -= m_increment; |
| } |
| } |
| // update value |
| setSelection(newValue); |
| notifySelectionListeners(true); |
| } |
| |
| /** |
| * Sets the valid/invalid state. |
| * |
| * @param message |
| * the message to show, or <code>null</code> if valid. |
| */ |
| private void setState(String message) { |
| m_text.setToolTipText(message); |
| if (message == null) { |
| m_text.setBackground(COLOR_VALID); |
| } else { |
| m_text.setBackground(COLOR_INVALID); |
| } |
| } |
| |
| /** |
| * Notifies {@link SWT#Selection} listeners with value and state. |
| */ |
| private void notifySelectionListeners(boolean valid) { |
| Event event = new Event(); |
| event.detail = m_value; |
| event.doit = valid; |
| notifyListeners(SWT.Selection, event); |
| } |
| |
| //////////////////////////////////////////////////////////////////////////// |
| // |
| // Windows XP |
| // |
| //////////////////////////////////////////////////////////////////////////// |
| /** |
| * Implementation of {@link Layout} for Windows XP. |
| */ |
| private class WindowsXpLayout extends Layout { |
| @Override |
| protected Point computeSize(Composite composite, int wHint, int hHint, boolean flushCache) { |
| Point size = m_text.computeSize(SWT.DEFAULT, SWT.DEFAULT); |
| size.x += m_spinner.computeSize(SWT.DEFAULT, SWT.DEFAULT).x - m_spinner.getClientArea().width; |
| // add Text widget margin |
| size.y += 2; |
| // apply hints |
| if (wHint != SWT.DEFAULT) { |
| size.x = Math.min(size.x, wHint); |
| } |
| if (hHint != SWT.DEFAULT) { |
| size.y = Math.min(size.y, hHint); |
| } |
| // OK, final size |
| return size; |
| } |
| |
| @Override |
| protected void layout(Composite composite, boolean flushCache) { |
| Rectangle cRect = composite.getClientArea(); |
| if (cRect.isEmpty()) { |
| return; |
| } |
| // prepare size of Text |
| Point tSize = m_text.computeSize(SWT.DEFAULT, SWT.DEFAULT); |
| // prepare size of Spinner |
| Point sSize; |
| sSize = m_spinner.computeSize(SWT.DEFAULT, SWT.DEFAULT, flushCache); |
| sSize.y = Math.min(sSize.y, Math.min(tSize.y, cRect.height)); |
| sSize.x = Math.min(sSize.x, cRect.width); |
| // prepare width of arrows part of Spinner |
| int arrowWidth = m_button.computeSize(SWT.DEFAULT, SWT.DEFAULT).x; |
| // set bounds for Spinner and Text |
| m_spinner.setBounds( |
| cRect.x + cRect.width - sSize.x + 1, |
| cRect.y - 1, |
| sSize.x, |
| cRect.height + 2); |
| m_text.setBounds(cRect.x, cRect.y + 1, cRect.width - arrowWidth, tSize.y); |
| win32Hack.setBounds(cRect.x, cRect.y, cRect.width - arrowWidth, sSize.y); |
| } |
| } |
| //////////////////////////////////////////////////////////////////////////// |
| // |
| // Windows Vista |
| // |
| //////////////////////////////////////////////////////////////////////////// |
| /** |
| * Implementation of {@link Layout} for Windows Vista. |
| */ |
| private class WindowsVistaLayout extends Layout { |
| @Override |
| protected Point computeSize(Composite composite, int wHint, int hHint, boolean flushCache) { |
| Point size = m_text.computeSize(SWT.DEFAULT, SWT.DEFAULT); |
| size.x += m_spinner.computeSize(SWT.DEFAULT, SWT.DEFAULT).x - m_spinner.getClientArea().width; |
| // add Text widget margin |
| size.y += 3; |
| // apply hints |
| if (wHint != SWT.DEFAULT) { |
| size.x = Math.min(size.x, wHint); |
| } |
| if (hHint != SWT.DEFAULT) { |
| size.y = Math.min(size.y, hHint); |
| } |
| // OK, final size |
| return size; |
| } |
| |
| @Override |
| protected void layout(Composite composite, boolean flushCache) { |
| Rectangle cRect = composite.getClientArea(); |
| if (cRect.isEmpty()) { |
| return; |
| } |
| // prepare size of Text |
| Point tSize = m_text.computeSize(SWT.DEFAULT, SWT.DEFAULT); |
| // prepare size of Spinner |
| Point sSize; |
| sSize = m_spinner.computeSize(SWT.DEFAULT, SWT.DEFAULT, flushCache); |
| sSize.y = Math.min(sSize.y, Math.min(tSize.y, cRect.height)); |
| sSize.x = Math.min(sSize.x, cRect.width); |
| // prepare width of arrows part of Spinner |
| int arrowWidth = m_button.computeSize(SWT.DEFAULT, SWT.DEFAULT).x; |
| // set bounds for Spinner and Text |
| m_spinner.setBounds( |
| cRect.x + cRect.width - sSize.x + 1, |
| cRect.y - 1, |
| sSize.x, |
| cRect.height + 2); |
| m_text.setBounds(cRect.x, cRect.y + 1, cRect.width - arrowWidth, tSize.y); |
| win32Hack.setBounds(cRect.x, cRect.y, cRect.width - arrowWidth, sSize.y); |
| } |
| } |
| //////////////////////////////////////////////////////////////////////////// |
| // |
| // Linux |
| // |
| //////////////////////////////////////////////////////////////////////////// |
| /** |
| * Implementation of {@link Layout} for Linux. |
| */ |
| private class LinuxLayout extends Layout { |
| @Override |
| protected Point computeSize(Composite composite, int wHint, int hHint, boolean flushCache) { |
| Point size = m_text.computeSize(SWT.DEFAULT, SWT.DEFAULT); |
| size.x += m_spinner.computeSize(SWT.DEFAULT, SWT.DEFAULT).x - m_spinner.getClientArea().width; |
| // apply hints |
| if (wHint != SWT.DEFAULT) { |
| size.x = Math.min(size.x, wHint); |
| } |
| if (hHint != SWT.DEFAULT) { |
| size.y = Math.min(size.y, hHint); |
| } |
| // OK, final size |
| return size; |
| } |
| |
| @Override |
| protected void layout(Composite composite, boolean flushCache) { |
| Rectangle cRect = composite.getClientArea(); |
| if (cRect.isEmpty()) { |
| return; |
| } |
| // prepare size of Text |
| Point tSize = m_text.computeSize(SWT.DEFAULT, SWT.DEFAULT); |
| // prepare size of Spinner |
| Point sSize; |
| sSize = m_spinner.computeSize(SWT.DEFAULT, SWT.DEFAULT, flushCache); |
| sSize.y = Math.min(sSize.y, Math.min(tSize.y, cRect.height)); |
| sSize.x = Math.min(sSize.x, cRect.width); |
| // prepare width of arrows part of Spinner |
| int arrowWidth; |
| { |
| m_spinner.setSize(sSize); |
| arrowWidth = sSize.x - m_spinner.getClientArea().width; |
| } |
| // set bounds for Spinner and Text |
| m_spinner.setBounds(cRect.x + cRect.width - sSize.x, cRect.y - 2, sSize.x, cRect.height + 4); |
| m_text.setBounds(cRect.x, cRect.y, cRect.width - arrowWidth, tSize.y); |
| } |
| } |
| //////////////////////////////////////////////////////////////////////////// |
| // |
| // MacOSX |
| // |
| //////////////////////////////////////////////////////////////////////////// |
| /** |
| * Implementation of {@link Layout} for MacOSX. |
| */ |
| private class MacLayout extends Layout { |
| @Override |
| protected Point computeSize(Composite composite, int wHint, int hHint, boolean flushCache) { |
| Point size = m_text.computeSize(SWT.DEFAULT, SWT.DEFAULT); |
| size.x += m_spinner.computeSize(SWT.DEFAULT, SWT.DEFAULT).x - m_spinner.getClientArea().width; |
| // add Text widget margin |
| size.y += 4; |
| // apply hints |
| if (wHint != SWT.DEFAULT) { |
| size.x = Math.min(size.x, wHint); |
| } |
| if (hHint != SWT.DEFAULT) { |
| size.y = Math.min(size.y, hHint); |
| } |
| // OK, final size |
| return size; |
| } |
| |
| @Override |
| protected void layout(Composite composite, boolean flushCache) { |
| Rectangle cRect = composite.getClientArea(); |
| if (cRect.isEmpty()) { |
| return; |
| } |
| // prepare size of Text |
| Point tSize = m_text.computeSize(SWT.DEFAULT, SWT.DEFAULT); |
| tSize.y += 4; |
| // prepare size of Spinner |
| Point sSize; |
| sSize = m_spinner.computeSize(SWT.DEFAULT, SWT.DEFAULT, flushCache); |
| sSize.y = Math.min(sSize.y, Math.min(tSize.y, cRect.height)); |
| sSize.x = Math.min(sSize.x, cRect.width); |
| // prepare width of arrows part of Spinner |
| int arrowWidth = m_button.computeSize(-1, -1).x; |
| // set bounds for Spinner and Text |
| m_spinner.setBounds(cRect.x + cRect.width - sSize.x, cRect.y, sSize.x, cRect.height); |
| m_text.setBounds(cRect.x, cRect.y + 2, cRect.width - arrowWidth - 2, tSize.y); |
| } |
| } |
| /** |
| * Implementation of {@link Layout} for MacOSX Cocoa. |
| */ |
| private class MacCocoaLayout extends Layout { |
| @Override |
| protected Point computeSize(Composite composite, int wHint, int hHint, boolean flushCache) { |
| Point textSize = m_text.computeSize(SWT.DEFAULT, SWT.DEFAULT); |
| Point spinnerSize = m_spinner.computeSize(SWT.DEFAULT, SWT.DEFAULT); |
| int arrowWidth = m_button.computeSize(SWT.DEFAULT, SWT.DEFAULT).x; |
| int width = textSize.x + arrowWidth; |
| int height = Math.max(spinnerSize.y, textSize.y); |
| // apply hints |
| if (wHint != SWT.DEFAULT) { |
| width = Math.min(width, wHint); |
| } |
| if (hHint != SWT.DEFAULT) { |
| height = Math.min(height, hHint); |
| } |
| return new Point(width, height); |
| } |
| |
| @Override |
| protected void layout(Composite composite, boolean flushCache) { |
| Rectangle clientArea = composite.getClientArea(); |
| if (clientArea.isEmpty()) { |
| return; |
| } |
| // prepare size of Spinner |
| Point spinnerSize = m_spinner.computeSize(SWT.DEFAULT, SWT.DEFAULT, flushCache); |
| // prepare width of arrows part of Spinner |
| int arrowWidth = m_button.computeSize(SWT.DEFAULT, SWT.DEFAULT).x; |
| m_spinner.setBounds(clientArea.x + clientArea.width - arrowWidth - 1, clientArea.y |
| + clientArea.height |
| - spinnerSize.y, arrowWidth + 2, spinnerSize.y); |
| m_text.setBounds( |
| clientArea.x + 2, |
| clientArea.y + 2, |
| clientArea.width - arrowWidth - 5, |
| clientArea.y + clientArea.height - 4); |
| } |
| } |
| |
| //////////////////////////////////////////////////////////////////////////// |
| // |
| // System utils |
| // |
| //////////////////////////////////////////////////////////////////////////// |
| private static final String OS_NAME = System.getProperty("os.name"); |
| private static final String OS_VERSION = System.getProperty("os.version"); |
| private static final String WS_TYPE = SWT.getPlatform(); |
| private static final boolean IS_OS_MAC_OSX = isOS("Mac OS X"); |
| private static final boolean IS_OS_MAC_OSX_COCOA = IS_OS_MAC_OSX && "cocoa".equals(WS_TYPE); |
| private static final boolean IS_OS_LINUX = isOS("Linux") || isOS("LINUX"); |
| private static final boolean IS_OS_WINDOWS_XP = isWindowsVersion("5.1"); |
| private static final boolean IS_OS_WINDOWS_2003 = isWindowsVersion("5.2"); |
| private static final boolean IS_OS_WINDOWS_VISTA = isWindowsVersion("6.0"); |
| private static final boolean IS_OS_WINDOWS_7 = isWindowsVersion("6.1"); |
| |
| private static boolean isOS(String osName) { |
| return OS_NAME != null && OS_NAME.startsWith(osName); |
| } |
| |
| private static boolean isWindowsVersion(String windowsVersion) { |
| return isOS("Windows") && OS_VERSION != null && OS_VERSION.startsWith(windowsVersion); |
| } |
| } |