Added new feature : user configurable custom expression evaluation for the simulation...
authorrichardgraham <richardgraham@180e2498-e6e9-4542-8430-84ac67f01cd8>
Sat, 2 Jun 2012 17:58:47 +0000 (17:58 +0000)
committerrichardgraham <richardgraham@180e2498-e6e9-4542-8430-84ac67f01cd8>
Sat, 2 Jun 2012 17:58:47 +0000 (17:58 +0000)
git-svn-id: https://openrocket.svn.sourceforge.net/svnroot/openrocket/trunk@738 180e2498-e6e9-4542-8430-84ac67f01cd8

24 files changed:
core/resources/l10n/messages.properties
core/resources/pix/icons/copyright.txt
core/resources/pix/icons/down.png [new file with mode: 0644]
core/resources/pix/icons/pencil.png [new file with mode: 0644]
core/resources/pix/icons/up.png [new file with mode: 0644]
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/gui/customexpression/CustomExpressionPanel.java [new file with mode: 0644]
core/src/net/sf/openrocket/gui/customexpression/ExpressionBuilderDialog.java [new file with mode: 0644]
core/src/net/sf/openrocket/gui/customexpression/OperatorSelector.java [new file with mode: 0644]
core/src/net/sf/openrocket/gui/customexpression/OperatorTableModel.java [new file with mode: 0644]
core/src/net/sf/openrocket/gui/customexpression/VariableSelector.java [new file with mode: 0644]
core/src/net/sf/openrocket/gui/customexpression/VariableTableModel.java [new file with mode: 0644]
core/src/net/sf/openrocket/gui/main/SimulationEditDialog.java
core/src/net/sf/openrocket/gui/util/Icons.java
core/src/net/sf/openrocket/simulation/BasicEventSimulationEngine.java
core/src/net/sf/openrocket/simulation/CustomExpression.java [new file with mode: 0644]
core/src/net/sf/openrocket/simulation/FlightDataBranch.java
core/src/net/sf/openrocket/simulation/FlightDataType.java
core/src/net/sf/openrocket/simulation/SimulationConditions.java
core/src/net/sf/openrocket/simulation/SimulationOptions.java
core/src/net/sf/openrocket/simulation/listeners/example/RollControlListener.java
core/src/net/sf/openrocket/unit/FixedUnitGroup.java [new file with mode: 0644]

index c7a472bbb4caa4d1fa44ff253e4a59c475f273cf..2e5ba18c116122f3504d029fdb82ed0860ceb877 100644 (file)
@@ -89,7 +89,6 @@ dlg.but.ok = OK
 dlg.but.cancel = Cancel
 dlg.but.close = Close
 
-
 ! General file type names
 filetypes.pdf = PDF files (*.pdf)
 BasicFrame.SimpleFileFilter1 = All rocket designs (*.ork; *.rkt)
@@ -286,6 +285,7 @@ simedtdlg.lbl.Simname = Simulation name:
 simedtdlg.tab.Launchcond = Launch conditions
 simedtdlg.tab.Simopt = Simulation options
 simedtdlg.tab.Plotdata = Plot data
+simedtdlg.tab.CustomExpressions = Custom expressions
 simedtdlg.tab.Exportdata = Export data
 simedtdlg.lbl.Motorcfg = Motor configuration:
 simedtdlg.lbl.ttip.Motorcfg = Select the motor configuration to use.
@@ -444,6 +444,37 @@ CsvOptionPanel.separator.space = SPACE
 CsvOptionPanel.separator.tab = TAB
 
 
+! Custom expression general stuff
+customExpression.Name = Name
+customExpression.Symbol = Symbol
+customExpression.Expression = Expression
+customExpression.Units = Units
+customExpression.Operator = Operator
+customExpression.Description = Description
+
+! Custom expression panel
+customExpressionPanel.but.NewExpression = New expression
+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 :
+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
+ExpressionBuilderDialog.InsertOperator = Insert Operator
+ExpressionBuilderDialog.led.ttip.Name = Name must not have already been used
+ExpressionBuilderDialog.led.ttip.Symbol = Symbol must not have already been used
+ExpressionBuilderDialog.led.ttip.Expression = Expression must use only known symbols and operators
+
+! Custom expression variable selector
+CustomVariableSelector.title = Variable Selector
+
+! Custom operator selector
+CustomOperatorSelector.title = Operator Selector
 
 ! MotorPlot
 MotorPlot.title.Motorplot = Motor plot
@@ -457,8 +488,6 @@ MotorPlot.txt.Type = Type:
 MotorPlot.txt.Delays = Delays:
 MotorPlot.txt.Comment = Comment:\n
 
-
-
 ! Simulation plot panel
 simplotpanel.lbl.Presetplotconf = Preset plot configurations:
 simplotpanel.lbl.Xaxistype = X axis type:
@@ -481,7 +510,6 @@ simplotpanel.CUSTOM = Custom
 SimulationPlotPanel.error.noPlotSelected = Please add one or more variables to plot on the Y-axis.
 SimulationPlotPanel.error.noPlotSelected.title = Nothing to plot
 
-
 ! Component add buttons
 compaddbuttons.Bodycompandfinsets = Body components and fin sets
 compaddbuttons.Nosecone = Nose cone
index 7320b9501e4837033a5fbff2b5fd64220c18bbb5..809fdd681fa4a849d84fd8baf7c43bb925a1d65a 100644 (file)
@@ -20,6 +20,9 @@ edit-delete.png
 edit-paste.png
 edit-redo.png
 edit-undo.png
+down.png
+pencil.png
+up.png
 edit-scale.png (modified from edit-copy.png)
 
 
diff --git a/core/resources/pix/icons/down.png b/core/resources/pix/icons/down.png
new file mode 100644 (file)
index 0000000..f3bc4cd
Binary files /dev/null and b/core/resources/pix/icons/down.png differ
diff --git a/core/resources/pix/icons/pencil.png b/core/resources/pix/icons/pencil.png
new file mode 100644 (file)
index 0000000..5b8cc89
Binary files /dev/null and b/core/resources/pix/icons/pencil.png differ
diff --git a/core/resources/pix/icons/up.png b/core/resources/pix/icons/up.png
new file mode 100644 (file)
index 0000000..184c118
Binary files /dev/null and b/core/resources/pix/icons/up.png differ
index 74d3ab28ed517b03f9d2163f95b999f47054ffde..33a4866e2ccee10e5c1bac9c8171df199f7b16f7 100644 (file)
@@ -13,6 +13,7 @@ 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;
@@ -70,13 +71,12 @@ 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;
        private Class<? extends AerodynamicCalculator> aerodynamicCalculatorClass = BarrowmanCalculator.class;
        private Class<? extends MassCalculator> massCalculatorClass = BasicMassCalculator.class;
-       
-
 
        /** Listeners for this object */
        private List<EventListener> listeners = new ArrayList<EventListener>();
@@ -148,6 +148,20 @@ public class Simulation implements ChangeSource, Cloneable {
                
        }
        
+       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.
@@ -280,6 +294,7 @@ public class Simulation implements ChangeSource, Cloneable {
                        }
                        
                        SimulationConditions simulationConditions = options.toSimulationConditions();
+                       simulationConditions.setSimulation(this);
                        for (SimulationListener l : additionalListeners) {
                                simulationConditions.getSimulationListenerList().add(l);
                        }
index 0525a4ed58be061f68cbbe89071db7d130cb7a01..ac061150e1f16788eb7114f3dd311cc65ce7979b 100644 (file)
@@ -24,6 +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.FlightData;
 import net.sf.openrocket.simulation.FlightDataBranch;
 import net.sf.openrocket.simulation.FlightDataType;
