Merge commit '42b2e5ca519766e37ce6941ba4faecc9691cc403' into upstream
[debian/openrocket] / core / src / net / sf / openrocket / unit / FractionalUnit.java
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..740e477
--- /dev/null
@@ -0,0 +1,245 @@
+package net.sf.openrocket.unit;
+
+import java.text.DecimalFormat;
+import java.text.NumberFormat;
+import java.util.ArrayList;
+
+import net.sf.openrocket.util.Chars;
+
+public class FractionalUnit extends Unit {
+       
+       private final static char FRACTION = Chars.FRACTION;
+       
+       private final static String[] NUMERATOR = {
+                       "\u2070", // 0
+                       "\u00B9", // 1
+                       "\u00B2", // 2
+                       "\u00B3", // 3
+                       "\u2074", // 4
+                       "\u2075", // 5
+                       "\u2076", // 6
+                       "\u2077", // 7
+                       "\u2078", // 8
+                       "\u2079" // 9
+       };
+       
+       private final static String[] DENOMINATOR = {
+                       "\u2080", // 0
+                       "\u2081", // 1
+                       "\u2082", // 2
+                       "\u2083", // 3
+                       "\u2084", // 4
+                       "\u2085", // 5
+                       "\u2086", // 6
+                       "\u2087", // 7
+                       "\u2088", // 8
+                       "\u2089" // 9
+       };
+       
+       // 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 (sign < 0 ? "-" : "") + numeratorString(Double.valueOf(frac).intValue())
+                                       + FRACTION + denominatorString(Double.valueOf(fracBase).intValue());
+               } else {
+                       return intFormat.format(sign * intPart) + " " + numeratorString(Double.valueOf(frac).intValue())
+                                       + FRACTION + denominatorString(Double.valueOf(fracBase).intValue());
+               }
+               
+       }
+       
+       private String numeratorString(int value) {
+               
+               String rep = "";
+               if (value == 0) {
+                       return "0";
+               }
+               while (value > 0) {
+                       rep = NUMERATOR[value % 10] + rep;
+                       value = value / 10;
+               }
+               return rep;
+       }
+       
+       private String denominatorString(int value) {
+               String rep = "";
+               if (value == 0) {
+                       return "0";
+               }
+               while (value > 0) {
+                       rep = DENOMINATOR[value % 10] + rep;
+                       value = value / 10;
+               }
+               return rep;
+       }
+       
+       @Override
+       public String toStringUnit(double value) {
+               if (Double.isNaN(value))
+                       return "N/A";
+               
+               String s = toString(value);
+               s += " " + unitLabel;
+               return s;
+       }
+       
+}