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