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