@@ -322,10 +323,25 @@ public class OpenRocketSaver extends RocketSaver {
                
                writeln("<name>" + escapeXML(simulation.getName()) + "</name>");
                // TODO: MEDIUM: Other simulators/calculators
+               
                writeln("<simulator>RK4Simulator</simulator>");
                writeln("<calculator>BarrowmanCalculator</calculator>");
-               writeln("<conditions>");
-               indent++;
+               
+               // 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());
                writeElement("launchrodlength", cond.getLaunchRodLength());
@@ -359,7 +375,6 @@ public class OpenRocketSaver extends RocketSaver {
                        writeElement("listener", escapeXML(s));
                }
                
-               
                // Write basic simulation data
                
                FlightData data = simulation.getSimulatedData();
index 6a112f55d63df91dd353e7f7d99b83a1d9331e9e..a56f0d78e08b4f649c3a86a2e3c522426c5ff837 100644 (file)
@@ -67,6 +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.FlightData;
 import net.sf.openrocket.simulation.FlightDataBranch;
 import net.sf.openrocket.simulation.FlightDataType;
@@ -1201,6 +1202,8 @@ class SimulationsHandler extends AbstractElementHandler {
 }
 
 class SingleSimulationHandler extends AbstractElementHandler {
+       private static final LogHelper log = Application.getLogger();
+
        private final DocumentLoadingContext context;
 
        private final OpenRocketDocument doc;
@@ -1209,7 +1212,9 @@ 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) {
@@ -1217,7 +1222,9 @@ class SingleSimulationHandler extends AbstractElementHandler {
                this.context = context;
        }
 
-
+       public void setCustomExpressions(ArrayList<CustomExpression> expressions){
+               this.customExpressions = expressions;
+       }
 
        @Override
        public ElementHandler openElement(String element, HashMap<String, String> attributes,
@@ -1226,6 +1233,9 @@ 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;
@@ -1288,13 +1298,70 @@ class SingleSimulationHandler extends AbstractElementHandler {
 
                Simulation simulation = new Simulation(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;
@@ -1605,7 +1672,8 @@ class FlightDataBranchHandler extends AbstractElementHandler {
                String[] split = typeList.split(",");
                types = new FlightDataType[split.length];
                for (int i = 0; i < split.length; i++) {
-                       types[i] = FlightDataType.getType(split[i], UnitGroup.UNITS_NONE);
+                       types[i] = FlightDataType.getType(split[i], "None ("+split[i]+")", UnitGroup.UNITS_NONE);
+                       // TODO: HIGH: Deal with symbols
                }
 
                // TODO: LOW: May throw an IllegalArgumentException
diff --git a/core/src/net/sf/openrocket/gui/customexpression/CustomExpressionPanel.java b/core/src/net/sf/openrocket/gui/customexpression/CustomExpressionPanel.java
new file mode 100644 (file)
index 0000000..e0bd236
--- /dev/null
@@ -0,0 +1,195 @@
+package net.sf.openrocket.gui.customexpression;
+
+import java.awt.Color;
+import java.awt.Window;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.util.ArrayList;
+import java.util.Collections;
+
+import javax.swing.BorderFactory;
+import javax.swing.JButton;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.SwingUtilities;
+
+import net.miginfocom.swing.MigLayout;
+import net.sf.openrocket.document.Simulation;
+import net.sf.openrocket.gui.components.DescriptionArea;
+import net.sf.openrocket.gui.components.UnitSelector;
+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.startup.Application;
+
+public class CustomExpressionPanel extends JPanel {
+       
+       private static final LogHelper log = Application.getLogger();
+       private static final Translator trans = Application.getTranslator();
+       
+       private JPanel expressionSelectorPanel;
+       private Simulation simulation;
+       
+       public CustomExpressionPanel(final Simulation simulation) {
+               super(new MigLayout("fill"));
+               this.simulation = simulation;
+
+               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");
+               
+               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");
+               
+               //// New expression
+               JButton button = new JButton(trans.get("customExpressionPanel.but.NewExpression"));
+               button.addActionListener(new ActionListener() {
+                       @Override
+                       public void actionPerformed(ActionEvent e) {
+                               // Open window to configure expression
+                               log.debug("Opening window to configure new expression");
+                               Window parent = SwingUtilities.getWindowAncestor(CustomExpressionPanel.this);
+                               new ExpressionBuilderDialog(parent, simulation).setVisible(true);
+                               updateExpressions();
+                       }
+               });
+                               
+               this.add(button, "left");
+               
+               updateExpressions();
+       }
+       
+       /*
+        * Update the expressionSelectorPanel
+        */
+       private void updateExpressions(){
+               
+               expressionSelectorPanel.removeAll();
+               for (CustomExpression expression : simulation.getCustomExpressions()){
+                       SingleExpression se = new SingleExpression(expression);
+                       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
+               
+               
+       }
+       
+       private void deleteExpression(CustomExpression expression){
+               simulation.getCustomExpressions().remove(expression);
+       }
+       
+       /**
+        * Moves an expression up or down in the expression list
+        * @param expression
+        * @param move integer - +1 to move down, -1 to move up
+        */
+       private void moveExpression(CustomExpression expression, int move){
+               ArrayList<CustomExpression> expressions = simulation.getCustomExpressions();
+               int i = expressions.indexOf(expression);
+               if (i+move == expressions.size() || i+move < 0)
+                       return;
+               else
+                       Collections.swap(expressions, i, i+move);
+       }
+
+       
+       /*
+        * A JPanel which configures a single expression
+        */
+       private class SingleExpression extends JPanel {
+               
+               // Convenience method to make the labels consistent
+               private JLabel setLabelStyle(JLabel l){
+                       l.setBackground(Color.WHITE);
+                       l.setOpaque(true);
+                       l.setBorder(BorderFactory.createRaisedBevelBorder() );
+                       l.setText(" " + l.getText() + " ");
+                       return l;
+               }
+               
+               private SingleExpression(final CustomExpression expression) {
+                       super(new MigLayout("ins 0"));
+                       //                      name:    aName    symbol:  a      Unit:  m/s
+                       //super(new MigLayout("","[::100][:200:400][::100][:100:200][::100][:100:200]",""));
+                       
+                       JLabel nameLabel = new JLabel( trans.get("customExpression.Name")+ " :");
+                       JLabel name = new JLabel ( expression.getName() );
+                       name = setLabelStyle(name);
+                       JLabel symbolLabel = new JLabel( trans.get("customExpression.Symbol")+ " :" );
+                       JLabel symbol = new JLabel ( expression.getSymbol());
+                       symbol = setLabelStyle(symbol);
+                       symbol.setBackground(Color.WHITE);
+                       
+                       JLabel unitLabel = new JLabel( trans.get("customExpression.Units")+ " :");
+                       UnitSelector unitSelector = new UnitSelector(expression.getType().getUnitGroup());
+                       
+                       JButton editButton = new JButton(Icons.EDIT);
+                       editButton.setToolTipText(trans.get("customExpression.Units.but.ttip.Edit"));
+                       editButton.setBorderPainted(false);
+                       editButton.addActionListener( new ActionListener() {
+                               @Override
+                               public void actionPerformed(ActionEvent e){
+                                       Window parent = SwingUtilities.getWindowAncestor(CustomExpressionPanel.this);
+                                       expression.editExpression(parent);
+                                       updateExpressions();
+                               }
+                       });
+                       
+                       JButton upButton = new JButton(Icons.UP);
+                       upButton.setToolTipText(trans.get("customExpression.Units.but.ttip.MoveUp"));
+                       upButton.setBorderPainted(false);
+                       upButton.addActionListener( new ActionListener() {
+                               @Override
+                               public void actionPerformed(ActionEvent e) {
+                                       moveExpression(expression, -1);
+                                       updateExpressions();
+                               }
+                       });
+                       
+                       JButton downButton = new JButton(Icons.DOWN);
+                       downButton.setToolTipText(trans.get("customExpression.Units.but.ttip.MoveDown"));
+                       downButton.setBorderPainted(false);
+                       downButton.addActionListener( new ActionListener() {
+                               @Override
+                               public void actionPerformed(ActionEvent e) {
+                                       moveExpression(expression, 1);
+                                       updateExpressions();
+                               }
+                       });
+                       
+                       
+                       JButton deleteButton = new JButton(Icons.DELETE);
+                       //// Remove this expression
+                       deleteButton.setToolTipText(trans.get("customExpression.Units.but.ttip.Remove"));
+                       deleteButton.setBorderPainted(false);
+                       deleteButton.addActionListener(new ActionListener() {
+                               @Override
+                               public void actionPerformed(ActionEvent e) {
+                                       deleteExpression(expression);
+                                       updateExpressions();
+                               }
+                       });
+                       
+                       this.add(nameLabel);
+                       this.add(name, "width 200:200:400, growx");
+                       this.add(new JPanel());
+                       this.add(symbolLabel);
+                       this.add(symbol, "width :50:200");
+                       this.add(new JPanel());
+                       this.add(unitLabel);
+                       this.add(unitSelector, "width :50:100");
+                       this.add(new JPanel(), "growx");
+                       this.add(upButton, "right");
+                       this.add(downButton, "right");
+                       this.add(editButton, "right");
+                       this.add(deleteButton, "right");
+               }
+       }
+}
diff --git a/core/src/net/sf/openrocket/gui/customexpression/ExpressionBuilderDialog.java b/core/src/net/sf/openrocket/gui/customexpression/ExpressionBuilderDialog.java
new file mode 100644 (file)
index 0000000..0c25cbf
--- /dev/null
@@ -0,0 +1,266 @@
+package net.sf.openrocket.gui.customexpression;
+
+import java.awt.Window;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.FocusEvent;
+import java.awt.event.FocusListener;
+import java.awt.event.KeyEvent;
+import java.awt.event.KeyListener;
+
+import javax.swing.ImageIcon;
+import javax.swing.JButton;
+import javax.swing.JDialog;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.JTextField;
+import javax.swing.SwingUtilities;
+
+import net.miginfocom.swing.MigLayout;
+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.startup.Application;
+
+/**
+ * Dialog box for making a custom expression
+ * @author Richard Graham
+ *
+ */
+
+public class ExpressionBuilderDialog extends JDialog {
+
+       private static final Translator trans = Application.getTranslator();
+       private static final LogHelper log = Application.getLogger();
+       
+       private static final ImageIcon GreenIcon = Icons.loadImageIcon("pix/spheres/green-16x16.png", "OK");
+       private static final ImageIcon RedIcon = Icons.loadImageIcon("pix/spheres/red-16x16.png", "Bad");
+       
+       private CustomExpression expression;
+       private CustomExpression previousExpressionCopy;
+       
+       private final Window parentWindow;
+       private final Simulation simulation;
+       
+       // Define these check indicators to show if fields are OK
+       private final JLabel nameCheck = new JLabel(RedIcon);
+       private final JLabel expressionCheck = new JLabel(RedIcon);
+       private final JLabel unitCheck = new JLabel(RedIcon);
+       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, final Simulation simulation, final CustomExpression previousExpression){
+               
+               super(parent, trans.get("ExpressionBuilderDialog.title"), JDialog.ModalityType.DOCUMENT_MODAL);
+               
+               this.parentWindow = parent;
+               this.simulation = simulation;
+               this.previousExpressionCopy = (CustomExpression) previousExpression.clone();
+               this.expression = previousExpression;
+                                       
+               //// Name box -- Check input when focus changes and transfer focus to next box on enter key
+               JLabel nameLabel = new JLabel(trans.get("customExpression.Name"));
+               final JTextField nameField = new JTextField(20); 
+               nameField.setText(expression.getName());
+               nameField.setFocusTraversalKeysEnabled(true);
+               nameField.addFocusListener(new FocusListener() {
+                       @Override
+                       public void focusGained(FocusEvent e) { }
+
+                       @Override
+                       public void focusLost(FocusEvent e) {
+                               expression.setName(nameField.getText());
+                               ExpressionBuilderDialog.this.updateOK();                                
+                       }
+               });
+               nameField.addActionListener(new ActionListener() {
+                       @Override
+                       public void actionPerformed(ActionEvent arg0) {
+                               nameField.transferFocus();
+                       }
+               });
+               
+               //// Expression box -- for this one we check after each keypress using a keyListener. Enter transfers to next field
+               JLabel expressionLabel = new JLabel(trans.get("customExpression.Expression"));
+               expressionField.setText(expression.getExpressionString());
+               expressionField.addKeyListener(new KeyListener() {
+                       @Override
+                       public void keyReleased(KeyEvent arg0) {
+                               expression.setExpression(  expressionField.getText() );
+                               ExpressionBuilderDialog.this.updateOK();
+                       }
+
+                       @Override
+                       public void keyPressed(KeyEvent e) {}
+
+                       @Override
+                       public void keyTyped(KeyEvent e) {}
+               });
+               expressionField.addActionListener(new ActionListener(){
+                       @Override
+                       public void actionPerformed(ActionEvent e) {
+                               expressionField.transferFocus();
+                       }
+               });
+               
+               //// Units box -- with action listeners checking input after change in focus or enter press
+               JLabel unitLabel = new JLabel(trans.get("customExpression.Units"));
+               final JTextField unitField = new JTextField(5);
+               unitField.setText(expression.getUnit());
+               unitField.addFocusListener(new FocusListener(){
+                       @Override
+                       public void focusLost(FocusEvent arg0) { 
+                               expression.setUnit(unitField.getText()) ;
+                               ExpressionBuilderDialog.this.updateOK();
+                       }
+                       @Override
+                       public void focusGained(FocusEvent arg0) {}                     
+               });
+               unitField.addActionListener(new ActionListener() {
+                       @Override
+                       public void actionPerformed(ActionEvent e) {
+                               unitField.transferFocus();
+                       }
+               });
+               
+               //// Symbol box
+               JLabel symbolLabel = new JLabel(trans.get("customExpression.Symbol"));
+               final JTextField symbolField = new JTextField(5);
+               symbolField.setText(expression.getSymbol());
+               symbolField.addFocusListener(new FocusListener(){
+                       @Override
+                       public void focusLost(FocusEvent arg0) { 
+                               expression.setSymbol(symbolField.getText()) ;
+                               ExpressionBuilderDialog.this.updateOK();
+                       }
+                       @Override
+                       public void focusGained(FocusEvent arg0) {}                     
+               });
+               symbolField.addActionListener(new ActionListener() {
+                       @Override
+                       public void actionPerformed(ActionEvent e) {
+                               symbolField.transferFocus();
+                       }
+               });
+               
+               
+               //// Insert variable button
+               final JButton insertVariableButton = new JButton(trans.get("ExpressionBuilderDialog.InsertVariable"));
+               insertVariableButton.addActionListener(new ActionListener() {
+                       @Override
+                       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);
+                       }
+               });
+               
+               //// Insert operator button
+               final JButton insertOperatorButton = new JButton(trans.get("ExpressionBuilderDialog.InsertOperator"));
+               insertOperatorButton.addActionListener(new ActionListener() {
+                       @Override
+                       public void actionPerformed(ActionEvent e) {
+                               log.debug("Opening insert operator window");
+                               Window parentWindow = SwingUtilities.getWindowAncestor(ExpressionBuilderDialog.this);
+                               new OperatorSelector(parentWindow, ExpressionBuilderDialog.this).setVisible(true);
+                       }
+               });
+               
+               
+               //// OK Button
+               okButton.setEnabled(false);
+               okButton.addActionListener(new ActionListener() {
+                       @Override
+                       public void actionPerformed(ActionEvent e) {
+                               expression.addToSimulation();
+                               ExpressionBuilderDialog.this.dispose();
+                       }
+               });
+
+               //// Cancel button
+               final JButton cancelButton = new JButton(trans.get("dlg.but.cancel"));
+               cancelButton.addActionListener(new ActionListener() {
+                       @Override
+                       public void actionPerformed(ActionEvent e) {
+                               expression.overwrite(previousExpressionCopy);
+                               ExpressionBuilderDialog.this.dispose();
+                       }
+               });
+                       
+               //// Set to tips
+               nameCheck.setToolTipText(trans.get("ExpressionBuilderDialog.led.ttip.Name"));
+               unitCheck.setToolTipText(trans.get("ExpressionBuilderDialog.led.ttip.Symbol"));
+               expressionCheck.setToolTipText(trans.get("ExpressionBuilderDialog.led.ttip.Expression"));
+               
+               //// Do the layout
+               JPanel mainPanel = new JPanel(new MigLayout());
+               mainPanel.add(nameLabel);
+               mainPanel.add(nameField);
+               mainPanel.add(nameCheck, "wrap, center");
+               mainPanel.add(symbolLabel);
+               mainPanel.add(symbolField, "split 4, growx");
+               mainPanel.add(new JPanel());
+               mainPanel.add(unitLabel, "right");
+               mainPanel.add(unitField, "right, growx");
+               mainPanel.add(unitCheck, "wrap, center");
+               mainPanel.add(expressionLabel);
+               mainPanel.add(expressionField);
+               mainPanel.add(expressionCheck, "wrap, center");
+               mainPanel.add(insertOperatorButton, "span 2, right, split 2");
+               mainPanel.add(insertVariableButton, "right, wrap");
+               mainPanel.add(cancelButton, "span 2, right, width :50:100");
+               mainPanel.add(okButton, "right, width :50:100, wrap");
+
+               this.add(mainPanel);
+               this.validate();
+               this.pack();
+               this.setLocationByPlatform(true);
+               this.updateOK();
+               
+       }
+
+       /**
+        * Enable OK button only if all the fields are ok
+        * @param okButton
+        */
+       protected void updateOK() {
+               
+               boolean nameOK = expression.checkName();
+               boolean unitOK = expression.checkUnit();
+               boolean symbolOK = expression.checkSymbol();
+               boolean expressionOK = expression.checkExpression();
+               
+               if (nameOK)                             { nameCheck.setIcon(GreenIcon);                 } else { nameCheck.setIcon(RedIcon); }
+               if (unitOK && symbolOK) { unitCheck.setIcon(GreenIcon);                 } else { unitCheck.setIcon(RedIcon); }
+               if (expressionOK)               { expressionCheck.setIcon(GreenIcon);   } else { expressionCheck.setIcon(RedIcon); }
+               
+               okButton.setEnabled( nameOK && unitOK && symbolOK && expressionOK );
+       }
+       
+       /**
+        * Inserts a string into the expression box at the position of the cursor.
+        * String will be padded with spaces either side
+        * Expression box will be focused after this is called.
+        * For strings containing an ( , cursor will be moved to the point after that, otherwise, cursor will move to the end of the inserted string.
+        * @param str
+        */
+       public void pasteIntoExpression(String str) {
+           int pos = expressionField.getCaretPosition();
+           String current = expressionField.getText();
+           expressionField.setText(current.subSequence(0, pos) + " " + str + " " + current.subSequence(pos, current.length()));
+           expressionField.requestFocus();
+           int bracketPos = str.indexOf("(");
+           if (bracketPos != -1){
+               expressionField.setCaretPosition(pos+2+bracketPos);
+           }
+           else {
+               expressionField.setCaretPosition(pos+2+str.length());
+           }
+       }
+}
diff --git a/core/src/net/sf/openrocket/gui/customexpression/OperatorSelector.java b/core/src/net/sf/openrocket/gui/customexpression/OperatorSelector.java
new file mode 100644 (file)
index 0000000..bc1f806
--- /dev/null
@@ -0,0 +1,90 @@
+package net.sf.openrocket.gui.customexpression;
+
+import java.awt.Window;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+
+import javax.swing.JButton;
+import javax.swing.JDialog;
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.JTable;
+import javax.swing.event.ListSelectionEvent;
+import javax.swing.event.ListSelectionListener;
+
+import net.miginfocom.swing.MigLayout;
+import net.sf.openrocket.l10n.Translator;
+import net.sf.openrocket.logging.LogHelper;
+import net.sf.openrocket.startup.Application;
+
+public class OperatorSelector extends JDialog {
+       
+       private static final Translator trans = Application.getTranslator();
+       private static final LogHelper log = Application.getLogger();
+
+       private final Window parentWindow;
+       
+       public OperatorSelector(Window parent, final ExpressionBuilderDialog parentBuilder){
+               
+               super(parent, trans.get("CustomOperatorSelector.title"), JDialog.ModalityType.DOCUMENT_MODAL);
+               
+               this.parentWindow = parent;
+               
+               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);
+               
+               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));
+               
+               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);
+                                       }
+                               }
+                       });
+               
+               mainPanel.add(scrollPane, "wrap");
+               
+               //// Cancel button
+               final JButton cancelButton = new JButton(trans.get("dlg.but.cancel"));
+               cancelButton.addActionListener(new ActionListener() {
+                       @Override
+                       public void actionPerformed(ActionEvent e) {
+                               OperatorSelector.this.dispose();
+                       }
+               });
+               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.getOperatorAt(row);
+                               parentBuilder.pasteIntoExpression(str);
+                               OperatorSelector.this.dispose();
+                       }
+               });
+               insertButton.setEnabled(false); // disabled by default, only enable when a variable selected
+               mainPanel.add(insertButton, "right, width :100:200, wrap");
+
+               this.add(mainPanel);
+               this.validate();
+               this.pack();
+               this.setLocationByPlatform(true);       
+       }
+}
diff --git a/core/src/net/sf/openrocket/gui/customexpression/OperatorTableModel.java b/core/src/net/sf/openrocket/gui/customexpression/OperatorTableModel.java
new file mode 100644 (file)
index 0000000..76a1a8f
--- /dev/null
@@ -0,0 +1,52 @@
+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.startup.Application;
+
+public class OperatorTableModel extends AbstractTableModel {
+
+       private static final Translator trans = Application.getTranslator();
+       
+       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();
+       
+       public OperatorTableModel(){
+               
+       }
+       
+       @Override
+       public int getColumnCount() {
+               return 2;
+       }
+
+       @Override
+       public int getRowCount() {
+               return CustomExpression.AVAILABLE_OPERATORS.size();
+       }
+
+       @Override
+       public Object getValueAt(int row, int col) {
+               if (col == 0){
+                       return operators[row].toString();
+               }
+               else if (col == 1){
+                       return descriptions[row].toString();
+               }
+               return null;
+       }
+       
+       @Override
+       public String getColumnName(int col) {
+        return columnNames[col];
+    }
+       
+       public String getOperatorAt(int row) {
+               return operators[row].toString();
+       }
+
+}
diff --git a/core/src/net/sf/openrocket/gui/customexpression/VariableSelector.java b/core/src/net/sf/openrocket/gui/customexpression/VariableSelector.java
new file mode 100644 (file)
index 0000000..d432a18
--- /dev/null
@@ -0,0 +1,100 @@
+package net.sf.openrocket.gui.customexpression;
+
+import java.awt.Window;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+
+import javax.swing.JButton;
+import javax.swing.JDialog;
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.JTable;
+import javax.swing.event.ListSelectionEvent;
+import javax.swing.event.ListSelectionListener;
+
+import net.miginfocom.swing.MigLayout;
+import net.sf.openrocket.document.Simulation;
+import net.sf.openrocket.l10n.Translator;
+import net.sf.openrocket.logging.LogHelper;
+import net.sf.openrocket.startup.Application;
+
+/**
+ * Dialog to select from available custom variables
+ * @author Richard Graham
+ *
+ */
+
+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){
+               
+               super(parent, trans.get("CustomVariableSelector.title"), JDialog.ModalityType.DOCUMENT_MODAL);
+               
+               this.parentWindow = parent;
+               this.simulation = simulation;
+               
+               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);
+               
+               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));
+               
+               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);
+                                       }
+                               }
+                       });
+               
+               mainPanel.add(scrollPane, "wrap");
+               
+               //// Cancel button
+               final JButton cancelButton = new JButton(trans.get("dlg.but.cancel"));
+               cancelButton.addActionListener(new ActionListener() {
+                       @Override
+                       public void actionPerformed(ActionEvent e) {
+                               VariableSelector.this.dispose();
+                       }
+               });
+               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();
+                       }
+               });
+               insertButton.setEnabled(false); // disabled by default, only enable when a variable selected
+               mainPanel.add(insertButton, "right, width :100:200, wrap");
+
+               this.add(mainPanel);
+               this.validate();
+               this.pack();
+               this.setLocationByPlatform(true);       
+       }
+}
diff --git a/core/src/net/sf/openrocket/gui/customexpression/VariableTableModel.java b/core/src/net/sf/openrocket/gui/customexpression/VariableTableModel.java
new file mode 100644 (file)
index 0000000..b81a6b0
--- /dev/null
@@ -0,0 +1,77 @@
+/**
+ * 
+ */
+package net.sf.openrocket.gui.customexpression;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+
+import javax.swing.table.AbstractTableModel;
+
+import net.sf.openrocket.document.Simulation;
+import net.sf.openrocket.l10n.Translator;
+import net.sf.openrocket.simulation.CustomExpression;
+import net.sf.openrocket.simulation.FlightDataType;
+import net.sf.openrocket.startup.Application;
+
+/**
+ * @author Richard Graham
+ *
+ */
+public class VariableTableModel extends AbstractTableModel {
+
+       private static final Translator trans = Application.getTranslator();
+
+       private ArrayList<FlightDataType> types = new ArrayList<FlightDataType>();
+       private static final String[] columnNames = {trans.get("customExpression.Name"), trans.get("customExpression.Symbol"), trans.get("customExpression.Units")};
+       
+       /*
+        * Table model will be constructed with all the built in variables and any custom variables defined
+        */
+       public VariableTableModel(Simulation sim){
+               
+               Collections.addAll(types, FlightDataType.ALL_TYPES);
+               
+               for (CustomExpression expression : sim.getCustomExpressions()){
+                       types.add(expression.getType());
+               }
+               
+       }
+       
+       @Override
+       public int getColumnCount() {
+               return 3;
+       }
+
+       @Override
+       public int getRowCount() {
+               return types.size();
+       }
+
+       @Override
+       public Object getValueAt(int row, int col) {
+               if (col == 0)
+                       return types.get(row).getName();
+               else if (col == 1)
+                       return types.get(row).getSymbol();
+               else if (col == 2)
+                       return types.get(row).getUnitGroup().getDefaultUnit().toString();
+               
+               return null;
+       }
+       
+       @Override
+       public String getColumnName(int col) {
+        return columnNames[col];
+    }
+       
+       public String getSymbolAt(int row) {
+               if (row < 0 || row > types.size()){
+                       return "";
+               }
+               else { 
+                       return types.get(row).getSymbol();
+               }
+       }
+}
index cb9e0d34b5c74dd3285b5cf80a6918e6132f2ce4..daba47e716944c9405693f6891c65811d9d11320 100644 (file)
@@ -41,6 +41,7 @@ import net.sf.openrocket.gui.components.DescriptionArea;
 import net.sf.openrocket.gui.components.SimulationExportPanel;
 import net.sf.openrocket.gui.components.UnitSelector;
 import net.sf.openrocket.gui.plot.Axis;
