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