Big update to custom expression feature.
authorrichardgraham <richardgraham@180e2498-e6e9-4542-8430-84ac67f01cd8>
Sun, 5 Aug 2012 23:59:54 +0000 (23:59 +0000)
committerrichardgraham <richardgraham@180e2498-e6e9-4542-8430-84ac67f01cd8>
Sun, 5 Aug 2012 23:59:54 +0000 (23:59 +0000)
 - supports range and index subexpressions and many new operators
 - switched to my patched version of exp4j to support all this.
 - expressions belong to rocket document. Accessed from analysis menu.
 - expression importing from file
 - datatypes section defined in file for storing datatypes other than internal ones
 - flightdatatype fix to forget outdated types
 - many GUI fixes to custom expressions
 - new unitgroups supported. Auto unit detection for SI units in custom expressions.

Had to carefully merge loading/saving code with Kevins recent de-localization update. Hopefully changes to materials saving kept but switched datatype access to just using symbol as the key.

Hopefully can get the changes to exp4j upstream so we don't need to keep using this patched version.

git-svn-id: https://openrocket.svn.sourceforge.net/svnroot/openrocket/trunk@953 180e2498-e6e9-4542-8430-84ac67f01cd8

34 files changed:
core/fileformat.txt
core/lib/exp4j-rdg.jar [new file with mode: 0644]
core/resources/l10n/messages.properties
core/src/net/sf/openrocket/document/OpenRocketDocument.java
core/src/net/sf/openrocket/document/Simulation.java
core/src/net/sf/openrocket/file/openrocket/OpenRocketSaver.java
core/src/net/sf/openrocket/file/openrocket/importt/OpenRocketLoader.java
core/src/net/sf/openrocket/file/openrocket/savers/RocketSaver.java
core/src/net/sf/openrocket/gui/customexpression/CustomExpressionDialog.java [new file with mode: 0644]
core/src/net/sf/openrocket/gui/customexpression/CustomExpressionPanel.java
core/src/net/sf/openrocket/gui/customexpression/ExpressionBuilderDialog.java
core/src/net/sf/openrocket/gui/customexpression/OperatorSelector.java
core/src/net/sf/openrocket/gui/customexpression/OperatorTableModel.java
core/src/net/sf/openrocket/gui/customexpression/VariableSelector.java
core/src/net/sf/openrocket/gui/customexpression/VariableTableModel.java
core/src/net/sf/openrocket/gui/main/BasicFrame.java
core/src/net/sf/openrocket/gui/main/SimulationEditDialog.java
core/src/net/sf/openrocket/rocketcomponent/Rocket.java
core/src/net/sf/openrocket/simulation/BasicEventSimulationEngine.java
core/src/net/sf/openrocket/simulation/CustomExpression.java [deleted file]
core/src/net/sf/openrocket/simulation/FlightDataType.java
core/src/net/sf/openrocket/simulation/customexpression/CustomExpression.java [new file with mode: 0644]
core/src/net/sf/openrocket/simulation/customexpression/Functions.java [new file with mode: 0644]
core/src/net/sf/openrocket/simulation/customexpression/IndexExpression.java [new file with mode: 0644]
core/src/net/sf/openrocket/simulation/customexpression/RangeExpression.java [new file with mode: 0644]
core/src/net/sf/openrocket/unit/FixedUnitGroup.java
core/src/net/sf/openrocket/unit/UnitGroup.java
core/src/net/sf/openrocket/util/ArrayUtils.java
core/src/net/sf/openrocket/util/ExpressionParser.java
core/src/net/sf/openrocket/util/LinearInterpolator.java
core/src/net/sf/openrocket/util/MathUtil.java
core/src/net/sf/openrocket/util/TextUtil.java
core/test/net/sf/openrocket/simulation/customexpression/TestExpressions.java [new file with mode: 0644]
core/test/net/sf/openrocket/util/MathUtilTest.java

index 3e58beee3a0679bc56887bc0ffa47fffd2b148af..7f8fb93219758543fad42d1860846c6a377e77b4 100644 (file)
@@ -40,4 +40,8 @@ The following file format versions exist:
       <separationdelay> elements to stage components (except sustainer).
 
 1.5:  Introduced with OpenRocket 12.xx.  Added ComponentPresets.
-      Added lowerstageseparation as recovery device deployment event.
\ No newline at end of file
+      Added lowerstageseparation as recovery device deployment event.
+
+1.6 (pre):
+      Added <datatypes> section for supporting datatypes other than
+      internal ones. Currently only supports datatypes from custom expressions.
\ No newline at end of file
diff --git a/core/lib/exp4j-rdg.jar b/core/lib/exp4j-rdg.jar
new file mode 100644 (file)
index 0000000..0de4e9d
Binary files /dev/null and b/core/lib/exp4j-rdg.jar differ
index 0715bc7968fb823d3634ce75834294c6973fb87b..d8da23a54e91f75ff8913d7a3579ceb830acde4e 100644 (file)
@@ -454,14 +454,18 @@ customExpression.Description = Description
 
 ! Custom expression panel
 customExpressionPanel.but.NewExpression = New expression
+customExpressionPanel.but.ttip.NewExpression = Add a new custom expression
+customExpressionPanel.but.Import = Import
+customExpressionPanel.but.ttip.Import = Import custom expressions from another .ork file
 customExpressionPanel.lbl.UpdateNote = You must run the simulation before data will be available for plotting.
 customExpressionPanel.lbl.CalcNote = Expressions will be calculated in the order shown.
-customExpressionPanel.lbl.CustomExpressions = Custom Expressions :
+customExpressionPanel.lbl.CustomExpressions = Custom Expressions
 customExpression.Units.but.ttip.Remove = Remove this expression
 customExpression.Units.but.ttip.Edit = Edit this expression
 customExpression.Units.but.ttip.MoveUp = Move expression up in calculation order
 customExpression.Units.but.ttip.MoveDown = Move expression down in calculation order
 
+
 ! Custom expression builder window
 ExpressionBuilderDialog.title = Expression Builder
 ExpressionBuilderDialog.InsertVariable = Insert Variable
@@ -482,12 +486,12 @@ CustomOperatorSelector.title = Operator Selector
 Operator.plus = Addition
 Operator.minus = Subtraction
 Operator.star = Multiplication
-Operator.div = Divison
+Operator.div = Division
 Operator.mod = Modulo
 Operator.pow = Exponentiation
 Operator.abs = Absolute value
