1 package net.sf.openrocket.gui.main;
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;
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;
32 import net.miginfocom.swing.MigLayout;
33 import net.sf.openrocket.document.OpenRocketDocument;
34 import net.sf.openrocket.document.Simulation;
35 import net.sf.openrocket.gui.SpinnerEditor;
36 import net.sf.openrocket.gui.adaptors.BooleanModel;
37 import net.sf.openrocket.gui.adaptors.DoubleModel;
38 import net.sf.openrocket.gui.adaptors.EnumModel;
39 import net.sf.openrocket.gui.adaptors.MotorConfigurationModel;
40 import net.sf.openrocket.gui.components.BasicSlider;
41 import net.sf.openrocket.gui.components.DescriptionArea;
42 import net.sf.openrocket.gui.components.SimulationExportPanel;
43 import net.sf.openrocket.gui.components.UnitSelector;
44 import net.sf.openrocket.gui.plot.Axis;
45 import net.sf.openrocket.gui.plot.PlotConfiguration;
46 import net.sf.openrocket.gui.plot.SimulationPlotPanel;
47 import net.sf.openrocket.gui.util.GUIUtil;
48 import net.sf.openrocket.gui.util.Icons;
49 import net.sf.openrocket.l10n.Translator;
50 import net.sf.openrocket.models.atmosphere.ExtendedISAModel;
51 import net.sf.openrocket.rocketcomponent.Configuration;
52 import net.sf.openrocket.simulation.FlightData;
53 import net.sf.openrocket.simulation.FlightDataBranch;
54 import net.sf.openrocket.simulation.FlightDataType;
55 import net.sf.openrocket.simulation.RK4SimulationStepper;
56 import net.sf.openrocket.simulation.SimulationOptions;
57 import net.sf.openrocket.simulation.listeners.SimulationListener;
58 import net.sf.openrocket.simulation.listeners.example.CSVSaveListener;
59 import net.sf.openrocket.startup.Application;
60 import net.sf.openrocket.unit.Unit;
61 import net.sf.openrocket.unit.UnitGroup;
62 import net.sf.openrocket.util.Chars;
63 import net.sf.openrocket.util.GeodeticComputationStrategy;
65 import org.jfree.chart.ChartFactory;
66 import org.jfree.chart.ChartPanel;
67 import org.jfree.chart.JFreeChart;
68 import org.jfree.chart.axis.NumberAxis;
69 import org.jfree.chart.plot.PlotOrientation;
70 import org.jfree.chart.plot.ValueMarker;
71 import org.jfree.chart.plot.XYPlot;
72 import org.jfree.chart.renderer.xy.StandardXYItemRenderer;
73 import org.jfree.data.xy.XYSeries;
74 import org.jfree.data.xy.XYSeriesCollection;
77 public class SimulationEditDialog extends JDialog {
79 public static final int DEFAULT = -1;
80 public static final int EDIT = 1;
81 public static final int PLOT = 2;
84 private final Window parentWindow;
85 private final Simulation simulation;
86 private final OpenRocketDocument document;
87 private final SimulationOptions conditions;
88 private final Configuration configuration;
89 private static final Translator trans = Application.getTranslator();
92 public SimulationEditDialog(Window parent, OpenRocketDocument document, Simulation s) {
93 this(parent, document, s, 0);
96 public SimulationEditDialog(Window parent, OpenRocketDocument document, Simulation s, int tab) {
98 super(parent, trans.get("simedtdlg.title.Editsim"), JDialog.ModalityType.DOCUMENT_MODAL);
99 this.document = document;
100 this.parentWindow = parent;
102 this.conditions = simulation.getOptions();
103 configuration = simulation.getConfiguration();
105 JPanel mainPanel = new JPanel(new MigLayout("fill", "[grow, fill]"));
107 //// Simulation name:
108 mainPanel.add(new JLabel(trans.get("simedtdlg.lbl.Simname") + " "), "span, split 2, shrink");
109 final JTextField field = new JTextField(simulation.getName());
110 field.getDocument().addDocumentListener(new DocumentListener() {
112 public void changedUpdate(DocumentEvent e) {
117 public void insertUpdate(DocumentEvent e) {
122 public void removeUpdate(DocumentEvent e) {
126 private void setText() {
127 String name = field.getText();
128 if (name == null || name.equals(""))
130 //System.out.println("Setting name:" + name);
131 simulation.setName(name);
135 mainPanel.add(field, "shrinky, growx, wrap");
137 JTabbedPane tabbedPane = new JTabbedPane();
139 //// Launch conditions
140 tabbedPane.addTab(trans.get("simedtdlg.tab.Launchcond"), flightConditionsTab());
141 //// Simulation options
142 tabbedPane.addTab(trans.get("simedtdlg.tab.Simopt"), simulationOptionsTab());
144 tabbedPane.addTab(trans.get("simedtdlg.tab.Plotdata"), plotTab());
146 tabbedPane.addTab(trans.get("simedtdlg.tab.Exportdata"), exportTab());
148 // Select the initial tab
150 tabbedPane.setSelectedIndex(0);
151 } else if (tab == PLOT) {
152 tabbedPane.setSelectedIndex(2);
154 FlightData data = s.getSimulatedData();
155 if (data == null || data.getBranchCount() == 0)
156 tabbedPane.setSelectedIndex(0);
158 tabbedPane.setSelectedIndex(2);
161 mainPanel.add(tabbedPane, "spanx, grow, wrap");
165 mainPanel.add(new JPanel(), "spanx, split, growx");
168 //// Run simulation button
169 button = new JButton(trans.get("simedtdlg.but.runsimulation"));
170 button.addActionListener(new ActionListener() {
172 public void actionPerformed(ActionEvent e) {
173 SimulationEditDialog.this.dispose();
174 SimulationRunDialog.runSimulations(parentWindow, SimulationEditDialog.this.document, simulation);
177 mainPanel.add(button, "gapright para");
180 JButton close = new JButton(trans.get("dlg.but.close"));
181 close.addActionListener(new ActionListener() {
183 public void actionPerformed(ActionEvent e) {
184 SimulationEditDialog.this.dispose();
187 mainPanel.add(close, "");
193 this.setLocationByPlatform(true);
195 GUIUtil.setDisposableDialogOptions(this, button);
202 private JPanel flightConditionsTab() {
203 JPanel panel = new JPanel(new MigLayout("fill"));
212 //// Motor configuration:
213 JLabel label = new JLabel(trans.get("simedtdlg.lbl.Motorcfg"));
214 //// Select the motor configuration to use.
215 label.setToolTipText(trans.get("simedtdlg.lbl.ttip.Motorcfg"));
216 panel.add(label, "shrinkx, spanx, split 2");
218 JComboBox combo = new JComboBox(new MotorConfigurationModel(configuration));
219 //// Select the motor configuration to use.
220 combo.setToolTipText(trans.get("simedtdlg.combo.ttip.motorconf"));
221 combo.addActionListener(new ActionListener() {
223 public void actionPerformed(ActionEvent e) {
224 conditions.setMotorConfigurationID(configuration.getMotorConfigurationID());
227 panel.add(combo, "growx, wrap para");
230 //// Wind settings: Average wind speed, turbulence intensity, std. deviation
231 sub = new JPanel(new MigLayout("fill, gap rel unrel",
232 "[grow][65lp!][30lp!][75lp!]", ""));
234 sub.setBorder(BorderFactory.createTitledBorder(trans.get("simedtdlg.lbl.Wind")));
235 panel.add(sub, "growx, split 2, aligny 0, flowy, gapright para");
239 //// Average windspeed:
240 label = new JLabel(trans.get("simedtdlg.lbl.Averwindspeed"));
241 //// The average windspeed relative to the ground.
242 tip = trans.get("simedtdlg.lbl.ttip.Averwindspeed");
243 label.setToolTipText(tip);
246 m = new DoubleModel(conditions, "WindSpeedAverage", UnitGroup.UNITS_WINDSPEED, 0);
248 spin = new JSpinner(m.getSpinnerModel());
249 spin.setEditor(new SpinnerEditor(spin));
250 spin.setToolTipText(tip);
251 sub.add(spin, "w 65lp!");
253 unit = new UnitSelector(m);
254 unit.setToolTipText(tip);
255 sub.add(unit, "growx");
256 slider = new BasicSlider(m.getSliderModel(0, 10.0));
257 slider.setToolTipText(tip);
258 sub.add(slider, "w 75lp, wrap");
262 // Wind std. deviation
263 //// Standard deviation:
264 label = new JLabel(trans.get("simedtdlg.lbl.Stddeviation"));
265 //// <html>The standard deviation of the windspeed.<br>
266 //// The windspeed is within twice the standard deviation from the average for 95% of the time.
267 tip = trans.get("simedtdlg.lbl.ttip.Stddeviation");
268 label.setToolTipText(tip);
271 m = new DoubleModel(conditions, "WindSpeedDeviation", UnitGroup.UNITS_WINDSPEED, 0);
272 DoubleModel m2 = new DoubleModel(conditions, "WindSpeedAverage", 0.25,
273 UnitGroup.UNITS_COEFFICIENT, 0);
275 spin = new JSpinner(m.getSpinnerModel());
276 spin.setEditor(new SpinnerEditor(spin));
277 spin.setToolTipText(tip);
278 sub.add(spin, "w 65lp!");
280 unit = new UnitSelector(m);
281 unit.setToolTipText(tip);
282 sub.add(unit, "growx");
283 slider = new BasicSlider(m.getSliderModel(new DoubleModel(0), m2));
284 slider.setToolTipText(tip);
285 sub.add(slider, "w 75lp, wrap");
288 // Wind turbulence intensity
289 //// Turbulence intensity:
290 label = new JLabel(trans.get("simedtdlg.lbl.Turbulenceintensity"));
291 //// <html>The turbulence intensity is the standard deviation divided by the average windspeed.<br>
292 //// Typical values range from
294 tip = trans.get("simedtdlg.lbl.ttip.Turbulenceintensity1") +
295 trans.get("simedtdlg.lbl.ttip.Turbulenceintensity2") + " " +
296 UnitGroup.UNITS_RELATIVE.getDefaultUnit().toStringUnit(0.05) +
297 " " + trans.get("simedtdlg.lbl.ttip.Turbulenceintensity3") + " " +
298 UnitGroup.UNITS_RELATIVE.getDefaultUnit().toStringUnit(0.20) + ".";
299 label.setToolTipText(tip);
302 m = new DoubleModel(conditions, "WindTurbulenceIntensity", UnitGroup.UNITS_RELATIVE, 0);
304 spin = new JSpinner(m.getSpinnerModel());
305 spin.setEditor(new SpinnerEditor(spin));
306 spin.setToolTipText(tip);
307 sub.add(spin, "w 65lp!");
309 unit = new UnitSelector(m);
310 unit.setToolTipText(tip);
311 sub.add(unit, "growx");
313 final JLabel intensityLabel = new JLabel(
314 getIntensityDescription(conditions.getWindTurbulenceIntensity()));
315 intensityLabel.setToolTipText(tip);
316 sub.add(intensityLabel, "w 75lp, wrap");
317 m.addChangeListener(new ChangeListener() {
319 public void stateChanged(ChangeEvent e) {
320 intensityLabel.setText(
321 getIntensityDescription(conditions.getWindTurbulenceIntensity()));
329 //// Temperature and pressure
330 sub = new JPanel(new MigLayout("fill, gap rel unrel",
331 "[grow][65lp!][30lp!][75lp!]", ""));
332 //// Atmospheric conditions
333 sub.setBorder(BorderFactory.createTitledBorder(trans.get("simedtdlg.border.Atmoscond")));
334 panel.add(sub, "growx, aligny 0, gapright para");
337 BooleanModel isa = new BooleanModel(conditions, "ISAAtmosphere");
338 JCheckBox check = new JCheckBox(isa);
339 //// Use International Standard Atmosphere
340 check.setText(trans.get("simedtdlg.checkbox.InterStdAtmosphere"));
341 //// <html>Select to use the International Standard Atmosphere model.
342 //// <br>This model has a temperature of
343 //// and a pressure of
345 check.setToolTipText(trans.get("simedtdlg.checkbox.ttip.InterStdAtmosphere1") + " " +
346 UnitGroup.UNITS_TEMPERATURE.toStringUnit(ExtendedISAModel.STANDARD_TEMPERATURE) +
347 " " + trans.get("simedtdlg.checkbox.ttip.InterStdAtmosphere2") + " " +
348 UnitGroup.UNITS_PRESSURE.toStringUnit(ExtendedISAModel.STANDARD_PRESSURE) +
349 " " + trans.get("simedtdlg.checkbox.ttip.InterStdAtmosphere3"));
350 sub.add(check, "spanx, wrap unrel");
353 label = new JLabel(trans.get("simedtdlg.lbl.Temperature"));
354 //// The temperature at the launch site.
355 tip = trans.get("simedtdlg.lbl.ttip.Temperature");
356 label.setToolTipText(tip);
357 isa.addEnableComponent(label, false);
360 m = new DoubleModel(conditions, "LaunchTemperature", UnitGroup.UNITS_TEMPERATURE, 0);
362 spin = new JSpinner(m.getSpinnerModel());
363 spin.setEditor(new SpinnerEditor(spin));
364 spin.setToolTipText(tip);
365 isa.addEnableComponent(spin, false);
366 sub.add(spin, "w 65lp!");
368 unit = new UnitSelector(m);
369 unit.setToolTipText(tip);
370 isa.addEnableComponent(unit, false);
371 sub.add(unit, "growx");
372 slider = new BasicSlider(m.getSliderModel(253.15, 308.15)); // -20 ... 35
373 slider.setToolTipText(tip);
374 isa.addEnableComponent(slider, false);
375 sub.add(slider, "w 75lp, wrap");
380 label = new JLabel(trans.get("simedtdlg.lbl.Pressure"));
381 //// The atmospheric pressure at the launch site.
382 tip = trans.get("simedtdlg.lbl.ttip.Pressure");
383 label.setToolTipText(tip);
384 isa.addEnableComponent(label, false);
387 m = new DoubleModel(conditions, "LaunchPressure", UnitGroup.UNITS_PRESSURE, 0);
389 spin = new JSpinner(m.getSpinnerModel());
390 spin.setEditor(new SpinnerEditor(spin));
391 spin.setToolTipText(tip);
392 isa.addEnableComponent(spin, false);
393 sub.add(spin, "w 65lp!");
395 unit = new UnitSelector(m);
396 unit.setToolTipText(tip);
397 isa.addEnableComponent(unit, false);
398 sub.add(unit, "growx");
399 slider = new BasicSlider(m.getSliderModel(0.950e5, 1.050e5));
400 slider.setToolTipText(tip);
401 isa.addEnableComponent(slider, false);
402 sub.add(slider, "w 75lp, wrap");
408 //// Launch site conditions
409 sub = new JPanel(new MigLayout("fill, gap rel unrel",
410 "[grow][65lp!][30lp!][75lp!]", ""));
412 sub.setBorder(BorderFactory.createTitledBorder(trans.get("simedtdlg.lbl.Launchsite")));
413 panel.add(sub, "growx, split 2, aligny 0, flowy");
417 label = new JLabel(trans.get("simedtdlg.lbl.Latitude"));
418 //// <html>The launch site latitude affects the gravitational pull of Earth.<br>
419 //// Positive values are on the Northern hemisphere, negative values on the Southern hemisphere.
420 tip = trans.get("simedtdlg.lbl.ttip.Latitude");
421 label.setToolTipText(tip);
424 m = new DoubleModel(conditions, "LaunchLatitude", UnitGroup.UNITS_NONE, -90, 90);
426 spin = new JSpinner(m.getSpinnerModel());
427 spin.setEditor(new SpinnerEditor(spin));
428 spin.setToolTipText(tip);
429 sub.add(spin, "w 65lp!");
431 label = new JLabel(Chars.DEGREE + " N");
432 label.setToolTipText(tip);
433 sub.add(label, "growx");
434 slider = new BasicSlider(m.getSliderModel(-90, 90));
435 slider.setToolTipText(tip);
436 sub.add(slider, "w 75lp, wrap");
440 label = new JLabel(trans.get("simedtdlg.lbl.Longitude"));
441 tip = trans.get("simedtdlg.lbl.ttip.Longitude");
442 label.setToolTipText(tip);
445 m = new DoubleModel(conditions, "LaunchLongitude", UnitGroup.UNITS_NONE, -180, 180);
447 spin = new JSpinner(m.getSpinnerModel());
448 spin.setEditor(new SpinnerEditor(spin));
449 spin.setToolTipText(tip);
450 sub.add(spin, "w 65lp!");
452 label = new JLabel(Chars.DEGREE + " E");
453 label.setToolTipText(tip);
454 sub.add(label, "growx");
455 slider = new BasicSlider(m.getSliderModel(-180, 180));
456 slider.setToolTipText(tip);
457 sub.add(slider, "w 75lp, wrap");
461 label = new JLabel(trans.get("simedtdlg.lbl.Altitude"));
462 //// <html>The launch altitude above mean sea level.<br>
463 //// This affects the position of the rocket in the atmospheric model.
464 tip = trans.get("simedtdlg.lbl.ttip.Altitude");
465 label.setToolTipText(tip);
468 m = new DoubleModel(conditions, "LaunchAltitude", UnitGroup.UNITS_DISTANCE, 0);
470 spin = new JSpinner(m.getSpinnerModel());
471 spin.setEditor(new SpinnerEditor(spin));
472 spin.setToolTipText(tip);
473 sub.add(spin, "w 65lp!");
475 unit = new UnitSelector(m);
476 unit.setToolTipText(tip);
477 sub.add(unit, "growx");
478 slider = new BasicSlider(m.getSliderModel(0, 250, 1000));
479 slider.setToolTipText(tip);
480 sub.add(slider, "w 75lp, wrap");
487 sub = new JPanel(new MigLayout("fill, gap rel unrel",
488 "[grow][65lp!][30lp!][75lp!]", ""));
490 sub.setBorder(BorderFactory.createTitledBorder(trans.get("simedtdlg.border.Launchrod")));
491 panel.add(sub, "growx, aligny 0, wrap");
495 label = new JLabel(trans.get("simedtdlg.lbl.Length"));
496 //// The length of the launch rod.
497 tip = trans.get("simedtdlg.lbl.ttip.Length");
498 label.setToolTipText(tip);
501 m = new DoubleModel(conditions, "LaunchRodLength", UnitGroup.UNITS_LENGTH, 0);
503 spin = new JSpinner(m.getSpinnerModel());
504 spin.setEditor(new SpinnerEditor(spin));
505 spin.setToolTipText(tip);
506 sub.add(spin, "w 65lp!");
508 unit = new UnitSelector(m);
509 unit.setToolTipText(tip);
510 sub.add(unit, "growx");
511 slider = new BasicSlider(m.getSliderModel(0, 1, 5));
512 slider.setToolTipText(tip);
513 sub.add(slider, "w 75lp, wrap");
518 label = new JLabel(trans.get("simedtdlg.lbl.Angle"));
519 //// The angle of the launch rod from vertical.
520 tip = trans.get("simedtdlg.lbl.ttip.Angle");
521 label.setToolTipText(tip);
524 m = new DoubleModel(conditions, "LaunchRodAngle", UnitGroup.UNITS_ANGLE,
525 0, SimulationOptions.MAX_LAUNCH_ROD_ANGLE);
527 spin = new JSpinner(m.getSpinnerModel());
528 spin.setEditor(new SpinnerEditor(spin));
529 spin.setToolTipText(tip);
530 sub.add(spin, "w 65lp!");
532 unit = new UnitSelector(m);
533 unit.setToolTipText(tip);
534 sub.add(unit, "growx");
535 slider = new BasicSlider(m.getSliderModel(0, Math.PI / 9,
536 SimulationOptions.MAX_LAUNCH_ROD_ANGLE));
537 slider.setToolTipText(tip);
538 sub.add(slider, "w 75lp, wrap");
543 label = new JLabel(trans.get("simedtdlg.lbl.Direction"));
544 //// <html>Direction of the launch rod relative to the wind.<br>
545 //// = towards the wind,
547 tip = trans.get("simedtdlg.lbl.ttip.Direction1") +
548 UnitGroup.UNITS_ANGLE.toStringUnit(0) +
549 " " + trans.get("simedtdlg.lbl.ttip.Direction2") + " " +
550 UnitGroup.UNITS_ANGLE.toStringUnit(Math.PI) +
551 " " + trans.get("simedtdlg.lbl.ttip.Direction3");
552 label.setToolTipText(tip);
555 m = new DoubleModel(conditions, "LaunchRodDirection", UnitGroup.UNITS_ANGLE,
558 spin = new JSpinner(m.getSpinnerModel());
559 spin.setEditor(new SpinnerEditor(spin));
560 spin.setToolTipText(tip);
561 sub.add(spin, "w 65lp!");
563 unit = new UnitSelector(m);
564 unit.setToolTipText(tip);
565 sub.add(unit, "growx");
566 slider = new BasicSlider(m.getSliderModel(-Math.PI, Math.PI));
567 slider.setToolTipText(tip);
568 sub.add(slider, "w 75lp, wrap");
574 private String getIntensityDescription(double i) {
577 return trans.get("simedtdlg.IntensityDesc.None");
580 return trans.get("simedtdlg.IntensityDesc.Verylow");
583 return trans.get("simedtdlg.IntensityDesc.Low");
586 return trans.get("simedtdlg.IntensityDesc.Medium");
589 return trans.get("simedtdlg.IntensityDesc.High");
592 return trans.get("simedtdlg.IntensityDesc.Veryhigh");
594 return trans.get("simedtdlg.IntensityDesc.Extreme");
599 private JPanel simulationOptionsTab() {
600 JPanel panel = new JPanel(new MigLayout("fill"));
610 //// Simulation options
611 sub = new JPanel(new MigLayout("fill, gap rel unrel",
612 "[grow][65lp!][30lp!][75lp!]", ""));
613 //// Simulator options
614 sub.setBorder(BorderFactory.createTitledBorder(trans.get("simedtdlg.border.Simopt")));
615 panel.add(sub, "growx, growy, aligny 0");
618 // Separate panel for computation methods, as they use a different layout
619 subsub = new JPanel(new MigLayout("insets 0, fill"));
622 //// Calculation method:
623 tip = trans.get("simedtdlg.lbl.ttip.Calcmethod");
624 label = new JLabel(trans.get("simedtdlg.lbl.Calcmethod"));
625 label.setToolTipText(tip);
626 subsub.add(label, "gapright para");
628 //// Extended Barrowman
629 label = new JLabel(trans.get("simedtdlg.lbl.ExtBarrowman"));
630 label.setToolTipText(tip);
631 subsub.add(label, "growx, wrap para");
635 tip = trans.get("simedtdlg.lbl.ttip.Simmethod1") +
636 trans.get("simedtdlg.lbl.ttip.Simmethod2");
637 label = new JLabel(trans.get("simedtdlg.lbl.Simmethod"));
638 label.setToolTipText(tip);
639 subsub.add(label, "gapright para");
641 label = new JLabel("6-DOF Runge-Kutta 4");
642 label.setToolTipText(tip);
643 subsub.add(label, "growx, wrap para");
646 //// Geodetic calculation method:
647 label = new JLabel(trans.get("simedtdlg.lbl.GeodeticMethod"));
648 label.setToolTipText(trans.get("simedtdlg.lbl.ttip.GeodeticMethodTip"));
649 subsub.add(label, "gapright para");
651 EnumModel<GeodeticComputationStrategy> gcsModel = new EnumModel<GeodeticComputationStrategy>(conditions, "GeodeticComputation");
652 final JComboBox gcsCombo = new JComboBox(gcsModel);
653 ActionListener gcsTTipListener = new ActionListener() {
655 public void actionPerformed(ActionEvent e) {
656 GeodeticComputationStrategy gcs = (GeodeticComputationStrategy) gcsCombo.getSelectedItem();
657 gcsCombo.setToolTipText(gcs.getDescription());
660 gcsCombo.addActionListener(gcsTTipListener);
661 gcsTTipListener.actionPerformed(null);
662 subsub.add(gcsCombo, "growx, wrap para");
664 sub.add(subsub, "spanx, wrap para");
668 label = new JLabel(trans.get("simedtdlg.lbl.Timestep"));
669 tip = trans.get("simedtdlg.lbl.ttip.Timestep1") +
670 trans.get("simedtdlg.lbl.ttip.Timestep2") + " " +
671 UnitGroup.UNITS_TIME_STEP.toStringUnit(RK4SimulationStepper.RECOMMENDED_TIME_STEP) +
673 label.setToolTipText(tip);
676 m = new DoubleModel(conditions, "TimeStep", UnitGroup.UNITS_TIME_STEP, 0, 1);
678 spin = new JSpinner(m.getSpinnerModel());
679 spin.setEditor(new SpinnerEditor(spin));
680 spin.setToolTipText(tip);
681 sub.add(spin, "w 65lp!");
682 //sub.add(spin, "nogrid");
684 unit = new UnitSelector(m);
685 unit.setToolTipText(tip);
686 sub.add(unit, "w 25");
687 //sub.add(unit, "nogrid");
688 slider = new BasicSlider(m.getSliderModel(0, 0.2));
689 slider.setToolTipText(tip);
690 sub.add(slider, "w 75lp, wrap");
691 //sub.add(slider,"wrap");
696 //// Reset to default button
697 JButton button = new JButton(trans.get("simedtdlg.but.resettodefault"));
698 //// Reset the time step to its default value (
699 button.setToolTipText(trans.get("simedtdlg.but.ttip.resettodefault") +
700 UnitGroup.UNITS_SHORT_TIME.toStringUnit(RK4SimulationStepper.RECOMMENDED_TIME_STEP) +
702 button.addActionListener(new ActionListener() {
704 public void actionPerformed(ActionEvent e) {
705 conditions.setTimeStep(RK4SimulationStepper.RECOMMENDED_TIME_STEP);
706 conditions.setGeodeticComputation(GeodeticComputationStrategy.SPHERICAL);
710 sub.add(button, "align left");
715 //// Simulation listeners
716 sub = new JPanel(new MigLayout("fill, gap 0 0"));
717 //// Simulator listeners
718 sub.setBorder(BorderFactory.createTitledBorder(trans.get("simedtdlg.border.Simlist")));
719 panel.add(sub, "growx, growy");
722 DescriptionArea desc = new DescriptionArea(5);
723 //// <html><i>Simulation listeners</i> is an advanced feature that allows user-written code to listen to and interact with the simulation.
724 //// For details on writing simulation listeners, see the OpenRocket technical documentation.
725 desc.setText(trans.get("simedtdlg.txt.longA1") +
726 trans.get("simedtdlg.txt.longA2"));
727 sub.add(desc, "aligny 0, growx, wrap para");
729 //// Current listeners:
730 label = new JLabel(trans.get("simedtdlg.lbl.Curlist"));
731 sub.add(label, "spanx, wrap rel");
733 final ListenerListModel listenerModel = new ListenerListModel();
734 final JList list = new JList(listenerModel);
735 list.setCellRenderer(new ListenerCellRenderer());
736 JScrollPane scroll = new JScrollPane(list);
737 // scroll.setPreferredSize(new Dimension(1,1));
738 sub.add(scroll, "height 1px, grow, wrap rel");
741 button = new JButton(trans.get("simedtdlg.but.add"));
742 button.addActionListener(new ActionListener() {
744 public void actionPerformed(ActionEvent e) {
745 String previous = Application.getPreferences().getString("previousListenerName", "");
746 String input = (String) JOptionPane.showInputDialog(SimulationEditDialog.this,
748 //// Type the full Java class name of the simulation listener, for example:
749 "Type the full Java class name of the simulation listener, for example:",
750 "<html><tt>" + CSVSaveListener.class.getName() + "</tt>" },
751 //// Add simulation listener
752 trans.get("simedtdlg.lbl.Addsimlist"),
753 JOptionPane.QUESTION_MESSAGE,
757 if (input == null || input.equals(""))
760 Application.getPreferences().putString("previousListenerName", input);
761 simulation.getSimulationListeners().add(input);
762 listenerModel.fireContentsChanged();
765 sub.add(button, "split 2, sizegroup buttons, alignx 50%, gapright para");
768 button = new JButton(trans.get("simedtdlg.but.remove"));
769 button.addActionListener(new ActionListener() {
771 public void actionPerformed(ActionEvent e) {
772 int[] selected = list.getSelectedIndices();
773 Arrays.sort(selected);
774 for (int i = selected.length - 1; i >= 0; i--) {
775 simulation.getSimulationListeners().remove(selected[i]);
777 listenerModel.fireContentsChanged();
780 sub.add(button, "sizegroup buttons, alignx 50%");
787 private class ListenerListModel extends AbstractListModel {
789 public String getElementAt(int index) {
790 if (index < 0 || index >= getSize())
792 return simulation.getSimulationListeners().get(index);
796 public int getSize() {
797 return simulation.getSimulationListeners().size();
800 public void fireContentsChanged() {
801 super.fireContentsChanged(this, 0, getSize());
809 * A panel for plotting the previously calculated data.
811 private JPanel plotTab() {
813 // Check that data exists
814 if (simulation.getSimulatedData() == null ||
815 simulation.getSimulatedData().getBranchCount() == 0) {
816 return noDataPanel();
819 return new SimulationPlotPanel(simulation);
825 * A panel for exporting the data.
827 private JPanel exportTab() {
828 FlightData data = simulation.getSimulatedData();
830 // Check that data exists
831 if (data == null || data.getBranchCount() == 0 ||
832 data.getBranch(0).getTypes().length == 0) {
833 return noDataPanel();
836 return new SimulationExportPanel(simulation);
841 * Return a panel stating that there is no data available, and that the user
842 * should run the simulation first.
844 public static JPanel noDataPanel() {
845 JPanel panel = new JPanel(new MigLayout("fill"));
848 //// No flight data available.
849 panel.add(new JLabel(trans.get("simedtdlg.lbl.Noflightdata")),
850 "alignx 50%, aligny 100%, wrap para");
851 //// Please run the simulation first.
852 panel.add(new JLabel(trans.get("simedtdlg.lbl.runsimfirst")),
853 "alignx 50%, aligny 0%, wrap");
858 private void performPlot(PlotConfiguration config) {
860 // Fill the auto-selections
861 FlightDataBranch branch = simulation.getSimulatedData().getBranch(0);
862 PlotConfiguration filled = config.fillAutoAxes(branch);
863 List<Axis> axes = filled.getAllAxes();
866 // Create the data series for both axes
867 XYSeriesCollection[] data = new XYSeriesCollection[2];
868 data[0] = new XYSeriesCollection();
869 data[1] = new XYSeriesCollection();
872 // Get the domain axis type
873 final FlightDataType domainType = filled.getDomainAxisType();
874 final Unit domainUnit = filled.getDomainAxisUnit();
875 if (domainType == null) {
876 throw new IllegalArgumentException("Domain axis type not specified.");
878 List<Double> x = branch.get(domainType);
881 // Create the XYSeries objects from the flight data and store into the collections
882 int length = filled.getTypeCount();
883 String[] axisLabel = new String[2];
884 for (int i = 0; i < length; i++) {
886 FlightDataType type = filled.getType(i);
887 Unit unit = filled.getUnit(i);
888 int axis = filled.getAxis(i);
889 String name = getLabel(type, unit);
891 // Store data in provided units
892 List<Double> y = branch.get(type);
893 XYSeries series = new XYSeries(name, false, true);
894 for (int j = 0; j < x.size(); j++) {
895 series.add(domainUnit.toUnit(x.get(j)), unit.toUnit(y.get(j)));
897 data[axis].addSeries(series);
900 if (axisLabel[axis] == null)
901 axisLabel[axis] = type.getName();
903 axisLabel[axis] += "; " + type.getName();
907 // Create the chart using the factory to get all default settings
908 JFreeChart chart = ChartFactory.createXYLineChart(
909 //// Simulated flight
910 trans.get("simedtdlg.chart.Simflight"),
914 PlotOrientation.VERTICAL,
921 // Add the data and formatting to the plot
922 XYPlot plot = chart.getXYPlot();
924 for (int i = 0; i < 2; i++) {
925 // Check whether axis has any data
926 if (data[i].getSeriesCount() > 0) {
927 // Create and set axis
928 double min = axes.get(i).getMinValue();
929 double max = axes.get(i).getMaxValue();
930 NumberAxis axis = new PresetNumberAxis(min, max);
931 axis.setLabel(axisLabel[i]);
932 // axis.setRange(axes.get(i).getMinValue(), axes.get(i).getMaxValue());
933 plot.setRangeAxis(axisno, axis);
935 // Add data and map to the axis
936 plot.setDataset(axisno, data[i]);
937 plot.setRenderer(axisno, new StandardXYItemRenderer());
938 plot.mapDatasetToRangeAxis(axisno, axisno);
943 plot.getDomainAxis().setLabel(getLabel(domainType, domainUnit));
944 plot.addDomainMarker(new ValueMarker(0));
945 plot.addRangeMarker(new ValueMarker(0));
949 //// Simulation results
950 final JDialog dialog = new JDialog(this, trans.get("simedtdlg.dlg.Simres"));
951 dialog.setModalityType(ModalityType.DOCUMENT_MODAL);
953 JPanel panel = new JPanel(new MigLayout("fill"));
956 ChartPanel chartPanel = new ChartPanel(chart,
962 chartPanel.setMouseWheelEnabled(true);
963 chartPanel.setEnforceFileExtensions(true);
964 chartPanel.setInitialDelay(500);
966 chartPanel.setBorder(BorderFactory.createLineBorder(Color.GRAY, 1));
968 panel.add(chartPanel, "grow, wrap 20lp");
971 JButton button = new JButton(trans.get("dlg.but.close"));
972 button.addActionListener(new ActionListener() {
974 public void actionPerformed(ActionEvent e) {
975 dialog.setVisible(false);
978 panel.add(button, "right");
980 dialog.setLocationByPlatform(true);
983 GUIUtil.setDisposableDialogOptions(dialog, button);
985 dialog.setVisible(true);
989 private class PresetNumberAxis extends NumberAxis {
990 private final double min;
991 private final double max;
993 public PresetNumberAxis(double min, double max) {
1000 protected void autoAdjustRange() {
1001 this.setRange(min, max);
1006 private String getLabel(FlightDataType type, Unit unit) {
1007 String name = type.getName();
1008 if (unit != null && !UnitGroup.UNITS_NONE.contains(unit) &&
1009 !UnitGroup.UNITS_COEFFICIENT.contains(unit) && unit.getUnit().length() > 0)
1010 name += " (" + unit.getUnit() + ")";
1016 private class ListenerCellRenderer extends JLabel implements ListCellRenderer {
1019 public Component getListCellRendererComponent(JList list, Object value,
1020 int index, boolean isSelected, boolean cellHasFocus) {
1021 String s = value.toString();
1024 // Attempt instantiating, catch any exceptions
1025 Exception ex = null;
1027 Class<?> c = Class.forName(s);
1028 @SuppressWarnings("unused")
1029 SimulationListener l = (SimulationListener) c.newInstance();
1030 } catch (Exception e) {
1035 setIcon(Icons.SIMULATION_LISTENER_OK);
1036 //// Listener instantiated successfully.
1037 setToolTipText("Listener instantiated successfully.");
1039 setIcon(Icons.SIMULATION_LISTENER_ERROR);
1040 //// <html>Unable to instantiate listener due to exception:<br>
1041 setToolTipText("<html>Unable to instantiate listener due to exception:<br>" +
1046 setBackground(list.getSelectionBackground());
1047 setForeground(list.getSelectionForeground());
1049 setBackground(list.getBackground());
1050 setForeground(list.getForeground());