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