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.Simulation;
34 import net.sf.openrocket.gui.SpinnerEditor;
35 import net.sf.openrocket.gui.adaptors.BooleanModel;
36 import net.sf.openrocket.gui.adaptors.DoubleModel;
37 import net.sf.openrocket.gui.adaptors.EnumModel;
38 import net.sf.openrocket.gui.adaptors.MotorConfigurationModel;
39 import net.sf.openrocket.gui.components.BasicSlider;
40 import net.sf.openrocket.gui.components.DescriptionArea;
41 import net.sf.openrocket.gui.components.SimulationExportPanel;
42 import net.sf.openrocket.gui.components.UnitSelector;
43 import net.sf.openrocket.gui.plot.Axis;
44 import net.sf.openrocket.gui.customexpression.CustomExpressionPanel;
45 import net.sf.openrocket.gui.plot.PlotConfiguration;
46 import net.sf.openrocket.gui.plot.SimulationPlotPanel;
47 import net.sf.openrocket.gui.util.GUIUtil;
48 import net.sf.openrocket.gui.util.Icons;
49 import net.sf.openrocket.l10n.Translator;
50 import net.sf.openrocket.models.atmosphere.ExtendedISAModel;
51 import net.sf.openrocket.rocketcomponent.Configuration;
52 import net.sf.openrocket.simulation.FlightData;
53 import net.sf.openrocket.simulation.FlightDataBranch;
54 import net.sf.openrocket.simulation.FlightDataType;
55 import net.sf.openrocket.simulation.RK4SimulationStepper;
56 import net.sf.openrocket.simulation.SimulationOptions;
57 import net.sf.openrocket.simulation.listeners.SimulationListener;
58 import net.sf.openrocket.simulation.listeners.example.CSVSaveListener;
59 import net.sf.openrocket.startup.Application;
60 import net.sf.openrocket.unit.Unit;
61 import net.sf.openrocket.unit.UnitGroup;
62 import net.sf.openrocket.util.Chars;
63 import net.sf.openrocket.util.GeodeticComputationStrategy;
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 = 3;
84 private final Window parentWindow;
85 private final Simulation simulation;
86 private final SimulationOptions conditions;
87 private final Configuration configuration;
88 private static final Translator trans = Application.getTranslator();
91 public SimulationEditDialog(Window parent, Simulation s) {
95 public SimulationEditDialog(Window parent, Simulation s, int tab) {
97 super(parent, trans.get("simedtdlg.title.Editsim"), JDialog.ModalityType.DOCUMENT_MODAL);
99 this.parentWindow = parent;
101 this.conditions = simulation.getOptions();
102 configuration = simulation.getConfiguration();
104 JPanel mainPanel = new JPanel(new MigLayout("fill", "[grow, fill]"));
106 //// Simulation name:
107 mainPanel.add(new JLabel(trans.get("simedtdlg.lbl.Simname") + " "), "span, split 2, shrink");
108 final JTextField field = new JTextField(simulation.getName());
109 field.getDocument().addDocumentListener(new DocumentListener() {
111 public void changedUpdate(DocumentEvent e) {
116 public void insertUpdate(DocumentEvent e) {
121 public void removeUpdate(DocumentEvent e) {
125 private void setText() {
126 String name = field.getText();
127 if (name == null || name.equals(""))
129 //System.out.println("Setting name:" + name);
130 simulation.setName(name);
134 mainPanel.add(field, "shrinky, growx, wrap");
136 JTabbedPane tabbedPane = new JTabbedPane();
138 //// Launch conditions
139 tabbedPane.addTab(trans.get("simedtdlg.tab.Launchcond"), flightConditionsTab());
140 //// Simulation options
141 tabbedPane.addTab(trans.get("simedtdlg.tab.Simopt"), simulationOptionsTab());
142 //// Custom expressions tab
143 tabbedPane.addTab(trans.get("simedtdlg.tab.CustomExpressions"), customExpressionsTab());
145 tabbedPane.addTab(trans.get("simedtdlg.tab.Plotdata"), plotTab());
147 tabbedPane.addTab(trans.get("simedtdlg.tab.Exportdata"), exportTab());
149 // Select the initial tab
151 tabbedPane.setSelectedIndex(0);
152 } else if (tab == PLOT) {
153 tabbedPane.setSelectedIndex(3);
155 FlightData data = s.getSimulatedData();
156 if (data == null || data.getBranchCount() == 0)
157 tabbedPane.setSelectedIndex(0);
159 tabbedPane.setSelectedIndex(2);
162 mainPanel.add(tabbedPane, "spanx, grow, wrap");
166 mainPanel.add(new JPanel(), "spanx, split, growx");
169 //// Run simulation button
170 button = new JButton(trans.get("simedtdlg.but.runsimulation"));
171 button.addActionListener(new ActionListener() {
173 public void actionPerformed(ActionEvent e) {
174 SimulationEditDialog.this.dispose();
175 SimulationRunDialog.runSimulations(parentWindow, simulation);
178 mainPanel.add(button, "gapright para");
181 JButton close = new JButton(trans.get("dlg.but.close"));
182 close.addActionListener(new ActionListener() {
184 public void actionPerformed(ActionEvent e) {
185 SimulationEditDialog.this.dispose();
188 mainPanel.add(close, "");
194 this.setLocationByPlatform(true);
196 GUIUtil.setDisposableDialogOptions(this, button);
203 private JPanel flightConditionsTab() {
204 JPanel panel = new JPanel(new MigLayout("fill"));
213 //// Motor configuration:
214 JLabel label = new JLabel(trans.get("simedtdlg.lbl.Motorcfg"));
215 //// Select the motor configuration to use.
216 label.setToolTipText(trans.get("simedtdlg.lbl.ttip.Motorcfg"));
217 panel.add(label, "shrinkx, spanx, split 2");
219 JComboBox combo = new JComboBox(new MotorConfigurationModel(configuration));
220 //// Select the motor configuration to use.
221 combo.setToolTipText(trans.get("simedtdlg.combo.ttip.motorconf"));
222 combo.addActionListener(new ActionListener() {
224 public void actionPerformed(ActionEvent e) {
225 conditions.setMotorConfigurationID(configuration.getMotorConfigurationID());
228 panel.add(combo, "growx, wrap para");
231 //// Wind settings: Average wind speed, turbulence intensity, std. deviation
232 sub = new JPanel(new MigLayout("fill, gap rel unrel",
233 "[grow][65lp!][30lp!][75lp!]", ""));
235 sub.setBorder(BorderFactory.createTitledBorder(trans.get("simedtdlg.lbl.Wind")));
236 panel.add(sub, "growx, split 2, aligny 0, flowy, gapright para");
240 //// Average windspeed:
241 label = new JLabel(trans.get("simedtdlg.lbl.Averwindspeed"));
242 //// The average windspeed relative to the ground.
243 tip = trans.get("simedtdlg.lbl.ttip.Averwindspeed");
244 label.setToolTipText(tip);
247 m = new DoubleModel(conditions, "WindSpeedAverage", UnitGroup.UNITS_WINDSPEED, 0);
249 spin = new JSpinner(m.getSpinnerModel());
250 spin.setEditor(new SpinnerEditor(spin));
251 spin.setToolTipText(tip);
252 sub.add(spin, "w 65lp!");
254 unit = new UnitSelector(m);
255 unit.setToolTipText(tip);
256 sub.add(unit, "growx");
257 slider = new BasicSlider(m.getSliderModel(0, 10.0));
258 slider.setToolTipText(tip);
259 sub.add(slider, "w 75lp, wrap");
263 // Wind std. deviation
264 //// Standard deviation:
265 label = new JLabel(trans.get("simedtdlg.lbl.Stddeviation"));
266 //// <html>The standard deviation of the windspeed.<br>
267 //// The windspeed is within twice the standard deviation from the average for 95% of the time.
268 tip = trans.get("simedtdlg.lbl.ttip.Stddeviation");
269 label.setToolTipText(tip);
272 m = new DoubleModel(conditions, "WindSpeedDeviation", UnitGroup.UNITS_WINDSPEED, 0);
273 DoubleModel m2 = new DoubleModel(conditions, "WindSpeedAverage", 0.25,
274 UnitGroup.UNITS_COEFFICIENT, 0);
276 spin = new JSpinner(m.getSpinnerModel());
277 spin.setEditor(new SpinnerEditor(spin));
278 spin.setToolTipText(tip);
279 sub.add(spin, "w 65lp!");
281 unit = new UnitSelector(m);
282 unit.setToolTipText(tip);
283 sub.add(unit, "growx");
284 slider = new BasicSlider(m.getSliderModel(new DoubleModel(0), m2));
285 slider.setToolTipText(tip);
286 sub.add(slider, "w 75lp, wrap");
289 // Wind turbulence intensity
290 //// Turbulence intensity:
291 label = new JLabel(trans.get("simedtdlg.lbl.Turbulenceintensity"));
292 //// <html>The turbulence intensity is the standard deviation divided by the average windspeed.<br>
293 //// Typical values range from
295 tip = trans.get("simedtdlg.lbl.ttip.Turbulenceintensity1") +
296 trans.get("simedtdlg.lbl.ttip.Turbulenceintensity2") + " " +
297 UnitGroup.UNITS_RELATIVE.getDefaultUnit().toStringUnit(0.05) +
298 " " + trans.get("simedtdlg.lbl.ttip.Turbulenceintensity3") + " " +
299 UnitGroup.UNITS_RELATIVE.getDefaultUnit().toStringUnit(0.20) + ".";
300 label.setToolTipText(tip);
303 m = new DoubleModel(conditions, "WindTurbulenceIntensity", UnitGroup.UNITS_RELATIVE, 0);
305 spin = new JSpinner(m.getSpinnerModel());
306 spin.setEditor(new SpinnerEditor(spin));
307 spin.setToolTipText(tip);
308 sub.add(spin, "w 65lp!");
310 unit = new UnitSelector(m);
311 unit.setToolTipText(tip);
312 sub.add(unit, "growx");
314 final JLabel intensityLabel = new JLabel(
315 getIntensityDescription(conditions.getWindTurbulenceIntensity()));
316 intensityLabel.setToolTipText(tip);
317 sub.add(intensityLabel, "w 75lp, wrap");
318 m.addChangeListener(new ChangeListener() {
320 public void stateChanged(ChangeEvent e) {
321 intensityLabel.setText(
322 getIntensityDescription(conditions.getWindTurbulenceIntensity()));
330 //// Temperature and pressure
331 sub = new JPanel(new MigLayout("fill, gap rel unrel",
332 "[grow][65lp!][30lp!][75lp!]", ""));
333 //// Atmospheric conditions
334 sub.setBorder(BorderFactory.createTitledBorder(trans.get("simedtdlg.border.Atmoscond")));
335 panel.add(sub, "growx, aligny 0, gapright para");
338 BooleanModel isa = new BooleanModel(conditions, "ISAAtmosphere");
339 JCheckBox check = new JCheckBox(isa);
340 //// Use International Standard Atmosphere
341 check.setText(trans.get("simedtdlg.checkbox.InterStdAtmosphere"));
342 //// <html>Select to use the International Standard Atmosphere model.
343 //// <br>This model has a temperature of
344 //// and a pressure of
346 check.setToolTipText(trans.get("simedtdlg.checkbox.ttip.InterStdAtmosphere1") + " " +
347 UnitGroup.UNITS_TEMPERATURE.toStringUnit(ExtendedISAModel.STANDARD_TEMPERATURE) +
348 " " + trans.get("simedtdlg.checkbox.ttip.InterStdAtmosphere2") + " " +
349 UnitGroup.UNITS_PRESSURE.toStringUnit(ExtendedISAModel.STANDARD_PRESSURE) +
350 " " + trans.get("simedtdlg.checkbox.ttip.InterStdAtmosphere3"));
351 sub.add(check, "spanx, wrap unrel");
354 label = new JLabel(trans.get("simedtdlg.lbl.Temperature"));
355 //// The temperature at the launch site.
356 tip = trans.get("simedtdlg.lbl.ttip.Temperature");
357 label.setToolTipText(tip);
358 isa.addEnableComponent(label, false);
361 m = new DoubleModel(conditions, "LaunchTemperature", UnitGroup.UNITS_TEMPERATURE, 0);
363 spin = new JSpinner(m.getSpinnerModel());
364 spin.setEditor(new SpinnerEditor(spin));
365 spin.setToolTipText(tip);
366 isa.addEnableComponent(spin, false);
367 sub.add(spin, "w 65lp!");
369 unit = new UnitSelector(m);
370 unit.setToolTipText(tip);
371 isa.addEnableComponent(unit, false);
372 sub.add(unit, "growx");
373 slider = new BasicSlider(m.getSliderModel(253.15, 308.15)); // -20 ... 35
374 slider.setToolTipText(tip);
375 isa.addEnableComponent(slider, false);
376 sub.add(slider, "w 75lp, wrap");
381 label = new JLabel(trans.get("simedtdlg.lbl.Pressure"));
382 //// The atmospheric pressure at the launch site.
383 tip = trans.get("simedtdlg.lbl.ttip.Pressure");
384 label.setToolTipText(tip);
385 isa.addEnableComponent(label, false);
388 m = new DoubleModel(conditions, "LaunchPressure", UnitGroup.UNITS_PRESSURE, 0);
390 spin = new JSpinner(m.getSpinnerModel());
391 spin.setEditor(new SpinnerEditor(spin));
392 spin.setToolTipText(tip);
393 isa.addEnableComponent(spin, false);
394 sub.add(spin, "w 65lp!");
396 unit = new UnitSelector(m);
397 unit.setToolTipText(tip);
398 isa.addEnableComponent(unit, false);
399 sub.add(unit, "growx");
400 slider = new BasicSlider(m.getSliderModel(0.950e5, 1.050e5));
401 slider.setToolTipText(tip);
402 isa.addEnableComponent(slider, false);
403 sub.add(slider, "w 75lp, wrap");
409 //// Launch site conditions
410 sub = new JPanel(new MigLayout("fill, gap rel unrel",
411 "[grow][65lp!][30lp!][75lp!]", ""));
413 sub.setBorder(BorderFactory.createTitledBorder(trans.get("simedtdlg.lbl.Launchsite")));
414 panel.add(sub, "growx, split 2, aligny 0, flowy");
418 label = new JLabel(trans.get("simedtdlg.lbl.Latitude"));
419 //// <html>The launch site latitude affects the gravitational pull of Earth.<br>
420 //// Positive values are on the Northern hemisphere, negative values on the Southern hemisphere.
421 tip = trans.get("simedtdlg.lbl.ttip.Latitude");
422 label.setToolTipText(tip);
425 m = new DoubleModel(conditions, "LaunchLatitude", UnitGroup.UNITS_NONE, -90, 90);
427 spin = new JSpinner(m.getSpinnerModel());
428 spin.setEditor(new SpinnerEditor(spin));
429 spin.setToolTipText(tip);
430 sub.add(spin, "w 65lp!");
432 label = new JLabel(Chars.DEGREE + " N");
433 label.setToolTipText(tip);
434 sub.add(label, "growx");
435 slider = new BasicSlider(m.getSliderModel(-90, 90));
436 slider.setToolTipText(tip);
437 sub.add(slider, "w 75lp, wrap");
441 label = new JLabel(trans.get("simedtdlg.lbl.Longitude"));
442 tip = trans.get("simedtdlg.lbl.ttip.Longitude");
443 label.setToolTipText(tip);
446 m = new DoubleModel(conditions, "LaunchLongitude", UnitGroup.UNITS_NONE, -180, 180);
448 spin = new JSpinner(m.getSpinnerModel());
449 spin.setEditor(new SpinnerEditor(spin));
450 spin.setToolTipText(tip);
451 sub.add(spin, "w 65lp!");
453 label = new JLabel(Chars.DEGREE + " E");
454 label.setToolTipText(tip);
455 sub.add(label, "growx");
456 slider = new BasicSlider(m.getSliderModel(-180, 180));
457 slider.setToolTipText(tip);
458 sub.add(slider, "w 75lp, wrap");
462 label = new JLabel(trans.get("simedtdlg.lbl.Altitude"));
463 //// <html>The launch altitude above mean sea level.<br>
464 //// This affects the position of the rocket in the atmospheric model.
465 tip = trans.get("simedtdlg.lbl.ttip.Altitude");
466 label.setToolTipText(tip);
469 m = new DoubleModel(conditions, "LaunchAltitude", UnitGroup.UNITS_DISTANCE, 0);
471 spin = new JSpinner(m.getSpinnerModel());
472 spin.setEditor(new SpinnerEditor(spin));
473 spin.setToolTipText(tip);
474 sub.add(spin, "w 65lp!");
476 unit = new UnitSelector(m);
477 unit.setToolTipText(tip);
478 sub.add(unit, "growx");
479 slider = new BasicSlider(m.getSliderModel(0, 250, 1000));
480 slider.setToolTipText(tip);
481 sub.add(slider, "w 75lp, wrap");
488 sub = new JPanel(new MigLayout("fill, gap rel unrel",
489 "[grow][65lp!][30lp!][75lp!]", ""));
491 sub.setBorder(BorderFactory.createTitledBorder(trans.get("simedtdlg.border.Launchrod")));
492 panel.add(sub, "growx, aligny 0, wrap");
496 label = new JLabel(trans.get("simedtdlg.lbl.Length"));
497 //// The length of the launch rod.
498 tip = trans.get("simedtdlg.lbl.ttip.Length");
499 label.setToolTipText(tip);
502 m = new DoubleModel(conditions, "LaunchRodLength", UnitGroup.UNITS_LENGTH, 0);
504 spin = new JSpinner(m.getSpinnerModel());
505 spin.setEditor(new SpinnerEditor(spin));
506 spin.setToolTipText(tip);
507 sub.add(spin, "w 65lp!");
509 unit = new UnitSelector(m);
510 unit.setToolTipText(tip);
511 sub.add(unit, "growx");
512 slider = new BasicSlider(m.getSliderModel(0, 1, 5));
513 slider.setToolTipText(tip);
514 sub.add(slider, "w 75lp, wrap");
519 label = new JLabel(trans.get("simedtdlg.lbl.Angle"));
520 //// The angle of the launch rod from vertical.
521 tip = trans.get("simedtdlg.lbl.ttip.Angle");
522 label.setToolTipText(tip);
525 m = new DoubleModel(conditions, "LaunchRodAngle", UnitGroup.UNITS_ANGLE,
526 0, SimulationOptions.MAX_LAUNCH_ROD_ANGLE);
528 spin = new JSpinner(m.getSpinnerModel());
529 spin.setEditor(new SpinnerEditor(spin));
530 spin.setToolTipText(tip);
531 sub.add(spin, "w 65lp!");
533 unit = new UnitSelector(m);
534 unit.setToolTipText(tip);
535 sub.add(unit, "growx");
536 slider = new BasicSlider(m.getSliderModel(0, Math.PI / 9,
537 SimulationOptions.MAX_LAUNCH_ROD_ANGLE));
538 slider.setToolTipText(tip);
539 sub.add(slider, "w 75lp, wrap");
544 label = new JLabel(trans.get("simedtdlg.lbl.Direction"));
545 //// <html>Direction of the launch rod relative to the wind.<br>
546 //// = towards the wind,
548 tip = trans.get("simedtdlg.lbl.ttip.Direction1") +
549 UnitGroup.UNITS_ANGLE.toStringUnit(0) +
550 " " + trans.get("simedtdlg.lbl.ttip.Direction2") + " " +
551 UnitGroup.UNITS_ANGLE.toStringUnit(Math.PI) +
552 " " + trans.get("simedtdlg.lbl.ttip.Direction3");
553 label.setToolTipText(tip);
556 m = new DoubleModel(conditions, "LaunchRodDirection", UnitGroup.UNITS_ANGLE,
559 spin = new JSpinner(m.getSpinnerModel());
560 spin.setEditor(new SpinnerEditor(spin));
561 spin.setToolTipText(tip);
562 sub.add(spin, "w 65lp!");
564 unit = new UnitSelector(m);
565 unit.setToolTipText(tip);
566 sub.add(unit, "growx");
567 slider = new BasicSlider(m.getSliderModel(-Math.PI, Math.PI));
568 slider.setToolTipText(tip);
569 sub.add(slider, "w 75lp, wrap");
575 private String getIntensityDescription(double i) {
578 return trans.get("simedtdlg.IntensityDesc.None");
581 return trans.get("simedtdlg.IntensityDesc.Verylow");
584 return trans.get("simedtdlg.IntensityDesc.Low");
587 return trans.get("simedtdlg.IntensityDesc.Medium");
590 return trans.get("simedtdlg.IntensityDesc.High");
593 return trans.get("simedtdlg.IntensityDesc.Veryhigh");
595 return trans.get("simedtdlg.IntensityDesc.Extreme");
600 private JPanel simulationOptionsTab() {
601 JPanel panel = new JPanel(new MigLayout("fill"));
611 //// Simulation options
612 sub = new JPanel(new MigLayout("fill, gap rel unrel",
613 "[grow][65lp!][30lp!][75lp!]", ""));
614 //// Simulator options
615 sub.setBorder(BorderFactory.createTitledBorder(trans.get("simedtdlg.border.Simopt")));
616 panel.add(sub, "growx, growy, aligny 0");
619 // Separate panel for computation methods, as they use a different layout
620 subsub = new JPanel(new MigLayout("insets 0, fill"));
623 //// Calculation method:
624 tip = trans.get("simedtdlg.lbl.ttip.Calcmethod");
625 label = new JLabel(trans.get("simedtdlg.lbl.Calcmethod"));
626 label.setToolTipText(tip);
627 subsub.add(label, "gapright para");
629 //// Extended Barrowman
630 label = new JLabel(trans.get("simedtdlg.lbl.ExtBarrowman"));
631 label.setToolTipText(tip);
632 subsub.add(label, "growx, wrap para");
636 tip = trans.get("simedtdlg.lbl.ttip.Simmethod1") +
637 trans.get("simedtdlg.lbl.ttip.Simmethod2");
638 label = new JLabel(trans.get("simedtdlg.lbl.Simmethod"));
639 label.setToolTipText(tip);
640 subsub.add(label, "gapright para");
642 label = new JLabel("6-DOF Runge-Kutta 4");
643 label.setToolTipText(tip);
644 subsub.add(label, "growx, wrap para");
647 //// Geodetic calculation method:
648 label = new JLabel(trans.get("simedtdlg.lbl.GeodeticMethod"));
649 label.setToolTipText(trans.get("simedtdlg.lbl.ttip.GeodeticMethodTip"));
650 subsub.add(label, "gapright para");
652 EnumModel<GeodeticComputationStrategy> gcsModel = new EnumModel<GeodeticComputationStrategy>(conditions, "GeodeticComputation");
653 final JComboBox gcsCombo = new JComboBox(gcsModel);
654 ActionListener gcsTTipListener = new ActionListener() {
656 public void actionPerformed(ActionEvent e) {
657 GeodeticComputationStrategy gcs = (GeodeticComputationStrategy) gcsCombo.getSelectedItem();
658 gcsCombo.setToolTipText(gcs.getDescription());
661 gcsCombo.addActionListener(gcsTTipListener);
662 gcsTTipListener.actionPerformed(null);
663 subsub.add(gcsCombo, "growx, wrap para");
665 sub.add(subsub, "spanx, wrap para");
669 label = new JLabel(trans.get("simedtdlg.lbl.Timestep"));
670 tip = trans.get("simedtdlg.lbl.ttip.Timestep1") +
671 trans.get("simedtdlg.lbl.ttip.Timestep2") + " " +
672 UnitGroup.UNITS_TIME_STEP.toStringUnit(RK4SimulationStepper.RECOMMENDED_TIME_STEP) +
674 label.setToolTipText(tip);
677 m = new DoubleModel(conditions, "TimeStep", UnitGroup.UNITS_TIME_STEP, 0, 1);
679 spin = new JSpinner(m.getSpinnerModel());
680 spin.setEditor(new SpinnerEditor(spin));
681 spin.setToolTipText(tip);
682 sub.add(spin, "w 65lp!");
683 //sub.add(spin, "nogrid");
685 unit = new UnitSelector(m);
686 unit.setToolTipText(tip);
687 sub.add(unit, "w 25");
688 //sub.add(unit, "nogrid");
689 slider = new BasicSlider(m.getSliderModel(0, 0.2));
690 slider.setToolTipText(tip);
691 sub.add(slider, "w 75lp, wrap");
692 //sub.add(slider,"wrap");
697 //// Reset to default button
698 JButton button = new JButton(trans.get("simedtdlg.but.resettodefault"));
699 //// Reset the time step to its default value (
700 button.setToolTipText(trans.get("simedtdlg.but.ttip.resettodefault") +
701 UnitGroup.UNITS_SHORT_TIME.toStringUnit(RK4SimulationStepper.RECOMMENDED_TIME_STEP) +
703 button.addActionListener(new ActionListener() {
705 public void actionPerformed(ActionEvent e) {
706 conditions.setTimeStep(RK4SimulationStepper.RECOMMENDED_TIME_STEP);
707 conditions.setGeodeticComputation(GeodeticComputationStrategy.SPHERICAL);
711 sub.add(button, "align left");
716 //// Simulation listeners
717 sub = new JPanel(new MigLayout("fill, gap 0 0"));
718 //// Simulator listeners
719 sub.setBorder(BorderFactory.createTitledBorder(trans.get("simedtdlg.border.Simlist")));
720 panel.add(sub, "growx, growy");
723 DescriptionArea desc = new DescriptionArea(5);
724 //// <html><i>Simulation listeners</i> is an advanced feature that allows user-written code to listen to and interact with the simulation.
725 //// For details on writing simulation listeners, see the OpenRocket technical documentation.
726 desc.setText(trans.get("simedtdlg.txt.longA1") +
727 trans.get("simedtdlg.txt.longA2"));
728 sub.add(desc, "aligny 0, growx, wrap para");
730 //// Current listeners:
731 label = new JLabel(trans.get("simedtdlg.lbl.Curlist"));
732 sub.add(label, "spanx, wrap rel");
734 final ListenerListModel listenerModel = new ListenerListModel();
735 final JList list = new JList(listenerModel);
736 list.setCellRenderer(new ListenerCellRenderer());
737 JScrollPane scroll = new JScrollPane(list);
738 // scroll.setPreferredSize(new Dimension(1,1));
739 sub.add(scroll, "height 1px, grow, wrap rel");
742 button = new JButton(trans.get("simedtdlg.but.add"));
743 button.addActionListener(new ActionListener() {
745 public void actionPerformed(ActionEvent e) {
746 String previous = Application.getPreferences().getString("previousListenerName", "");
747 String input = (String) JOptionPane.showInputDialog(SimulationEditDialog.this,
749 //// Type the full Java class name of the simulation listener, for example:
750 "Type the full Java class name of the simulation listener, for example:",
751 "<html><tt>" + CSVSaveListener.class.getName() + "</tt>" },
752 //// Add simulation listener
753 trans.get("simedtdlg.lbl.Addsimlist"),
754 JOptionPane.QUESTION_MESSAGE,
758 if (input == null || input.equals(""))
761 Application.getPreferences().putString("previousListenerName", input);
762 simulation.getSimulationListeners().add(input);
763 listenerModel.fireContentsChanged();
766 sub.add(button, "split 2, sizegroup buttons, alignx 50%, gapright para");
769 button = new JButton(trans.get("simedtdlg.but.remove"));
770 button.addActionListener(new ActionListener() {
772 public void actionPerformed(ActionEvent e) {
773 int[] selected = list.getSelectedIndices();
774 Arrays.sort(selected);
775 for (int i = selected.length - 1; i >= 0; i--) {
776 simulation.getSimulationListeners().remove(selected[i]);
778 listenerModel.fireContentsChanged();
781 sub.add(button, "sizegroup buttons, alignx 50%");
788 private class ListenerListModel extends AbstractListModel {
790 public String getElementAt(int index) {
791 if (index < 0 || index >= getSize())
793 return simulation.getSimulationListeners().get(index);
797 public int getSize() {
798 return simulation.getSimulationListeners().size();
801 public void fireContentsChanged() {
802 super.fireContentsChanged(this, 0, getSize());
810 * A panel for plotting the previously calculated data.
812 private JPanel plotTab() {
814 // Check that data exists
815 if (simulation.getSimulatedData() == null ||
816 simulation.getSimulatedData().getBranchCount() == 0) {
817 return noDataPanel();
820 return new SimulationPlotPanel(simulation);
826 * A panel for exporting the data.
828 private JPanel exportTab() {
829 FlightData data = simulation.getSimulatedData();
831 // Check that data exists
832 if (data == null || data.getBranchCount() == 0 ||
833 data.getBranch(0).getTypes().length == 0) {
834 return noDataPanel();
837 return new SimulationExportPanel(simulation);
841 private JPanel customExpressionsTab() {
842 return new CustomExpressionPanel(simulation);
847 * Return a panel stating that there is no data available, and that the user
848 * should run the simulation first.
850 public static JPanel noDataPanel() {
851 JPanel panel = new JPanel(new MigLayout("fill"));
854 //// No flight data available.
855 panel.add(new JLabel(trans.get("simedtdlg.lbl.Noflightdata")),
856 "alignx 50%, aligny 100%, wrap para");
857 //// Please run the simulation first.
858 panel.add(new JLabel(trans.get("simedtdlg.lbl.runsimfirst")),
859 "alignx 50%, aligny 0%, wrap");
864 private void performPlot(PlotConfiguration config) {
866 // Fill the auto-selections
867 FlightDataBranch branch = simulation.getSimulatedData().getBranch(0);
868 PlotConfiguration filled = config.fillAutoAxes(branch);
869 List<Axis> axes = filled.getAllAxes();
872 // Create the data series for both axes
873 XYSeriesCollection[] data = new XYSeriesCollection[2];
874 data[0] = new XYSeriesCollection();
875 data[1] = new XYSeriesCollection();
878 // Get the domain axis type
879 final FlightDataType domainType = filled.getDomainAxisType();
880 final Unit domainUnit = filled.getDomainAxisUnit();
881 if (domainType == null) {
882 throw new IllegalArgumentException("Domain axis type not specified.");
884 List<Double> x = branch.get(domainType);
887 // Create the XYSeries objects from the flight data and store into the collections
888 int length = filled.getTypeCount();
889 String[] axisLabel = new String[2];
890 for (int i = 0; i < length; i++) {
892 FlightDataType type = filled.getType(i);
893 Unit unit = filled.getUnit(i);
894 int axis = filled.getAxis(i);
895 String name = getLabel(type, unit);
897 // Store data in provided units
898 List<Double> y = branch.get(type);
899 XYSeries series = new XYSeries(name, false, true);
900 for (int j = 0; j < x.size(); j++) {
901 series.add(domainUnit.toUnit(x.get(j)), unit.toUnit(y.get(j)));
903 data[axis].addSeries(series);
906 if (axisLabel[axis] == null)
907 axisLabel[axis] = type.getName();
909 axisLabel[axis] += "; " + type.getName();
913 // Create the chart using the factory to get all default settings
914 JFreeChart chart = ChartFactory.createXYLineChart(
915 //// Simulated flight
916 trans.get("simedtdlg.chart.Simflight"),
920 PlotOrientation.VERTICAL,
927 // Add the data and formatting to the plot
928 XYPlot plot = chart.getXYPlot();
930 for (int i = 0; i < 2; i++) {
931 // Check whether axis has any data
932 if (data[i].getSeriesCount() > 0) {
933 // Create and set axis
934 double min = axes.get(i).getMinValue();
935 double max = axes.get(i).getMaxValue();
936 NumberAxis axis = new PresetNumberAxis(min, max);
937 axis.setLabel(axisLabel[i]);
938 // axis.setRange(axes.get(i).getMinValue(), axes.get(i).getMaxValue());
939 plot.setRangeAxis(axisno, axis);
941 // Add data and map to the axis
942 plot.setDataset(axisno, data[i]);
943 plot.setRenderer(axisno, new StandardXYItemRenderer());
944 plot.mapDatasetToRangeAxis(axisno, axisno);
949 plot.getDomainAxis().setLabel(getLabel(domainType, domainUnit));
950 plot.addDomainMarker(new ValueMarker(0));
951 plot.addRangeMarker(new ValueMarker(0));
955 //// Simulation results
956 final JDialog dialog = new JDialog(this, trans.get("simedtdlg.dlg.Simres"));
957 dialog.setModalityType(ModalityType.DOCUMENT_MODAL);
959 JPanel panel = new JPanel(new MigLayout("fill"));
962 ChartPanel chartPanel = new ChartPanel(chart,
968 chartPanel.setMouseWheelEnabled(true);
969 chartPanel.setEnforceFileExtensions(true);
970 chartPanel.setInitialDelay(500);
972 chartPanel.setBorder(BorderFactory.createLineBorder(Color.GRAY, 1));
974 panel.add(chartPanel, "grow, wrap 20lp");
977 JButton button = new JButton(trans.get("dlg.but.close"));
978 button.addActionListener(new ActionListener() {
980 public void actionPerformed(ActionEvent e) {
981 dialog.setVisible(false);
984 panel.add(button, "right");
986 dialog.setLocationByPlatform(true);
989 GUIUtil.setDisposableDialogOptions(dialog, button);
991 dialog.setVisible(true);
995 private class PresetNumberAxis extends NumberAxis {
996 private final double min;
997 private final double max;
999 public PresetNumberAxis(double min, double max) {
1006 protected void autoAdjustRange() {
1007 this.setRange(min, max);
1012 private String getLabel(FlightDataType type, Unit unit) {
1013 String name = type.getName();
1014 if (unit != null && !UnitGroup.UNITS_NONE.contains(unit) &&
1015 !UnitGroup.UNITS_COEFFICIENT.contains(unit) && unit.getUnit().length() > 0)
1016 name += " (" + unit.getUnit() + ")";
1022 private class ListenerCellRenderer extends JLabel implements ListCellRenderer {
1025 public Component getListCellRendererComponent(JList list, Object value,
1026 int index, boolean isSelected, boolean cellHasFocus) {
1027 String s = value.toString();
1030 // Attempt instantiating, catch any exceptions
1031 Exception ex = null;
1033 Class<?> c = Class.forName(s);
1034 @SuppressWarnings("unused")
1035 SimulationListener l = (SimulationListener) c.newInstance();
1036 } catch (Exception e) {
1041 setIcon(Icons.SIMULATION_LISTENER_OK);
1042 //// Listener instantiated successfully.
1043 setToolTipText("Listener instantiated successfully.");
1045 setIcon(Icons.SIMULATION_LISTENER_ERROR);
1046 //// <html>Unable to instantiate listener due to exception:<br>
1047 setToolTipText("<html>Unable to instantiate listener due to exception:<br>" +
1052 setBackground(list.getSelectionBackground());
1053 setForeground(list.getSelectionForeground());
1055 setBackground(list.getBackground());
1056 setForeground(list.getForeground());