]> git.gag.com Git - debian/openrocket/commitdiff
Added support for Unit 'in/64' which is inches with fractional representation. Doubl...
authorkruland2607 <kruland2607@180e2498-e6e9-4542-8430-84ac67f01cd8>
Thu, 10 May 2012 02:48:07 +0000 (02:48 +0000)
committerkruland2607 <kruland2607@180e2498-e6e9-4542-8430-84ac67f01cd8>
Thu, 10 May 2012 02:48:07 +0000 (02:48 +0000)
git-svn-id: https://openrocket.svn.sourceforge.net/svnroot/openrocket/trunk@659 180e2498-e6e9-4542-8430-84ac67f01cd8

core/src/net/sf/openrocket/gui/SpinnerEditor.java
core/src/net/sf/openrocket/gui/adaptors/DoubleModel.java
core/src/net/sf/openrocket/gui/print/visitor/PartsDetailVisitorStrategy.java
core/src/net/sf/openrocket/unit/FractionalUnit.java [new file with mode: 0644]
core/src/net/sf/openrocket/unit/UnitGroup.java
core/src/net/sf/openrocket/util/FractionUtil.java [new file with mode: 0644]
core/test/net/sf/openrocket/unit/FractionalUnitTest.java [new file with mode: 0644]
core/test/net/sf/openrocket/util/FractionUtilTest.java [new file with mode: 0644]

index 843eed6a77bed1d9c21352f09d4d579de02df2bf..6d2ca1b892993aa79716872f577c67a9acfa4359 100644 (file)
@@ -9,13 +9,13 @@ import javax.swing.JSpinner;
  * @author Sampo Niskanen <sampo.niskanen@iki.fi>
  */
 
