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