-Operator.ceil = Ceiling (next integer value
-Operator.floor = Floor (previous integer value
+Operator.ceil = Ceiling (next integer value)
+Operator.floor = Floor (previous integer value)
 Operator.sqrt = Square root
 Operator.cbrt = Cubic root
 Operator.exp = Euler\'s number raised to the value (e^x)
@@ -498,9 +502,24 @@ Operator.tan = Tangent
 Operator.asin = Arc sine
 Operator.acos = Arc cosine
 Operator.atan = Arc tangent
-Operator.hsin = Hyerbolic sine
+Operator.hsin = Hyperbolic sine
 Operator.hcos = Hyperbolic cosine
 Operator.htan = Hyperbolic tangent
+Operator.log10 = Base 10 logarithm
+Operator.round = Round to nearest integer value
+Operator.random = Random number between zero and given value
+Operator.expm1 = The same as exp(x)-1, but more accurate for small x 
+Operator.mean = The arithmetic mean of a given range
+Operator.min = The minimum value in a given range
+Operator.max = The maximum value in a given range
+Operator.var = The variance of a given range
+Operator.stdev = The standard deviation of a given range
+Operator.rms = The root-mean-squared value of a given range
+Operator.lclip = Clips a value (1st parameter) to be no less than a given value (2nd parameter)
+Operator.uclip = Clips a value (1st parameter) to be no greater than a given value (2nd parameter)
+Operator.binf = Gives the fraction of values in a given range (1st parameter) inside a bin with given lower (2nd parameter) and upper (3rd parameter) bounds
+Operator.trapz = Integrates the given range using trapezoidal integration
+Operator.tnear = Find the time corresponding to the point in a range (1st parameter) nearest to a given value (2nd parameter)
 
 ! MotorPlot
 MotorPlot.title.Motorplot = Motor plot
@@ -1068,6 +1087,8 @@ main.menu.analyze.componentAnalysis = Component analysis
 main.menu.analyze.componentAnalysis.desc = Analyze the rocket components separately
 main.menu.analyze.optimization = Rocket optimization
 main.menu.analyze.optimization.desc = General rocket design optimization
+main.menu.analyze.customExpressions = Custom expressions
+main.menu.analyze.customExpressions.desc = Define new flight data types by writing custom mathematical expressions 
 
 main.menu.help = Help
 main.menu.help.desc = Information about OpenRocket
index cafc98f33d174665a88081ad3ca9e34cd1be97a9..c7719346b36b440633dbdeae9594ff6613645025 100644 (file)
@@ -13,6 +13,7 @@ import net.sf.openrocket.rocketcomponent.ComponentChangeEvent;
 import net.sf.openrocket.rocketcomponent.ComponentChangeListener;
 import net.sf.openrocket.rocketcomponent.Configuration;
 import net.sf.openrocket.rocketcomponent.Rocket;
+import net.sf.openrocket.simulation.customexpression.CustomExpression;
 import net.sf.openrocket.startup.Application;
 import net.sf.openrocket.util.ArrayList;
 
@@ -50,7 +51,8 @@ public class OpenRocketDocument implements ComponentChangeListener {
        private final Configuration configuration;
        
        private final ArrayList<Simulation> simulations = new ArrayList<Simulation>();
-       
+       private ArrayList<CustomExpression> customExpressions = new ArrayList<CustomExpression>();
+
 
        /*
         * The undo/redo variables and mechanism are documented in doc/undo-redo-flow.*
@@ -103,7 +105,22 @@ public class OpenRocketDocument implements ComponentChangeListener {
        }
        
        
-
+       public void addCustomExpression(CustomExpression expression){
+               if (customExpressions.contains(expression)){
+                       log.user("Could not add custom expression "+expression.getName()+" to document as document alerady has a matching expression.");
+               } else {
+                       customExpressions.add(expression);
+               }
+       }
+       
+       public void removeCustomExpression(CustomExpression expression){
+               customExpressions.remove(expression);
+       }
+       
+       public ArrayList<CustomExpression> getCustomExpressions(){
+               return customExpressions;
+       }
+       
 
        public Rocket getRocket() {
                return rocket;
index c947e23d9ac61fed5869b02d47ecbf989310ffa8..162ee20d06f5ee01febae1c90a8c8af911495316 100644 (file)
@@ -13,7 +13,6 @@ import net.sf.openrocket.masscalc.MassCalculator;
 import net.sf.openrocket.rocketcomponent.Configuration;
 import net.sf.openrocket.rocketcomponent.Rocket;
 import net.sf.openrocket.simulation.BasicEventSimulationEngine;
-import net.sf.openrocket.simulation.CustomExpression;
 import net.sf.openrocket.simulation.FlightData;
 import net.sf.openrocket.simulation.RK4SimulationStepper;
 import net.sf.openrocket.simulation.SimulationConditions;
@@ -72,7 +71,6 @@ public class Simulation implements ChangeSource, Cloneable {
        private SimulationOptions options;
        
        private ArrayList<String> simulationListeners = new ArrayList<String>();
-       private ArrayList<CustomExpression> customExpressions = new ArrayList<CustomExpression>();
        
        private final Class<? extends SimulationEngine> simulationEngineClass = BasicEventSimulationEngine.class;
        private Class<? extends SimulationStepper> simulationStepperClass = RK4SimulationStepper.class;
@@ -161,21 +159,6 @@ public class Simulation implements ChangeSource, Cloneable {
                return document;
        }
        
-       public void addCustomExpression(CustomExpression expression){
-               this.status = Simulation.Status.OUTDATED;
-               log.debug("Simulation must be run again to update custom expression.");
-               customExpressions.add(expression);
-       }
-       
-       public void removeCustomExpression(CustomExpression expression){
-               customExpressions.remove(expression);
-       }
-       
-       public ArrayList<CustomExpression> getCustomExpressions(){
-               return customExpressions;
-       }
-       
-       
        /**
         * Return the rocket associated with this simulation.
         * 
index 9378a455131ef739a2c1d632aa08b4a5dfedd15f..fa1c372f5aae949a7e0720abc28d47bce2b82b44 100644 (file)
@@ -24,7 +24,7 @@ import net.sf.openrocket.rocketcomponent.RecoveryDevice.DeployEvent;
 import net.sf.openrocket.rocketcomponent.Rocket;
 import net.sf.openrocket.rocketcomponent.RocketComponent;
 import net.sf.openrocket.rocketcomponent.TubeCoupler;
-import net.sf.openrocket.simulation.CustomExpression;
+import net.sf.openrocket.simulation.customexpression.CustomExpression;
 import net.sf.openrocket.simulation.FlightData;
 import net.sf.openrocket.simulation.FlightDataBranch;
 import net.sf.openrocket.simulation.FlightDataType;
@@ -101,6 +101,9 @@ public class OpenRocketSaver extends RocketSaver {
                
                writeln("");
                
+               // Save custom expressions;
+               saveCustomDatatypes(document);
+               
                // Save all simulations
                writeln("<simulations>");
                indent++;
@@ -124,7 +127,37 @@ public class OpenRocketSaver extends RocketSaver {
                }
        }
        
+       /*
+        * Save all the custom expressions
+        */
+       private void saveCustomDatatypes(OpenRocketDocument doc) throws IOException {
+               
+               if (doc.getCustomExpressions().isEmpty())
+                       return;
+               
+               writeln("<datatypes>"); indent++;
+               
+               for (CustomExpression exp : doc.getCustomExpressions()){
+                       saveCustomExpressionDatatype(exp);
+               }
+               
+               indent--; writeln("</datatypes>");
+               writeln("");
+       }
        
+       /*
+        * Save one custom expression datatype
+        */
+       private void saveCustomExpressionDatatype(CustomExpression exp) throws IOException {                                            
+               // Write out custom expression
+               
+               writeln("<type source=\"customexpression\">"); indent++;
+                       writeln("<name>"                 + exp.getName()                         + "</name>");
+                       writeln("<symbol>"           + exp.getSymbol()                   + "</symbol>");
+                       writeln("<unit unittype=\"auto\">" + exp.getUnit()               + "</unit>"); // auto unit type means it will be determined from string
+                       writeln("<expression>" + exp.getExpressionString() + "</expression>");
+               indent--; writeln("</type>");
+       }
        
        @Override
        public long estimateFileSize(OpenRocketDocument doc, StorageOptions options) {
@@ -327,20 +360,6 @@ public class OpenRocketSaver extends RocketSaver {
                writeln("<simulator>RK4Simulator</simulator>");
                writeln("<calculator>BarrowmanCalculator</calculator>");
                
-               // Write out custom expressions
-               if (!simulation.getCustomExpressions().isEmpty()){
-                       writeln("<customexpressions>"); indent++;
-                       for (CustomExpression expression : simulation.getCustomExpressions()){
-                               writeln("<expression>"); indent++;
-                               writeElement("name", expression.getName());
-                               writeElement("symbol", expression.getSymbol());
-                               writeElement("unit", expression.getUnit());
-                               writeElement("expressionstring", expression.getExpressionString());
-                               indent--; writeln("</expression>");
-                       }
-                       indent--; writeln("</customexpressions>");
-               }
-               
                writeln("<conditions>"); indent++;
                
                writeElement("configid", cond.getMotorConfigurationID());
@@ -453,13 +472,16 @@ public class OpenRocketSaver extends RocketSaver {
                sb.append("<databranch name=\"");
                sb.append(escapeXML(branch.getBranchName()));
                
-               
+               // Kevins version where typekeys are used
+               /*
                sb.append("\" typekeys=\"");
                for (int i = 0; i < types.length; i++) {
                        if (i > 0)
                                sb.append(",");
                        sb.append(escapeXML(types[i].getKey()));
                }
+               */
+               
                sb.append("\" types=\"");
                for (int i = 0; i < types.length; i++) {
                        if (i > 0)
index 7ef136b83eea70062ae19b467236a6530e1c85c0..b8b34b3b9a34729627bf82900463e7bc52629a88 100644 (file)
@@ -67,7 +67,7 @@ import net.sf.openrocket.rocketcomponent.ThicknessRingComponent;
 import net.sf.openrocket.rocketcomponent.Transition;
 import net.sf.openrocket.rocketcomponent.TrapezoidFinSet;
 import net.sf.openrocket.rocketcomponent.TubeCoupler;
-import net.sf.openrocket.simulation.CustomExpression;
+import net.sf.openrocket.simulation.customexpression.CustomExpression;
 import net.sf.openrocket.simulation.FlightData;
 import net.sf.openrocket.simulation.FlightDataBranch;
 import net.sf.openrocket.simulation.FlightDataType;
@@ -649,6 +649,7 @@ class OpenRocketContentHandler extends AbstractElementHandler {
 
        private boolean rocketDefined = false;
        private boolean simulationsDefined = false;
+       private boolean datatypesDefined = false;
 
        public OpenRocketContentHandler(DocumentLoadingContext context) {
                this.context = context;
@@ -656,7 +657,6 @@ class OpenRocketContentHandler extends AbstractElementHandler {
                this.doc = new OpenRocketDocument(rocket);
        }
 
-
        public OpenRocketDocument getDocument() {
                if (!rocketDefined)
                        return null;
@@ -677,6 +677,15 @@ class OpenRocketContentHandler extends AbstractElementHandler {
                        rocketDefined = true;
                        return new ComponentParameterHandler(rocket, context);
                }
+               
+               if (element.equals("datatypes")){
+                       if (datatypesDefined) {
+                               warnings.add(Warning.fromString("Multiple datatype blocks. Ignoring later ones."));
+                               return null;
+                       }
+                       datatypesDefined = true;
+                       return new DatatypeHandler(this, context);
+               }
 
                if (element.equals("simulations")) {
                        if (simulationsDefined) {
@@ -697,6 +706,90 @@ class OpenRocketContentHandler extends AbstractElementHandler {
 
 
 
+class DatatypeHandler extends AbstractElementHandler {
+       private final DocumentLoadingContext context;
+       private final OpenRocketContentHandler contentHandler;
+       private CustomExpressionHandler customExpressionHandler = null;
+       
+       public DatatypeHandler(OpenRocketContentHandler contentHandler, DocumentLoadingContext context) {
+               this.context = context;
+               this.contentHandler = contentHandler;
+       }
+       
+       @Override
+       public ElementHandler openElement(String element,
+                       HashMap<String, String> attributes, WarningSet warnings)
+                       throws SAXException {
+               
+               if (element.equals("type") && attributes.get("source").equals("customexpression") ){
+                       customExpressionHandler = new CustomExpressionHandler(contentHandler, context);
+                       return customExpressionHandler;
+               }
+               else {
+                       warnings.add(Warning.fromString("Unknown datatype " + element + " defined, ignoring"));
+               }
+               
+               return this;
+       }
+       
+       @Override
+       public void closeElement(String element, HashMap<String, String> attributes, String content, WarningSet warnings) throws SAXException {
+               attributes.remove("source");
+               super.closeElement(element, attributes, content, warnings);
+               
+               if (customExpressionHandler != null){
+                       contentHandler.getDocument().addCustomExpression(customExpressionHandler.currentExpression);
+               }
+               
+       }
+       
+}
+
+class CustomExpressionHandler extends AbstractElementHandler{
+       private final DocumentLoadingContext context;
+       private final OpenRocketContentHandler contentHandler;
+       public CustomExpression currentExpression;
+       
+       public CustomExpressionHandler(OpenRocketContentHandler contentHandler, DocumentLoadingContext context) {
+               this.context = context;
+               this.contentHandler = contentHandler;
+               currentExpression = new CustomExpression(contentHandler.getDocument());
+
+       }
+       
+       @Override
+       public ElementHandler openElement(String element,
+                       HashMap<String, String> attributes, WarningSet warnings)
+                       throws SAXException {
+                               
+               return this;
+       }
+       
+       @Override
+       public void closeElement(String element, HashMap<String, String> attributes,
+               String content, WarningSet warnings) throws SAXException {
+                               
+               if (element.equals("type")) {
+                       contentHandler.getDocument().addCustomExpression(currentExpression);
+               }
+                       
+               if (element.equals("name")) {
+                       currentExpression.setName(content);
+               }
+                
+               if (element.equals("symbol")) {
+                       currentExpression.setSymbol(content);
+               }
+               
+               if (element.equals("unit") && attributes.get("unittype").equals("auto")) {
+                       currentExpression.setUnit(content);
+               }
+               
+               if (element.equals("expression")){
+                       currentExpression.setExpression(content);
+               }
+       }       
+}
 
 /**
  * A handler that creates components from the corresponding elements.  The control of the
@@ -1211,9 +1304,7 @@ class SingleSimulationHandler extends AbstractElementHandler {
 
        private SimulationConditionsHandler conditionHandler;
        private FlightDataHandler dataHandler;
-       private CustomExpressionsHandler customExpressionsHandler;
-
-       private ArrayList<CustomExpression> customExpressions = new ArrayList<CustomExpression>();
+       
        private final List<String> listeners = new ArrayList<String>();
 
        public SingleSimulationHandler(OpenRocketDocument doc, DocumentLoadingContext context) {
@@ -1221,14 +1312,10 @@ class SingleSimulationHandler extends AbstractElementHandler {
                this.context = context;
        }
 
-       public void setCustomExpressions(ArrayList<CustomExpression> expressions){
-               this.customExpressions = expressions;
-       }
-
-       public ArrayList<CustomExpression> getCustomExpressions(){
-               return customExpressions;
+       public OpenRocketDocument getDocument(){
+               return doc;
        }
-
+       
        @Override
        public ElementHandler openElement(String element, HashMap<String, String> attributes,
                        WarningSet warnings) {
@@ -1236,9 +1323,6 @@ class SingleSimulationHandler extends AbstractElementHandler {
                if (element.equals("name") || element.equals("simulator") ||
                                element.equals("calculator") || element.equals("listener")) {
                        return PlainTextHandler.INSTANCE;
-               } else if (element.equals("customexpressions")) {
-                       customExpressionsHandler = new CustomExpressionsHandler(this, context);
-                       return customExpressionsHandler;
                } else if (element.equals("conditions")) {
                        conditionHandler = new SimulationConditionsHandler(doc.getRocket(), context);
                        return conditionHandler;
@@ -1301,70 +1385,11 @@ class SingleSimulationHandler extends AbstractElementHandler {
 
                Simulation simulation = new Simulation(doc, doc.getRocket(), status, name,
                                conditions, listeners, data);
-
-               // Note : arraylist implementation in simulation different from standard one
-               for (CustomExpression exp : customExpressions){
-                       exp.setSimulation(simulation);
-                       if (exp.checkAll())
-                               simulation.addCustomExpression(exp);
-               }
-
+                               
                doc.addSimulation(simulation);
        }
 }
-
-class CustomExpressionsHandler extends AbstractElementHandler {
-       private final DocumentLoadingContext context;
-       private final SingleSimulationHandler simHandler;
-       public CustomExpression currentExpression = new CustomExpression();
-       private final ArrayList<CustomExpression> customExpressions = new ArrayList<CustomExpression>();
-
-
-       public CustomExpressionsHandler(SingleSimulationHandler simHandler, DocumentLoadingContext context) {
-               this.context = context;
-               this.simHandler = simHandler;
-       }
-
-       @Override
-       public ElementHandler openElement(String element,
-                       HashMap<String, String> attributes, WarningSet warnings)
-                                       throws SAXException {
-
-               if (element.equals("expression")){
-                       currentExpression = new CustomExpression();
-               }
-
-               return this;
-       }
-
-       @Override
-       public void closeElement(String element, HashMap<String, String> attributes,
-                       String content, WarningSet warnings) {
-
-               if (element.equals("expression"))
-                       customExpressions.add(currentExpression);
-
-               if (element.equals("name"))
-                       currentExpression.setName(content);
-
-               else if (element.equals("symbol"))
-                       currentExpression.setSymbol(content);
-
-               else if (element.equals("unit"))
-                       currentExpression.setUnit(content);
-
-               else if (element.equals("expressionstring"))
-                       currentExpression.setExpression(content);
-
-       }
-
-       @Override
-       public void endHandler(String element, HashMap<String, String> attributes,
-                       String content, WarningSet warnings) {
-               simHandler.setCustomExpressions(customExpressions);
-       }
-}
-
+       
 class SimulationConditionsHandler extends AbstractElementHandler {
        private final DocumentLoadingContext context;
        private SimulationOptions conditions;
@@ -1548,7 +1573,7 @@ class FlightDataHandler extends AbstractElementHandler {
        private FlightDataBranchHandler dataHandler;
        private WarningSet warningSet = new WarningSet();
        private List<FlightDataBranch> branches = new ArrayList<FlightDataBranch>();
-
+       
        private SingleSimulationHandler simHandler;
        private FlightData data;
 
@@ -1575,9 +1600,8 @@ class FlightDataHandler extends AbstractElementHandler {
                                return null;
                        }
                        dataHandler = new FlightDataBranchHandler(      attributes.get("name"),
-                                       attributes.get("typekeys"),
-                                       attributes.get("types"), 
-                                       simHandler, context);
+                                                                                                               attributes.get("types"), 
+                                                                                                               simHandler, context);
                        return dataHandler;
                }
 
@@ -1673,23 +1697,18 @@ class FlightDataBranchHandler extends AbstractElementHandler {
        private final DocumentLoadingContext context;
        private final FlightDataType[] types;
        private final FlightDataBranch branch;
-
+       
        private static final LogHelper log = Application.getLogger();
        private final SingleSimulationHandler simHandler;
-
-       public FlightDataBranchHandler(String name, String typeKeyList, String typeList, SingleSimulationHandler simHandler, DocumentLoadingContext context) {
+       
+       public FlightDataBranchHandler(String name, String typeList, SingleSimulationHandler simHandler, DocumentLoadingContext context) {
                this.simHandler = simHandler;
                this.context = context;
-               String[] typeNames = typeList.split(",");
-               String[] typeKeys = null;
-               if ( typeKeyList != null ) {
-                       typeKeys = typeKeyList.split(",");
-               }
-               types = new FlightDataType[typeNames.length];
-               for (int i = 0; i < typeNames.length; i++) {
-                       String typeName = typeNames[i];
-                       String typeKey = (typeKeys != null ) ? typeKeys[i] : null ;
-                       FlightDataType matching = findFlightDataType(typeKey, typeName);
+               String[] split = typeList.split(",");
+               types = new FlightDataType[split.length];
+               for (int i = 0; i < split.length; i++) {
+                       String typeName = split[i];
+                       FlightDataType matching = findFlightDataType(typeName);
                        types[i] = matching;
                        //types[i] = FlightDataType.getType(typeName, matching.getSymbol(), matching.getUnitGroup());
                }
@@ -1697,13 +1716,14 @@ class FlightDataBranchHandler extends AbstractElementHandler {
                // TODO: LOW: May throw an IllegalArgumentException
                branch = new FlightDataBranch(name, types);
        }
-
+       
        // Find the full flight data type given name only
        // Note: this way of doing it requires that custom expressions always come before flight data in the file,
        // not the nicest but this is always the case anyway.
-       private FlightDataType findFlightDataType(String key, String name){
-
-               // Look in built in types by key.
+       private FlightDataType findFlightDataType(String name){
+               
+        // Kevins version with lookup by key. Not using right now
+               /*
                if ( key != null ) {
                        for (FlightDataType t : FlightDataType.ALL_TYPES){
                                if (t.getKey().equals(key) ){
@@ -1711,33 +1731,22 @@ class FlightDataBranchHandler extends AbstractElementHandler {
                                }
                        }
                }
-               // Look in built in types by name.
+               */
+        
+               // Look in built in types
                for (FlightDataType t : FlightDataType.ALL_TYPES){
                        if (t.getName().equals(name) ){
                                return t;
                        }
                }
-
+               
                // Look in custom expressions
-               for (CustomExpression exp : simHandler.getCustomExpressions()){
-                       if (exp.getName().equals(name) ){
-                               return exp.getType();
-                       }
-               }
-
-               // Look in custom expressions, meanwhile set priority based on order in file
-               /*
-               int totalExpressions = simHandler.getCustomExpressions().size();
-               for (int i=0; i<totalExpressions; i++){
-                       CustomExpression exp = simHandler.getCustomExpressions().get(i);                        
+               for (CustomExpression exp : simHandler.getDocument().getCustomExpressions()){
                        if (exp.getName().equals(name) ){
-                               FlightDataType t = exp.getType();
-                               t.setPriority(-1*(totalExpressions-i));
                                return exp.getType();
                        }
                }
-                */
-
+               
                log.warn("Could not find the flight data type '"+name+"' used in the XML file. Substituted type with unknown symbol and units.");
                return FlightDataType.getType(name, "Unknown", UnitGroup.UNITS_NONE);
        }
@@ -2069,132 +2078,133 @@ class ColorSetter implements Setter {
 
 ////ComponentPresetSetter  -  sets a ComponentPreset value
 class ComponentPresetSetter implements Setter {
-       private final Reflection.Method setMethod;
+private final Reflection.Method setMethod;
 
-       public ComponentPresetSetter(Reflection.Method set) {
-               this.setMethod = set;
-       }
+public ComponentPresetSetter(Reflection.Method set) {
+       this.setMethod = set;
+}
 
-       @Override
-       public void set(RocketComponent c, String name, HashMap<String, String> attributes,
-                       WarningSet warnings) {
-               // FIXME - probably need more data in the warning messages - like what component preset...
-               String manufacturerName = attributes.get("manufacturer");
-               if ( manufacturerName == null ) {
-                       warnings.add(Warning.fromString("Invalid ComponentPreset, no manufacturer specified.  Ignored"));
-                       return;
-               }
+@Override
+public void set(RocketComponent c, String name, HashMap<String, String> attributes,
+               WarningSet warnings) {
+       // FIXME - probably need more data in the warning messages - like what component preset...
+       String manufacturerName = attributes.get("manufacturer");
+       if ( manufacturerName == null ) {
+               warnings.add(Warning.fromString("Invalid ComponentPreset, no manufacturer specified.  Ignored"));
+               return;
+       }
 
-               String productNo = attributes.get("partno");
-               if ( productNo == null ) {
-                       warnings.add(Warning.fromString("Invalid ComponentPreset, no partno specified.  Ignored"));
-                       return;
-               }
+       String productNo = attributes.get("partno");
+       if ( productNo == null ) {
+               warnings.add(Warning.fromString("Invalid ComponentPreset, no partno specified.  Ignored"));
+               return;
+       }
 
-               String digest = attributes.get("digest");
-               if ( digest == null ) {
-                       warnings.add(Warning.fromString("Invalid ComponentPreset, no digest specified."));
-               }
+       String digest = attributes.get("digest");
+       if ( digest == null ) {
+               warnings.add(Warning.fromString("Invalid ComponentPreset, no digest specified."));
+       }
 
-               String type = attributes.get("type");
-               if ( type == null ) {
-                       warnings.add(Warning.fromString("Invalid ComponentPreset, no type specified."));
-               }
+       String type = attributes.get("type");
+       if ( type == null ) {
+               warnings.add(Warning.fromString("Invalid ComponentPreset, no type specified."));
+       }
 
-               List<ComponentPreset> presets = Application.getComponentPresetDao().find( manufacturerName, productNo );
+       List<ComponentPreset> presets = Application.getComponentPresetDao().find( manufacturerName, productNo );
 
-               ComponentPreset matchingPreset = null;
+       ComponentPreset matchingPreset = null;
 
-               for( ComponentPreset preset: presets ) {
-                       if ( digest != null && preset.getDigest().equals(digest) ) {
-                               // Found one with matching digest.  Take it.
-                               matchingPreset = preset;
-                               break;
-                       }
-                       if ( type != null && preset.getType().name().equals(type) && matchingPreset != null) {
-                               // Found the first one with matching type.
-                               matchingPreset = preset;
-                       }
+       for( ComponentPreset preset: presets ) {
+               if ( digest != null && preset.getDigest().equals(digest) ) {
+                       // Found one with matching digest.  Take it.
+                       matchingPreset = preset;
+                       break;
                }
-
-               // Was any found?
-               if ( matchingPreset == null ) {
-                       warnings.add(Warning.fromString("No matching ComponentPreset found " + manufacturerName + " " + productNo));
-                       return;
+               if ( type != null && preset.getType().name().equals(type) && matchingPreset != null) {
+                       // Found the first one with matching type.
+                       matchingPreset = preset;
                }
+       }
 
-               if ( digest != null && !matchingPreset.getDigest().equals(digest) ) {
-                       warnings.add(Warning.fromString("ComponentPreset has wrong digest"));
-               }
+       // Was any found?
+       if ( matchingPreset == null ) {
+               warnings.add(Warning.fromString("No matching ComponentPreset found " + manufacturerName + " " + productNo));
+               return;
+       }
 
-               setMethod.invoke(c, matchingPreset);
+       if ( digest != null && !matchingPreset.getDigest().equals(digest) ) {
+               warnings.add(Warning.fromString("ComponentPreset has wrong digest"));
        }
+
+       setMethod.invoke(c, matchingPreset);
+}
 }
 
 
 ////MaterialSetter  -  sets a Material value
 class MaterialSetter implements Setter {
-       private final Reflection.Method setMethod;
-       private final Material.Type type;
+private final Reflection.Method setMethod;
+private final Material.Type type;
 
-       public MaterialSetter(Reflection.Method set, Material.Type type) {
-               this.setMethod = set;
-               this.type = type;
-       }
+public MaterialSetter(Reflection.Method set, Material.Type type) {
+       this.setMethod = set;
+       this.type = type;
+}
 
-       @Override
-       public void set(RocketComponent c, String name, HashMap<String, String> attributes,
-                       WarningSet warnings) {
+@Override
+public void set(RocketComponent c, String name, HashMap<String, String> attributes,
+               WarningSet warnings) {
 
-               Material mat;
+       Material mat;
 
-               // Check name != ""
-               name = name.trim();
-               if (name.equals("")) {
-                       warnings.add(Warning.fromString("Illegal material specification, ignoring."));
-                       return;
-               }
+       // Check name != ""
+       name = name.trim();
+       if (name.equals("")) {
+               warnings.add(Warning.fromString("Illegal material specification, ignoring."));
+               return;
+       }
 
-               // Parse density
-               double density;
-               String str;
-               str = attributes.remove("density");
-               if (str == null) {
-                       warnings.add(Warning.fromString("Illegal material specification, ignoring."));
-                       return;
-               }
-               try {
-                       density = Double.parseDouble(str);
-               } catch (NumberFormatException e) {
-                       warnings.add(Warning.fromString("Illegal material specification, ignoring."));
-                       return;
-               }
+       // Parse density
+       double density;
+       String str;
+       str = attributes.remove("density");
+       if (str == null) {
+               warnings.add(Warning.fromString("Illegal material specification, ignoring."));
+               return;
+       }
+       try {
+               density = Double.parseDouble(str);
+       } catch (NumberFormatException e) {
+               warnings.add(Warning.fromString("Illegal material specification, ignoring."));
+               return;
+       }
 
-               // Parse thickness
-               //              double thickness = 0;
-               //              str = attributes.remove("thickness");
-               //              try {
-               //                      if (str != null)
-               //                              thickness = Double.parseDouble(str);
-               //              } catch (NumberFormatException e){
-               //                      warnings.add(Warning.fromString("Illegal material specification, ignoring."));
-               //                      return;
-               //              }
-
-               // Check type if specified
-               str = attributes.remove("type");
-               if (str != null && !type.name().toLowerCase(Locale.ENGLISH).equals(str)) {
-                       warnings.add(Warning.fromString("Illegal material type specified, ignoring."));
-                       return;
-               }
+       // Parse thickness
+       //              double thickness = 0;
+       //              str = attributes.remove("thickness");
+       //              try {
+       //                      if (str != null)
+       //                              thickness = Double.parseDouble(str);
+       //              } catch (NumberFormatException e){
+       //                      warnings.add(Warning.fromString("Illegal material specification, ignoring."));
+       //                      return;
+       //              }
 
-               String key = attributes.remove("key");
+       // Check type if specified
+       str = attributes.remove("type");
+       if (str != null && !type.name().toLowerCase(Locale.ENGLISH).equals(str)) {
+               warnings.add(Warning.fromString("Illegal material type specified, ignoring."));
+               return;
+       }
 
-               mat = Databases.findMaterial(type, key, name, density);
+       String key = attributes.remove("key");
 
-               setMethod.invoke(c, mat);
-       }
+       mat = Databases.findMaterial(type, key, name, density);
+
+       setMethod.invoke(c, mat);
 }
+}
+
 
 
 
index 44d5599d2fecc584c7aa9c0de7fc20485f0f3bbf..be7c52af809133a7fa31239e1b20065b7d1d74f1 100644 (file)
@@ -7,7 +7,6 @@ import java.util.Locale;
 import net.sf.openrocket.rocketcomponent.ReferenceType;
 import net.sf.openrocket.rocketcomponent.Rocket;
 
-
 public class RocketSaver extends RocketComponentSaver {
        
        private static final RocketSaver instance = new RocketSaver();
@@ -22,8 +21,6 @@ public class RocketSaver extends RocketComponentSaver {
                return list;
        }
        
-       
-       
        @Override
        protected void addParams(net.sf.openrocket.rocketcomponent.RocketComponent c, List<String> elements) {
                super.addParams(c, elements);
diff --git a/core/src/net/sf/openrocket/gui/customexpression/CustomExpressionDialog.java b/core/src/net/sf/openrocket/gui/customexpression/CustomExpressionDialog.java
new file mode 100644 (file)
index 0000000..b8dee9f
--- /dev/null
@@ -0,0 +1,35 @@
+package net.sf.openrocket.gui.customexpression;
+
+import java.awt.Window;
+
+import javax.swing.BorderFactory;
+import javax.swing.JDialog;
+import javax.swing.JPanel;
+
+import net.sf.openrocket.document.OpenRocketDocument;
+import net.sf.openrocket.document.Simulation;
+import net.sf.openrocket.gui.util.GUIUtil;
+import net.sf.openrocket.l10n.Translator;
+import net.sf.openrocket.logging.LogHelper;
+import net.sf.openrocket.rocketcomponent.Rocket;
+import net.sf.openrocket.startup.Application;
+
+public class CustomExpressionDialog extends JDialog {
+       private static final Translator trans = Application.getTranslator();
+       private static final LogHelper log = Application.getLogger();
+       
+       private final Window parentWindow;
+       private final OpenRocketDocument doc;
+       
+       public CustomExpressionDialog(OpenRocketDocument doc, Window parent){
+               super(parent, trans.get("customExpressionPanel.lbl.CustomExpressions"));
+               
+               this.doc = doc;
+               this.parentWindow = parent;
+               
+               JPanel panel = new CustomExpressionPanel(doc, this);
+               this.add( panel );
+               
+               GUIUtil.setDisposableDialogOptions(this, null);
+       }
+}
index cf35cbb409dda9d942ff07a815845e39004284b7..ec60f25000dd483f603c878151675cc1b58b839e 100644 (file)
@@ -4,17 +4,28 @@ import java.awt.Color;
 import java.awt.Window;
 import java.awt.event.ActionEvent;
 import java.awt.event.ActionListener;
+import java.io.File;
 import java.util.ArrayList;
 import java.util.Collections;
 
 import javax.swing.BorderFactory;
 import javax.swing.JButton;
+import javax.swing.JDialog;
+import javax.swing.JFileChooser;
 import javax.swing.JLabel;
 import javax.swing.JPanel;
 import javax.swing.JScrollPane;
 import javax.swing.SwingUtilities;
+import javax.swing.border.Border;
+import javax.swing.filechooser.FileFilter;
+import javax.swing.filechooser.FileNameExtensionFilter;
 
 import net.miginfocom.swing.MigLayout;
+import net.sf.openrocket.document.OpenRocketDocument;
+import net.sf.openrocket.file.DatabaseMotorFinder;
+import net.sf.openrocket.file.GeneralRocketLoader;
+import net.sf.openrocket.file.MotorFinder;
+import net.sf.openrocket.file.RocketLoadException;
 import net.sf.openrocket.document.Simulation;
 import net.sf.openrocket.gui.components.DescriptionArea;
 import net.sf.openrocket.gui.components.UnitSelector;
@@ -22,7 +33,8 @@ import net.sf.openrocket.gui.customexpression.ExpressionBuilderDialog;
 import net.sf.openrocket.gui.util.Icons;
 import net.sf.openrocket.l10n.Translator;
 import net.sf.openrocket.logging.LogHelper;
-import net.sf.openrocket.simulation.CustomExpression;
+import net.sf.openrocket.rocketcomponent.Rocket;
+import net.sf.openrocket.simulation.customexpression.CustomExpression;
 import net.sf.openrocket.startup.Application;
 
 public class CustomExpressionPanel extends JPanel {
@@ -31,34 +43,91 @@ public class CustomExpressionPanel extends JPanel {
        private static final Translator trans = Application.getTranslator();
        
        private JPanel expressionSelectorPanel;
-       private Simulation simulation;
+       private OpenRocketDocument doc;
        
-       public CustomExpressionPanel(final Simulation simulation) {
+       public CustomExpressionPanel(final OpenRocketDocument doc, final JDialog parentDialog) {
                super(new MigLayout("fill"));
-               this.simulation = simulation;
+               this.doc = doc;
 
                expressionSelectorPanel = new JPanel(new MigLayout("gapy rel"));
-               JScrollPane scroll = new JScrollPane(expressionSelectorPanel);
-               this.add(scroll, "spany 2, height 10px, wmin 400lp, grow 100, gapright para");
+               expressionSelectorPanel.setToolTipText(trans.get("customExpressionPanel.lbl.CalcNote"));
                
-               DescriptionArea desc = new DescriptionArea(trans.get("customExpressionPanel.lbl.UpdateNote")+"\n\n"+trans.get("customExpressionPanel.lbl.CalcNote"), 8, -2f);
-               desc.setViewportBorder(BorderFactory.createEmptyBorder());
-               this.add(desc, "width 1px, growx 1, wrap unrel");
+               JScrollPane scroll = new JScrollPane();
+               Border bdr = BorderFactory.createTitledBorder(trans.get("customExpressionPanel.lbl.CustomExpressions"));
+
+               expressionSelectorPanel.setBorder(bdr);
+               expressionSelectorPanel.add(scroll);
+               
+               //this.add(expressionSelectorPanel, "spany 1, height 10px, wmin 600lp, grow 100, gapright para");
+               this.add(expressionSelectorPanel, "hmin 200lp, wmin 700lp, grow 100, wrap");
+               
+               //DescriptionArea desc = new DescriptionArea(trans.get("customExpressionPanel.lbl.UpdateNote")+"\n\n"+trans.get("customExpressionPanel.lbl.CalcNote"), 8, -2f);
+               //desc.setViewportBorder(BorderFactory.createEmptyBorder());
+               //this.add(desc, "width 1px, growx 1, wrap unrel, wrap");
                
                //// New expression
                JButton button = new JButton(trans.get("customExpressionPanel.but.NewExpression"));
+               button.setToolTipText(trans.get("customExpressionPanel.but.ttip.NewExpression"));
                button.addActionListener(new ActionListener() {
                        @Override
                        public void actionPerformed(ActionEvent e) {
                                // Open window to configure expression
-                               log.debug("Opening window to configure new expression");
+                               log.info("Opening window to configure new expression");
                                Window parent = SwingUtilities.getWindowAncestor(CustomExpressionPanel.this);
-                               new ExpressionBuilderDialog(parent, simulation).setVisible(true);
+                               new ExpressionBuilderDialog(parent, doc).setVisible(true);
                                updateExpressions();
                        }
                });
+               this.add(button, "split 4, width :100:200");
+               
+               //// Import
+               final JButton importButton = new JButton(trans.get("customExpressionPanel.but.Import"));
+               importButton.setToolTipText(trans.get("customExpressionPanel.but.ttip.Import"));
+               importButton.addActionListener(new ActionListener() {
+                       @Override
+                       public void actionPerformed(ActionEvent e) {
+                               
+                               //Create a file chooser
+                               final JFileChooser fc = new JFileChooser();
+                               if (doc.getFile() != null){
+                                       fc.setCurrentDirectory(doc.getFile().getParentFile());
+                               }
+                               fc.setFileFilter(new FileNameExtensionFilter("Openrocket file", "ork"));
+                               fc.setAcceptAllFileFilterUsed(false);
                                
-               this.add(button, "left");
+                               int returnVal = fc.showOpenDialog(CustomExpressionPanel.this);
+                               if (returnVal == JFileChooser.APPROVE_OPTION){
+                                       File importFile = fc.getSelectedFile();
+                                       log.info("User selected a file to import expressions from "+fc.getSelectedFile().toString());
+                                       
+                                       //TODO: This should probably be somewhere else and ideally we would use an alternative minimal rocket loader. Still, it doesn't seem particularly slow this way.
+                                       
+                                       // Load expressions from selected document
+                                       GeneralRocketLoader loader = new GeneralRocketLoader();
+                                       try {
+                                               OpenRocketDocument importedDocument = loader.load(importFile, new DatabaseMotorFinder());
+                                               for (CustomExpression exp : importedDocument.getCustomExpressions()){
+                                                       doc.addCustomExpression(exp);
+                                               }
+                                       } catch (RocketLoadException e1) {
+                                               log.user("Error opening document to import expressions from.");
+                                       }
+                                       updateExpressions();
+                               }
+                       }
+               });
+               this.add(importButton, "width :100:200");
+               
+               //// Close button
+               final JButton closeButton = new JButton(trans.get("dlg.but.close"));
+               closeButton.addActionListener(new ActionListener() {
+                       @Override
+                       public void actionPerformed(ActionEvent e) {
+                               parentDialog.dispose();
+                       }
+               });
+               this.add(new JPanel(), "growx");
+               this.add(closeButton, "width :100:200");
                
                updateExpressions();
        }
@@ -69,19 +138,18 @@ public class CustomExpressionPanel extends JPanel {
        private void updateExpressions(){
                
                expressionSelectorPanel.removeAll();
-               int totalExpressions = simulation.getCustomExpressions().size();
+               int totalExpressions = doc.getCustomExpressions().size();
                for (int i=0; i<totalExpressions; i++){
-                       SingleExpression se = new SingleExpression(simulation.getCustomExpressions().get(i), i != 0, i != totalExpressions-1);
+                       SingleExpression se = new SingleExpression(doc.getCustomExpressions().get(i), i != 0, i != totalExpressions-1);
                        expressionSelectorPanel.add(se, "wrap");
                }
 
-               //TODO: High : Find out why repaint method not working properly here.
-               //expressionSelectorPanel.repaint();
-               expressionSelectorPanel.updateUI(); // Not the correct method to use but works
+               expressionSelectorPanel.revalidate();
+               expressionSelectorPanel.repaint();
        }
        
        private void deleteExpression(CustomExpression expression){
-               simulation.getCustomExpressions().remove(expression);
+               doc.getCustomExpressions().remove(expression);
        }
        
        /**
@@ -90,7 +158,7 @@ public class CustomExpressionPanel extends JPanel {
         * @param move integer - +1 to move down, -1 to move up
         */
        private void moveExpression(CustomExpression expression, int move){
-               ArrayList<CustomExpression> expressions = simulation.getCustomExpressions();
+               ArrayList<CustomExpression> expressions = doc.getCustomExpressions();
                int i = expressions.indexOf(expression);
                if (i+move == expressions.size() || i+move < 0)
                        return;
@@ -128,6 +196,9 @@ public class CustomExpressionPanel extends JPanel {
                        
                        JLabel unitLabel = new JLabel( trans.get("customExpression.Units")+ " :");
                        UnitSelector unitSelector = new UnitSelector(expression.getType().getUnitGroup());
+                       //JLabel unitSelector = new JLabel ( expression.getUnit() );
+                       //unitSelector = setLabelStyle(unitSelector);
+                       //unitSelector.setBackground(Color.WHITE);
                        
                        JButton editButton = new JButton(Icons.EDIT);
                        editButton.setToolTipText(trans.get("customExpression.Units.but.ttip.Edit"));
@@ -136,7 +207,7 @@ public class CustomExpressionPanel extends JPanel {
                                @Override
                                public void actionPerformed(ActionEvent e){
                                        Window parent = SwingUtilities.getWindowAncestor(CustomExpressionPanel.this);
-                                       new ExpressionBuilderDialog(parent, expression.getSimulation(), expression).setVisible(true);
+                                       new ExpressionBuilderDialog(parent, doc, expression).setVisible(true);
                                        updateExpressions();
                                }
                        });
index 77fa3a4b2853f019f5b02572f68ba5d4d5b46ab5..615fdc14987c758a89ff85718f0f8d574a6f536f 100644 (file)
@@ -19,11 +19,13 @@ import javax.swing.SwingConstants;
 import javax.swing.SwingUtilities;
 
 import net.miginfocom.swing.MigLayout;
+import net.sf.openrocket.document.OpenRocketDocument;
 import net.sf.openrocket.document.Simulation;
 import net.sf.openrocket.gui.util.Icons;
 import net.sf.openrocket.l10n.Translator;
 import net.sf.openrocket.logging.LogHelper;
-import net.sf.openrocket.simulation.CustomExpression;
+import net.sf.openrocket.rocketcomponent.Rocket;
+import net.sf.openrocket.simulation.customexpression.CustomExpression;
 import net.sf.openrocket.startup.Application;
 
 /**
@@ -44,7 +46,7 @@ public class ExpressionBuilderDialog extends JDialog {
        private CustomExpression previousExpressionCopy;
        
        private final Window parentWindow;
-       private final Simulation simulation;
+       private final OpenRocketDocument doc;
        
        // Define these check indicators to show if fields are OK
        private final JLabel nameCheck = new JLabel(RedIcon);
@@ -53,16 +55,16 @@ public class ExpressionBuilderDialog extends JDialog {
        private final JButton okButton = new JButton(trans.get("dlg.but.ok"));
        private final JTextField expressionField = new JTextField(20);
        
-       public ExpressionBuilderDialog(Window parent, Simulation simulation){
-               this(parent, simulation, new CustomExpression(simulation));
+       public ExpressionBuilderDialog(Window parent, OpenRocketDocument doc){
+               this(parent, doc, new CustomExpression(doc));
        }
        
-       public ExpressionBuilderDialog(Window parent, final Simulation simulation, final CustomExpression previousExpression){
+       public ExpressionBuilderDialog(Window parent, final OpenRocketDocument doc, final CustomExpression previousExpression){
                
                super(parent, trans.get("ExpressionBuilderDialog.title"), JDialog.ModalityType.DOCUMENT_MODAL);
                
+               this.doc = doc;
                this.parentWindow = parent;
-               this.simulation = simulation;
                this.previousExpressionCopy = (CustomExpression) previousExpression.clone();
                this.expression = previousExpression;
                                        
@@ -159,7 +161,7 @@ public class ExpressionBuilderDialog extends JDialog {
                        public void actionPerformed(ActionEvent e) {
                                log.debug("Opening insert variable window");
                                Window parentWindow = SwingUtilities.getWindowAncestor(ExpressionBuilderDialog.this);
-                               new VariableSelector(parentWindow, ExpressionBuilderDialog.this, simulation).setVisible(true);
+                               new VariableSelector(parentWindow, ExpressionBuilderDialog.this, doc).setVisible(true);
                        }
                });
                
@@ -174,21 +176,13 @@ public class ExpressionBuilderDialog extends JDialog {
                        }
                });
                
-               //// Copy expression check box
-               final JCheckBox copyCheckBox = new JCheckBox(trans.get("ExpressionBuilderDialog.CopyToOtherSimulations"));
-               copyCheckBox.setHorizontalTextPosition(SwingConstants.LEFT);
-               copyCheckBox.setToolTipText(trans.get("ExpressionBuilderDialog.CopyToOtherSimulations.ttip"));
-               
                //// OK Button
                okButton.setEnabled(false);
                okButton.addActionListener(new ActionListener() {
                        @Override
                        public void actionPerformed(ActionEvent e) {
                                // add to this simulation
-                               expression.addToSimulation();
-                               if (copyCheckBox.isSelected()){
-                                       expression.copyToOtherSimulations();
-                               }
+                               expression.addToDocument();
                                
                                // close window
                                ExpressionBuilderDialog.this.dispose();
@@ -226,7 +220,6 @@ public class ExpressionBuilderDialog extends JDialog {
                mainPanel.add(expressionCheck, "wrap, center");
                mainPanel.add(insertOperatorButton, "span 2, right, split 2");
                mainPanel.add(insertVariableButton, "right, wrap");
-               mainPanel.add(copyCheckBox, "span 2, right, wrap");
                mainPanel.add(cancelButton, "span 2, right, width :50:100");
                mainPanel.add(okButton, "right, width :50:100, wrap");
 
index bc1f8066c50b86c5ebc44e4a8f4e80ac523700f9..49f4865708bf2fc62cb0600ecddc1b6e8471ec74 100644 (file)
@@ -1,14 +1,24 @@
 package net.sf.openrocket.gui.customexpression;
 
+import java.awt.Point;
 import java.awt.Window;
 import java.awt.event.ActionEvent;
 import java.awt.event.ActionListener;
+import java.awt.event.KeyEvent;
+import java.awt.event.MouseEvent;
+import java.awt.event.MouseListener;
+import java.awt.event.MouseMotionAdapter;
 
+import javax.swing.AbstractAction;
+import javax.swing.ActionMap;
+import javax.swing.InputMap;
 import javax.swing.JButton;
+import javax.swing.JComponent;
 import javax.swing.JDialog;
 import javax.swing.JPanel;
 import javax.swing.JScrollPane;
 import javax.swing.JTable;
+import javax.swing.KeyStroke;
 import javax.swing.event.ListSelectionEvent;
 import javax.swing.event.ListSelectionListener;
 
@@ -16,6 +26,7 @@ import net.miginfocom.swing.MigLayout;
 import net.sf.openrocket.l10n.Translator;
 import net.sf.openrocket.logging.LogHelper;
 import net.sf.openrocket.startup.Application;
+import net.sf.openrocket.util.TextUtil;
 
 public class OperatorSelector extends JDialog {
        
@@ -24,24 +35,76 @@ public class OperatorSelector extends JDialog {
 
        private final Window parentWindow;
        
+       private final JTable table;
+       private final OperatorTableModel tableModel;
+       private final ExpressionBuilderDialog parentBuilder;
+       
        public OperatorSelector(Window parent, final ExpressionBuilderDialog parentBuilder){
                
                super(parent, trans.get("CustomOperatorSelector.title"), JDialog.ModalityType.DOCUMENT_MODAL);
                
                this.parentWindow = parent;
+               this.parentBuilder = parentBuilder;
                
                final JButton insertButton = new JButton(trans.get("ExpressionBuilderDialog.InsertOperator"));
                
                JPanel mainPanel = new JPanel(new MigLayout());
                
                //// Table of variables and model
-               final OperatorTableModel tableModel = new OperatorTableModel();
-               final JTable table = new JTable(tableModel);
+               tableModel = new OperatorTableModel();
+               table = new JTable(tableModel);
                
                table.setSelectionMode(javax.swing.ListSelectionModel.SINGLE_SELECTION);
                int width = table.getColumnModel().getTotalColumnWidth();
-               table.getColumnModel().getColumn(0).setPreferredWidth( (int) (.2 * width));
-               table.getColumnModel().getColumn(1).setPreferredWidth( (int) (.8 * width));
+               table.getColumnModel().getColumn(0).setPreferredWidth( (int) (.1 * width));
+               table.getColumnModel().getColumn(1).setPreferredWidth( (int) (.9 * width));
+               table.setAutoCreateRowSorter(true);
+               
+               table.addMouseMotionListener(new MouseMotionAdapter(){
+                       @Override
+                       public void mouseMoved(MouseEvent e){
+                               Point p = e.getPoint();
+                               int row = table.rowAtPoint(p);
+                               int col = table.columnAtPoint(p);
+                               if (col == 1){
+                                       String description = String.valueOf(table.getValueAt(row, 1));
+                                       description = TextUtil.wrap(description, 60);
+                                       table.setToolTipText(description);
+                               } else {
+                                       table.setToolTipText(null);
+                               }
+                       }
+               });
+               
+               table.addMouseListener(new MouseListener(){
+                       @Override
+                       public void mouseClicked(MouseEvent e){
+                               if (e.getClickCount() == 2){
+                                       log.debug("Selected operator by double clicking.");
+                                       selectOperator();
+                               }
+                       }
+                       @Override
+                       public void mouseEntered(MouseEvent e) {}
+                       @Override
+                       public void mouseExited(MouseEvent e) {}
+                       @Override
+                       public void mousePressed(MouseEvent e) {}
+                       @Override
+                       public void mouseReleased(MouseEvent e) {}
+               } );
+               
+               InputMap inputMap = table.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
+               ActionMap actionMap = table.getActionMap();
+               KeyStroke enter = KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0);
+               inputMap.put(enter, "select");
+               actionMap.put("select", new AbstractAction(){
+                       @Override
+                       public void actionPerformed(ActionEvent arg0) {
+                               log.debug("Selected operator by enter key");
+                               selectOperator();
+                       }
+               });
                
                JScrollPane scrollPane = new JScrollPane(table);
                table.setFillsViewportHeight(true);
@@ -57,7 +120,7 @@ public class OperatorSelector extends JDialog {
                                }
                        });
                
-               mainPanel.add(scrollPane, "wrap");
+               mainPanel.add(scrollPane, "wrap, push, grow");
                
                //// Cancel button
                final JButton cancelButton = new JButton(trans.get("dlg.but.cancel"));
@@ -73,10 +136,7 @@ public class OperatorSelector extends JDialog {
                insertButton.addActionListener(new ActionListener() {
                        @Override
                        public void actionPerformed(ActionEvent e) {
-                               int row = table.getSelectedRow();
-                               String str = tableModel.getOperatorAt(row);
-                               parentBuilder.pasteIntoExpression(str);
-                               OperatorSelector.this.dispose();
+                               selectOperator();
                        }
                });
                insertButton.setEnabled(false); // disabled by default, only enable when a variable selected
@@ -87,4 +147,11 @@ public class OperatorSelector extends JDialog {
                this.pack();
                this.setLocationByPlatform(true);       
        }
+       
+       private void selectOperator(){
+               int row = table.getSelectedRow();
+               String str = tableModel.getOperatorAt(row);
+               parentBuilder.pasteIntoExpression(str);
+               OperatorSelector.this.dispose();
+       }
 }
index 76a1a8f2d9ee1e777220996489e9d5f4c130d4bb..3b084da51a86434bc93d3e0eae249ab760f23b5c 100644 (file)
@@ -3,7 +3,7 @@ package net.sf.openrocket.gui.customexpression;
 import javax.swing.table.AbstractTableModel;
 
 import net.sf.openrocket.l10n.Translator;
-import net.sf.openrocket.simulation.CustomExpression;
+import net.sf.openrocket.simulation.customexpression.Functions;
 import net.sf.openrocket.startup.Application;
 
 public class OperatorTableModel extends AbstractTableModel {
@@ -12,8 +12,8 @@ public class OperatorTableModel extends AbstractTableModel {
        
        private static final String[] columnNames = {trans.get("customExpression.Operator"), trans.get("customExpression.Description")};
        
-       private final Object[] operators = CustomExpression.AVAILABLE_OPERATORS.keySet().toArray();
-       private final Object[] descriptions = CustomExpression.AVAILABLE_OPERATORS.values().toArray();
+       private final Object[] operators = Functions.AVAILABLE_OPERATORS.keySet().toArray();
+       private final Object[] descriptions = Functions.AVAILABLE_OPERATORS.values().toArray();
        
        public OperatorTableModel(){
                
@@ -26,7 +26,7 @@ public class OperatorTableModel extends AbstractTableModel {
 
        @Override
        public int getRowCount() {
-               return CustomExpression.AVAILABLE_OPERATORS.size();
+               return Functions.AVAILABLE_OPERATORS.size();
        }
 
        @Override
index d432a18cf2c30994764ab2bcc318b3dec0c4ee16..5e0cfe9c16778aef0fa934f72c9731b9ef5e6322 100644 (file)
@@ -3,19 +3,31 @@ package net.sf.openrocket.gui.customexpression;
 import java.awt.Window;
 import java.awt.event.ActionEvent;
 import java.awt.event.ActionListener;
+import java.awt.event.KeyEvent;
+import java.awt.event.KeyListener;
+import java.awt.event.MouseEvent;
+import java.awt.event.MouseListener;
 
+import javax.swing.AbstractAction;
+import javax.swing.ActionMap;
+import javax.swing.InputMap;
 import javax.swing.JButton;
+import javax.swing.JComponent;
 import javax.swing.JDialog;
 import javax.swing.JPanel;
 import javax.swing.JScrollPane;
 import javax.swing.JTable;
+import javax.swing.KeyStroke;
 import javax.swing.event.ListSelectionEvent;
 import javax.swing.event.ListSelectionListener;
+import javax.swing.table.JTableHeader;
 
 import net.miginfocom.swing.MigLayout;
+import net.sf.openrocket.document.OpenRocketDocument;
 import net.sf.openrocket.document.Simulation;
 import net.sf.openrocket.l10n.Translator;
 import net.sf.openrocket.logging.LogHelper;
+import net.sf.openrocket.rocketcomponent.Rocket;
 import net.sf.openrocket.startup.Application;
 
 /**
@@ -25,50 +37,82 @@ import net.sf.openrocket.startup.Application;
  */
 
 public class VariableSelector extends JDialog {
-       
+
        private static final Translator trans = Application.getTranslator();
        private static final LogHelper log = Application.getLogger();
-
-       private final Window parentWindow;
-       private final Simulation simulation;
        
-       public VariableSelector(Window parent, final ExpressionBuilderDialog parentBuilder, final Simulation simulation){
-               
+       private final JTable table;
+       private final VariableTableModel tableModel;
+       private final ExpressionBuilderDialog parentBuilder;
+
+       public VariableSelector(Window parent, final ExpressionBuilderDialog parentBuilder, final OpenRocketDocument doc){
+
                super(parent, trans.get("CustomVariableSelector.title"), JDialog.ModalityType.DOCUMENT_MODAL);
-               
-               this.parentWindow = parent;
-               this.simulation = simulation;
-               
+
+               this.parentBuilder = parentBuilder;
                final JButton insertButton = new JButton(trans.get("ExpressionBuilderDialog.InsertVariable"));
-               
+
                JPanel mainPanel = new JPanel(new MigLayout());
-               
+
                //// Table of variables and model
-               final VariableTableModel tableModel = new VariableTableModel(simulation);
-               final JTable table = new JTable(tableModel);
+               tableModel = new VariableTableModel(doc);
+               table = new JTable(tableModel);
                
+               table.setAutoResizeMode(JTable.AUTO_RESIZE_ALL_COLUMNS);
                table.setSelectionMode(javax.swing.ListSelectionModel.SINGLE_SELECTION);
                int width = table.getColumnModel().getTotalColumnWidth();
                table.getColumnModel().getColumn(0).setPreferredWidth( (int) (.7 * width));
                table.getColumnModel().getColumn(1).setPreferredWidth( (int) (.15 * width));
                table.getColumnModel().getColumn(2).setPreferredWidth( (int) (.15 * width));
+               table.setAutoCreateRowSorter(true);
+
+               table.addMouseListener(new MouseListener(){
+                       @Override
+                       public void mouseClicked(MouseEvent e){
+                               if (e.getClickCount() == 2){
+                                       log.debug("Selected variable by double clicking.");
+                                       selectVariable();
+                               }
+                       }
+                       @Override
+                       public void mouseEntered(MouseEvent e) {}
+                       @Override
+                       public void mouseExited(MouseEvent e) {}
+                       @Override
+                       public void mousePressed(MouseEvent e) {}
+                       @Override
+                       public void mouseReleased(MouseEvent e) {}
+               } );
+               
+               InputMap inputMap = table.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
+               ActionMap actionMap = table.getActionMap();
+               KeyStroke enter = KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0);
+               inputMap.put(enter, "select");
+               actionMap.put("select", new AbstractAction(){
+                       @Override
+                       public void actionPerformed(ActionEvent arg0) {
+                               log.debug("Selected variable by enter key");
+                               selectVariable();
+                       }
+               });
                
+
                JScrollPane scrollPane = new JScrollPane(table);
                table.setFillsViewportHeight(true);
                table.getSelectionModel().addListSelectionListener(new ListSelectionListener() {
-                               @Override
-                               public void valueChanged(ListSelectionEvent e){
-                                       if (table.getSelectedRowCount() == 1){
-                                               insertButton.setEnabled(true);
-                                       }
-                                       else {
-                                               insertButton.setEnabled(false);
-                                       }
+                       @Override
+                       public void valueChanged(ListSelectionEvent e){
+                               if (table.getSelectedRowCount() == 1){
+                                       insertButton.setEnabled(true);
                                }
-                       });
-               
-               mainPanel.add(scrollPane, "wrap");
-               
+                               else {
+                                       insertButton.setEnabled(false);
+                               }
+                       }
+               });
+
+               mainPanel.add(scrollPane, "wrap, push, grow");
+
                //// Cancel button
                final JButton cancelButton = new JButton(trans.get("dlg.but.cancel"));
                cancelButton.addActionListener(new ActionListener() {
@@ -78,15 +122,12 @@ public class VariableSelector extends JDialog {
                        }
                });
                mainPanel.add(cancelButton, "right, width :100:200, split 2");
-               
+
                //// Insert button
                insertButton.addActionListener(new ActionListener() {
                        @Override
                        public void actionPerformed(ActionEvent e) {
-                               int row = table.getSelectedRow();
-                               String str = tableModel.getSymbolAt(row);
-                               parentBuilder.pasteIntoExpression(str);
-                               VariableSelector.this.dispose();
+                               selectVariable();
                        }
                });
                insertButton.setEnabled(false); // disabled by default, only enable when a variable selected
@@ -97,4 +138,12 @@ public class VariableSelector extends JDialog {
                this.pack();
                this.setLocationByPlatform(true);       
        }
+       
+       private void selectVariable(){
+               int row = table.getSelectedRow();
+               String str = tableModel.getSymbolAt(row);
+               parentBuilder.pasteIntoExpression(str);
+               VariableSelector.this.dispose();
+       }
+       
 }
index b81a6b05e388e188a5cb680cbb90fdc2dd967b14..950828add1c798d9bdee3b834affe34d5cc84853 100644 (file)
@@ -3,15 +3,20 @@
  */
 package net.sf.openrocket.gui.customexpression;
 
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.Collections;
+import java.util.Vector;
 
+import javax.swing.JTable;
 import javax.swing.table.AbstractTableModel;
+import javax.swing.table.TableColumn;
+import javax.swing.table.TableColumnModel;
 
-import net.sf.openrocket.document.Simulation;
+import net.sf.openrocket.document.OpenRocketDocument;
 import net.sf.openrocket.l10n.Translator;
-import net.sf.openrocket.simulation.CustomExpression;
+import net.sf.openrocket.simulation.customexpression.CustomExpression;
 import net.sf.openrocket.simulation.FlightDataType;
 import net.sf.openrocket.startup.Application;
 
@@ -29,14 +34,13 @@ public class VariableTableModel extends AbstractTableModel {
        /*
         * Table model will be constructed with all the built in variables and any custom variables defined
         */
-       public VariableTableModel(Simulation sim){
+       public VariableTableModel(OpenRocketDocument doc){
                
                Collections.addAll(types, FlightDataType.ALL_TYPES);
                
-               for (CustomExpression expression : sim.getCustomExpressions()){
+               for (CustomExpression expression : doc.getCustomExpressions()){
                        types.add(expression.getType());
                }
-               
        }
        
        @Override
@@ -56,7 +60,7 @@ public class VariableTableModel extends AbstractTableModel {
                else if (col == 1)
                        return types.get(row).getSymbol();
                else if (col == 2)
-                       return types.get(row).getUnitGroup().getDefaultUnit().toString();
+                       return types.get(row).getUnitGroup().getSIUnit().toString();
                
                return null;
        }
index 9f758c347a5d971150561eab92cc7f476ef53142..ab6174825c12ad53f647e28b156c4bc601e175e7 100644 (file)
@@ -11,6 +11,7 @@ import net.sf.openrocket.file.openrocket.OpenRocketSaver;
 import net.sf.openrocket.file.rocksim.export.RocksimSaver;
 import net.sf.openrocket.gui.StorageOptionChooser;
 import net.sf.openrocket.gui.configdialog.ComponentConfigDialog;
+import net.sf.openrocket.gui.customexpression.CustomExpressionDialog;
 import net.sf.openrocket.gui.dialogs.AboutDialog;
 import net.sf.openrocket.gui.dialogs.BugReportDialog;
 import net.sf.openrocket.gui.dialogs.ComponentAnalysisDialog;
@@ -637,7 +638,7 @@ public class BasicFrame extends JFrame {
                });
                menu.add(item);
 
-
+               //// Optimize
                item = new JMenuItem(trans.get("main.menu.analyze.optimization"), KeyEvent.VK_O);
                item.getAccessibleContext().setAccessibleDescription(trans.get("main.menu.analyze.optimization.desc"));
                item.addActionListener(new ActionListener() {
@@ -649,7 +650,17 @@ public class BasicFrame extends JFrame {
                });
                menu.add(item);
 
-
+               //// Custom expressions
+               item = new JMenuItem(trans.get("main.menu.analyze.customExpressions"), KeyEvent.VK_E);
+               item.getAccessibleContext().setAccessibleDescription(trans.get("main.menu.analyze.customExpressions.desc"));
+               item.addActionListener(new ActionListener() {
+                       @Override
+                       public void actionPerformed(ActionEvent e) {
+                               log.debug("Custom expressions selected");
+                               new CustomExpressionDialog(document, BasicFrame.this).setVisible(true);
+                       }
+               });
+               menu.add(item);
 
                ////  Debug
                // (shown if openrocket.debug.menu is defined)
index 0f9f6656cd1a8dda31e14675b4f6b7c751625205..b8a59ea790ca98ae65598523e2ba5f09f0196a38 100644 (file)
@@ -78,7 +78,7 @@ public class SimulationEditDialog extends JDialog {
        
        public static final int DEFAULT = -1;
        public static final int EDIT = 1;
-       public static final int PLOT = 3;
+       public static final int PLOT = 2;
        
 
        private final Window parentWindow;
@@ -139,8 +139,6 @@ public class SimulationEditDialog extends JDialog {
                tabbedPane.addTab(trans.get("simedtdlg.tab.Launchcond"), flightConditionsTab());
                //// Simulation options
                tabbedPane.addTab(trans.get("simedtdlg.tab.Simopt"), simulationOptionsTab());
-               //// Custom expressions tab
-               tabbedPane.addTab(trans.get("simedtdlg.tab.CustomExpressions"), customExpressionsTab());
                //// Plot data
                tabbedPane.addTab(trans.get("simedtdlg.tab.Plotdata"), plotTab());
                //// Export data
@@ -150,7 +148,7 @@ public class SimulationEditDialog extends JDialog {
                if (tab == EDIT) {
                        tabbedPane.setSelectedIndex(0);
                } else if (tab == PLOT) {
-                       tabbedPane.setSelectedIndex(3);
+                       tabbedPane.setSelectedIndex(2);
                } else {
                        FlightData data = s.getSimulatedData();
                        if (data == null || data.getBranchCount() == 0)
@@ -837,11 +835,6 @@ public class SimulationEditDialog extends JDialog {
                return new SimulationExportPanel(simulation);
        }
        
-       
-       private JPanel customExpressionsTab() {
-               return new CustomExpressionPanel(simulation);
-       }
-
 
        /**
         * Return a panel stating that there is no data available, and that the user
index 226f9527c68ce585d722bcf380c0a5505fdefd22..d493ca493b98a563c25b7b5875f2a0a97b245260 100644 (file)
@@ -42,7 +42,7 @@ public class Rocket extends RocketComponent {
         * List of component change listeners.
         */
        private List<EventListener> listenerList = new ArrayList<EventListener>();
-       
+               
        /**
         * When freezeList != null, events are not dispatched but stored in the list.
         * When the structure is thawed, a single combined event will be fired.
@@ -121,9 +121,6 @@ public class Rocket extends RocketComponent {
                fireComponentChangeEvent(ComponentChangeEvent.NONFUNCTIONAL_CHANGE);
        }
        
-       
-
-
        /**
         * Return the number of stages in this rocket.
         *
index b0139fbbd35f684bc20f2384b4c84b27bdc23e0b..d8f44ba05288b42f055ee96b49fac2fa416ced0b 100644 (file)
@@ -18,6 +18,7 @@ import net.sf.openrocket.rocketcomponent.MotorMount;
 import net.sf.openrocket.rocketcomponent.RecoveryDevice;
 import net.sf.openrocket.rocketcomponent.RocketComponent;
 import net.sf.openrocket.rocketcomponent.Stage;
+import net.sf.openrocket.simulation.customexpression.CustomExpression;
 import net.sf.openrocket.simulation.exception.MotorIgnitionException;
 import net.sf.openrocket.simulation.exception.SimulationException;
 import net.sf.openrocket.simulation.exception.SimulationLaunchException;
@@ -91,9 +92,9 @@ public class BasicEventSimulationEngine implements SimulationEngine {
                                
                                // Calculate values for custom expressions
                                FlightDataBranch data = status.getFlightData();
-                               ArrayList<CustomExpression> allExpressions = status.getSimulationConditions().getSimulation().getCustomExpressions();
+                               ArrayList<CustomExpression> allExpressions = status.getSimulationConditions().getSimulation().getDocument().getCustomExpressions();
                                for (CustomExpression expression : allExpressions ) {
-                                       data.setValue(expression.getType(), expression.evaluate(status));
+                                       data.setValue(expression.getType(), expression.evaluateDouble(status));
                                }
                                
                                // Check for NaN values in the simulation status
diff --git a/core/src/net/sf/openrocket/simulation/CustomExpression.java b/core/src/net/sf/openrocket/simulation/CustomExpression.java
deleted file mode 100644 (file)
index a383dd8..0000000
+++ /dev/null
@@ -1,341 +0,0 @@
-package net.sf.openrocket.simulation;
-
-import java.util.SortedMap;
-import java.util.TreeMap;
-
-import net.sf.openrocket.document.Simulation;
-import net.sf.openrocket.l10n.Translator;
-import net.sf.openrocket.logging.LogHelper;
-import net.sf.openrocket.startup.Application;
-import net.sf.openrocket.unit.FixedUnitGroup;
-import net.sf.openrocket.unit.UnitGroup;
-import net.sf.openrocket.util.ArrayList;
-import de.congrace.exp4j.Calculable;
-import de.congrace.exp4j.ExpressionBuilder;
-
-
-/**
- * Represents a single custom expression
- * @author Richard Graham
- *
- */
-public class CustomExpression implements Cloneable{
-       
-       private static final LogHelper log = Application.getLogger();
-       private static final Translator trans = Application.getTranslator();
-       
-       private String name, symbol, unit, expression;
-       private ExpressionBuilder builder;
-       private Simulation sim = null;
-       
-       // A map of available operator strings (keys) and description of function (value)
-       public static final SortedMap<String, String> AVAILABLE_OPERATORS = new TreeMap<String, String>() {{
-           put("+"             , trans.get("Operator.plus"));
-           put("-"                     , trans.get("Operator.minus"));
-           put("*"                     , trans.get("Operator.star"));
-           put("/"                     , trans.get("Operator.div"));
-           put("%"                     , trans.get("Operator.mod"));
-           put("^"                     , trans.get("Operator.pow"));
-           put("abs()"         , trans.get("Operator.abs"));
-           put("ceil()"        , trans.get("Operator.ceil"));
-           put("floor()"       , trans.get("Operator.floor"));
-           put("sqrt()"        , trans.get("Operator.sqrt"));
-           put("cbrt()"        , trans.get("Operator.cbrt"));
-           put("exp()"         , trans.get("Operator.exp"));
-           put("log()"         , trans.get("Operator.ln"));
-           put("sin()"         , trans.get("Operator.sin"));
-           put("cos()"         , trans.get("Operator.cos"));
-           put("tan()"         , trans.get("Operator.tan"));
-           put("asin()"        , trans.get("Operator.asin"));
-           put("acos()"        , trans.get("Operator.acos"));
-           put("atan()"        , trans.get("Operator.atan"));
-           put("sinh()"        , trans.get("Operator.hsin"));
-           put("cosh()"        , trans.get("Operator.hcos"));
-           put("tanh()"        , trans.get("Operator.htan"));
-       }};  
-       
-       public CustomExpression(){
-               setName("");
-               setSymbol("");
-               setUnit("");
-               setExpression("");
-       }
-       
-       public CustomExpression(Simulation sim){
-               this();
-               setSimulation(sim);
-       }
-       
-       public CustomExpression(Simulation sim, String name, String symbol, String unit, String expression) {
-               
-               setName(name);
-               setSymbol(symbol);
-               setUnit(unit);
-               setExpression(expression);
-               setSimulation(sim);
-       }
-       
-       /*
-        * Use this to update the simulation this is associated with
-        */
-       public void setSimulation(Simulation sim){
-               this.sim = sim;
-       }
-       
-       public Simulation getSimulation() {
-               return this.sim;
-       }
-       
-       /*
-        * Returns the flight data branch 0 for this simulation, or an empty branch
-        * if no simulated data exists
-        */
-       private FlightDataBranch getBranch() {
-               if (    sim == null || sim.getSimulatedData() == null || sim.getSimulatedData().getBranchCount() == 0){
-                       return new FlightDataBranch();
-               }
-               else {
-                       return sim.getSimulatedData().getBranch(0);
-               }
-       }
-       
-       
-       public void setName(String name){
-               this.name = name;
-       }
-       
-       public void setUnit(String unit){
-               this.unit = unit;
-       }
-       
-       public void setSymbol(String symbol){
-               this.symbol = symbol;
-       }
-       
-       public void setExpression(String expression){
-               this.expression = expression;
-               builder = new ExpressionBuilder(expression);
-       }
-       
-       // get a list of all the names of all the available variables
-       private ArrayList<String> getAllNames(){
-               ArrayList<String> names = new ArrayList<String>();
-               for (FlightDataType type : FlightDataType.ALL_TYPES)
-                       names.add(type.getName());
-               for (CustomExpression exp : sim.getCustomExpressions() ){
-                       if (exp != this)
-                               names.add(exp.getName());
-               }
-               return names;
-       }
-       
-       // get a list of all the symbols of the available variables ignoring this one
-       private ArrayList<String> getAllSymbols(){
-               ArrayList<String> symbols = new ArrayList<String>();
-               for (FlightDataType type : FlightDataType.ALL_TYPES)
-                       symbols.add(type.getSymbol());
-               for (CustomExpression exp : sim.getCustomExpressions() ){
-                       if (exp != this)
-                               symbols.add(exp.getSymbol());
-               }
-               return symbols;
-       }
-       
-       public boolean checkSymbol(){
-               if (symbol.trim().isEmpty())
-                       return false;
-               
-               // No bad characters
-               for (char c : "0123456789.,()[]{}<> ".toCharArray())
-                       if (symbol.indexOf(c) != -1 )
-                               return false;
-               
-               // No operators (ignoring brackets)
-               for (String s : CustomExpression.AVAILABLE_OPERATORS.keySet()){
-                       if (symbol.contains(s.replaceAll("\\(|\\)", "")))
-                               return false;
-               }
-               
-               // No already defined symbols
-               ArrayList<String> symbols = getAllSymbols().clone();
-               if (symbols.contains(symbol.trim())){
-                       int index = symbols.indexOf(symbol.trim());
-                       log.user("Symbol "+symbol+" already exists, found "+symbols.get(index));
-                       return false;
-               }
-               
-               return true;
-       }
-       
-       public boolean checkName(){
-               if (name.trim().isEmpty())
-                       return false;
-               
-               // No characters that could mess things up saving etc
-               for (char c : ",()[]{}<>".toCharArray())
-                       if (name.indexOf(c) != -1 )
-                               return false;
-               
-               ArrayList<String> names = getAllNames().clone();
-               if (names.contains(name.trim())){
-                       int index = names.indexOf(name.trim());
-                       log.user("Name "+name+" already exists, found "+names.get(index));
-                       return false;
-               }
-               
-               return true;
-       }
-       
-       // Currently no restrictions on unit
-       public boolean checkUnit(){
-               return true;
-       }
-       
-       public boolean checkAll(){
-               return checkUnit() && checkSymbol() && checkName();
-       }
-       
-       public String getName(){
-               return name;
-       }
-       
-       public String getSymbol(){
-               return symbol;
-       }
-       
-       public String getUnit(){
-               return unit;
-       }
-       
-       public String getExpressionString(){
-               return expression;
-       }
-       
-       
-       /*
-        * Check if the current expression is valid
-        */
-       public boolean checkExpression(){
-               
-               if (expression.trim().isEmpty()){
-                       return false;
-               }
-               
-               // Define the available variables as 0
-               for (FlightDataType type : getBranch().getTypes()){
-                       builder.withVariable(type.getSymbol(), 0.0);
-               }
-               
-               for (String symb : getAllSymbols()){
-                       builder.withVariable(symb, 0.0);
-               }
-               
-               // Try to build
-               try {
-                       builder.build();
-               } catch (Exception e) {
-                       log.user("Custom expression invalid : " + e.toString());
-                       return false;
-               }
-               
-               // Otherwise, all OK
-               return true;
-       }
-       
-       /*
-        * Evaluate the expression using the last variable values from the simulation status.
-        * Returns NaN on any error.
-        */
-       public Double evaluate(SimulationStatus status){
-               
-               for (FlightDataType type : status.getFlightData().getTypes()){
-                       builder.withVariable(type.getSymbol(), status.getFlightData().getLast(type) );
-               }
-               
-               Calculable calc;
-               try {
-                       calc = builder.build();
-                       return new Double(calc.calculate());
-               } catch (Exception e) {
-                       log.user("Could not calculate custom expression "+name);
-                       return Double.NaN;
-               }
-       }
-
-       /*
-        * Returns the new flight data type corresponding to this calculated data
-        */
-       public FlightDataType getType(){
-               
-               UnitGroup ug = new FixedUnitGroup(unit);
-               FlightDataType type =  FlightDataType.getType(name, symbol, ug);
-               
-               // If in a simulation, figure out priority from order in array so that customs expressions are always at the top
-               //if (sim != null && sim.getCustomExpressions().contains(this)){
-               //      int totalExpressions = sim.getCustomExpressions().size();
-               //      int p = -1*(totalExpressions-sim.getCustomExpressions().indexOf(this));
-               //      type.setPriority(p);
-               //}
-               
-               return type;
-       }
-       
-       /*
-        * Add this expression to the simulation if valid and not already added
-        */
-       public void addToSimulation(){
-               // Abort if exact expression already in
-               if ( !sim.getCustomExpressions().contains(this) && this.checkAll() )
-                       sim.addCustomExpression( this );        
-       }
-       
-       /*
-        * Removes this expression from the simulation, replacing it with a given new expression
-        */
-       public void overwrite(CustomExpression newExpression){
-               if (!sim.getCustomExpressions().contains(this)) 
-                       return;
-               else {
-                       int index = sim.getCustomExpressions().indexOf(this);
-                       sim.getCustomExpressions().set(index, newExpression);
-               }
-       }
-       
-       /*
-        * Add a copy to other simulations in this document if possible
-        * Will not overwrite existing expressions
-        */
-       public void copyToOtherSimulations(){                   
-               for (Simulation s : this.getSimulation().getDocument().getSimulations()){
-                               CustomExpression newExpression = (CustomExpression) this.clone();
-                               newExpression.setSimulation(s);
-                               newExpression.addToSimulation();
-               }
-       }
-       
-       @Override
-       public String toString(){
-               return "Custom expression : "+this.name.toString()+ " " + this.expression.toString();
-       }
-       
-       @Override
-       /*
-        * Clone method makes a deep copy of everything except the simulation.
-        * If you want to apply this to another simulation, set simulation manually after cloning.
-        * @see java.lang.Object#clone()
-        */
-       public Object clone() {
-             try {
-                 return super.clone();
-             }
-             catch( CloneNotSupportedException e )
-             {
-                 return new CustomExpression(  sim  , 
-                               new String(this.getName()), 
-                               new String(this.getSymbol()),
-                               new String(this.getUnit()),
-                               new String(this.getExpressionString()));
-             }
-         } 
-       
-}
index 8d12426101b08453468cff9d2281288712d84bfe..6a95c3045d53e3db01ddc8310054428346b27159 100644 (file)
@@ -5,6 +5,7 @@ import java.util.Locale;
 import java.util.Map;
 
 import net.sf.openrocket.l10n.Translator;
+import net.sf.openrocket.logging.LogHelper;
 import net.sf.openrocket.startup.Application;
 import net.sf.openrocket.unit.UnitGroup;
 
@@ -23,159 +24,160 @@ import net.sf.openrocket.unit.UnitGroup;
  */
 public class FlightDataType implements Comparable<FlightDataType> {
        private static final Translator trans = Application.getTranslator();
+       private static final LogHelper log = Application.getLogger();
        
        /** Priority of custom-created variables */
        private static final int DEFAULT_PRIORITY = 999;
        
        /** List of existing types.  MUST BE DEFINED BEFORE ANY TYPES!! */
+       /** NOTE: The String key here is now the symbol */
        private static final Map<String, FlightDataType> EXISTING_TYPES = new HashMap<String, FlightDataType>();
        
        
-       
        //// Time
-       public static final FlightDataType TYPE_TIME = newType("TYPE_TIME", "t", UnitGroup.UNITS_FLIGHT_TIME, 1);
+       public static final FlightDataType TYPE_TIME = newType(trans.get("FlightDataType.TYPE_TIME"), "t", UnitGroup.UNITS_FLIGHT_TIME, 1);
        
        //// Vertical position and motion
        //// Altitude
-       public static final FlightDataType TYPE_ALTITUDE = newType("TYPE_ALTITUDE", "h", UnitGroup.UNITS_DISTANCE, 10);
+       public static final FlightDataType TYPE_ALTITUDE = newType(trans.get("FlightDataType.TYPE_ALTITUDE"), "h", UnitGroup.UNITS_DISTANCE, 10);
        //// Vertical velocity
-       public static final FlightDataType TYPE_VELOCITY_Z = newType("TYPE_VELOCITY_Z", "Vz", UnitGroup.UNITS_VELOCITY, 11);
+       public static final FlightDataType TYPE_VELOCITY_Z = newType(trans.get("FlightDataType.TYPE_VELOCITY_Z"), "Vz", UnitGroup.UNITS_VELOCITY, 11);
        //// Vertical acceleration
-       public static final FlightDataType TYPE_ACCELERATION_Z = newType("TYPE_ACCELERATION_Z", "Az", UnitGroup.UNITS_ACCELERATION, 12);
+       public static final FlightDataType TYPE_ACCELERATION_Z = newType(trans.get("FlightDataType.TYPE_ACCELERATION_Z"), "Az", UnitGroup.UNITS_ACCELERATION, 12);
        
        
        //// Total motion
        //// Total velocity
-       public static final FlightDataType TYPE_VELOCITY_TOTAL = newType("TYPE_VELOCITY_TOTAL", "Vt", UnitGroup.UNITS_VELOCITY, 20);
+       public static final FlightDataType TYPE_VELOCITY_TOTAL = newType(trans.get("FlightDataType.TYPE_VELOCITY_TOTAL"), "Vt", UnitGroup.UNITS_VELOCITY, 20);
        //// Total acceleration
-       public static final FlightDataType TYPE_ACCELERATION_TOTAL = newType("TYPE_ACCELERATION_TOTAL", "At", UnitGroup.UNITS_ACCELERATION, 21);
+       public static final FlightDataType TYPE_ACCELERATION_TOTAL = newType(trans.get("FlightDataType.TYPE_ACCELERATION_TOTAL"), "At", UnitGroup.UNITS_ACCELERATION, 21);
        
        
        //// Lateral position and motion
        //// Position upwind
-       public static final FlightDataType TYPE_POSITION_X = newType("TYPE_POSITION_X", "Px", UnitGroup.UNITS_DISTANCE, 30);
+       public static final FlightDataType TYPE_POSITION_X = newType(trans.get("FlightDataType.TYPE_POSITION_X"), "Px", UnitGroup.UNITS_DISTANCE, 30);
        //// Position parallel to wind
-       public static final FlightDataType TYPE_POSITION_Y = newType("TYPE_POSITION_Y", "Py", UnitGroup.UNITS_DISTANCE, 31);
+       public static final FlightDataType TYPE_POSITION_Y = newType(trans.get("FlightDataType.TYPE_POSITION_Y"), "Py", UnitGroup.UNITS_DISTANCE, 31);
        //// Lateral distance
-       public static final FlightDataType TYPE_POSITION_XY = newType("TYPE_POSITION_XY", "Pl", UnitGroup.UNITS_DISTANCE, 32);
+       public static final FlightDataType TYPE_POSITION_XY = newType(trans.get("FlightDataType.TYPE_POSITION_XY"), "Pl", UnitGroup.UNITS_DISTANCE, 32);
        //// Lateral direction
-       public static final FlightDataType TYPE_POSITION_DIRECTION = newType("TYPE_POSITION_DIRECTION", "\u03b8l", UnitGroup.UNITS_ANGLE, 33);
+       public static final FlightDataType TYPE_POSITION_DIRECTION = newType(trans.get("FlightDataType.TYPE_POSITION_DIRECTION"), "\u03b8l", UnitGroup.UNITS_ANGLE, 33);
        //// Lateral velocity
-       public static final FlightDataType TYPE_VELOCITY_XY = newType("TYPE_VELOCITY_XY", "Vl", UnitGroup.UNITS_VELOCITY, 34);
+       public static final FlightDataType TYPE_VELOCITY_XY = newType(trans.get("FlightDataType.TYPE_VELOCITY_XY"), "Vl", UnitGroup.UNITS_VELOCITY, 34);
        //// Lateral acceleration
-       public static final FlightDataType TYPE_ACCELERATION_XY = newType("TYPE_ACCELERATION_XY", "Al", UnitGroup.UNITS_ACCELERATION, 35);
+       public static final FlightDataType TYPE_ACCELERATION_XY = newType(trans.get("FlightDataType.TYPE_ACCELERATION_XY"), "Al", UnitGroup.UNITS_ACCELERATION, 35);
        //// Latitude
-       public static final FlightDataType TYPE_LATITUDE = newType("TYPE_LATITUDE", "\u03c6", UnitGroup.UNITS_ANGLE, 36);
+       public static final FlightDataType TYPE_LATITUDE = newType(trans.get("FlightDataType.TYPE_LATITUDE"), "\u03c6", UnitGroup.UNITS_ANGLE, 36);
        //// Longitude
-       public static final FlightDataType TYPE_LONGITUDE = newType("TYPE_LONGITUDE", "\u03bb", UnitGroup.UNITS_ANGLE, 37);
+       public static final FlightDataType TYPE_LONGITUDE = newType(trans.get("FlightDataType.TYPE_LONGITUDE"), "\u03bb", UnitGroup.UNITS_ANGLE, 37);
        
        //// Angular motion
        //// Angle of attack
-       public static final FlightDataType TYPE_AOA = newType("TYPE_AOA", "\u03b1", UnitGroup.UNITS_ANGLE, 40);
+       public static final FlightDataType TYPE_AOA = newType(trans.get("FlightDataType.TYPE_AOA"), "\u03b1", UnitGroup.UNITS_ANGLE, 40);
        //// Roll rate
-       public static final FlightDataType TYPE_ROLL_RATE = newType("TYPE_ROLL_RATE", "d\u03a6", UnitGroup.UNITS_ROLL, 41);
+       public static final FlightDataType TYPE_ROLL_RATE = newType(trans.get("FlightDataType.TYPE_ROLL_RATE"), "d\u03a6", UnitGroup.UNITS_ROLL, 41);
        //// Pitch rate
-       public static final FlightDataType TYPE_PITCH_RATE = newType("TYPE_PITCH_RATE", "d\u03b8", UnitGroup.UNITS_ROLL, 42);
+       public static final FlightDataType TYPE_PITCH_RATE = newType(trans.get("FlightDataType.TYPE_PITCH_RATE"), "d\u03b8", UnitGroup.UNITS_ROLL, 42);
        //// Yaw rate
-       public static final FlightDataType TYPE_YAW_RATE = newType("TYPE_YAW_RATE", "d\u03a8", UnitGroup.UNITS_ROLL, 43);
+       public static final FlightDataType TYPE_YAW_RATE = newType(trans.get("FlightDataType.TYPE_YAW_RATE"), "d\u03a8", UnitGroup.UNITS_ROLL, 43);
        
        
        //// Stability information
        //// Mass
-       public static final FlightDataType TYPE_MASS = newType("TYPE_MASS", "m", UnitGroup.UNITS_MASS, 50);
+       public static final FlightDataType TYPE_MASS = newType(trans.get("FlightDataType.TYPE_MASS"), "m", UnitGroup.UNITS_MASS, 50);
        //// Longitudinal moment of inertia
-       public static final FlightDataType TYPE_LONGITUDINAL_INERTIA = newType("TYPE_LONGITUDINAL_INERTIA", "Il", UnitGroup.UNITS_INERTIA, 51);
+       public static final FlightDataType TYPE_LONGITUDINAL_INERTIA = newType(trans.get("FlightDataType.TYPE_LONGITUDINAL_INERTIA"), "Il", UnitGroup.UNITS_INERTIA, 51);
        //// Rotational moment of inertia
-       public static final FlightDataType TYPE_ROTATIONAL_INERTIA = newType("TYPE_ROTATIONAL_INERTIA", "Ir", UnitGroup.UNITS_INERTIA, 52);
+       public static final FlightDataType TYPE_ROTATIONAL_INERTIA = newType(trans.get("FlightDataType.TYPE_ROTATIONAL_INERTIA"), "Ir", UnitGroup.UNITS_INERTIA, 52);
        //// CP location
-       public static final FlightDataType TYPE_CP_LOCATION = newType("TYPE_CP_LOCATION", "Cp", UnitGroup.UNITS_LENGTH, 53);
+       public static final FlightDataType TYPE_CP_LOCATION = newType(trans.get("FlightDataType.TYPE_CP_LOCATION"), "Cp", UnitGroup.UNITS_LENGTH, 53);
        //// CG location
-       public static final FlightDataType TYPE_CG_LOCATION = newType("TYPE_CG_LOCATION", "Cg", UnitGroup.UNITS_LENGTH, 54);
+       public static final FlightDataType TYPE_CG_LOCATION = newType(trans.get("FlightDataType.TYPE_CG_LOCATION"), "Cg", UnitGroup.UNITS_LENGTH, 54);
        //// Stability margin calibers
-       public static final FlightDataType TYPE_STABILITY = newType("TYPE_STABILITY", "S", UnitGroup.UNITS_COEFFICIENT, 55);
+       public static final FlightDataType TYPE_STABILITY = newType(trans.get("FlightDataType.TYPE_STABILITY"), "S", UnitGroup.UNITS_COEFFICIENT, 55);
        
        
        //// Characteristic numbers
        //// Mach number
-       public static final FlightDataType TYPE_MACH_NUMBER = newType("TYPE_MACH_NUMBER", "M", UnitGroup.UNITS_COEFFICIENT, 60);
+       public static final FlightDataType TYPE_MACH_NUMBER = newType(trans.get("FlightDataType.TYPE_MACH_NUMBER"), "M", UnitGroup.UNITS_COEFFICIENT, 60);
        //// Reynolds number
-       public static final FlightDataType TYPE_REYNOLDS_NUMBER = newType("TYPE_REYNOLDS_NUMBER", "R", UnitGroup.UNITS_COEFFICIENT, 61);
+       public static final FlightDataType TYPE_REYNOLDS_NUMBER = newType(trans.get("FlightDataType.TYPE_REYNOLDS_NUMBER"), "R", UnitGroup.UNITS_COEFFICIENT, 61);
        
        
        //// Thrust and drag
        //// Thrust
-       public static final FlightDataType TYPE_THRUST_FORCE = newType("TYPE_THRUST_FORCE", "Ft", UnitGroup.UNITS_FORCE, 70);
+       public static final FlightDataType TYPE_THRUST_FORCE = newType(trans.get("FlightDataType.TYPE_THRUST_FORCE"), "Ft", UnitGroup.UNITS_FORCE, 70);
        //// Drag force
-       public static final FlightDataType TYPE_DRAG_FORCE = newType("TYPE_DRAG_FORCE", "Fd", UnitGroup.UNITS_FORCE, 71);
+       public static final FlightDataType TYPE_DRAG_FORCE = newType(trans.get("FlightDataType.TYPE_DRAG_FORCE"), "Fd", UnitGroup.UNITS_FORCE, 71);
        //// Drag coefficient
-       public static final FlightDataType TYPE_DRAG_COEFF = newType("TYPE_DRAG_COEFF", "Cd", UnitGroup.UNITS_COEFFICIENT, 72);
+       public static final FlightDataType TYPE_DRAG_COEFF = newType(trans.get("FlightDataType.TYPE_DRAG_COEFF"), "Cd", UnitGroup.UNITS_COEFFICIENT, 72);
        //// Axial drag coefficient
-       public static final FlightDataType TYPE_AXIAL_DRAG_COEFF = newType("TYPE_AXIAL_DRAG_COEFF", "Cda", UnitGroup.UNITS_COEFFICIENT, 73);
+       public static final FlightDataType TYPE_AXIAL_DRAG_COEFF = newType(trans.get("FlightDataType.TYPE_AXIAL_DRAG_COEFF"), "Cda", UnitGroup.UNITS_COEFFICIENT, 73);
        
        
        ////  Component drag coefficients
        //// Friction drag coefficient
-       public static final FlightDataType TYPE_FRICTION_DRAG_COEFF = newType("TYPE_FRICTION_DRAG_COEFF", "Cdf", UnitGroup.UNITS_COEFFICIENT, 80);
+       public static final FlightDataType TYPE_FRICTION_DRAG_COEFF = newType(trans.get("FlightDataType.TYPE_FRICTION_DRAG_COEFF"), "Cdf", UnitGroup.UNITS_COEFFICIENT, 80);
        //// Pressure drag coefficient
-       public static final FlightDataType TYPE_PRESSURE_DRAG_COEFF = newType("TYPE_PRESSURE_DRAG_COEFF", "Cdp", UnitGroup.UNITS_COEFFICIENT, 81);
+       public static final FlightDataType TYPE_PRESSURE_DRAG_COEFF = newType(trans.get("FlightDataType.TYPE_PRESSURE_DRAG_COEFF"), "Cdp", UnitGroup.UNITS_COEFFICIENT, 81);
        //// Base drag coefficient
-       public static final FlightDataType TYPE_BASE_DRAG_COEFF = newType("TYPE_BASE_DRAG_COEFF", "Cdb", UnitGroup.UNITS_COEFFICIENT, 82);
+       public static final FlightDataType TYPE_BASE_DRAG_COEFF = newType(trans.get("FlightDataType.TYPE_BASE_DRAG_COEFF"), "Cdb", UnitGroup.UNITS_COEFFICIENT, 82);
        
        
        ////  Other coefficients
        //// Normal force coefficient
-       public static final FlightDataType TYPE_NORMAL_FORCE_COEFF = newType("TYPE_NORMAL_FORCE_COEFF", "Cn", UnitGroup.UNITS_COEFFICIENT, 90);
+       public static final FlightDataType TYPE_NORMAL_FORCE_COEFF = newType(trans.get("FlightDataType.TYPE_NORMAL_FORCE_COEFF"), "Cn", UnitGroup.UNITS_COEFFICIENT, 90);
        //// Pitch moment coefficient
-       public static final FlightDataType TYPE_PITCH_MOMENT_COEFF = newType("TYPE_PITCH_MOMENT_COEFF", "C\u03b8", UnitGroup.UNITS_COEFFICIENT, 91);
+       public static final FlightDataType TYPE_PITCH_MOMENT_COEFF = newType(trans.get("FlightDataType.TYPE_PITCH_MOMENT_COEFF"), "C\u03b8", UnitGroup.UNITS_COEFFICIENT, 91);
        //// Yaw moment coefficient
-       public static final FlightDataType TYPE_YAW_MOMENT_COEFF = newType("TYPE_YAW_MOMENT_COEFF", "C\u03c4\u03a8", UnitGroup.UNITS_COEFFICIENT, 92);
+       public static final FlightDataType TYPE_YAW_MOMENT_COEFF = newType(trans.get("FlightDataType.TYPE_YAW_MOMENT_COEFF"), "C\u03c4\u03a8", UnitGroup.UNITS_COEFFICIENT, 92);
        //// Side force coefficient
-       public static final FlightDataType TYPE_SIDE_FORCE_COEFF = newType("TYPE_SIDE_FORCE_COEFF", "C\u03c4s", UnitGroup.UNITS_COEFFICIENT, 93);
+       public static final FlightDataType TYPE_SIDE_FORCE_COEFF = newType(trans.get("FlightDataType.TYPE_SIDE_FORCE_COEFF"), "C\u03c4s", UnitGroup.UNITS_COEFFICIENT, 93);
        //// Roll moment coefficient
-       public static final FlightDataType TYPE_ROLL_MOMENT_COEFF = newType("TYPE_ROLL_MOMENT_COEFF", "C\u03c4\u03a6", UnitGroup.UNITS_COEFFICIENT, 94);
+       public static final FlightDataType TYPE_ROLL_MOMENT_COEFF = newType(trans.get("FlightDataType.TYPE_ROLL_MOMENT_COEFF"), "C\u03c4\u03a6", UnitGroup.UNITS_COEFFICIENT, 94);
        //// Roll forcing coefficient
-       public static final FlightDataType TYPE_ROLL_FORCING_COEFF = newType("TYPE_ROLL_FORCING_COEFF", "Cf\u03a6", UnitGroup.UNITS_COEFFICIENT, 95);
+       public static final FlightDataType TYPE_ROLL_FORCING_COEFF = newType(trans.get("FlightDataType.TYPE_ROLL_FORCING_COEFF"), "Cf\u03a6", UnitGroup.UNITS_COEFFICIENT, 95);
        //// Roll damping coefficient
-       public static final FlightDataType TYPE_ROLL_DAMPING_COEFF = newType("TYPE_ROLL_DAMPING_COEFF", "C\u03b6\u03a6", UnitGroup.UNITS_COEFFICIENT, 96);
+       public static final FlightDataType TYPE_ROLL_DAMPING_COEFF = newType(trans.get("FlightDataType.TYPE_ROLL_DAMPING_COEFF"), "C\u03b6\u03a6", UnitGroup.UNITS_COEFFICIENT, 96);
        
        //// Pitch damping coefficient
-       public static final FlightDataType TYPE_PITCH_DAMPING_MOMENT_COEFF = newType("TYPE_PITCH_DAMPING_MOMENT_COEFF", "C\u03b6\u03b8", UnitGroup.UNITS_COEFFICIENT, 97);
+       public static final FlightDataType TYPE_PITCH_DAMPING_MOMENT_COEFF = newType(trans.get("FlightDataType.TYPE_PITCH_DAMPING_MOMENT_COEFF"), "C\u03b6\u03b8", UnitGroup.UNITS_COEFFICIENT, 97);
        //// Yaw damping coefficient
-       public static final FlightDataType TYPE_YAW_DAMPING_MOMENT_COEFF = newType("TYPE_YAW_DAMPING_MOMENT_COEFF", "C\u03b6\u03a8", UnitGroup.UNITS_COEFFICIENT, 98);
+       public static final FlightDataType TYPE_YAW_DAMPING_MOMENT_COEFF = newType(trans.get("FlightDataType.TYPE_YAW_DAMPING_MOMENT_COEFF"), "C\u03b6\u03a8", UnitGroup.UNITS_COEFFICIENT, 98);
        
        //// Coriolis acceleration
-       public static final FlightDataType TYPE_CORIOLIS_ACCELERATION = newType("TYPE_CORIOLIS_ACCELERATION", "Ac", UnitGroup.UNITS_ACCELERATION, 99);
+       public static final FlightDataType TYPE_CORIOLIS_ACCELERATION = newType(trans.get("FlightDataType.TYPE_CORIOLIS_ACCELERATION"), "Ac", UnitGroup.UNITS_ACCELERATION, 99);
        
        
        ////  Reference length + area
        //// Reference length
-       public static final FlightDataType TYPE_REFERENCE_LENGTH = newType("TYPE_REFERENCE_LENGTH", "Lr", UnitGroup.UNITS_LENGTH, 100);
+       public static final FlightDataType TYPE_REFERENCE_LENGTH = newType(trans.get("FlightDataType.TYPE_REFERENCE_LENGTH"), "Lr", UnitGroup.UNITS_LENGTH, 100);
        //// Reference area
-       public static final FlightDataType TYPE_REFERENCE_AREA = newType("TYPE_REFERENCE_AREA", "Ar", UnitGroup.UNITS_AREA, 101);
+       public static final FlightDataType TYPE_REFERENCE_AREA = newType(trans.get("FlightDataType.TYPE_REFERENCE_AREA"), "Ar", UnitGroup.UNITS_AREA, 101);
        
        
        ////  Orientation
        //// Vertical orientation (zenith)
-       public static final FlightDataType TYPE_ORIENTATION_THETA = newType("TYPE_ORIENTATION_THETA", "\u0398", UnitGroup.UNITS_ANGLE, 106);
+       public static final FlightDataType TYPE_ORIENTATION_THETA = newType(trans.get("FlightDataType.TYPE_ORIENTATION_THETA"), "\u0398", UnitGroup.UNITS_ANGLE, 106);
        //// Lateral orientation (azimuth)
-       public static final FlightDataType TYPE_ORIENTATION_PHI = newType("TYPE_ORIENTATION_PHI", "\u03a6", UnitGroup.UNITS_ANGLE, 107);
+       public static final FlightDataType TYPE_ORIENTATION_PHI = newType(trans.get("FlightDataType.TYPE_ORIENTATION_PHI"), "\u03a6", UnitGroup.UNITS_ANGLE, 107);
        
        
        ////  Atmospheric conditions
        //// Wind velocity
-       public static final FlightDataType TYPE_WIND_VELOCITY = newType("TYPE_WIND_VELOCITY", "Vw", UnitGroup.UNITS_VELOCITY, 110);
+       public static final FlightDataType TYPE_WIND_VELOCITY = newType(trans.get("FlightDataType.TYPE_WIND_VELOCITY"), "Vw", UnitGroup.UNITS_VELOCITY, 110);
        //// Air temperature
-       public static final FlightDataType TYPE_AIR_TEMPERATURE = newType("TYPE_AIR_TEMPERATURE", "T", UnitGroup.UNITS_TEMPERATURE, 111);
+       public static final FlightDataType TYPE_AIR_TEMPERATURE = newType(trans.get("FlightDataType.TYPE_AIR_TEMPERATURE"), "T", UnitGroup.UNITS_TEMPERATURE, 111);
        //// Air pressure
-       public static final FlightDataType TYPE_AIR_PRESSURE = newType("TYPE_AIR_PRESSURE", "p", UnitGroup.UNITS_PRESSURE, 112);
+       public static final FlightDataType TYPE_AIR_PRESSURE = newType(trans.get("FlightDataType.TYPE_AIR_PRESSURE"), "p", UnitGroup.UNITS_PRESSURE, 112);
        //// Speed of sound
-       public static final FlightDataType TYPE_SPEED_OF_SOUND = newType("TYPE_SPEED_OF_SOUND", "Vs", UnitGroup.UNITS_VELOCITY, 113);
+       public static final FlightDataType TYPE_SPEED_OF_SOUND = newType(trans.get("FlightDataType.TYPE_SPEED_OF_SOUND"), "Vs", UnitGroup.UNITS_VELOCITY, 113);
        
        ////  Simulation information
        //// Simulation time step
-       public static final FlightDataType TYPE_TIME_STEP = newType("TYPE_TIME_STEP", "dt", UnitGroup.UNITS_TIME_STEP, 200);
+       public static final FlightDataType TYPE_TIME_STEP = newType(trans.get("FlightDataType.TYPE_TIME_STEP"), "dt", UnitGroup.UNITS_TIME_STEP, 200);
        //// Computation time
-       public static final FlightDataType TYPE_COMPUTATION_TIME = newType("TYPE_COMPUTATION_TIME", "tc", UnitGroup.UNITS_SHORT_TIME, 201);     
+       public static final FlightDataType TYPE_COMPUTATION_TIME = newType(trans.get("FlightDataType.TYPE_COMPUTATION_TIME"), "tc", UnitGroup.UNITS_SHORT_TIME, 201);   
        
        // An array of all the built in types
        public static final FlightDataType[] ALL_TYPES = { 
@@ -235,46 +237,90 @@ public class FlightDataType implements Comparable<FlightDataType> {
                };
        
        /**
-        * Return a {@link FlightDataType} based on a string description.  This returns known data types
-        * if possible, or a new type otherwise.
+        * Return a {@link FlightDataType} with a given string description, symbol and unitgroup.
+        * This returns an existing data type if the symbol matches that of an existing type. 
+        * 
+        * If the symbol matches but the unit and description information differ, then the old stored datatype
+        * is erased and the updated version based on the given parametes is returned.
+        * The only exception is if the description or unitgroup are undefined (null or empty string). In this case 
+        * we just get these parameters from the existing type when making the new one.
         * 
         * @param s             the string description of the type.
         * @param u             the unit group the new type should belong to if a new group is created.
         * @return              a data type.
         */
+       @SuppressWarnings("null")
        public static synchronized FlightDataType getType(String s, String symbol, UnitGroup u) {
-               // modified to include the unit
-               FlightDataType type = EXISTING_TYPES.get(s.toLowerCase(Locale.ENGLISH));
+
+               // if symbol is null : try finding by name
+               // if unit is null : don't do anything to the unit if found, just return datatype if found and generate an error and an empty unit otherwise
+               int oldPriority = DEFAULT_PRIORITY;
                
-               // added this for backward compatibility. Will update type if symbol undefined
-               //if (type != null && type.getSymbol() != symbol){
-               //      EXISTING_TYPES.remove(type);
-               //      type = null;
-               //}
+               //FlightDataType type = findFromSymbol(symbol);
+               FlightDataType type = EXISTING_TYPES.get(symbol);
                
                if (type != null) {
-                       return type;
+                       // found it from symbol
+                       
+                       // if name was not give (empty string), can use the one we found name
+                       if ( s.equals("") || s == null ){
+                               s = type.getName();
+                       }
+                       if ( u == null ){
+                               u = type.getUnitGroup();
+                       }
+                       
+                       // if something has changed, then we need to remove the old one
+                       // otherwise, just return what we found
+                       if ( !u.equals(type.getUnitGroup()) ||
+                                !s.equals(type.getName())
+                               )
+                          {
+                               oldPriority = type.priority;
+                               
+                               EXISTING_TYPES.remove(type);
+                               log.info("Something changed with the type "+type.getName()+", removed old version.");
+                       }
+                       else{
+                               return type;
+                       }
                }
-               type = newType("UserDefined." + s, s, symbol, u, DEFAULT_PRIORITY);
-               return type;
+               
+               if (u == null){
+                       u = UnitGroup.UNITS_NONE;
+                       log.error("Made a new flightdatatype, but did not know what units to use.");
+               }
+               
+               // make a new one
+               type = newType(s, symbol, u, oldPriority);
+               return type;    
        }
        
-       /**
-        * Used while initializing the class.
+       /*
+        * Get the flightdatatype from existing types based on the symbol.
         */
-
-       private static FlightDataType newType( String key , String symbol, UnitGroup u, int priority ) {
-               String name = trans.get("FlightDataType." + key );
-               return newType( key, name, symbol, u, priority );
+       /*
+       private static FlightDataType findFromSymbol(String symbol){
+               for (FlightDataType t : EXISTING_TYPES.values()){
+                       if (t.getSymbol().equals(symbol)){
+                               return t;
+                       }
+               }
+               return null;
        }
+       */
        
-       private static synchronized FlightDataType newType(String key, String s, String symbol, UnitGroup u, int priority) {
-               FlightDataType type = new FlightDataType(key, s, symbol, u, priority);
-               EXISTING_TYPES.put(s.toLowerCase(Locale.ENGLISH), type);
+       /**
+        * Used while initializing the class.
+        */
+       private static synchronized FlightDataType newType(String s, String symbol, UnitGroup u, int priority) {
+               FlightDataType type = new FlightDataType(s, symbol, u, priority);
+               //EXISTING_TYPES.put(s.toLowerCase(Locale.ENGLISH), type);
+               EXISTING_TYPES.put(symbol, type);
                return type;
        }
        
-       private final String key;
+       
        private final String name;
        private final String symbol;
        private final UnitGroup units;
@@ -282,8 +328,7 @@ public class FlightDataType implements Comparable<FlightDataType> {
        private final int hashCode;
        
        
-       private FlightDataType(String key, String typeName, String symbol, UnitGroup units, int priority) {
-               this.key = key;
+       private FlightDataType(String typeName, String symbol, UnitGroup units, int priority) {
                if (typeName == null)
                        throw new IllegalArgumentException("typeName is null");
                if (units == null)
@@ -292,7 +337,7 @@ public class FlightDataType implements Comparable<FlightDataType> {
                this.symbol = symbol;
                this.units = units;
                this.priority = priority;
-               this.hashCode = this.key.hashCode();
+               this.hashCode = this.name.toLowerCase(Locale.ENGLISH).hashCode();
        }
        
        /*
@@ -301,10 +346,6 @@ public class FlightDataType implements Comparable<FlightDataType> {
        }
        */
        
-       public String getKey() {
-               return key;
-       }
-       
        public String getName() {
                return name;
        }
@@ -326,7 +367,7 @@ public class FlightDataType implements Comparable<FlightDataType> {
        public boolean equals(Object other) {
                if (!(other instanceof FlightDataType))
                        return false;
-               return this.hashCode == other.hashCode();
+               return this.name.equalsIgnoreCase(((FlightDataType) other).name);
        }
        
        @Override
diff --git a/core/src/net/sf/openrocket/simulation/customexpression/CustomExpression.java b/core/src/net/sf/openrocket/simulation/customexpression/CustomExpression.java
new file mode 100644 (file)
index 0000000..2ac41ff
--- /dev/null
@@ -0,0 +1,496 @@
+package net.sf.openrocket.simulation.customexpression;
+
+import java.util.List;
+import java.util.regex.*;
+
+import net.sf.openrocket.document.OpenRocketDocument;
+import net.sf.openrocket.logging.LogHelper;
+import net.sf.openrocket.simulation.FlightDataType;
+import net.sf.openrocket.simulation.SimulationStatus;
+import net.sf.openrocket.startup.Application;
+import net.sf.openrocket.unit.FixedUnitGroup;
+import net.sf.openrocket.unit.UnitGroup;
+import net.sf.openrocket.util.ArrayList;
+import de.congrace.exp4j.Calculable;
+import de.congrace.exp4j.ExpressionBuilder;
+import de.congrace.exp4j.UnknownFunctionException;
+import de.congrace.exp4j.UnparsableExpressionException;
+import de.congrace.exp4j.Variable;
+
+/**
+ * Represents a single custom expression
+ * @author Richard Graham
+ *
+ */
+public class CustomExpression implements Cloneable{
+       
+       private static final LogHelper log = Application.getLogger();
+       
+       private OpenRocketDocument doc;
+       private String name, symbol, unit;
+
+       protected String expression;
+       private ExpressionBuilder builder;
+       private List<CustomExpression> subExpressions = new ArrayList<CustomExpression>();
+       
+       public CustomExpression(OpenRocketDocument doc){
+               setName("");
+               setSymbol("");
+               setUnit("");
+               setExpression("");
+               this.doc = doc;
+       }
+       
+       public CustomExpression(OpenRocketDocument doc, 
+                                                       String name, 
+                                                       String symbol, 
+                                                       String unit, 
+                                                       String expression) {
+               this.doc = doc;
+               
+               setName(name);
+               setSymbol(symbol);
+               setUnit(unit);
+               setExpression(expression);
+       }
+       
+       /*
+        * Sets the long name of this expression, e.g. 'Kinetic energy'  
+        */
+       public void setName(String name){
+               this.name = name;
+       }
+       
+       /*
+        * Sets the string for the units of the result of this expression.
+        */
+       public void setUnit(String unit){
+               this.unit = unit;
+       }
+       
+       /*
+        * Sets the symbol string. This is the short, locale independent symbol for this whole expression
+        */
+       public void setSymbol(String symbol){
+               this.symbol = symbol;
+       }
+       
+       /*
+        * Sets the actual expression string for this expression
+        */
+       public void setExpression(String expression){
+               
+               // This is the expression as supplied
+               this.expression = expression;
+               
+               // Replace any indexed variables
+               expression = subTimeIndexes(expression);
+               expression = subTimeRanges(expression);
+               
+               builder = new ExpressionBuilder(expression);
+               for (String n : getAllSymbols()){
+                       builder.withVariable(new Variable(n));
+               }
+               for (CustomExpression exp : this.subExpressions){
+                       builder.withVariable(new Variable(exp.hash()));
+               }
+       
+               builder.withCustomFunctions(Functions.getInstance().getAllFunction());
+               log.info("Built expression "+expression);
+       }
+       
+       /*
+        * Replaces expressions of the form:
+        *   a[x:y]  with a hash and creates an associated RangeExpression from x to y
+        */
+       private String subTimeRanges(String str){
+               
+               Pattern p = Pattern.compile(variableRegex()+"\\[[^\\]]*:.*?\\]");
+               Matcher m = p.matcher(str);
+               
+               // for each match, make a new custom expression (in subExpressions) with a hashed name
+               // and replace the expression and variable in the original expression string with [hash].
+               while (m.find()){
+                       String match = m.group();
+                       
+                       int start = match.indexOf("[");
+                       int end = match.indexOf("]");
+                       int colon = match.indexOf(":");
+                       
+                       String startTime = match.substring(start+1, colon);
+                       String endTime = match.substring(colon+1, end);
+                       String variableType = match.substring(0, start);
+                       
+                       RangeExpression exp = new RangeExpression(doc, startTime, endTime, variableType);
+                       subExpressions.add( exp );
+                       str = str.replace(match, exp.hash());
+               }
+               return str;
+       }
+       
+       /*
+        * Replaces expressions of the form
+        *   a[x]    with a hash and creates an associated IndexExpression with x
+        */
+       private String subTimeIndexes(String str){
+               
+               // find any matches of the time-indexed variable notation, e.g. m[1.2] for mass at 1.2 sec
+               Pattern p = Pattern.compile(variableRegex()+"\\[[^:]*?\\]");
+               Matcher m = p.matcher(str);
+                               
+               // for each match, make a new custom expression (in subExpressions) with a hashed name
+               // and replace the expression and variable in the original expression string with [hash].
+               while (m.find()){
+                       String match = m.group();
+                       // just the index part (in the square brackets) :
+                       String indexText = match.substring(match.indexOf("[")+1, match.length()-1);
+                       // just the flight data type
+                       String typeText = match.substring(0, match.indexOf("[")); 
+                                               
+                       // Do the replacement and add a corresponding new IndexExpression to the list
+                       IndexExpression exp = new IndexExpression(doc, indexText, typeText);
+                       subExpressions.add( exp );
+                       str = str.replace(match, exp.hash());
+               }
+               return str;
+       }
+       
+       /*
+        * Returns a string of the form (t|a| ... ) with all variable symbols available
+        * This is useful for regex evaluation
+        */
+       protected String variableRegex(){
+               String regex = "(";
+               for (String s : getAllSymbols()){
+                       regex = regex + s + "|";
+               }
+               regex = regex.substring(0, regex.length()-1) + ")";
+               return regex;
+       }
+       
+       // get a list of all the names of all the available variables
+       protected ArrayList<String> getAllNames(){
+               ArrayList<String> names = new ArrayList<String>();
+               for (FlightDataType type : FlightDataType.ALL_TYPES)
+                       names.add(type.getName());
+
+               if (doc != null){
+                       ArrayList<CustomExpression> expressions = doc.getCustomExpressions();
+                       for (CustomExpression exp : expressions ){
+                               if (exp != this)
+                                       names.add(exp.getName());
+                       }
+               }
+               return names;
+       }
+       
+       // get a list of all the symbols of the available variables ignoring this one
+       protected ArrayList<String> getAllSymbols(){
+               ArrayList<String> symbols = new ArrayList<String>();
+               for (FlightDataType type : FlightDataType.ALL_TYPES)
+                       symbols.add(type.getSymbol());
+               
+               if (doc != null){
+                       for (CustomExpression exp : doc.getCustomExpressions() ){
+                               if (exp != this)
+                                       symbols.add(exp.getSymbol());
+                       }
+               }
+               return symbols;
+       }
+       
+       public boolean checkSymbol(){
+               if (symbol.trim().isEmpty())
+                       return false;
+               
+               // No bad characters
+               for (char c : "0123456789.,()[]{}<>:#@%^&* ".toCharArray())
+                       if (symbol.indexOf(c) != -1 )
+                               return false;
+               
+               // No operators (ignoring brackets)
+               for (String s : Functions.AVAILABLE_OPERATORS.keySet()){
+                       if (symbol.equals(s.trim().replaceAll("\\(|\\)|\\]|\\[|:", "")))
+                               return false;
+               }
+               
+               // No already defined symbols
+               ArrayList<String> symbols = getAllSymbols().clone();
+               if (symbols.contains(symbol.trim())){
+                       int index = symbols.indexOf(symbol.trim());
+                       log.user("Symbol "+symbol+" already exists, found "+symbols.get(index));
+                       return false;
+               }
+               
+               return true;
+       }
+       
+       public boolean checkName(){
+               if (name.trim().isEmpty())
+                       return false;
+               
+               // No characters that could mess things up saving etc
+               for (char c : ",()[]{}<>#".toCharArray())
+                       if (name.indexOf(c) != -1 )
+                               return false;
+               
+               ArrayList<String> names = getAllNames().clone();
+               if (names.contains(name.trim())){
+                       int index = names.indexOf(name.trim());
+                       log.user("Name "+name+" already exists, found "+names.get(index));
+                       return false;
+               }
+               
+               return true;
+       }
+       
+       // Currently no restrictions on unit
+       public boolean checkUnit(){
+               return true;
+       }
+       
+       public boolean checkAll(){
+               return checkUnit() && checkSymbol() && checkName() && checkExpression();
+       }
+       
+       public String getName(){
+               return name;
+       }
+       
+       public String getSymbol(){
+               return symbol;
+       }
+       
+       public String getUnit(){
+               return unit;
+       }
+       
+       public String getExpressionString(){
+               return expression;
+       }
+       
+       /**
+        * Performs a basic check to see if the current expression string is valid
+        * This includes checking for bad characters and balanced brackets and test
+        * building the expression.
+        */
+       public boolean checkExpression(){
+               if (expression.trim().isEmpty()){
+                       return false;
+               }
+               
+               int round = 0, square = 0; // count of bracket openings
+               for (char c : expression.toCharArray()){
+                       switch (c) {
+                               case '(' : round++; break;
+                               case ')' : round--; break;
+                               case '[' : square++; break;
+                               case ']' : square--; break;
+                               case ':' : 
+                                       if (square <= 0){
+                                               log.user(": found outside range expression");
+                                               return false;
+                                       }
+                                       else break;
+                               case '#' : return false;
+                               case '=' : return false;
+                       }
+               }
+               if (round != 0 || square != 0) {
+                       log.user("Expression has unballanced brackets");
+                       return false;
+               }
+               
+               
+               //// Define the available variables as empty
+               // The built in data types
+               for (FlightDataType type : FlightDataType.ALL_TYPES){
+                       builder.withVariable(new Variable(type.getSymbol()));
+               }
+               
+               for (String symb : getAllSymbols()){
+                       builder.withVariable(new Variable(symb));
+               }
+               
+               // Try to build
+               try {
+                       builder.build();
+               } catch (Exception e) {
+                       log.user("Custom expression invalid : " + e.toString());
+                       return false;
+               }
+               
+               
+               // Otherwise, all OK
+               return true;
+       }
+       
+       public Double evaluateDouble(SimulationStatus status){
+               return evaluate(status).getDoubleValue();
+       }
+       
+       /*
+        * Builds the expression, done automatically during evaluation. Logs any errors. Returns null in case of error.
+        */
+       protected Calculable buildExpression(){
+               return buildExpression(builder);
+       }
+       
+       /*
+        * Builds a specified expression, log any errors and returns null in case of error.
+        */
+       protected Calculable buildExpression(ExpressionBuilder b){
+               Calculable calc;
+               try {
+                       calc = b.build();
+               } catch (UnknownFunctionException e1) {
+                       log.user("Unknown function. Could not build custom expression "+name);
+                       return null;
+               } catch (UnparsableExpressionException e1) {
+                       log.user("Unparsable expression. Could not build custom expression "+name+". "+e1.getMessage());
+                       return null;
+               }
+               
+               return calc;
+       }
+       
+       /*
+        * Evaluate the expression using the last variable values from the simulation status.
+        * Returns NaN on any error.
+        */
+       public Variable evaluate(SimulationStatus status){
+               
+               Calculable calc = buildExpression(builder);
+               if (calc == null){
+                       return new Variable("Unknown");
+               }
+               
+               // Evaluate any sub expressions and set associated variables in the calculable
+               for (CustomExpression expr : this.subExpressions){
+                       calc.setVariable( expr.evaluate(status) );
+               }
+                       
+               // Set all the built-in variables. Strictly we surely won't need all of them
+               // Going through and checking them to include only the ones used *might* give a speedup
+               for (FlightDataType type : status.getFlightData().getTypes()){
+                       double value = status.getFlightData().getLast(type); 
+                       calc.setVariable( new Variable(type.getSymbol(), value ) );
+               }
+               
+               double result = Double.NaN;
+               try{
+                       result = calc.calculate().getDoubleValue();
+               }
+               catch (java.util.EmptyStackException e){
+                       log.user("Unable to calculate expression "+this.expression+" due to empty stack exception");
+               }
+                       
+               return new Variable(name, result);
+       }
+       
+       /*
+        * Returns the new flight data type corresponding to this calculated data
+        * If the unit matches a SI unit string then the datatype will have the corresponding unitgroup.
+        * Otherwise, a fixed unit group will be created
+        */
+       public FlightDataType getType(){
+               
+               UnitGroup ug = UnitGroup.SIUNITS.get(unit); 
+               if ( ug == null ){
+                       ug = new FixedUnitGroup(unit);
+               }
+               
+               FlightDataType type = FlightDataType.getType(name, symbol, ug);
+               
+               //log.debug(this.getClass().getSimpleName()+" returned type "+type.getName()+" (" + type.getSymbol() + ")" );           
+               
+               return type;
+       }
+       
+       /*
+        * Add this expression to the document if valid and not in document already
+        */
+       public void addToDocument(){
+               // Abort if exact expression already in
+               ArrayList<CustomExpression> expressions = doc.getCustomExpressions();
+               if ( !expressions.isEmpty() ) {
+                       // check if expression already exists
+                       if ( expressions.contains(this) ){
+                               log.user("Expression already in document. This unit : "+this.getUnit()+", existing unit : "+expressions.get(0).getUnit());
+                               return;
+                       }
+               }
+                       
+               if (this.checkAll()){
+                       log.user("Custom expression added to rocket document");
+                       doc.addCustomExpression( this );
+               }
+       }
+       
+       /*
+        * Removes this expression from the document, replacing it with a given new expression
+        */
+       public void overwrite(CustomExpression newExpression){
+               if (!doc.getCustomExpressions().contains(this)) 
+                       return;
+               else {
+                       int index = doc.getCustomExpressions().indexOf(this);
+                       doc.getCustomExpressions().set(index, newExpression);
+               }
+       }
+       
+       @Override
+       public String toString(){
+               return "Custom expression : "+this.name.toString()+ " " + this.expression.toString();
+       }
+       
+       @Override
+       /*
+        * Clone method makes a deep copy of everything except the reference to the document.
+        * If you want to apply this to another simulation, set simulation manually after cloning.
+        * @see java.lang.Object#clone()
+        */
+       public Object clone() {
+             try {
+                 return super.clone();
+             }
+             catch( CloneNotSupportedException e )
+             {
+                 return new CustomExpression(  doc  , 
+                               new String(this.getName()), 
+                               new String(this.getSymbol()),
+                               new String(this.getUnit()),
+                               new String(this.getExpressionString()));
+             }
+       } 
+       
+       /*
+        * Returns a simple all upper case string hash code with a proceeding # mark.
+        * Used for temporary substitution when evaluating index and range expressions.
+        */
+       public String hash(){
+               Integer hashint = new Integer(this.getExpressionString().hashCode());
+               String hash = "#";
+               for (char c : hashint.toString().toCharArray()){
+                       char newc = (char) (c + 17);
+                       hash = hash + newc;
+               }
+               return hash;
+       }
+       
+       @Override
+       public boolean equals(Object obj){
+               CustomExpression other = (CustomExpression) obj;
+               
+               return ( this.getName().equals( other.getName() ) && 
+                                this.getSymbol().equals( other.getSymbol() ) &&
+                                this.getExpressionString().equals( other.getExpressionString() ) &&
+                                this.getUnit().equals( other.getUnit() )
+                               );
+       }
+
+       @Override
+       public int hashCode() {
+               return hash().hashCode();
+       }
+}
diff --git a/core/src/net/sf/openrocket/simulation/customexpression/Functions.java b/core/src/net/sf/openrocket/simulation/customexpression/Functions.java
new file mode 100644 (file)
index 0000000..6e638d9
--- /dev/null
@@ -0,0 +1,269 @@
+package net.sf.openrocket.simulation.customexpression;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.SortedMap;
+import java.util.TreeMap;
+
+import net.sf.openrocket.l10n.Translator;
+import net.sf.openrocket.logging.LogHelper;
+import net.sf.openrocket.startup.Application;
+import net.sf.openrocket.util.ArrayUtils;
+
+import de.congrace.exp4j.CustomFunction;
+import de.congrace.exp4j.InvalidCustomFunctionException;
+import de.congrace.exp4j.Variable;
+
+/*
+ * This is a singleton class which contains all the functions for custom expressions not provided by exp4j
+ */
+public class Functions {
+       private static Functions instance = null;
+       
+       private static final LogHelper log = Application.getLogger();
+       private static final Translator trans = Application.getTranslator();
+       
+       private List<CustomFunction> allFunctions = new ArrayList<CustomFunction>();
+
+       public static Functions getInstance() {
+               if(instance == null) {
+                       try {
+                               instance = new Functions();
+                       } catch (InvalidCustomFunctionException e) {
+                               log.error("Invalid custom function.");
+                       }
+               }
+               return instance;
+       }
+       
+       public List<CustomFunction> getAllFunction(){
+               return allFunctions;
+       }
+       
+       // A map of available operator strings (keys) and description of function (value)
+       public static final SortedMap<String, String> AVAILABLE_OPERATORS = new TreeMap<String, String>() {{
+           put("+"             , trans.get("Operator.plus"));
+           put("-"                     , trans.get("Operator.minus"));
+           put("*"                     , trans.get("Operator.star"));
+           put("/"                     , trans.get("Operator.div"));
+           put("%"                     , trans.get("Operator.mod"));
+           put("^"                     , trans.get("Operator.pow"));
+           put("abs()"         , trans.get("Operator.abs"));
+           put("ceil()"        , trans.get("Operator.ceil"));
+           put("floor()"       , trans.get("Operator.floor"));
+           put("sqrt()"        , trans.get("Operator.sqrt"));
+           put("cbrt()"        , trans.get("Operator.cbrt"));
+           put("exp()"         , trans.get("Operator.exp"));
+           put("log()"         , trans.get("Operator.ln"));
+           put("sin()"         , trans.get("Operator.sin"));
+           put("cos()"         , trans.get("Operator.cos"));
+           put("tan()"         , trans.get("Operator.tan"));
+           put("asin()"        , trans.get("Operator.asin"));
+           put("acos()"        , trans.get("Operator.acos"));
+           put("atan()"        , trans.get("Operator.atan"));
+           put("sinh()"        , trans.get("Operator.hsin"));
+           put("cosh()"        , trans.get("Operator.hcos"));
+           put("tanh()"        , trans.get("Operator.htan"));
+           put("log10()"       , trans.get("Operator.log10"));
+           put("round()"       , trans.get("Operator.round"));
+           put("random()"      , trans.get("Operator.random"));
+           put("expm1()"       , trans.get("Operator.expm1"));
+           put("mean([:])"     , trans.get("Operator.mean"));
+           put("min([:])"      , trans.get("Operator.min"));
+           put("max([:])"      , trans.get("Operator.max"));
+           put("var([:])"      , trans.get("Operator.var"));
+           put("rms([:])"      , trans.get("Operator.rms"));
+           put("stdev([:])", trans.get("Operator.stdev"));
+           put("lclip(,)"  , trans.get("Operator.lclip"));
+           put("uclip(,)"  , trans.get("Operator.uclip"));
+           put("binf([:],,)"   , trans.get("Operator.binf"));
+           put("trapz([:])"    , trans.get("Operator.trapz"));
+           put("tnear([:],)"   , trans.get("Operator.tnear"));
+       }}; 
+       
+       
+       protected Functions() throws InvalidCustomFunctionException {
+               
+               CustomFunction meanFn = new CustomFunction("mean") {
+                       @Override
+                       public Variable applyFunction(List<Variable> vars) {
+                       double[] vals;
+                       try{
+                               vals = vars.get(0).getArrayValue();
+                       } catch (Exception e) {
+                               return new Variable("Invalid");
+                       }
+                       return new Variable("double MEAN result, ", ArrayUtils.mean(vals));
+                       }       
+               };
+               allFunctions.add(meanFn);
+               
+               CustomFunction minFn = new CustomFunction("min") {
+                       @Override
+                       public Variable applyFunction(List<Variable> vars) {
+                       double[] vals;
+                       try{
+                               vals = vars.get(0).getArrayValue();
+                       } catch (Exception e) {
+                               return new Variable("Invalid");
+                       }
+                       return new Variable("double MIN result, ", ArrayUtils.min(vals));
+                       }
+               };
+               allFunctions.add(minFn);
+               
+               CustomFunction maxFn = new CustomFunction("max") {
+                       @Override
+                       public Variable applyFunction(List<Variable> vars) {
+                       double[] vals;
+                       try{
+                               vals = vars.get(0).getArrayValue();
+                       } catch (Exception e) {
+                               return new Variable("Invalid");
+                       }
+                       return new Variable("double MAX result, ", ArrayUtils.max(vals));
+                       }
+               };
+               allFunctions.add(maxFn);
+       
+               CustomFunction varFn = new CustomFunction("var") {
+                       @Override
+                       public Variable applyFunction(List<Variable> vars) {
+                       double[] vals;
+                       try{
+                               vals = vars.get(0).getArrayValue();
+                       } catch (Exception e) {
+                               return new Variable("Invalid");
+                       }
+                       return new Variable("double VAR result, ", ArrayUtils.variance(vals));
+                       }
+               };
+               allFunctions.add(varFn);
+               
+               CustomFunction stdevFn = new CustomFunction("stdev") {
+                       @Override
+                       public Variable applyFunction(List<Variable> vars) {
+                       double[] vals;
+                       try{
+                               vals = vars.get(0).getArrayValue();
+                       } catch (Exception e) {
+                               return new Variable("Invalid");
+                       }
+                       return new Variable("double STDEV result, ", ArrayUtils.stdev(vals));
+                       }
+               };
+               allFunctions.add(stdevFn);
+               
+               CustomFunction rmsFn = new CustomFunction("rms") {
+                       @Override
+                       public Variable applyFunction(List<Variable> vars) {
+                       double[] vals;
+                       try{
+                               vals = vars.get(0).getArrayValue();
+                       } catch (Exception e) {
+                               return new Variable("Invalid");
+                       }
+                       return new Variable("double RMS result, ", ArrayUtils.rms(vals));
+                       }
+               };
+               allFunctions.add(rmsFn);
+               
+               CustomFunction lclipFn = new CustomFunction("lclip",2) {
+                       @Override
+                       public Variable applyFunction(List<Variable> vars) {
+                       double val, clip;
+                       try{
+                               val = vars.get(0).getDoubleValue();
+                               clip = vars.get(1).getDoubleValue();
+                       } catch (Exception e) {
+                               return new Variable("Invalid");
+                       }
+                       if (val < clip){
+                               val = clip;
+                       }
+                       return new Variable("double LCLIP result, ", val);
+                       }
+               };
+               allFunctions.add(lclipFn);
+               
+               CustomFunction uclipFn = new CustomFunction("uclip",2) {
+                       @Override
+                       public Variable applyFunction(List<Variable> vars) {
+                               double val, clip;
+                       try{
+                               val = vars.get(0).getDoubleValue();
+                               clip = vars.get(1).getDoubleValue();
+                       } catch (Exception e) {
+                               return new Variable("Invalid");
+                       }
+                       if (val > clip){
+                               val = clip;
+                       }
+                       return new Variable("double UCLIP result, ", val);
+                       }
+               };
+               allFunctions.add(uclipFn);
+               
+               CustomFunction binfFn = new CustomFunction("binf", 3) {
+                       @Override
+                       public Variable applyFunction(List<Variable> vars) {
+                               double[] range;
+                               double min, max;
+                               try{
+                                       range = vars.get(0).getArrayValue();
+                                       min = vars.get(1).getDoubleValue();
+                                       max = vars.get(2).getDoubleValue();
+                               } catch (Exception e) {
+                                       return new Variable("Invalid");
+                               }
+                               
+                               int ins = 0;
+                               for (double x: range){
+                                       if (x < max && x > min){
+                                               ins++;
+                                       }
+                               }
+                               return new Variable("double BINF result", (double) ins/ (double) range.length);
+                       }
+               };
+               allFunctions.add(binfFn);
+               
+               CustomFunction rombintFn = new CustomFunction("trapz") {
+                       @Override
+                       public Variable applyFunction(List<Variable> vars) {
+                               double[] range;
+                               double dt = 0;
+                               try{
+                                       range = vars.get(0).getArrayValue();
+                                       dt = vars.get(0).getStep();
+                               } catch (Exception e) {
+                                       return new Variable("Invalid");
+                               }
+                               
+                               return new Variable("double TRAPZ result", ArrayUtils.trapz(range, dt) );
+                       }
+               };
+               allFunctions.add(rombintFn);
+               
+               CustomFunction tnearFn = new CustomFunction("tnear", 2) {
+                       @Override
+                       public Variable applyFunction(List<Variable> vars) {
+                               double[] range;
+                               double dt = 0;
+                               double start = 0;
+                               double near = 0;
+                               try{
+                                       range = vars.get(0).getArrayValue();
+                                       dt = vars.get(0).getStep();
+                                       start = vars.get(0).getStart();
+                                       near = vars.get(1).getDoubleValue();
+                               } catch (Exception e) {
+                                       return new Variable("Invalid");
+                               }
+                               
+                               return new Variable("double TNEAR result", ArrayUtils.tnear(range, near, start, dt) );
+                       } 
+               };
+               allFunctions.add(tnearFn);
+       }
+}
diff --git a/core/src/net/sf/openrocket/simulation/customexpression/IndexExpression.java b/core/src/net/sf/openrocket/simulation/customexpression/IndexExpression.java
new file mode 100644 (file)
index 0000000..ab7e157
--- /dev/null
@@ -0,0 +1,54 @@
+package net.sf.openrocket.simulation.customexpression;
+
+import java.util.List;
+
+import de.congrace.exp4j.Calculable;
+import de.congrace.exp4j.Variable;
+import net.sf.openrocket.document.OpenRocketDocument;
+import net.sf.openrocket.logging.LogHelper;
+import net.sf.openrocket.simulation.customexpression.CustomExpression;
+import net.sf.openrocket.simulation.FlightDataType;
+import net.sf.openrocket.simulation.SimulationStatus;
+import net.sf.openrocket.startup.Application;
+import net.sf.openrocket.util.LinearInterpolator;
+
+public class IndexExpression extends CustomExpression {
+
+       FlightDataType type;
+       private static final LogHelper log = Application.getLogger();
+       
+       public IndexExpression(OpenRocketDocument doc, String indexText, String typeText){
+               super(doc);
+               
+               setExpression(indexText);
+               this.setName("");
+               this.setSymbol(typeText);
+               
+       }
+       
+       @Override
+       public Variable evaluate(SimulationStatus status){
+               
+               Calculable calc = buildExpression();
+               if (calc == null){
+                       return new Variable("Unknown");
+               }
+               
+               // From the given datatype, get the time and function values and make an interpolator
+               FlightDataType type = getType();
+               List<Double> data = status.getFlightData().get(type);
+               List<Double> time = status.getFlightData().get(FlightDataType.TYPE_TIME);
+               LinearInterpolator interp = new LinearInterpolator(time, data); 
+               
+               // Evaluate this expression to get the t value
+               try{
+                       double tvalue = calc.calculate().getDoubleValue();
+                       return new Variable(hash(), interp.getValue( tvalue ) );
+               }
+               catch (java.util.EmptyStackException e){
+                       log.user("Unable to calculate time index for indexed expression "+getExpressionString()+" due to empty stack exception");
+                       return new Variable("Unknown");
+               }
+               
+       }
+}
diff --git a/core/src/net/sf/openrocket/simulation/customexpression/RangeExpression.java b/core/src/net/sf/openrocket/simulation/customexpression/RangeExpression.java
new file mode 100644 (file)
index 0000000..c479b31
--- /dev/null
@@ -0,0 +1,116 @@
+/*
+ * A range expression contains two indexExpressions for the beginning and end time index of a range
+ */
+
+package net.sf.openrocket.simulation.customexpression;
+
+import java.util.List;
+
+import de.congrace.exp4j.Calculable;
+import de.congrace.exp4j.ExpressionBuilder;
+import de.congrace.exp4j.Variable;
+import net.sf.openrocket.document.OpenRocketDocument;
+import net.sf.openrocket.logging.LogHelper;
+import net.sf.openrocket.simulation.customexpression.CustomExpression;
+import net.sf.openrocket.simulation.FlightDataType;
+import net.sf.openrocket.simulation.SimulationStatus;
+import net.sf.openrocket.startup.Application;
+import net.sf.openrocket.util.ArrayUtils;
+import net.sf.openrocket.util.LinearInterpolator;
+import net.sf.openrocket.util.MathUtil;
+
+public class RangeExpression extends CustomExpression {
+       private static final LogHelper log = Application.getLogger();
+
+       private ExpressionBuilder startBuilder, endBuilder;
+       
+       public RangeExpression(OpenRocketDocument doc, String startTime, String endTime, String variableType) {
+               super(doc);
+               
+               if (startTime.isEmpty()){
+                       startTime = "0";
+               }
+               if (endTime.isEmpty()){
+                       endTime = "t";
+               }
+               
+               this.setName("");
+               this.setSymbol(variableType);
+               this.setExpressions(startTime, endTime);
+               this.expression = variableType+startTime+endTime; // this is used just for generating the hash
+               
+               log.info("New range expression, "+startTime + " to "+endTime);
+               
+       }
+       
+       /*
+        * Sets the actual expression string for this expression
+        */
+       private void setExpressions(String start, String end){
+               
+               startBuilder = new ExpressionBuilder(start);
+               endBuilder = new ExpressionBuilder(end);
+               for (String n : getAllSymbols()){
+                       startBuilder.withVariable(new Variable(n));
+                       endBuilder.withVariable(new Variable(n));
+               }
+       }
+       
+       @Override
+       public Variable evaluate(SimulationStatus status){
+               
+               Calculable startCalc = buildExpression(startBuilder);
+               Calculable endCalc = buildExpression(endBuilder);
+               if (startCalc == null || endCalc == null){
+                       return new Variable("Unknown");
+               }
+               
+               // Set the variables in the start and end calculators
+               for (FlightDataType type : status.getFlightData().getTypes()){
+                       double value = status.getFlightData().getLast(type); 
+                       startCalc.setVariable( new Variable(type.getSymbol(), value ) );
+                       endCalc.setVariable( new Variable(type.getSymbol(), value ) );
+               }               
+               
+               // From the given datatype, get the time and function values and make an interpolator
+               FlightDataType type = getType();
+               
+               List<Double> data = status.getFlightData().get(type);
+               List<Double> time = status.getFlightData().get(FlightDataType.TYPE_TIME);
+               LinearInterpolator interp = new LinearInterpolator(time, data); 
+               
+               // Evaluate the expression to get the start and end of the range
+               double startTime, endTime;
+               try{
+                       startTime = startCalc.calculate().getDoubleValue();
+                       startTime = MathUtil.clamp(startTime, 0, Double.MAX_VALUE);
+                       
+                       endTime = endCalc.calculate().getDoubleValue();
+                       endTime = MathUtil.clamp(endTime, 0, time.get(time.size()-1));
+               }
+               catch (java.util.EmptyStackException e){
+                       log.user("Unable to calculate time index for range expression "+getSymbol()+" due to empty stack exception");
+                       return new Variable("Unknown");
+               }
+               
+               // generate an array representing the range
+               double step = status.getSimulationConditions().getSimulation().getOptions().getTimeStep();
+               double[] t = ArrayUtils.range(startTime, endTime,  step);
+               double[] y = new double[t.length]; 
+               int i = 0;
+               for (double tval : t){
+                       y[i] = interp.getValue( tval );
+                       i++;
+               }
+                               
+               Variable result;
+               if (y.length == 0){
+                       result = new Variable("Unknown");
+               }
+               else {
+                       result = new Variable(hash(), y, startTime, step);
+               }
+               
+               return result;
+       }
+}
index a0984961b3f0c7fbe38fb189232404d71ad5b908..8cb6fde494dc2ac4668022eaa1f4bd8b901a0637 100644 (file)
@@ -24,6 +24,10 @@ public class FixedUnitGroup extends UnitGroup {
                return new GeneralUnit(1, unitString);
        }
        
+       public Unit getSIUnit(){
+               return new GeneralUnit(1, unitString);
+       }
+       
        public boolean contains(Unit u){
                return true;
        }
index 88ab4a77fd11be5f57ed0a6d49bed6fb69373e10..48ae67a9ebe273f46d0280686c642db2ec78a117 100644 (file)
@@ -27,6 +27,7 @@ public class UnitGroup {
        
        public static final UnitGroup UNITS_MOTOR_DIMENSIONS;
        public static final UnitGroup UNITS_LENGTH;
+       public static final UnitGroup UNITS_ALL_LENGTHS;
        public static final UnitGroup UNITS_DISTANCE;
        
        public static final UnitGroup UNITS_AREA;
@@ -63,11 +64,17 @@ public class UnitGroup {
        public static final UnitGroup UNITS_ROUGHNESS;
        
        public static final UnitGroup UNITS_COEFFICIENT;
+       public static final UnitGroup UNITS_FREQUENCY;
        
-       //      public static final UnitGroup UNITS_FREQUENCY;
+       public static final UnitGroup UNITS_ENERGY;
+       public static final UnitGroup UNITS_POWER;
+       public static final UnitGroup UNITS_MOMENTUM;
+       public static final UnitGroup UNITS_VOLTAGE;
+       public static final UnitGroup UNITS_CURRENT;
        
        
-       public static final Map<String, UnitGroup> UNITS;
+       public static final Map<String, UnitGroup> UNITS; // keys such as "LENGTH", "VELOCITY"
+       public static final Map<String, UnitGroup> SIUNITS; // keys such a "m", "m/s"
        
        
        /*
@@ -80,6 +87,36 @@ public class UnitGroup {
                UNITS_NONE = new UnitGroup();
                UNITS_NONE.addUnit(Unit.NOUNIT2);
                
+               UNITS_ENERGY = new UnitGroup();
+               UNITS_ENERGY.addUnit(new GeneralUnit(1, "J"));
+               UNITS_ENERGY.addUnit(new GeneralUnit(1e-7, "erg"));
+               UNITS_ENERGY.addUnit(new GeneralUnit(1.055, "BTU"));
+               UNITS_ENERGY.addUnit(new GeneralUnit(4.184, "cal"));
+               UNITS_ENERGY.addUnit(new GeneralUnit(1.3558179483314, "ft"+DOT+"lbf"));
+               UNITS_ENERGY.setDefaultUnit(0);
+               
+               UNITS_POWER = new UnitGroup();
+               UNITS_POWER.addUnit(new GeneralUnit(1e-3, "mW"));
+               UNITS_POWER.addUnit(new GeneralUnit(1, "W"));
+               UNITS_POWER.addUnit(new GeneralUnit(1e3, "kW"));
+               UNITS_POWER.addUnit(new GeneralUnit(1e-7, "ergs"));
+               UNITS_POWER.addUnit(new GeneralUnit(745.699872, "hp"));
+               UNITS_POWER.setDefaultUnit(1);
+               
+               UNITS_MOMENTUM = new UnitGroup();
+               UNITS_MOMENTUM.addUnit(new GeneralUnit(1, "kg"+DOT+"m/s"));
+               UNITS_MOMENTUM.setDefaultUnit(0);
+               
+               UNITS_VOLTAGE = new UnitGroup();
+               UNITS_VOLTAGE.addUnit(new GeneralUnit(1e-3, "mV"));
+               UNITS_VOLTAGE.addUnit(new GeneralUnit(1, "V"));
+               UNITS_VOLTAGE.setDefaultUnit(1);
+               
+               UNITS_CURRENT = new UnitGroup();
+               UNITS_CURRENT.addUnit(new GeneralUnit(1e-3, "mA"));
+               UNITS_CURRENT.addUnit(new GeneralUnit(1, "A"));
+               UNITS_CURRENT.setDefaultUnit(1);
+               
                UNITS_LENGTH = new UnitGroup();
                UNITS_LENGTH.addUnit(new GeneralUnit(0.001, "mm"));
                UNITS_LENGTH.addUnit(new GeneralUnit(0.01, "cm"));
@@ -90,6 +127,7 @@ public class UnitGroup {
                UNITS_LENGTH.setDefaultUnit(1);
                
                UNITS_MOTOR_DIMENSIONS = new UnitGroup();
+               UNITS_MOTOR_DIMENSIONS.addUnit(new GeneralUnit(1, "m")); // just added
                UNITS_MOTOR_DIMENSIONS.addUnit(new GeneralUnit(0.001, "mm"));
                UNITS_MOTOR_DIMENSIONS.addUnit(new GeneralUnit(0.01, "cm"));
                UNITS_MOTOR_DIMENSIONS.addUnit(new GeneralUnit(0.0254, "in"));
@@ -103,6 +141,19 @@ public class UnitGroup {
                UNITS_DISTANCE.addUnit(new GeneralUnit(1609.344, "mi"));
                UNITS_DISTANCE.addUnit(new GeneralUnit(1852, "nmi"));
                
+               UNITS_ALL_LENGTHS = new UnitGroup();
+               UNITS_ALL_LENGTHS.addUnit(new GeneralUnit(0.001, "mm"));
+               UNITS_ALL_LENGTHS.addUnit(new GeneralUnit(0.01, "cm"));
+               UNITS_ALL_LENGTHS.addUnit(new GeneralUnit(1, "m"));
+               UNITS_ALL_LENGTHS.addUnit(new GeneralUnit(1000, "km"));
+               UNITS_ALL_LENGTHS.addUnit(new GeneralUnit(0.0254, "in"));
+               UNITS_ALL_LENGTHS.addUnit(new FractionalUnit(0.0254, "in/64", "in", 64, 1d / 16d, 0.5d / 64d));
+               UNITS_ALL_LENGTHS.addUnit(new GeneralUnit(0.3048, "ft"));
+               UNITS_ALL_LENGTHS.addUnit(new GeneralUnit(0.9144, "yd"));
+               UNITS_ALL_LENGTHS.addUnit(new GeneralUnit(1609.344, "mi"));
+               UNITS_ALL_LENGTHS.addUnit(new GeneralUnit(1852, "nmi"));
+               UNITS_ALL_LENGTHS.setDefaultUnit(2);
+               
                UNITS_AREA = new UnitGroup();
                UNITS_AREA.addUnit(new GeneralUnit(pow2(0.001), "mm" + SQUARED));
                UNITS_AREA.addUnit(new GeneralUnit(pow2(0.01), "cm" + SQUARED));
@@ -113,6 +164,7 @@ public class UnitGroup {
                
                
                UNITS_STABILITY = new UnitGroup();
+               UNITS_STABILITY.addUnit(new GeneralUnit(1, "m"));
                UNITS_STABILITY.addUnit(new GeneralUnit(0.001, "mm"));
                UNITS_STABILITY.addUnit(new GeneralUnit(0.01, "cm"));
                UNITS_STABILITY.addUnit(new GeneralUnit(0.0254, "in"));
@@ -234,6 +286,7 @@ public class UnitGroup {
                
                
                UNITS_ROUGHNESS = new UnitGroup();
+               UNITS_ROUGHNESS.addUnit(new GeneralUnit(1, "m")); // just added
                UNITS_ROUGHNESS.addUnit(new GeneralUnit(0.000001, MICRO + "m"));
                UNITS_ROUGHNESS.addUnit(new GeneralUnit(0.0000254, "mil"));
                
@@ -243,18 +296,20 @@ public class UnitGroup {
                
                
                // This is not used by OpenRocket, and not extensively tested:
-               //              UNITS_FREQUENCY = new UnitGroup();
+               UNITS_FREQUENCY = new UnitGroup();
                //              UNITS_FREQUENCY.addUnit(new GeneralUnit(1, "s"));
                //              UNITS_FREQUENCY.addUnit(new GeneralUnit(0.001, "ms"));
                //              UNITS_FREQUENCY.addUnit(new GeneralUnit(0.000001, MICRO + "s"));
-               //              UNITS_FREQUENCY.addUnit(new FrequencyUnit(1, "Hz"));
-               //              UNITS_FREQUENCY.addUnit(new FrequencyUnit(1000, "kHz"));
-               //              UNITS_FREQUENCY.setDefaultUnit(3);
+               UNITS_FREQUENCY.addUnit(new FrequencyUnit(.001, "mHz"));
+               UNITS_FREQUENCY.addUnit(new FrequencyUnit(1, "Hz"));
+               UNITS_FREQUENCY.addUnit(new FrequencyUnit(1000, "kHz"));
+               UNITS_FREQUENCY.setDefaultUnit(1);
                
                
                HashMap<String, UnitGroup> map = new HashMap<String, UnitGroup>();
                map.put("NONE", UNITS_NONE);
                map.put("LENGTH", UNITS_LENGTH);
+               map.put("ALL_LENGTHS", UNITS_ALL_LENGTHS);
                map.put("MOTOR_DIMENSIONS", UNITS_MOTOR_DIMENSIONS);
                map.put("DISTANCE", UNITS_DISTANCE);
                map.put("VELOCITY", UNITS_VELOCITY);
@@ -278,8 +333,36 @@ public class UnitGroup {
                map.put("RELATIVE", UNITS_RELATIVE);
                map.put("ROUGHNESS", UNITS_ROUGHNESS);
                map.put("COEFFICIENT", UNITS_COEFFICIENT);
+               map.put("VOLTAGE", UNITS_VOLTAGE);
+               map.put("CURRENT", UNITS_CURRENT);
+               map.put("ENERGY", UNITS_ENERGY);
+               map.put("POWER", UNITS_POWER);
+               map.put("MOMENTUM", UNITS_MOMENTUM);
+               map.put("FREQUENCY", UNITS_FREQUENCY);
                
                UNITS = Collections.unmodifiableMap(map);
+               
+               HashMap<String, UnitGroup> simap = new HashMap<String, UnitGroup>();
+               simap.put("m", UNITS_ALL_LENGTHS);
+               simap.put("m^2", UNITS_AREA);
+               simap.put("m/s", UNITS_VELOCITY);
+               simap.put("m/s^2", UNITS_ACCELERATION); 
+               simap.put("kg", UNITS_MASS);
+               simap.put("kg m^2", UNITS_INERTIA);
+               simap.put("kg/m^3", UNITS_DENSITY_BULK);
+               simap.put("N", UNITS_FORCE);
+               simap.put("Ns", UNITS_IMPULSE);
+               simap.put("s", UNITS_FLIGHT_TIME);
+               simap.put("Pa", UNITS_PRESSURE);
+               simap.put("V", UNITS_VOLTAGE);
+               simap.put("A", UNITS_CURRENT);
+               simap.put("J", UNITS_ENERGY);
+               simap.put("W", UNITS_POWER);
+               simap.put("kg m/s", UNITS_MOMENTUM);
+               simap.put("Hz", UNITS_FREQUENCY);
+               simap.put("K", UNITS_TEMPERATURE);
+               
+               SIUNITS = Collections.unmodifiableMap(simap);
        }
        
        public static void setDefaultMetricUnits() {
@@ -393,7 +476,14 @@ public class UnitGroup {
                defaultUnit = n;
        }
        
-       
+       public Unit getSIUnit(){
+               for (Unit u : units){
+                       if (u.multiplier == 1){
+                               return u;
+                       }
+               }
+               return UNITS_NONE.getDefaultUnit();
+       }
        
        /**
         * Find a unit by approximate unit name.  Only letters and (ordinary) numbers are
@@ -510,7 +600,28 @@ public class UnitGroup {
                return this.getDefaultUnit().toValue(value);
        }
        
+       @Override
+       public String toString(){
+               return this.getClass().getSimpleName()+":"+this.getSIUnit().toString();
+       }
        
+       @Override
+       public boolean equals(Object o){
+               UnitGroup u = (UnitGroup) o;
+               int size = units.size();
+               if (size != u.units.size()){
+                       return false;
+               }
+               
+               for (int i=0; i<size; i++){
+                       if ( !units.get(i).equals(u.units.get(i)) ){
+                               return false;
+                       }
+               }
+               
+               return true;
+                       
+       }
        
        
        private static final Pattern STRING_PATTERN = Pattern.compile("^\\s*([0-9.,-]+)(.*?)$");
@@ -561,6 +672,15 @@ public class UnitGroup {
        ///////////////////////////
        
        
+       @Override
+       public int hashCode() {
+               int code = 0;
+               for (Unit u : units){
+                       code = code + u.hashCode();
+               }
+               return code;
+       }
+
        /**
         * A private class that switches the CaliberUnit to a rocket-specific CaliberUnit.
         * All other methods are passed through to UNITS_STABILITY.
index 67209f323ea4a0db5b5fa014cfdfab96c15744c4..59290b12d9c8fd48401436ad62bf93dffc2330f4 100644 (file)
@@ -4,6 +4,132 @@ import java.lang.reflect.Array;
 
 public class ArrayUtils {
 
+       /**
+        * Returns a double array with values from start to end with given step.
+        * Starts exactly at start and stops at the step before stop is reached. 
+        */
+       public static double[] range(double start, double stop, double step){
+               
+               int size = (int) Math.floor(((stop - start) / step));
+               
+               //System.out.println("Range from "+start+" to "+stop+" step "+step+" has length "+size);
+               
+               double[] output = new double[size];
+               int i = 0;
+               double x = start;
+               while (i<size){
+                       output[i] = x;
+                       x = x+step;
+                       i++;
+               }
+               
+               return output;
+       }
+
+       /**
+        * Return the mean of an array
+        */
+       public static double mean(double[] vals){
+               double subtotal = 0;
+               for (int i = 0; i < vals.length; i++ ){
+                       subtotal += vals[i];
+               }
+               subtotal = subtotal / vals.length;
+               return subtotal;
+       }
+
+       /**
+        * Returns the maximum value in the array.
+        */
+
+       public static double max(double[] vals) {
+               double m = vals[0];
+               for (int i = 1; i < vals.length; i++)
+                       m = Math.max(m, vals[i]);
+               return m;
+       }
+
+       /**
+        * Returns the minimum value in the array.
+        */
+
+       public static double min(double[] vals) {
+               double m = vals[0];
+               for (int i = 1; i < vals.length; i++)
+                       m = Math.min(m, vals[i]);
+               return m;
+       }
+
+       /**
+        * Returns the variance of the array of doubles
+        */
+       public static double variance(double[] vals) {
+               double mu = mean(vals);
+               double sumsq = 0.0;
+               double temp = 0;
+               for (int i = 0; i < vals.length; i++){
+                       temp = (mu - vals[i]);
+                       sumsq += temp*temp;
+               }
+               return sumsq / (vals.length);
+       }
+
+       /**
+        * Returns the standard deviation of an array of doubles
+        */
+       public static double stdev(double[] vals) {
+               return Math.sqrt(variance(vals));
+       }
+       
+       /**
+        * Returns the RMS value of an array of doubles 
+        */
+       public static double rms(double[] vals) {
+               double m = mean(vals);
+               double s = stdev(vals);
+               return Math.sqrt( m*m + s*s );
+       }
+       
+       /**
+        * Returns the integral of a given array calculated by the trapezoidal rule
+        * dt is the time step between each array value
+        */
+       public static double trapz(double[] y, double dt){
+               double stop = y.length * dt;
+               
+               if (y.length <= 1 || dt <= 0) return 0;
+               
+               double[] x = range(0, stop, dt);
+           
+           double sum = 0.0;
+           for (int i = 1; i < x.length; i++) {
+               sum += (x[i] - x[i-1]) * (y[i] + y[i-1]);
+           }
+           return sum * 0.5;
+       }
+       
+       /**
+        * Returns the nearest value in an array to a given value
+        * Search starts from the lowest array index
+        */
+       public static double tnear(double[] range, double near, double start, double step){
+               double min = Double.POSITIVE_INFINITY;
+               int mini = 0;
+               
+               //System.out.println("Nearest to "+near+" in range length "+range.length);
+               for (int i=0; i < range.length; i++){
+                       double x = Math.abs(range[i] - near);
+                       if (x < min){
+                               min = x;
+                               mini = i;
+                       }
+               }
+               
+               //System.out.println("Found nearest at i="+mini);               
+               return start + (mini*step);
+       }
+       
+       
        public static <T> T[] copyOf( T[] original, int length ) {
                return copyOfRange(original,0,length);
        }
@@ -114,3 +240,4 @@ public class ArrayUtils {
        }
 
 }
+
index b180f61928cad3c18f64084ee41e7c33058aff93..d3a3fa62bcaf83a5b05c35f6f67eccd8e400a558 100644 (file)
@@ -25,7 +25,7 @@ public class ExpressionParser {
                        modified = modify(expression);
                        ExpressionBuilder builder = new ExpressionBuilder(modified);
                        Calculable calc = builder.build();
-                       double n = calc.calculate();
+                       double n = calc.calculate().getDoubleValue();
                        log.debug("Evaluated expression '" + expression + "' (modified='" + modified + "') to " + n);
                        return n;
                } catch (Exception e) {
index 6a9a308b6f11e3b8cde69e4a223080d8b11ea548..1631a9de680a1df812f5cf9a9d78dd2a0e6f59d4 100644 (file)
@@ -1,7 +1,9 @@
 package net.sf.openrocket.util;
 
+import java.util.Arrays;
 import java.util.Iterator;
-import java.util.SortedMap;
+import java.util.List;
+import java.util.Map;
 import java.util.TreeMap;
 
 public class LinearInterpolator implements Cloneable {
@@ -14,7 +16,7 @@ public class LinearInterpolator implements Cloneable {
         */
        public LinearInterpolator() {
        }
-
+       
        /**
         * Construct a <code>LinearInterpolator</code> with the given points.
         * 
@@ -27,8 +29,11 @@ public class LinearInterpolator implements Cloneable {
        public LinearInterpolator(double[] x, double[] y) {
                addPoints(x,y);
        }
-
-
+       
+       public LinearInterpolator(List<Double> x, List<Double> y) {
+               addPoints(x,y);
+       }
+       
        /**
         * Add the point to the linear interpolation.
         * 
@@ -38,7 +43,7 @@ public class LinearInterpolator implements Cloneable {
        public void addPoint(double x, double y) {
                sortMap.put(x, y);
        }
-
+       
        /**
         * Add the points to the linear interpolation.
         * 
@@ -56,66 +61,50 @@ public class LinearInterpolator implements Cloneable {
                        sortMap.put(x[i],y[i]);
                }
        }
-
-
-
+       
+       public void addPoints(List<Double> x, List<Double> y){
+               if (x.size() != y.size()) {
+                       throw new IllegalArgumentException("Array lengths do not match, x="+x.size() +
+                                       " y="+y.size());
+               }
+               for (int i=0; i < x.size(); i++) {
+                       sortMap.put( (Double) x.toArray()[i], (Double) y.toArray()[i]);
+               }
+       }
+       
+       
        public double getValue(double x) {
+               Map.Entry<Double,Double> e1, e2;
                double x1, x2;
-               Double y1, y2;
-               // Froyo does not support floorEntry, firstEntry or higherEntry.  We instead have to
-               // resort to using other more awkward methods.
-
-               y1 = sortMap.get(x);
-
-               if ( y1 != null ) {
-                       // Wow, x was a key in the map.  Such luck.
-                       return y1.doubleValue();
-               }
-
-               // we now know that x is not in the map, so we need to find the lower and higher keys.
+               double y1, y2;
                
-               // let's just make certain that our map is not empty.
-               if ( sortMap.isEmpty() ) {
-                       throw new IllegalStateException("No points added yet to the interpolator.");
-               }
+               e1 = sortMap.floorEntry(x);
                
-               // firstKey in the map - cannot be null since the map is not empty.
-               Double firstKey = sortMap.firstKey();
-
-               // x is smaller than the first entry in the map.
-               if ( x < firstKey.doubleValue() ) {
-                       y1 = sortMap.get(firstKey);
-                       return y1.doubleValue();
+               if (e1 == null) {
+                       // x smaller than any value in the set
+                       e1 = sortMap.firstEntry();
+                       if (e1 == null) {
+                               throw new IllegalStateException("No points added yet to the interpolator.");
+                       }
+                       return e1.getValue();
                }
                
-               // floor key is the largest key smaller than x - since we have at least one key,
-               // and x>=firstKey, we know that floorKey != null.
-               Double floorKey = sortMap.subMap(firstKey, x).lastKey();
-
-               x1 = floorKey.doubleValue();
-               y1 = sortMap.get(floorKey);
-
-               // Now we need to find the key that is greater or equal to x
-               SortedMap<Double,Double> tailMap = sortMap.tailMap(x);
+               x1 = e1.getKey();
+               e2 = sortMap.higherEntry(x1);
 
-               // Check if x is bigger than all the entries.
-               if ( tailMap.isEmpty() ) {
-                       return y1.doubleValue();
+               if (e2 == null) {
+                       // x larger than any value in the set
+                       return e1.getValue();
                }
-               Double ceilKey = tailMap.firstKey();
                
-               // Check if x is bigger than all the entries.
-               if ( ceilKey == null ) {
-                       return y1.doubleValue();
-               }
+               x2 = e2.getKey();
+               y1 = e1.getValue();
+               y2 = e2.getValue();
                
-               x2 = ceilKey.doubleValue();
-               y2 = sortMap.get(ceilKey);
-
                return (x - x1)/(x2-x1) * (y2-y1) + y1;
        }
-
-
+       
+       
        public double[] getXPoints() {
                double[] x = new double[sortMap.size()];
                Iterator<Double> iter = sortMap.keySet().iterator();
@@ -124,8 +113,8 @@ public class LinearInterpolator implements Cloneable {
                }
                return x;
        }
-
-
+       
+       
        @SuppressWarnings("unchecked")
        @Override
        public LinearInterpolator clone() {
@@ -138,4 +127,25 @@ public class LinearInterpolator implements Cloneable {
                }
        }
 
+       
+       public static void main(String[] args) {
+               LinearInterpolator interpolator = new LinearInterpolator(
+                               new double[] {1, 1.5, 2, 4, 5},
+                               new double[] {0, 1,   0, 2, 2}
+               );
+               
+               for (double x=0; x < 6; x+=0.1) {
+                       System.out.printf("%.1f:  %.2f\n", x, interpolator.getValue(x));
+               }
+               
+               // Should be the same
+               
+               ArrayList<Double> time = new ArrayList<Double>( Arrays.asList( new Double[] {1.0, 1.5, 2.0, 4.0, 5.0} ));
+               ArrayList<Double> y = new ArrayList<Double>( Arrays.asList( new Double[] {0.0, 1.0, 0.0, 2.0, 2.0} ));
+               
+               LinearInterpolator interpolator2 = new LinearInterpolator(time,y);
+               for (double x=0; x < 6; x+=0.1) {
+                       System.out.printf("%.1f:  %.2f\n", x, interpolator2.getValue(x));
+               }
+       }       
 }
index c88e85853562bc5907f64bfb4654d329b2140128..09a316899cec68ce70ef6140d3aaafb6e93ae130 100644 (file)
@@ -310,7 +310,7 @@ public class MathUtil {
                        return sorted.get(n / 2).doubleValue();
                }
        }
-
+       
        /**
         * Use interpolation to determine the value of the function at point t.
         * Current implementation uses simple linear interpolation.   The domain
index 0189545e0dd7f00810515801b1f5fb6f3d9e3cad..0b86876de9055e3047b5ea3649b9c19e50325a31 100644 (file)
@@ -184,4 +184,17 @@ public class TextUtil {
                s = s.replace(">", "&gt;");
                return s;
        }
+       
+       /*
+        * Returns a word-wrapped version of given input string using HTML syntax, wrapped to len characters.
+        */
+       public static String wrap(String in,int len) {
+               in=in.trim();
+               if(in.length()<len) return in;
+               if(in.substring(0, len).contains("\n"))
+                       return in.substring(0, in.indexOf("\n")).trim() + "\n\n" + wrap(in.substring(in.indexOf("\n") + 1), len);
+               int place=Math.max(Math.max(in.lastIndexOf(" ",len),in.lastIndexOf("\t",len)),in.lastIndexOf("-",len));
+               return "<html>"+in.substring(0,place).trim()+"<br>"+wrap(in.substring(place),len);
+       }
+
 }
diff --git a/core/test/net/sf/openrocket/simulation/customexpression/TestExpressions.java b/core/test/net/sf/openrocket/simulation/customexpression/TestExpressions.java
new file mode 100644 (file)
index 0000000..d67bca0
--- /dev/null
@@ -0,0 +1,23 @@
+package net.sf.openrocket.simulation.customexpression;
+
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+import net.sf.openrocket.document.OpenRocketDocument;
+import net.sf.openrocket.rocketcomponent.Rocket;
+
+public class TestExpressions {
+
+       @Test
+       public void testExpressions() {
+               // TODO Auto-generated constructor stub
+               
+               OpenRocketDocument doc = new OpenRocketDocument(new Rocket());
+               
+               //CustomExpression exp = new CustomExpression(doc, "Kinetic energy", "Ek", "J", ".5*m*Vt^2");
+               
+               CustomExpression exp = new CustomExpression(doc, "Average mass", "Mavg", "kg", "mean(m[0:t])");
+               System.out.println( exp.getExpressionString() );
+               
+       }
+}
index f11ba89c3797d98ffe590853929b4e2412bfef80..6b9c1e1ad7841dd883da808e1ddfb6d2b0dcd773 100644 (file)
@@ -13,6 +13,23 @@ public class MathUtilTest {
        
        public static final double EPS = 0.00000000001;
        
+       /*
+       @Test 
+       public void rangeTest() {
+               double[] a;
+               
+               a = MathUtil.range(0, 10, 2);
+               assertEquals(0, a[0], 0);
+               assertEquals(10, a[5], 0);
+               assertEquals(6, a.length, 0);
+               
+               a = MathUtil.range(1, 2, 2);
+               assertEquals(1, a[0], 0);
+               assertEquals(1, a.length, 0);
+               
+       }
+       */
+       
        @Test
        public void miscMathTest() {