updates for 0.9.2
[debian/openrocket] / src / net / sf / openrocket / gui / main / SimulationEditDialog.java
1 package net.sf.openrocket.gui.main;
2
3
4 import java.awt.Color;
5 import java.awt.Component;
6 import java.awt.Window;
7 import java.awt.event.ActionEvent;
8 import java.awt.event.ActionListener;
9 import java.util.Arrays;
10 import java.util.List;
11
12 import javax.swing.AbstractListModel;
13 import javax.swing.BorderFactory;
14 import javax.swing.JButton;
15 import javax.swing.JCheckBox;
16 import javax.swing.JComboBox;
17 import javax.swing.JDialog;
18 import javax.swing.JLabel;
19 import javax.swing.JList;
20 import javax.swing.JOptionPane;
21 import javax.swing.JPanel;
22 import javax.swing.JScrollPane;
23 import javax.swing.JSpinner;
24 import javax.swing.JTabbedPane;
25 import javax.swing.JTextField;
26 import javax.swing.ListCellRenderer;
27 import javax.swing.event.ChangeEvent;
28 import javax.swing.event.ChangeListener;
29 import javax.swing.event.DocumentEvent;
30 import javax.swing.event.DocumentListener;
31
32 import net.miginfocom.swing.MigLayout;
33 import net.sf.openrocket.aerodynamics.ExtendedISAModel;
34 import net.sf.openrocket.document.Simulation;
35 import net.sf.openrocket.gui.SpinnerEditor;
36 import net.sf.openrocket.gui.adaptors.BooleanModel;
37 import net.sf.openrocket.gui.adaptors.DoubleModel;
38 import net.sf.openrocket.gui.adaptors.MotorConfigurationModel;
39 import net.sf.openrocket.gui.components.BasicSlider;
40 import net.sf.openrocket.gui.components.DescriptionArea;
41 import net.sf.openrocket.gui.components.UnitSelector;
42 import net.sf.openrocket.gui.plot.Axis;
43 import net.sf.openrocket.gui.plot.PlotConfiguration;
44 import net.sf.openrocket.gui.plot.PlotPanel;
45 import net.sf.openrocket.rocketcomponent.Configuration;
46 import net.sf.openrocket.simulation.FlightData;
47 import net.sf.openrocket.simulation.FlightDataBranch;
48 import net.sf.openrocket.simulation.RK4Simulator;
49 import net.sf.openrocket.simulation.SimulationConditions;
50 import net.sf.openrocket.simulation.SimulationListener;
51 import net.sf.openrocket.simulation.listeners.CSVSaveListener;
52 import net.sf.openrocket.unit.Unit;
53 import net.sf.openrocket.unit.UnitGroup;
54 import net.sf.openrocket.util.GUIUtil;
55 import net.sf.openrocket.util.Icons;
56 import net.sf.openrocket.util.Prefs;
57
58 import org.jfree.chart.ChartFactory;
59 import org.jfree.chart.ChartPanel;
60 import org.jfree.chart.JFreeChart;
61 import org.jfree.chart.axis.NumberAxis;
62 import org.jfree.chart.plot.PlotOrientation;
63 import org.jfree.chart.plot.ValueMarker;
64 import org.jfree.chart.plot.XYPlot;
65 import org.jfree.chart.renderer.xy.StandardXYItemRenderer;
66 import org.jfree.data.xy.XYSeries;
67 import org.jfree.data.xy.XYSeriesCollection;
68
69
70 public class SimulationEditDialog extends JDialog {
71
72         public static final int DEFAULT = -1;
73         public static final int EDIT = 1;
74         public static final int PLOT = 2;
75         
76         
77         private final Window parentWindow;
78         private final Simulation simulation;
79         private final SimulationConditions conditions;
80         private final Configuration configuration;
81         
82         
83         public SimulationEditDialog(Window parent, Simulation s) {
84                 this(parent, s, 0);
85         }       
86         
87         public SimulationEditDialog(Window parent, Simulation s, int tab) {
88                 super(parent, "Edit simulation", JDialog.ModalityType.DOCUMENT_MODAL);
89                 
90                 this.parentWindow = parent;
91                 this.simulation = s;
92                 this.conditions = simulation.getConditions();
93                 configuration = simulation.getConfiguration();
94                 
95                 JPanel mainPanel = new JPanel(new MigLayout("fill","[grow, fill]"));
96                 
97                 mainPanel.add(new JLabel("Simulation name: "), "span, split 2, shrink");
98                 final JTextField field = new JTextField(simulation.getName());
99                 field.getDocument().addDocumentListener(new DocumentListener() {
100                         @Override
101                         public void changedUpdate(DocumentEvent e) {
102                                 setText();
103                         }
104                         @Override
105                         public void insertUpdate(DocumentEvent e) {
106                                 setText();
107                         }
108                         @Override
109                         public void removeUpdate(DocumentEvent e) {
110                                 setText();
111                         }
112                         private void setText() {
113                                 String name = field.getText();
114                                 if (name == null || name.equals(""))
115                                         return;
116                                 System.out.println("Setting name:"+name);
117                                 simulation.setName(name);
118                                 
119                         }
120                 });
121                 mainPanel.add(field, "shrinky, growx, wrap");
122                 
123                 JTabbedPane tabbedPane = new JTabbedPane();
124                 
125                 
126                 tabbedPane.addTab("Launch conditions", flightConditionsTab());
127                 tabbedPane.addTab("Simulation options", simulationOptionsTab());
128                 tabbedPane.addTab("Plot data", plotTab());
129 //              tabbedPane.addTab("Export data", exportTab());
130                 
131                 // Select the initial tab
132                 if (tab == EDIT) {
133                         tabbedPane.setSelectedIndex(0);
134                 } else if (tab == PLOT) {
135                         tabbedPane.setSelectedIndex(2);
136                 } else {
137                         FlightData data = s.getSimulatedData();
138                         if (data == null || data.getBranchCount() == 0)
139                                 tabbedPane.setSelectedIndex(0);
140                         else
141                                 tabbedPane.setSelectedIndex(2);
142                 }
143                 
144                 mainPanel.add(tabbedPane, "spanx, grow, wrap");
145
146                 
147                 // Buttons
148                 mainPanel.add(new JPanel(), "spanx, split, growx");
149
150                 JButton button;
151                 button = new JButton("Run simulation");
152                 button.addActionListener(new ActionListener() {
153                         @Override
154                         public void actionPerformed(ActionEvent e) {
155                                 SimulationEditDialog.this.dispose();
156                                 SimulationRunDialog.runSimulations(parentWindow, simulation);
157                         }
158                 });
159                 mainPanel.add(button, "gapright para");
160                 
161                 
162                 JButton close = new JButton("Close");
163                 close.addActionListener(new ActionListener() {
164                         @Override
165                         public void actionPerformed(ActionEvent e) {
166                                 SimulationEditDialog.this.dispose();
167                         }
168                 });
169                 mainPanel.add(close, "");
170                 
171                 
172                 this.add(mainPanel);
173                 this.validate();
174                 this.pack();
175                 this.setLocationByPlatform(true);
176                 GUIUtil.setDefaultButton(button);
177                 GUIUtil.installEscapeCloseOperation(this);
178         }
179         
180         
181         
182         
183         
184         private JPanel flightConditionsTab() {
185                 JPanel panel = new JPanel(new MigLayout("fill"));
186                 JPanel sub;
187                 String tip;
188                 UnitSelector unit;
189                 BasicSlider slider;
190                 DoubleModel m;
191                 JSpinner spin;
192                 
193                 //// Motor selector
194                 JLabel label = new JLabel("Motor configuration:");
195                 label.setToolTipText("Select the motor configuration to use.");
196                 panel.add(label, "shrinkx, spanx, split 2");
197                 
198                 JComboBox combo = new JComboBox(new MotorConfigurationModel(configuration));
199                 combo.setToolTipText("Select the motor configuration to use.");
200                 combo.addActionListener(new ActionListener() {
201                         @Override
202                         public void actionPerformed(ActionEvent e) {
203                                 conditions.setMotorConfigurationID(configuration.getMotorConfigurationID());
204                         }
205                 });
206                 panel.add(combo, "growx, wrap para");
207                 
208                 
209                 //// Wind settings:  Average wind speed, turbulence intensity, std. deviation
210                 sub = new JPanel(new MigLayout("fill, gap rel unrel",
211                                 "[grow][65lp!][30lp!][75lp!]",""));
212                 sub.setBorder(BorderFactory.createTitledBorder("Wind"));
213                 panel.add(sub, "growx, split 2, aligny 0, flowy, gapright para");
214
215
216                 // Wind average
217                 label = new JLabel("Average windspeed:");
218                 tip = "The average windspeed relative to the ground.";
219                 label.setToolTipText(tip);
220                 sub.add(label);
221                 
222                 m = new DoubleModel(conditions,"WindSpeedAverage", UnitGroup.UNITS_VELOCITY,0);
223                 
224                 spin = new JSpinner(m.getSpinnerModel());
225                 spin.setEditor(new SpinnerEditor(spin));
226                 spin.setToolTipText(tip);
227                 sub.add(spin,"w 65lp!");
228                 
229                 unit = new UnitSelector(m);
230                 unit.setToolTipText(tip);
231                 sub.add(unit,"growx");
232                 slider = new BasicSlider(m.getSliderModel(0, 10.0));
233                 slider.setToolTipText(tip);
234                 sub.add(slider,"w 75lp, wrap");
235                 
236
237
238                 // Wind std. deviation
239                 label = new JLabel("Standard deviation:");
240                 tip = "<html>The standard deviation of the windspeed.<br>" +
241                                 "The windspeed is within twice the standard deviation from the average for " +
242                                 "95% of the time.";
243                 label.setToolTipText(tip);
244                 sub.add(label);
245                 
246                 m = new DoubleModel(conditions,"WindSpeedDeviation", UnitGroup.UNITS_VELOCITY,0);
247                 DoubleModel m2 = new DoubleModel(conditions,"WindSpeedAverage", 0.25, 
248                                 UnitGroup.UNITS_COEFFICIENT,0);
249                 
250                 spin = new JSpinner(m.getSpinnerModel());
251                 spin.setEditor(new SpinnerEditor(spin));
252                 spin.setToolTipText(tip);
253                 sub.add(spin,"w 65lp!");
254                 
255                 unit = new UnitSelector(m);
256                 unit.setToolTipText(tip);
257                 sub.add(unit,"growx");
258                 slider = new BasicSlider(m.getSliderModel(new DoubleModel(0), m2));
259                 slider.setToolTipText(tip);
260                 sub.add(slider,"w 75lp, wrap");
261                                 
262
263                 // Wind turbulence intensity
264                 label = new JLabel("Turbulence intensity:");
265                 tip = "<html>The turbulence intensity is the standard deviation " +
266                         "divided by the average windspeed.<br>" +
267                         "Typical values range from "+
268                         UnitGroup.UNITS_RELATIVE.getDefaultUnit().toStringUnit(0.05) +
269                         " to " +
270                         UnitGroup.UNITS_RELATIVE.getDefaultUnit().toStringUnit(0.20) + ".";
271                 label.setToolTipText(tip);
272                 sub.add(label);
273                 
274                 m = new DoubleModel(conditions,"WindTurbulenceIntensity", UnitGroup.UNITS_RELATIVE,0);
275                 
276                 spin = new JSpinner(m.getSpinnerModel());
277                 spin.setEditor(new SpinnerEditor(spin));
278                 spin.setToolTipText(tip);
279                 sub.add(spin,"w 65lp!");
280                 
281                 unit = new UnitSelector(m);
282                 unit.setToolTipText(tip);
283                 sub.add(unit,"growx");
284                 
285                 final JLabel intensityLabel = new JLabel(
286                                 getIntensityDescription(conditions.getWindTurbulenceIntensity()));
287                 intensityLabel.setToolTipText(tip);
288                 sub.add(intensityLabel,"w 75lp, wrap");
289                 m.addChangeListener(new ChangeListener() {
290                         @Override
291                         public void stateChanged(ChangeEvent e) {
292                                 intensityLabel.setText(
293                                                 getIntensityDescription(conditions.getWindTurbulenceIntensity()));
294                         }
295                 });
296                 
297
298                 
299                 
300                 
301                 //// Temperature and pressure
302                 sub = new JPanel(new MigLayout("fill, gap rel unrel",
303                                 "[grow][65lp!][30lp!][75lp!]",""));
304                 sub.setBorder(BorderFactory.createTitledBorder("Atmospheric conditions"));
305                 panel.add(sub, "growx, aligny 0, gapright para");
306
307                 
308                 BooleanModel isa = new BooleanModel(conditions, "ISAAtmosphere");
309                 JCheckBox check = new JCheckBox(isa);
310                 check.setText("Use International Standard Atmosphere");
311                 check.setToolTipText("<html>Select to use the International Standard Atmosphere model."+
312                                 "<br>This model has a temperature of " + 
313                                 UnitGroup.UNITS_TEMPERATURE.toStringUnit(ExtendedISAModel.STANDARD_TEMPERATURE)+
314                                 " and a pressure of " +
315                                 UnitGroup.UNITS_PRESSURE.toStringUnit(ExtendedISAModel.STANDARD_PRESSURE) +
316                                 " at sea level.");
317                 sub.add(check, "spanx, wrap unrel");
318                 
319                 // Temperature
320                 label = new JLabel("Temperature:");
321                 tip = "The temperature at the launch site.";
322                 label.setToolTipText(tip);
323                 isa.addEnableComponent(label, false);
324                 sub.add(label);
325                 
326                 m = new DoubleModel(conditions,"LaunchTemperature", UnitGroup.UNITS_TEMPERATURE,0);
327                 
328                 spin = new JSpinner(m.getSpinnerModel());
329                 spin.setEditor(new SpinnerEditor(spin));
330                 spin.setToolTipText(tip);
331                 isa.addEnableComponent(spin, false);
332                 sub.add(spin,"w 65lp!");
333                 
334                 unit = new UnitSelector(m);
335                 unit.setToolTipText(tip);
336                 isa.addEnableComponent(unit, false);
337                 sub.add(unit,"growx");
338                 slider = new BasicSlider(m.getSliderModel(253.15, 308.15));  // -20 ... 35
339                 slider.setToolTipText(tip);
340                 isa.addEnableComponent(slider, false);
341                 sub.add(slider,"w 75lp, wrap");
342                 
343
344                 
345                 // Pressure
346                 label = new JLabel("Pressure:");
347                 tip = "The atmospheric pressure at the launch site.";
348                 label.setToolTipText(tip);
349                 isa.addEnableComponent(label, false);
350                 sub.add(label);
351                 
352                 m = new DoubleModel(conditions,"LaunchPressure", UnitGroup.UNITS_PRESSURE,0);
353                 
354                 spin = new JSpinner(m.getSpinnerModel());
355                 spin.setEditor(new SpinnerEditor(spin));
356                 spin.setToolTipText(tip);
357                 isa.addEnableComponent(spin, false);
358                 sub.add(spin,"w 65lp!");
359                 
360                 unit = new UnitSelector(m);
361                 unit.setToolTipText(tip);
362                 isa.addEnableComponent(unit, false);
363                 sub.add(unit,"growx");
364                 slider = new BasicSlider(m.getSliderModel(0.950e5, 1.050e5));
365                 slider.setToolTipText(tip);
366                 isa.addEnableComponent(slider, false);
367                 sub.add(slider,"w 75lp, wrap");
368                 
369
370                 
371                 
372                 
373                 //// Launch site conditions
374                 sub = new JPanel(new MigLayout("fill, gap rel unrel",
375                                 "[grow][65lp!][30lp!][75lp!]",""));
376                 sub.setBorder(BorderFactory.createTitledBorder("Launch site"));
377                 panel.add(sub, "growx, split 2, aligny 0, flowy");
378
379                 
380                 // Latitude
381                 label = new JLabel("Latitude:");
382                 tip = "<html>The launch site latitude affects the gravitational pull of Earth.<br>" +
383                         "Positive values are on the Northern hemisphere, negative values on the " +
384                         "Southern hemisphere.";
385                 label.setToolTipText(tip);
386                 sub.add(label);
387                 
388                 m = new DoubleModel(conditions,"LaunchLatitude", UnitGroup.UNITS_NONE, -90, 90);
389                 
390                 spin = new JSpinner(m.getSpinnerModel());
391                 spin.setEditor(new SpinnerEditor(spin));
392                 spin.setToolTipText(tip);
393                 sub.add(spin,"w 65lp!");
394                 
395                 label = new JLabel("\u00b0 N");
396                 label.setToolTipText(tip);
397                 sub.add(label,"growx");
398                 slider = new BasicSlider(m.getSliderModel(-90, 90));
399                 slider.setToolTipText(tip);
400                 sub.add(slider,"w 75lp, wrap");
401                 
402
403                 
404                 // Altitude
405                 label = new JLabel("Altitude:");
406                 tip = "<html>The launch altitude above mean sea level.<br>" +
407                                 "This affects the position of the rocket in the atmospheric model.";
408                 label.setToolTipText(tip);
409                 sub.add(label);
410                 
411                 m = new DoubleModel(conditions,"LaunchAltitude", UnitGroup.UNITS_DISTANCE,0);
412                 
413                 spin = new JSpinner(m.getSpinnerModel());
414                 spin.setEditor(new SpinnerEditor(spin));
415                 spin.setToolTipText(tip);
416                 sub.add(spin,"w 65lp!");
417                 
418                 unit = new UnitSelector(m);
419                 unit.setToolTipText(tip);
420                 sub.add(unit,"growx");
421                 slider = new BasicSlider(m.getSliderModel(0, 250, 1000));
422                 slider.setToolTipText(tip);
423                 sub.add(slider,"w 75lp, wrap");
424                 
425
426                 
427
428                 
429                 //// Launch rod
430                 sub = new JPanel(new MigLayout("fill, gap rel unrel",
431                                 "[grow][65lp!][30lp!][75lp!]",""));
432                 sub.setBorder(BorderFactory.createTitledBorder("Launch rod"));
433                 panel.add(sub, "growx, aligny 0, wrap");
434
435                 
436                 // Length
437                 label = new JLabel("Length:");
438                 tip = "The length of the launch rod.";
439                 label.setToolTipText(tip);
440                 sub.add(label);
441                 
442                 m = new DoubleModel(conditions,"LaunchRodLength", UnitGroup.UNITS_LENGTH, 0);
443                 
444                 spin = new JSpinner(m.getSpinnerModel());
445                 spin.setEditor(new SpinnerEditor(spin));
446                 spin.setToolTipText(tip);
447                 sub.add(spin,"w 65lp!");
448                 
449                 unit = new UnitSelector(m);
450                 unit.setToolTipText(tip);
451                 sub.add(unit,"growx");
452                 slider = new BasicSlider(m.getSliderModel(0, 1, 5));
453                 slider.setToolTipText(tip);
454                 sub.add(slider,"w 75lp, wrap");
455                 
456
457                 
458                 // Angle
459                 label = new JLabel("Angle:");
460                 tip = "The angle of the launch rod from vertical.";
461                 label.setToolTipText(tip);
462                 sub.add(label);
463                 
464                 m = new DoubleModel(conditions,"LaunchRodAngle", UnitGroup.UNITS_ANGLE,
465                                 0, SimulationConditions.MAX_LAUNCH_ROD_ANGLE);
466                 
467                 spin = new JSpinner(m.getSpinnerModel());
468                 spin.setEditor(new SpinnerEditor(spin));
469                 spin.setToolTipText(tip);
470                 sub.add(spin,"w 65lp!");
471                 
472                 unit = new UnitSelector(m);
473                 unit.setToolTipText(tip);
474                 sub.add(unit,"growx");
475                 slider = new BasicSlider(m.getSliderModel(0, Math.PI/9, 
476                                 SimulationConditions.MAX_LAUNCH_ROD_ANGLE));
477                 slider.setToolTipText(tip);
478                 sub.add(slider,"w 75lp, wrap");
479                 
480
481                 
482                 // Direction
483                 label = new JLabel("Direction:");
484                 tip = "<html>Direction of the launch rod relative to the wind.<br>" +
485                                 UnitGroup.UNITS_ANGLE.toStringUnit(0) +
486                                 " = towards the wind, "+
487                                 UnitGroup.UNITS_ANGLE.toStringUnit(Math.PI) +
488                                 " = downwind.";
489                 label.setToolTipText(tip);
490                 sub.add(label);
491                 
492                 m = new DoubleModel(conditions,"LaunchRodDirection", UnitGroup.UNITS_ANGLE,
493                                 -Math.PI, Math.PI);
494                 
495                 spin = new JSpinner(m.getSpinnerModel());
496                 spin.setEditor(new SpinnerEditor(spin));
497                 spin.setToolTipText(tip);
498                 sub.add(spin,"w 65lp!");
499                 
500                 unit = new UnitSelector(m);
501                 unit.setToolTipText(tip);
502                 sub.add(unit,"growx");
503                 slider = new BasicSlider(m.getSliderModel(-Math.PI, Math.PI));
504                 slider.setToolTipText(tip);
505                 sub.add(slider,"w 75lp, wrap");
506                 
507                 return panel;
508         }
509
510         
511         private String getIntensityDescription(double i) {
512                 if (i < 0.001)
513                         return "None";
514                 if (i < 0.05)
515                         return "Very low";
516                 if (i < 0.10)
517                         return "Low";
518                 if (i < 0.15)
519                         return "Medium";
520                 if (i < 0.20)
521                         return "High";
522                 if (i < 0.25)
523                         return "Very high";
524                 return "Extreme";
525         }
526
527         
528         
529         private JPanel simulationOptionsTab() {
530                 JPanel panel = new JPanel(new MigLayout("fill"));
531                 JPanel sub;
532                 String tip;
533                 JLabel label;
534                 DoubleModel m;
535                 JSpinner spin;
536                 UnitSelector unit;
537                 BasicSlider slider;
538
539                 
540                 //// Simulation options
541                 sub = new JPanel(new MigLayout("fill, gap rel unrel",
542                                 "[grow][65lp!][30lp!][75lp!]",""));
543                 sub.setBorder(BorderFactory.createTitledBorder("Simulator options"));
544                 panel.add(sub, "w 330lp!, growy, aligny 0");
545
546                 
547                 //  Calculation method
548                 tip = "<html>" +
549                                 "The Extended Barrowman method calculates aerodynamic forces according <br>" +
550                                 "to the Barrowman equations extended to accommodate more components.";
551
552                 label = new JLabel("Calculation method:");
553                 label.setToolTipText(tip);
554                 sub.add(label, "gaptop unrel, gapright para, spanx, split 2, w 150lp!");
555                 
556                 label = new JLabel("Extended Barrowman");
557                 label.setToolTipText(tip);
558                 sub.add(label, "growx, wrap para");
559                 
560                 
561                 //  Simulation method
562                 tip = "<html>" +
563                                 "The six degree-of-freedom simulator allows the rocket total freedom during " +
564                                 "flight.<br>" +
565                                 "Integration is performed using a 4<sup>th</sup> order Runge-Kutta 4 " +
566                                 "numerical integration.";
567
568                 label = new JLabel("Simulation method:");
569                 label.setToolTipText(tip);
570                 sub.add(label, "gaptop unrel, gapright para, spanx, split 2, w 150lp!");
571                 
572                 label = new JLabel("6-DOF Runge-Kutta 4");
573                 label.setToolTipText(tip);
574                 sub.add(label, "growx, wrap 35lp");
575                 
576                 
577                 // Wind average
578                 label = new JLabel("Time step:");
579                 tip = "<html>The time between simulation steps.<br>" +
580                 "A smaller time step results in a more accurate but slower simulation.<br>" +
581                                 "The 4<sup>th</sup> order simulation method is quite accurate with a time " +
582                                 "step of " +
583                                 UnitGroup.UNITS_TIME_STEP.toStringUnit(RK4Simulator.RECOMMENDED_TIME_STEP) +
584                                 ".";
585                 label.setToolTipText(tip);
586                 sub.add(label);
587                 
588                 m = new DoubleModel(conditions,"TimeStep", UnitGroup.UNITS_TIME_STEP, 0, 1);
589                 
590                 spin = new JSpinner(m.getSpinnerModel());
591                 spin.setEditor(new SpinnerEditor(spin));
592                 spin.setToolTipText(tip);
593                 sub.add(spin,"w 65lp!");
594                 
595                 unit = new UnitSelector(m);
596                 unit.setToolTipText(tip);
597                 sub.add(unit,"growx");
598                 slider = new BasicSlider(m.getSliderModel(0, 0.2));
599                 slider.setToolTipText(tip);
600                 sub.add(slider,"w 75lp, wrap");
601                 
602
603
604                 // Maximum angle step
605                 /*
606                 label = new JLabel("Max. angle step:");
607                 tip = "<html>" +
608                                 "This defines the maximum angle the rocket will turn during one time step.<br>"+
609                                 "Smaller values result in a more accurate but possibly slower simulation.<br>"+
610                                 "A recommended value is " +
611                                 UnitGroup.UNITS_ANGLE.toStringUnit(RK4Simulator.RECOMMENDED_ANGLE_STEP) + ".";
612                 label.setToolTipText(tip);
613                 sub.add(label);
614                 
615                 m = new DoubleModel(conditions,"MaximumStepAngle", UnitGroup.UNITS_ANGLE, 
616                                 1*Math.PI/180, Math.PI/9);
617                 
618                 spin = new JSpinner(m.getSpinnerModel());
619                 spin.setEditor(new SpinnerEditor(spin));
620                 spin.setToolTipText(tip);
621                 sub.add(spin,"w 65lp!");
622                 
623                 unit = new UnitSelector(m);
624                 unit.setToolTipText(tip);
625                 sub.add(unit,"growx");
626                 slider = new BasicSlider(m.getSliderModel(0, Math.toRadians(10)));
627                 slider.setToolTipText(tip);
628                 sub.add(slider,"w 75lp, wrap para");
629                 */
630
631                 JButton button = new JButton("Reset to default");
632                 button.setToolTipText("Reset the time step to its default value (" +
633                                 UnitGroup.UNITS_SHORT_TIME.toStringUnit(RK4Simulator.RECOMMENDED_TIME_STEP) +
634                                 ").");
635                                 
636 //              button.setToolTipText("<html>Reset the step value to its default:<br>" +
637 //                              "Time step " +
638 //                              UnitGroup.UNITS_SHORT_TIME.toStringUnit(RK4Simulator.RECOMMENDED_TIME_STEP) +
639 //                              "; maximum angle step " +
640 //                              UnitGroup.UNITS_ANGLE.toStringUnit(RK4Simulator.RECOMMENDED_ANGLE_STEP) + ".");
641                 sub.add(button, "spanx, tag right, wrap para");
642                 
643                 
644                 
645                 
646                 //// Simulation listeners
647                 sub = new JPanel(new MigLayout("fill, gap 0 0"));
648                 sub.setBorder(BorderFactory.createTitledBorder("Simulator listeners"));
649                 panel.add(sub, "growx, growy");
650                 
651                 
652                 DescriptionArea desc = new DescriptionArea(5);
653                 desc.setText("<html><p>" +
654                                 "<i>Simulation listeners</i> is an advanced feature that allows "+
655                                 "user-written code to listen to and interact with the simulation.  " +
656                                 "For details on writing simulation listeners, see the OpenRocket " +
657                                 "technical documentation.</p>");
658                 sub.add(desc, "aligny 0, growx, wrap para");
659                 
660                 
661                 label = new JLabel("Current listeners:");
662                 sub.add(label, "spanx, wrap rel");
663
664                 final ListenerListModel listenerModel = new ListenerListModel();
665                 final JList list = new JList(listenerModel);
666                 list.setCellRenderer(new ListenerCellRenderer());
667                 JScrollPane scroll = new JScrollPane(list);
668 //              scroll.setPreferredSize(new Dimension(1,1));
669                 sub.add(scroll, "height 1px, grow, wrap rel");
670                 
671                 
672                 button = new JButton("Add");
673                 button.addActionListener(new ActionListener() {
674                         @Override
675                         public void actionPerformed(ActionEvent e) {
676                                 String previous = Prefs.NODE.get("previousListenerName", "");
677                                 String input = (String)JOptionPane.showInputDialog(SimulationEditDialog.this,
678                                                 new Object[] {
679                                                 "Type the full Java class name of the simulation listener, for example:",
680                                                 "<html><tt>" + CSVSaveListener.class.getName() + "</tt>" },
681                                                 "Add simulation listener",
682                                                 JOptionPane.QUESTION_MESSAGE,
683                                                 null, null,
684                                                 previous
685                                 );
686                                 if (input == null || input.equals(""))
687                                         return;
688
689                                 Prefs.NODE.put("previousListenerName", input);
690                                 simulation.getSimulationListeners().add(input);
691                                 listenerModel.fireContentsChanged();
692                         }
693                 });
694                 sub.add(button, "split 2, sizegroup buttons, alignx 50%, gapright para");
695                 
696                 button = new JButton("Remove");
697                 button.addActionListener(new ActionListener() {
698                         @Override
699                         public void actionPerformed(ActionEvent e) {
700                                 int[] selected = list.getSelectedIndices();
701                                 Arrays.sort(selected);
702                                 for (int i=selected.length-1; i>=0; i--) {
703                                         simulation.getSimulationListeners().remove(selected[i]);
704                                 }
705                                 listenerModel.fireContentsChanged();
706                         }
707                 });
708                 sub.add(button, "sizegroup buttons, alignx 50%");
709                 
710
711                 return panel;
712         }
713
714
715         private class ListenerListModel extends AbstractListModel {
716                 @Override
717                 public String getElementAt(int index) {
718                         if (index < 0 || index >= getSize())
719                                 return null;
720                         return simulation.getSimulationListeners().get(index);
721                 }
722                 @Override
723                 public int getSize() {
724                         return simulation.getSimulationListeners().size();
725                 }
726                 public void fireContentsChanged() {
727                         super.fireContentsChanged(this, 0, getSize());
728                 }
729         }
730         
731         
732         
733         
734         /**
735          * A panel for plotting the previously calculated data.
736          */
737         private JPanel plotTab() {
738
739                 // Check that data exists
740                 if (simulation.getSimulatedData() == null  ||
741                                 simulation.getSimulatedData().getBranchCount() == 0) {
742                         return noDataPanel();
743                 }
744                 
745                 
746                 if (true)
747                         return new PlotPanel(simulation);
748                 
749                 JPanel panel = new JPanel(new MigLayout("fill"));
750                 
751                 
752                 
753                 
754                 JButton button = new JButton("test");
755
756                 button.addActionListener(new ActionListener() {
757                         @Override
758                         public void actionPerformed(ActionEvent e) {
759                                 PlotConfiguration config = new PlotConfiguration();
760                                 config.addPlotDataType(FlightDataBranch.TYPE_ALTITUDE);
761                                 config.addPlotDataType(FlightDataBranch.TYPE_VELOCITY_Z);
762                                 config.addPlotDataType(FlightDataBranch.TYPE_ACCELERATION_Z);
763                                 config.addPlotDataType(FlightDataBranch.TYPE_ACCELERATION_TOTAL);
764                                 
765                                 config.setDomainAxisType(FlightDataBranch.TYPE_TIME);
766                                 
767                                 performPlot(config);
768                         }
769                 });
770                 panel.add(button);
771                 
772                 return panel;
773         }
774         
775         
776         
777         /**
778          * A panel for exporting the data.
779          */
780         private JPanel exportTab() {
781
782                 // Check that data exists
783                 if (simulation.getSimulatedData() == null  ||
784                                 simulation.getSimulatedData().getBranchCount() == 0) {
785                         return noDataPanel();
786                 }
787                 
788                 
789                 JPanel panel = new JPanel(new MigLayout("fill"));
790                 
791                 panel.add(new JLabel("Not implemented yet.")); // TODO: HIGH: Implement export
792                 
793                 return panel;
794         }
795         
796         
797         
798         
799
800         /**
801          * Return a panel stating that there is no data available, and that the user
802          * should run the simulation first.
803          */
804         private JPanel noDataPanel() {
805                 JPanel panel = new JPanel(new MigLayout("fill"));
806                 
807                 // No data available
808                 panel.add(new JLabel("No flight data available."),
809                 "alignx 50%, aligny 100%, wrap para");
810                 panel.add(new JLabel("Please run the simulation first."),
811                 "alignx 50%, aligny 0%, wrap");
812                 return panel;
813         }
814         
815
816         private void performPlot(PlotConfiguration config) {
817
818                 // Fill the auto-selections
819                 FlightDataBranch branch = simulation.getSimulatedData().getBranch(0);
820                 PlotConfiguration filled = config.fillAutoAxes(branch);
821                 List<Axis> axes = filled.getAllAxes();
822
823
824                 // Create the data series for both axes
825                 XYSeriesCollection[] data = new XYSeriesCollection[2];
826                 data[0] = new XYSeriesCollection();
827                 data[1] = new XYSeriesCollection();
828                 
829                 
830                 // Get the domain axis type
831                 final FlightDataBranch.Type domainType = filled.getDomainAxisType();
832                 final Unit domainUnit = filled.getDomainAxisUnit();
833                 if (domainType == null) {
834                         throw new IllegalArgumentException("Domain axis type not specified.");
835                 }
836                 List<Double> x = branch.get(domainType);
837
838                 
839                 // Create the XYSeries objects from the flight data and store into the collections
840                 int length = filled.getTypeCount();
841                 String[] axisLabel = new String[2];
842                 for (int i = 0; i < length; i++) {
843                         // Get info
844                         FlightDataBranch.Type type = filled.getType(i);
845                         Unit unit = filled.getUnit(i);
846                         int axis = filled.getAxis(i);
847                         String name = getLabel(type, unit);
848                         
849                         // Store data in provided units
850                         List<Double> y = branch.get(type);
851                         XYSeries series = new XYSeries(name, false, true);
852                         for (int j=0; j<x.size(); j++) {
853                                 series.add(domainUnit.toUnit(x.get(j)), unit.toUnit(y.get(j)));
854                         }
855                         data[axis].addSeries(series);
856
857                         // Update axis label
858                         if (axisLabel[axis] == null)
859                                 axisLabel[axis] = type.getName();
860                         else
861                                 axisLabel[axis] += "; " + type.getName();
862                 }
863                 
864                 
865                 // Create the chart using the factory to get all default settings
866         JFreeChart chart = ChartFactory.createXYLineChart(
867             "Simulated flight",
868             null, 
869             null, 
870             null,
871             PlotOrientation.VERTICAL,
872             true,
873             true,
874             false
875         );
876                 
877         
878                 // Add the data and formatting to the plot
879                 XYPlot plot = chart.getXYPlot();
880                 int axisno = 0;
881                 for (int i=0; i<2; i++) {
882                         // Check whether axis has any data
883                         if (data[i].getSeriesCount() > 0) {
884                                 // Create and set axis
885                                 double min = axes.get(i).getMinValue();
886                                 double max = axes.get(i).getMaxValue();
887                                 NumberAxis axis = new PresetNumberAxis(min, max);
888                                 axis.setLabel(axisLabel[i]);
889 //                              axis.setRange(axes.get(i).getMinValue(), axes.get(i).getMaxValue());
890                                 plot.setRangeAxis(axisno, axis);
891                                 
892                                 // Add data and map to the axis
893                                 plot.setDataset(axisno, data[i]);
894                                 plot.setRenderer(axisno, new StandardXYItemRenderer());
895                                 plot.mapDatasetToRangeAxis(axisno, axisno);
896                                 axisno++;
897                         }
898                 }
899                 
900                 plot.getDomainAxis().setLabel(getLabel(domainType,domainUnit));
901                 plot.addDomainMarker(new ValueMarker(0));
902                 plot.addRangeMarker(new ValueMarker(0));
903                 
904                 
905                 // Create the dialog
906                 final JDialog dialog = new JDialog(this, "Simulation results");
907                 dialog.setModalityType(ModalityType.DOCUMENT_MODAL);
908                 
909                 JPanel panel = new JPanel(new MigLayout("fill"));
910                 dialog.add(panel);
911                 
912                 ChartPanel chartPanel = new ChartPanel(chart,
913                                 false, // properties
914                                 true,  // save
915                                 false, // print
916                                 true,  // zoom
917                                 true); // tooltips
918                 chartPanel.setMouseWheelEnabled(true);
919                 chartPanel.setEnforceFileExtensions(true);
920                 chartPanel.setInitialDelay(500);
921                 
922                 chartPanel.setBorder(BorderFactory.createLineBorder(Color.GRAY, 1));
923                 
924                 panel.add(chartPanel, "grow, wrap 20lp");
925
926                 JButton button = new JButton("Close");
927                 button.addActionListener(new ActionListener() {
928                         @Override
929                         public void actionPerformed(ActionEvent e) {
930                                 dialog.setVisible(false);
931                         }
932                 });
933                 panel.add(button, "right");
934
935                 dialog.setLocationByPlatform(true);
936                 dialog.pack();
937                 GUIUtil.installEscapeCloseOperation(dialog);
938                 GUIUtil.setDefaultButton(button);
939
940                 dialog.setVisible(true);
941         }
942
943         
944         private class PresetNumberAxis extends NumberAxis {
945                 private final double min;
946                 private final double max;
947                 
948                 public PresetNumberAxis(double min, double max) {
949                         this.min = min;
950                         this.max = max;
951                         autoAdjustRange();
952                 }
953                 
954                 @Override
955                 protected void autoAdjustRange() {
956                         this.setRange(min, max);
957                 }
958         }
959         
960         
961         private String getLabel(FlightDataBranch.Type type, Unit unit) {
962                 String name = type.getName();
963                 if (unit != null  &&  !UnitGroup.UNITS_NONE.contains(unit)  &&
964                                 !UnitGroup.UNITS_COEFFICIENT.contains(unit) && unit.getUnit().length() > 0)
965                         name += " ("+unit.getUnit() + ")";
966                 return name;
967         }
968         
969
970
971         private class ListenerCellRenderer extends JLabel implements ListCellRenderer {
972
973                 public Component getListCellRendererComponent(JList list, Object value,
974                                 int index, boolean isSelected, boolean cellHasFocus) {
975                         String s = value.toString();
976                         setText(s);
977
978                         // Attempt instantiating, catch any exceptions
979                         Exception ex = null;
980                         try {
981                                 Class<?> c = Class.forName(s);
982                                 @SuppressWarnings("unused")
983                                 SimulationListener l = (SimulationListener)c.newInstance();
984                         } catch (Exception e) {
985                                 ex = e;
986                         }
987
988                         if (ex == null) {
989                                 setIcon(Icons.SIMULATION_LISTENER_OK);
990                                 setToolTipText("Listener instantiated successfully.");
991                         } else {
992                                 setIcon(Icons.SIMULATION_LISTENER_ERROR);
993                                 setToolTipText("<html>Unable to instantiate listener due to exception:<br>" +
994                                                 ex.toString());
995                         }
996
997                         if (isSelected) {
998                                 setBackground(list.getSelectionBackground());
999                                 setForeground(list.getSelectionForeground());
1000                         } else {
1001                                 setBackground(list.getBackground());
1002                                 setForeground(list.getForeground());
1003                         }
1004                         setOpaque(true);
1005                         return this;
1006                 }
1007         }
1008 }