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