Merge commit '42b2e5ca519766e37ce6941ba4faecc9691cc403' into upstream
[debian/openrocket] / core / src / net / sf / openrocket / gui / adaptors / DoubleModel.java
index 820a54a66c4508d3f9f67ac3afd79c50106bb42c..3216f0a7d7bf7d36f6c995b2b346736e0752cd8a 100644 (file)
@@ -10,10 +10,10 @@ import java.util.EventListener;
 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;
 
@@ -23,6 +23,8 @@ import net.sf.openrocket.unit.Unit;
 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;
@@ -48,20 +50,25 @@ import net.sf.openrocket.util.StateChangeListener;
 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
@@ -72,14 +79,41 @@ public class DoubleModel implements StateChangeListener, ChangeSource, Invalidat
                                                " 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
@@ -106,18 +140,6 @@ public class DoubleModel implements StateChangeListener, ChangeSource, Invalidat
                        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);
@@ -144,10 +166,6 @@ public class DoubleModel implements StateChangeListener, ChangeSource, Invalidat
                return new ValueSpinnerModel();
        }
        
-       
-
-
-
        ////////////  JSlider model  ////////////
        
        private class ValueSliderModel implements BoundedRangeModel, StateChangeListener, Invalidatable {
@@ -157,7 +175,8 @@ public class DoubleModel implements StateChangeListener, ChangeSource, Invalidat
                 * Use linear scale  value = linear1 * x + linear0  when x < linearPosition
                 * Use quadratic scale  value = quad2 * x^2 + quad1 * x + quad0  otherwise
                 */
-
+               private final boolean islinear;
+               
                // Linear in range x <= linearPosition
                private final double linearPosition;
                
@@ -169,11 +188,10 @@ public class DoubleModel implements StateChangeListener, ChangeSource, Invalidat
                //private final double linear0;
                
                // Non-linear multiplier, exponent and constant
-               private final double quad2, quad1, quad0;
+               private double quad2, quad1, quad0;
                
-               
-
                public ValueSliderModel(DoubleModel min, DoubleModel max) {
+                       this.islinear = true;
                        linearPosition = 1.0;
                        
                        this.min = min;
@@ -187,11 +205,12 @@ public class DoubleModel implements StateChangeListener, ChangeSource, Invalidat
                }
                
                
-
+               
                /**
                 * Generate a linear model from min to max.
                 */
                public ValueSliderModel(double min, double max) {
+                       this.islinear = true;
                        linearPosition = 1.0;
                        
                        this.min = new DoubleModel(min);
@@ -205,6 +224,10 @@ public class DoubleModel implements StateChangeListener, ChangeSource, Invalidat
                        this(min, 0.5, mid, max);
                }
                
+               public ValueSliderModel(double min, double mid, DoubleModel max) {
+                       this(min, 0.5, mid, max);
+               }
+               
                /*
                 * v(x)  = mul * x^exp + add
                 * 
@@ -213,32 +236,46 @@ public class DoubleModel implements StateChangeListener, ChangeSource, Invalidat
                 * v'(pos) = mul*exp * pos^(exp-1) = linearMul
                 */
                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 = new DoubleModel(max);
+                       this.max = max;
+                       
+                       this.islinear = false;
+                       
+                       max.addChangeListener(this);
                        
-
                        linearPosition = pos;
                        //linear0 = min;
                        //linear1 = (mid-min)/pos;
                        
-                       if (!(min < mid && mid <= max && 0 < pos && pos < 1)) {
+                       if (!(min < mid && mid <= max.getValue() && 0 < pos && pos < 1)) {
                                throw new IllegalArgumentException("Bad arguments for ValueSliderModel " +
                                                "min=" + min + " mid=" + mid + " max=" + max + " pos=" + pos);
                        }
                        
+                       updateExponentialParameters();
+                       
+               }
+               
+               private void updateExponentialParameters() {
+                       double pos = this.linearPosition;
+                       double minValue = this.min.getValue();
+                       double midValue = this.mid.getValue();
+                       double maxValue = this.max.getValue();
                        /*
                         * quad2..0 are calculated such that
                         *   f(pos)  = mid      - continuity
                         *   f(1)    = max      - end point
                         *   f'(pos) = linear1  - continuity of derivative
                         */
-
-                       double delta = (mid - min) / pos;
-                       quad2 = (max - mid - delta + delta * pos) / pow2(pos - 1);
-                       quad1 = (delta + 2 * (mid - max) * pos - delta * pos * pos) / pow2(pos - 1);
-                       quad0 = (mid - (2 * mid + delta) * pos + (max + delta) * pos * pos) / pow2(pos - 1);
-                       
+                       double delta = (midValue - minValue) / pos;
+                       quad2 = (maxValue - midValue - delta + delta * pos) / pow2(pos - 1);
+                       quad1 = (delta + 2 * (midValue - maxValue) * pos - delta * pos * pos) / pow2(pos - 1);
+                       quad0 = (midValue - (2 * midValue + delta) * pos + (maxValue + delta) * pos * pos) / pow2(pos - 1);
                }
                
                private double pow2(double x) {
@@ -366,6 +403,11 @@ public class DoubleModel implements StateChangeListener, ChangeSource, Invalidat
                @Override
                public void stateChanged(EventObject e) {
                        // Min or max range has changed.
+                       if (!islinear) {
+                               double midValue = (max.getValue() - min.getValue()) / 3.0;
+                               mid.setValue(midValue);
+                               updateExponentialParameters();
+                       }
                        // Fire if not already firing
                        if (firing == 0)
                                fireStateChanged();
@@ -385,14 +427,18 @@ public class DoubleModel implements StateChangeListener, ChangeSource, Invalidat
                return new ValueSliderModel(min, mid, max);
        }
        
+       public BoundedRangeModel getSliderModel(double min, double mid, DoubleModel max) {
+               return new ValueSliderModel(min, mid, max);
+       }
+       
        public BoundedRangeModel getSliderModel(double min, double pos, double mid, double max) {
                return new ValueSliderModel(min, pos, mid, max);
        }
        
        
-
-
-
+       
+       
+       
        ////////////  Action model  ////////////
        
        private class AutomaticActionModel extends AbstractAction implements StateChangeListener, Invalidatable {
@@ -491,15 +537,15 @@ public class DoubleModel implements StateChangeListener, ChangeSource, Invalidat
        }
        
        
-
-
-
+       
+       
+       
        ////////////  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;
@@ -516,14 +562,14 @@ public class DoubleModel implements StateChangeListener, ChangeSource, Invalidat
        private Unit currentUnit;
        
        private final double minValue;
-       private final double maxValue;
+       private double maxValue;
        
        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;
@@ -677,7 +723,7 @@ public class DoubleModel implements StateChangeListener, ChangeSource, Invalidat
        }
        
        
-
+       
        /**
         * Returns the value of the variable (in SI units).
         */
@@ -725,7 +771,6 @@ public class DoubleModel implements StateChangeListener, ChangeSource, Invalidat
                }
        }
        
-       
        /**
         * Returns whether setting the value automatically is available.
         */
@@ -811,7 +856,7 @@ public class DoubleModel implements StateChangeListener, ChangeSource, Invalidat
        }
        
        
-
+       
        /**
         * Add a listener to the model.  Adds the model as a listener to the value source if this
         * is the first listener.
@@ -897,10 +942,10 @@ public class DoubleModel implements StateChangeListener, ChangeSource, Invalidat
                // 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--;