move gui to another source package
authorBill Kuker <bkuker@billkuker.com>
Sat, 30 Oct 2010 16:50:03 +0000 (16:50 +0000)
committerBill Kuker <bkuker@billkuker.com>
Sat, 30 Oct 2010 16:50:03 +0000 (16:50 +0000)
29 files changed:
.classpath
gui/MotorSim.java [new file with mode: 0644]
gui/com/billkuker/rocketry/motorsim/visual/BurnPanel.java [new file with mode: 0644]
gui/com/billkuker/rocketry/motorsim/visual/Chart.java [new file with mode: 0644]
gui/com/billkuker/rocketry/motorsim/visual/Editor.java [new file with mode: 0644]
gui/com/billkuker/rocketry/motorsim/visual/FuelPanel.java [new file with mode: 0644]
gui/com/billkuker/rocketry/motorsim/visual/GrainPanel.java [new file with mode: 0644]
gui/com/billkuker/rocketry/motorsim/visual/HardwarePanel.java [new file with mode: 0644]
gui/com/billkuker/rocketry/motorsim/visual/workbench/BurnWatcher.java [new file with mode: 0644]
gui/com/billkuker/rocketry/motorsim/visual/workbench/MotorEditor.java [new file with mode: 0644]
gui/com/billkuker/rocketry/motorsim/visual/workbench/MotorWorkbench.java [new file with mode: 0644]
gui/com/billkuker/rocketry/motorsim/visual/workbench/MultiBurnChart.java [new file with mode: 0644]
gui/com/billkuker/rocketry/motorsim/visual/workbench/SRFuelEditor.java [new file with mode: 0644]
gui/com/billkuker/rocketry/motorsim/visual/workbench/WorkbenchTreeCellRenderer.java [new file with mode: 0644]
gui/com/billkuker/rocketry/motorsim/visual/workbench/WorkbenchTreeModel.java [new file with mode: 0644]
src/MotorSim.java [deleted file]
src/com/billkuker/rocketry/motorsim/visual/BurnPanel.java [deleted file]
src/com/billkuker/rocketry/motorsim/visual/Chart.java [deleted file]
src/com/billkuker/rocketry/motorsim/visual/Editor.java [deleted file]
src/com/billkuker/rocketry/motorsim/visual/FuelPanel.java [deleted file]
src/com/billkuker/rocketry/motorsim/visual/GrainPanel.java [deleted file]
src/com/billkuker/rocketry/motorsim/visual/HardwarePanel.java [deleted file]
src/com/billkuker/rocketry/motorsim/visual/workbench/BurnWatcher.java [deleted file]
src/com/billkuker/rocketry/motorsim/visual/workbench/MotorEditor.java [deleted file]
src/com/billkuker/rocketry/motorsim/visual/workbench/MotorWorkbench.java [deleted file]
src/com/billkuker/rocketry/motorsim/visual/workbench/MultiBurnChart.java [deleted file]
src/com/billkuker/rocketry/motorsim/visual/workbench/SRFuelEditor.java [deleted file]
src/com/billkuker/rocketry/motorsim/visual/workbench/WorkbenchTreeCellRenderer.java [deleted file]
src/com/billkuker/rocketry/motorsim/visual/workbench/WorkbenchTreeModel.java [deleted file]

index 5bfd266e09a02e1fd6b9b2242eadf7af8f88e87c..ae85208b16bac8c4bd377bf1213fce348f955a1a 100644 (file)
@@ -3,6 +3,7 @@
        <classpathentry kind="src" path="src"/>\r
        <classpathentry kind="src" path="test"/>\r
        <classpathentry kind="src" path="motors"/>\r
+       <classpathentry kind="src" path="gui"/>\r
        <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>\r
        <classpathentry kind="lib" path="jscience.jar"/>\r
        <classpathentry kind="lib" path="jfreechart-1.0.12.jar"/>\r
