// Taken from http://www.java-forums.org/swt/9902-how-use-swt-spinner.html and modified. package org.atdl4j.ui.swt.util; import java.math.BigDecimal; import java.math.RoundingMode; import org.apache.log4j.Logger; import org.eclipse.swt.SWT; import org.eclipse.swt.SWTError; import org.eclipse.swt.events.SelectionListener; import org.eclipse.swt.graphics.Font; import org.eclipse.swt.graphics.Point; import org.eclipse.swt.widgets.Button; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Event; import org.eclipse.swt.widgets.Listener; import org.eclipse.swt.widgets.Text; import org.eclipse.swt.widgets.TypedListener; public class SWTNullableSpinner extends Composite { private static final Logger logger = Logger.getLogger( SWTNullableSpinner.class ); static final int BUTTON_WIDTH = 20; Text text; Button up, down; BigDecimal minimum, maximum; BigDecimal increment = new BigDecimal( 1 ); int digits = 0; public static BigDecimal MIN_INTEGER_VALUE_AS_BIG_DECIMAL = new BigDecimal( -Integer.MAX_VALUE ); public static BigDecimal MAX_INTEGER_VALUE_AS_BIG_DECIMAL = new BigDecimal( Integer.MAX_VALUE ); public SWTNullableSpinner(Composite parent, int style) { super(parent, style); /* GridData gd = new GridData(GridData.FILL_VERTICAL | GridData.HORIZONTAL_ALIGN_BEGINNING); gd.grabExcessHorizontalSpace = false; this.setLayoutData(gd);*/ text = new Text(this, SWT.SINGLE | SWT.NONE); up = new Button(this, SWT.ARROW | SWT.UP); down = new Button(this, SWT.ARROW | SWT.DOWN); text.addListener(SWT.Verify, new Listener() { public void handleEvent(Event e) { verify(e); } }); text.addListener(SWT.Modify, new Listener() { public void handleEvent(Event e) { stripExtraDecimalPoints(); } }); text.addListener(SWT.Traverse, new Listener() { public void handleEvent(Event e) { traverse(e); } }); up.addListener(SWT.Selection, new Listener() { public void handleEvent(Event e) { up(); } }); down.addListener(SWT.Selection, new Listener() { public void handleEvent(Event e) { down(); } }); addListener(SWT.Resize, new Listener() { public void handleEvent(Event e) { resize(); } }); addListener(SWT.FocusIn, new Listener() { public void handleEvent(Event e) { focusIn(); } }); text.setFont(getFont()); } void verify(Event e) { try { if (e.text==null||e.text.equals("")) return; if ( ( e.text.equals( "." ) ) || ( e.text.endsWith( "." ) ) ) { // -- Let it go, assume user is entering the decimal place portion and just entered the "." part thus far -- } else { new BigDecimal( e.text ); // validate text as a big decimal } } catch (NumberFormatException ex) { e.doit = false; } } void traverse(Event e) { switch (e.detail) { case SWT.TRAVERSE_ARROW_PREVIOUS: if (e.keyCode == SWT.ARROW_UP) { e.doit = true; e.detail = SWT.NULL; up(); } break; case SWT.TRAVERSE_ARROW_NEXT: if (e.keyCode == SWT.ARROW_DOWN) { e.doit = true; e.detail = SWT.NULL; down(); } break; } } void up() { BigDecimal tempValue = getValue(); if ( tempValue == null ) { if ( ( getMinimum() != null ) && ( ! MIN_INTEGER_VALUE_AS_BIG_DECIMAL.equals( getMinimum() ) ) ) { setValue( getMinimum() ); } else { setValue( getIncrement() ); } } else { increment( getIncrement() ); } notifyListeners(SWT.Selection, new Event()); } void down() { BigDecimal tempValue = getValue(); if ( tempValue == null ) { if ( ( getMaximum() != null ) && ( ! MAX_INTEGER_VALUE_AS_BIG_DECIMAL.equals( getMaximum() ) ) ) { setValue( getMaximum() ); } else { setValue( getIncrement() ); } } else { decrement( getIncrement() ); } notifyListeners(SWT.Selection, new Event()); } void focusIn() { text.setFocus(); } public void setFont(Font font) { super.setFont(font); text.setFont(font); } public void setValue(BigDecimal aValue) { BigDecimal tempValue = aValue; // 12/15/2010 Scott Atwell if ( ( getMinimum() != null ) && ( tempValue.compareTo( getMinimum() ) < 0 ) ) if ( ( getMinimum() != null ) && ( tempValue != null ) && ( tempValue.compareTo( getMinimum() ) < 0 ) ) { tempValue = getMinimum(); } // 12/15/2010 Scott Atwell else if ( ( getMaximum() != null ) && ( tempValue.compareTo( getMaximum() ) > 0 ) ) else if ( ( getMaximum() != null ) && ( tempValue != null ) && ( tempValue.compareTo( getMaximum() ) > 0 ) ) { tempValue = getMaximum(); } // 12/15/2010 Scott Atwell text.setText( tempValue.toPlainString() ); if ( tempValue != null ) { text.setText( tempValue.toPlainString() ); } else { text.setText( "" ); } text.selectAll(); text.setFocus(); } public BigDecimal getValue() { if ( ( text.getText()==null ) || ( text.getText().equals("") ) ) { return null; } else { // note this is what we had to use in SWTSpinnerWidget when getSelection() returned equiv of BigDecimal.unscaledValue() // return BigDecimal.valueOf( spinner.getSelection(), spinner.getDigits() ); BigDecimal tempValue = new BigDecimal( text.getText() ); BigDecimal tempValueScaled = tempValue.setScale( getDigits(), RoundingMode.HALF_UP ); if ( ! tempValue.equals( tempValueScaled ) ) { String tempNewValueString = tempValueScaled.toPlainString(); logger.debug( "tempValue: " + tempValue + " tempValueScaled: " + tempValueScaled + " tempValueScaled.toPlainString(): " + tempNewValueString ); int tempCaretPosition = text.getCaretPosition(); logger.debug( "getCaretPosition(): " + text.getCaretPosition() ); // -- Handle user entering ".12" and formatter converting to "0.12" -- avoid result of "0.21") -- if ( ( tempCaretPosition == 2 ) && ( text.getText().charAt(0) == '.' ) && ( tempNewValueString.startsWith( "0." ) ) ) { // -- we're notified at ".1" but formatted value is converting to "0.1" -- logger.debug("Incrementing CaretPosition (was " + tempCaretPosition + ") to account for formatted string having \".\" converted to \"0.\" automatically." ); tempCaretPosition++; } text.setText( tempNewValueString ); text.setSelection( tempCaretPosition, tempCaretPosition ); } return tempValueScaled; } } void resize() { Point pt = computeSize(SWT.DEFAULT, SWT.DEFAULT); int textWidth = pt.x - BUTTON_WIDTH; int buttonHeight = pt.y / 2; text.setBounds(0, 0, textWidth, pt.y); up.setBounds(textWidth, -3, BUTTON_WIDTH, buttonHeight); down.setBounds(textWidth, pt.y - buttonHeight - 3, BUTTON_WIDTH, buttonHeight); } public Point computeSize(int wHint, int hHint, boolean changed) { int width = 100; int height = 20; if (wHint != SWT.DEFAULT) width = wHint; if (hHint != SWT.DEFAULT) height = hHint; return new Point(width, height); } public void addSelectionListener(SelectionListener listener) { if (listener == null) throw new SWTError(SWT.ERROR_NULL_ARGUMENT); addListener(SWT.Selection, new TypedListener(listener)); } public void setDigits(int aDigits) { digits = aDigits; } public int getDigits() { return digits; } public void increment( BigDecimal aIncrement ) { if ( getValue() != null ) { setValue( getValue().add( aIncrement ) ); } else if ( aIncrement != null ) { setValue( aIncrement ); } } public void decrement( BigDecimal aDecrement ) { if ( getValue() != null ) { setValue( getValue().subtract( aDecrement ) ); } else if ( aDecrement != null ) { setValue( aDecrement ); } } /** * @return the minimum */ public BigDecimal getMinimum() { return this.minimum; } /** * @param aMinimum the minimum to set */ public void setMinimum(BigDecimal aMinimum) { this.minimum = aMinimum; } /** * @return the maximum */ public BigDecimal getMaximum() { return this.maximum; } /** * @param aMaximum the maximum to set */ public void setMaximum(BigDecimal aMaximum) { this.maximum = aMaximum; } /** * @return the increment */ public BigDecimal getIncrement() { return this.increment; } /** * @param aIncrement the increment to set */ public void setIncrement(BigDecimal aIncrement) { this.increment = aIncrement; } public void addListener(Listener listener) { text.addListener( SWT.Modify, listener ); } public void removeListener(Listener listener) { text.removeListener( SWT.Modify, listener ); } /** * Spinners with decimal places use DecimalFormatter to automatically render/re-render the string value. * When entering a decimal value via keyboard within an empty Spinner text field, the displayed value * will automatically adjust (eg entering "1.23" in a field with 2 decimal places will render "1.23.00") * without the use of this adjustment method. * * @return true if extra decimal point characters had to be stripped */ private boolean stripExtraDecimalPoints() { String tempText = text.getText(); if ( tempText.length() > 0 ) { int tempFirstDecimalPoint = tempText.indexOf( '.', 0 ); if ( tempFirstDecimalPoint >= 0 ) { int tempNextDecimalPoint = tempText.indexOf( '.', (tempFirstDecimalPoint + 1) ); if ( tempNextDecimalPoint >= 0 ) { logger.debug(" Multiple decimal points... tempText: " + tempText ); boolean tempDecimalPointFound = false; StringBuffer tempStringBuffer = new StringBuffer(); for ( int i=0; i < tempText.length(); i++ ) { if ( tempText.charAt( i ) == '.' ) { if ( ! tempDecimalPointFound ) { tempDecimalPointFound = true; } else { // skip/omit the character continue; } } tempStringBuffer.append( tempText.charAt( i ) ); } logger.debug(" text.setText( tempStringBuffer.toString() ): " + tempStringBuffer.toString() ); int tempCaretPosition = text.getCaretPosition(); text.setText( tempStringBuffer.toString() ); text.setSelection( tempCaretPosition, tempCaretPosition ); return true; } } } return false; } }