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