-public class SpinnerEditor extends JSpinner.NumberEditor {
-//public class SpinnerEditor extends JSpinner.DefaultEditor {
+//public class SpinnerEditor extends JSpinner.NumberEditor {
+public class SpinnerEditor extends JSpinner.DefaultEditor {
 
        public SpinnerEditor(JSpinner spinner) {
-               //super(spinner);
-               super(spinner,"0.0##");
-               //getTextField().setEditable(true);
+               super(spinner);
+               //super(spinner,"0.0##");
+               getTextField().setEditable(true);
        }
 
 }
index cd7066f8511a3e4290765932b40f3881b5258d2d..12676fcb373b351155f417956e2fadc6893b731f 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,7 @@ 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.FractionUtil;
 import net.sf.openrocket.util.Invalidatable;
 import net.sf.openrocket.util.Invalidator;
 import net.sf.openrocket.util.MathUtil;
@@ -47,23 +48,23 @@ 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
         * to be compatible with the NumberEditor, but only has the necessary methods defined.
         */
-       private class ValueSpinnerModel extends SpinnerNumberModel implements Invalidatable {
-               
+       public class ValueSpinnerModel extends AbstractSpinnerModel implements Invalidatable {
+
                @Override
                public Object getValue() {
-                       return currentUnit.toUnit(DoubleModel.this.getValue());
+                       return currentUnit.toString(DoubleModel.this.getValue());
                }
-               
+
                @Override
                public void setValue(Object value) {
                        if (firing > 0) {
@@ -72,16 +73,28 @@ public class DoubleModel implements StateChangeListener, ChangeSource, Invalidat
                                                " value=" + value + ", currently firing events");
                                return;
                        }
-                       Number num = (Number) value;
+                       Number num = 0;
+                       if ( value instanceof Number ) {
+                               num = (Number)value;
+                       } else if ( value instanceof String ) {
+                               try {
+                                       String newValString = (String)value;
+                                       num = FractionUtil.parseFraction(newValString);
+                               }
+                               catch ( java.lang.NumberFormatException nfex ) {
+                                       num = 0.0d;
+                               }
+                       }
+
                        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);
-                       
+
                }
-               
+
                @Override
                public Object getNextValue() {
                        double d = currentUnit.toUnit(DoubleModel.this.getValue());
@@ -93,7 +106,7 @@ public class DoubleModel implements StateChangeListener, ChangeSource, Invalidat
                                d = max;
                        return d;
                }
-               
+
                @Override
                public Object getPreviousValue() {
                        double d = currentUnit.toUnit(DoubleModel.this.getValue());
@@ -105,35 +118,33 @@ public class DoubleModel implements StateChangeListener, ChangeSource, Invalidat
                                d = min;
                        return d;
                }
-               
-               
+/* FIXME - put min & max back
                @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);
                }
-               
+
                @Override
                public void removeChangeListener(ChangeListener l) {
                        DoubleModel.this.removeChangeListener(l);
                }
-               
+
                @Override
                public void invalidate() {
                        DoubleModel.this.invalidate();
                }
        }
-       
+
        /**
         * Returns a new SpinnerModel with the same base as the DoubleModel.
         * The values given to the JSpinner are in the currently selected units.
@@ -143,50 +154,46 @@ public class DoubleModel implements StateChangeListener, ChangeSource, Invalidat
        public SpinnerModel getSpinnerModel() {
                return new ValueSpinnerModel();
        }
-       
-       
-
-
 
        ////////////  JSlider model  ////////////
-       
+
        private class ValueSliderModel implements BoundedRangeModel, StateChangeListener, Invalidatable {
                private static final int MAX = 1000;
-               
+
                /*
                 * 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;
-               
+
                // May be changing DoubleModels when using linear model
                private final DoubleModel min, mid, max;
-               
+
                // Linear multiplier and constant
                //private final double linear1;
                //private final double linear0;
-               
+
                // Non-linear multiplier, exponent and constant
                private double quad2, quad1, quad0;
 
                public ValueSliderModel(DoubleModel min, DoubleModel max) {
                        this.islinear = true;
                        linearPosition = 1.0;
-                       
+
                        this.min = min;
                        this.mid = max; // Never use exponential scale
                        this.max = max;
-                       
+
                        min.addChangeListener(this);
                        max.addChangeListener(this);
-                       
+
                        quad2 = quad1 = quad0 = 0; // Not used
                }
-               
-               
+
+
 
                /**
                 * Generate a linear model from min to max.
@@ -194,18 +201,18 @@ public class DoubleModel implements StateChangeListener, ChangeSource, Invalidat
                public ValueSliderModel(double min, double max) {
                        this.islinear = true;
                        linearPosition = 1.0;
-                       
+
                        this.min = new DoubleModel(min);
                        this.mid = new DoubleModel(max); // Never use exponential scale
                        this.max = new DoubleModel(max);
-                       
+
                        quad2 = quad1 = quad0 = 0; // Not used
                }
-               
+
                public ValueSliderModel(double min, double mid, double max) {
                        this(min, 0.5, mid, max);
                }
-               
+
                public ValueSliderModel(double min, double mid, DoubleModel max) {
                        this(min, 0.5, mid, max);
                }
@@ -226,22 +233,22 @@ public class DoubleModel implements StateChangeListener, ChangeSource, Invalidat
                        this.max = max;
 
                        this.islinear = false;
-                       
+
                        max.addChangeListener(this);
-                       
+
                        linearPosition = pos;
                        //linear0 = min;
                        //linear1 = (mid-min)/pos;
-                       
+
                        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();
@@ -258,11 +265,11 @@ public class DoubleModel implements StateChangeListener, ChangeSource, Invalidat
                        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) {
                        return x * x;
                }
-               
+
                @Override
                public int getValue() {
                        double value = DoubleModel.this.getValue();
@@ -270,13 +277,13 @@ public class DoubleModel implements StateChangeListener, ChangeSource, Invalidat
                                return 0;
                        if (value >= max.getValue())
                                return MAX;
-                       
+
                        double x;
                        if (value <= mid.getValue()) {
                                // Use linear scale
                                //linear0 = min;
                                //linear1 = (mid-min)/pos;
-                               
+
                                x = (value - min.getValue()) * linearPosition / (mid.getValue() - min.getValue());
                        } else {
                                // Use quadratic scale
@@ -286,8 +293,8 @@ public class DoubleModel implements StateChangeListener, ChangeSource, Invalidat
                        }
                        return (int) (x * MAX);
                }
-               
-               
+
+
                @Override
                public void setValue(int newValue) {
                        if (firing > 0) {
@@ -296,91 +303,91 @@ public class DoubleModel implements StateChangeListener, ChangeSource, Invalidat
                                                " value=" + newValue + ", currently firing events");
                                return;
                        }
-                       
+
                        double x = (double) newValue / MAX;
                        double scaledValue;
-                       
+
                        if (x <= linearPosition) {
                                // Use linear scale
                                //linear0 = min;
                                //linear1 = (mid-min)/pos;
-                               
+
                                scaledValue = (mid.getValue() - min.getValue()) / linearPosition * x + min.getValue();
                        } else {
                                // Use quadratic scale
                                scaledValue = quad2 * x * x + quad1 * x + quad0;
                        }
-                       
+
                        double converted = currentUnit.fromUnit(currentUnit.round(currentUnit.toUnit(scaledValue)));
                        log.user("SliderModel setValue called for " + DoubleModel.this.toString() + " newValue=" + newValue +
                                        " scaledValue=" + scaledValue + " converted=" + converted);
                        DoubleModel.this.setValue(converted);
                }
-               
-               
+
+
                // Static get-methods
                private boolean isAdjusting;
-               
+
                @Override
                public int getExtent() {
                        return 0;
                }
-               
+
                @Override
                public int getMaximum() {
                        return MAX;
                }
-               
+
                @Override
                public int getMinimum() {
                        return 0;
                }
-               
+
                @Override
                public boolean getValueIsAdjusting() {
                        return isAdjusting;
                }
-               
+
                // Ignore set-values
                @Override
                public void setExtent(int newExtent) {
                }
-               
+
                @Override
                public void setMaximum(int newMaximum) {
                }
-               
+
                @Override
                public void setMinimum(int newMinimum) {
                }
-               
+
                @Override
                public void setValueIsAdjusting(boolean b) {
                        isAdjusting = b;
                }
-               
+
                @Override
                public void setRangeProperties(int value, int extent, int min, int max, boolean adjusting) {
                        setValueIsAdjusting(adjusting);
                        setValue(value);
                }
-               
+
                // Pass change listeners to the underlying model
                @Override
                public void addChangeListener(ChangeListener l) {
                        DoubleModel.this.addChangeListener(l);
                }
-               
+
                @Override
                public void removeChangeListener(ChangeListener l) {
                        DoubleModel.this.removeChangeListener(l);
                }
-               
+
                @Override
                public void invalidate() {
                        DoubleModel.this.invalidate();
                }
-               
+
                @Override
                public void stateChanged(EventObject e) {
                        // Min or max range has changed.
@@ -394,48 +401,48 @@ public class DoubleModel implements StateChangeListener, ChangeSource, Invalidat
                                fireStateChanged();
                }
        }
-       
-       
+
+
        public BoundedRangeModel getSliderModel(DoubleModel min, DoubleModel max) {
                return new ValueSliderModel(min, max);
        }
-       
+
        public BoundedRangeModel getSliderModel(double min, double max) {
                return new ValueSliderModel(min, max);
        }
-       
+
        public BoundedRangeModel getSliderModel(double min, double mid, double max) {
                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 {
                private boolean oldValue = false;
-               
+
                public AutomaticActionModel() {
                        oldValue = isAutomatic();
                        addChangeListener(this);
                }
-               
-               
+
+
                @Override
                public boolean isEnabled() {
                        return isAutomaticAvailable();
                }
-               
+
                @Override
                public Object getValue(String key) {
                        if (key.equals(Action.SELECTED_KEY)) {
@@ -444,7 +451,7 @@ public class DoubleModel implements StateChangeListener, ChangeSource, Invalidat
                        }
                        return super.getValue(key);
                }
-               
+
                @Override
                public void putValue(String key, Object value) {
                        if (firing > 0) {
@@ -463,24 +470,24 @@ public class DoubleModel implements StateChangeListener, ChangeSource, Invalidat
                                super.putValue(key, value);
                        }
                }
-               
+
                // Implement a wrapper to the ChangeListeners
                ArrayList<PropertyChangeListener> propertyChangeListeners =
                                new ArrayList<PropertyChangeListener>();
-               
+
                @Override
                public void addPropertyChangeListener(PropertyChangeListener listener) {
                        propertyChangeListeners.add(listener);
                        DoubleModel.this.addChangeListener(this);
                }
-               
+
                @Override
                public void removePropertyChangeListener(PropertyChangeListener listener) {
                        propertyChangeListeners.remove(listener);
                        if (propertyChangeListeners.isEmpty())
                                DoubleModel.this.removeChangeListener(this);
                }
-               
+
                // If the value has changed, generate an event to the listeners
                @Override
                public void stateChanged(EventObject e) {
@@ -495,18 +502,18 @@ public class DoubleModel implements StateChangeListener, ChangeSource, Invalidat
                                ((PropertyChangeListener) l[i]).propertyChange(event);
                        }
                }
-               
+
                @Override
                public void actionPerformed(ActionEvent e) {
                        // Setting performed in putValue
                }
-               
+
                @Override
                public void invalidate() {
                        DoubleModel.this.invalidate();
                }
        }
-       
+
        /**
         * Returns a new Action corresponding to the changes of the automatic setting
         * property of the value model.  This may be used directly with e.g. check buttons.
@@ -516,13 +523,13 @@ public class DoubleModel implements StateChangeListener, ChangeSource, Invalidat
        public Action getAutomaticAction() {
                return new AutomaticActionModel();
        }
-       
-       
+
+
 
 
 
        ////////////  Main model  /////////////
-       
+
        /*
         * The main model handles all values in SI units, i.e. no conversion is made within the model.
         */
@@ -530,34 +537,34 @@ public class DoubleModel implements StateChangeListener, ChangeSource, Invalidat
        private final ChangeSource source;
        private final String valueName;
        private final double multiplier;
-       
+
        private final Method getMethod;
        private final Method setMethod;
-       
+
        private final Method getAutoMethod;
        private final Method setAutoMethod;
-       
+
        private final ArrayList<EventListener> listeners = new ArrayList<EventListener>();
-       
+
        private final UnitGroup units;
        private Unit currentUnit;
-       
+
        private final double minValue;
        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;
-       
+
        private Invalidator invalidator = new Invalidator(this);
-       
-       
+
+
        /**
         * Generate a DoubleModel that contains an internal double value.
         * 
@@ -566,7 +573,7 @@ public class DoubleModel implements StateChangeListener, ChangeSource, Invalidat
        public DoubleModel(double value) {
                this(value, UnitGroup.UNITS_NONE, Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY);
        }
-       
+
        /**
         * Generate a DoubleModel that contains an internal double value.
         * 
@@ -576,7 +583,7 @@ public class DoubleModel implements StateChangeListener, ChangeSource, Invalidat
        public DoubleModel(double value, UnitGroup unit) {
                this(value, unit, Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY);
        }
-       
+
        /**
         * Generate a DoubleModel that contains an internal double value.
         * 
@@ -587,7 +594,7 @@ public class DoubleModel implements StateChangeListener, ChangeSource, Invalidat
        public DoubleModel(double value, UnitGroup unit, double min) {
                this(value, unit, min, Double.POSITIVE_INFINITY);
        }
-       
+
        /**
         * Generate a DoubleModel that contains an internal double value.
         * 
@@ -600,18 +607,18 @@ public class DoubleModel implements StateChangeListener, ChangeSource, Invalidat
                this.lastValue = value;
                this.minValue = min;
                this.maxValue = max;
-               
+
                source = null;
                valueName = "Constant value";
                multiplier = 1;
-               
+
                getMethod = setMethod = null;
                getAutoMethod = setAutoMethod = null;
                units = unit;
                currentUnit = units.getDefaultUnit();
        }
-       
-       
+
+
        /**
         * Generates a new DoubleModel that changes the values of the specified component.
         * The double value is read and written using the methods "get"/"set" + valueName.
@@ -627,37 +634,37 @@ public class DoubleModel implements StateChangeListener, ChangeSource, Invalidat
                this.source = source;
                this.valueName = valueName;
                this.multiplier = multiplier;
-               
+
                this.units = unit;
                currentUnit = units.getDefaultUnit();
-               
+
                this.minValue = min;
                this.maxValue = max;
-               
+
                try {
                        getMethod = source.getClass().getMethod("get" + valueName);
                } catch (NoSuchMethodException e) {
                        throw new IllegalArgumentException("get method for value '" + valueName +
                                        "' not present in class " + source.getClass().getCanonicalName());
                }
-               
+
                Method s = null;
                try {
                        s = source.getClass().getMethod("set" + valueName, double.class);
                } catch (NoSuchMethodException e1) {
                } // Ignore
                setMethod = s;
-               
+
                // Automatic selection methods
-               
+
                Method set = null, get = null;
-               
+
                try {
                        get = source.getClass().getMethod("is" + valueName + "Automatic");
                        set = source.getClass().getMethod("set" + valueName + "Automatic", boolean.class);
                } catch (NoSuchMethodException e) {
                } // ignore
-               
+
                if (set != null && get != null) {
                        getAutoMethod = get;
                        setAutoMethod = set;
@@ -665,45 +672,45 @@ public class DoubleModel implements StateChangeListener, ChangeSource, Invalidat
                        getAutoMethod = null;
                        setAutoMethod = null;
                }
-               
+
        }
-       
+
        public DoubleModel(ChangeSource source, String valueName, double multiplier, UnitGroup unit,
                        double min) {
                this(source, valueName, multiplier, unit, min, Double.POSITIVE_INFINITY);
        }
-       
+
        public DoubleModel(ChangeSource source, String valueName, double multiplier, UnitGroup unit) {
                this(source, valueName, multiplier, unit, Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY);
        }
-       
+
        public DoubleModel(ChangeSource source, String valueName, UnitGroup unit,
                        double min, double max) {
                this(source, valueName, 1.0, unit, min, max);
        }
-       
+
        public DoubleModel(ChangeSource source, String valueName, UnitGroup unit, double min) {
                this(source, valueName, 1.0, unit, min, Double.POSITIVE_INFINITY);
        }
-       
+
        public DoubleModel(ChangeSource source, String valueName, UnitGroup unit) {
                this(source, valueName, 1.0, unit, Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY);
        }
-       
+
        public DoubleModel(ChangeSource source, String valueName) {
                this(source, valueName, 1.0, UnitGroup.UNITS_NONE,
                                Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY);
        }
-       
+
        public DoubleModel(ChangeSource source, String valueName, double min) {
                this(source, valueName, 1.0, UnitGroup.UNITS_NONE, min, Double.POSITIVE_INFINITY);
        }
-       
+
        public DoubleModel(ChangeSource source, String valueName, double min, double max) {
                this(source, valueName, 1.0, UnitGroup.UNITS_NONE, min, max);
        }
-       
-       
+
+
 
        /**
         * Returns the value of the variable (in SI units).
@@ -711,7 +718,7 @@ public class DoubleModel implements StateChangeListener, ChangeSource, Invalidat
        public double getValue() {
                if (getMethod == null) // Constant value
                        return lastValue;
-               
+
                try {
                        return (Double) getMethod.invoke(source) * multiplier;
                } catch (IllegalArgumentException e) {
@@ -722,14 +729,14 @@ public class DoubleModel implements StateChangeListener, ChangeSource, Invalidat
                        throw Reflection.handleWrappedException(e);
                }
        }
-       
+
        /**
         * Sets the value of the variable.
         * @param v New value for parameter in SI units.
         */
        public void setValue(double v) {
                checkState(true);
-               
+
                log.debug("Setting value " + v + " for " + this);
                if (setMethod == null) {
                        if (getMethod != null) {
@@ -740,7 +747,7 @@ public class DoubleModel implements StateChangeListener, ChangeSource, Invalidat
                        fireStateChanged();
                        return;
                }
-               
+
                try {
                        setMethod.invoke(source, v / multiplier);
                } catch (IllegalArgumentException e) {
@@ -751,14 +758,14 @@ public class DoubleModel implements StateChangeListener, ChangeSource, Invalidat
                        throw Reflection.handleWrappedException(e);
                }
        }
-       
+
        /**
         * Returns whether setting the value automatically is available.
         */
        public boolean isAutomaticAvailable() {
                return (getAutoMethod != null) && (setAutoMethod != null);
        }
-       
+
        /**
         * Returns whether the value is currently being set automatically.
         * Returns false if automatic setting is not available at all.
@@ -766,7 +773,7 @@ public class DoubleModel implements StateChangeListener, ChangeSource, Invalidat
        public boolean isAutomatic() {
                if (getAutoMethod == null)
                        return false;
-               
+
                try {
                        return (Boolean) getAutoMethod.invoke(source);
                } catch (IllegalArgumentException e) {
@@ -777,20 +784,20 @@ public class DoubleModel implements StateChangeListener, ChangeSource, Invalidat
                        throw Reflection.handleWrappedException(e);
                }
        }
-       
+
        /**
         * Sets whether the value should be set automatically.  Simply fires a
         * state change event if automatic setting is not available.
         */
        public void setAutomatic(boolean auto) {
                checkState(true);
-               
+
                if (setAutoMethod == null) {
                        log.debug("Setting automatic to " + auto + " for " + this + ", automatic not available");
                        fireStateChanged(); // in case something is out-of-sync
                        return;
                }
-               
+
                log.debug("Setting automatic to " + auto + " for " + this);
                lastAutomatic = auto;
                try {
@@ -803,8 +810,8 @@ public class DoubleModel implements StateChangeListener, ChangeSource, Invalidat
                        throw Reflection.handleWrappedException(e);
                }
        }
-       
-       
+
+
        /**
         * Returns the current Unit.  At the beginning it is the default unit of the UnitGroup.
         * @return The most recently set unit.
@@ -812,7 +819,7 @@ public class DoubleModel implements StateChangeListener, ChangeSource, Invalidat
        public Unit getCurrentUnit() {
                return currentUnit;
        }
-       
+
        /**
         * Sets the current Unit.  The unit must be one of those included in the UnitGroup.
         * @param u  The unit to set active.
@@ -825,8 +832,8 @@ public class DoubleModel implements StateChangeListener, ChangeSource, Invalidat
                currentUnit = u;
                fireStateChanged();
        }
-       
-       
+
+
        /**
         * Returns the UnitGroup associated with the parameter value.
         *
@@ -835,8 +842,8 @@ public class DoubleModel implements StateChangeListener, ChangeSource, Invalidat
        public UnitGroup getUnitGroup() {
                return units;
        }
-       
-       
+
+
 
        /**
         * Add a listener to the model.  Adds the model as a listener to the value source if this
@@ -846,7 +853,7 @@ public class DoubleModel implements StateChangeListener, ChangeSource, Invalidat
        @Override
        public void addChangeListener(EventListener l) {
                checkState(true);
-               
+
                if (listeners.isEmpty()) {
                        if (source != null) {
                                source.addChangeListener(this);
@@ -854,11 +861,11 @@ public class DoubleModel implements StateChangeListener, ChangeSource, Invalidat
                                lastAutomatic = isAutomatic();
                        }
                }
-               
+
                listeners.add(l);
                log.verbose(this + " adding listener (total " + listeners.size() + "): " + l);
        }
-       
+
        /**
         * Remove a listener from the model.  Removes the model from being a listener to the Component
         * if this was the last listener of the model.
@@ -867,15 +874,15 @@ public class DoubleModel implements StateChangeListener, ChangeSource, Invalidat
        @Override
        public void removeChangeListener(EventListener l) {
                checkState(false);
-               
+
                listeners.remove(l);
                if (listeners.isEmpty() && source != null) {
                        source.removeChangeListener(this);
                }
                log.verbose(this + " removing listener (total " + listeners.size() + "): " + l);
        }
-       
-       
+
+
        /**
         * Invalidates this model by removing all listeners and removing this from
         * listening to the source.  After invalidation no listeners can be added to this
@@ -885,7 +892,7 @@ public class DoubleModel implements StateChangeListener, ChangeSource, Invalidat
        public void invalidate() {
                log.verbose("Invalidating " + this);
                invalidator.invalidate();
-               
+
                if (!listeners.isEmpty()) {
                        log.warn("Invalidating " + this + " while still having listeners " + listeners);
                }
@@ -895,13 +902,13 @@ public class DoubleModel implements StateChangeListener, ChangeSource, Invalidat
                }
                MemoryManagement.collectable(this);
        }
-       
-       
+
+
        private void checkState(boolean error) {
                invalidator.check(error);
        }
-       
-       
+
+
        @Override
        protected void finalize() throws Throwable {
                super.finalize();
@@ -909,14 +916,14 @@ public class DoubleModel implements StateChangeListener, ChangeSource, Invalidat
                        log.warn(this + " being garbage-collected while having listeners " + listeners);
                }
        };
-       
-       
+
+
        /**
         * Fire a ChangeEvent to all listeners.
         */
        protected void fireStateChanged() {
                checkState(true);
-               
+
                EventObject event = new EventObject(this);
                ChangeEvent cevent = new ChangeEvent(this);
                firing++;
@@ -931,7 +938,7 @@ public class DoubleModel implements StateChangeListener, ChangeSource, Invalidat
                }
                firing--;
        }
-       
+
        /**
         * Called when the component changes.  Checks whether the modeled value has changed, and if
         * it has, updates lastValue and generates ChangeEvents for all listeners of the model.
@@ -939,7 +946,7 @@ public class DoubleModel implements StateChangeListener, ChangeSource, Invalidat
        @Override
        public void stateChanged(EventObject e) {
                checkState(true);
-               
+
                double v = getValue();
                boolean b = isAutomatic();
                if (lastValue == v && lastAutomatic == b)
@@ -948,8 +955,8 @@ public class DoubleModel implements StateChangeListener, ChangeSource, Invalidat
                lastAutomatic = b;
                fireStateChanged();
        }
-       
-       
+
+
        /**
         * Explain the DoubleModel as a String.
         */
index 9805a02eb652d9bb5bcf1518a9f09e6163984aee..4320f0699ebb04dcb7577d8a124b38e98df5d891 100644 (file)
@@ -3,19 +3,11 @@
  */
 package net.sf.openrocket.gui.print.visitor;
 
-import com.itextpdf.text.Chunk;
-import com.itextpdf.text.Document;
-import com.itextpdf.text.DocumentException;
-import com.itextpdf.text.Element;
-import com.itextpdf.text.Font;
-import com.itextpdf.text.Image;
-import com.itextpdf.text.Paragraph;
-import com.itextpdf.text.Phrase;
-import com.itextpdf.text.Rectangle;
-import com.itextpdf.text.pdf.PdfPCell;
-import com.itextpdf.text.pdf.PdfPTable;
-import com.itextpdf.text.pdf.PdfWriter;
-import com.itextpdf.text.pdf.draw.VerticalPositionMark;
+import java.util.List;
+import java.util.Set;
+
+import javax.swing.ImageIcon;
+
 import net.sf.openrocket.gui.main.ComponentIcons;
 import net.sf.openrocket.gui.print.ITextHelper;
 import net.sf.openrocket.gui.print.PrintUtilities;
@@ -45,13 +37,20 @@ import net.sf.openrocket.rocketcomponent.Transition;
 import net.sf.openrocket.startup.Application;
 import net.sf.openrocket.unit.Unit;
 import net.sf.openrocket.unit.UnitGroup;
-import net.sf.openrocket.util.Coordinate;
 
-import javax.swing.*;
-import java.text.NumberFormat;
-import java.util.Collection;
-import java.util.List;
-import java.util.Set;
+import com.itextpdf.text.Chunk;
+import com.itextpdf.text.Document;
+import com.itextpdf.text.DocumentException;
+import com.itextpdf.text.Element;
+import com.itextpdf.text.Font;
+import com.itextpdf.text.Image;
+import com.itextpdf.text.Paragraph;
+import com.itextpdf.text.Phrase;
+import com.itextpdf.text.Rectangle;
+import com.itextpdf.text.pdf.PdfPCell;
+import com.itextpdf.text.pdf.PdfPTable;
+import com.itextpdf.text.pdf.PdfWriter;
+import com.itextpdf.text.pdf.draw.VerticalPositionMark;
 
 /**
  * A visitor strategy for creating documentation about parts details.
@@ -539,8 +538,6 @@ public class PartsDetailVisitorStrategy {
         Image img = null;
         java.awt.Image awtImage = new PrintableFinSet(theFinSet).createImage();
 
-        Collection<Coordinate> x = theFinSet.getComponentBounds();
-
         try {
             img = Image.getInstance(writer, awtImage, 0.25f);
         }
@@ -714,7 +711,7 @@ public class PartsDetailVisitorStrategy {
      */
     protected String toLength (double length) {
         final Unit defaultUnit = UnitGroup.UNITS_LENGTH.getDefaultUnit();
-        return NumberFormat.getNumberInstance().format(defaultUnit.toUnit(length)) + defaultUnit.toString();
+        return defaultUnit.toStringUnit(length);
     }
 
     /**
@@ -726,7 +723,7 @@ public class PartsDetailVisitorStrategy {
      */
     protected String toMass (double mass) {
         final Unit defaultUnit = UnitGroup.UNITS_MASS.getDefaultUnit();
-        return NumberFormat.getNumberInstance().format(defaultUnit.toUnit(mass)) + defaultUnit.toString();
+        return defaultUnit.toStringUnit(mass);
     }
 
     /**
diff --git a/core/src/net/sf/openrocket/unit/FractionalUnit.java b/core/src/net/sf/openrocket/unit/FractionalUnit.java
new file mode 100644 (file)
index 0000000..2643647
--- /dev/null
@@ -0,0 +1,188 @@
+package net.sf.openrocket.unit;
+
+import java.text.DecimalFormat;
+import java.text.NumberFormat;
+import java.util.ArrayList;
+
+public class FractionalUnit extends Unit {
+
+       // This is the base of the fractions.  ie, 16d for 1/16ths.
+       private final int fractionBase;
+       // This is 1d/fractionBase;
+       private final double fractionValue;
+
+       // This is the value used when incrementing/decrementing.
+       private final double incrementValue;
+
+       // If the actual value differs from the decimal representation by more than this,
+       // we display as decimals.
+       private final double epsilon;
+
+       private final String unitLabel;
+
+       public FractionalUnit(double multiplier, String unit, String unitLabel, int fractionBase, double incrementValue) {
+               this( multiplier, unit, unitLabel, fractionBase, incrementValue, 0.1d/fractionBase);
+       }
+
+       public FractionalUnit(double multiplier, String unit, String unitLabel, int fractionBase, double incrementValue, double epsilon) {
+               super(multiplier, unit);
+               this.unitLabel = unitLabel;
+               this.fractionBase = fractionBase;
+               this.fractionValue = 1.0d/fractionBase;
+               this.incrementValue = incrementValue;
+               this.epsilon = epsilon;
+       }
+
+       @Override
+       public double round(double value) {
+               return roundTo( value, fractionValue );
+       }
+
+       private double roundTo( double value, double fraction ) {
+               double remainder = Math.IEEEremainder( value, fraction );
+               return value - remainder;
+       }
+
+       @Override
+       public double getNextValue(double value) {
+               double rounded = roundTo(value, incrementValue);
+               if ( rounded <= value + epsilon) {
+                       rounded += incrementValue;
+               }
+               return rounded;
+       }
+
+       @Override
+       public double getPreviousValue(double value) {
+               double rounded = roundTo(value, incrementValue);
+               if ( rounded >= value - epsilon ) {
+                       rounded -= incrementValue;
+               }
+               return rounded;
+       }
+
+       @Override
+       public Tick[] getTicks(double start, double end, double minor, double major) {
+               // Convert values
+               start = toUnit(start);
+               end = toUnit(end);
+               minor = toUnit(minor);
+               major = toUnit(major);
+
+               if (minor <= 0 || major <= 0 || major < minor) {
+                       throw new IllegalArgumentException("getTicks called with minor="+minor+" major="+major);
+               }
+
+               ArrayList<Tick> ticks = new ArrayList<Tick>();
+
+               int mod2,mod3,mod4;  // Moduli for minor-notable, major-nonnotable, major-notable
+               double minstep;
+
+               // Find the smallest possible step size
+               double one=1;
+               while (one > minor)
+                       one /= 2;
+               while (one < minor)
+                       one *= 2;
+               minstep = one;
+               mod2 = 16;
+
+               // Find step size for major ticks
+               one = 1;
+               while (one > major)
+                       one /= 10;
+               while (one < major)
+                       one *= 10;
+               if (one/2 >= major) {
+                       // major step is round-five, major-notable is next round-ten
+                       double majorstep = one/2;
+                       mod3 = (int)Math.round(majorstep/minstep);
+                       mod4 = mod3*2;
+               } else {
+                       // major step is round-ten, major-notable is next round-ten
+                       mod3 = (int)Math.round(one/minstep);
+                       mod4 = mod3*10;
+               }
+               // Check for clashes between minor-notable and major-nonnotable
+               if (mod3 == mod2) {
+                       if (mod2==2)
+                               mod2 = 1;  // Every minor tick is notable
+                       else
+                               mod2 = 5;  // Every fifth minor tick is notable
+               }
+
+
+               // Calculate starting position
+               int pos = (int)Math.ceil(start/minstep);
+               //              System.out.println("mod2="+mod2+" mod3="+mod3+" mod4="+mod4);
+               while (pos*minstep <= end) {
+                       double unitValue = pos*minstep;
+                       double value = fromUnit(unitValue);
+
+                       if (pos%mod4 == 0)
+                               ticks.add(new Tick(value,unitValue,true,true));
+                       else if (pos%mod3 == 0)
+                               ticks.add(new Tick(value,unitValue,true,false));
+                       else if (pos%mod2 == 0)
+                               ticks.add(new Tick(value,unitValue,false,true));
+                       else
+                               ticks.add(new Tick(value,unitValue,false,false));
+
+                       pos++;
+               }
+
+               return ticks.toArray(new Tick[0]);
+       }
+
+
+       @Override
+       public String toString(double value) {
+
+               double correctVal = toUnit(value);
+               double val = round(correctVal);
+
+
+               if ( Math.abs( val - correctVal ) > epsilon ) {
+                       NumberFormat decFormat = new DecimalFormat("#.###");
+                       return decFormat.format(correctVal);
+               }
+
+               NumberFormat intFormat = new DecimalFormat("#");
+               double sign = Math.signum(val);
+
+               double posValue = sign * val;
+
+               double intPart = Math.floor(posValue);
+
+               double frac = Math.rint((posValue - intPart)/fractionValue);
+               double fracBase = fractionBase;
+
+               // Reduce fraction.
+               while ( frac > 0 && fracBase > 2 && frac % 2 == 0 ) {
+                       frac /= 2.0;
+                       fracBase /= 2.0;
+               }
+
+               posValue *= sign;
+
+               if ( frac == 0.0 )  {
+                       return intFormat.format(posValue);
+               } else if (intPart == 0.0 ){
+                       return intFormat.format(sign*frac) + "/" + intFormat.format(fracBase);
+               } else {
+                       return intFormat.format(sign*intPart) + " " + intFormat.format(frac) + "/" + intFormat.format(fracBase);
+               }
+
+       }
+
+       @Override
+       public String toStringUnit(double value) {
+               if (Double.isNaN(value))
+                       return "N/A";
+
+               String s = toString(value);
+               s += " " + unitLabel;
+               return s;
+       }
+
+}
index 5a0b692c94febb3b820469e93be661dfc80d2e87..dcd0003bcf5b629315e5ed9edb00cb0de27f3e70 100644 (file)
@@ -84,6 +84,7 @@ public class UnitGroup {
                UNITS_LENGTH.addUnit(new GeneralUnit(0.01, "cm"));
                UNITS_LENGTH.addUnit(new GeneralUnit(1, "m"));
                UNITS_LENGTH.addUnit(new GeneralUnit(0.0254, "in"));
+               UNITS_LENGTH.addUnit(new FractionalUnit(0.0254, "in/64", "in", 64, 1d/16d));
                UNITS_LENGTH.addUnit(new GeneralUnit(0.3048, "ft"));
                UNITS_LENGTH.setDefaultUnit(1);
                
diff --git a/core/src/net/sf/openrocket/util/FractionUtil.java b/core/src/net/sf/openrocket/util/FractionUtil.java
new file mode 100644 (file)
index 0000000..2eff84d
--- /dev/null
@@ -0,0 +1,53 @@
+package net.sf.openrocket.util;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public abstract class FractionUtil {
+
+       
+       private final static Pattern fractionPattern = Pattern.compile("(-?\\d+)/(\\d+)");
+       private final static Pattern mixedFractionPattern = Pattern.compile("(-?\\d+)\\s+(\\d+)/(\\d+)");
+       /**
+        * Parse a double from a string supporting fraction formats.
+        * 
+        * Will parse fractions specified with '/'.  Mixed numbers separated by ' ' (space).
+        * If no fraction is found in the input string, it is parsed with Double.parseDouble()
+        * which my throw the runtime exception java.lang.NumberFormatException.
+        * 
+        * Valid input may look like:
+        * 
+        * "1/4" = 0.25d
+        * "-1/4" = -0.25d
+        * "1 1/4" = 1.25d
+        * "-1 1/4" = 1.25d
+        * "1.25" = 1.25d
+        * 
+        * @param str
+        * @return
+        */
+       public static Double parseFraction( String str ) {
+               
+               if ( str == null ) {
+                       throw new java.lang.NumberFormatException("null String");
+               }
+               
+               Matcher m1 = mixedFractionPattern.matcher(str);
+               if ( m1.find() ) {
+                       double wholepart = Double.parseDouble(m1.group(1));
+                       double num = Double.parseDouble(m1.group(2));
+                       double den = Double.parseDouble(m1.group(3));
+                       return wholepart + Math.copySign(num,wholepart) / den;
+               }
+               
+               Matcher m2 = fractionPattern.matcher(str);
+               if( m2.find() ) {
+                       double num = Double.parseDouble(m2.group(1));
+                       double den = Double.parseDouble(m2.group(2));
+                       return num / den;
+               }
+               
+               return Double.parseDouble(str);
+       }
+
+}
diff --git a/core/test/net/sf/openrocket/unit/FractionalUnitTest.java b/core/test/net/sf/openrocket/unit/FractionalUnitTest.java
new file mode 100644 (file)
index 0000000..1c2b0d5
--- /dev/null
@@ -0,0 +1,206 @@
+package net.sf.openrocket.unit;
+
+import static org.junit.Assert.*;
+
+import org.junit.Test;
+
+public class FractionalUnitTest {
+
+       private final static Unit testUnit = new FractionalUnit(1, "unit", "unit", 4, 0.5);
+       private final static Unit testUnitApprox = new FractionalUnit(1, "unit", "unit", 16, 0.5, 0.02);
+
+       private final static Unit inchUnit = new FractionalUnit(0.0254, "in/64", "in", 64, 1d/16d);
+       
+       @Test
+       public void testRound() {
+
+               assertEquals(-1d, testUnit.round(-1.125), 0.0); // rounds to -1 since mod is even
+               assertEquals(-1d, testUnit.round(-1.0), 0.0);
+               assertEquals(-1d, testUnit.round(-.875), 0.0); // rounds to -1 since mod is even
+
+               assertEquals(-0.75d, testUnit.round(-.874), 0.0);
+               assertEquals(-0.75d, testUnit.round(-.75), 0.0);
+               assertEquals(-0.75d, testUnit.round(-.626), 0.0);
+
+               assertEquals(-0.5d, testUnit.round(-.625), 0.0); // rounds to -.5 since mod is even
+               assertEquals(-0.5d, testUnit.round(-.5), 0.0);
+               assertEquals(-0.5d, testUnit.round(-.375), 0.0); // rounds to -.5 since mod is even
+
+               assertEquals(-0.25d, testUnit.round(-.374), 0.0);
+               assertEquals(-0.25d, testUnit.round(-.25), 0.0);
+               assertEquals(-0.25d, testUnit.round(-.126), 0.0);
+
+               assertEquals(0d, testUnit.round(-.125), 0.0);
+               assertEquals(0d, testUnit.round(0), 0.0);
+               assertEquals(0d, testUnit.round(.125), 0.0);
+
+               assertEquals(0.25d, testUnit.round(.126), 0.0);
+               assertEquals(0.25d, testUnit.round(.25), 0.0);
+               assertEquals(0.25d, testUnit.round(.374), 0.0);
+
+               assertEquals(0.5d, testUnit.round(.375), 0.0); // rounds to .5 since mod is even
+               assertEquals(0.5d, testUnit.round(.5), 0.0);
+               assertEquals(0.5d, testUnit.round(.625), 0.0); // rounds to .5 since mod is even
+
+               assertEquals(0.75d, testUnit.round(.626), 0.0);
+               assertEquals(0.75d, testUnit.round(.75), 0.0);
+               assertEquals(0.75d, testUnit.round(.874), 0.0);
+
+               assertEquals(1d, testUnit.round(.875), 0.0); // rounds to 1 since mod is even
+               assertEquals(1d, testUnit.round(1.0), 0.0);
+               assertEquals(1d, testUnit.round(1.125), 0.0); // rounds to 1 since mod is even
+
+       }
+
+       @Test
+       public void testIncrement() {
+
+               assertEquals( -1d, testUnit.getNextValue(-1.2), 0.0);
+               assertEquals( -1d, testUnit.getNextValue(-1.4), 0.0);
+
+               assertEquals( -0.5d, testUnit.getNextValue(-0.7), 0.0);
+               assertEquals( -0.5d, testUnit.getNextValue(-0.9), 0.0);
+               assertEquals( -0.5d, testUnit.getNextValue(-1.0), 0.0);
+
+               assertEquals( 0.0d, testUnit.getNextValue(-0.05), 0.0 );
+               assertEquals( 0.0d, testUnit.getNextValue(-0.062), 0.0 );
+               assertEquals( 0.0d, testUnit.getNextValue(-0.07), 0.0 );
+               assertEquals( 0.0d, testUnit.getNextValue(-0.11), 0.0 );
+
+               assertEquals( 0.5d, testUnit.getNextValue(0), 0.0 );
+               assertEquals( 0.5d, testUnit.getNextValue(0.01), 0.0 );
+               assertEquals( 0.5d, testUnit.getNextValue(0.062), 0.0 );
+               assertEquals( 0.5d, testUnit.getNextValue(0.0625), 0.0);
+
+               assertEquals( 1d, testUnit.getNextValue(0.51), 0.0);
+               assertEquals( 1d, testUnit.getNextValue(0.7), 0.0);
+       }
+
+       @Test
+       public void testDecrement() {
+
+               assertEquals( -1.5d, testUnit.getPreviousValue(-1.2), 0.0);
+               assertEquals( -1.5d, testUnit.getPreviousValue(-1.4), 0.0);
+               assertEquals( -1.5d, testUnit.getPreviousValue(-1.0), 0.0);
+
+               assertEquals( -1d, testUnit.getPreviousValue(-0.7), 0.0);
+               assertEquals( -1d, testUnit.getPreviousValue(-0.9), 0.0);
+
+               assertEquals( -0.5d, testUnit.getPreviousValue(-0.01), 0.0 );
+               assertEquals( -0.5d, testUnit.getPreviousValue(-0.05), 0.0 );
+               assertEquals( -0.5d, testUnit.getPreviousValue(-0.062), 0.0 );
+               assertEquals( -0.5d, testUnit.getPreviousValue(-0.07), 0.0 );
+               assertEquals( -0.5d, testUnit.getPreviousValue(0), 0.0 );
+
+               assertEquals( 0.0d, testUnit.getPreviousValue(0.49), 0.0 );
+               assertEquals( 0.0d, testUnit.getPreviousValue(0.262), 0.0 );
+               assertEquals( 0.0d, testUnit.getPreviousValue(0.51), 0.0);
+
+               assertEquals( 0.5d, testUnit.getPreviousValue(0.7), 0.0);
+
+               assertEquals( 1.0d, testUnit.getPreviousValue(1.2), 0.0);
+       }
+
+       @Test
+       public void testToStringDefaultPrecision() {
+
+               // default epsilon is 0.025
+               assertEquals("-1.2", testUnit.toString(-1.2)); 
+               assertEquals("-1 1/4", testUnit.toString(-1.225));
+               assertEquals("-1 1/4", testUnit.toString(-1.227));
+               assertEquals("-1 1/4", testUnit.toString(-1.25));
+               assertEquals("-1 1/4", testUnit.toString(-1.25));
+               assertEquals("-1 1/4", testUnit.toString(-1.275));
+               assertEquals("-1.3", testUnit.toString(-1.3));
+
+               assertEquals("-0.2", testUnit.toString(-.2));
+               assertEquals("-1/4", testUnit.toString(-.225));
+               assertEquals("-1/4", testUnit.toString(-.25));
+               assertEquals("-1/4", testUnit.toString(-.274));
+               //assertEquals("-1/4", testUnit.toString(-.275)); // this has roundoff error which pushes it over epsilon
+               assertEquals("-0.3", testUnit.toString(-.3));
+
+               assertEquals("-0.1", testUnit.toString(-.1));
+               assertEquals("0", testUnit.toString(-0.024));
+               assertEquals("0", testUnit.toString(0));
+               assertEquals("0", testUnit.toString(.024));
+               assertEquals("0.1", testUnit.toString(.1));
+
+               assertEquals("0.2", testUnit.toString(.2));
+               assertEquals("1/4", testUnit.toString(.225));
+               assertEquals("1/4", testUnit.toString(.25));
+               assertEquals("1/4", testUnit.toString(.274));
+               assertEquals("0.3", testUnit.toString(.3));
+
+               assertEquals("1.2", testUnit.toString(1.2));
+               assertEquals("1 1/4", testUnit.toString(1.225));
+               assertEquals("1 1/4", testUnit.toString(1.25));
+               assertEquals("1 1/4", testUnit.toString(1.275));
+               assertEquals("1.3", testUnit.toString(1.3));
+
+       }
+
+       @Test
+       public void testToStringWithPrecision() {
+
+               // epsilon is .02
+               assertEquals("-1 3/16", testUnitApprox.toString(-1.2));
+               assertEquals("-1.225", testUnitApprox.toString(-1.225));
+               assertEquals("-1 1/4", testUnitApprox.toString(-1.25));
+               assertEquals("-1.275", testUnitApprox.toString(-1.275));
+               assertEquals("-1 5/16", testUnitApprox.toString(-1.3));
+
+               assertEquals("-3/16", testUnitApprox.toString(-.2));
+               assertEquals("-0.225", testUnitApprox.toString(-.225));
+               assertEquals("-1/4", testUnitApprox.toString(-.25));
+               assertEquals("-0.275", testUnitApprox.toString(-.275));
+               assertEquals("-5/16", testUnitApprox.toString(-.3));
+
+               assertEquals("-0.1", testUnitApprox.toString(-.1));
+               assertEquals("-0.024", testUnitApprox.toString(-0.024));
+               assertEquals("0", testUnitApprox.toString(0));
+               assertEquals("0.024", testUnitApprox.toString(.024));
+               assertEquals("0.1", testUnitApprox.toString(.1));
+
+               assertEquals("3/16", testUnitApprox.toString(.2));
+               assertEquals("0.225", testUnitApprox.toString(.225));
+               assertEquals("1/4", testUnitApprox.toString(.25));
+               assertEquals("0.275", testUnitApprox.toString(.275));
+               assertEquals("5/16", testUnitApprox.toString(.3));
+
+               assertEquals("1 3/16", testUnitApprox.toString(1.2));
+               assertEquals("1.225", testUnitApprox.toString(1.225));
+               assertEquals("1 1/4", testUnitApprox.toString(1.25));
+               assertEquals("1.275", testUnitApprox.toString(1.275));
+               assertEquals("1 5/16", testUnitApprox.toString(1.3));
+
+       }
+       
+       @Test
+       public void testInchToString() {
+               
+               // Just some random test points.
+               assertEquals( "1/64", inchUnit.toString( 1d/64d*0.0254));
+               
+               assertEquals( "-5/64", inchUnit.toString( -5d/64d*0.0254));
+               
+               assertEquals( "4 1/2", inchUnit.toString( 9d/2d*0.0254));
+               
+               assertEquals( "0.002", inchUnit.toString( 0.002*0.0254));
+               
+               // default body tube length:
+               double length = 8d * 0.025;
+               
+               assertEquals( "7 7/8", inchUnit.toString( length) );
+               
+               // had problems with roundoff in decrement.
+               
+               double v = inchUnit.toUnit(length);
+               for ( int i = 0; i< 15; i++ ) {
+                       assertTrue( v > inchUnit.getPreviousValue(v) );
+                       v = inchUnit.getPreviousValue(v);
+               }
+               
+       }
+
+}
diff --git a/core/test/net/sf/openrocket/util/FractionUtilTest.java b/core/test/net/sf/openrocket/util/FractionUtilTest.java
new file mode 100644 (file)
index 0000000..1e75497
--- /dev/null
@@ -0,0 +1,58 @@
+package net.sf.openrocket.util;
+
+import static org.junit.Assert.assertEquals;
+
+import org.junit.Test;
+
+public class FractionUtilTest {
+       
+       @Test
+       public void testParseFractions() {
+
+               // zeros
+               assertEquals( 0.0d, FractionUtil.parseFraction("0"), 0.0);
+               assertEquals( 0.0d, FractionUtil.parseFraction("-0"), 0.0);
+               assertEquals( 0.0d, FractionUtil.parseFraction("0/2"), 0.0);
+               assertEquals( 0.0d, FractionUtil.parseFraction("-0/2"), 0.0);
+               assertEquals( 0.0d, FractionUtil.parseFraction("0 0/4"), 0.0);
+               assertEquals( 0.0d, FractionUtil.parseFraction("0 -0/4"), 0.0);
+               
+               // Simple fraction.
+               assertEquals( 0.25, FractionUtil.parseFraction("1/4"),0.0);
+               
+               // ignores leading & trailing spaces
+               assertEquals( 0.25, FractionUtil.parseFraction("  1/4  "),0.0);
+
+               // non reduced fraction
+               assertEquals( 0.25, FractionUtil.parseFraction("2/8"),0.0);
+               
+               // negative number
+               assertEquals( -0.25, FractionUtil.parseFraction("-1/4"),0.0);
+
+               // improper fraction
+               assertEquals( 1.75, FractionUtil.parseFraction("7/4"),0.0);
+               
+               // negative improper fraction
+               assertEquals( -1.75, FractionUtil.parseFraction("-7/4"),0.0);
+               
+               // two digit numerator & denominators
+               assertEquals( 11d/16d, FractionUtil.parseFraction("11/16)"),0.0);
+               assertEquals( -11d/16d, FractionUtil.parseFraction("-11/16)"),0.0);
+               
+               // Mixed fractions
+               assertEquals( 1.25d, FractionUtil.parseFraction("1 1/4"),0.0);
+               assertEquals( -1.25d, FractionUtil.parseFraction("-1 1/4"),0.0);
+               
+               // extra spaces
+               assertEquals( 1.25d, FractionUtil.parseFraction("   1 1/4"),0.0);
+               assertEquals( 1.25d, FractionUtil.parseFraction("1    1/4"),0.0);
+               assertEquals( 1.25d, FractionUtil.parseFraction("1 1/4  "),0.0);
+               
+               assertEquals( 2.75d, FractionUtil.parseFraction("2 3/4"),0.0);
+               assertEquals( 15.75d, FractionUtil.parseFraction("15 3/4"),0.0);
+               assertEquals( 2.75d, FractionUtil.parseFraction("1 7/4"),0.0);
+
+               assertEquals( 69d/64d, FractionUtil.parseFraction("1 5/64"),0.0);
+               assertEquals( -69d/64d, FractionUtil.parseFraction("-1 5/64"),0.0);
+       }
+}