/******************************************************************************* * 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. *
* 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 value, 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 selection, 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 null
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);
}
}