// This file is part of PleoCommand: // Interactively control Pleo with psychobiological parameters // // Copyright (C) 2010 Oliver Hoffmann - Hoffmann_Oliver@gmx.de // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License // as published by the Free Software Foundation; either version 2 // of the License, or (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software // Foundation, Inc., 51 Franklin Street, Boston, USA. package pleocmd.cfg; import java.awt.Dimension; import java.text.ParseException; import java.util.List; import javax.swing.JSpinner; import javax.swing.JSpinner.DefaultEditor; import javax.swing.SpinnerNumberModel; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; import javax.swing.event.UndoableEditEvent; import javax.swing.event.UndoableEditListener; import pleocmd.Log; import pleocmd.exc.ConfigurationException; import pleocmd.exc.InternalException; import pleocmd.itfc.gui.Layouter; abstract class ConfigNumber<E extends Number> extends ConfigValue { private E content; private final E min; private final E max; private final E step; private JSpinner sp; private boolean internalMod; public ConfigNumber(final String label, final E min, final E max, final E step) { super(label); if (lessThan(max, min)) throw new IllegalArgumentException(String.format( "min (%s) must not be larger than max (%s)", min, max)); this.min = min; this.max = max; this.step = step; try { setContent(min); } catch (final ConfigurationException e) { throw new InternalException(e); } } protected abstract boolean lessThan(E nr1, E nr2); protected abstract E valueOf(String str) throws ConfigurationException; @Override public final E getContent() { return content; } public final void setContent(final E content) throws ConfigurationException { if (lessThan(content, min) || lessThan(max, content)) throw new ConfigurationException("%s not between %s and %s", content, min, max); this.content = content; if (sp != null) sp.setValue(content); } public final E getContentGUI() { try { return sp == null ? content : valueOf(sp.getValue().toString()); } catch (final ConfigurationException e) { return null; } } public final void setContentGUI(final E content) { internalMod = true; try { if (sp != null) sp.setValue(content); } finally { internalMod = false; } } public final E getMin() { return min; } public final E getMax() { return max; } @Override public final String asString() { return String.valueOf(content); } @Override public final void setFromString(final String string) throws ConfigurationException { try { setContent(valueOf(string)); } catch (final NumberFormatException e) { throw new ConfigurationException("Invalid number: '%s'", string); } } @Override final List<String> asStrings() { throw new UnsupportedOperationException(); } @Override final void setFromStrings(final List<String> strings) { throw new UnsupportedOperationException(); } @Override final boolean isSingleLined() { return true; } @Override @SuppressWarnings("unchecked") // all Number implementations are Comparable to themselves // but we can't express this in Java genericals :( public final boolean insertGUIComponents(final Layouter lay) { sp = new JSpinner(new SpinnerNumberModel(content, (Comparable<E>) min, (Comparable<E>) max, step)); sp.addChangeListener(new ChangeListener() { @Override public void stateChanged(final ChangeEvent e) { if (!isInternalMod()) invokeChangingContent(getSp().getValue()); } }); ((DefaultEditor) sp.getEditor()).getTextField().getDocument() .addUndoableEditListener(new UndoableEditListener() { @Override public void undoableEditHappened(final UndoableEditEvent e) { if (!isInternalMod()) try { getSp().commitEdit(); invokeChangingContent(getSp().getValue()); } catch (final ParseException exc) { // silently ignore invalid content here } } }); sp.setPreferredSize(new Dimension(150, sp.getMinimumSize().height)); lay.add(sp, true); invokeChangingContent(sp.getValue()); return false; } @Override public final void setFromGUIComponents() { try { sp.commitEdit(); setContent(valueOf(sp.getValue().toString())); } catch (final ConfigurationException e) { Log.error(e, "Cannot set value '%s'", getLabel()); } catch (final ParseException e) { Log.error(e, "Cannot set value '%s'", getLabel()); } } @Override public void setGUIEnabled(final boolean enabled) { if (sp != null) sp.setEnabled(enabled); } protected JSpinner getSp() { return sp; } protected boolean isInternalMod() { return internalMod; } }