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