+import net.sf.openrocket.gui.customexpression.CustomExpressionPanel;
 import net.sf.openrocket.gui.plot.PlotConfiguration;
 import net.sf.openrocket.gui.plot.SimulationPlotPanel;
 import net.sf.openrocket.gui.util.GUIUtil;
@@ -77,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 = 2;
+       public static final int PLOT = 3;
        
 
        private final Window parentWindow;
@@ -138,6 +139,8 @@ 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
@@ -147,7 +150,7 @@ public class SimulationEditDialog extends JDialog {
                if (tab == EDIT) {
                        tabbedPane.setSelectedIndex(0);
                } else if (tab == PLOT) {
-                       tabbedPane.setSelectedIndex(2);
+                       tabbedPane.setSelectedIndex(3);
                } else {
                        FlightData data = s.getSimulatedData();
                        if (data == null || data.getBranchCount() == 0)
@@ -835,7 +838,9 @@ public class SimulationEditDialog extends JDialog {
        }
        
        
-
+       private JPanel customExpressionsTab() {
+               return new CustomExpressionPanel(simulation);
+       }
 
 
        /**
index 8f304b6324a9cb2b056e343b7936b733dabae96a..b202ff4e881f141f608921c5a17a37e91038989d 100644 (file)
@@ -73,6 +73,10 @@ public class Icons {
        public static final Icon PREFERENCES = loadImageIcon("pix/icons/preferences.png", "Preferences");
        
        public static final Icon DELETE = loadImageIcon("pix/icons/delete.png", "Delete");
+       public static final Icon EDIT = loadImageIcon("pix/icons/pencil.png", "Edit");
+       public static final Icon UP = loadImageIcon("pix/icons/up.png", "Up");
+       public static final Icon DOWN = loadImageIcon("pix/icons/down.png", "Down");
+       
        
        static {
                log.debug("Icons loaded");
index 305516ad78b0a8ff122064086258ae245f642250..b0139fbbd35f684bc20f2384b4c84b27bdc23e0b 100644 (file)
@@ -1,5 +1,6 @@
 package net.sf.openrocket.simulation;
 
+import java.util.ArrayList;
 import java.util.HashSet;
 import java.util.Iterator;
 import java.util.Set;
@@ -88,6 +89,12 @@ public class BasicEventSimulationEngine implements SimulationEngine {
                                }
                                SimulationListenerHelper.firePostStep(status);
                                
+                               // Calculate values for custom expressions
+                               FlightDataBranch data = status.getFlightData();
+                               ArrayList<CustomExpression> allExpressions = status.getSimulationConditions().getSimulation().getCustomExpressions();
+                               for (CustomExpression expression : allExpressions ) {
+                                       data.setValue(expression.getType(), expression.evaluate(status));
+                               }
                                
                                // Check for NaN values in the simulation status
                                checkNaN();
diff --git a/core/src/net/sf/openrocket/simulation/CustomExpression.java b/core/src/net/sf/openrocket/simulation/CustomExpression.java
new file mode 100644 (file)
index 0000000..d060c07
--- /dev/null
@@ -0,0 +1,317 @@
+package net.sf.openrocket.simulation;
+
+import java.awt.Window;
+import java.util.SortedMap;
+import java.util.TreeMap;
+
+import net.sf.openrocket.document.Simulation;
+import net.sf.openrocket.gui.customexpression.ExpressionBuilderDialog;
+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 String name, symbol, unit, expression;
+       private ExpressionBuilder builder;
+       private static 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("+"             , "Addition");
+           put("-"                     , "Subtraction");
+           put("*"                     , "Multiplication");
+           put("/"                     , "Divison");
+           put("%"                     , "Modulo");
+           put("^"                     , "Exponentiation");
+           put("abs()"         , "Absolute value");
+           put("ceil()"        , "Ceiling (next integer value");
+           put("floor()"       , "Floor (previous integer value");
+           put("sqrt()"        , "Square root");
+           put("cbrt()"        , "Cubic root");
+           put("exp()"         , "Euler\'s number raised to the value (e^x)");
+           put("log()"         , "Natural logarithm");
+           put("sin()"         , "Sine");
+           put("cos()"         , "Cosine");
+           put("tan()"         , "Tangent");
+           put("asin()"        , "Arc sine");
+           put("acos()"        , "Arc cosine");
+           put("atan()"        , "Arc tangent");
+           put("sinh()"        , "Hyerbolic sine");
+           put("cosh()"        , "Hyperbolic cosine");
+           put("tanh()"        , "Hyperbolic tangent");
+       }};  
+       
+       
+       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);
+       }
+       
+       /*
+        * Opens an ExpressionBuilderDialog for this expression 
+        */
+       public void editExpression(Window parent){
+               log.debug("Opening window to edit an existing custom expression");
+               new ExpressionBuilderDialog(parent, sim, this).setVisible(true);
+       }
+       
+       /*
+        * Use this to update the simulation this is associated with
+        */
+       public void setSimulation(Simulation sim){
+               CustomExpression.sim = 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().getBranch(0) == null) {
+                       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;
+               
+               ArrayList<String> names = getAllNames().clone();
+               if (names.contains(name.trim())){
+                       int index = names.indexOf(name.trim());
+                       log.user("Symbol "+symbol+" 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);
+               return FlightDataType.getType(name, symbol, ug);
+       }
+       
+       /*
+        * Add this expression to the simulation if not already added
+        */
+       public void addToSimulation(){
+               if (! sim.getCustomExpressions().contains(this))
+                       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);
+               }
+       }
+       
+       @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
+        * @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 f3eb3d9c751d1a7b0d6ef5faf6f3dff0e9ea22c3..475a068eecf96faf20dda54dfffb7ef6da733596 100644 (file)
@@ -68,7 +68,16 @@ public class FlightDataBranch implements Monitorable {
                }
        }
        
-       
+       /**
+        * Makes an 'empty' flight data branch which has no data but all built in data types are defined.
+        */
+       public FlightDataBranch() {
+               branchName = "Empty branch";
+               for (FlightDataType type : FlightDataType.ALL_TYPES){
+                       this.setValue(type, Double.NaN);
+               }
+               this.immute();
+       }
 
        /**
         * Adds a new point into the data branch.  The value for all types is set to NaN by default.
index 0e8b41a428a6d9b1d67d47370a9720378455d588..9a7df76420029953393c65401126f9c56b9a2d55 100644 (file)
@@ -33,152 +33,206 @@ public class FlightDataType implements Comparable<FlightDataType> {
        
        
        //// Time
-       public static final FlightDataType TYPE_TIME = newType(trans.get("FlightDataType.TYPE_TIME"), 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(trans.get("FlightDataType.TYPE_ALTITUDE"), UnitGroup.UNITS_DISTANCE, 10);
+       public static final FlightDataType TYPE_ALTITUDE = newType(trans.get("FlightDataType.TYPE_ALTITUDE"), "a", UnitGroup.UNITS_DISTANCE, 10);
        //// Vertical velocity
-       public static final FlightDataType TYPE_VELOCITY_Z = newType(trans.get("FlightDataType.TYPE_VELOCITY_Z"), 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(trans.get("FlightDataType.TYPE_ACCELERATION_Z"), 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(trans.get("FlightDataType.TYPE_VELOCITY_TOTAL"), 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(trans.get("FlightDataType.TYPE_ACCELERATION_TOTAL"), 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(trans.get("FlightDataType.TYPE_POSITION_X"), 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(trans.get("FlightDataType.TYPE_POSITION_Y"), 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(trans.get("FlightDataType.TYPE_POSITION_XY"), 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(trans.get("FlightDataType.TYPE_POSITION_DIRECTION"), UnitGroup.UNITS_ANGLE, 33);
+       public static final FlightDataType TYPE_POSITION_DIRECTION = newType(trans.get("FlightDataType.TYPE_POSITION_DIRECTION"), "θl", UnitGroup.UNITS_ANGLE, 33);
        //// Lateral velocity
-       public static final FlightDataType TYPE_VELOCITY_XY = newType(trans.get("FlightDataType.TYPE_VELOCITY_XY"), 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(trans.get("FlightDataType.TYPE_ACCELERATION_XY"), 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(trans.get("FlightDataType.TYPE_LATITUDE"), UnitGroup.UNITS_ANGLE, 36);
+       public static final FlightDataType TYPE_LATITUDE = newType(trans.get("FlightDataType.TYPE_LATITUDE"), "φ", UnitGroup.UNITS_ANGLE, 36);
        //// Longitude
-       public static final FlightDataType TYPE_LONGITUDE = newType(trans.get("FlightDataType.TYPE_LONGITUDE"), UnitGroup.UNITS_ANGLE, 37);
+       public static final FlightDataType TYPE_LONGITUDE = newType(trans.get("FlightDataType.TYPE_LONGITUDE"), "λ", UnitGroup.UNITS_ANGLE, 37);
        
        //// Angular motion
        //// Angle of attack
-       public static final FlightDataType TYPE_AOA = newType(trans.get("FlightDataType.TYPE_AOA"), UnitGroup.UNITS_ANGLE, 40);
+       public static final FlightDataType TYPE_AOA = newType(trans.get("FlightDataType.TYPE_AOA"), "α", UnitGroup.UNITS_ANGLE, 40);
        //// Roll rate
-       public static final FlightDataType TYPE_ROLL_RATE = newType(trans.get("FlightDataType.TYPE_ROLL_RATE"), UnitGroup.UNITS_ROLL, 41);
+       public static final FlightDataType TYPE_ROLL_RATE = newType(trans.get("FlightDataType.TYPE_ROLL_RATE"), "dΦ", UnitGroup.UNITS_ROLL, 41);
        //// Pitch rate
-       public static final FlightDataType TYPE_PITCH_RATE = newType(trans.get("FlightDataType.TYPE_PITCH_RATE"), UnitGroup.UNITS_ROLL, 42);
+       public static final FlightDataType TYPE_PITCH_RATE = newType(trans.get("FlightDataType.TYPE_PITCH_RATE"), "dθ", UnitGroup.UNITS_ROLL, 42);
        //// Yaw rate
-       public static final FlightDataType TYPE_YAW_RATE = newType(trans.get("FlightDataType.TYPE_YAW_RATE"), UnitGroup.UNITS_ROLL, 43);
+       public static final FlightDataType TYPE_YAW_RATE = newType(trans.get("FlightDataType.TYPE_YAW_RATE"), "dΨ", UnitGroup.UNITS_ROLL, 43);
        
        
        //// Stability information
        //// Mass
-       public static final FlightDataType TYPE_MASS = newType(trans.get("FlightDataType.TYPE_MASS"), 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(trans.get("FlightDataType.TYPE_LONGITUDINAL_INERTIA"), 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(trans.get("FlightDataType.TYPE_ROTATIONAL_INERTIA"), 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(trans.get("FlightDataType.TYPE_CP_LOCATION"), 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(trans.get("FlightDataType.TYPE_CG_LOCATION"), 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(trans.get("FlightDataType.TYPE_STABILITY"), 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(trans.get("FlightDataType.TYPE_MACH_NUMBER"), 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(trans.get("FlightDataType.TYPE_REYNOLDS_NUMBER"), 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(trans.get("FlightDataType.TYPE_THRUST_FORCE"), 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(trans.get("FlightDataType.TYPE_DRAG_FORCE"), 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(trans.get("FlightDataType.TYPE_DRAG_COEFF"), 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(trans.get("FlightDataType.TYPE_AXIAL_DRAG_COEFF"), 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(trans.get("FlightDataType.TYPE_FRICTION_DRAG_COEFF"), 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(trans.get("FlightDataType.TYPE_PRESSURE_DRAG_COEFF"), 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(trans.get("FlightDataType.TYPE_BASE_DRAG_COEFF"), 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(trans.get("FlightDataType.TYPE_NORMAL_FORCE_COEFF"), 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(trans.get("FlightDataType.TYPE_PITCH_MOMENT_COEFF"), UnitGroup.UNITS_COEFFICIENT, 91);
+       public static final FlightDataType TYPE_PITCH_MOMENT_COEFF = newType(trans.get("FlightDataType.TYPE_PITCH_MOMENT_COEFF"), "Cθ", UnitGroup.UNITS_COEFFICIENT, 91);
        //// Yaw moment coefficient
-       public static final FlightDataType TYPE_YAW_MOMENT_COEFF = newType(trans.get("FlightDataType.TYPE_YAW_MOMENT_COEFF"), UnitGroup.UNITS_COEFFICIENT, 92);
+       public static final FlightDataType TYPE_YAW_MOMENT_COEFF = newType(trans.get("FlightDataType.TYPE_YAW_MOMENT_COEFF"), "CτΨ", UnitGroup.UNITS_COEFFICIENT, 92);
        //// Side force coefficient
-       public static final FlightDataType TYPE_SIDE_FORCE_COEFF = newType(trans.get("FlightDataType.TYPE_SIDE_FORCE_COEFF"), UnitGroup.UNITS_COEFFICIENT, 93);
+       public static final FlightDataType TYPE_SIDE_FORCE_COEFF = newType(trans.get("FlightDataType.TYPE_SIDE_FORCE_COEFF"), "Cτs", UnitGroup.UNITS_COEFFICIENT, 93);
        //// Roll moment coefficient
-       public static final FlightDataType TYPE_ROLL_MOMENT_COEFF = newType(trans.get("FlightDataType.TYPE_ROLL_MOMENT_COEFF"), UnitGroup.UNITS_COEFFICIENT, 94);
+       public static final FlightDataType TYPE_ROLL_MOMENT_COEFF = newType(trans.get("FlightDataType.TYPE_ROLL_MOMENT_COEFF"), "CτΦ", UnitGroup.UNITS_COEFFICIENT, 94);
        //// Roll forcing coefficient
-       public static final FlightDataType TYPE_ROLL_FORCING_COEFF = newType(trans.get("FlightDataType.TYPE_ROLL_FORCING_COEFF"), UnitGroup.UNITS_COEFFICIENT, 95);
+       public static final FlightDataType TYPE_ROLL_FORCING_COEFF = newType(trans.get("FlightDataType.TYPE_ROLL_FORCING_COEFF"), "CfΦ", UnitGroup.UNITS_COEFFICIENT, 95);
        //// Roll damping coefficient
-       public static final FlightDataType TYPE_ROLL_DAMPING_COEFF = newType(trans.get("FlightDataType.TYPE_ROLL_DAMPING_COEFF"), UnitGroup.UNITS_COEFFICIENT, 96);
+       public static final FlightDataType TYPE_ROLL_DAMPING_COEFF = newType(trans.get("FlightDataType.TYPE_ROLL_DAMPING_COEFF"), "CζΦ", UnitGroup.UNITS_COEFFICIENT, 96);
        
        //// Pitch damping coefficient
-       public static final FlightDataType TYPE_PITCH_DAMPING_MOMENT_COEFF = newType(trans.get("FlightDataType.TYPE_PITCH_DAMPING_MOMENT_COEFF"), UnitGroup.UNITS_COEFFICIENT, 97);
+       public static final FlightDataType TYPE_PITCH_DAMPING_MOMENT_COEFF = newType(trans.get("FlightDataType.TYPE_PITCH_DAMPING_MOMENT_COEFF"), "Cζθ", UnitGroup.UNITS_COEFFICIENT, 97);
        //// Yaw damping coefficient
-       public static final FlightDataType TYPE_YAW_DAMPING_MOMENT_COEFF = newType(trans.get("FlightDataType.TYPE_YAW_DAMPING_MOMENT_COEFF"), UnitGroup.UNITS_COEFFICIENT, 98);
+       public static final FlightDataType TYPE_YAW_DAMPING_MOMENT_COEFF = newType(trans.get("FlightDataType.TYPE_YAW_DAMPING_MOMENT_COEFF"), "CζΨ", UnitGroup.UNITS_COEFFICIENT, 98);
        
        //// Coriolis acceleration
-       public static final FlightDataType TYPE_CORIOLIS_ACCELERATION = newType(trans.get("FlightDataType.TYPE_CORIOLIS_ACCELERATION"), 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(trans.get("FlightDataType.TYPE_REFERENCE_LENGTH"), 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(trans.get("FlightDataType.TYPE_REFERENCE_AREA"), 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(trans.get("FlightDataType.TYPE_ORIENTATION_THETA"), UnitGroup.UNITS_ANGLE, 106);
+       public static final FlightDataType TYPE_ORIENTATION_THETA = newType(trans.get("FlightDataType.TYPE_ORIENTATION_THETA"), "Θ", UnitGroup.UNITS_ANGLE, 106);
        //// Lateral orientation (azimuth)
-       public static final FlightDataType TYPE_ORIENTATION_PHI = newType(trans.get("FlightDataType.TYPE_ORIENTATION_PHI"), UnitGroup.UNITS_ANGLE, 107);
+       public static final FlightDataType TYPE_ORIENTATION_PHI = newType(trans.get("FlightDataType.TYPE_ORIENTATION_PHI"), "Φ", UnitGroup.UNITS_ANGLE, 107);
        
        
        ////  Atmospheric conditions
        //// Wind velocity
-       public static final FlightDataType TYPE_WIND_VELOCITY = newType(trans.get("FlightDataType.TYPE_WIND_VELOCITY"), 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(trans.get("FlightDataType.TYPE_AIR_TEMPERATURE"), 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(trans.get("FlightDataType.TYPE_AIR_PRESSURE"), 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(trans.get("FlightDataType.TYPE_SPEED_OF_SOUND"), 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(trans.get("FlightDataType.TYPE_TIME_STEP"), 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(trans.get("FlightDataType.TYPE_COMPUTATION_TIME"), 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 = { 
+               TYPE_ALTITUDE ,
+               TYPE_VELOCITY_Z ,
+               TYPE_ACCELERATION_Z, 
+               TYPE_VELOCITY_TOTAL, 
+               TYPE_ACCELERATION_TOTAL, 
+               TYPE_POSITION_X, 
+               TYPE_POSITION_Y,  
+               TYPE_POSITION_XY, 
+               TYPE_POSITION_DIRECTION, 
+               TYPE_VELOCITY_XY, 
+               TYPE_ACCELERATION_XY, 
+               TYPE_LATITUDE, 
+               TYPE_LONGITUDE, 
+               TYPE_AOA,
+               TYPE_ROLL_RATE,
+               TYPE_PITCH_RATE,
+               TYPE_YAW_RATE,
+               TYPE_MASS,
+               TYPE_LONGITUDINAL_INERTIA,
+               TYPE_ROTATIONAL_INERTIA,
+               TYPE_CP_LOCATION,
+               TYPE_CG_LOCATION,
+               TYPE_STABILITY,
+               TYPE_MACH_NUMBER,
+               TYPE_REYNOLDS_NUMBER,
+               TYPE_THRUST_FORCE,
+               TYPE_DRAG_FORCE,
+               TYPE_DRAG_COEFF,
+               TYPE_AXIAL_DRAG_COEFF,
+               TYPE_FRICTION_DRAG_COEFF,
+               TYPE_PRESSURE_DRAG_COEFF,
+               TYPE_BASE_DRAG_COEFF,
+               TYPE_NORMAL_FORCE_COEFF,
+               TYPE_PITCH_MOMENT_COEFF,
+               TYPE_YAW_MOMENT_COEFF,
+               TYPE_SIDE_FORCE_COEFF,
+               TYPE_ROLL_MOMENT_COEFF,
+               TYPE_ROLL_FORCING_COEFF,
+               TYPE_ROLL_DAMPING_COEFF,
+               TYPE_PITCH_DAMPING_MOMENT_COEFF,
+               TYPE_YAW_DAMPING_MOMENT_COEFF,
+               TYPE_CORIOLIS_ACCELERATION,
+               TYPE_REFERENCE_LENGTH,
+               TYPE_REFERENCE_AREA,
+               TYPE_ORIENTATION_THETA,
+               TYPE_ORIENTATION_PHI,
+               TYPE_WIND_VELOCITY,
+               TYPE_AIR_TEMPERATURE,
+               TYPE_AIR_PRESSURE,
+               TYPE_SPEED_OF_SOUND,
+               TYPE_TIME_STEP,
+               TYPE_COMPUTATION_TIME
+               };
        
        /**
         * Return a {@link FlightDataType} based on a string description.  This returns known data types
@@ -188,37 +242,39 @@ public class FlightDataType implements Comparable<FlightDataType> {
         * @param u             the unit group the new type should belong to if a new group is created.
         * @return              a data type.
         */
-       public static synchronized FlightDataType getType(String s, UnitGroup u) {
+       public static synchronized FlightDataType getType(String s, String symbol, UnitGroup u) {
                FlightDataType type = EXISTING_TYPES.get(s.toLowerCase(Locale.ENGLISH));
                if (type != null) {
                        return type;
                }
-               type = newType(s, u, DEFAULT_PRIORITY);
+               type = newType(s, symbol, u, DEFAULT_PRIORITY);
                return type;
        }
        
        /**
         * Used while initializing the class.
         */
-       private static synchronized FlightDataType newType(String s, UnitGroup u, int priority) {
-               FlightDataType type = new FlightDataType(s, u, priority);
+       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);
                return type;
        }
        
        
        private final String name;
+       private final String symbol;
        private final UnitGroup units;
        private final int priority;
        private final int hashCode;
        
        
-       private FlightDataType(String typeName, UnitGroup units, int priority) {
+       private FlightDataType(String typeName, String symbol, UnitGroup units, int priority) {
                if (typeName == null)
                        throw new IllegalArgumentException("typeName is null");
                if (units == null)
                        throw new IllegalArgumentException("units is null");
                this.name = typeName;
+               this.symbol = symbol;
                this.units = units;
                this.priority = priority;
                this.hashCode = this.name.toLowerCase(Locale.ENGLISH).hashCode();
@@ -231,6 +287,10 @@ public class FlightDataType implements Comparable<FlightDataType> {
                return name;
        }
        
+       public String getSymbol(){
+               return symbol;
+       }
+       
        public UnitGroup getUnitGroup() {
                return units;
        }
index f2412717a34a55bcfe6200c1a924eecd09829267..ffd9e003a6593a37621cc96b66520796267b57fb 100644 (file)
@@ -4,6 +4,7 @@ import java.util.ArrayList;
 import java.util.List;
 
 import net.sf.openrocket.aerodynamics.AerodynamicCalculator;
+import net.sf.openrocket.document.Simulation;
 import net.sf.openrocket.masscalc.MassCalculator;
 import net.sf.openrocket.models.atmosphere.AtmosphericModel;
 import net.sf.openrocket.models.gravity.GravityModel;
@@ -27,6 +28,7 @@ public class SimulationConditions implements Monitorable, Cloneable {
        private Rocket rocket;
        private String motorID = null;
        
+       private Simulation simulation; // The parent simulation 
 
        private double launchRodLength = 1;
        
@@ -262,9 +264,14 @@ public class SimulationConditions implements Monitorable, Cloneable {
                this.modID++;
        }
        
-       
-
+       public void setSimulation(Simulation sim) {
+               this.simulation = sim;
+       }
 
+       public Simulation getSimulation(){
+               return this.simulation;
+       }
+       
        // TODO: HIGH: Make cleaner
        public List<SimulationListener> getSimulationListenerList() {
                return simulationListeners;
index 7734ec5dac778921b32d17eae70fdc0bbe5dded8..a1b6bae91d22f03fd3827a8d6a751c03875893b9 100644 (file)
@@ -5,8 +5,10 @@ import java.util.EventListener;
 import java.util.EventObject;
 import java.util.List;
 import java.util.Random;
+import java.util.Set;
 
 import net.sf.openrocket.aerodynamics.BarrowmanCalculator;
+import net.sf.openrocket.logging.LogHelper;
 import net.sf.openrocket.masscalc.BasicMassCalculator;
 import net.sf.openrocket.models.atmosphere.AtmosphericModel;
 import net.sf.openrocket.models.atmosphere.ExtendedISAModel;
@@ -14,6 +16,7 @@ import net.sf.openrocket.models.gravity.GravityModel;
 import net.sf.openrocket.models.gravity.WGSGravityModel;
 import net.sf.openrocket.models.wind.PinkNoiseWindModel;
 import net.sf.openrocket.rocketcomponent.Rocket;
+import net.sf.openrocket.startup.Application;
 import net.sf.openrocket.util.BugException;
 import net.sf.openrocket.util.ChangeSource;
 import net.sf.openrocket.util.GeodeticComputationStrategy;
@@ -31,6 +34,8 @@ import net.sf.openrocket.util.WorldCoordinate;
  */
 public class SimulationOptions implements ChangeSource, Cloneable {
        
+       private static final LogHelper log = Application.getLogger();
+       
        public static final double MAX_LAUNCH_ROD_ANGLE = Math.PI / 3;
        
        /**
@@ -93,7 +98,6 @@ public class SimulationOptions implements ChangeSource, Cloneable {
                this.rocket = rocket;
        }
        
-       
        public Rocket getRocket() {
                return rocket;
        }
index 80993218c9a32f4c3ef9851d060f18db84bc2ca6..4c99375b18ad8df785ec68fe2ebc4d961415a6c3 100644 (file)
@@ -22,8 +22,7 @@ public class RollControlListener extends AbstractSimulationListener {
        private static final String CONTROL_FIN_NAME = "CONTROL";
        
        // Define custom flight data type
-       private static final FlightDataType FIN_CANT_TYPE = FlightDataType.getType("Control fin cant",
-                       UnitGroup.UNITS_ANGLE);
+       private static final FlightDataType FIN_CANT_TYPE = FlightDataType.getType("Control fin cant", "αfc", UnitGroup.UNITS_ANGLE);
        
        // Simulation time at which PID controller is activated
        private static final double START_TIME = 0.5;
diff --git a/core/src/net/sf/openrocket/unit/FixedUnitGroup.java b/core/src/net/sf/openrocket/unit/FixedUnitGroup.java
new file mode 100644 (file)
index 0000000..a098496
--- /dev/null
@@ -0,0 +1,31 @@
+package net.sf.openrocket.unit;
+
+/*
+ * This class provides a 'dumb' version of UnitGroup
+ * It allows any arbitrary unit to be created. It doesn't store any value and can't be converted into anything else.
+ * This is useful for custom expression units.
+ * 
+ * @author Richard Graham
+ */
+
+public class FixedUnitGroup extends UnitGroup {
+       
+       String unitString;
+       
+       public FixedUnitGroup( String unitString ){
+               this.unitString = unitString; 
+       }
+       
+       public int getUnitCount(){
+               return 1;
+       }
+       
+       public Unit getDefaultUnit(){
+               return new GeneralUnit(1, unitString);
+       }
+       
+       public boolean contains(Unit u){
+               return true;
+       }
+       
+}