import java.util.EventObject;
import javax.swing.AbstractAction;
+import javax.swing.AbstractSpinnerModel;
import javax.swing.Action;
import javax.swing.BoundedRangeModel;
import javax.swing.SpinnerModel;
-import javax.swing.SpinnerNumberModel;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import net.sf.openrocket.unit.UnitGroup;
import net.sf.openrocket.util.BugException;
import net.sf.openrocket.util.ChangeSource;
+import net.sf.openrocket.util.ExpressionParser;
+import net.sf.openrocket.util.InvalidExpressionException;
import net.sf.openrocket.util.Invalidatable;
import net.sf.openrocket.util.Invalidator;
import net.sf.openrocket.util.MathUtil;
public class DoubleModel implements StateChangeListener, ChangeSource, Invalidatable {
private static final LogHelper log = Application.getLogger();
-
+
public static final DoubleModel ZERO = new DoubleModel(0);
//////////// JSpinner Model ////////////
/**
- * Model suitable for JSpinner using JSpinner.NumberEditor. It extends SpinnerNumberModel
+ * Model suitable for JSpinner.
+ * Note: Previously used using JSpinner.NumberEditor and extended SpinnerNumberModel
* to be compatible with the NumberEditor, but only has the necessary methods defined.
+ * This is still the design, but now extends AbstractSpinnerModel to allow other characters
+ * to be entered so that fractional units and expressions can be used.
*/
- private class ValueSpinnerModel extends SpinnerNumberModel implements Invalidatable {
+ public class ValueSpinnerModel extends AbstractSpinnerModel implements Invalidatable {
+
+ private ExpressionParser parser = new ExpressionParser();
@Override
public Object getValue() {
- return currentUnit.toUnit(DoubleModel.this.getValue());
+ return currentUnit.toString(DoubleModel.this.getValue());
}
@Override
" value=" + value + ", currently firing events");
return;
}
- Number num = (Number) value;
- double newValue = num.doubleValue();
- double converted = currentUnit.fromUnit(newValue);
- log.user("SpinnerModel setValue called for " + DoubleModel.this.toString() + " newValue=" + newValue +
- " converted=" + converted);
- DoubleModel.this.setValue(converted);
+ Number num = Double.NaN;
+
+ // Set num if possible
+ if (value instanceof Number) {
+ num = (Number) value;
+ }
+ else if (value instanceof String) {
+ try {
+ String newValString = (String) value;
+ num = parser.parse(newValString);
+ } catch (InvalidExpressionException e) {
+ // Ignore
+ }
+ }
+ // Update the doublemodel with the new number or return to the last number if not possible
+ if (((Double) num).isNaN()) {
+ DoubleModel.this.setValue(lastValue);
+ log.user("SpinnerModel could not set value for " + DoubleModel.this.toString() + ". Could not convert " + value.toString());
+ }
+ else {
+ double newValue = num.doubleValue();
+ double converted = currentUnit.fromUnit(newValue);
+
+ log.user("SpinnerModel setValue called for " + DoubleModel.this.toString() + " newValue=" + newValue +
+ " converted=" + converted);
+ DoubleModel.this.setValue(converted);
+ }
+
+ // Force a refresh if text doesn't match up exactly with the stored value
+ if (!((Double) lastValue).toString().equals(this.getValue().toString())) {
+ DoubleModel.this.fireStateChanged();
+ log.debug("SpinnerModel " + DoubleModel.this.toString() + " refresh forced because string did not match actual value.");
+ }
}
@Override
return d;
}
-
- @Override
- public Comparable<Double> getMinimum() {
- return currentUnit.toUnit(minValue);
- }
-
- @Override
- public Comparable<Double> getMaximum() {
- return currentUnit.toUnit(maxValue);
- }
-
-
@Override
public void addChangeListener(ChangeListener l) {
DoubleModel.this.addChangeListener(l);
return new ValueSpinnerModel();
}
-
-
-
-
//////////// JSlider model ////////////
private class ValueSliderModel implements BoundedRangeModel, StateChangeListener, Invalidatable {
// Non-linear multiplier, exponent and constant
private double quad2, quad1, quad0;
-
+
public ValueSliderModel(DoubleModel min, DoubleModel max) {
this.islinear = true;
linearPosition = 1.0;
}
-
+
/**
* Generate a linear model from min to max.
*/
public ValueSliderModel(double min, double mid, DoubleModel max) {
this(min, 0.5, mid, max);
}
-
+
/*
* v(x) = mul * x^exp + add
*
* v(1) = mul + add = max
* v'(pos) = mul*exp * pos^(exp-1) = linearMul
*/
- public ValueSliderModel(double min, double pos, double mid, double max ) {
+ public ValueSliderModel(double min, double pos, double mid, double max) {
this(min, pos, mid, new DoubleModel(max));
}
+
public ValueSliderModel(double min, double pos, double mid, DoubleModel max) {
this.min = new DoubleModel(min);
this.mid = new DoubleModel(mid);
this.max = max;
-
+
this.islinear = false;
max.addChangeListener(this);
@Override
public void stateChanged(EventObject e) {
// Min or max range has changed.
- if ( !islinear ) {
- double midValue = (max.getValue() - min.getValue()) /3.0;
+ if (!islinear) {
+ double midValue = (max.getValue() - min.getValue()) / 3.0;
mid.setValue(midValue);
updateExponentialParameters();
}
}
-
-
-
+
+
+
//////////// Action model ////////////
private class AutomaticActionModel extends AbstractAction implements StateChangeListener, Invalidatable {
}
-
-
-
+
+
+
//////////// Main model /////////////
/*
* The main model handles all values in SI units, i.e. no conversion is made within the model.
*/
-
+
private final ChangeSource source;
private final String valueName;
private final double multiplier;
private String toString = null;
-
+
private int firing = 0; // >0 when model itself is sending events
-
+
// Used to differentiate changes in valueName and other changes in the component:
private double lastValue = 0;
private boolean lastAutomatic = false;
}
-
+
/**
* Returns the value of the variable (in SI units).
*/
}
-
+
/**
* Add a listener to the model. Adds the model as a listener to the value source if this
* is the first listener.
// Copy the list before iterating to prevent concurrent modification exceptions.
EventListener[] ls = listeners.toArray(new EventListener[0]);
for (EventListener l : ls) {
- if ( l instanceof StateChangeListener ) {
- ((StateChangeListener)l).stateChanged(event);
- } else if ( l instanceof ChangeListener ) {
- ((ChangeListener)l).stateChanged(cevent);
+ if (l instanceof StateChangeListener) {
+ ((StateChangeListener) l).stateChanged(event);
+ } else if (l instanceof ChangeListener) {
+ ((ChangeListener) l).stateChanged(cevent);
}
}
firing--;