diff --git a/gui/MotorSim.java b/gui/MotorSim.java
new file mode 100644 (file)
index 0000000..6f1a07f
--- /dev/null
@@ -0,0 +1,26 @@
+import javax.swing.SwingUtilities;
+import javax.swing.UIManager;
+
+public class MotorSim {
+
+       public static void main(String args[]) throws Exception {
+
+               try {
+                       System.setProperty("apple.laf.useScreenMenuBar", "true");
+                       System.setProperty(
+                                       "com.apple.mrj.application.apple.menu.about.name",
+                                       "MotorSim");
+                       UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
+               } catch (Exception e1) {
+                       e1.printStackTrace();
+               }
+               SwingUtilities.invokeLater(new Runnable(){
+                       @Override
+                       public void run() {
+                               new com.billkuker.rocketry.motorsim.visual.workbench.MotorWorkbench().setVisible(true);
+                       }
+               });
+               
+       }
+
+}
diff --git a/gui/com/billkuker/rocketry/motorsim/visual/BurnPanel.java b/gui/com/billkuker/rocketry/motorsim/visual/BurnPanel.java
new file mode 100644 (file)
index 0000000..a42aaa1
--- /dev/null
@@ -0,0 +1,209 @@
+package com.billkuker.rocketry.motorsim.visual;\r
+\r
+import java.awt.BorderLayout;\r
+import java.awt.Dimension;\r
+import java.awt.GridLayout;\r
+import java.text.NumberFormat;\r
+\r
+import javax.measure.quantity.Duration;\r
+import javax.measure.quantity.Force;\r
+import javax.measure.quantity.Pressure;\r
+import javax.measure.quantity.Velocity;\r
+import javax.measure.unit.SI;\r
+import javax.swing.JFrame;\r
+import javax.swing.JLabel;\r
+import javax.swing.JPanel;\r
+import javax.swing.JSlider;\r
+import javax.swing.JSplitPane;\r
+import javax.swing.WindowConstants;\r
+import javax.swing.event.ChangeEvent;\r
+import javax.swing.event.ChangeListener;\r
+\r
+import org.jscience.physics.amount.Amount;\r
+\r
+import com.billkuker.rocketry.motorsim.Burn;\r
+import com.billkuker.rocketry.motorsim.Burn.Interval;\r
+import com.billkuker.rocketry.motorsim.RocketScience;\r
+\r
+public class BurnPanel extends JPanel {\r
+       private static final long serialVersionUID = 1L;\r
+       private Burn burn;\r
+       Chart<Duration, Pressure> pressure;\r
+       Chart<Duration, Force> thrust;\r
+       Chart<Pressure, Velocity> burnRate;\r
+       GrainPanel grain;\r
+       Amount<Duration> displayedTime = Amount.valueOf(0, SI.SECOND);\r
+       \r
+       public BurnPanel(Burn b){\r
+               super( new BorderLayout() );\r
+               burn = b;\r
+               \r
+               try {\r
+                       pressure = new Chart<Duration, Pressure>(\r
+                                       SI.SECOND,\r
+                                       SI.MEGA(SI.PASCAL),\r
+                                       b,\r
+                                       "pressure");\r
+                       pressure.setDomain(burn.getData().keySet());\r
+                       \r
+                       thrust = new Chart<Duration, Force>(\r
+                                       SI.SECOND,\r
+                                       SI.NEWTON,\r
+                                       b,\r
+                                       "thrust");\r
+                       thrust.setDomain(burn.getData().keySet());\r
+                       \r
+                       burnRate = new Chart<Pressure, Velocity>(\r
+                                       SI.MEGA(SI.PASCAL),\r
+                                       SI.METERS_PER_SECOND,\r
+                                       burn.getMotor().getFuel(),\r
+                                       "burnRate");\r
+                       burnRate.setDomain(\r
+                                       burnRate.new IntervalDomain(\r
+                                                       Amount.valueOf(0, SI.MEGA(SI.PASCAL)),\r
+                                                       Amount.valueOf(11, SI.MEGA(SI.PASCAL)),\r
+                                                       20\r
+                                                       ));\r
+                       \r
+                       \r
+                       JSplitPane tp = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, thrust, pressure);\r
+                       tp.setDividerLocation(.5);\r
+                       tp.setResizeWeight(.5);\r
+                       \r
+                       grain = new GrainPanel(burn.getMotor().getGrain()){\r
+                               private static final long serialVersionUID = 1L;\r
+                               @Override protected void addComponents(java.awt.Component crossSection, java.awt.Component slider, java.awt.Component label, java.awt.Component area, java.awt.Component volume) {\r
+                                       JSplitPane h = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, crossSection, area); \r
+                                       add(h, BorderLayout.CENTER);\r
+                                       h.resetToPreferredSizes();\r
+                               };\r
+                       };\r
+                       \r
+                       JSplitPane grains = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, grain, burnRate);\r
+                       grains.setDividerLocation(.5);\r
+                       grains.setResizeWeight(.5);\r
+                       \r
+                       JSplitPane main = new JSplitPane(JSplitPane.VERTICAL_SPLIT, grains, tp);\r
+                       Dimension minimumSize = new Dimension(800, 200);\r
+                       grains.setMinimumSize(minimumSize);\r
+                       tp.setMinimumSize(minimumSize);\r
+                       main.setDividerLocation(.5);\r
+                       main.setResizeWeight(.5);\r
+                       \r
+                       add( main, BorderLayout.CENTER );\r
+                       \r
+                       add( new SL(), BorderLayout.SOUTH);\r
+                       \r
+                       \r
+\r
+                               Amount<RocketScience.Impulse> ns = Amount.valueOf(0, RocketScience.NEWTON_SECOND);\r
+                               \r
+                               Amount<Duration> thrustTime = Amount.valueOf(0, SI.SECOND);\r
+                               Amount<Force> maxThrust = Amount.valueOf(0, SI.NEWTON);\r
+                               Amount<Pressure> maxPressure = Amount.valueOf(0, SI.MEGA(SI.PASCAL));\r
+\r
+                               for( Interval i: burn.getData().values() ){\r
+                                       ns = ns.plus(i.dt.times(i.thrust));\r
+                                       if ( i.thrust.isGreaterThan(Amount.valueOf(0.01, SI.NEWTON))){\r
+                                               thrustTime = thrustTime.plus(i.dt);\r
+                                       }\r
+                                       if ( i.thrust.isGreaterThan(maxThrust))\r
+                                               maxThrust = i.thrust;\r
+                                       if ( i.chamberPressure.isGreaterThan(maxPressure))\r
+                                               maxPressure = i.chamberPressure;\r
+                               }\r
+                               \r
+                               Amount<Force> averageThrust = Amount.valueOf(0, SI.NEWTON);\r
+                               if ( thrustTime.isGreaterThan(Amount.valueOf(0, SI.SECOND)))\r
+                                       averageThrust = ns.divide(thrustTime).to(SI.NEWTON);\r
+\r
+                               float cnf = (float)(Math.log(ns.doubleValue(RocketScience.NEWTON_SECOND)/1.25) / Math.log(2));\r
+                               int cn = (int)cnf;\r
+                               float fraction = cnf - cn;\r
+                               int percent = (int)(100 * fraction);\r
+                               char cl = (char)((int)'A' + cn);\r
+\r
+                               \r
+                               Amount<Duration> isp = ns.divide(\r
+                                               b.getMotor().getGrain().volume(Amount.valueOf(0, SI.MILLIMETER))\r
+                                                       .times(b.getMotor().getFuel().getIdealDensity().times(b.getMotor().getFuel().getDensityRatio()))\r
+                                               ).to(SI.METERS_PER_SECOND).divide(Amount.valueOf(9.81, SI.METERS_PER_SQUARE_SECOND)).to(SI.SECOND);\r
+                       \r
+                       JPanel text = new JPanel(new GridLayout(2,5));\r
+\r
+                       text.add(new JLabel("Rating"));\r
+                       text.add(new JLabel("Total Impulse"));\r
+                       text.add(new JLabel("ISP"));\r
+                       text.add(new JLabel("Max Thrust"));\r
+                       text.add(new JLabel("Average Thust"));\r
+                       text.add(new JLabel("Max Pressure"));\r
+                       \r
+                       text.add(new JLabel(percent + "% " + new String(new char[]{cl}) + "-" +Math.round(averageThrust.doubleValue(SI.NEWTON))));\r
+                       text.add(new JLabel(RocketScience.approx(ns)));\r
+                       text.add(new JLabel(RocketScience.approx(isp)));                        \r
+                       text.add(new JLabel(RocketScience.approx(maxThrust)));\r
+                       text.add(new JLabel(RocketScience.approx(averageThrust)));\r
+                       text.add(new JLabel(RocketScience.approx(maxPressure)));\r
+                       \r
+                       add(text, BorderLayout.NORTH);\r
+                       \r
+                       \r
+               } catch (NoSuchMethodException e){\r
+                       throw new Error(e);\r
+               }\r
+               \r
+\r
+       }\r
+       \r
+       private class SL extends JSlider implements ChangeListener{\r
+               private static final long serialVersionUID = 1L;\r
+               private static final int STEPS = 80;\r
+               public SL(){\r
+                       addChangeListener(this);\r
+                       setMinimum(0);\r
+                       setMaximum(STEPS);\r
+                       setValue(0);\r
+               }\r
+               \r
+               public void stateChanged(ChangeEvent e) {\r
+                       double t = ((SL)e.getSource()).getValue();\r
+                       displayedTime = burn.burnTime().divide(STEPS).times(t);\r
+                       \r
+                       //Find the nearest key in the data set\r
+                       displayedTime =burn.getData().tailMap(displayedTime).firstKey();\r
+                       \r
+                       NumberFormat nf = NumberFormat.getInstance();\r
+                       nf.setMaximumFractionDigits(2);\r
+                       \r
+                       pressure.mark(displayedTime);\r
+                       thrust.mark(displayedTime);\r
+                       \r
+                       grain.setDisplayedRegression(burn.getData().get(displayedTime).regression);\r
+                       \r
+                       burnRate.mark(burn.getData().get(displayedTime).chamberPressure);\r
+                       \r
+                       \r
+                       /*\r
+                       double r = ((SL)e.getSource()).getValue();\r
+                       displayedRegression = grain.webThickness().divide(STEPS).times(r);\r
+                       NumberFormat nf = NumberFormat.getInstance();\r
+                       nf.setMaximumFractionDigits(2);\r
+                       l.setText("Regression: " + nf.format(displayedRegression.doubleValue(SI.MILLIMETER)) + "mm");\r
+                       area.mark(displayedRegression);\r
+                       volume.mark(displayedRegression);\r
+                       if ( xc != null )\r
+                               xc.repaint();\r
+                       */\r
+               }\r
+       }\r
+       \r
+       public void showAsWindow(){\r
+               JFrame f = new JFrame();\r
+               f.setTitle(burn.getMotor().getName());\r
+               f.setSize(1280,720);\r
+               f.setLocation(0, 0);\r
+               f.setContentPane(this);\r
+               f.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);\r
+               f.setVisible(true);\r
+       }\r
+}\r
diff --git a/gui/com/billkuker/rocketry/motorsim/visual/Chart.java b/gui/com/billkuker/rocketry/motorsim/visual/Chart.java
new file mode 100644 (file)
index 0000000..97eb6df
--- /dev/null
@@ -0,0 +1,295 @@
+package com.billkuker.rocketry.motorsim.visual;\r
+\r
+import java.awt.BorderLayout;\r
+import java.awt.Color;\r
+import java.awt.Font;\r
+import java.lang.reflect.InvocationTargetException;\r
+import java.lang.reflect.Method;\r
+import java.util.Collection;\r
+import java.util.Iterator;\r
+import java.util.concurrent.ExecutorService;\r
+import java.util.concurrent.Executors;\r
+import java.util.concurrent.ThreadFactory;\r
+\r
+import javax.measure.quantity.Area;\r
+import javax.measure.quantity.Length;\r
+import javax.measure.quantity.Quantity;\r
+import javax.measure.quantity.Volume;\r
+import javax.measure.unit.SI;\r
+import javax.measure.unit.Unit;\r
+import javax.swing.JFrame;\r
+import javax.swing.JPanel;\r
+import javax.swing.SwingUtilities;\r
+\r
+import org.apache.log4j.Logger;\r
+import org.jfree.chart.ChartFactory;\r
+import org.jfree.chart.ChartPanel;\r
+import org.jfree.chart.JFreeChart;\r
+import org.jfree.chart.plot.Marker;\r
+import org.jfree.chart.plot.PlotOrientation;\r
+import org.jfree.chart.plot.ValueMarker;\r
+import org.jfree.data.xy.XYSeries;\r
+import org.jfree.data.xy.XYSeriesCollection;\r
+import org.jfree.ui.RectangleInsets;\r
+import org.jfree.ui.TextAnchor;\r
+import org.jscience.physics.amount.Amount;\r
+\r
+import com.billkuker.rocketry.motorsim.Burn;\r
+import com.billkuker.rocketry.motorsim.RocketScience;\r
+import com.billkuker.rocketry.motorsim.grain.CoredCylindricalGrain;\r
+\r
+public class Chart<X extends Quantity, Y extends Quantity> extends JPanel {\r
+       private static final long serialVersionUID = 1L;\r
+       private static Logger log = Logger.getLogger(Burn.class);\r
+\r
+       private static ThreadFactory tf = new ThreadFactory() {\r
+               public Thread newThread(Runnable r) {\r
+                       Thread t = new Thread(r);\r
+                       t.setDaemon(true);\r
+                       return t;\r
+               }\r
+       };\r
+       private static ExecutorService fast = Executors.newFixedThreadPool(2, tf);\r
+       private static ExecutorService slow = Executors.newFixedThreadPool(2, tf);\r
+       private volatile boolean stop = false;\r
+\r
+       public class IntervalDomain implements Iterable<Amount<X>> {\r
+\r
+               Amount<X> low, high, delta;\r
+               int steps = 100;\r
+\r
+               public IntervalDomain(Amount<X> low, Amount<X> high) {\r
+                       this.low = low;\r
+                       this.high = high;\r
+                       delta = high.minus(low).divide(steps);\r
+               }\r
+\r
+               public IntervalDomain(Amount<X> low, Amount<X> high, int steps) {\r
+                       this.steps = steps;\r
+                       this.low = low;\r
+                       this.high = high;\r
+                       delta = high.minus(low).divide(steps);\r
+               }\r
+\r
+               public Iterator<Amount<X>> iterator() {\r
+                       return new Iterator<Amount<X>>() {\r
+                               Amount<X> current = low;\r
+\r
+                               public boolean hasNext() {\r
+                                       return current.isLessThan(high.plus(delta));\r
+                               }\r
+\r
+                               public Amount<X> next() {\r
+                                       Amount<X> ret = current;\r
+                                       current = current.plus(delta);\r
+                                       return ret;\r
+                               }\r
+\r
+                               public final void remove() {\r
+                                       throw new UnsupportedOperationException(\r
+                                                       "Chart domain iterators are not modifiable.");\r
+                               }\r
+                       };\r
+               }\r
+\r
+       }\r
+\r
+       XYSeriesCollection dataset = new XYSeriesCollection();\r
+       JFreeChart chart;\r
+\r
+       Unit<X> xUnit;\r
+       Unit<Y> yUnit;\r
+\r
+       Object source;\r
+       Method f;\r
+\r
+       public Chart(Unit<X> xUnit, Unit<Y> yUnit, Object source, String method)\r
+                       throws NoSuchMethodException {\r
+               super(new BorderLayout());\r
+               f = source.getClass().getMethod(method, Amount.class);\r
+\r
+               this.source = source;\r
+\r
+\r
+               this.xUnit = RocketScience.UnitPreference.getUnitPreference()\r
+                               .getPreferredUnit(xUnit);\r
+               this.yUnit = RocketScience.UnitPreference.getUnitPreference()\r
+                               .getPreferredUnit(yUnit);\r
+\r
+               chart = ChartFactory.createXYLineChart(method.substring(0, 1)\r
+                               .toUpperCase()\r
+                               + method.substring(1), // Title\r
+                               this.xUnit.toString(), // x-axis Label\r
+                               this.yUnit.toString(), // y-axis Label\r
+                               dataset, PlotOrientation.VERTICAL, // Plot Orientation\r
+                               false, // Show Legend\r
+                               true, // Use tool tips\r
+                               false // Configure chart to generate URLs?\r
+                               );\r
+               add(new ChartPanel(chart));\r
+       }\r
+\r
+       private Marker marker;\r
+\r
+       public void mark(Amount<X> m) {\r
+               if (marker != null)\r
+                       chart.getXYPlot().removeDomainMarker(marker);\r
+               if (m != null) {\r
+                       marker = new ValueMarker(m.doubleValue(xUnit));\r
+                       marker.setPaint(Color.blue);\r
+                       marker.setAlpha(0.8f);\r
+                       \r
+                       Amount<Y> val = getNear(m);\r
+                       if ( val != null )\r
+                               marker.setLabel(RocketScience.approx(val));\r
+                       \r
+                       marker.setLabelTextAnchor(TextAnchor.TOP_LEFT);\r
+                       marker.setLabelOffset(new RectangleInsets(0,-5,0,0));\r
+                       \r
+                       marker.setLabelFont(new Font(Font.DIALOG, Font.BOLD, 12));\r
+                       chart.getXYPlot().addDomainMarker(marker);\r
+               }\r
+       }\r
+       \r
+       /**\r
+        * Get the Y value at or near a given X\r
+        * For display use only!\r
+        * \r
+        * @param ax\r
+        * @return\r
+        */\r
+       private Amount<Y> getNear(final Amount<X> ax){\r
+               if ( dataset.getSeriesCount() != 1 )\r
+                       return null;\r
+               final XYSeries s = dataset.getSeries(0);\r
+               final double x = ax.doubleValue(xUnit);\r
+               int idx = s.getItemCount() / 2;\r
+               int delta = s.getItemCount() / 4;\r
+               while(true){\r
+                       if ( s.getX(idx).doubleValue() < x ){\r
+                               idx += delta;\r
+                       } else {\r
+                               idx -= delta;\r
+                       }\r
+                       delta = delta / 2;\r
+                       if ( delta < 1 ){\r
+                               int idxL = idx-1;\r
+                               int idxH = idx;\r
+                               final double lowerX = s.getX(idxL).doubleValue();\r
+                               final double higherX = s.getX(idxH).doubleValue();\r
+                               final double sampleXDiff = higherX - lowerX;\r
+                               final double xDiff = x - lowerX;\r
+                               final double dist = xDiff / sampleXDiff;\r
+                               final double lowerY = s.getY(idxL).doubleValue();\r
+                               final double higherY = s.getY(idxH).doubleValue();\r
+                               final double y = lowerY + dist * (higherY - lowerY);\r
+                               \r
+                               return Amount.valueOf( y, yUnit);\r
+                       }\r
+               }\r
+       }\r
+\r
+       public void setDomain(final Iterable<Amount<X>> d) {\r
+               stop = true;\r
+               fill(d, 100);\r
+               fast.submit(new Thread() {\r
+                       public void run() {\r
+                               if (!stop)\r
+                                       fill(d, 10);\r
+                               slow.submit(new Thread() {\r
+                                       public void run() {\r
+                                               if (!stop)\r
+                                                       fill(d, 1);\r
+                                       }\r
+                               });\r
+                       }\r
+               });\r
+       }\r
+\r
+       @SuppressWarnings("unchecked")\r
+       private synchronized void fill(Iterable<Amount<X>> d, int skip) {\r
+               log.debug(f.getName() + " " + skip + " Start");\r
+               stop = false;\r
+               int sz = 0;\r
+               if (d instanceof Collection) {\r
+                       sz = ((Collection<Amount<X>>) d).size();\r
+                       int sk2 = sz / 200;\r
+                       if (skip < sk2)\r
+                               skip = sk2;\r
+               }\r
+               // series.clear();\r
+               int cnt = 0;\r
+\r
+               final XYSeries newSeries = new XYSeries(f.getName());\r
+               try {\r
+                       Amount<X> last = null;\r
+                       for (Amount<X> ax : d) {\r
+                               if (stop) {\r
+                                       log.debug(f.getName() + " " + skip + " Abort");\r
+                                       return;\r
+                               }\r
+                               last = ax;\r
+                               if (cnt % skip == 0) {\r
+                                       Amount<Y> y = (Amount<Y>) f.invoke(source, ax);\r
+                                       newSeries.add(ax.doubleValue(xUnit), y.doubleValue(yUnit));\r
+                               }\r
+                               cnt++;\r
+                       }\r
+                       Amount<Y> y = (Amount<Y>) f.invoke(source, last);\r
+                       newSeries.add(last.doubleValue(xUnit), y.doubleValue(yUnit));\r
+                       SwingUtilities.invokeLater(new Thread() {\r
+                               @Override\r
+                               public void run() {\r
+                                       dataset.removeAllSeries();\r
+                                       dataset.addSeries(newSeries);\r
+                                       log.debug(f.getName() + " Replaced");\r
+                               }\r
+                       });\r
+               } catch (IllegalArgumentException e) {\r
+                       // TODO Auto-generated catch block\r
+                       e.printStackTrace();\r
+               } catch (IllegalAccessException e) {\r
+                       // TODO Auto-generated catch block\r
+                       e.printStackTrace();\r
+               } catch (InvocationTargetException e) {\r
+                       // TODO Auto-generated catch block\r
+                       e.printStackTrace();\r
+               }\r
+               log.debug(f.getName() + " " + skip + " Done");\r
+       }\r
+\r
+       public void show() {\r
+               new JFrame() {\r
+                       private static final long serialVersionUID = 1L;\r
+                       {\r
+                               setContentPane(Chart.this);\r
+                               setSize(640, 480);\r
+                               setDefaultCloseOperation(DISPOSE_ON_CLOSE);\r
+                       }\r
+               }.setVisible(true);\r
+       }\r
+\r
+       public static void main(String args[]) throws Exception {\r
+               CoredCylindricalGrain g = new CoredCylindricalGrain();\r
+               g.setLength(Amount.valueOf(70, SI.MILLIMETER));\r
+               g.setOD(Amount.valueOf(30, SI.MILLIMETER));\r
+               g.setID(Amount.valueOf(10, SI.MILLIMETER));\r
+\r
+               Chart<Length, Area> c = new Chart<Length, Area>(SI.MILLIMETER,\r
+                               SI.MILLIMETER.pow(2).asType(Area.class), g, "surfaceArea");\r
+\r
+               c.setDomain(c.new IntervalDomain(Amount.valueOf(0, SI.CENTIMETER), g\r
+                               .webThickness()));\r
+\r
+               c.show();\r
+\r
+               Chart<Length, Volume> v = new Chart<Length, Volume>(SI.MILLIMETER,\r
+                               SI.MILLIMETER.pow(3).asType(Volume.class), g, "volume");\r
+\r
+               v.setDomain(c.new IntervalDomain(Amount.valueOf(0, SI.CENTIMETER), g\r
+                               .webThickness()));\r
+\r
+               v.show();\r
+       }\r
+\r
+}\r
diff --git a/gui/com/billkuker/rocketry/motorsim/visual/Editor.java b/gui/com/billkuker/rocketry/motorsim/visual/Editor.java
new file mode 100644 (file)
index 0000000..11ddd8e
--- /dev/null
@@ -0,0 +1,196 @@
+package com.billkuker.rocketry.motorsim.visual;\r
+\r
+import java.awt.Component;\r
+import java.awt.Dimension;\r
+import java.beans.IntrospectionException;\r
+import java.beans.Introspector;\r
+import java.beans.PropertyChangeEvent;\r
+import java.beans.PropertyChangeListener;\r
+import java.beans.PropertyDescriptor;\r
+import java.beans.PropertyEditorManager;\r
+import java.beans.PropertyEditorSupport;\r
+import java.text.DecimalFormat;\r
+import java.text.NumberFormat;\r
+import java.util.Vector;\r
+\r
+import javax.measure.unit.SI;\r
+import javax.measure.unit.Unit;\r
+import javax.swing.JFrame;\r
+import javax.swing.JLabel;\r
+import javax.swing.JTable;\r
+import javax.swing.JTextField;\r
+import javax.swing.WindowConstants;\r
+import javax.swing.table.TableCellRenderer;\r
+\r
+import org.apache.log4j.Logger;\r
+import org.jscience.physics.amount.Amount;\r
+\r
+import com.billkuker.rocketry.motorsim.CylindricalChamber;\r
+import com.l2fprod.common.propertysheet.PropertySheetPanel;\r
+\r
+public class Editor extends PropertySheetPanel {\r
+       private static final long serialVersionUID = 1L;\r
+       private static final NumberFormat nf = new DecimalFormat("##########.###");\r
+       private static final Logger log = Logger.getLogger(Editor.class);\r
+\r
+       private Object obj;\r
+\r
+       @SuppressWarnings("deprecation")\r
+       public Editor(Object o) {\r
+               obj = o;\r
+\r
+               PropertyEditorManager.registerEditor(Amount.class,\r
+                               AmountPropertyEditor.class);\r
+               \r
+               setToolBarVisible(false);\r
+               //setMinimumSize(new Dimension(150,200));\r
+               \r
+               getRendererRegistry().registerRenderer(Amount.class, AmountRenderer.class);\r
+\r
+               // Build the list of properties we want it to edit\r
+               //final PropertySheetPanel ps = new PropertySheetPanel();\r
+               PropertyDescriptor props[];\r
+               try {\r
+                       props = Introspector.getBeanInfo(obj.getClass())\r
+                                       .getPropertyDescriptors();\r
+               } catch (IntrospectionException e) {\r
+                       throw new Error(e);\r
+               }\r
+               Vector<PropertyDescriptor> v = new Vector<PropertyDescriptor>();\r
+               for (int i = 0; i < props.length; i++) {\r
+                       if (props[i].getName().equals("class"))\r
+                               continue;\r
+                       v.add(props[i]);\r
+               }\r
+               setProperties(v.toArray(new PropertyDescriptor[v.size()]));\r
+\r
+               readFromObject(obj);\r
+               \r
+               getTable().setRowHeight(22);\r
+               \r
+               setMinimumSize(new Dimension(\r
+                               getTable().getPreferredSize().width,\r
+                               getTable().getPreferredSize().height + 10));\r
+\r
+               addPropertySheetChangeListener(new PropertyChangeListener() {\r
+\r
+                       public void propertyChange(PropertyChangeEvent evt) {\r
+                               // When something changes just update the\r
+                               // object, I want the changes to be immediate.\r
+                               try {\r
+                                       log.debug("Writing properties to object.");\r
+                                       writeToObject(obj);\r
+                               } catch (Exception v) {\r
+                                       // TODO\r
+                                       v.printStackTrace();\r
+                                       java.awt.Toolkit.getDefaultToolkit().beep();\r
+                               } finally {\r
+                                       readFromObject(obj);\r
+                               }\r
+                       }\r
+\r
+               });\r
+       }\r
+\r
+       public void showAsWindow() {\r
+               JFrame f = new JFrame();\r
+               f.setTitle(obj.getClass().getName());\r
+               f.setSize(600, 400);\r
+               f.setContentPane(this);\r
+               f.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);\r
+               f.setVisible(true);\r
+       }\r
+       \r
+       public static void main(String args[]){\r
+               CylindricalChamber o = new CylindricalChamber();\r
+               o.setLength(Amount.valueOf(100.5, SI.MILLIMETER));\r
+               o.setID(Amount.valueOf(30, SI.MILLIMETER));\r
+               Editor e = new Editor(o);\r
+               e.showAsWindow();\r
+       }\r
+       \r
+       public static class AmountRenderer implements TableCellRenderer {\r
+               @Override\r
+               public Component getTableCellRendererComponent(JTable table,\r
+                               Object value, boolean isSelected, boolean hasFocus, int row,\r
+                               int column) {\r
+                       Amount a = (Amount)value;\r
+                       return new JLabel(nf.format(a.doubleValue(a.getUnit())) + " " + a.getUnit() );\r
+               }\r
+               \r
+       }\r
+\r
+       public static class AmountPropertyEditor extends PropertyEditorSupport {\r
+               JTextField editor = new JTextField();\r
+               Unit<?> oldUnit;\r
+\r
+               @Override\r
+               public boolean supportsCustomEditor() {\r
+                       return true;\r
+               }\r
+\r
+               @Override\r
+               public String getAsText() {\r
+                       return editor.getText();\r
+               }\r
+\r
+               @Override\r
+               public Object getValue() {\r
+                       String text = editor.getText().trim();\r
+\r
+                       // Trying to determine if the value is integer or\r
+                       // has a decimal part will prevent the uncertainty\r
+                       // term from appearing when user types an exact value\r
+                       try {\r
+                               try {\r
+                                       return Amount.valueOf(Integer.parseInt(text), oldUnit);\r
+                               } catch (NumberFormatException e) {\r
+\r
+                               }\r
+                               Amount<?> a = Amount.valueOf(Double.parseDouble(text), oldUnit);\r
+                               return a;\r
+                       } catch (NumberFormatException e) {\r
+                               // Storing the old unit allows you to type 10 into a field\r
+                               // that says 20 mm and get 10 mm, so you dont have to\r
+                               // type the unit if they havn't changed.\r
+                               \r
+                               //Amount wants a leading 0\r
+                               if (text.startsWith(".")){\r
+                                       text = "0" + text;\r
+                               }\r
+                               \r
+                               Amount<?> a = Amount.valueOf(text);\r
+                               oldUnit = a.getUnit();\r
+                               return a;\r
+                       }\r
+\r
+               }\r
+\r
+               @SuppressWarnings("unchecked")\r
+               @Override\r
+               public void setValue(Object o) {\r
+                       Amount a = (Amount) o;\r
+                       oldUnit = a.getUnit();\r
+\r
+                       String text;\r
+                       //Leave off the fractional part if it is not relevant\r
+                       if (a.isExact())\r
+                               text = a.getExactValue() + " " + a.getUnit();\r
+                       else\r
+                               text = nf.format(a.doubleValue(a.getUnit())) + " " + a.getUnit();\r
+\r
+                       setAsText(text);\r
+               }\r
+\r
+               @Override\r
+               public void setAsText(String text) throws IllegalArgumentException {\r
+                       editor.setText(text);\r
+               };\r
+\r
+               @Override\r
+               public Component getCustomEditor() {\r
+                       return editor;\r
+               }\r
+       }\r
+\r
+}\r
diff --git a/gui/com/billkuker/rocketry/motorsim/visual/FuelPanel.java b/gui/com/billkuker/rocketry/motorsim/visual/FuelPanel.java
new file mode 100644 (file)
index 0000000..2a9ca06
--- /dev/null
@@ -0,0 +1,43 @@
+package com.billkuker.rocketry.motorsim.visual;
+
+import javax.measure.quantity.Pressure;
+import javax.measure.quantity.Velocity;
+import javax.measure.unit.SI;
+import javax.swing.BoxLayout;
+import javax.swing.JPanel;
+import javax.swing.JSplitPane;
+
+import org.jscience.physics.amount.Amount;
+
+import com.billkuker.rocketry.motorsim.Fuel;
+
+public class FuelPanel extends JSplitPane {
+       private static final long serialVersionUID = 1L;
+
+       public FuelPanel(Fuel f) {
+               super(JSplitPane.HORIZONTAL_SPLIT);
+               setName("Fuel");
+               Chart<Pressure, Velocity> burnRate;
+               try {
+                       burnRate = new Chart<Pressure, Velocity>(SI.MEGA(SI.PASCAL),
+                                       SI.METERS_PER_SECOND, f, "burnRate");
+               } catch (NoSuchMethodException e) {
+                       throw new Error(e);
+               }
+               burnRate.setDomain(burnRate.new IntervalDomain(Amount.valueOf(0, SI
+                               .MEGA(SI.PASCAL)), Amount.valueOf(11, SI.MEGA(SI.PASCAL)), 20));
+
+               final JPanel p = new JPanel();
+               p.setLayout(new BoxLayout(p, BoxLayout.Y_AXIS));
+
+               p.add(new Editor(f));
+               try {
+                       p.add(new Editor(f.getCombustionProduct()));
+               } catch (Exception e) {
+
+               }
+
+               setLeftComponent(p);
+               setRightComponent(burnRate);
+       }
+}
diff --git a/gui/com/billkuker/rocketry/motorsim/visual/GrainPanel.java b/gui/com/billkuker/rocketry/motorsim/visual/GrainPanel.java
new file mode 100644 (file)
index 0000000..35974ec
--- /dev/null
@@ -0,0 +1,245 @@
+package com.billkuker.rocketry.motorsim.visual;\r
+\r
+import java.awt.BasicStroke;\r
+import java.awt.BorderLayout;\r
+import java.awt.Color;\r
+import java.awt.Component;\r
+import java.awt.Dimension;\r
+import java.awt.Graphics;\r
+import java.awt.Graphics2D;\r
+import java.awt.Rectangle;\r
+import java.awt.geom.AffineTransform;\r
+import java.beans.PropertyChangeEvent;\r
+import java.beans.PropertyChangeListener;\r
+import java.text.NumberFormat;\r
+\r
+import javax.measure.quantity.Area;\r
+import javax.measure.quantity.Length;\r
+import javax.measure.quantity.Volume;\r
+import javax.measure.unit.SI;\r
+import javax.swing.JFrame;\r
+import javax.swing.JLabel;\r
+import javax.swing.JPanel;\r
+import javax.swing.JSlider;\r
+import javax.swing.JSplitPane;\r
+import javax.swing.WindowConstants;\r
+import javax.swing.event.ChangeEvent;\r
+import javax.swing.event.ChangeListener;\r
+\r
+import org.jscience.physics.amount.Amount;\r
+\r
+import com.billkuker.rocketry.motorsim.ChangeListening;\r
+import com.billkuker.rocketry.motorsim.Grain;\r
+\r
+public class GrainPanel extends JPanel {\r
+       private static final long serialVersionUID = 1L;\r
+       private Amount<Length> displayedRegression = Amount.valueOf(0, SI.MILLIMETER);\r
+       private JLabel l = new JLabel();\r
+       private Chart<Length,Area> area;\r
+       Chart<Length, Volume> volume;\r
+       private XC xc;\r
+       private Grain grain;\r
+       \r
+       public GrainPanel(Grain g){\r
+               super(new BorderLayout());\r
+\r
+               grain = g;\r
+               \r
+               if ( g instanceof ChangeListening.Subject ){\r
+                       ((ChangeListening.Subject)g).addPropertyChangeListener(new PropertyChangeListener(){\r
+                               public void propertyChange(PropertyChangeEvent evt) {\r
+                                       repaint();\r
+                                       area.setDomain(area.new IntervalDomain(Amount.valueOf(0, SI.MILLIMETER), grain.webThickness()));\r
+                                       volume.setDomain(volume.new IntervalDomain(Amount.valueOf(0, SI.MILLIMETER), grain.webThickness()));\r
+                               }\r
+                       });\r
+               }\r
+\r
+\r
+               try {\r
+\r
+                       area = new Chart<Length, Area>(\r
+                                       SI.MILLIMETER,\r
+                                       SI.MILLIMETER.pow(2).asType(Area.class),\r
+                                       grain,\r
+                                       "surfaceArea");\r
+                       area.setDomain(area.new IntervalDomain(Amount.valueOf(0, SI.MILLIMETER), grain.webThickness()));\r
+                       \r
+                       volume = new Chart<Length, Volume>(\r
+                                       SI.MILLIMETER,\r
+                                       SI.MILLIMETER.pow(3).asType(Volume.class),\r
+                                       grain,\r
+                                       "volume");\r
+                       volume.setDomain(volume.new IntervalDomain(Amount.valueOf(0, SI.MILLIMETER), grain.webThickness()));\r
+\r
+                       area.setMaximumSize(new Dimension(200,100));\r
+                       volume.setMaximumSize(new Dimension(200,100));\r
+                       \r
+\r
+               } catch (ClassCastException e) {\r
+                       // TODO Auto-generated catch block\r
+                       e.printStackTrace();\r
+               } catch (NoSuchMethodException e) {\r
+                       // TODO Auto-generated catch block\r
+                       e.printStackTrace();\r
+               }\r
+               \r
+               addComponents(\r
+                               xc = new XC(grain),\r
+                               new SL(),\r
+                               l,\r
+                               area,\r
+                               volume\r
+               );\r
+       }\r
+       \r
+       protected void addComponents(\r
+                       Component crossSection,\r
+                       Component slider,\r
+                       Component label,\r
+                       Component area,\r
+                       Component volume\r
+       ){\r
+               \r
+               JSplitPane v = new JSplitPane(JSplitPane.VERTICAL_SPLIT);\r
+               JSplitPane h = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT);\r
+               \r
+               JPanel graphics = new JPanel(new BorderLayout());\r
+               graphics.add(crossSection, BorderLayout.CENTER);\r
+               graphics.add(label, BorderLayout.NORTH);\r
+               graphics.add(slider, BorderLayout.SOUTH);\r
+       \r
+               v.setTopComponent(h);\r
+               v.setBottomComponent(area);\r
+               h.setLeftComponent(graphics);\r
+               h.setRightComponent(volume);\r
+               add(v);\r
+               \r
+               h.resetToPreferredSizes();\r
+               v.resetToPreferredSizes();\r
+\r
+       }\r
+       \r
+       public void setDisplayedRegression( Amount<Length> r ){\r
+               displayedRegression = r;\r
+               \r
+               NumberFormat nf = NumberFormat.getInstance();\r
+               nf.setMaximumFractionDigits(2);\r
+               l.setText("Regression: " + nf.format(displayedRegression.doubleValue(SI.MILLIMETER)) + "mm");\r
+               \r
+               area.mark(displayedRegression);\r
+               volume.mark(displayedRegression);\r
+               if ( xc != null )\r
+                       xc.repaint();\r
+       }\r
+       \r
+       private class XC extends JPanel{\r
+               private static final long serialVersionUID = 1L;\r
+               Grain grain;\r
+               public XC(Grain g){\r
+                       grain = g;\r
+                       java.awt.geom.Area unburnt = grain.getSideView(Amount.valueOf(0, SI.MILLIMETER));\r
+                       \r
+                       Rectangle bounds = unburnt.getBounds();\r
+                       double max = bounds.getWidth();\r
+                       if ( bounds.getHeight() > max )\r
+                               max = bounds.getHeight();\r
+                       int w = (int)(bounds.getWidth() * 200.0 / max);\r
+                       if ( w < 40 )\r
+                               w = 40;\r
+                       \r
+                       Dimension sz = new Dimension(240+w, 250);\r
+                       setMinimumSize(sz);\r
+                       setPreferredSize(sz);\r
+                       setMaximumSize(sz);\r
+               }\r
+               public void paint(Graphics g){\r
+                       super.paint(g);\r
+                       Graphics2D g2d = (Graphics2D)g;\r
+                       g2d.translate(10, 30);\r
+                       /*\r
+                       grain.draw(g2d, displayedRegression );\r
+                       */\r
+                       \r
+                       {\r
+                               AffineTransform t = g2d.getTransform();\r
+                               java.awt.geom.Area unburnt = grain.getCrossSection(Amount.valueOf(0, SI.MILLIMETER));\r
+                               \r
+                               Rectangle bounds = unburnt.getBounds();\r
+                               g2d.scale(200 / bounds.getWidth(), 200 / bounds.getHeight());\r
+                               g2d.translate(-bounds.getX(), -bounds.getY());\r
+       \r
+                               //Draw the fuel that is left\r
+                               java.awt.geom.Area burning = grain.getCrossSection(displayedRegression);\r
+                               g2d.setColor(Color.RED);\r
+                               g2d.fill(burning);\r
+                               //Draw the fuel that is left\r
+                               java.awt.geom.Area left = grain.getCrossSection(displayedRegression.plus(grain.webThickness().divide(30)));\r
+                               g2d.setColor(Color.GRAY);\r
+                               g2d.fill(left);\r
+                               //Draw the outline of the unburnt grain\r
+                               g2d.setStroke(new BasicStroke(1.0f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_ROUND));\r
+                               g2d.setColor(Color.BLACK);\r
+                               g2d.draw(unburnt);\r
+                               //untranslate\r
+                               g2d.setTransform(t);\r
+                       }\r
+                       {\r
+                               AffineTransform t = g2d.getTransform();\r
+                               java.awt.geom.Area unburnt = grain.getSideView(Amount.valueOf(0, SI.MILLIMETER));\r
+                               \r
+                               Rectangle bounds = unburnt.getBounds();\r
+                               g2d.translate(220, 0);\r
+                               \r
+                               double max = bounds.getWidth();\r
+                               if ( bounds.getHeight() > max )\r
+                                       max = bounds.getHeight();\r
+                               \r
+                               g2d.scale(200 / max, 200 / max);\r
+                               g2d.translate(-bounds.getX(), -bounds.getY());\r
+       \r
+                               //Draw the fuel that is left\r
+                               java.awt.geom.Area burning = grain.getSideView(displayedRegression);\r
+                               g2d.setColor(Color.RED);\r
+                               g2d.fill(burning);\r
+                               //Draw the fuel that is left\r
+                               java.awt.geom.Area left = grain.getSideView(displayedRegression.plus(grain.webThickness().divide(30)));\r
+                               g2d.setColor(Color.GRAY);\r
+                               g2d.fill(left);\r
+                               //Draw the outline of the unburnt grain\r
+                               g2d.setColor(Color.BLACK);\r
+                               g2d.draw(unburnt);\r
+                               //untranslate\r
+                               g2d.setTransform(t);\r
+                       }\r
+                       \r
+               }\r
+       }\r
+       \r
+       private class SL extends JSlider implements ChangeListener{\r
+               private static final long serialVersionUID = 1L;\r
+               private static final int STEPS = 60;\r
+               public SL(){\r
+                       addChangeListener(this);\r
+                       setMinimum(0);\r
+                       setMaximum(STEPS);\r
+                       setValue(0);\r
+               }\r
+               \r
+               public void stateChanged(ChangeEvent e) {\r
+                       double r = ((SL)e.getSource()).getValue();\r
+\r
+                       setDisplayedRegression(grain.webThickness().divide(STEPS).times(r));\r
+               }\r
+       }\r
+       \r
+       public void showAsWindow(){\r
+               JFrame f = new JFrame();\r
+               f.setTitle(grain.getClass().getName());\r
+               f.setSize(1024,600);\r
+               f.setContentPane(this);\r
+               f.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);\r
+               f.setVisible(true);\r
+       }\r
+\r
+}\r
diff --git a/gui/com/billkuker/rocketry/motorsim/visual/HardwarePanel.java b/gui/com/billkuker/rocketry/motorsim/visual/HardwarePanel.java
new file mode 100644 (file)
index 0000000..00e1fa5
--- /dev/null
@@ -0,0 +1,104 @@
+package com.billkuker.rocketry.motorsim.visual;\r
+\r
+import java.awt.BasicStroke;\r
+import java.awt.Color;\r
+import java.awt.Graphics;\r
+import java.awt.Graphics2D;\r
+import java.awt.Rectangle;\r
+import java.awt.Shape;\r
+import java.beans.PropertyChangeEvent;\r
+import java.beans.PropertyChangeListener;\r
+\r
+import javax.measure.unit.SI;\r
+import javax.swing.JFrame;\r
+import javax.swing.JPanel;\r
+import javax.swing.WindowConstants;\r
+\r
+import org.jscience.physics.amount.Amount;\r
+\r
+import com.billkuker.rocketry.motorsim.Chamber;\r
+import com.billkuker.rocketry.motorsim.ChangeListening;\r
+import com.billkuker.rocketry.motorsim.ConvergentDivergentNozzle;\r
+import com.billkuker.rocketry.motorsim.CylindricalChamber;\r
+import com.billkuker.rocketry.motorsim.Nozzle;\r
+\r
+public class HardwarePanel extends JPanel {\r
+       private static final long serialVersionUID = 1L;\r
+       private Nozzle nozzle;\r
+       private Chamber chamber;\r
+       \r
+       public HardwarePanel(Nozzle n, Chamber c){\r
+               nozzle = n;\r
+               chamber = c;\r
+               if ( n instanceof ChangeListening.Subject ){\r
+                       ((ChangeListening.Subject)n).addPropertyChangeListener(new PropertyChangeListener(){\r
+                               public void propertyChange(PropertyChangeEvent evt) {\r
+                                       repaint();\r
+                               }\r
+                       });\r
+               }\r
+               if ( c instanceof ChangeListening.Subject ){\r
+                       ((ChangeListening.Subject)c).addPropertyChangeListener(new PropertyChangeListener(){\r
+                               public void propertyChange(PropertyChangeEvent evt) {\r
+                                       repaint();\r
+                               }\r
+                       });\r
+               }\r
+       }\r
+       \r
+       public void paint(Graphics g){\r
+               super.paint(g);\r
+               Graphics2D g2d = (Graphics2D)g;\r
+               g2d.translate(10, 10);\r
+\r
+               g2d.setColor(Color.black);\r
+               \r
+               Shape c = chamber.chamberShape();\r
+               \r
+               Shape n = nozzle.nozzleShape(((CylindricalChamber)chamber).getID());\r
+               \r
+               Rectangle cb = c.getBounds();\r
+               Rectangle nb = n.getBounds();\r
+               double w, h;\r
+               w = Math.max(cb.getWidth(), nb.getWidth());\r
+               h = cb.getHeight() + nb.getHeight();\r
+               \r
+               double mw, mh;\r
+               mw = getHeight() - 10;\r
+               mh = getWidth() - 10;\r
+               \r
+               double sw, sh, s;\r
+               sw = mw / w;\r
+               sh = mh / h;\r
+               s = Math.min(sw, sh);\r
+               \r
+               g2d.rotate(-Math.PI / 2);\r
+               \r
+               g2d.translate(0, -cb.getY() - 5);\r
+               g2d.scale(s, s);\r
+               g2d.translate(-(getHeight()/(s*2)), 0);\r
+               \r
+               g2d.setStroke(new BasicStroke(1));\r
+               g2d.draw( c );\r
+               g2d.translate(0, cb.getHeight());\r
+               \r
+               g2d.draw(n);\r
+       }\r
+       \r
+       public void showAsWindow(){\r
+               JFrame f = new JFrame();\r
+               f.setSize(220,250);\r
+               f.setContentPane(this);\r
+               f.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);\r
+               f.setVisible(true);\r
+       }\r
+       \r
+       public static void main(String args[]) throws Exception{\r
+               ConvergentDivergentNozzle n = new ConvergentDivergentNozzle();\r
+               CylindricalChamber c = new CylindricalChamber();\r
+               n.setThroatDiameter(Amount.valueOf(10, SI.MILLIMETER));\r
+               n.setExitDiameter(Amount.valueOf(20, SI.MILLIMETER));\r
+               //new Editor(n).showAsWindow();\r
+               new HardwarePanel(n,c).showAsWindow();\r
+       }\r
+}\r
diff --git a/gui/com/billkuker/rocketry/motorsim/visual/workbench/BurnWatcher.java b/gui/com/billkuker/rocketry/motorsim/visual/workbench/BurnWatcher.java
new file mode 100644 (file)
index 0000000..7301cbd
--- /dev/null
@@ -0,0 +1,7 @@
+package com.billkuker.rocketry.motorsim.visual.workbench;
+
+import com.billkuker.rocketry.motorsim.Burn;
+
+public interface BurnWatcher {
+       public void replace( Burn oldBurn, Burn newBurn);
+}
diff --git a/gui/com/billkuker/rocketry/motorsim/visual/workbench/MotorEditor.java b/gui/com/billkuker/rocketry/motorsim/visual/workbench/MotorEditor.java
new file mode 100644 (file)
index 0000000..2e7ecbe
--- /dev/null
@@ -0,0 +1,429 @@
+package com.billkuker.rocketry.motorsim.visual.workbench;\r
+\r
+import java.awt.BorderLayout;\r
+import java.awt.Dimension;\r
+import java.awt.event.ActionEvent;\r
+import java.awt.event.ActionListener;\r
+import java.awt.event.FocusEvent;\r
+import java.awt.event.FocusListener;\r
+import java.beans.PropertyChangeEvent;\r
+import java.beans.PropertyChangeListener;\r
+import java.beans.PropertyVetoException;\r
+import java.io.IOException;\r
+import java.util.HashMap;\r
+import java.util.Map;\r
+import java.util.Vector;\r
+\r
+import javax.measure.quantity.Length;\r
+import javax.measure.unit.SI;\r
+import javax.swing.Box;\r
+import javax.swing.BoxLayout;\r
+import javax.swing.ComboBoxModel;\r
+import javax.swing.JButton;\r
+import javax.swing.JComboBox;\r
+import javax.swing.JFrame;\r
+import javax.swing.JLabel;\r
+import javax.swing.JPanel;\r
+import javax.swing.JProgressBar;\r
+import javax.swing.JSplitPane;\r
+import javax.swing.JTabbedPane;\r
+import javax.swing.JTextArea;\r
+import javax.swing.JTextField;\r
+import javax.swing.SwingUtilities;\r
+import javax.swing.UIManager;\r
+import javax.swing.WindowConstants;\r
+\r
+import org.apache.log4j.Logger;\r
+import org.fife.ui.rsyntaxtextarea.RSyntaxTextArea;\r
+import org.fife.ui.rsyntaxtextarea.SyntaxConstants;\r
+import org.jscience.physics.amount.Amount;\r
+\r
+import com.billkuker.rocketry.motorsim.Burn;\r
+import com.billkuker.rocketry.motorsim.Chamber;\r
+import com.billkuker.rocketry.motorsim.ChangeListening;\r
+import com.billkuker.rocketry.motorsim.ConvergentDivergentNozzle;\r
+import com.billkuker.rocketry.motorsim.CylindricalChamber;\r
+import com.billkuker.rocketry.motorsim.Fuel;\r
+import com.billkuker.rocketry.motorsim.Grain;\r
+import com.billkuker.rocketry.motorsim.Motor;\r
+import com.billkuker.rocketry.motorsim.Nozzle;\r
+import com.billkuker.rocketry.motorsim.RocketScience;\r
+import com.billkuker.rocketry.motorsim.fuel.KNSU;\r
+import com.billkuker.rocketry.motorsim.grain.CSlot;\r
+import com.billkuker.rocketry.motorsim.grain.CoredCylindricalGrain;\r
+import com.billkuker.rocketry.motorsim.grain.EndBurner;\r
+import com.billkuker.rocketry.motorsim.grain.Finocyl;\r
+import com.billkuker.rocketry.motorsim.grain.Moonburner;\r
+import com.billkuker.rocketry.motorsim.grain.MultiGrain;\r
+import com.billkuker.rocketry.motorsim.grain.RodAndTubeGrain;\r
+import com.billkuker.rocketry.motorsim.io.MotorIO;\r
+import com.billkuker.rocketry.motorsim.visual.BurnPanel;\r
+import com.billkuker.rocketry.motorsim.visual.Editor;\r
+import com.billkuker.rocketry.motorsim.visual.GrainPanel;\r
+import com.billkuker.rocketry.motorsim.visual.HardwarePanel;\r
+\r
+public class MotorEditor extends JTabbedPane implements PropertyChangeListener {\r
+       private static final long serialVersionUID = 1L;\r
+       private static Logger log = Logger.getLogger(MotorEditor.class);\r
+       RSyntaxTextArea text = new RSyntaxTextArea();\r
+       Motor motor;\r
+       GrainEditor grainEditor;\r
+       BurnTab bt;\r
+       Burn burn;\r
+\r
+       private Vector<BurnWatcher> burnWatchers = new Vector<BurnWatcher>();\r
+       private ComboBoxModel availableFuels;\r
+\r
+       //private static final int XML_TAB = 0;\r
+       private static final int CASING_TAB = 0;\r
+       private static final int GRAIN_TAB = 1;\r
+       private static final int BURN_TAB = 2;\r
+\r
+       @SuppressWarnings("unchecked")\r
+       private Class[] grainTypes = { CoredCylindricalGrain.class, Finocyl.class,\r
+                       Moonburner.class, RodAndTubeGrain.class, CSlot.class, EndBurner.class };\r
+\r
+       private abstract class Chooser<T> extends JPanel {\r
+               private static final long serialVersionUID = 1L;\r
+               private Class<? extends T>[] types;\r
+               private Map<Class<? extends T>, T> old = new HashMap<Class<? extends T>, T>();\r
+\r
+               public Chooser(T initial, Class<? extends T>... ts) {\r
+                       types = ts;\r
+                       if ( initial != null )\r
+                               old.put((Class<? extends T>)initial.getClass(), initial);\r
+                       setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));\r
+                       for (final Class<? extends T> c : types) {\r
+                               JButton b = new JButton(c.getSimpleName());\r
+                               add(b);\r
+                               b.addActionListener(new ActionListener() {\r
+                                       public void actionPerformed(ActionEvent e) {\r
+                                               try {\r
+                                                       T val = old.get(c);\r
+                                                       if ( val == null ){\r
+                                                               System.err.println("CREATED NEW =========================");\r
+                                                               val = c.newInstance();\r
+                                                               old.put(c, val);\r
+                                                       }\r
+                                                       choiceMade(val);\r
+                                               } catch (InstantiationException e1) {\r
+                                                       e1.printStackTrace();\r
+                                               } catch (IllegalAccessException e1) {\r
+                                                       e1.printStackTrace();\r
+                                               }\r
+                                       }\r
+                               });\r
+                       }\r
+               }\r
+\r
+               protected abstract void choiceMade(T o);\r
+       }\r
+\r
+       private class BurnTab extends JPanel {\r
+               private static final long serialVersionUID = 1L;\r
+               private Thread currentThread;\r
+               \r
+               public BurnTab() {\r
+                       setLayout(new BorderLayout());\r
+                       setName("Simulation Results");\r
+                       reBurn();\r
+               }\r
+               \r
+               private class BurnCanceled extends RuntimeException{\r
+                       private static final long serialVersionUID = 1L;\r
+               };\r
+\r
+               public void reBurn() {\r
+                       removeAll();\r
+                       currentThread = new Thread() {\r
+                               public void run() {\r
+                                       final Thread me = this;\r
+                                       final JProgressBar bar = new JProgressBar(0, 100);\r
+                                       add(bar, BorderLayout.NORTH);\r
+                                       final JLabel progress = new JLabel();\r
+                                       add(progress, BorderLayout.CENTER);\r
+                                       try {\r
+                                               final Burn b = new Burn(motor,\r
+                                                               new Burn.BurnProgressListener() {\r
+                                                                       @Override\r
+                                                                       public void setProgress(float f) {\r
+                                                                               int pct = (int)(f*100);\r
+                                                                               bar.setValue(pct);\r
+                                                                               Amount<Length> web = motor.getGrain().webThickness();\r
+                                                                               Amount<Length> remaining = web.times(1.0 - f);\r
+                                                                               \r
+                                                                               progress.setText("Progress: " + pct + "% (" + RocketScience.approx(remaining) + " web thickness remaining)");\r
+                                                                               if ( currentThread != me ){\r
+                                                                                       throw new BurnCanceled();\r
+                                                                               }\r
+                                                                       }\r
+                                                               });\r
+\r
+                                               final BurnPanel bp = new BurnPanel(b);\r
+                                               SwingUtilities.invokeLater(new Thread() {\r
+                                                       public void run() {\r
+                                                               remove(bar);\r
+                                                               add(bp, BorderLayout.CENTER);\r
+\r
+                                                               for (BurnWatcher bw : burnWatchers)\r
+                                                                       bw.replace(burn, b);\r
+                                                               burn = b;\r
+\r
+                                                               revalidate();\r
+                                                       }\r
+                                               });\r
+                                       } catch (BurnCanceled c){\r
+                                               log.info("Burn Canceled!");\r
+                                       } catch (Exception e) {\r
+                                               remove(bar);\r
+                                               JTextArea t = new JTextArea(e.getMessage());\r
+                                               t.setEditable(false);\r
+                                               add(t);\r
+                                       }\r
+                               }\r
+                       };\r
+                       currentThread.start();\r
+               }\r
+       }\r
+\r
+       private class GrainEditor extends JSplitPane {\r
+               private static final long serialVersionUID = 1L;\r
+\r
+               public GrainEditor(final Grain g) {\r
+                       super(JSplitPane.HORIZONTAL_SPLIT);\r
+                       setName("Grain Geometry");\r
+                       setRightComponent(new GrainPanel(g));\r
+                       if (g instanceof Grain.Composite) {\r
+                               final JPanel p = new JPanel();\r
+                               p.setLayout(new BoxLayout(p, BoxLayout.Y_AXIS));\r
+                               p.add(new Editor(g));\r
+                               for (Grain gg : ((Grain.Composite) g).getGrains()) {\r
+                                       final int grainEditorIndex = p.getComponentCount() + 1;\r
+                                       p.add(new Chooser<Grain>(gg, grainTypes) {\r
+                                               private static final long serialVersionUID = 1L;\r
+\r
+                                               @Override\r
+                                               protected void choiceMade(Grain ng) {\r
+                                                       if (g instanceof MultiGrain) {\r
+                                                               ((MultiGrain) g).setGrain(ng);\r
+                                                               p.remove(grainEditorIndex);\r
+                                                               p.add(new Editor(ng), grainEditorIndex);\r
+                                                               p.remove(0);\r
+                                                               p.add(new Editor(g), 0);\r
+                                                       }\r
+                                               }\r
+                                       });\r
+                                       p.add(new Editor(gg));\r
+                                       if (gg instanceof ChangeListening.Subject) {\r
+                                               ((ChangeListening.Subject) gg)\r
+                                                               .addPropertyChangeListener(MotorEditor.this);\r
+                                       }\r
+                               }\r
+                               setLeftComponent(p);\r
+                       } else {\r
+                               setLeftComponent(new Editor(g));\r
+                       }\r
+                       // setDividerLocation(.25);\r
+                       // setResizeWeight(.25);\r
+                       if (g instanceof ChangeListening.Subject) {\r
+                               ((ChangeListening.Subject) g)\r
+                                               .addPropertyChangeListener(MotorEditor.this);\r
+                       }\r
+               }\r
+       }\r
+\r
+       private class CaseEditor extends JSplitPane {\r
+               private static final long serialVersionUID = 1L;\r
+\r
+               public CaseEditor(Nozzle n, Chamber c) {\r
+                       super(JSplitPane.VERTICAL_SPLIT);\r
+                       setName("General Parameters");\r
+                       \r
+                       JPanel parts = new JPanel();\r
+                       parts.setLayout(new BoxLayout(parts, BoxLayout.X_AXIS));\r
+                       setTopComponent(parts);\r
+                       setBottomComponent(new HardwarePanel(n, c));\r
+                       \r
+                       JPanel nameAndFuel = new JPanel();\r
+                       nameAndFuel.setLayout(new BoxLayout(nameAndFuel, BoxLayout.Y_AXIS));\r
+\r
+                       nameAndFuel.add(new JLabel("Name:"));\r
+                       nameAndFuel.add(new JTextField(motor.getName()) {\r
+                               private static final long serialVersionUID = 1L;\r
+                               {\r
+                                       setMinimumSize(new Dimension(200, 20));\r
+                                       setMaximumSize(new Dimension(Short.MAX_VALUE, 20));\r
+                                       final JTextField t = this;\r
+                                       addFocusListener(new FocusListener() {\r
+\r
+                                               @Override\r
+                                               public void focusLost(FocusEvent e) {\r
+                                                       String n = t.getText();\r
+                                                       if (!"".equals(n) && !n.equals(motor.getName())) {\r
+                                                               motor.setName(n);\r
+                                                       } else {\r
+                                                               t.setText(motor.getName());\r
+                                                       }\r
+                                               }\r
+\r
+                                               @Override\r
+                                               public void focusGained(FocusEvent e) {\r
+\r
+                                               }\r
+                                       });\r
+\r
+                               }\r
+                       });\r
+                       nameAndFuel.add(new JLabel("Fuel:"));\r
+                       nameAndFuel.add( new JComboBox(availableFuels){{\r
+                               setMinimumSize(new Dimension(200, 20));\r
+                               setMaximumSize(new Dimension(Short.MAX_VALUE, 20));\r
+                               addActionListener(new ActionListener(){\r
+                                       @Override\r
+                                       public void actionPerformed(ActionEvent e) {\r
+                                               motor.setFuel((Fuel)getSelectedItem());\r
+                                               System.out.println("FUEL CHANGED");\r
+                                       }});\r
+                       }});\r
+                       nameAndFuel.add(Box.createVerticalGlue());\r
+                       parts.add(nameAndFuel);\r
+                       \r
+                       JPanel casing = new JPanel();\r
+                       casing.setLayout(new BoxLayout(casing, BoxLayout.Y_AXIS));\r
+                       casing.add(new JLabel("Casing:"));\r
+                       casing.add(new Editor(c));\r
+                       parts.add(casing);\r
+                       \r
+                       JPanel nozzle = new JPanel();\r
+                       nozzle.setLayout(new BoxLayout(nozzle, BoxLayout.Y_AXIS));\r
+                       nozzle.add(new JLabel("Nozzle:"));\r
+                       nozzle.add(new Editor(n));\r
+                       parts.add(nozzle);\r
+\r
+                       if (n instanceof ChangeListening.Subject) {\r
+                               ((ChangeListening.Subject) n)\r
+                                               .addPropertyChangeListener(MotorEditor.this);\r
+                       }\r
+                       if (c instanceof ChangeListening.Subject) {\r
+                               ((ChangeListening.Subject) c)\r
+                                               .addPropertyChangeListener(MotorEditor.this);\r
+                       }\r
+               }\r
+       }\r
+\r
+       {\r
+               text.setSyntaxEditingStyle(SyntaxConstants.SYNTAX_STYLE_XML);\r
+\r
+       }\r
+\r
+       public MotorEditor(Motor m, ComboBoxModel fuels) {\r
+               super(JTabbedPane.BOTTOM);\r
+               this.availableFuels = fuels;\r
+               text.setName("XML");\r
+               text.setEditable(false);\r
+               //add(text, XML_TAB);\r
+               setMotor(m, true);\r
+       }\r
+\r
+       public Motor getMotor() {\r
+               return motor;\r
+       }\r
+\r
+       private void reText() {\r
+               try {\r
+                       text.setText(MotorIO.writeMotor(motor));\r
+               } catch (IOException e) {\r
+                       throw new Error(e);\r
+               }\r
+       }\r
+\r
+       private void setMotor(Motor m, boolean retext) {\r
+               if (motor != null)\r
+                       motor.removePropertyChangeListener(this);\r
+               motor = m;\r
+               motor.addPropertyChangeListener(this);\r
+               if (retext)\r
+                       reText();\r
+               if (grainEditor != null)\r
+                       remove(grainEditor);\r
+               while (getTabCount() > 1)\r
+                       removeTabAt(1);\r
+               add(new CaseEditor(motor.getNozzle(), motor.getChamber()), CASING_TAB);\r
+               add(new GrainEditor(motor.getGrain()), GRAIN_TAB);\r
+               add(bt = new BurnTab(), BURN_TAB);\r
+       }\r
+\r
+       @Deprecated\r
+       public static Motor defaultMotor() {\r
+               Motor m = new Motor();\r
+               m.setName("Example Motor");\r
+               m.setFuel(new KNSU());\r
+\r
+               CylindricalChamber c = new CylindricalChamber();\r
+               c.setLength(Amount.valueOf(200, SI.MILLIMETER));\r
+               c.setID(Amount.valueOf(30, SI.MILLIMETER));\r
+               m.setChamber(c);\r
+\r
+               CoredCylindricalGrain g = new CoredCylindricalGrain();\r
+               try {\r
+                       g.setLength(Amount.valueOf(70, SI.MILLIMETER));\r
+                       g.setOD(Amount.valueOf(30, SI.MILLIMETER));\r
+                       g.setID(Amount.valueOf(10, SI.MILLIMETER));\r
+               } catch (PropertyVetoException v) {\r
+                       throw new Error(v);\r
+               }\r
+\r
+               m.setGrain(new MultiGrain(g, 2));\r
+\r
+               ConvergentDivergentNozzle n = new ConvergentDivergentNozzle();\r
+               n.setThroatDiameter(Amount.valueOf(7.962, SI.MILLIMETER));\r
+               n.setExitDiameter(Amount.valueOf(13.79, SI.MILLIMETER));\r
+               n.setEfficiency(.85);\r
+               m.setNozzle(n);\r
+\r
+               return m;\r
+       }\r
+\r
+       public void focusOnObject(Object o) {\r
+               if (o instanceof Grain)\r
+                       setSelectedIndex(GRAIN_TAB);\r
+               if (o instanceof Chamber || o instanceof Nozzle)\r
+                       setSelectedIndex(CASING_TAB);\r
+       }\r
+\r
+       public void addBurnWatcher(BurnWatcher bw) {\r
+               burnWatchers.add(bw);\r
+       }\r
+\r
+       @Deprecated\r
+       public void showAsWindow() {\r
+               JFrame f = new JFrame();\r
+               f.setSize(1024, 768);\r
+               f.setContentPane(this);\r
+               f.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);\r
+               f.setVisible(true);\r
+       }\r
+\r
+       public static void main(String args[]) throws Exception {\r
+               try {\r
+                       UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());\r
+               } catch (Exception e1) {\r
+                       e1.printStackTrace();\r
+               }\r
+               Vector<Fuel> ff = new Vector<Fuel>();\r
+               ff.add(new KNSU());\r
+               //new MotorEditor(defaultMotor(), ff).showAsWindow();\r
+       }\r
+\r
+       public void propertyChange(PropertyChangeEvent evt) {\r
+               reText();\r
+               // Dont re-burn for a name change!\r
+               if (!evt.getPropertyName().equals("Name")){\r
+                       bt.reBurn();\r
+               } else {\r
+                       for (BurnWatcher bw : burnWatchers)\r
+                               bw.replace(burn, burn);\r
+               }\r
+       }\r
+\r
+}\r
diff --git a/gui/com/billkuker/rocketry/motorsim/visual/workbench/MotorWorkbench.java b/gui/com/billkuker/rocketry/motorsim/visual/workbench/MotorWorkbench.java
new file mode 100644 (file)
index 0000000..b6047ee
--- /dev/null
@@ -0,0 +1,424 @@
+package com.billkuker.rocketry.motorsim.visual.workbench;\r
+\r
+import java.awt.BorderLayout;\r
+import java.awt.Dimension;\r
+import java.awt.FileDialog;\r
+import java.awt.event.ActionEvent;\r
+import java.awt.event.ActionListener;\r
+import java.beans.PropertyChangeEvent;\r
+import java.beans.PropertyChangeListener;\r
+import java.io.File;\r
+import java.util.HashMap;\r
+import java.util.Vector;\r
+\r
+import javax.swing.ButtonGroup;\r
+import javax.swing.DefaultComboBoxModel;\r
+import javax.swing.JFrame;\r
+import javax.swing.JMenu;\r
+import javax.swing.JMenuBar;\r
+import javax.swing.JMenuItem;\r
+import javax.swing.JOptionPane;\r
+import javax.swing.JPanel;\r
+import javax.swing.JRadioButtonMenuItem;\r
+import javax.swing.JScrollPane;\r
+import javax.swing.JSeparator;\r
+import javax.swing.JSplitPane;\r
+import javax.swing.JTabbedPane;\r
+import javax.swing.JTree;\r
+import javax.swing.WindowConstants;\r
+import javax.swing.event.TreeSelectionEvent;\r
+import javax.swing.event.TreeSelectionListener;\r
+import javax.swing.tree.DefaultMutableTreeNode;\r
+import javax.swing.tree.TreePath;\r
+import javax.swing.tree.TreeSelectionModel;\r
+\r
+import com.billkuker.rocketry.motorsim.Burn;\r
+import com.billkuker.rocketry.motorsim.Fuel;\r
+import com.billkuker.rocketry.motorsim.Motor;\r
+import com.billkuker.rocketry.motorsim.RocketScience.UnitPreference;\r
+import com.billkuker.rocketry.motorsim.fuel.KNDX;\r
+import com.billkuker.rocketry.motorsim.fuel.KNER;\r
+import com.billkuker.rocketry.motorsim.fuel.KNSB;\r
+import com.billkuker.rocketry.motorsim.fuel.KNSU;\r
+import com.billkuker.rocketry.motorsim.io.ENGExporter;\r
+import com.billkuker.rocketry.motorsim.io.MotorIO;\r
+import com.billkuker.rocketry.motorsim.visual.FuelPanel;\r
+import com.billkuker.rocketry.motorsim.visual.workbench.WorkbenchTreeModel.FuelEditNode;\r
+import com.billkuker.rocketry.motorsim.visual.workbench.WorkbenchTreeModel.FuelNode;\r
+\r
+public class MotorWorkbench extends JFrame implements TreeSelectionListener {\r
+       private static final long serialVersionUID = 1L;\r
+       \r
+       @SuppressWarnings("unchecked")\r
+       private Class[] fuelTypes = { KNSB.class, KNSU.class, KNER.class,\r
+                       KNDX.class };\r
+       \r
+       private JPanel top;\r
+       private JSplitPane split;\r
+       private JTree tree;\r
+       private JTabbedPane motors;\r
+       private WorkbenchTreeModel tm;\r
+       private MultiBurnChart mb;\r
+       private JFrame allBurns;\r
+\r
+       private HashMap<MotorEditor, File> e2f = new HashMap<MotorEditor, File>();\r
+       private HashMap<File, MotorEditor> f2e = new HashMap<File, MotorEditor>();\r
+\r
+       private HashMap<Motor, MotorEditor> m2e = new HashMap<Motor, MotorEditor>();\r
+       \r
+       private DefaultComboBoxModel fuels = new DefaultComboBoxModel();\r
+       \r
+       public MotorWorkbench() {\r
+               setTitle("MotorSim 1.0 RC1");\r
+               addMenu();\r
+               setSize(1024, 768);\r
+               top = new JPanel(new BorderLayout());\r
+               setContentPane(top);\r
+               \r
+               mb = new MultiBurnChart();\r
+               allBurns = new JFrame();\r
+               allBurns.setTitle("All Burns");\r
+               allBurns.setSize(800, 600);\r
+               setDefaultCloseOperation(WindowConstants.HIDE_ON_CLOSE);\r
+               allBurns.add(mb);\r
+\r
+               motors = new JTabbedPane();\r
+\r
+               tree = new JTree(tm = new WorkbenchTreeModel());\r
+               tree.setCellRenderer(new WorkbenchTreeCellRenderer());\r
+               tree.getSelectionModel().setSelectionMode(\r
+                               TreeSelectionModel.SINGLE_TREE_SELECTION);\r
+               tree.setMinimumSize(new Dimension(200, 100));\r
+\r
+               // Listen for when the selection changes.\r
+               tree.addTreeSelectionListener(this);\r
+\r
+               split = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, new JScrollPane(\r
+                               tree), motors);\r
+               split.setDividerLocation(200);\r
+               split.setResizeWeight(0);\r
+               split.resetToPreferredSizes();\r
+               split.revalidate();\r
+               \r
+               top.add(split, BorderLayout.CENTER);\r
+               \r
+               for ( Class<Fuel> f : fuelTypes){\r
+                       try {\r
+                               addFuel(f.newInstance());\r
+                       } catch (InstantiationException e) {\r
+                               // TODO Auto-generated catch block\r
+                               e.printStackTrace();\r
+                       } catch (IllegalAccessException e) {\r
+                               // TODO Auto-generated catch block\r
+                               e.printStackTrace();\r
+                       }\r
+               }\r
+\r
+               setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);\r
+               setVisible(true);\r
+\r
+       }\r
+\r
+       private void addMenu() {\r
+\r
+               setJMenuBar(new JMenuBar() {\r
+                       private static final long serialVersionUID = 1L;\r
+\r
+                       {\r
+                               add(new JMenu("File") {\r
+                                       private static final long serialVersionUID = 1L;\r
+\r
+                                       {\r
+                                               add(new JMenuItem("New Motor") {\r
+                                                       private static final long serialVersionUID = 1L;\r
+                                                       {\r
+                                                               addActionListener(new ActionListener() {\r
+\r
+                                                                       @Override\r
+                                                                       public void actionPerformed(ActionEvent arg0) {\r
+                                                                               addMotor(MotorEditor.defaultMotor(),\r
+                                                                                               null);\r
+                                                                       }\r
+                                                               });\r
+\r
+                                                       }\r
+                                               });\r
+                                               add(new JMenuItem("Open...") {\r
+                                                       private static final long serialVersionUID = 1L;\r
+                                                       {\r
+                                                               addActionListener(new ActionListener() {\r
+                                                                       @Override\r
+                                                                       public void actionPerformed(ActionEvent arg0) {\r
+\r
+                                                                               final FileDialog fd = new FileDialog(MotorWorkbench.this, "Open Motor", FileDialog.LOAD);\r
+                                                                               fd.setVisible(true);\r
+                                                                               if ( fd.getFile() != null ) {\r
+                                                                                       File file = new File(fd.getDirectory() + fd.getFile());\r
+                                                                                       if (f2e.get(file) != null) {\r
+                                                                                               motors.setSelectedComponent(f2e\r
+                                                                                                               .get(file));\r
+                                                                                               return;\r
+                                                                                       }\r
+                                                                                       try {\r
+                                                                                               Motor m = MotorIO\r
+                                                                                                               .readMotor(file);\r
+                                                                                               addMotor(m, file);\r
+                                                                                       } catch (Exception e) {\r
+                                                                                               JOptionPane.showMessageDialog(\r
+                                                                                                               MotorWorkbench.this, e\r
+                                                                                                                               .getMessage());\r
+                                                                                       }\r
+                                                                               }\r
+                                                                       }\r
+                                                               });\r
+                                                       }\r
+                                               });\r
+                                               \r
+                                               add(new JSeparator());\r
+                                               \r
+                                               add(new JMenuItem("Close Motor") {\r
+                                                       private static final long serialVersionUID = 1L;\r
+                                                       {\r
+                                                               addActionListener(new ActionListener() {\r
+                                                                       @Override\r
+                                                                       public void actionPerformed(ActionEvent ev) {\r
+                                                                               MotorEditor e = (MotorEditor) motors\r
+                                                                                               .getSelectedComponent();\r
+                                                                               tm.removeMotor(e.getMotor());\r
+                                                                               motors.remove(e);\r
+                                                                               f2e.remove(e2f.get(e));\r
+                                                                               e2f.remove(e);\r
+                                                                               m2e.remove(e.getMotor());\r
+                                                                               mb.removeBurn(e.burn);\r
+                                                                       }\r
+                                                               });\r
+                                                       }\r
+                                               });\r
+                                               \r
+                                               add(new JMenuItem("Save Motor") {\r
+                                                       private static final long serialVersionUID = 1L;\r
+                                                       {\r
+                                                               addActionListener(new ActionListener() {\r
+                                                                       @Override\r
+                                                                       public void actionPerformed(ActionEvent e) {\r
+                                                                               MotorEditor m = (MotorEditor) motors\r
+                                                                                               .getSelectedComponent();\r
+                                                                               File f = e2f.get(m);\r
+                                                                               if (f != null)\r
+                                                                                       save(m.getMotor(), f);\r
+\r
+                                                                       }\r
+                                                               });\r
+                                                       }\r
+                                               });\r
+                                               add(new JMenuItem("Save Motor As...") {\r
+                                                       private static final long serialVersionUID = 1L;\r
+                                                       {\r
+                                                               addActionListener(new ActionListener() {\r
+                                                                       @Override\r
+                                                                       public void actionPerformed(ActionEvent arg0) {\r
+\r
+                                                                               final FileDialog fd = new FileDialog(MotorWorkbench.this, "Save Motor As", FileDialog.SAVE);\r
+                                                                               fd.setVisible(true);\r
+                                                                               if (fd.getFile() != null ) {\r
+                                                                                       File file = new File(fd.getDirectory() + fd.getFile());\r
+                                                                                       MotorEditor m = (MotorEditor) motors\r
+                                                                                                       .getSelectedComponent();\r
+                                                                                       try {\r
+                                                                                               save(m.getMotor(), file);\r
+                                                                                               e2f.put(m, file);\r
+                                                                                               f2e.put(file, m);\r
+                                                                                               motors.setTitleAt(motors\r
+                                                                                                               .getSelectedIndex(),\r
+                                                                                                               file.getName());\r
+                                                                                               // TODO Set tab title\r
+                                                                                       } catch (Exception e) {\r
+                                                                                               JOptionPane.showMessageDialog(\r
+                                                                                                               MotorWorkbench.this, e\r
+                                                                                                                               .getMessage());\r
+                                                                                       }\r
+                                                                               }\r
+                                                                       }\r
+                                                               });\r
+                                                       }\r
+                                               });\r
+                                               \r
+\r
+                                               add(new JSeparator());\r
+                                               add(new JMenuItem("New Fuel") {\r
+                                                       private static final long serialVersionUID = 1L;\r
+                                                       {\r
+                                                               addActionListener(new ActionListener() {\r
+                                                                       @Override\r
+                                                                       public void actionPerformed(ActionEvent arg0) {\r
+                                                                               newFuel();\r
+                                                                       }\r
+                                                               });\r
+\r
+                                                       }\r
+                                               });\r
+                                               add(new JMenuItem("Save Fuel") {});\r
+                                               add(new JSeparator());\r
+                                               add(new JMenuItem("Export .ENG"){\r
+                                                       private static final long serialVersionUID = 1L;\r
+\r
+                                                       {\r
+                                                               addActionListener(new ActionListener() {\r
+                                                                       @Override\r
+                                                                       public void actionPerformed(ActionEvent arg0) {\r
+\r
+                                                                               final FileDialog fd = new FileDialog(MotorWorkbench.this, "Export .ENG File", FileDialog.SAVE);\r
+                                                                               fd.setFile("motorsim.eng");\r
+                                                                               fd.setVisible(true);\r
+                                                                               if (fd.getFile() != null ) {\r
+                                                                                       File file = new File(fd.getDirectory() + fd.getFile());\r
+                                                                                       Vector<Burn> bb = new Vector<Burn>();\r
+                                                                                       for( MotorEditor me : m2e.values() )\r
+                                                                                               bb.add(me.burn);\r
+                                                                                       try{\r
+                                                                                               ENGExporter.export(bb, file);\r
+                                                                                       } catch ( Exception e ){\r
+                                                                                               e.printStackTrace();\r
+                                                                                       }\r
+                                                                               }\r
+                                                                       }\r
+                                                               });\r
+                                                       }\r
+                                               });\r
+                                       }\r
+                               });\r
+                               add(new JMenu("Settings") {\r
+                                       private static final long serialVersionUID = 1L;\r
+                                       {\r
+                                               ButtonGroup units = new ButtonGroup();\r
+                                               JRadioButtonMenuItem sci = new JRadioButtonMenuItem(\r
+                                                               "SI");\r
+                                               JRadioButtonMenuItem nonsci = new JRadioButtonMenuItem(\r
+                                                               "NonSI");\r
+                                               units.add(sci);\r
+                                               units.add(nonsci);\r
+                                               sci.setSelected(UnitPreference.getUnitPreference() == UnitPreference.SI);\r
+                                               nonsci.setSelected(UnitPreference.getUnitPreference() == UnitPreference.NONSI);\r
+                                               sci.addActionListener(new ActionListener() {\r
+                                                       public void actionPerformed(ActionEvent arg0) {\r
+                                                               UnitPreference\r
+                                                                               .setUnitPreference(UnitPreference.SI);\r
+                                                       }\r
+                                               });\r
+                                               nonsci.addActionListener(new ActionListener() {\r
+                                                       public void actionPerformed(ActionEvent arg0) {\r
+                                                               UnitPreference\r
+                                                                               .setUnitPreference(UnitPreference.NONSI);\r
+                                                       }\r
+                                               });\r
+                                               add(sci);\r
+                                               add(nonsci);\r
+                                       }\r
+                               });\r
+                               add(new JMenu("View") {\r
+                                       private static final long serialVersionUID = 1L;\r
+                                       {\r
+                                               add(new JMenuItem("Show All Motors Graph") {\r
+                                                       private static final long serialVersionUID = 1L;\r
+                                                       {\r
+                                                               addActionListener(new ActionListener() {\r
+                                                                       @Override\r
+                                                                       public void actionPerformed(ActionEvent arg0) {\r
+                                                                               allBurns.setVisible(true);\r
+                                                                               allBurns.toFront();\r
+                                                                       }\r
+                                                               });\r
+                                                       }\r
+                                               });\r
+                                       }\r
+                               });\r
+                       }\r
+               });\r
+       }\r
+       \r
+       private void addFuel(Fuel f){\r
+               fuels.addElement(f);\r
+               FuelPanel fp = new FuelPanel(f);\r
+               FuelNode fn = tm.new FuelNode(fp, f);\r
+               tm.getFuels().add(fn);\r
+               tm.nodeStructureChanged(tm.getFuels());\r
+               motors.addTab(f.getName(), fp);\r
+       }\r
+       \r
+       private void newFuel(){\r
+               final SRFuelEditor ed = new SRFuelEditor();\r
+               fuels.addElement(ed.getFuel());\r
+               final FuelEditNode node = tm.new FuelEditNode(ed);\r
+               tm.getFuels().add(node);\r
+               tm.nodeStructureChanged(tm.getFuels());\r
+               motors.addTab(ed.getFuel().getName(), ed);\r
+               ed.getFuel().addPropertyChangeListener(new PropertyChangeListener(){\r
+                       @Override\r
+                       public void propertyChange(PropertyChangeEvent evt) {\r
+                               if ( evt.getPropertyName().equals("Name")){\r
+                                       for ( int i = 0; i < motors.getTabCount(); i++ ){\r
+                                               if ( motors.getComponent(i) == ed ){\r
+                                                       motors.setTitleAt(i, ed.getFuel().getName());\r
+                                                       tm.nodeChanged(node);\r
+                                               }\r
+                                       }\r
+                               }\r
+                       }});\r
+       }\r
+\r
+       private void save(Motor m, File f) {\r
+               try {\r
+                       MotorIO.writeMotor(m, f);\r
+               } catch (Throwable t) {\r
+                       JOptionPane.showMessageDialog(MotorWorkbench.this, t.getMessage());\r
+               }\r
+       }\r
+\r
+       public void addMotor(Motor m, File f) {\r
+               tm.addMotor(m);\r
+               MotorEditor e = new MotorEditor(m, fuels);\r
+               e.addBurnWatcher(mb);\r
+               String title;\r
+               if (f == null) {\r
+                       title = "New Motor";\r
+               } else {\r
+                       title = f.getName();\r
+                       e2f.put(e, f);\r
+                       f2e.put(f, e);\r
+               }\r
+               m2e.put(m, e);\r
+               motors.addTab(title, e);\r
+       }\r
+\r
+       @Override\r
+       public void valueChanged(TreeSelectionEvent e) {                \r
+               if ( e.getPath().getLastPathComponent() instanceof FuelNode ){\r
+                       FuelNode fen = ((FuelNode)e.getPath().getLastPathComponent());\r
+                               motors.setSelectedComponent(fen.getUserObject());                       \r
+               }\r
+               \r
+               Motor m = getMotor(e.getPath());\r
+               \r
+               if ( m == null )\r
+                       return;\r
+\r
+               motors.setSelectedComponent(m2e.get(m));\r
+\r
+               if (e.getPath().getLastPathComponent() instanceof DefaultMutableTreeNode) {\r
+                       Object o = ((DefaultMutableTreeNode) e.getPath()\r
+                                       .getLastPathComponent()).getUserObject();\r
+                       m2e.get(m).focusOnObject(o);\r
+               }\r
+               \r
+\r
+       }\r
+\r
+       private Motor getMotor(TreePath p) {\r
+               if (p.getLastPathComponent() instanceof WorkbenchTreeModel.MotorNode) {\r
+                       return ((WorkbenchTreeModel.MotorNode) p.getLastPathComponent())\r
+                                       .getUserObject();\r
+               } else if (p.getPath().length > 1)\r
+                       return getMotor(p.getParentPath());\r
+               return null;\r
+       }\r
+}\r
diff --git a/gui/com/billkuker/rocketry/motorsim/visual/workbench/MultiBurnChart.java b/gui/com/billkuker/rocketry/motorsim/visual/workbench/MultiBurnChart.java
new file mode 100644 (file)
index 0000000..ee629d8
--- /dev/null
@@ -0,0 +1,104 @@
+package com.billkuker.rocketry.motorsim.visual.workbench;
+
+import java.awt.BorderLayout;
+import java.util.HashMap;
+
+import javax.measure.quantity.Duration;
+import javax.measure.quantity.Force;
+import javax.measure.unit.SI;
+import javax.measure.unit.Unit;
+import javax.swing.JFrame;
+import javax.swing.JPanel;
+import javax.swing.WindowConstants;
+
+import org.jfree.chart.ChartFactory;
+import org.jfree.chart.ChartPanel;
+import org.jfree.chart.JFreeChart;
+import org.jfree.chart.plot.PlotOrientation;
+import org.jfree.data.xy.XYSeries;
+import org.jfree.data.xy.XYSeriesCollection;
+import org.jscience.physics.amount.Amount;
+
+import com.billkuker.rocketry.motorsim.Burn;
+import com.billkuker.rocketry.motorsim.ConvergentDivergentNozzle;
+import com.billkuker.rocketry.motorsim.Motor;
+import com.billkuker.rocketry.motorsim.RocketScience;
+
+public class MultiBurnChart extends JPanel implements BurnWatcher {
+       private static final long serialVersionUID = 1L;
+
+       private XYSeriesCollection dataset = new XYSeriesCollection();
+
+       private HashMap<Burn, XYSeries> burnToSeries = new HashMap<Burn, XYSeries>();
+       private Unit<Duration> time;
+       private Unit<Force> force;
+
+       @SuppressWarnings("unchecked")
+       public MultiBurnChart() {
+               this.setLayout(new BorderLayout());
+               time = RocketScience.UnitPreference.getUnitPreference()
+                               .getPreferredUnit(SI.SECOND);
+               force = RocketScience.UnitPreference.getUnitPreference()
+                               .getPreferredUnit(SI.NEWTON);
+               JFreeChart chart = ChartFactory.createXYLineChart(
+                               "", // Title
+                               time.toString(), // x-axis Label
+                               force.toString(), // y-axis Label
+                               dataset, PlotOrientation.VERTICAL, // Plot Orientation
+                               true, // Show Legend
+                               true, // Use tool tips
+                               false // Configure chart to generate URLs?
+                               );
+               add(new ChartPanel(chart));
+       }
+
+       public void addBurn(Burn b) {
+               XYSeries s = createSeries(b);
+               burnToSeries.put(b, s);
+               dataset.addSeries(s);
+       }
+
+       private XYSeries createSeries(Burn b) {
+               XYSeries s = new XYSeries(b.getMotor().getName());
+               for( Burn.Interval i : b.getData().values() ){
+                       s.add(i.time.doubleValue(time), i.thrust.doubleValue(force));
+               }
+               return s;
+       }
+
+       public void removeBurn(Burn b) {
+               XYSeries s = burnToSeries.get(b);
+               if (s == null)
+                       return;
+               dataset.removeSeries(s);
+       }
+       
+       public static void main(String args[]) throws Exception{
+               MultiBurnChart c = new MultiBurnChart();
+               
+               JFrame f = new JFrame();
+               f.setSize(1024, 768);
+               f.setContentPane(c);
+               f.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
+               f.setVisible(true);
+
+               Motor m = MotorEditor.defaultMotor();
+               Burn b = new Burn(m);
+               c.addBurn(b);
+               
+               m.setName("Motor2");
+               ((ConvergentDivergentNozzle)m.getNozzle()).setThroatDiameter(Amount.valueOf(3, SI.MILLIMETER));
+               c.addBurn(new Burn(m));
+               
+               Thread.sleep(5000);
+               
+               c.removeBurn(b);
+               
+       }
+
+       @Override
+       public void replace(Burn oldBurn, Burn newBurn) {
+               removeBurn(oldBurn);
+               addBurn(newBurn);
+       }
+}
diff --git a/gui/com/billkuker/rocketry/motorsim/visual/workbench/SRFuelEditor.java b/gui/com/billkuker/rocketry/motorsim/visual/workbench/SRFuelEditor.java
new file mode 100644 (file)
index 0000000..013216f
--- /dev/null
@@ -0,0 +1,331 @@
+package com.billkuker.rocketry.motorsim.visual.workbench;
+
+import java.awt.Dimension;
+import java.awt.FlowLayout;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.beans.PropertyChangeEvent;
+import java.text.DecimalFormat;
+import java.text.NumberFormat;
+import java.util.Collections;
+import java.util.Vector;
+
+import javax.measure.quantity.Pressure;
+import javax.measure.quantity.Velocity;
+import javax.measure.quantity.VolumetricDensity;
+import javax.measure.unit.SI;
+import javax.swing.ButtonGroup;
+import javax.swing.JButton;
+import javax.swing.JFrame;
+import javax.swing.JPanel;
+import javax.swing.JRadioButton;
+import javax.swing.JScrollPane;
+import javax.swing.JSplitPane;
+import javax.swing.JTable;
+import javax.swing.SwingUtilities;
+import javax.swing.event.ChangeEvent;
+import javax.swing.event.ChangeListener;
+import javax.swing.table.AbstractTableModel;
+
+import org.jscience.physics.amount.Amount;
+
+import com.billkuker.rocketry.motorsim.Fuel;
+import com.billkuker.rocketry.motorsim.RocketScience;
+import com.billkuker.rocketry.motorsim.fuel.EditableFuel.EditableCombustionProduct;
+import com.billkuker.rocketry.motorsim.fuel.PiecewiseSaintRobertFuel;
+import com.billkuker.rocketry.motorsim.fuel.SaintRobertFuel;
+import com.billkuker.rocketry.motorsim.fuel.SaintRobertFuel.Type;
+import com.billkuker.rocketry.motorsim.visual.Chart;
+import com.billkuker.rocketry.motorsim.visual.Editor;
+
+public class SRFuelEditor extends JSplitPane {
+       private static final NumberFormat nf = new DecimalFormat("##########.###");
+
+       Chart<Pressure, Velocity> burnRate;
+
+       private class Entry implements Comparable<Entry> {
+               Amount<Pressure> p = Amount.valueOf(0, RocketScience.UnitPreference.getUnitPreference().getPreferredUnit(RocketScience.PSI));
+               double a;
+               double n;
+
+               @Override
+               public int compareTo(Entry o) {
+                       return p.compareTo(o.p);
+               }
+       }
+
+       public static class EditablePSRFuel extends PiecewiseSaintRobertFuel {
+
+               private Amount<VolumetricDensity> idealDensity = (Amount<VolumetricDensity>) Amount
+                               .valueOf("1 g/mm^3");
+               private double combustionEfficiency = 1;
+               private double densityRatio = 1;
+               private EditableCombustionProduct cp;
+               private String name = "New Fuel";
+
+               public EditablePSRFuel(Type t) {
+                       super(t);
+                       cp = new EditableCombustionProduct();
+               }
+               
+               public void clear(){
+                       super.clear();
+               }
+               
+               public void setType(Type t){
+                       super.setType(t);
+               }
+
+               public void add(Amount<Pressure> p, final double _a, final double _n) {
+                       super.add(p, _a, _n);
+
+               }
+
+               public Amount<VolumetricDensity> getIdealDensity() {
+                       return idealDensity;
+               }
+
+               public void setIdealDensity(Amount<VolumetricDensity> idealDensity) {
+                       this.idealDensity = idealDensity;
+               }
+
+               public double getCombustionEfficiency() {
+                       return combustionEfficiency;
+               }
+
+               public void setCombustionEfficiency(double combustionEfficiency) {
+                       this.combustionEfficiency = combustionEfficiency;
+               }
+
+               public double getDensityRatio() {
+                       return densityRatio;
+               }
+
+               public void setDensityRatio(double densityRatio) {
+                       this.densityRatio = densityRatio;
+               }
+
+               @Override
+               public CombustionProduct getCombustionProduct() {
+                       return cp;
+               }
+
+               public String getName() {
+                       return name;
+               }
+
+               public void setName(String name) {
+                       this.name = name;
+               }
+
+       }
+
+       final EditablePSRFuel f = new EditablePSRFuel(SaintRobertFuel.Type.SI);
+
+       private class TM extends AbstractTableModel {
+
+               @Override
+               public int getColumnCount() {
+                       return 3;
+               }
+
+               @Override
+               public int getRowCount() {
+                       return entries.size();
+               }
+
+               @Override
+               public String getColumnName(int col) {
+                       switch (col) {
+                       case 0:
+                               return "Pressure";
+                       case 1:
+                               return "Coefficient (a)";
+                       case 2:
+                               return "Exponent (n)";
+                       }
+                       return null;
+               }
+
+               @Override
+               public Object getValueAt(int rowIndex, int columnIndex) {
+                       Entry e = entries.get(rowIndex);
+                       switch (columnIndex) {
+                       case 0:
+                               //Format like 100 psi or 4.8 Mpa
+                               return nf.format(e.p.doubleValue(e.p.getUnit())) + " " + e.p.getUnit();
+                       case 1:
+                               return e.a;
+                       case 2:
+                               return e.n;
+                       }
+                       return null;
+               }
+
+               public boolean isCellEditable(int row, int col) {
+                       return true;
+               }
+
+               public void setValueAt(Object value, int row, int col) {
+                       Entry e = entries.get(row);
+                       try {
+                               switch (col) {
+                               case 0:
+                                       try {
+                                               e.p = (Amount<Pressure>) Amount.valueOf((String) value);
+                                       } catch ( Exception ee ){
+                                               double d = Double.parseDouble((String)value);
+                                               e.p = (Amount<Pressure>)Amount.valueOf(d, e.p.getUnit());
+                                       }
+                                       break;
+                               case 1:
+                                       e.a = Double.valueOf((String) value);
+                                       break;
+                               case 2:
+                                       e.n = Double.valueOf((String) value);
+                               }
+                       } catch (Exception ex) {
+                               ex.printStackTrace();
+                       }
+                       Collections.sort(entries);
+                       fireTableDataChanged();
+                       //f = new EditablePSRFuel(SaintRobertFuel.Type.NONSI);
+                       f.clear();
+                       for (Entry en : entries) {
+                               f.add(en.p, en.a, en.n);
+                       }
+                       f.firePropertyChange(new PropertyChangeEvent(f,"entries", null, null));
+
+                       update();
+
+               }
+
+               @Override
+               public void fireTableDataChanged() {
+                       super.fireTableDataChanged();
+               }
+
+       };
+       
+       public Fuel getFuel(){
+               return f;
+       }
+
+       private void update() {
+               SwingUtilities.invokeLater(new Runnable() {
+
+                       @Override
+                       public void run() {
+                               editTop.setTopComponent(new Editor(f));
+                               editTop.setBottomComponent(new Editor(f.getCombustionProduct()));
+                               if (burnRate != null)
+                                       SRFuelEditor.this.remove(burnRate);
+                               try {
+                                       burnRate = new Chart<Pressure, Velocity>(
+                                                       SI.MEGA(SI.PASCAL), SI.MILLIMETER.divide(SI.SECOND)
+                                                                       .asType(Velocity.class), f, "burnRate");
+                               } catch (NoSuchMethodException e) {
+                                       throw new Error(e);
+                               }
+                               burnRate.setDomain(burnRate.new IntervalDomain(Amount.valueOf(
+                                               0, SI.MEGA(SI.PASCAL)), Amount.valueOf(11, SI
+                                               .MEGA(SI.PASCAL)), 50));
+                               SRFuelEditor.this.setRightComponent(burnRate);
+                               SRFuelEditor.this.revalidate();
+                       }
+               });
+       }
+
+       private Vector<Entry> entries = new Vector<Entry>();
+
+       JSplitPane editParent;
+       JSplitPane editTop;
+       JSplitPane editBottom;
+       JPanel controls;
+
+       public SRFuelEditor() {
+               super(HORIZONTAL_SPLIT);
+               setResizeWeight(0);
+               setDividerLocation(.3);
+               
+               final TM tm = new TM();
+
+               editParent = new JSplitPane(JSplitPane.VERTICAL_SPLIT);
+               editTop = new JSplitPane(JSplitPane.VERTICAL_SPLIT);
+               editBottom = new JSplitPane(JSplitPane.VERTICAL_SPLIT);
+               
+               editParent.setTopComponent(editTop);
+               editParent.setBottomComponent(editBottom);
+               
+               editTop.setTopComponent(new Editor(f));
+
+               JTable table = new JTable(tm);
+               JScrollPane scrollpane = new JScrollPane(table);
+               scrollpane.setMinimumSize(new Dimension(200, 200));
+               editBottom.setTopComponent(scrollpane);
+
+               setLeftComponent(editParent);
+
+               JButton add = new JButton("Add Data");
+               add.addActionListener(new ActionListener() {
+                       @Override
+                       public void actionPerformed(ActionEvent e) {
+                               entries.add(new Entry());
+                               tm.fireTableDataChanged();
+                       }
+               });
+               controls = new JPanel();
+               controls.setPreferredSize(new Dimension(200, 50));
+               controls.setLayout(new FlowLayout());
+                       
+               controls.add(add);
+
+               
+               final JRadioButton si, nonsi;
+               ButtonGroup type = new ButtonGroup();
+               JPanel radio = new JPanel();
+               radio.add(si = new JRadioButton("SI"));
+               radio.add(nonsi = new JRadioButton("NonSI"));
+               controls.add(radio);
+               type.add(si);
+               type.add(nonsi);
+
+               si.setSelected(true);
+               
+               si.addChangeListener(new ChangeListener(){
+                       @Override
+                       public void stateChanged(ChangeEvent e) {
+                               if ( si.isSelected() ){
+                                       System.err.println("SI");
+                                       f.setType(Type.SI);
+                               } else {
+                                       System.err.println("NONSI");
+                                       f.setType(Type.NONSI);
+                               }
+                               update();
+                       }});
+               
+               editBottom.setBottomComponent(controls);
+               
+               
+               editParent.setDividerLocation(.5);
+               editTop.setDividerLocation(.5);
+               editBottom.setDividerLocation(.8);
+               
+               editParent.resetToPreferredSizes();
+               revalidate();
+
+               update();
+       }
+
+       public static void main(String args[]) {
+               SRFuelEditor ed;
+               JFrame f = new JFrame();
+               f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
+               f.setContentPane(ed = new SRFuelEditor());
+               f.setSize(800, 600);
+               f.setVisible(true);
+
+       }
+
+}
diff --git a/gui/com/billkuker/rocketry/motorsim/visual/workbench/WorkbenchTreeCellRenderer.java b/gui/com/billkuker/rocketry/motorsim/visual/workbench/WorkbenchTreeCellRenderer.java
new file mode 100644 (file)
index 0000000..ae3e3a6
--- /dev/null
@@ -0,0 +1,63 @@
+package com.billkuker.rocketry.motorsim.visual.workbench;
+
+import java.awt.Color;
+import java.awt.Component;
+
+import javax.swing.JTree;
+import javax.swing.tree.DefaultMutableTreeNode;
+import javax.swing.tree.DefaultTreeCellRenderer;
+
+import com.billkuker.rocketry.motorsim.Motor;
+import com.billkuker.rocketry.motorsim.Validating;
+import com.billkuker.rocketry.motorsim.Validating.ValidationException;
+import com.billkuker.rocketry.motorsim.visual.workbench.WorkbenchTreeModel.FuelNode;
+
+public class WorkbenchTreeCellRenderer extends DefaultTreeCellRenderer {
+       private static final long serialVersionUID = 1L;
+
+       @Override
+       public Component getTreeCellRendererComponent(JTree tree, final Object value,
+                       boolean sel, boolean expanded, boolean leaf, int row,
+                       boolean hasFocus) {
+
+               String tip = null;
+               setTextNonSelectionColor(Color.black);
+               setTextSelectionColor(Color.white);
+               
+               Object part = null;
+               if (value instanceof DefaultMutableTreeNode) {
+                       part = ((DefaultMutableTreeNode) value).getUserObject();
+               }
+               
+               if ( part instanceof Validating ){
+                               try {
+                                       ((Validating)part).validate();
+                               } catch (ValidationException e) {
+                                       setTextSelectionColor(Color.RED);
+                                       setTextNonSelectionColor(Color.RED);
+                                       setToolTipText(e.getMessage());
+                                       tip = e.getMessage();
+                               }
+               }
+               
+               super.getTreeCellRendererComponent(tree, value, sel, expanded, leaf,
+                               row, hasFocus);
+
+               if (part instanceof Motor) {
+                       setText(((Motor) part).getName());
+               } else if ( value instanceof FuelNode ){
+                       setText(((FuelNode)value).getFuel().getName());
+               } else if ( part instanceof String ) {
+                       setText((String)part);
+               } else if ( part == null ) {
+                       setText("");
+               } else {
+                       setText(part.getClass().getSimpleName());
+               }
+               setToolTipText(tip);
+               
+
+
+               return this;
+       }
+}
diff --git a/gui/com/billkuker/rocketry/motorsim/visual/workbench/WorkbenchTreeModel.java b/gui/com/billkuker/rocketry/motorsim/visual/workbench/WorkbenchTreeModel.java
new file mode 100644 (file)
index 0000000..1745d6e
--- /dev/null
@@ -0,0 +1,172 @@
+package com.billkuker.rocketry.motorsim.visual.workbench;
+
+import java.awt.Component;
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
+import java.util.Enumeration;
+
+import javax.swing.tree.DefaultMutableTreeNode;
+import javax.swing.tree.DefaultTreeModel;
+import javax.swing.tree.TreeNode;
+
+import com.billkuker.rocketry.motorsim.ChangeListening;
+import com.billkuker.rocketry.motorsim.Fuel;
+import com.billkuker.rocketry.motorsim.Motor;
+import com.billkuker.rocketry.motorsim.grain.MultiGrain;
+
+public class WorkbenchTreeModel extends DefaultTreeModel {
+
+       private static final long serialVersionUID = 1L;
+       
+       //TreeNode root = new DefaultMutableTreeNode("Root");
+       DefaultMutableTreeNode motors = new DefaultMutableTreeNode("All Motors");
+       DefaultMutableTreeNode fuel = new DefaultMutableTreeNode("Fuels");
+       
+       public class MultiGrainNode extends PartNode{
+               private static final long serialVersionUID = 1L;
+               public MultiGrainNode(MultiGrain part) {
+                       super(part);
+                       setAllowsChildren(true);
+                       add(new PartNode(part.getGrain()));
+               }
+               @Override
+               public void propertyChange(PropertyChangeEvent e) {
+                       if ( e.getPropertyName().equals("Grain")){
+                               remove(0);
+                               add(new PartNode(((MultiGrain)getUserObject()).getGrain()));
+                               nodesChanged(this, new int[]{0});
+                       }
+                       super.propertyChange(e);
+               }
+       }
+       
+       public class FuelNode extends DefaultMutableTreeNode{
+               private static final long serialVersionUID = 1L;
+               Fuel f;
+               public FuelNode(Component c, Fuel f){
+                       super(c, false);
+                       this.f = f;
+               }
+               
+               @Override
+               public Component getUserObject(){
+                       return (Component)super.getUserObject();
+               }
+               
+               public Fuel getFuel(){
+                       return f;
+               }
+       }
+       
+       public class FuelEditNode extends FuelNode {
+               private static final long serialVersionUID = 1L;
+
+               public FuelEditNode(SRFuelEditor sr){
+                       super(sr, sr.getFuel());
+                       sr.getFuel().addPropertyChangeListener(new PropertyChangeListener(){
+
+                               @Override
+                               public void propertyChange(PropertyChangeEvent evt) {
+                                       nodeChanged(FuelEditNode.this);
+                               }});
+               }
+               
+               @Override
+               public SRFuelEditor getUserObject(){
+                       return (SRFuelEditor)super.getUserObject();
+               }
+
+       }
+
+       public class PartNode extends DefaultMutableTreeNode implements PropertyChangeListener {
+               private static final long serialVersionUID = 1L;
+
+               public PartNode(Object part) {
+                       super(part, false);
+                       if (part instanceof ChangeListening.Subject) {
+                               ((ChangeListening.Subject) part).addPropertyChangeListener(this);
+                       }
+               }
+       
+               @Override
+               public void propertyChange(PropertyChangeEvent e) {
+                       nodeChanged(this);
+               }
+       
+       }
+       
+       public class MotorNode extends PartNode implements PropertyChangeListener {
+               private static final long serialVersionUID = 1L;
+               Motor motor;
+               PartNode cn, nn, gn, fn;
+
+               public MotorNode(Motor m) {
+                       super(m);
+                       setAllowsChildren(true);
+                       motor = m;
+                       add( cn = new PartNode(m.getChamber()));
+                       add( nn = new PartNode(m.getNozzle()));
+                       if ( m.getGrain() instanceof MultiGrain ){
+                               gn = new MultiGrainNode(((MultiGrain)m.getGrain()));
+                       } else {
+                               gn = new PartNode(m.getGrain());
+                       }
+                       add(gn);
+                       if (m instanceof ChangeListening.Subject) {
+                               ((ChangeListening.Subject) m).addPropertyChangeListener(this);
+                       }
+               }
+               
+               @Override
+               public Motor getUserObject(){
+                       return (Motor)super.getUserObject();
+               }
+
+               @Override
+               public void propertyChange(PropertyChangeEvent e) {
+                               nodeChanged(this);
+                       super.propertyChange(e);
+               }
+
+       }
+
+       public WorkbenchTreeModel() {
+               super(new DefaultMutableTreeNode("Root"), true);
+               getRoot().add(motors);
+               getRoot().add(fuel);
+       }
+       
+       @Override
+       public DefaultMutableTreeNode getRoot(){
+               return (DefaultMutableTreeNode)super.getRoot();
+       }
+       
+       public DefaultMutableTreeNode getMotors(){
+               return motors;
+       }
+       
+       public DefaultMutableTreeNode getFuels(){
+               return fuel;
+       }
+       
+       public void addMotor(Motor m){
+               DefaultMutableTreeNode root = getRoot();
+               motors.add(new MotorNode(m));
+               nodesWereInserted(motors, new int[]{motors.getChildCount()-1});
+               
+       }
+       
+       @SuppressWarnings("unchecked")
+       public void removeMotor(Motor m){
+               Enumeration<TreeNode> e = motors.children();
+               while ( e.hasMoreElements() ){
+                       TreeNode n = e.nextElement();
+                       if ( n instanceof MotorNode ){
+                               if ( ((MotorNode)n).getUserObject() == m ){
+                                       removeNodeFromParent((MotorNode)n);
+                               }
+                       }
+               }
+       }
+
+}
diff --git a/src/MotorSim.java b/src/MotorSim.java
deleted file mode 100644 (file)
index 6f1a07f..0000000
+++ /dev/null
@@ -1,26 +0,0 @@
-import javax.swing.SwingUtilities;
-import javax.swing.UIManager;
-
-public class MotorSim {
-
-       public static void main(String args[]) throws Exception {
-
-               try {
-                       System.setProperty("apple.laf.useScreenMenuBar", "true");
-                       System.setProperty(
-                                       "com.apple.mrj.application.apple.menu.about.name",
-                                       "MotorSim");
-                       UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
-               } catch (Exception e1) {
-                       e1.printStackTrace();
-               }
-               SwingUtilities.invokeLater(new Runnable(){
-                       @Override
-                       public void run() {
-                               new com.billkuker.rocketry.motorsim.visual.workbench.MotorWorkbench().setVisible(true);
-                       }
-               });
-               
-       }
-
-}
diff --git a/src/com/billkuker/rocketry/motorsim/visual/BurnPanel.java b/src/com/billkuker/rocketry/motorsim/visual/BurnPanel.java
deleted file mode 100644 (file)
index a42aaa1..0000000
+++ /dev/null
@@ -1,209 +0,0 @@
-package com.billkuker.rocketry.motorsim.visual;\r
-\r
-import java.awt.BorderLayout;\r
-import java.awt.Dimension;\r
-import java.awt.GridLayout;\r
-import java.text.NumberFormat;\r
-\r
-import javax.measure.quantity.Duration;\r
-import javax.measure.quantity.Force;\r
-import javax.measure.quantity.Pressure;\r
-import javax.measure.quantity.Velocity;\r
-import javax.measure.unit.SI;\r
-import javax.swing.JFrame;\r
-import javax.swing.JLabel;\r
-import javax.swing.JPanel;\r
-import javax.swing.JSlider;\r
-import javax.swing.JSplitPane;\r
-import javax.swing.WindowConstants;\r
-import javax.swing.event.ChangeEvent;\r
-import javax.swing.event.ChangeListener;\r
-\r
-import org.jscience.physics.amount.Amount;\r
-\r
-import com.billkuker.rocketry.motorsim.Burn;\r
-import com.billkuker.rocketry.motorsim.Burn.Interval;\r
-import com.billkuker.rocketry.motorsim.RocketScience;\r
-\r
-public class BurnPanel extends JPanel {\r
-       private static final long serialVersionUID = 1L;\r
-       private Burn burn;\r
-       Chart<Duration, Pressure> pressure;\r
-       Chart<Duration, Force> thrust;\r
-       Chart<Pressure, Velocity> burnRate;\r
-       GrainPanel grain;\r
-       Amount<Duration> displayedTime = Amount.valueOf(0, SI.SECOND);\r
-       \r
-       public BurnPanel(Burn b){\r
-               super( new BorderLayout() );\r
-               burn = b;\r
-               \r
-               try {\r
-                       pressure = new Chart<Duration, Pressure>(\r
-                                       SI.SECOND,\r
-                                       SI.MEGA(SI.PASCAL),\r
-                                       b,\r
-                                       "pressure");\r
-                       pressure.setDomain(burn.getData().keySet());\r
-                       \r
-                       thrust = new Chart<Duration, Force>(\r
-                                       SI.SECOND,\r
-                                       SI.NEWTON,\r
-                                       b,\r
-                                       "thrust");\r
-                       thrust.setDomain(burn.getData().keySet());\r
-                       \r
-                       burnRate = new Chart<Pressure, Velocity>(\r
-                                       SI.MEGA(SI.PASCAL),\r
-                                       SI.METERS_PER_SECOND,\r
-                                       burn.getMotor().getFuel(),\r
-                                       "burnRate");\r
-                       burnRate.setDomain(\r
-                                       burnRate.new IntervalDomain(\r
-                                                       Amount.valueOf(0, SI.MEGA(SI.PASCAL)),\r
-                                                       Amount.valueOf(11, SI.MEGA(SI.PASCAL)),\r
-                                                       20\r
-                                                       ));\r
-                       \r
-                       \r
-                       JSplitPane tp = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, thrust, pressure);\r
-                       tp.setDividerLocation(.5);\r
-                       tp.setResizeWeight(.5);\r
-                       \r
-                       grain = new GrainPanel(burn.getMotor().getGrain()){\r
-                               private static final long serialVersionUID = 1L;\r
-                               @Override protected void addComponents(java.awt.Component crossSection, java.awt.Component slider, java.awt.Component label, java.awt.Component area, java.awt.Component volume) {\r
-                                       JSplitPane h = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, crossSection, area); \r
-                                       add(h, BorderLayout.CENTER);\r
-                                       h.resetToPreferredSizes();\r
-                               };\r
-                       };\r
-                       \r
-                       JSplitPane grains = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, grain, burnRate);\r
-                       grains.setDividerLocation(.5);\r
-                       grains.setResizeWeight(.5);\r
-                       \r
-                       JSplitPane main = new JSplitPane(JSplitPane.VERTICAL_SPLIT, grains, tp);\r
-                       Dimension minimumSize = new Dimension(800, 200);\r
-                       grains.setMinimumSize(minimumSize);\r
-                       tp.setMinimumSize(minimumSize);\r
-                       main.setDividerLocation(.5);\r
-                       main.setResizeWeight(.5);\r
-                       \r
-                       add( main, BorderLayout.CENTER );\r
-                       \r
-                       add( new SL(), BorderLayout.SOUTH);\r
-                       \r
-                       \r
-\r
-                               Amount<RocketScience.Impulse> ns = Amount.valueOf(0, RocketScience.NEWTON_SECOND);\r
-                               \r
-                               Amount<Duration> thrustTime = Amount.valueOf(0, SI.SECOND);\r
-                               Amount<Force> maxThrust = Amount.valueOf(0, SI.NEWTON);\r
-                               Amount<Pressure> maxPressure = Amount.valueOf(0, SI.MEGA(SI.PASCAL));\r
-\r
-                               for( Interval i: burn.getData().values() ){\r
-                                       ns = ns.plus(i.dt.times(i.thrust));\r
-                                       if ( i.thrust.isGreaterThan(Amount.valueOf(0.01, SI.NEWTON))){\r
-                                               thrustTime = thrustTime.plus(i.dt);\r
-                                       }\r
-                                       if ( i.thrust.isGreaterThan(maxThrust))\r
-                                               maxThrust = i.thrust;\r
-                                       if ( i.chamberPressure.isGreaterThan(maxPressure))\r
-                                               maxPressure = i.chamberPressure;\r
-                               }\r
-                               \r
-                               Amount<Force> averageThrust = Amount.valueOf(0, SI.NEWTON);\r
-                               if ( thrustTime.isGreaterThan(Amount.valueOf(0, SI.SECOND)))\r
-                                       averageThrust = ns.divide(thrustTime).to(SI.NEWTON);\r
-\r
-                               float cnf = (float)(Math.log(ns.doubleValue(RocketScience.NEWTON_SECOND)/1.25) / Math.log(2));\r
-                               int cn = (int)cnf;\r
-                               float fraction = cnf - cn;\r
-                               int percent = (int)(100 * fraction);\r
-                               char cl = (char)((int)'A' + cn);\r
-\r
-                               \r
-                               Amount<Duration> isp = ns.divide(\r
-                                               b.getMotor().getGrain().volume(Amount.valueOf(0, SI.MILLIMETER))\r
-                                                       .times(b.getMotor().getFuel().getIdealDensity().times(b.getMotor().getFuel().getDensityRatio()))\r
-                                               ).to(SI.METERS_PER_SECOND).divide(Amount.valueOf(9.81, SI.METERS_PER_SQUARE_SECOND)).to(SI.SECOND);\r
-                       \r
-                       JPanel text = new JPanel(new GridLayout(2,5));\r
-\r
-                       text.add(new JLabel("Rating"));\r
-                       text.add(new JLabel("Total Impulse"));\r
-                       text.add(new JLabel("ISP"));\r
-                       text.add(new JLabel("Max Thrust"));\r
-                       text.add(new JLabel("Average Thust"));\r
-                       text.add(new JLabel("Max Pressure"));\r
-                       \r
-                       text.add(new JLabel(percent + "% " + new String(new char[]{cl}) + "-" +Math.round(averageThrust.doubleValue(SI.NEWTON))));\r
-                       text.add(new JLabel(RocketScience.approx(ns)));\r
-                       text.add(new JLabel(RocketScience.approx(isp)));                        \r
-                       text.add(new JLabel(RocketScience.approx(maxThrust)));\r
-                       text.add(new JLabel(RocketScience.approx(averageThrust)));\r
-                       text.add(new JLabel(RocketScience.approx(maxPressure)));\r
-                       \r
-                       add(text, BorderLayout.NORTH);\r
-                       \r
-                       \r
-               } catch (NoSuchMethodException e){\r
-                       throw new Error(e);\r
-               }\r
-               \r
-\r
-       }\r
-       \r
-       private class SL extends JSlider implements ChangeListener{\r
-               private static final long serialVersionUID = 1L;\r
-               private static final int STEPS = 80;\r
-               public SL(){\r
-                       addChangeListener(this);\r
-                       setMinimum(0);\r
-                       setMaximum(STEPS);\r
-                       setValue(0);\r
-               }\r
-               \r
-               public void stateChanged(ChangeEvent e) {\r
-                       double t = ((SL)e.getSource()).getValue();\r
-                       displayedTime = burn.burnTime().divide(STEPS).times(t);\r
-                       \r
-                       //Find the nearest key in the data set\r
-                       displayedTime =burn.getData().tailMap(displayedTime).firstKey();\r
-                       \r
-                       NumberFormat nf = NumberFormat.getInstance();\r
-                       nf.setMaximumFractionDigits(2);\r
-                       \r
-                       pressure.mark(displayedTime);\r
-                       thrust.mark(displayedTime);\r
-                       \r
-                       grain.setDisplayedRegression(burn.getData().get(displayedTime).regression);\r
-                       \r
-                       burnRate.mark(burn.getData().get(displayedTime).chamberPressure);\r
-                       \r
-                       \r
-                       /*\r
-                       double r = ((SL)e.getSource()).getValue();\r
-                       displayedRegression = grain.webThickness().divide(STEPS).times(r);\r
-                       NumberFormat nf = NumberFormat.getInstance();\r
-                       nf.setMaximumFractionDigits(2);\r
-                       l.setText("Regression: " + nf.format(displayedRegression.doubleValue(SI.MILLIMETER)) + "mm");\r
-                       area.mark(displayedRegression);\r
-                       volume.mark(displayedRegression);\r
-                       if ( xc != null )\r
-                               xc.repaint();\r
-                       */\r
-               }\r
-       }\r
-       \r
-       public void showAsWindow(){\r
-               JFrame f = new JFrame();\r
-               f.setTitle(burn.getMotor().getName());\r
-               f.setSize(1280,720);\r
-               f.setLocation(0, 0);\r
-               f.setContentPane(this);\r
-               f.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);\r
-               f.setVisible(true);\r
-       }\r
-}\r
diff --git a/src/com/billkuker/rocketry/motorsim/visual/Chart.java b/src/com/billkuker/rocketry/motorsim/visual/Chart.java
deleted file mode 100644 (file)
index 97eb6df..0000000
+++ /dev/null
@@ -1,295 +0,0 @@
-package com.billkuker.rocketry.motorsim.visual;\r
-\r
-import java.awt.BorderLayout;\r
-import java.awt.Color;\r
-import java.awt.Font;\r
-import java.lang.reflect.InvocationTargetException;\r
-import java.lang.reflect.Method;\r
-import java.util.Collection;\r
-import java.util.Iterator;\r
-import java.util.concurrent.ExecutorService;\r
-import java.util.concurrent.Executors;\r
-import java.util.concurrent.ThreadFactory;\r
-\r
-import javax.measure.quantity.Area;\r
-import javax.measure.quantity.Length;\r
-import javax.measure.quantity.Quantity;\r
-import javax.measure.quantity.Volume;\r
-import javax.measure.unit.SI;\r
-import javax.measure.unit.Unit;\r
-import javax.swing.JFrame;\r
-import javax.swing.JPanel;\r
-import javax.swing.SwingUtilities;\r
-\r
-import org.apache.log4j.Logger;\r
-import org.jfree.chart.ChartFactory;\r
-import org.jfree.chart.ChartPanel;\r
-import org.jfree.chart.JFreeChart;\r
-import org.jfree.chart.plot.Marker;\r
-import org.jfree.chart.plot.PlotOrientation;\r
-import org.jfree.chart.plot.ValueMarker;\r
-import org.jfree.data.xy.XYSeries;\r
-import org.jfree.data.xy.XYSeriesCollection;\r
-import org.jfree.ui.RectangleInsets;\r
-import org.jfree.ui.TextAnchor;\r
-import org.jscience.physics.amount.Amount;\r
-\r
-import com.billkuker.rocketry.motorsim.Burn;\r
-import com.billkuker.rocketry.motorsim.RocketScience;\r
-import com.billkuker.rocketry.motorsim.grain.CoredCylindricalGrain;\r
-\r
-public class Chart<X extends Quantity, Y extends Quantity> extends JPanel {\r
-       private static final long serialVersionUID = 1L;\r
-       private static Logger log = Logger.getLogger(Burn.class);\r
-\r
-       private static ThreadFactory tf = new ThreadFactory() {\r
-               public Thread newThread(Runnable r) {\r
-                       Thread t = new Thread(r);\r
-                       t.setDaemon(true);\r
-                       return t;\r
-               }\r
-       };\r
-       private static ExecutorService fast = Executors.newFixedThreadPool(2, tf);\r
-       private static ExecutorService slow = Executors.newFixedThreadPool(2, tf);\r
-       private volatile boolean stop = false;\r
-\r
-       public class IntervalDomain implements Iterable<Amount<X>> {\r
-\r
-               Amount<X> low, high, delta;\r
-               int steps = 100;\r
-\r
-               public IntervalDomain(Amount<X> low, Amount<X> high) {\r
-                       this.low = low;\r
-                       this.high = high;\r
-                       delta = high.minus(low).divide(steps);\r
-               }\r
-\r
-               public IntervalDomain(Amount<X> low, Amount<X> high, int steps) {\r
-                       this.steps = steps;\r
-                       this.low = low;\r
-                       this.high = high;\r
-                       delta = high.minus(low).divide(steps);\r
-               }\r
-\r
-               public Iterator<Amount<X>> iterator() {\r
-                       return new Iterator<Amount<X>>() {\r
-                               Amount<X> current = low;\r
-\r
-                               public boolean hasNext() {\r
-                                       return current.isLessThan(high.plus(delta));\r
-                               }\r
-\r
-                               public Amount<X> next() {\r
-                                       Amount<X> ret = current;\r
-                                       current = current.plus(delta);\r
-                                       return ret;\r
-                               }\r
-\r
-                               public final void remove() {\r
-                                       throw new UnsupportedOperationException(\r
-                                                       "Chart domain iterators are not modifiable.");\r
-                               }\r
-                       };\r
-               }\r
-\r
-       }\r
-\r
-       XYSeriesCollection dataset = new XYSeriesCollection();\r
-       JFreeChart chart;\r
-\r
-       Unit<X> xUnit;\r
-       Unit<Y> yUnit;\r
-\r
-       Object source;\r
-       Method f;\r
-\r
-       public Chart(Unit<X> xUnit, Unit<Y> yUnit, Object source, String method)\r
-                       throws NoSuchMethodException {\r
-               super(new BorderLayout());\r
-               f = source.getClass().getMethod(method, Amount.class);\r
-\r
-               this.source = source;\r
-\r
-\r
-               this.xUnit = RocketScience.UnitPreference.getUnitPreference()\r
-                               .getPreferredUnit(xUnit);\r
-               this.yUnit = RocketScience.UnitPreference.getUnitPreference()\r
-                               .getPreferredUnit(yUnit);\r
-\r
-               chart = ChartFactory.createXYLineChart(method.substring(0, 1)\r
-                               .toUpperCase()\r
-                               + method.substring(1), // Title\r
-                               this.xUnit.toString(), // x-axis Label\r
-                               this.yUnit.toString(), // y-axis Label\r
-                               dataset, PlotOrientation.VERTICAL, // Plot Orientation\r
-                               false, // Show Legend\r
-                               true, // Use tool tips\r
-                               false // Configure chart to generate URLs?\r
-                               );\r
-               add(new ChartPanel(chart));\r
-       }\r
-\r
-       private Marker marker;\r
-\r
-       public void mark(Amount<X> m) {\r
-               if (marker != null)\r
-                       chart.getXYPlot().removeDomainMarker(marker);\r
-               if (m != null) {\r
-                       marker = new ValueMarker(m.doubleValue(xUnit));\r
-                       marker.setPaint(Color.blue);\r
-                       marker.setAlpha(0.8f);\r
-                       \r
-                       Amount<Y> val = getNear(m);\r
-                       if ( val != null )\r
-                               marker.setLabel(RocketScience.approx(val));\r
-                       \r
-                       marker.setLabelTextAnchor(TextAnchor.TOP_LEFT);\r
-                       marker.setLabelOffset(new RectangleInsets(0,-5,0,0));\r
-                       \r
-                       marker.setLabelFont(new Font(Font.DIALOG, Font.BOLD, 12));\r
-                       chart.getXYPlot().addDomainMarker(marker);\r
-               }\r
-       }\r
-       \r
-       /**\r
-        * Get the Y value at or near a given X\r
-        * For display use only!\r
-        * \r
-        * @param ax\r
-        * @return\r
-        */\r
-       private Amount<Y> getNear(final Amount<X> ax){\r
-               if ( dataset.getSeriesCount() != 1 )\r
-                       return null;\r
-               final XYSeries s = dataset.getSeries(0);\r
-               final double x = ax.doubleValue(xUnit);\r
-               int idx = s.getItemCount() / 2;\r
-               int delta = s.getItemCount() / 4;\r
-               while(true){\r
-                       if ( s.getX(idx).doubleValue() < x ){\r
-                               idx += delta;\r
-                       } else {\r
-                               idx -= delta;\r
-                       }\r
-                       delta = delta / 2;\r
-                       if ( delta < 1 ){\r
-                               int idxL = idx-1;\r
-                               int idxH = idx;\r
-                               final double lowerX = s.getX(idxL).doubleValue();\r
-                               final double higherX = s.getX(idxH).doubleValue();\r
-                               final double sampleXDiff = higherX - lowerX;\r
-                               final double xDiff = x - lowerX;\r
-                               final double dist = xDiff / sampleXDiff;\r
-                               final double lowerY = s.getY(idxL).doubleValue();\r
-                               final double higherY = s.getY(idxH).doubleValue();\r
-                               final double y = lowerY + dist * (higherY - lowerY);\r
-                               \r
-                               return Amount.valueOf( y, yUnit);\r
-                       }\r
-               }\r
-       }\r
-\r
-       public void setDomain(final Iterable<Amount<X>> d) {\r
-               stop = true;\r
-               fill(d, 100);\r
-               fast.submit(new Thread() {\r
-                       public void run() {\r
-                               if (!stop)\r
-                                       fill(d, 10);\r
-                               slow.submit(new Thread() {\r
-                                       public void run() {\r
-                                               if (!stop)\r
-                                                       fill(d, 1);\r
-                                       }\r
-                               });\r
-                       }\r
-               });\r
-       }\r
-\r
-       @SuppressWarnings("unchecked")\r
-       private synchronized void fill(Iterable<Amount<X>> d, int skip) {\r
-               log.debug(f.getName() + " " + skip + " Start");\r
-               stop = false;\r
-               int sz = 0;\r
-               if (d instanceof Collection) {\r
-                       sz = ((Collection<Amount<X>>) d).size();\r
-                       int sk2 = sz / 200;\r
-                       if (skip < sk2)\r
-                               skip = sk2;\r
-               }\r
-               // series.clear();\r
-               int cnt = 0;\r
-\r
-               final XYSeries newSeries = new XYSeries(f.getName());\r
-               try {\r
-                       Amount<X> last = null;\r
-                       for (Amount<X> ax : d) {\r
-                               if (stop) {\r
-                                       log.debug(f.getName() + " " + skip + " Abort");\r
-                                       return;\r
-                               }\r
-                               last = ax;\r
-                               if (cnt % skip == 0) {\r
-                                       Amount<Y> y = (Amount<Y>) f.invoke(source, ax);\r
-                                       newSeries.add(ax.doubleValue(xUnit), y.doubleValue(yUnit));\r
-                               }\r
-                               cnt++;\r
-                       }\r
-                       Amount<Y> y = (Amount<Y>) f.invoke(source, last);\r
-                       newSeries.add(last.doubleValue(xUnit), y.doubleValue(yUnit));\r
-                       SwingUtilities.invokeLater(new Thread() {\r
-                               @Override\r
-                               public void run() {\r
-                                       dataset.removeAllSeries();\r
-                                       dataset.addSeries(newSeries);\r
-                                       log.debug(f.getName() + " Replaced");\r
-                               }\r
-                       });\r
-               } catch (IllegalArgumentException e) {\r
-                       // TODO Auto-generated catch block\r
-                       e.printStackTrace();\r
-               } catch (IllegalAccessException e) {\r
-                       // TODO Auto-generated catch block\r
-                       e.printStackTrace();\r
-               } catch (InvocationTargetException e) {\r
-                       // TODO Auto-generated catch block\r
-                       e.printStackTrace();\r
-               }\r
-               log.debug(f.getName() + " " + skip + " Done");\r
-       }\r
-\r
-       public void show() {\r
-               new JFrame() {\r
-                       private static final long serialVersionUID = 1L;\r
-                       {\r
-                               setContentPane(Chart.this);\r
-                               setSize(640, 480);\r
-                               setDefaultCloseOperation(DISPOSE_ON_CLOSE);\r
-                       }\r
-               }.setVisible(true);\r
-       }\r
-\r
-       public static void main(String args[]) throws Exception {\r
-               CoredCylindricalGrain g = new CoredCylindricalGrain();\r
-               g.setLength(Amount.valueOf(70, SI.MILLIMETER));\r
-               g.setOD(Amount.valueOf(30, SI.MILLIMETER));\r
-               g.setID(Amount.valueOf(10, SI.MILLIMETER));\r
-\r
-               Chart<Length, Area> c = new Chart<Length, Area>(SI.MILLIMETER,\r
-                               SI.MILLIMETER.pow(2).asType(Area.class), g, "surfaceArea");\r
-\r
-               c.setDomain(c.new IntervalDomain(Amount.valueOf(0, SI.CENTIMETER), g\r
-                               .webThickness()));\r
-\r
-               c.show();\r
-\r
-               Chart<Length, Volume> v = new Chart<Length, Volume>(SI.MILLIMETER,\r
-                               SI.MILLIMETER.pow(3).asType(Volume.class), g, "volume");\r
-\r
-               v.setDomain(c.new IntervalDomain(Amount.valueOf(0, SI.CENTIMETER), g\r
-                               .webThickness()));\r
-\r
-               v.show();\r
-       }\r
-\r
-}\r
diff --git a/src/com/billkuker/rocketry/motorsim/visual/Editor.java b/src/com/billkuker/rocketry/motorsim/visual/Editor.java
deleted file mode 100644 (file)
index 11ddd8e..0000000
+++ /dev/null
@@ -1,196 +0,0 @@
-package com.billkuker.rocketry.motorsim.visual;\r
-\r
-import java.awt.Component;\r
-import java.awt.Dimension;\r
-import java.beans.IntrospectionException;\r
-import java.beans.Introspector;\r
-import java.beans.PropertyChangeEvent;\r
-import java.beans.PropertyChangeListener;\r
-import java.beans.PropertyDescriptor;\r
-import java.beans.PropertyEditorManager;\r
-import java.beans.PropertyEditorSupport;\r
-import java.text.DecimalFormat;\r
-import java.text.NumberFormat;\r
-import java.util.Vector;\r
-\r
-import javax.measure.unit.SI;\r
-import javax.measure.unit.Unit;\r
-import javax.swing.JFrame;\r
-import javax.swing.JLabel;\r
-import javax.swing.JTable;\r
-import javax.swing.JTextField;\r
-import javax.swing.WindowConstants;\r
-import javax.swing.table.TableCellRenderer;\r
-\r
-import org.apache.log4j.Logger;\r
-import org.jscience.physics.amount.Amount;\r
-\r
-import com.billkuker.rocketry.motorsim.CylindricalChamber;\r
-import com.l2fprod.common.propertysheet.PropertySheetPanel;\r
-\r
-public class Editor extends PropertySheetPanel {\r
-       private static final long serialVersionUID = 1L;\r
-       private static final NumberFormat nf = new DecimalFormat("##########.###");\r
-       private static final Logger log = Logger.getLogger(Editor.class);\r
-\r
-       private Object obj;\r
-\r
-       @SuppressWarnings("deprecation")\r
-       public Editor(Object o) {\r
-               obj = o;\r
-\r
-               PropertyEditorManager.registerEditor(Amount.class,\r
-                               AmountPropertyEditor.class);\r
-               \r
-               setToolBarVisible(false);\r
-               //setMinimumSize(new Dimension(150,200));\r
-               \r
-               getRendererRegistry().registerRenderer(Amount.class, AmountRenderer.class);\r
-\r
-               // Build the list of properties we want it to edit\r
-               //final PropertySheetPanel ps = new PropertySheetPanel();\r
-               PropertyDescriptor props[];\r
-               try {\r
-                       props = Introspector.getBeanInfo(obj.getClass())\r
-                                       .getPropertyDescriptors();\r
-               } catch (IntrospectionException e) {\r
-                       throw new Error(e);\r
-               }\r
-               Vector<PropertyDescriptor> v = new Vector<PropertyDescriptor>();\r
-               for (int i = 0; i < props.length; i++) {\r
-                       if (props[i].getName().equals("class"))\r
-                               continue;\r
-                       v.add(props[i]);\r
-               }\r
-               setProperties(v.toArray(new PropertyDescriptor[v.size()]));\r
-\r
-               readFromObject(obj);\r
-               \r
-               getTable().setRowHeight(22);\r
-               \r
-               setMinimumSize(new Dimension(\r
-                               getTable().getPreferredSize().width,\r
-                               getTable().getPreferredSize().height + 10));\r
-\r
-               addPropertySheetChangeListener(new PropertyChangeListener() {\r
-\r
-                       public void propertyChange(PropertyChangeEvent evt) {\r
-                               // When something changes just update the\r
-                               // object, I want the changes to be immediate.\r
-                               try {\r
-                                       log.debug("Writing properties to object.");\r
-                                       writeToObject(obj);\r
-                               } catch (Exception v) {\r
-                                       // TODO\r
-                                       v.printStackTrace();\r
-                                       java.awt.Toolkit.getDefaultToolkit().beep();\r
-                               } finally {\r
-                                       readFromObject(obj);\r
-                               }\r
-                       }\r
-\r
-               });\r
-       }\r
-\r
-       public void showAsWindow() {\r
-               JFrame f = new JFrame();\r
-               f.setTitle(obj.getClass().getName());\r
-               f.setSize(600, 400);\r
-               f.setContentPane(this);\r
-               f.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);\r
-               f.setVisible(true);\r
-       }\r
-       \r
-       public static void main(String args[]){\r
-               CylindricalChamber o = new CylindricalChamber();\r
-               o.setLength(Amount.valueOf(100.5, SI.MILLIMETER));\r
-               o.setID(Amount.valueOf(30, SI.MILLIMETER));\r
-               Editor e = new Editor(o);\r
-               e.showAsWindow();\r
-       }\r
-       \r
-       public static class AmountRenderer implements TableCellRenderer {\r
-               @Override\r
-               public Component getTableCellRendererComponent(JTable table,\r
-                               Object value, boolean isSelected, boolean hasFocus, int row,\r
-                               int column) {\r
-                       Amount a = (Amount)value;\r
-                       return new JLabel(nf.format(a.doubleValue(a.getUnit())) + " " + a.getUnit() );\r
-               }\r
-               \r
-       }\r
-\r
-       public static class AmountPropertyEditor extends PropertyEditorSupport {\r
-               JTextField editor = new JTextField();\r
-               Unit<?> oldUnit;\r
-\r
-               @Override\r
-               public boolean supportsCustomEditor() {\r
-                       return true;\r
-               }\r
-\r
-               @Override\r
-               public String getAsText() {\r
-                       return editor.getText();\r
-               }\r
-\r
-               @Override\r
-               public Object getValue() {\r
-                       String text = editor.getText().trim();\r
-\r
-                       // Trying to determine if the value is integer or\r
-                       // has a decimal part will prevent the uncertainty\r
-                       // term from appearing when user types an exact value\r
-                       try {\r
-                               try {\r
-                                       return Amount.valueOf(Integer.parseInt(text), oldUnit);\r
-                               } catch (NumberFormatException e) {\r
-\r
-                               }\r
-                               Amount<?> a = Amount.valueOf(Double.parseDouble(text), oldUnit);\r
-                               return a;\r
-                       } catch (NumberFormatException e) {\r
-                               // Storing the old unit allows you to type 10 into a field\r
-                               // that says 20 mm and get 10 mm, so you dont have to\r
-                               // type the unit if they havn't changed.\r
-                               \r
-                               //Amount wants a leading 0\r
-                               if (text.startsWith(".")){\r
-                                       text = "0" + text;\r
-                               }\r
-                               \r
-                               Amount<?> a = Amount.valueOf(text);\r
-                               oldUnit = a.getUnit();\r
-                               return a;\r
-                       }\r
-\r
-               }\r
-\r
-               @SuppressWarnings("unchecked")\r
-               @Override\r
-               public void setValue(Object o) {\r
-                       Amount a = (Amount) o;\r
-                       oldUnit = a.getUnit();\r
-\r
-                       String text;\r
-                       //Leave off the fractional part if it is not relevant\r
-                       if (a.isExact())\r
-                               text = a.getExactValue() + " " + a.getUnit();\r
-                       else\r
-                               text = nf.format(a.doubleValue(a.getUnit())) + " " + a.getUnit();\r
-\r
-                       setAsText(text);\r
-               }\r
-\r
-               @Override\r
-               public void setAsText(String text) throws IllegalArgumentException {\r
-                       editor.setText(text);\r
-               };\r
-\r
-               @Override\r
-               public Component getCustomEditor() {\r
-                       return editor;\r
-               }\r
-       }\r
-\r
-}\r
diff --git a/src/com/billkuker/rocketry/motorsim/visual/FuelPanel.java b/src/com/billkuker/rocketry/motorsim/visual/FuelPanel.java
deleted file mode 100644 (file)
index 2a9ca06..0000000
+++ /dev/null
@@ -1,43 +0,0 @@
-package com.billkuker.rocketry.motorsim.visual;
-
-import javax.measure.quantity.Pressure;
-import javax.measure.quantity.Velocity;
-import javax.measure.unit.SI;
-import javax.swing.BoxLayout;
-import javax.swing.JPanel;
-import javax.swing.JSplitPane;
-
-import org.jscience.physics.amount.Amount;
-
-import com.billkuker.rocketry.motorsim.Fuel;
-
-public class FuelPanel extends JSplitPane {
-       private static final long serialVersionUID = 1L;
-
-       public FuelPanel(Fuel f) {
-               super(JSplitPane.HORIZONTAL_SPLIT);
-               setName("Fuel");
-               Chart<Pressure, Velocity> burnRate;
-               try {
-                       burnRate = new Chart<Pressure, Velocity>(SI.MEGA(SI.PASCAL),
-                                       SI.METERS_PER_SECOND, f, "burnRate");
-               } catch (NoSuchMethodException e) {
-                       throw new Error(e);
-               }
-               burnRate.setDomain(burnRate.new IntervalDomain(Amount.valueOf(0, SI
-                               .MEGA(SI.PASCAL)), Amount.valueOf(11, SI.MEGA(SI.PASCAL)), 20));
-
-               final JPanel p = new JPanel();
-               p.setLayout(new BoxLayout(p, BoxLayout.Y_AXIS));
-
-               p.add(new Editor(f));
-               try {
-                       p.add(new Editor(f.getCombustionProduct()));
-               } catch (Exception e) {
-
-               }
-
-               setLeftComponent(p);
-               setRightComponent(burnRate);
-       }
-}
diff --git a/src/com/billkuker/rocketry/motorsim/visual/GrainPanel.java b/src/com/billkuker/rocketry/motorsim/visual/GrainPanel.java
deleted file mode 100644 (file)
index 35974ec..0000000
+++ /dev/null
@@ -1,245 +0,0 @@
-package com.billkuker.rocketry.motorsim.visual;\r
-\r
-import java.awt.BasicStroke;\r
-import java.awt.BorderLayout;\r
-import java.awt.Color;\r
-import java.awt.Component;\r
-import java.awt.Dimension;\r
-import java.awt.Graphics;\r
-import java.awt.Graphics2D;\r
-import java.awt.Rectangle;\r
-import java.awt.geom.AffineTransform;\r
-import java.beans.PropertyChangeEvent;\r
-import java.beans.PropertyChangeListener;\r
-import java.text.NumberFormat;\r
-\r
-import javax.measure.quantity.Area;\r
-import javax.measure.quantity.Length;\r
-import javax.measure.quantity.Volume;\r
-import javax.measure.unit.SI;\r
-import javax.swing.JFrame;\r
-import javax.swing.JLabel;\r
-import javax.swing.JPanel;\r
-import javax.swing.JSlider;\r
-import javax.swing.JSplitPane;\r
-import javax.swing.WindowConstants;\r
-import javax.swing.event.ChangeEvent;\r
-import javax.swing.event.ChangeListener;\r
-\r
-import org.jscience.physics.amount.Amount;\r
-\r
-import com.billkuker.rocketry.motorsim.ChangeListening;\r
-import com.billkuker.rocketry.motorsim.Grain;\r
-\r
-public class GrainPanel extends JPanel {\r
-       private static final long serialVersionUID = 1L;\r
-       private Amount<Length> displayedRegression = Amount.valueOf(0, SI.MILLIMETER);\r
-       private JLabel l = new JLabel();\r
-       private Chart<Length,Area> area;\r
-       Chart<Length, Volume> volume;\r
-       private XC xc;\r
-       private Grain grain;\r
-       \r
-       public GrainPanel(Grain g){\r
-               super(new BorderLayout());\r
-\r
-               grain = g;\r
-               \r
-               if ( g instanceof ChangeListening.Subject ){\r
-                       ((ChangeListening.Subject)g).addPropertyChangeListener(new PropertyChangeListener(){\r
-                               public void propertyChange(PropertyChangeEvent evt) {\r
-                                       repaint();\r
-                                       area.setDomain(area.new IntervalDomain(Amount.valueOf(0, SI.MILLIMETER), grain.webThickness()));\r
-                                       volume.setDomain(volume.new IntervalDomain(Amount.valueOf(0, SI.MILLIMETER), grain.webThickness()));\r
-                               }\r
-                       });\r
-               }\r
-\r
-\r
-               try {\r
-\r
-                       area = new Chart<Length, Area>(\r
-                                       SI.MILLIMETER,\r
-                                       SI.MILLIMETER.pow(2).asType(Area.class),\r
-                                       grain,\r
-                                       "surfaceArea");\r
-                       area.setDomain(area.new IntervalDomain(Amount.valueOf(0, SI.MILLIMETER), grain.webThickness()));\r
-                       \r
-                       volume = new Chart<Length, Volume>(\r
-                                       SI.MILLIMETER,\r
-                                       SI.MILLIMETER.pow(3).asType(Volume.class),\r
-                                       grain,\r
-                                       "volume");\r
-                       volume.setDomain(volume.new IntervalDomain(Amount.valueOf(0, SI.MILLIMETER), grain.webThickness()));\r
-\r
-                       area.setMaximumSize(new Dimension(200,100));\r
-                       volume.setMaximumSize(new Dimension(200,100));\r
-                       \r
-\r
-               } catch (ClassCastException e) {\r
-                       // TODO Auto-generated catch block\r
-                       e.printStackTrace();\r
-               } catch (NoSuchMethodException e) {\r
-                       // TODO Auto-generated catch block\r
-                       e.printStackTrace();\r
-               }\r
-               \r
-               addComponents(\r
-                               xc = new XC(grain),\r
-                               new SL(),\r
-                               l,\r
-                               area,\r
-                               volume\r
-               );\r
-       }\r
-       \r
-       protected void addComponents(\r
-                       Component crossSection,\r
-                       Component slider,\r
-                       Component label,\r
-                       Component area,\r
-                       Component volume\r
-       ){\r
-               \r
-               JSplitPane v = new JSplitPane(JSplitPane.VERTICAL_SPLIT);\r
-               JSplitPane h = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT);\r
-               \r
-               JPanel graphics = new JPanel(new BorderLayout());\r
-               graphics.add(crossSection, BorderLayout.CENTER);\r
-               graphics.add(label, BorderLayout.NORTH);\r
-               graphics.add(slider, BorderLayout.SOUTH);\r
-       \r
-               v.setTopComponent(h);\r
-               v.setBottomComponent(area);\r
-               h.setLeftComponent(graphics);\r
-               h.setRightComponent(volume);\r
-               add(v);\r
-               \r
-               h.resetToPreferredSizes();\r
-               v.resetToPreferredSizes();\r
-\r
-       }\r
-       \r
-       public void setDisplayedRegression( Amount<Length> r ){\r
-               displayedRegression = r;\r
-               \r
-               NumberFormat nf = NumberFormat.getInstance();\r
-               nf.setMaximumFractionDigits(2);\r
-               l.setText("Regression: " + nf.format(displayedRegression.doubleValue(SI.MILLIMETER)) + "mm");\r
-               \r
-               area.mark(displayedRegression);\r
-               volume.mark(displayedRegression);\r
-               if ( xc != null )\r
-                       xc.repaint();\r
-       }\r
-       \r
-       private class XC extends JPanel{\r
-               private static final long serialVersionUID = 1L;\r
-               Grain grain;\r
-               public XC(Grain g){\r
-                       grain = g;\r
-                       java.awt.geom.Area unburnt = grain.getSideView(Amount.valueOf(0, SI.MILLIMETER));\r
-                       \r
-                       Rectangle bounds = unburnt.getBounds();\r
-                       double max = bounds.getWidth();\r
-                       if ( bounds.getHeight() > max )\r
-                               max = bounds.getHeight();\r
-                       int w = (int)(bounds.getWidth() * 200.0 / max);\r
-                       if ( w < 40 )\r
-                               w = 40;\r
-                       \r
-                       Dimension sz = new Dimension(240+w, 250);\r
-                       setMinimumSize(sz);\r
-                       setPreferredSize(sz);\r
-                       setMaximumSize(sz);\r
-               }\r
-               public void paint(Graphics g){\r
-                       super.paint(g);\r
-                       Graphics2D g2d = (Graphics2D)g;\r
-                       g2d.translate(10, 30);\r
-                       /*\r
-                       grain.draw(g2d, displayedRegression );\r
-                       */\r
-                       \r
-                       {\r
-                               AffineTransform t = g2d.getTransform();\r
-                               java.awt.geom.Area unburnt = grain.getCrossSection(Amount.valueOf(0, SI.MILLIMETER));\r
-                               \r
-                               Rectangle bounds = unburnt.getBounds();\r
-                               g2d.scale(200 / bounds.getWidth(), 200 / bounds.getHeight());\r
-                               g2d.translate(-bounds.getX(), -bounds.getY());\r
-       \r
-                               //Draw the fuel that is left\r
-                               java.awt.geom.Area burning = grain.getCrossSection(displayedRegression);\r
-                               g2d.setColor(Color.RED);\r
-                               g2d.fill(burning);\r
-                               //Draw the fuel that is left\r
-                               java.awt.geom.Area left = grain.getCrossSection(displayedRegression.plus(grain.webThickness().divide(30)));\r
-                               g2d.setColor(Color.GRAY);\r
-                               g2d.fill(left);\r
-                               //Draw the outline of the unburnt grain\r
-                               g2d.setStroke(new BasicStroke(1.0f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_ROUND));\r
-                               g2d.setColor(Color.BLACK);\r
-                               g2d.draw(unburnt);\r
-                               //untranslate\r
-                               g2d.setTransform(t);\r
-                       }\r
-                       {\r
-                               AffineTransform t = g2d.getTransform();\r
-                               java.awt.geom.Area unburnt = grain.getSideView(Amount.valueOf(0, SI.MILLIMETER));\r
-                               \r
-                               Rectangle bounds = unburnt.getBounds();\r
-                               g2d.translate(220, 0);\r
-                               \r
-                               double max = bounds.getWidth();\r
-                               if ( bounds.getHeight() > max )\r
-                                       max = bounds.getHeight();\r
-                               \r
-                               g2d.scale(200 / max, 200 / max);\r
-                               g2d.translate(-bounds.getX(), -bounds.getY());\r
-       \r
-                               //Draw the fuel that is left\r
-                               java.awt.geom.Area burning = grain.getSideView(displayedRegression);\r
-                               g2d.setColor(Color.RED);\r
-                               g2d.fill(burning);\r
-                               //Draw the fuel that is left\r
-                               java.awt.geom.Area left = grain.getSideView(displayedRegression.plus(grain.webThickness().divide(30)));\r
-                               g2d.setColor(Color.GRAY);\r
-                               g2d.fill(left);\r
-                               //Draw the outline of the unburnt grain\r
-                               g2d.setColor(Color.BLACK);\r
-                               g2d.draw(unburnt);\r
-                               //untranslate\r
-                               g2d.setTransform(t);\r
-                       }\r
-                       \r
-               }\r
-       }\r
-       \r
-       private class SL extends JSlider implements ChangeListener{\r
-               private static final long serialVersionUID = 1L;\r
-               private static final int STEPS = 60;\r
-               public SL(){\r
-                       addChangeListener(this);\r
-                       setMinimum(0);\r
-                       setMaximum(STEPS);\r
-                       setValue(0);\r
-               }\r
-               \r
-               public void stateChanged(ChangeEvent e) {\r
-                       double r = ((SL)e.getSource()).getValue();\r
-\r
-                       setDisplayedRegression(grain.webThickness().divide(STEPS).times(r));\r
-               }\r
-       }\r
-       \r
-       public void showAsWindow(){\r
-               JFrame f = new JFrame();\r
-               f.setTitle(grain.getClass().getName());\r
-               f.setSize(1024,600);\r
-               f.setContentPane(this);\r
-               f.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);\r
-               f.setVisible(true);\r
-       }\r
-\r
-}\r
diff --git a/src/com/billkuker/rocketry/motorsim/visual/HardwarePanel.java b/src/com/billkuker/rocketry/motorsim/visual/HardwarePanel.java
deleted file mode 100644 (file)
index 00e1fa5..0000000
+++ /dev/null
@@ -1,104 +0,0 @@
-package com.billkuker.rocketry.motorsim.visual;\r
-\r
-import java.awt.BasicStroke;\r
-import java.awt.Color;\r
-import java.awt.Graphics;\r
-import java.awt.Graphics2D;\r
-import java.awt.Rectangle;\r
-import java.awt.Shape;\r
-import java.beans.PropertyChangeEvent;\r
-import java.beans.PropertyChangeListener;\r
-\r
-import javax.measure.unit.SI;\r
-import javax.swing.JFrame;\r
-import javax.swing.JPanel;\r
-import javax.swing.WindowConstants;\r
-\r
-import org.jscience.physics.amount.Amount;\r
-\r
-import com.billkuker.rocketry.motorsim.Chamber;\r
-import com.billkuker.rocketry.motorsim.ChangeListening;\r
-import com.billkuker.rocketry.motorsim.ConvergentDivergentNozzle;\r
-import com.billkuker.rocketry.motorsim.CylindricalChamber;\r
-import com.billkuker.rocketry.motorsim.Nozzle;\r
-\r
-public class HardwarePanel extends JPanel {\r
-       private static final long serialVersionUID = 1L;\r
-       private Nozzle nozzle;\r
-       private Chamber chamber;\r
-       \r
-       public HardwarePanel(Nozzle n, Chamber c){\r
-               nozzle = n;\r
-               chamber = c;\r
-               if ( n instanceof ChangeListening.Subject ){\r
-                       ((ChangeListening.Subject)n).addPropertyChangeListener(new PropertyChangeListener(){\r
-                               public void propertyChange(PropertyChangeEvent evt) {\r
-                                       repaint();\r
-                               }\r
-                       });\r
-               }\r
-               if ( c instanceof ChangeListening.Subject ){\r
-                       ((ChangeListening.Subject)c).addPropertyChangeListener(new PropertyChangeListener(){\r
-                               public void propertyChange(PropertyChangeEvent evt) {\r
-                                       repaint();\r
-                               }\r
-                       });\r
-               }\r
-       }\r
-       \r
-       public void paint(Graphics g){\r
-               super.paint(g);\r
-               Graphics2D g2d = (Graphics2D)g;\r
-               g2d.translate(10, 10);\r
-\r
-               g2d.setColor(Color.black);\r
-               \r
-               Shape c = chamber.chamberShape();\r
-               \r
-               Shape n = nozzle.nozzleShape(((CylindricalChamber)chamber).getID());\r
-               \r
-               Rectangle cb = c.getBounds();\r
-               Rectangle nb = n.getBounds();\r
-               double w, h;\r
-               w = Math.max(cb.getWidth(), nb.getWidth());\r
-               h = cb.getHeight() + nb.getHeight();\r
-               \r
-               double mw, mh;\r
-               mw = getHeight() - 10;\r
-               mh = getWidth() - 10;\r
-               \r
-               double sw, sh, s;\r
-               sw = mw / w;\r
-               sh = mh / h;\r
-               s = Math.min(sw, sh);\r
-               \r
-               g2d.rotate(-Math.PI / 2);\r
-               \r
-               g2d.translate(0, -cb.getY() - 5);\r
-               g2d.scale(s, s);\r
-               g2d.translate(-(getHeight()/(s*2)), 0);\r
-               \r
-               g2d.setStroke(new BasicStroke(1));\r
-               g2d.draw( c );\r
-               g2d.translate(0, cb.getHeight());\r
-               \r
-               g2d.draw(n);\r
-       }\r
-       \r
-       public void showAsWindow(){\r
-               JFrame f = new JFrame();\r
-               f.setSize(220,250);\r
-               f.setContentPane(this);\r
-               f.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);\r
-               f.setVisible(true);\r
-       }\r
-       \r
-       public static void main(String args[]) throws Exception{\r
-               ConvergentDivergentNozzle n = new ConvergentDivergentNozzle();\r
-               CylindricalChamber c = new CylindricalChamber();\r
-               n.setThroatDiameter(Amount.valueOf(10, SI.MILLIMETER));\r
-               n.setExitDiameter(Amount.valueOf(20, SI.MILLIMETER));\r
-               //new Editor(n).showAsWindow();\r
-               new HardwarePanel(n,c).showAsWindow();\r
-       }\r
-}\r
diff --git a/src/com/billkuker/rocketry/motorsim/visual/workbench/BurnWatcher.java b/src/com/billkuker/rocketry/motorsim/visual/workbench/BurnWatcher.java
deleted file mode 100644 (file)
index 7301cbd..0000000
+++ /dev/null
@@ -1,7 +0,0 @@
-package com.billkuker.rocketry.motorsim.visual.workbench;
-
-import com.billkuker.rocketry.motorsim.Burn;
-
-public interface BurnWatcher {
-       public void replace( Burn oldBurn, Burn newBurn);
-}
diff --git a/src/com/billkuker/rocketry/motorsim/visual/workbench/MotorEditor.java b/src/com/billkuker/rocketry/motorsim/visual/workbench/MotorEditor.java
deleted file mode 100644 (file)
index 2e7ecbe..0000000
+++ /dev/null
@@ -1,429 +0,0 @@
-package com.billkuker.rocketry.motorsim.visual.workbench;\r
-\r
-import java.awt.BorderLayout;\r
-import java.awt.Dimension;\r
-import java.awt.event.ActionEvent;\r
-import java.awt.event.ActionListener;\r
-import java.awt.event.FocusEvent;\r
-import java.awt.event.FocusListener;\r
-import java.beans.PropertyChangeEvent;\r
-import java.beans.PropertyChangeListener;\r
-import java.beans.PropertyVetoException;\r
-import java.io.IOException;\r
-import java.util.HashMap;\r
-import java.util.Map;\r
-import java.util.Vector;\r
-\r
-import javax.measure.quantity.Length;\r
-import javax.measure.unit.SI;\r
-import javax.swing.Box;\r
-import javax.swing.BoxLayout;\r
-import javax.swing.ComboBoxModel;\r
-import javax.swing.JButton;\r
-import javax.swing.JComboBox;\r
-import javax.swing.JFrame;\r
-import javax.swing.JLabel;\r
-import javax.swing.JPanel;\r
-import javax.swing.JProgressBar;\r
-import javax.swing.JSplitPane;\r
-import javax.swing.JTabbedPane;\r
-import javax.swing.JTextArea;\r
-import javax.swing.JTextField;\r
-import javax.swing.SwingUtilities;\r
-import javax.swing.UIManager;\r
-import javax.swing.WindowConstants;\r
-\r
-import org.apache.log4j.Logger;\r
-import org.fife.ui.rsyntaxtextarea.RSyntaxTextArea;\r
-import org.fife.ui.rsyntaxtextarea.SyntaxConstants;\r
-import org.jscience.physics.amount.Amount;\r
-\r
-import com.billkuker.rocketry.motorsim.Burn;\r
-import com.billkuker.rocketry.motorsim.Chamber;\r
-import com.billkuker.rocketry.motorsim.ChangeListening;\r
-import com.billkuker.rocketry.motorsim.ConvergentDivergentNozzle;\r
-import com.billkuker.rocketry.motorsim.CylindricalChamber;\r
-import com.billkuker.rocketry.motorsim.Fuel;\r
-import com.billkuker.rocketry.motorsim.Grain;\r
-import com.billkuker.rocketry.motorsim.Motor;\r
-import com.billkuker.rocketry.motorsim.Nozzle;\r
-import com.billkuker.rocketry.motorsim.RocketScience;\r
-import com.billkuker.rocketry.motorsim.fuel.KNSU;\r
-import com.billkuker.rocketry.motorsim.grain.CSlot;\r
-import com.billkuker.rocketry.motorsim.grain.CoredCylindricalGrain;\r
-import com.billkuker.rocketry.motorsim.grain.EndBurner;\r
-import com.billkuker.rocketry.motorsim.grain.Finocyl;\r
-import com.billkuker.rocketry.motorsim.grain.Moonburner;\r
-import com.billkuker.rocketry.motorsim.grain.MultiGrain;\r
-import com.billkuker.rocketry.motorsim.grain.RodAndTubeGrain;\r
-import com.billkuker.rocketry.motorsim.io.MotorIO;\r
-import com.billkuker.rocketry.motorsim.visual.BurnPanel;\r
-import com.billkuker.rocketry.motorsim.visual.Editor;\r
-import com.billkuker.rocketry.motorsim.visual.GrainPanel;\r
-import com.billkuker.rocketry.motorsim.visual.HardwarePanel;\r
-\r
-public class MotorEditor extends JTabbedPane implements PropertyChangeListener {\r
-       private static final long serialVersionUID = 1L;\r
-       private static Logger log = Logger.getLogger(MotorEditor.class);\r
-       RSyntaxTextArea text = new RSyntaxTextArea();\r
-       Motor motor;\r
-       GrainEditor grainEditor;\r
-       BurnTab bt;\r
-       Burn burn;\r
-\r
-       private Vector<BurnWatcher> burnWatchers = new Vector<BurnWatcher>();\r
-       private ComboBoxModel availableFuels;\r
-\r
-       //private static final int XML_TAB = 0;\r
-       private static final int CASING_TAB = 0;\r
-       private static final int GRAIN_TAB = 1;\r
-       private static final int BURN_TAB = 2;\r
-\r
-       @SuppressWarnings("unchecked")\r
-       private Class[] grainTypes = { CoredCylindricalGrain.class, Finocyl.class,\r
-                       Moonburner.class, RodAndTubeGrain.class, CSlot.class, EndBurner.class };\r
-\r
-       private abstract class Chooser<T> extends JPanel {\r
-               private static final long serialVersionUID = 1L;\r
-               private Class<? extends T>[] types;\r
-               private Map<Class<? extends T>, T> old = new HashMap<Class<? extends T>, T>();\r
-\r
-               public Chooser(T initial, Class<? extends T>... ts) {\r
-                       types = ts;\r
-                       if ( initial != null )\r
-                               old.put((Class<? extends T>)initial.getClass(), initial);\r
-                       setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));\r
-                       for (final Class<? extends T> c : types) {\r
-                               JButton b = new JButton(c.getSimpleName());\r
-                               add(b);\r
-                               b.addActionListener(new ActionListener() {\r
-                                       public void actionPerformed(ActionEvent e) {\r
-                                               try {\r
-                                                       T val = old.get(c);\r
-                                                       if ( val == null ){\r
-                                                               System.err.println("CREATED NEW =========================");\r
-                                                               val = c.newInstance();\r
-                                                               old.put(c, val);\r
-                                                       }\r
-                                                       choiceMade(val);\r
-                                               } catch (InstantiationException e1) {\r
-                                                       e1.printStackTrace();\r
-                                               } catch (IllegalAccessException e1) {\r
-                                                       e1.printStackTrace();\r
-                                               }\r
-                                       }\r
-                               });\r
-                       }\r
-               }\r
-\r
-               protected abstract void choiceMade(T o);\r
-       }\r
-\r
-       private class BurnTab extends JPanel {\r
-               private static final long serialVersionUID = 1L;\r
-               private Thread currentThread;\r
-               \r
-               public BurnTab() {\r
-                       setLayout(new BorderLayout());\r
-                       setName("Simulation Results");\r
-                       reBurn();\r
-               }\r
-               \r
-               private class BurnCanceled extends RuntimeException{\r
-                       private static final long serialVersionUID = 1L;\r
-               };\r
-\r
-               public void reBurn() {\r
-                       removeAll();\r
-                       currentThread = new Thread() {\r
-                               public void run() {\r
-                                       final Thread me = this;\r
-                                       final JProgressBar bar = new JProgressBar(0, 100);\r
-                                       add(bar, BorderLayout.NORTH);\r
-                                       final JLabel progress = new JLabel();\r
-                                       add(progress, BorderLayout.CENTER);\r
-                                       try {\r
-                                               final Burn b = new Burn(motor,\r
-                                                               new Burn.BurnProgressListener() {\r
-                                                                       @Override\r
-                                                                       public void setProgress(float f) {\r
-                                                                               int pct = (int)(f*100);\r
-                                                                               bar.setValue(pct);\r
-                                                                               Amount<Length> web = motor.getGrain().webThickness();\r
-                                                                               Amount<Length> remaining = web.times(1.0 - f);\r
-                                                                               \r
-                                                                               progress.setText("Progress: " + pct + "% (" + RocketScience.approx(remaining) + " web thickness remaining)");\r
-                                                                               if ( currentThread != me ){\r
-                                                                                       throw new BurnCanceled();\r
-                                                                               }\r
-                                                                       }\r
-                                                               });\r
-\r
-                                               final BurnPanel bp = new BurnPanel(b);\r
-                                               SwingUtilities.invokeLater(new Thread() {\r
-                                                       public void run() {\r
-                                                               remove(bar);\r
-                                                               add(bp, BorderLayout.CENTER);\r
-\r
-                                                               for (BurnWatcher bw : burnWatchers)\r
-                                                                       bw.replace(burn, b);\r
-                                                               burn = b;\r
-\r
-                                                               revalidate();\r
-                                                       }\r
-                                               });\r
-                                       } catch (BurnCanceled c){\r
-                                               log.info("Burn Canceled!");\r
-                                       } catch (Exception e) {\r
-                                               remove(bar);\r
-                                               JTextArea t = new JTextArea(e.getMessage());\r
-                                               t.setEditable(false);\r
-                                               add(t);\r
-                                       }\r
-                               }\r
-                       };\r
-                       currentThread.start();\r
-               }\r
-       }\r
-\r
-       private class GrainEditor extends JSplitPane {\r
-               private static final long serialVersionUID = 1L;\r
-\r
-               public GrainEditor(final Grain g) {\r
-                       super(JSplitPane.HORIZONTAL_SPLIT);\r
-                       setName("Grain Geometry");\r
-                       setRightComponent(new GrainPanel(g));\r
-                       if (g instanceof Grain.Composite) {\r
-                               final JPanel p = new JPanel();\r
-                               p.setLayout(new BoxLayout(p, BoxLayout.Y_AXIS));\r
-                               p.add(new Editor(g));\r
-                               for (Grain gg : ((Grain.Composite) g).getGrains()) {\r
-                                       final int grainEditorIndex = p.getComponentCount() + 1;\r
-                                       p.add(new Chooser<Grain>(gg, grainTypes) {\r
-                                               private static final long serialVersionUID = 1L;\r
-\r
-                                               @Override\r
-                                               protected void choiceMade(Grain ng) {\r
-                                                       if (g instanceof MultiGrain) {\r
-                                                               ((MultiGrain) g).setGrain(ng);\r
-                                                               p.remove(grainEditorIndex);\r
-                                                               p.add(new Editor(ng), grainEditorIndex);\r
-                                                               p.remove(0);\r
-                                                               p.add(new Editor(g), 0);\r
-                                                       }\r
-                                               }\r
-                                       });\r
-                                       p.add(new Editor(gg));\r
-                                       if (gg instanceof ChangeListening.Subject) {\r
-                                               ((ChangeListening.Subject) gg)\r
-                                                               .addPropertyChangeListener(MotorEditor.this);\r
-                                       }\r
-                               }\r
-                               setLeftComponent(p);\r
-                       } else {\r
-                               setLeftComponent(new Editor(g));\r
-                       }\r
-                       // setDividerLocation(.25);\r
-                       // setResizeWeight(.25);\r
-                       if (g instanceof ChangeListening.Subject) {\r
-                               ((ChangeListening.Subject) g)\r
-                                               .addPropertyChangeListener(MotorEditor.this);\r
-                       }\r
-               }\r
-       }\r
-\r
-       private class CaseEditor extends JSplitPane {\r
-               private static final long serialVersionUID = 1L;\r
-\r
-               public CaseEditor(Nozzle n, Chamber c) {\r
-                       super(JSplitPane.VERTICAL_SPLIT);\r
-                       setName("General Parameters");\r
-                       \r
-                       JPanel parts = new JPanel();\r
-                       parts.setLayout(new BoxLayout(parts, BoxLayout.X_AXIS));\r
-                       setTopComponent(parts);\r
-                       setBottomComponent(new HardwarePanel(n, c));\r
-                       \r
-                       JPanel nameAndFuel = new JPanel();\r
-                       nameAndFuel.setLayout(new BoxLayout(nameAndFuel, BoxLayout.Y_AXIS));\r
-\r
-                       nameAndFuel.add(new JLabel("Name:"));\r
-                       nameAndFuel.add(new JTextField(motor.getName()) {\r
-                               private static final long serialVersionUID = 1L;\r
-                               {\r
-                                       setMinimumSize(new Dimension(200, 20));\r
-                                       setMaximumSize(new Dimension(Short.MAX_VALUE, 20));\r
-                                       final JTextField t = this;\r
-                                       addFocusListener(new FocusListener() {\r
-\r
-                                               @Override\r
-                                               public void focusLost(FocusEvent e) {\r
-                                                       String n = t.getText();\r
-                                                       if (!"".equals(n) && !n.equals(motor.getName())) {\r
-                                                               motor.setName(n);\r
-                                                       } else {\r
-                                                               t.setText(motor.getName());\r
-                                                       }\r
-                                               }\r
-\r
-                                               @Override\r
-                                               public void focusGained(FocusEvent e) {\r
-\r
-                                               }\r
-                                       });\r
-\r
-                               }\r
-                       });\r
-                       nameAndFuel.add(new JLabel("Fuel:"));\r
-                       nameAndFuel.add( new JComboBox(availableFuels){{\r
-                               setMinimumSize(new Dimension(200, 20));\r
-                               setMaximumSize(new Dimension(Short.MAX_VALUE, 20));\r
-                               addActionListener(new ActionListener(){\r
-                                       @Override\r
-                                       public void actionPerformed(ActionEvent e) {\r
-                                               motor.setFuel((Fuel)getSelectedItem());\r
-                                               System.out.println("FUEL CHANGED");\r
-                                       }});\r
-                       }});\r
-                       nameAndFuel.add(Box.createVerticalGlue());\r
-                       parts.add(nameAndFuel);\r
-                       \r
-                       JPanel casing = new JPanel();\r
-                       casing.setLayout(new BoxLayout(casing, BoxLayout.Y_AXIS));\r
-                       casing.add(new JLabel("Casing:"));\r
-                       casing.add(new Editor(c));\r
-                       parts.add(casing);\r
-                       \r
-                       JPanel nozzle = new JPanel();\r
-                       nozzle.setLayout(new BoxLayout(nozzle, BoxLayout.Y_AXIS));\r
-                       nozzle.add(new JLabel("Nozzle:"));\r
-                       nozzle.add(new Editor(n));\r
-                       parts.add(nozzle);\r
-\r
-                       if (n instanceof ChangeListening.Subject) {\r
-                               ((ChangeListening.Subject) n)\r
-                                               .addPropertyChangeListener(MotorEditor.this);\r
-                       }\r
-                       if (c instanceof ChangeListening.Subject) {\r
-                               ((ChangeListening.Subject) c)\r
-                                               .addPropertyChangeListener(MotorEditor.this);\r
-                       }\r
-               }\r
-       }\r
-\r
-       {\r
-               text.setSyntaxEditingStyle(SyntaxConstants.SYNTAX_STYLE_XML);\r
-\r
-       }\r
-\r
-       public MotorEditor(Motor m, ComboBoxModel fuels) {\r
-               super(JTabbedPane.BOTTOM);\r
-               this.availableFuels = fuels;\r
-               text.setName("XML");\r
-               text.setEditable(false);\r
-               //add(text, XML_TAB);\r
-               setMotor(m, true);\r
-       }\r
-\r
-       public Motor getMotor() {\r
-               return motor;\r
-       }\r
-\r
-       private void reText() {\r
-               try {\r
-                       text.setText(MotorIO.writeMotor(motor));\r
-               } catch (IOException e) {\r
-                       throw new Error(e);\r
-               }\r
-       }\r
-\r
-       private void setMotor(Motor m, boolean retext) {\r
-               if (motor != null)\r
-                       motor.removePropertyChangeListener(this);\r
-               motor = m;\r
-               motor.addPropertyChangeListener(this);\r
-               if (retext)\r
-                       reText();\r
-               if (grainEditor != null)\r
-                       remove(grainEditor);\r
-               while (getTabCount() > 1)\r
-                       removeTabAt(1);\r
-               add(new CaseEditor(motor.getNozzle(), motor.getChamber()), CASING_TAB);\r
-               add(new GrainEditor(motor.getGrain()), GRAIN_TAB);\r
-               add(bt = new BurnTab(), BURN_TAB);\r
-       }\r
-\r
-       @Deprecated\r
-       public static Motor defaultMotor() {\r
-               Motor m = new Motor();\r
-               m.setName("Example Motor");\r
-               m.setFuel(new KNSU());\r
-\r
-               CylindricalChamber c = new CylindricalChamber();\r
-               c.setLength(Amount.valueOf(200, SI.MILLIMETER));\r
-               c.setID(Amount.valueOf(30, SI.MILLIMETER));\r
-               m.setChamber(c);\r
-\r
-               CoredCylindricalGrain g = new CoredCylindricalGrain();\r
-               try {\r
-                       g.setLength(Amount.valueOf(70, SI.MILLIMETER));\r
-                       g.setOD(Amount.valueOf(30, SI.MILLIMETER));\r
-                       g.setID(Amount.valueOf(10, SI.MILLIMETER));\r
-               } catch (PropertyVetoException v) {\r
-                       throw new Error(v);\r
-               }\r
-\r
-               m.setGrain(new MultiGrain(g, 2));\r
-\r
-               ConvergentDivergentNozzle n = new ConvergentDivergentNozzle();\r
-               n.setThroatDiameter(Amount.valueOf(7.962, SI.MILLIMETER));\r
-               n.setExitDiameter(Amount.valueOf(13.79, SI.MILLIMETER));\r
-               n.setEfficiency(.85);\r
-               m.setNozzle(n);\r
-\r
-               return m;\r
-       }\r
-\r
-       public void focusOnObject(Object o) {\r
-               if (o instanceof Grain)\r
-                       setSelectedIndex(GRAIN_TAB);\r
-               if (o instanceof Chamber || o instanceof Nozzle)\r
-                       setSelectedIndex(CASING_TAB);\r
-       }\r
-\r
-       public void addBurnWatcher(BurnWatcher bw) {\r
-               burnWatchers.add(bw);\r
-       }\r
-\r
-       @Deprecated\r
-       public void showAsWindow() {\r
-               JFrame f = new JFrame();\r
-               f.setSize(1024, 768);\r
-               f.setContentPane(this);\r
-               f.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);\r
-               f.setVisible(true);\r
-       }\r
-\r
-       public static void main(String args[]) throws Exception {\r
-               try {\r
-                       UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());\r
-               } catch (Exception e1) {\r
-                       e1.printStackTrace();\r
-               }\r
-               Vector<Fuel> ff = new Vector<Fuel>();\r
-               ff.add(new KNSU());\r
-               //new MotorEditor(defaultMotor(), ff).showAsWindow();\r
-       }\r
-\r
-       public void propertyChange(PropertyChangeEvent evt) {\r
-               reText();\r
-               // Dont re-burn for a name change!\r
-               if (!evt.getPropertyName().equals("Name")){\r
-                       bt.reBurn();\r
-               } else {\r
-                       for (BurnWatcher bw : burnWatchers)\r
-                               bw.replace(burn, burn);\r
-               }\r
-       }\r
-\r
-}\r
diff --git a/src/com/billkuker/rocketry/motorsim/visual/workbench/MotorWorkbench.java b/src/com/billkuker/rocketry/motorsim/visual/workbench/MotorWorkbench.java
deleted file mode 100644 (file)
index b6047ee..0000000
+++ /dev/null
@@ -1,424 +0,0 @@
-package com.billkuker.rocketry.motorsim.visual.workbench;\r
-\r
-import java.awt.BorderLayout;\r
-import java.awt.Dimension;\r
-import java.awt.FileDialog;\r
-import java.awt.event.ActionEvent;\r
-import java.awt.event.ActionListener;\r
-import java.beans.PropertyChangeEvent;\r
-import java.beans.PropertyChangeListener;\r
-import java.io.File;\r
-import java.util.HashMap;\r
-import java.util.Vector;\r
-\r
-import javax.swing.ButtonGroup;\r
-import javax.swing.DefaultComboBoxModel;\r
-import javax.swing.JFrame;\r
-import javax.swing.JMenu;\r
-import javax.swing.JMenuBar;\r
-import javax.swing.JMenuItem;\r
-import javax.swing.JOptionPane;\r
-import javax.swing.JPanel;\r
-import javax.swing.JRadioButtonMenuItem;\r
-import javax.swing.JScrollPane;\r
-import javax.swing.JSeparator;\r
-import javax.swing.JSplitPane;\r
-import javax.swing.JTabbedPane;\r
-import javax.swing.JTree;\r
-import javax.swing.WindowConstants;\r
-import javax.swing.event.TreeSelectionEvent;\r
-import javax.swing.event.TreeSelectionListener;\r
-import javax.swing.tree.DefaultMutableTreeNode;\r
-import javax.swing.tree.TreePath;\r
-import javax.swing.tree.TreeSelectionModel;\r
-\r
-import com.billkuker.rocketry.motorsim.Burn;\r
-import com.billkuker.rocketry.motorsim.Fuel;\r
-import com.billkuker.rocketry.motorsim.Motor;\r
-import com.billkuker.rocketry.motorsim.RocketScience.UnitPreference;\r
-import com.billkuker.rocketry.motorsim.fuel.KNDX;\r
-import com.billkuker.rocketry.motorsim.fuel.KNER;\r
-import com.billkuker.rocketry.motorsim.fuel.KNSB;\r
-import com.billkuker.rocketry.motorsim.fuel.KNSU;\r
-import com.billkuker.rocketry.motorsim.io.ENGExporter;\r
-import com.billkuker.rocketry.motorsim.io.MotorIO;\r
-import com.billkuker.rocketry.motorsim.visual.FuelPanel;\r
-import com.billkuker.rocketry.motorsim.visual.workbench.WorkbenchTreeModel.FuelEditNode;\r
-import com.billkuker.rocketry.motorsim.visual.workbench.WorkbenchTreeModel.FuelNode;\r
-\r
-public class MotorWorkbench extends JFrame implements TreeSelectionListener {\r
-       private static final long serialVersionUID = 1L;\r
-       \r
-       @SuppressWarnings("unchecked")\r
-       private Class[] fuelTypes = { KNSB.class, KNSU.class, KNER.class,\r
-                       KNDX.class };\r
-       \r
-       private JPanel top;\r
-       private JSplitPane split;\r
-       private JTree tree;\r
-       private JTabbedPane motors;\r
-       private WorkbenchTreeModel tm;\r
-       private MultiBurnChart mb;\r
-       private JFrame allBurns;\r
-\r
-       private HashMap<MotorEditor, File> e2f = new HashMap<MotorEditor, File>();\r
-       private HashMap<File, MotorEditor> f2e = new HashMap<File, MotorEditor>();\r
-\r
-       private HashMap<Motor, MotorEditor> m2e = new HashMap<Motor, MotorEditor>();\r
-       \r
-       private DefaultComboBoxModel fuels = new DefaultComboBoxModel();\r
-       \r
-       public MotorWorkbench() {\r
-               setTitle("MotorSim 1.0 RC1");\r
-               addMenu();\r
-               setSize(1024, 768);\r
-               top = new JPanel(new BorderLayout());\r
-               setContentPane(top);\r
-               \r
-               mb = new MultiBurnChart();\r
-               allBurns = new JFrame();\r
-               allBurns.setTitle("All Burns");\r
-               allBurns.setSize(800, 600);\r
-               setDefaultCloseOperation(WindowConstants.HIDE_ON_CLOSE);\r
-               allBurns.add(mb);\r
-\r
-               motors = new JTabbedPane();\r
-\r
-               tree = new JTree(tm = new WorkbenchTreeModel());\r
-               tree.setCellRenderer(new WorkbenchTreeCellRenderer());\r
-               tree.getSelectionModel().setSelectionMode(\r
-                               TreeSelectionModel.SINGLE_TREE_SELECTION);\r
-               tree.setMinimumSize(new Dimension(200, 100));\r
-\r
-               // Listen for when the selection changes.\r
-               tree.addTreeSelectionListener(this);\r
-\r
-               split = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, new JScrollPane(\r
-                               tree), motors);\r
-               split.setDividerLocation(200);\r
-               split.setResizeWeight(0);\r
-               split.resetToPreferredSizes();\r
-               split.revalidate();\r
-               \r
-               top.add(split, BorderLayout.CENTER);\r
-               \r
-               for ( Class<Fuel> f : fuelTypes){\r
-                       try {\r
-                               addFuel(f.newInstance());\r
-                       } catch (InstantiationException e) {\r
-                               // TODO Auto-generated catch block\r
-                               e.printStackTrace();\r
-                       } catch (IllegalAccessException e) {\r
-                               // TODO Auto-generated catch block\r
-                               e.printStackTrace();\r
-                       }\r
-               }\r
-\r
-               setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);\r
-               setVisible(true);\r
-\r
-       }\r
-\r
-       private void addMenu() {\r
-\r
-               setJMenuBar(new JMenuBar() {\r
-                       private static final long serialVersionUID = 1L;\r
-\r
-                       {\r
-                               add(new JMenu("File") {\r
-                                       private static final long serialVersionUID = 1L;\r
-\r
-                                       {\r
-                                               add(new JMenuItem("New Motor") {\r
-                                                       private static final long serialVersionUID = 1L;\r
-                                                       {\r
-                                                               addActionListener(new ActionListener() {\r
-\r
-                                                                       @Override\r
-                                                                       public void actionPerformed(ActionEvent arg0) {\r
-                                                                               addMotor(MotorEditor.defaultMotor(),\r
-                                                                                               null);\r
-                                                                       }\r
-                                                               });\r
-\r
-                                                       }\r
-                                               });\r
-                                               add(new JMenuItem("Open...") {\r
-                                                       private static final long serialVersionUID = 1L;\r
-                                                       {\r
-                                                               addActionListener(new ActionListener() {\r
-                                                                       @Override\r
-                                                                       public void actionPerformed(ActionEvent arg0) {\r
-\r
-                                                                               final FileDialog fd = new FileDialog(MotorWorkbench.this, "Open Motor", FileDialog.LOAD);\r
-                                                                               fd.setVisible(true);\r
-                                                                               if ( fd.getFile() != null ) {\r
-                                                                                       File file = new File(fd.getDirectory() + fd.getFile());\r
-                                                                                       if (f2e.get(file) != null) {\r
-                                                                                               motors.setSelectedComponent(f2e\r
-                                                                                                               .get(file));\r
-                                                                                               return;\r
-                                                                                       }\r
-                                                                                       try {\r
-                                                                                               Motor m = MotorIO\r
-                                                                                                               .readMotor(file);\r
-                                                                                               addMotor(m, file);\r
-                                                                                       } catch (Exception e) {\r
-                                                                                               JOptionPane.showMessageDialog(\r
-                                                                                                               MotorWorkbench.this, e\r
-                                                                                                                               .getMessage());\r
-                                                                                       }\r
-                                                                               }\r
-                                                                       }\r
-                                                               });\r
-                                                       }\r
-                                               });\r
-                                               \r
-                                               add(new JSeparator());\r
-                                               \r
-                                               add(new JMenuItem("Close Motor") {\r
-                                                       private static final long serialVersionUID = 1L;\r
-                                                       {\r
-                                                               addActionListener(new ActionListener() {\r
-                                                                       @Override\r
-                                                                       public void actionPerformed(ActionEvent ev) {\r
-                                                                               MotorEditor e = (MotorEditor) motors\r
-                                                                                               .getSelectedComponent();\r
-                                                                               tm.removeMotor(e.getMotor());\r
-                                                                               motors.remove(e);\r
-                                                                               f2e.remove(e2f.get(e));\r
-                                                                               e2f.remove(e);\r
-                                                                               m2e.remove(e.getMotor());\r
-                                                                               mb.removeBurn(e.burn);\r
-                                                                       }\r
-                                                               });\r
-                                                       }\r
-                                               });\r
-                                               \r
-                                               add(new JMenuItem("Save Motor") {\r
-                                                       private static final long serialVersionUID = 1L;\r
-                                                       {\r
-                                                               addActionListener(new ActionListener() {\r
-                                                                       @Override\r
-                                                                       public void actionPerformed(ActionEvent e) {\r
-                                                                               MotorEditor m = (MotorEditor) motors\r
-                                                                                               .getSelectedComponent();\r
-                                                                               File f = e2f.get(m);\r
-                                                                               if (f != null)\r
-                                                                                       save(m.getMotor(), f);\r
-\r
-                                                                       }\r
-                                                               });\r
-                                                       }\r
-                                               });\r
-                                               add(new JMenuItem("Save Motor As...") {\r
-                                                       private static final long serialVersionUID = 1L;\r
-                                                       {\r
-                                                               addActionListener(new ActionListener() {\r
-                                                                       @Override\r
-                                                                       public void actionPerformed(ActionEvent arg0) {\r
-\r
-                                                                               final FileDialog fd = new FileDialog(MotorWorkbench.this, "Save Motor As", FileDialog.SAVE);\r
-                                                                               fd.setVisible(true);\r
-                                                                               if (fd.getFile() != null ) {\r
-                                                                                       File file = new File(fd.getDirectory() + fd.getFile());\r
-                                                                                       MotorEditor m = (MotorEditor) motors\r
-                                                                                                       .getSelectedComponent();\r
-                                                                                       try {\r
-                                                                                               save(m.getMotor(), file);\r
-                                                                                               e2f.put(m, file);\r
-                                                                                               f2e.put(file, m);\r
-                                                                                               motors.setTitleAt(motors\r
-                                                                                                               .getSelectedIndex(),\r
-                                                                                                               file.getName());\r
-                                                                                               // TODO Set tab title\r
-                                                                                       } catch (Exception e) {\r
-                                                                                               JOptionPane.showMessageDialog(\r
-                                                                                                               MotorWorkbench.this, e\r
-                                                                                                                               .getMessage());\r
-                                                                                       }\r
-                                                                               }\r
-                                                                       }\r
-                                                               });\r
-                                                       }\r
-                                               });\r
-                                               \r
-\r
-                                               add(new JSeparator());\r
-                                               add(new JMenuItem("New Fuel") {\r
-                                                       private static final long serialVersionUID = 1L;\r
-                                                       {\r
-                                                               addActionListener(new ActionListener() {\r
-                                                                       @Override\r
-                                                                       public void actionPerformed(ActionEvent arg0) {\r
-                                                                               newFuel();\r
-                                                                       }\r
-                                                               });\r
-\r
-                                                       }\r
-                                               });\r
-                                               add(new JMenuItem("Save Fuel") {});\r
-                                               add(new JSeparator());\r
-                                               add(new JMenuItem("Export .ENG"){\r
-                                                       private static final long serialVersionUID = 1L;\r
-\r
-                                                       {\r
-                                                               addActionListener(new ActionListener() {\r
-                                                                       @Override\r
-                                                                       public void actionPerformed(ActionEvent arg0) {\r
-\r
-                                                                               final FileDialog fd = new FileDialog(MotorWorkbench.this, "Export .ENG File", FileDialog.SAVE);\r
-                                                                               fd.setFile("motorsim.eng");\r
-                                                                               fd.setVisible(true);\r
-                                                                               if (fd.getFile() != null ) {\r
-                                                                                       File file = new File(fd.getDirectory() + fd.getFile());\r
-                                                                                       Vector<Burn> bb = new Vector<Burn>();\r
-                                                                                       for( MotorEditor me : m2e.values() )\r
-                                                                                               bb.add(me.burn);\r
-                                                                                       try{\r
-                                                                                               ENGExporter.export(bb, file);\r
-                                                                                       } catch ( Exception e ){\r
-                                                                                               e.printStackTrace();\r
-                                                                                       }\r
-                                                                               }\r
-                                                                       }\r
-                                                               });\r
-                                                       }\r
-                                               });\r
-                                       }\r
-                               });\r
-                               add(new JMenu("Settings") {\r
-                                       private static final long serialVersionUID = 1L;\r
-                                       {\r
-                                               ButtonGroup units = new ButtonGroup();\r
-                                               JRadioButtonMenuItem sci = new JRadioButtonMenuItem(\r
-                                                               "SI");\r
-                                               JRadioButtonMenuItem nonsci = new JRadioButtonMenuItem(\r
-                                                               "NonSI");\r
-                                               units.add(sci);\r
-                                               units.add(nonsci);\r
-                                               sci.setSelected(UnitPreference.getUnitPreference() == UnitPreference.SI);\r
-                                               nonsci.setSelected(UnitPreference.getUnitPreference() == UnitPreference.NONSI);\r
-                                               sci.addActionListener(new ActionListener() {\r
-                                                       public void actionPerformed(ActionEvent arg0) {\r
-                                                               UnitPreference\r
-                                                                               .setUnitPreference(UnitPreference.SI);\r
-                                                       }\r
-                                               });\r
-                                               nonsci.addActionListener(new ActionListener() {\r
-                                                       public void actionPerformed(ActionEvent arg0) {\r
-                                                               UnitPreference\r
-                                                                               .setUnitPreference(UnitPreference.NONSI);\r
-                                                       }\r
-                                               });\r
-                                               add(sci);\r
-                                               add(nonsci);\r
-                                       }\r
-                               });\r
-                               add(new JMenu("View") {\r
-                                       private static final long serialVersionUID = 1L;\r
-                                       {\r
-                                               add(new JMenuItem("Show All Motors Graph") {\r
-                                                       private static final long serialVersionUID = 1L;\r
-                                                       {\r
-                                                               addActionListener(new ActionListener() {\r
-                                                                       @Override\r
-                                                                       public void actionPerformed(ActionEvent arg0) {\r
-                                                                               allBurns.setVisible(true);\r
-                                                                               allBurns.toFront();\r
-                                                                       }\r
-                                                               });\r
-                                                       }\r
-                                               });\r
-                                       }\r
-                               });\r
-                       }\r
-               });\r
-       }\r
-       \r
-       private void addFuel(Fuel f){\r
-               fuels.addElement(f);\r
-               FuelPanel fp = new FuelPanel(f);\r
-               FuelNode fn = tm.new FuelNode(fp, f);\r
-               tm.getFuels().add(fn);\r
-               tm.nodeStructureChanged(tm.getFuels());\r
-               motors.addTab(f.getName(), fp);\r
-       }\r
-       \r
-       private void newFuel(){\r
-               final SRFuelEditor ed = new SRFuelEditor();\r
-               fuels.addElement(ed.getFuel());\r
-               final FuelEditNode node = tm.new FuelEditNode(ed);\r
-               tm.getFuels().add(node);\r
-               tm.nodeStructureChanged(tm.getFuels());\r
-               motors.addTab(ed.getFuel().getName(), ed);\r
-               ed.getFuel().addPropertyChangeListener(new PropertyChangeListener(){\r
-                       @Override\r
-                       public void propertyChange(PropertyChangeEvent evt) {\r
-                               if ( evt.getPropertyName().equals("Name")){\r
-                                       for ( int i = 0; i < motors.getTabCount(); i++ ){\r
-                                               if ( motors.getComponent(i) == ed ){\r
-                                                       motors.setTitleAt(i, ed.getFuel().getName());\r
-                                                       tm.nodeChanged(node);\r
-                                               }\r
-                                       }\r
-                               }\r
-                       }});\r
-       }\r
-\r
-       private void save(Motor m, File f) {\r
-               try {\r
-                       MotorIO.writeMotor(m, f);\r
-               } catch (Throwable t) {\r
-                       JOptionPane.showMessageDialog(MotorWorkbench.this, t.getMessage());\r
-               }\r
-       }\r
-\r
-       public void addMotor(Motor m, File f) {\r
-               tm.addMotor(m);\r
-               MotorEditor e = new MotorEditor(m, fuels);\r
-               e.addBurnWatcher(mb);\r
-               String title;\r
-               if (f == null) {\r
-                       title = "New Motor";\r
-               } else {\r
-                       title = f.getName();\r
-                       e2f.put(e, f);\r
-                       f2e.put(f, e);\r
-               }\r
-               m2e.put(m, e);\r
-               motors.addTab(title, e);\r
-       }\r
-\r
-       @Override\r
-       public void valueChanged(TreeSelectionEvent e) {                \r
-               if ( e.getPath().getLastPathComponent() instanceof FuelNode ){\r
-                       FuelNode fen = ((FuelNode)e.getPath().getLastPathComponent());\r
-                               motors.setSelectedComponent(fen.getUserObject());                       \r
-               }\r
-               \r
-               Motor m = getMotor(e.getPath());\r
-               \r
-               if ( m == null )\r
-                       return;\r
-\r
-               motors.setSelectedComponent(m2e.get(m));\r
-\r
-               if (e.getPath().getLastPathComponent() instanceof DefaultMutableTreeNode) {\r
-                       Object o = ((DefaultMutableTreeNode) e.getPath()\r
-                                       .getLastPathComponent()).getUserObject();\r
-                       m2e.get(m).focusOnObject(o);\r
-               }\r
-               \r
-\r
-       }\r
-\r
-       private Motor getMotor(TreePath p) {\r
-               if (p.getLastPathComponent() instanceof WorkbenchTreeModel.MotorNode) {\r
-                       return ((WorkbenchTreeModel.MotorNode) p.getLastPathComponent())\r
-                                       .getUserObject();\r
-               } else if (p.getPath().length > 1)\r
-                       return getMotor(p.getParentPath());\r
-               return null;\r
-       }\r
-}\r
diff --git a/src/com/billkuker/rocketry/motorsim/visual/workbench/MultiBurnChart.java b/src/com/billkuker/rocketry/motorsim/visual/workbench/MultiBurnChart.java
deleted file mode 100644 (file)
index ee629d8..0000000
+++ /dev/null
@@ -1,104 +0,0 @@
-package com.billkuker.rocketry.motorsim.visual.workbench;
-
-import java.awt.BorderLayout;
-import java.util.HashMap;
-
-import javax.measure.quantity.Duration;
-import javax.measure.quantity.Force;
-import javax.measure.unit.SI;
-import javax.measure.unit.Unit;
-import javax.swing.JFrame;
-import javax.swing.JPanel;
-import javax.swing.WindowConstants;
-
-import org.jfree.chart.ChartFactory;
-import org.jfree.chart.ChartPanel;
-import org.jfree.chart.JFreeChart;
-import org.jfree.chart.plot.PlotOrientation;
-import org.jfree.data.xy.XYSeries;
-import org.jfree.data.xy.XYSeriesCollection;
-import org.jscience.physics.amount.Amount;
-
-import com.billkuker.rocketry.motorsim.Burn;
-import com.billkuker.rocketry.motorsim.ConvergentDivergentNozzle;
-import com.billkuker.rocketry.motorsim.Motor;
-import com.billkuker.rocketry.motorsim.RocketScience;
-
-public class MultiBurnChart extends JPanel implements BurnWatcher {
-       private static final long serialVersionUID = 1L;
-
-       private XYSeriesCollection dataset = new XYSeriesCollection();
-
-       private HashMap<Burn, XYSeries> burnToSeries = new HashMap<Burn, XYSeries>();
-       private Unit<Duration> time;
-       private Unit<Force> force;
-
-       @SuppressWarnings("unchecked")
-       public MultiBurnChart() {
-               this.setLayout(new BorderLayout());
-               time = RocketScience.UnitPreference.getUnitPreference()
-                               .getPreferredUnit(SI.SECOND);
-               force = RocketScience.UnitPreference.getUnitPreference()
-                               .getPreferredUnit(SI.NEWTON);
-               JFreeChart chart = ChartFactory.createXYLineChart(
-                               "", // Title
-                               time.toString(), // x-axis Label
-                               force.toString(), // y-axis Label
-                               dataset, PlotOrientation.VERTICAL, // Plot Orientation
-                               true, // Show Legend
-                               true, // Use tool tips
-                               false // Configure chart to generate URLs?
-                               );
-               add(new ChartPanel(chart));
-       }
-
-       public void addBurn(Burn b) {
-               XYSeries s = createSeries(b);
-               burnToSeries.put(b, s);
-               dataset.addSeries(s);
-       }
-
-       private XYSeries createSeries(Burn b) {
-               XYSeries s = new XYSeries(b.getMotor().getName());
-               for( Burn.Interval i : b.getData().values() ){
-                       s.add(i.time.doubleValue(time), i.thrust.doubleValue(force));
-               }
-               return s;
-       }
-
-       public void removeBurn(Burn b) {
-               XYSeries s = burnToSeries.get(b);
-               if (s == null)
-                       return;
-               dataset.removeSeries(s);
-       }
-       
-       public static void main(String args[]) throws Exception{
-               MultiBurnChart c = new MultiBurnChart();
-               
-               JFrame f = new JFrame();
-               f.setSize(1024, 768);
-               f.setContentPane(c);
-               f.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
-               f.setVisible(true);
-
-               Motor m = MotorEditor.defaultMotor();
-               Burn b = new Burn(m);
-               c.addBurn(b);
-               
-               m.setName("Motor2");
-               ((ConvergentDivergentNozzle)m.getNozzle()).setThroatDiameter(Amount.valueOf(3, SI.MILLIMETER));
-               c.addBurn(new Burn(m));
-               
-               Thread.sleep(5000);
-               
-               c.removeBurn(b);
-               
-       }
-
-       @Override
-       public void replace(Burn oldBurn, Burn newBurn) {
-               removeBurn(oldBurn);
-               addBurn(newBurn);
-       }
-}
diff --git a/src/com/billkuker/rocketry/motorsim/visual/workbench/SRFuelEditor.java b/src/com/billkuker/rocketry/motorsim/visual/workbench/SRFuelEditor.java
deleted file mode 100644 (file)
index 013216f..0000000
+++ /dev/null
@@ -1,331 +0,0 @@
-package com.billkuker.rocketry.motorsim.visual.workbench;
-
-import java.awt.Dimension;
-import java.awt.FlowLayout;
-import java.awt.event.ActionEvent;
-import java.awt.event.ActionListener;
-import java.beans.PropertyChangeEvent;
-import java.text.DecimalFormat;
-import java.text.NumberFormat;
-import java.util.Collections;
-import java.util.Vector;
-
-import javax.measure.quantity.Pressure;
-import javax.measure.quantity.Velocity;
-import javax.measure.quantity.VolumetricDensity;
-import javax.measure.unit.SI;
-import javax.swing.ButtonGroup;
-import javax.swing.JButton;
-import javax.swing.JFrame;
-import javax.swing.JPanel;
-import javax.swing.JRadioButton;
-import javax.swing.JScrollPane;
-import javax.swing.JSplitPane;
-import javax.swing.JTable;
-import javax.swing.SwingUtilities;
-import javax.swing.event.ChangeEvent;
-import javax.swing.event.ChangeListener;
-import javax.swing.table.AbstractTableModel;
-
-import org.jscience.physics.amount.Amount;
-
-import com.billkuker.rocketry.motorsim.Fuel;
-import com.billkuker.rocketry.motorsim.RocketScience;
-import com.billkuker.rocketry.motorsim.fuel.EditableFuel.EditableCombustionProduct;
-import com.billkuker.rocketry.motorsim.fuel.PiecewiseSaintRobertFuel;
-import com.billkuker.rocketry.motorsim.fuel.SaintRobertFuel;
-import com.billkuker.rocketry.motorsim.fuel.SaintRobertFuel.Type;
-import com.billkuker.rocketry.motorsim.visual.Chart;
-import com.billkuker.rocketry.motorsim.visual.Editor;
-
-public class SRFuelEditor extends JSplitPane {
-       private static final NumberFormat nf = new DecimalFormat("##########.###");
-
-       Chart<Pressure, Velocity> burnRate;
-
-       private class Entry implements Comparable<Entry> {
-               Amount<Pressure> p = Amount.valueOf(0, RocketScience.UnitPreference.getUnitPreference().getPreferredUnit(RocketScience.PSI));
-               double a;
-               double n;
-
-               @Override
-               public int compareTo(Entry o) {
-                       return p.compareTo(o.p);
-               }
-       }
-
-       public static class EditablePSRFuel extends PiecewiseSaintRobertFuel {
-
-               private Amount<VolumetricDensity> idealDensity = (Amount<VolumetricDensity>) Amount
-                               .valueOf("1 g/mm^3");
-               private double combustionEfficiency = 1;
-               private double densityRatio = 1;
-               private EditableCombustionProduct cp;
-               private String name = "New Fuel";
-
-               public EditablePSRFuel(Type t) {
-                       super(t);
-                       cp = new EditableCombustionProduct();
-               }
-               
-               public void clear(){
-                       super.clear();
-               }
-               
-               public void setType(Type t){
-                       super.setType(t);
-               }
-
-               public void add(Amount<Pressure> p, final double _a, final double _n) {
-                       super.add(p, _a, _n);
-
-               }
-
-               public Amount<VolumetricDensity> getIdealDensity() {
-                       return idealDensity;
-               }
-
-               public void setIdealDensity(Amount<VolumetricDensity> idealDensity) {
-                       this.idealDensity = idealDensity;
-               }
-
-               public double getCombustionEfficiency() {
-                       return combustionEfficiency;
-               }
-
-               public void setCombustionEfficiency(double combustionEfficiency) {
-                       this.combustionEfficiency = combustionEfficiency;
-               }
-
-               public double getDensityRatio() {
-                       return densityRatio;
-               }
-
-               public void setDensityRatio(double densityRatio) {
-                       this.densityRatio = densityRatio;
-               }
-
-               @Override
-               public CombustionProduct getCombustionProduct() {
-                       return cp;
-               }
-
-               public String getName() {
-                       return name;
-               }
-
-               public void setName(String name) {
-                       this.name = name;
-               }
-
-       }
-
-       final EditablePSRFuel f = new EditablePSRFuel(SaintRobertFuel.Type.SI);
-
-       private class TM extends AbstractTableModel {
-
-               @Override
-               public int getColumnCount() {
-                       return 3;
-               }
-
-               @Override
-               public int getRowCount() {
-                       return entries.size();
-               }
-
-               @Override
-               public String getColumnName(int col) {
-                       switch (col) {
-                       case 0:
-                               return "Pressure";
-                       case 1:
-                               return "Coefficient (a)";
-                       case 2:
-                               return "Exponent (n)";
-                       }
-                       return null;
-               }
-
-               @Override
-               public Object getValueAt(int rowIndex, int columnIndex) {
-                       Entry e = entries.get(rowIndex);
-                       switch (columnIndex) {
-                       case 0:
-                               //Format like 100 psi or 4.8 Mpa
-                               return nf.format(e.p.doubleValue(e.p.getUnit())) + " " + e.p.getUnit();
-                       case 1:
-                               return e.a;
-                       case 2:
-                               return e.n;
-                       }
-                       return null;
-               }
-
-               public boolean isCellEditable(int row, int col) {
-                       return true;
-               }
-
-               public void setValueAt(Object value, int row, int col) {
-                       Entry e = entries.get(row);
-                       try {
-                               switch (col) {
-                               case 0:
-                                       try {
-                                               e.p = (Amount<Pressure>) Amount.valueOf((String) value);
-                                       } catch ( Exception ee ){
-                                               double d = Double.parseDouble((String)value);
-                                               e.p = (Amount<Pressure>)Amount.valueOf(d, e.p.getUnit());
-                                       }
-                                       break;
-                               case 1:
-                                       e.a = Double.valueOf((String) value);
-                                       break;
-                               case 2:
-                                       e.n = Double.valueOf((String) value);
-                               }
-                       } catch (Exception ex) {
-                               ex.printStackTrace();
-                       }
-                       Collections.sort(entries);
-                       fireTableDataChanged();
-                       //f = new EditablePSRFuel(SaintRobertFuel.Type.NONSI);
-                       f.clear();
-                       for (Entry en : entries) {
-                               f.add(en.p, en.a, en.n);
-                       }
-                       f.firePropertyChange(new PropertyChangeEvent(f,"entries", null, null));
-
-                       update();
-
-               }
-
-               @Override
-               public void fireTableDataChanged() {
-                       super.fireTableDataChanged();
-               }
-
-       };
-       
-       public Fuel getFuel(){
-               return f;
-       }
-
-       private void update() {
-               SwingUtilities.invokeLater(new Runnable() {
-
-                       @Override
-                       public void run() {
-                               editTop.setTopComponent(new Editor(f));
-                               editTop.setBottomComponent(new Editor(f.getCombustionProduct()));
-                               if (burnRate != null)
-                                       SRFuelEditor.this.remove(burnRate);
-                               try {
-                                       burnRate = new Chart<Pressure, Velocity>(
-                                                       SI.MEGA(SI.PASCAL), SI.MILLIMETER.divide(SI.SECOND)
-                                                                       .asType(Velocity.class), f, "burnRate");
-                               } catch (NoSuchMethodException e) {
-                                       throw new Error(e);
-                               }
-                               burnRate.setDomain(burnRate.new IntervalDomain(Amount.valueOf(
-                                               0, SI.MEGA(SI.PASCAL)), Amount.valueOf(11, SI
-                                               .MEGA(SI.PASCAL)), 50));
-                               SRFuelEditor.this.setRightComponent(burnRate);
-                               SRFuelEditor.this.revalidate();
-                       }
-               });
-       }
-
-       private Vector<Entry> entries = new Vector<Entry>();
-
-       JSplitPane editParent;
-       JSplitPane editTop;
-       JSplitPane editBottom;
-       JPanel controls;
-
-       public SRFuelEditor() {
-               super(HORIZONTAL_SPLIT);
-               setResizeWeight(0);
-               setDividerLocation(.3);
-               
-               final TM tm = new TM();
-
-               editParent = new JSplitPane(JSplitPane.VERTICAL_SPLIT);
-               editTop = new JSplitPane(JSplitPane.VERTICAL_SPLIT);
-               editBottom = new JSplitPane(JSplitPane.VERTICAL_SPLIT);
-               
-               editParent.setTopComponent(editTop);
-               editParent.setBottomComponent(editBottom);
-               
-               editTop.setTopComponent(new Editor(f));
-
-               JTable table = new JTable(tm);
-               JScrollPane scrollpane = new JScrollPane(table);
-               scrollpane.setMinimumSize(new Dimension(200, 200));
-               editBottom.setTopComponent(scrollpane);
-
-               setLeftComponent(editParent);
-
-               JButton add = new JButton("Add Data");
-               add.addActionListener(new ActionListener() {
-                       @Override
-                       public void actionPerformed(ActionEvent e) {
-                               entries.add(new Entry());
-                               tm.fireTableDataChanged();
-                       }
-               });
-               controls = new JPanel();
-               controls.setPreferredSize(new Dimension(200, 50));
-               controls.setLayout(new FlowLayout());
-                       
-               controls.add(add);
-
-               
-               final JRadioButton si, nonsi;
-               ButtonGroup type = new ButtonGroup();
-               JPanel radio = new JPanel();
-               radio.add(si = new JRadioButton("SI"));
-               radio.add(nonsi = new JRadioButton("NonSI"));
-               controls.add(radio);
-               type.add(si);
-               type.add(nonsi);
-
-               si.setSelected(true);
-               
-               si.addChangeListener(new ChangeListener(){
-                       @Override
-                       public void stateChanged(ChangeEvent e) {
-                               if ( si.isSelected() ){
-                                       System.err.println("SI");
-                                       f.setType(Type.SI);
-                               } else {
-                                       System.err.println("NONSI");
-                                       f.setType(Type.NONSI);
-                               }
-                               update();
-                       }});
-               
-               editBottom.setBottomComponent(controls);
-               
-               
-               editParent.setDividerLocation(.5);
-               editTop.setDividerLocation(.5);
-               editBottom.setDividerLocation(.8);
-               
-               editParent.resetToPreferredSizes();
-               revalidate();
-
-               update();
-       }
-
-       public static void main(String args[]) {
-               SRFuelEditor ed;
-               JFrame f = new JFrame();
-               f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
-               f.setContentPane(ed = new SRFuelEditor());
-               f.setSize(800, 600);
-               f.setVisible(true);
-
-       }
-
-}
diff --git a/src/com/billkuker/rocketry/motorsim/visual/workbench/WorkbenchTreeCellRenderer.java b/src/com/billkuker/rocketry/motorsim/visual/workbench/WorkbenchTreeCellRenderer.java
deleted file mode 100644 (file)
index ae3e3a6..0000000
+++ /dev/null
@@ -1,63 +0,0 @@
-package com.billkuker.rocketry.motorsim.visual.workbench;
-
-import java.awt.Color;
-import java.awt.Component;
-
-import javax.swing.JTree;
-import javax.swing.tree.DefaultMutableTreeNode;
-import javax.swing.tree.DefaultTreeCellRenderer;
-
-import com.billkuker.rocketry.motorsim.Motor;
-import com.billkuker.rocketry.motorsim.Validating;
-import com.billkuker.rocketry.motorsim.Validating.ValidationException;
-import com.billkuker.rocketry.motorsim.visual.workbench.WorkbenchTreeModel.FuelNode;
-
-public class WorkbenchTreeCellRenderer extends DefaultTreeCellRenderer {
-       private static final long serialVersionUID = 1L;
-
-       @Override
-       public Component getTreeCellRendererComponent(JTree tree, final Object value,
-                       boolean sel, boolean expanded, boolean leaf, int row,
-                       boolean hasFocus) {
-
-               String tip = null;
-               setTextNonSelectionColor(Color.black);
-               setTextSelectionColor(Color.white);
-               
-               Object part = null;
-               if (value instanceof DefaultMutableTreeNode) {
-                       part = ((DefaultMutableTreeNode) value).getUserObject();
-               }
-               
-               if ( part instanceof Validating ){
-                               try {
-                                       ((Validating)part).validate();
-                               } catch (ValidationException e) {
-                                       setTextSelectionColor(Color.RED);
-                                       setTextNonSelectionColor(Color.RED);
-                                       setToolTipText(e.getMessage());
-                                       tip = e.getMessage();
-                               }
-               }
-               
-               super.getTreeCellRendererComponent(tree, value, sel, expanded, leaf,
-                               row, hasFocus);
-
-               if (part instanceof Motor) {
-                       setText(((Motor) part).getName());
-               } else if ( value instanceof FuelNode ){
-                       setText(((FuelNode)value).getFuel().getName());
-               } else if ( part instanceof String ) {
-                       setText((String)part);
-               } else if ( part == null ) {
-                       setText("");
-               } else {
-                       setText(part.getClass().getSimpleName());
-               }
-               setToolTipText(tip);
-               
-
-
-               return this;
-       }
-}
diff --git a/src/com/billkuker/rocketry/motorsim/visual/workbench/WorkbenchTreeModel.java b/src/com/billkuker/rocketry/motorsim/visual/workbench/WorkbenchTreeModel.java
deleted file mode 100644 (file)
index 1745d6e..0000000
+++ /dev/null
@@ -1,172 +0,0 @@
-package com.billkuker.rocketry.motorsim.visual.workbench;
-
-import java.awt.Component;
-import java.beans.PropertyChangeEvent;
-import java.beans.PropertyChangeListener;
-import java.util.Enumeration;
-
-import javax.swing.tree.DefaultMutableTreeNode;
-import javax.swing.tree.DefaultTreeModel;
-import javax.swing.tree.TreeNode;
-
-import com.billkuker.rocketry.motorsim.ChangeListening;
-import com.billkuker.rocketry.motorsim.Fuel;
-import com.billkuker.rocketry.motorsim.Motor;
-import com.billkuker.rocketry.motorsim.grain.MultiGrain;
-
-public class WorkbenchTreeModel extends DefaultTreeModel {
-
-       private static final long serialVersionUID = 1L;
-       
-       //TreeNode root = new DefaultMutableTreeNode("Root");
-       DefaultMutableTreeNode motors = new DefaultMutableTreeNode("All Motors");
-       DefaultMutableTreeNode fuel = new DefaultMutableTreeNode("Fuels");
-       
-       public class MultiGrainNode extends PartNode{
-               private static final long serialVersionUID = 1L;
-               public MultiGrainNode(MultiGrain part) {
-                       super(part);
-                       setAllowsChildren(true);
-                       add(new PartNode(part.getGrain()));
-               }
-               @Override
-               public void propertyChange(PropertyChangeEvent e) {
-                       if ( e.getPropertyName().equals("Grain")){
-                               remove(0);
-                               add(new PartNode(((MultiGrain)getUserObject()).getGrain()));
-                               nodesChanged(this, new int[]{0});
-                       }
-                       super.propertyChange(e);
-               }
-       }
-       
-       public class FuelNode extends DefaultMutableTreeNode{
-               private static final long serialVersionUID = 1L;
-               Fuel f;
-               public FuelNode(Component c, Fuel f){
-                       super(c, false);
-                       this.f = f;
-               }
-               
-               @Override
-               public Component getUserObject(){
-                       return (Component)super.getUserObject();
-               }
-               
-               public Fuel getFuel(){
-                       return f;
-               }
-       }
-       
-       public class FuelEditNode extends FuelNode {
-               private static final long serialVersionUID = 1L;
-
-               public FuelEditNode(SRFuelEditor sr){
-                       super(sr, sr.getFuel());
-                       sr.getFuel().addPropertyChangeListener(new PropertyChangeListener(){
-
-                               @Override
-                               public void propertyChange(PropertyChangeEvent evt) {
-                                       nodeChanged(FuelEditNode.this);
-                               }});
-               }
-               
-               @Override
-               public SRFuelEditor getUserObject(){
-                       return (SRFuelEditor)super.getUserObject();
-               }
-
-       }
-
-       public class PartNode extends DefaultMutableTreeNode implements PropertyChangeListener {
-               private static final long serialVersionUID = 1L;
-
-               public PartNode(Object part) {
-                       super(part, false);
-                       if (part instanceof ChangeListening.Subject) {
-                               ((ChangeListening.Subject) part).addPropertyChangeListener(this);
-                       }
-               }
-       
-               @Override
-               public void propertyChange(PropertyChangeEvent e) {
-                       nodeChanged(this);
-               }
-       
-       }
-       
-       public class MotorNode extends PartNode implements PropertyChangeListener {
-               private static final long serialVersionUID = 1L;
-               Motor motor;
-               PartNode cn, nn, gn, fn;
-
-               public MotorNode(Motor m) {
-                       super(m);
-                       setAllowsChildren(true);
-                       motor = m;
-                       add( cn = new PartNode(m.getChamber()));
-                       add( nn = new PartNode(m.getNozzle()));
-                       if ( m.getGrain() instanceof MultiGrain ){
-                               gn = new MultiGrainNode(((MultiGrain)m.getGrain()));
-                       } else {
-                               gn = new PartNode(m.getGrain());
-                       }
-                       add(gn);
-                       if (m instanceof ChangeListening.Subject) {
-                               ((ChangeListening.Subject) m).addPropertyChangeListener(this);
-                       }
-               }
-               
-               @Override
-               public Motor getUserObject(){
-                       return (Motor)super.getUserObject();
-               }
-
-               @Override
-               public void propertyChange(PropertyChangeEvent e) {
-                               nodeChanged(this);
-                       super.propertyChange(e);
-               }
-
-       }
-
-       public WorkbenchTreeModel() {
-               super(new DefaultMutableTreeNode("Root"), true);
-               getRoot().add(motors);
-               getRoot().add(fuel);
-       }
-       
-       @Override
-       public DefaultMutableTreeNode getRoot(){
-               return (DefaultMutableTreeNode)super.getRoot();
-       }
-       
-       public DefaultMutableTreeNode getMotors(){
-               return motors;
-       }
-       
-       public DefaultMutableTreeNode getFuels(){
-               return fuel;
-       }
-       
-       public void addMotor(Motor m){
-               DefaultMutableTreeNode root = getRoot();
-               motors.add(new MotorNode(m));
-               nodesWereInserted(motors, new int[]{motors.getChildCount()-1});
-               
-       }
-       
-       @SuppressWarnings("unchecked")
-       public void removeMotor(Motor m){
-               Enumeration<TreeNode> e = motors.children();
-               while ( e.hasMoreElements() ){
-                       TreeNode n = e.nextElement();
-                       if ( n instanceof MotorNode ){
-                               if ( ((MotorNode)n).getUserObject() == m ){
-                                       removeNodeFromParent((MotorNode)n);
-                               }
-                       }
-               }
-       }
-
-}