package org.atdl4j.ui.swing.widget; import java.awt.Color; import java.io.Serializable; import java.math.BigDecimal; import java.text.DecimalFormat; import java.text.NumberFormat; import java.text.ParseException; import java.util.Locale; import javax.swing.AbstractSpinnerModel; import javax.swing.JComponent; import javax.swing.JFormattedTextField; import javax.swing.JSpinner; import javax.swing.JTextField; import javax.swing.SpinnerModel; import javax.swing.SpinnerNumberModel; import javax.swing.text.DefaultFormatterFactory; import javax.swing.text.NumberFormatter; public class SwingNullableSpinner extends JSpinner { private static final long serialVersionUID = 2947835451995064559L; 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 SwingNullableSpinner() { super(); setModel(new SpinnerNumberModelNull()); setValue(null); } protected JComponent createEditor(SpinnerModel model) { return new NumberEditorNull(this); } public class SpinnerNumberModelNull extends AbstractSpinnerModel implements Serializable { private static final long serialVersionUID = -7274426043990492783L; private Number stepSize, value; private Comparable minimum, maximum; public SpinnerNumberModelNull(Number value, Comparable minimum, Comparable maximum, Number stepSize) { this.value = value; this.minimum = minimum; this.maximum = maximum; this.stepSize = stepSize; } public SpinnerNumberModelNull(int value, int minimum, int maximum, int stepSize) { this(new Integer(value), new Integer(minimum), new Integer(maximum), new Integer(stepSize)); } public SpinnerNumberModelNull(double value, double minimum, double maximum, double stepSize) { this(new BigDecimal(value), new BigDecimal(minimum), new BigDecimal(maximum), new BigDecimal(stepSize)); } public SpinnerNumberModelNull() { this(new BigDecimal(0), null, null, new BigDecimal(1.00)); } public void setMinimum(Comparable minimum) { if ((minimum == null) ? (this.minimum != null) : !minimum.equals(this.minimum)) { this.minimum = minimum; fireStateChanged(); } } public Comparable getMinimum() { return minimum; } public void setMaximum(Comparable maximum) { if ((maximum == null) ? (this.maximum != null) : !maximum.equals(this.maximum)) { this.maximum = maximum; fireStateChanged(); } } public Comparable getMaximum() { return maximum; } public void setStepSize(Number stepSize) { if (stepSize == null) { throw new IllegalArgumentException("null stepSize"); } if (!stepSize.equals(this.stepSize)) { this.stepSize = stepSize; fireStateChanged(); } } public Number getStepSize() { return stepSize; } private Number incrValue(int dir) { Number newValue; if ((value instanceof Long) || (value instanceof Integer) || (value instanceof Short) || (value instanceof Byte)) { if (value == null) value = 0; long v = value.longValue() + (stepSize.longValue() * (long) dir); if (value instanceof Long) { newValue = new Long(v); } else if (value instanceof Integer) { newValue = new Integer((int) v); } else if (value instanceof Short) { newValue = new Short((short) v); } else { newValue = new Byte((byte) v); } } else { if (value instanceof Float) { if (value == null) value = 0.; double v = value.doubleValue() + (stepSize.doubleValue() * (double) dir); newValue = new Float(v); } else if (value instanceof Double) { if (value == null) value = 0.; double v = value.doubleValue() + (stepSize.doubleValue() * (double) dir); newValue = new Double(v); } else { if (value == null) value = 0.; double v = value.doubleValue() + (stepSize.doubleValue() * (double) dir); newValue = new BigDecimal(v); } } if ((maximum != null) && (maximum.compareTo(newValue) < 0.)) { return null; } if ((minimum != null) && (minimum.compareTo(newValue) > 0.)) { return null; } else { return newValue; } } public Object getNextValue() { return incrValue(+1); } public Object getPreviousValue() { return incrValue(-1); } public Number getNumber() { return value; } public Object getValue() { return value; } public void setValue(Object value) { if ((this.value != null) && (value == null)){ this.value = null; fireStateChanged(); } else if ((value != null) && !value.equals(this.value)) { if (value instanceof Number) { this.value = (Number)value; } fireStateChanged(); } } } public static class NumberEditorNull extends DefaultEditor { private static final long serialVersionUID = 4927264073154690869L; private static DecimalFormat getDefaultPattern(Locale locale) { return (DecimalFormat) NumberFormat.getNumberInstance(locale); } public NumberEditorNull(JSpinner spinner) { this(spinner, getDefaultPattern(spinner.getLocale())); } public NumberEditorNull(JSpinner spinner, String decimalFormatPattern) { // this(spinner, new DecimalFormat(decimalFormatPattern)); this(spinner, new DecimalFormat("0.######")); } private NumberEditorNull(JSpinner spinner, DecimalFormat format) { super(spinner); if (!(spinner.getModel() instanceof SpinnerNumberModelNull)) { return; } SpinnerNumberModelNull model = (SpinnerNumberModelNull) spinner.getModel(); NumberFormatter formatter = new NumberEditorFormatterNull(model, format); DefaultFormatterFactory factory = new DefaultFormatterFactory(formatter); JFormattedTextField ftf = getTextField(); ftf.setEditable(true); ftf.setFormatterFactory(factory); ftf.setHorizontalAlignment(JTextField.RIGHT); try { String maxString = formatter.valueToString(model.getMinimum()); String minString = formatter.valueToString(model.getMaximum()); ftf.setColumns(Math.max(maxString.length(), minString.length())); } catch (ParseException e) { // TBD should throw a chained error here } } public DecimalFormat getFormat() { return (DecimalFormat) ((NumberFormatter) (getTextField().getFormatter())).getFormat(); } public SpinnerNumberModel getModel() { return (SpinnerNumberModel) (getSpinner().getModel()); } } private static class NumberEditorFormatterNull extends NumberFormatter { private static final long serialVersionUID = 4731911867350591824L; private final SpinnerNumberModelNull model; NumberEditorFormatterNull(SpinnerNumberModelNull model, NumberFormat format) { super(format); this.model = model; setValueClass(model.getValue().getClass()); setCommitsOnValidEdit(true); } public void setMinimum(Comparable min) { model.setMinimum(min); } public Comparable getMinimum() { return model.getMinimum(); } public void setMaximum(Comparable max) { model.setMaximum(max); } public Comparable getMaximum() { return model.getMaximum(); } /* (non-Javadoc) * @see javax.swing.text.InternationalFormatter#valueToString(java.lang.Object) */ @Override public String valueToString(Object value) throws ParseException { if (value == null) { getFormattedTextField().setBackground(Color.white); return ""; } String retVal = null; try { retVal = super.valueToString(value); getFormattedTextField().setBackground(Color.white); } catch(ParseException ex){ getFormattedTextField().setBackground(Color.red); throw ex; } return retVal; } /* (non-Javadoc) * @see javax.swing.text.InternationalFormatter#stringToValue(java.lang.String) */ @Override public Object stringToValue(String text) throws ParseException { if ("".equals(text)) { getFormattedTextField().setBackground(Color.white); return null; } Object retVal = null; try { retVal = super.stringToValue(text); try { Double.parseDouble(text); if (!text.contains("f") && !text.contains("F") && !text.contains("d") && !text.contains("D")) { getFormattedTextField().setBackground(Color.white); } else { throw new NumberFormatException(); } } catch(NumberFormatException exe){ getFormattedTextField().setBackground(Color.red); } } catch(ParseException ex){ getFormattedTextField().setBackground(Color.red); throw ex; } return retVal; } } }