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.plot.PlotConfiguration;
45 import net.sf.openrocket.gui.plot.SimulationPlotPanel;
46 import net.sf.openrocket.gui.util.GUIUtil;
47 import net.sf.openrocket.gui.util.Icons;
48 import net.sf.openrocket.l10n.Translator;
49 import net.sf.openrocket.models.atmosphere.ExtendedISAModel;
50 import net.sf.openrocket.rocketcomponent.Configuration;
51 import net.sf.openrocket.simulation.FlightData;
52 import net.sf.openrocket.simulation.FlightDataBranch;
53 import net.sf.openrocket.simulation.FlightDataType;
54 import net.sf.openrocket.simulation.RK4SimulationStepper;
55 import net.sf.openrocket.simulation.SimulationOptions;
56 import net.sf.openrocket.simulation.listeners.SimulationListener;
57 import net.sf.openrocket.simulation.listeners.example.CSVSaveListener;
58 import net.sf.openrocket.startup.Application;
59 import net.sf.openrocket.unit.Unit;
60 import net.sf.openrocket.unit.UnitGroup;
61 import net.sf.openrocket.util.Chars;
62 import net.sf.openrocket.util.GeodeticComputationStrategy;
64 import org.jfree.chart.ChartFactory;
65 import org.jfree.chart.ChartPanel;
66 import org.jfree.chart.JFreeChart;
67 import org.jfree.chart.axis.NumberAxis;
68 import org.jfree.chart.plot.PlotOrientation;
69 import org.jfree.chart.plot.ValueMarker;
70 import org.jfree.chart.plot.XYPlot;
71 import org.jfree.chart.renderer.xy.StandardXYItemRenderer;
72 import org.jfree.data.xy.XYSeries;
73 import org.jfree.data.xy.XYSeriesCollection;
76 public class SimulationEditDialog extends JDialog {
78 public static final int DEFAULT = -1;
79 public static final int EDIT = 1;
80 public static final int PLOT = 2;
83 private final Window parentWindow;
84 private final Simulation simulation;
85 private final SimulationOptions conditions;
86 private final Configuration configuration;
87 private static final Translator trans = Application.getTranslator();
90 public SimulationEditDialog(Window parent, Simulation s) {
94 public SimulationEditDialog(Window parent, Simulation s, int tab) {
96 super(parent, trans.get("simedtdlg.title.Editsim"), JDialog.ModalityType.DOCUMENT_MODAL);
98 this.parentWindow = parent;
100 this.conditions = simulation.getOptions();
101 configuration = simulation.getConfiguration();
103 JPanel mainPanel = new JPanel(new MigLayout("fill", "[grow, fill]"));
105 //// Simulation name:
106 mainPanel.add(new JLabel(trans.get("simedtdlg.lbl.Simname") + " "), "span, split 2, shrink");
107 final JTextField field = new JTextField(simulation.getName());
108 field.getDocument().addDocumentListener(new DocumentListener() {
110 public void changedUpdate(DocumentEvent e) {
115 public void insertUpdate(DocumentEvent e) {
120 public void removeUpdate(DocumentEvent e) {
124 private void setText() {
125 String name = field.getText();
126 if (name == null || name.equals(""))
128 System.out.println("Setting name:" + name);
129 simulation.setName(name);
133 mainPanel.add(field, "shrinky, growx, wrap");
135 JTabbedPane tabbedPane = new JTabbedPane();
137 //// Launch conditions
138 tabbedPane.addTab(trans.get("simedtdlg.tab.Launchcond"), flightConditionsTab());
139 //// Simulation options
140 tabbedPane.addTab(trans.get("simedtdlg.tab.Simopt"), simulationOptionsTab());
142 tabbedPane.addTab(trans.get("simedtdlg.tab.Plotdata"), plotTab());
144 tabbedPane.addTab(trans.get("simedtdlg.tab.Exportdata"), exportTab());
146 // Select the initial tab
148 tabbedPane.setSelectedIndex(0);
149 } else if (tab == PLOT) {
150 tabbedPane.setSelectedIndex(2);
152 FlightData data = s.getSimulatedData();
153 if (data == null || data.getBranchCount() == 0)
154 tabbedPane.setSelectedIndex(0);
156 tabbedPane.setSelectedIndex(2);
159 mainPanel.add(tabbedPane, "spanx, grow, wrap");
163 mainPanel.add(new JPanel(), "spanx, split, growx");
166 //// Run simulation button
167 button = new JButton(trans.get("simedtdlg.but.runsimulation"));
168 button.addActionListener(new ActionListener() {
170 public void actionPerformed(ActionEvent e) {
171 SimulationEditDialog.this.dispose();
172 SimulationRunDialog.runSimulations(parentWindow, simulation);
175 mainPanel.add(button, "gapright para");
178 JButton close = new JButton(trans.get("dlg.but.close"));
179 close.addActionListener(new ActionListener() {
181 public void actionPerformed(ActionEvent e) {
182 SimulationEditDialog.this.dispose();
185 mainPanel.add(close, "");
191 this.setLocationByPlatform(true);
193 GUIUtil.setDisposableDialogOptions(this, button);
200 private JPanel flightConditionsTab() {
201 JPanel panel = new JPanel(new MigLayout("fill"));
210 //// Motor configuration:
211 JLabel label = new JLabel(trans.get("simedtdlg.lbl.Motorcfg"));
212 //// Select the motor configuration to use.
213 label.setToolTipText(trans.get("simedtdlg.lbl.ttip.Motorcfg"));
214 panel.add(label, "shrinkx, spanx, split 2");
216 JComboBox combo = new JComboBox(new MotorConfigurationModel(configuration));
217 //// Select the motor configuration to use.
218 combo.setToolTipText(trans.get("simedtdlg.combo.ttip.motorconf"));
219 combo.addActionListener(new ActionListener() {
221 public void actionPerformed(ActionEvent e) {
222 conditions.setMotorConfigurationID(configuration.getMotorConfigurationID());
225 panel.add(combo, "growx, wrap para");
228 //// Wind settings: Average wind speed, turbulence intensity, std. deviation
229 sub = new JPanel(new MigLayout("fill, gap rel unrel",
230 "[grow][65lp!][30lp!][75lp!]", ""));
232 sub.setBorder(BorderFactory.createTitledBorder(trans.get("simedtdlg.lbl.Wind")));
233 panel.add(sub, "growx, split 2, aligny 0, flowy, gapright para");
237 //// Average windspeed:
238 label = new JLabel(trans.get("simedtdlg.lbl.Averwindspeed"));
239 //// The average windspeed relative to the ground.
240 tip = trans.get("simedtdlg.lbl.ttip.Averwindspeed");
241 label.setToolTipText(tip);
244 m = new DoubleModel(conditions, "WindSpeedAverage", UnitGroup.UNITS_VELOCITY, 0);
246 spin = new JSpinner(m.getSpinnerModel());
247 spin.setEditor(new SpinnerEditor(spin));
248 spin.setToolTipText(tip);
249 sub.add(spin, "w 65lp!");
251 unit = new UnitSelector(m);
252 unit.setToolTipText(tip);
253 sub.add(unit, "growx");
254 slider = new BasicSlider(m.getSliderModel(0, 10.0));
255 slider.setToolTipText(tip);
256 sub.add(slider, "w 75lp, wrap");
260 // Wind std. deviation
261 //// Standard deviation:
262 label = new JLabel(trans.get("simedtdlg.lbl.Stddeviation"));
263 //// <html>The standard deviation of the windspeed.<br>
264 //// The windspeed is within twice the standard deviation from the average for 95% of the time.
265 tip = trans.get("simedtdlg.lbl.ttip.Stddeviation");
266 label.setToolTipText(tip);
269 m = new DoubleModel(conditions, "WindSpeedDeviation", UnitGroup.UNITS_VELOCITY, 0);
270 DoubleModel m2 = new DoubleModel(conditions, "WindSpeedAverage", 0.25,
271 UnitGroup.UNITS_COEFFICIENT, 0);
273 spin = new JSpinner(m.getSpinnerModel());
274 spin.setEditor(new SpinnerEditor(spin));
275 spin.setToolTipText(tip);
276 sub.add(spin, "w 65lp!");
278 unit = new UnitSelector(m);
279 unit.setToolTipText(tip);
280 sub.add(unit, "growx");
281 slider = new BasicSlider(m.getSliderModel(new DoubleModel(0), m2));
282 slider.setToolTipText(tip);
283 sub.add(slider, "w 75lp, wrap");
286 // Wind turbulence intensity
287 //// Turbulence intensity:
288 label = new JLabel(trans.get("simedtdlg.lbl.Turbulenceintensity"));
289 //// <html>The turbulence intensity is the standard deviation divided by the average windspeed.<br>
290 //// Typical values range from
292 tip = trans.get("simedtdlg.lbl.ttip.Turbulenceintensity1") +
293 trans.get("simedtdlg.lbl.ttip.Turbulenceintensity2") + " " +
294 UnitGroup.UNITS_RELATIVE.getDefaultUnit().toStringUnit(0.05) +
295 " " + trans.get("simedtdlg.lbl.ttip.Turbulenceintensity3") + " " +
296 UnitGroup.UNITS_RELATIVE.getDefaultUnit().toStringUnit(0.20) + ".";
297 label.setToolTipText(tip);
300 m = new DoubleModel(conditions, "WindTurbulenceIntensity", UnitGroup.UNITS_RELATIVE, 0);
302 spin = new JSpinner(m.getSpinnerModel());
303 spin.setEditor(new SpinnerEditor(spin));
304 spin.setToolTipText(tip);
305 sub.add(spin, "w 65lp!");
307 unit = new UnitSelector(m);
308 unit.setToolTipText(tip);
309 sub.add(unit, "growx");
311 final JLabel intensityLabel = new JLabel(
312 getIntensityDescription(conditions.getWindTurbulenceIntensity()));
313 intensityLabel.setToolTipText(tip);
314 sub.add(intensityLabel, "w 75lp, wrap");
315 m.addChangeListener(new ChangeListener() {
317 public void stateChanged(ChangeEvent e) {
318 intensityLabel.setText(
319 getIntensityDescription(conditions.getWindTurbulenceIntensity()));
327 //// Temperature and pressure
328 sub = new JPanel(new MigLayout("fill, gap rel unrel",
329 "[grow][65lp!][30lp!][75lp!]", ""));
330 //// Atmospheric conditions
331 sub.setBorder(BorderFactory.createTitledBorder(trans.get("simedtdlg.border.Atmoscond")));
332 panel.add(sub, "growx, aligny 0, gapright para");
335 BooleanModel isa = new BooleanModel(conditions, "ISAAtmosphere");
336 JCheckBox check = new JCheckBox(isa);
337 //// Use International Standard Atmosphere
338 check.setText(trans.get("simedtdlg.checkbox.InterStdAtmosphere"));
339 //// <html>Select to use the International Standard Atmosphere model.
340 //// <br>This model has a temperature of
341 //// and a pressure of
343 check.setToolTipText(trans.get("simedtdlg.checkbox.ttip.InterStdAtmosphere1") + " " +
344 UnitGroup.UNITS_TEMPERATURE.toStringUnit(ExtendedISAModel.STANDARD_TEMPERATURE) +
345 " " + trans.get("simedtdlg.checkbox.ttip.InterStdAtmosphere2") + " " +
346 UnitGroup.UNITS_PRESSURE.toStringUnit(ExtendedISAModel.STANDARD_PRESSURE) +
347 " " + trans.get("simedtdlg.checkbox.ttip.InterStdAtmosphere3"));
348 sub.add(check, "spanx, wrap unrel");
351 label = new JLabel(trans.get("simedtdlg.lbl.Temperature"));
352 //// The temperature at the launch site.
353 tip = trans.get("simedtdlg.lbl.ttip.Temperature");
354 label.setToolTipText(tip);
355 isa.addEnableComponent(label, false);
358 m = new DoubleModel(conditions, "LaunchTemperature", UnitGroup.UNITS_TEMPERATURE, 0);
360 spin = new JSpinner(m.getSpinnerModel());
361 spin.setEditor(new SpinnerEditor(spin));
362 spin.setToolTipText(tip);
363 isa.addEnableComponent(spin, false);
364 sub.add(spin, "w 65lp!");
366 unit = new UnitSelector(m);
367 unit.setToolTipText(tip);
368 isa.addEnableComponent(unit, false);
369 sub.add(unit, "growx");
370 slider = new BasicSlider(m.getSliderModel(253.15, 308.15)); // -20 ... 35
371 slider.setToolTipText(tip);
372 isa.addEnableComponent(slider, false);
373 sub.add(slider, "w 75lp, wrap");
378 label = new JLabel(trans.get("simedtdlg.lbl.Pressure"));
379 //// The atmospheric pressure at the launch site.
380 tip = trans.get("simedtdlg.lbl.ttip.Pressure");
381 label.setToolTipText(tip);
382 isa.addEnableComponent(label, false);
385 m = new DoubleModel(conditions, "LaunchPressure", UnitGroup.UNITS_PRESSURE, 0);
387 spin = new JSpinner(m.getSpinnerModel());
388 spin.setEditor(new SpinnerEditor(spin));
389 spin.setToolTipText(tip);
390 isa.addEnableComponent(spin, false);
391 sub.add(spin, "w 65lp!");
393 unit = new UnitSelector(m);
394 unit.setToolTipText(tip);
395 isa.addEnableComponent(unit, false);
396 sub.add(unit, "growx");
397 slider = new BasicSlider(m.getSliderModel(0.950e5, 1.050e5));
398 slider.setToolTipText(tip);
399 isa.addEnableComponent(slider, false);
400 sub.add(slider, "w 75lp, wrap");
406 //// Launch site conditions
407 sub = new JPanel(new MigLayout("fill, gap rel unrel",
408 "[grow][65lp!][30lp!][75lp!]", ""));
410 sub.setBorder(BorderFactory.createTitledBorder(trans.get("simedtdlg.lbl.Launchsite")));
411 panel.add(sub, "growx, split 2, aligny 0, flowy");
415 label = new JLabel(trans.get("simedtdlg.lbl.Latitude"));
416 //// <html>The launch site latitude affects the gravitational pull of Earth.<br>
417 //// Positive values are on the Northern hemisphere, negative values on the Southern hemisphere.
418 tip = trans.get("simedtdlg.lbl.ttip.Latitude");
419 label.setToolTipText(tip);
422 m = new DoubleModel(conditions, "LaunchLatitude", UnitGroup.UNITS_NONE, -90, 90);
424 spin = new JSpinner(m.getSpinnerModel());
425 spin.setEditor(new SpinnerEditor(spin));
426 spin.setToolTipText(tip);
427 sub.add(spin, "w 65lp!");
429 label = new JLabel(Chars.DEGREE + " N");
430 label.setToolTipText(tip);
431 sub.add(label, "growx");
432 slider = new BasicSlider(m.getSliderModel(-90, 90));
433 slider.setToolTipText(tip);
434 sub.add(slider, "w 75lp, wrap");
438 label = new JLabel(trans.get("simedtdlg.lbl.Longitude"));
439 tip = trans.get("simedtdlg.lbl.ttip.Longitude");
440 label.setToolTipText(tip);
443 m = new DoubleModel(conditions, "LaunchLongitude", UnitGroup.UNITS_NONE, -180, 180);
445 spin = new JSpinner(m.getSpinnerModel());
446 spin.setEditor(new SpinnerEditor(spin));
447 spin.setToolTipText(tip);
448 sub.add(spin, "w 65lp!");
450 label = new JLabel(Chars.DEGREE + " E");
451 label.setToolTipText(tip);
452 sub.add(label, "growx");
453 slider = new BasicSlider(m.getSliderModel(-180, 180));
454 slider.setToolTipText(tip);
455 sub.add(slider, "w 75lp, wrap");
459 label = new JLabel(trans.get("simedtdlg.lbl.Altitude"));
460 //// <html>The launch altitude above mean sea level.<br>
461 //// This affects the position of the rocket in the atmospheric model.
462 tip = trans.get("simedtdlg.lbl.ttip.Altitude");
463 label.setToolTipText(tip);
466 m = new DoubleModel(conditions, "LaunchAltitude", UnitGroup.UNITS_DISTANCE, 0);
468 spin = new JSpinner(m.getSpinnerModel());
469 spin.setEditor(new SpinnerEditor(spin));
470 spin.setToolTipText(tip);
471 sub.add(spin, "w 65lp!");
473 unit = new UnitSelector(m);
474 unit.setToolTipText(tip);
475 sub.add(unit, "growx");
476 slider = new BasicSlider(m.getSliderModel(0, 250, 1000));
477 slider.setToolTipText(tip);
478 sub.add(slider, "w 75lp, wrap");
485 sub = new JPanel(new MigLayout("fill, gap rel unrel",
486 "[grow][65lp!][30lp!][75lp!]", ""));
488 sub.setBorder(BorderFactory.createTitledBorder(trans.get("simedtdlg.border.Launchrod")));
489 panel.add(sub, "growx, aligny 0, wrap");
493 label = new JLabel(trans.get("simedtdlg.lbl.Length"));
494 //// The length of the launch rod.
495 tip = trans.get("simedtdlg.lbl.ttip.Length");
496 label.setToolTipText(tip);
499 m = new DoubleModel(conditions, "LaunchRodLength", UnitGroup.UNITS_LENGTH, 0);
501 spin = new JSpinner(m.getSpinnerModel());
502 spin.setEditor(new SpinnerEditor(spin));
503 spin.setToolTipText(tip);
504 sub.add(spin, "w 65lp!");
506 unit = new UnitSelector(m);
507 unit.setToolTipText(tip);
508 sub.add(unit, "growx");
509 slider = new BasicSlider(m.getSliderModel(0, 1, 5));
510 slider.setToolTipText(tip);
511 sub.add(slider, "w 75lp, wrap");
516 label = new JLabel(trans.get("simedtdlg.lbl.Angle"));
517 //// The angle of the launch rod from vertical.
518 tip = trans.get("simedtdlg.lbl.ttip.Angle");
519 label.setToolTipText(tip);
522 m = new DoubleModel(conditions, "LaunchRodAngle", UnitGroup.UNITS_ANGLE,
523 0, SimulationOptions.MAX_LAUNCH_ROD_ANGLE);
525 spin = new JSpinner(m.getSpinnerModel());
526 spin.setEditor(new SpinnerEditor(spin));
527 spin.setToolTipText(tip);
528 sub.add(spin, "w 65lp!");
530 unit = new UnitSelector(m);
531 unit.setToolTipText(tip);
532 sub.add(unit, "growx");
533 slider = new BasicSlider(m.getSliderModel(0, Math.PI / 9,
534 SimulationOptions.MAX_LAUNCH_ROD_ANGLE));
535 slider.setToolTipText(tip);
536 sub.add(slider, "w 75lp, wrap");
541 label = new JLabel(trans.get("simedtdlg.lbl.Direction"));
542 //// <html>Direction of the launch rod relative to the wind.<br>
543 //// = towards the wind,
545 tip = trans.get("simedtdlg.lbl.ttip.Direction1") +
546 UnitGroup.UNITS_ANGLE.toStringUnit(0) +
547 " " + trans.get("simedtdlg.lbl.ttip.Direction2") + " " +
548 UnitGroup.UNITS_ANGLE.toStringUnit(Math.PI) +
549 " " + trans.get("simedtdlg.lbl.ttip.Direction3");
550 label.setToolTipText(tip);
553 m = new DoubleModel(conditions, "LaunchRodDirection", UnitGroup.UNITS_ANGLE,
556 spin = new JSpinner(m.getSpinnerModel());
557 spin.setEditor(new SpinnerEditor(spin));
558 spin.setToolTipText(tip);
559 sub.add(spin, "w 65lp!");
561 unit = new UnitSelector(m);
562 unit.setToolTipText(tip);
563 sub.add(unit, "growx");
564 slider = new BasicSlider(m.getSliderModel(-Math.PI, Math.PI));
565 slider.setToolTipText(tip);
566 sub.add(slider, "w 75lp, wrap");
572 private String getIntensityDescription(double i) {
575 return trans.get("simedtdlg.IntensityDesc.None");
578 return trans.get("simedtdlg.IntensityDesc.Verylow");
581 return trans.get("simedtdlg.IntensityDesc.Low");
584 return trans.get("simedtdlg.IntensityDesc.Medium");
587 return trans.get("simedtdlg.IntensityDesc.High");
590 return trans.get("simedtdlg.IntensityDesc.Veryhigh");
592 return trans.get("simedtdlg.IntensityDesc.Extreme");
597 private JPanel simulationOptionsTab() {
598 JPanel panel = new JPanel(new MigLayout("fill"));
608 //// Simulation options
609 sub = new JPanel(new MigLayout("fill, gap rel unrel",
610 "[grow][65lp!][30lp!][75lp!]", ""));
611 //// Simulator options
612 sub.setBorder(BorderFactory.createTitledBorder(trans.get("simedtdlg.border.Simopt")));
613 panel.add(sub, "growx, growy, aligny 0");
616 // Separate panel for computation methods, as they use a different layout
617 subsub = new JPanel(new MigLayout("insets 0, fill"));
620 //// Calculation method:
621 tip = trans.get("simedtdlg.lbl.ttip.Calcmethod");
622 label = new JLabel(trans.get("simedtdlg.lbl.Calcmethod"));
623 label.setToolTipText(tip);
624 subsub.add(label, "gapright para");
626 //// Extended Barrowman
627 label = new JLabel(trans.get("simedtdlg.lbl.ExtBarrowman"));
628 label.setToolTipText(tip);
629 subsub.add(label, "growx, wrap para");
633 tip = trans.get("simedtdlg.lbl.ttip.Simmethod1") +
634 trans.get("simedtdlg.lbl.ttip.Simmethod2");
635 label = new JLabel(trans.get("simedtdlg.lbl.Simmethod"));
636 label.setToolTipText(tip);
637 subsub.add(label, "gapright para");
639 label = new JLabel("6-DOF Runge-Kutta 4");
640 label.setToolTipText(tip);
641 subsub.add(label, "growx, wrap para");
644 //// Geodetic calculation method:
645 label = new JLabel(trans.get("simedtdlg.lbl.GeodeticMethod"));
646 label.setToolTipText(trans.get("simedtdlg.lbl.ttip.GeodeticMethodTip"));
647 subsub.add(label, "gapright para");
649 EnumModel<GeodeticComputationStrategy> gcsModel = new EnumModel<GeodeticComputationStrategy>(conditions, "GeodeticComputation");
650 final JComboBox gcsCombo = new JComboBox(gcsModel);
651 ActionListener gcsTTipListener = new ActionListener() {
653 public void actionPerformed(ActionEvent e) {
654 GeodeticComputationStrategy gcs = (GeodeticComputationStrategy) gcsCombo.getSelectedItem();
655 gcsCombo.setToolTipText(gcs.getDescription());
658 gcsCombo.addActionListener(gcsTTipListener);
659 gcsTTipListener.actionPerformed(null);
660 subsub.add(gcsCombo, "growx, wrap para");
662 sub.add(subsub, "spanx, wrap para");
666 label = new JLabel(trans.get("simedtdlg.lbl.Timestep"));
667 tip = trans.get("simedtdlg.lbl.ttip.Timestep1") +
668 trans.get("simedtdlg.lbl.ttip.Timestep2") + " " +
669 UnitGroup.UNITS_TIME_STEP.toStringUnit(RK4SimulationStepper.RECOMMENDED_TIME_STEP) +
671 label.setToolTipText(tip);
674 m = new DoubleModel(conditions, "TimeStep", UnitGroup.UNITS_TIME_STEP, 0, 1);
676 spin = new JSpinner(m.getSpinnerModel());
677 spin.setEditor(new SpinnerEditor(spin));
678 spin.setToolTipText(tip);
679 sub.add(spin, "w 65lp!");
680 //sub.add(spin, "nogrid");
682 unit = new UnitSelector(m);
683 unit.setToolTipText(tip);
684 sub.add(unit, "w 25");
685 //sub.add(unit, "nogrid");
686 slider = new BasicSlider(m.getSliderModel(0, 0.2));
687 slider.setToolTipText(tip);
688 sub.add(slider, "w 75lp, wrap");
689 //sub.add(slider,"wrap");
694 //// Reset to default button
695 JButton button = new JButton(trans.get("simedtdlg.but.resettodefault"));
696 //// Reset the time step to its default value (
697 button.setToolTipText(trans.get("simedtdlg.but.ttip.resettodefault") +
698 UnitGroup.UNITS_SHORT_TIME.toStringUnit(RK4SimulationStepper.RECOMMENDED_TIME_STEP) +
700 button.addActionListener(new ActionListener() {
702 public void actionPerformed(ActionEvent e) {
703 conditions.setTimeStep(RK4SimulationStepper.RECOMMENDED_TIME_STEP);
704 conditions.setGeodeticComputation(GeodeticComputationStrategy.SPHERICAL);
708 sub.add(button, "align left");
713 //// Simulation listeners
714 sub = new JPanel(new MigLayout("fill, gap 0 0"));
715 //// Simulator listeners
716 sub.setBorder(BorderFactory.createTitledBorder(trans.get("simedtdlg.border.Simlist")));
717 panel.add(sub, "growx, growy");
720 DescriptionArea desc = new DescriptionArea(5);
721 //// <html><i>Simulation listeners</i> is an advanced feature that allows user-written code to listen to and interact with the simulation.
722 //// For details on writing simulation listeners, see the OpenRocket technical documentation.
723 desc.setText(trans.get("simedtdlg.txt.longA1") +
724 trans.get("simedtdlg.txt.longA2"));
725 sub.add(desc, "aligny 0, growx, wrap para");
727 //// Current listeners:
728 label = new JLabel(trans.get("simedtdlg.lbl.Curlist"));
729 sub.add(label, "spanx, wrap rel");
731 final ListenerListModel listenerModel = new ListenerListModel();
732 final JList list = new JList(listenerModel);
733 list.setCellRenderer(new ListenerCellRenderer());
734 JScrollPane scroll = new JScrollPane(list);
735 // scroll.setPreferredSize(new Dimension(1,1));
736 sub.add(scroll, "height 1px, grow, wrap rel");
739 button = new JButton(trans.get("simedtdlg.but.add"));
740 button.addActionListener(new ActionListener() {
742 public void actionPerformed(ActionEvent e) {
743 String previous = Application.getPreferences().getString("previousListenerName", "");
744 String input = (String) JOptionPane.showInputDialog(SimulationEditDialog.this,
746 //// Type the full Java class name of the simulation listener, for example:
747 "Type the full Java class name of the simulation listener, for example:",
748 "<html><tt>" + CSVSaveListener.class.getName() + "</tt>" },
749 //// Add simulation listener
750 trans.get("simedtdlg.lbl.Addsimlist"),
751 JOptionPane.QUESTION_MESSAGE,
755 if (input == null || input.equals(""))
758 Application.getPreferences().putString("previousListenerName", input);
759 simulation.getSimulationListeners().add(input);
760 listenerModel.fireContentsChanged();
763 sub.add(button, "split 2, sizegroup buttons, alignx 50%, gapright para");
766 button = new JButton(trans.get("simedtdlg.but.remove"));
767 button.addActionListener(new ActionListener() {
769 public void actionPerformed(ActionEvent e) {
770 int[] selected = list.getSelectedIndices();
771 Arrays.sort(selected);
772 for (int i = selected.length - 1; i >= 0; i--) {
773 simulation.getSimulationListeners().remove(selected[i]);
775 listenerModel.fireContentsChanged();
778 sub.add(button, "sizegroup buttons, alignx 50%");
785 private class ListenerListModel extends AbstractListModel {
787 public String getElementAt(int index) {
788 if (index < 0 || index >= getSize())
790 return simulation.getSimulationListeners().get(index);
794 public int getSize() {
795 return simulation.getSimulationListeners().size();
798 public void fireContentsChanged() {
799 super.fireContentsChanged(this, 0, getSize());
807 * A panel for plotting the previously calculated data.
809 private JPanel plotTab() {
811 // Check that data exists
812 if (simulation.getSimulatedData() == null ||
813 simulation.getSimulatedData().getBranchCount() == 0) {
814 return noDataPanel();
817 return new SimulationPlotPanel(simulation);
823 * A panel for exporting the data.
825 private JPanel exportTab() {
826 FlightData data = simulation.getSimulatedData();
828 // Check that data exists
829 if (data == null || data.getBranchCount() == 0 ||
830 data.getBranch(0).getTypes().length == 0) {
831 return noDataPanel();
834 return new SimulationExportPanel(simulation);
842 * Return a panel stating that there is no data available, and that the user
843 * should run the simulation first.
845 public static JPanel noDataPanel() {
846 JPanel panel = new JPanel(new MigLayout("fill"));
849 //// No flight data available.
850 panel.add(new JLabel(trans.get("simedtdlg.lbl.Noflightdata")),
851 "alignx 50%, aligny 100%, wrap para");
852 //// Please run the simulation first.
853 panel.add(new JLabel(trans.get("simedtdlg.lbl.runsimfirst")),
854 "alignx 50%, aligny 0%, wrap");
859 private void performPlot(PlotConfiguration config) {
861 // Fill the auto-selections
862 FlightDataBranch branch = simulation.getSimulatedData().getBranch(0);
863 PlotConfiguration filled = config.fillAutoAxes(branch);
864 List<Axis> axes = filled.getAllAxes();
867 // Create the data series for both axes
868 XYSeriesCollection[] data = new XYSeriesCollection[2];
869 data[0] = new XYSeriesCollection();
870 data[1] = new XYSeriesCollection();
873 // Get the domain axis type
874 final FlightDataType domainType = filled.getDomainAxisType();
875 final Unit domainUnit = filled.getDomainAxisUnit();
876 if (domainType == null) {
877 throw new IllegalArgumentException("Domain axis type not specified.");
879 List<Double> x = branch.get(domainType);
882 // Create the XYSeries objects from the flight data and store into the collections
883 int length = filled.getTypeCount();
884 String[] axisLabel = new String[2];
885 for (int i = 0; i < length; i++) {
887 FlightDataType type = filled.getType(i);
888 Unit unit = filled.getUnit(i);
889 int axis = filled.getAxis(i);
890 String name = getLabel(type, unit);
892 // Store data in provided units
893 List<Double> y = branch.get(type);
894 XYSeries series = new XYSeries(name, false, true);
895 for (int j = 0; j < x.size(); j++) {
896 series.add(domainUnit.toUnit(x.get(j)), unit.toUnit(y.get(j)));
898 data[axis].addSeries(series);
901 if (axisLabel[axis] == null)
902 axisLabel[axis] = type.getName();
904 axisLabel[axis] += "; " + type.getName();
908 // Create the chart using the factory to get all default settings
909 JFreeChart chart = ChartFactory.createXYLineChart(
910 //// Simulated flight
911 trans.get("simedtdlg.chart.Simflight"),
915 PlotOrientation.VERTICAL,
922 // Add the data and formatting to the plot
923 XYPlot plot = chart.getXYPlot();
925 for (int i = 0; i < 2; i++) {
926 // Check whether axis has any data
927 if (data[i].getSeriesCount() > 0) {
928 // Create and set axis
929 double min = axes.get(i).getMinValue();
930 double max = axes.get(i).getMaxValue();
931 NumberAxis axis = new PresetNumberAxis(min, max);
932 axis.setLabel(axisLabel[i]);
933 // axis.setRange(axes.get(i).getMinValue(), axes.get(i).getMaxValue());
934 plot.setRangeAxis(axisno, axis);
936 // Add data and map to the axis
937 plot.setDataset(axisno, data[i]);
938 plot.setRenderer(axisno, new StandardXYItemRenderer());
939 plot.mapDatasetToRangeAxis(axisno, axisno);
944 plot.getDomainAxis().setLabel(getLabel(domainType, domainUnit));
945 plot.addDomainMarker(new ValueMarker(0));
946 plot.addRangeMarker(new ValueMarker(0));
950 //// Simulation results
951 final JDialog dialog = new JDialog(this, trans.get("simedtdlg.dlg.Simres"));
952 dialog.setModalityType(ModalityType.DOCUMENT_MODAL);
954 JPanel panel = new JPanel(new MigLayout("fill"));
957 ChartPanel chartPanel = new ChartPanel(chart,
963 chartPanel.setMouseWheelEnabled(true);
964 chartPanel.setEnforceFileExtensions(true);
965 chartPanel.setInitialDelay(500);
967 chartPanel.setBorder(BorderFactory.createLineBorder(Color.GRAY, 1));
969 panel.add(chartPanel, "grow, wrap 20lp");
972 JButton button = new JButton(trans.get("dlg.but.close"));
973 button.addActionListener(new ActionListener() {
975 public void actionPerformed(ActionEvent e) {
976 dialog.setVisible(false);
979 panel.add(button, "right");
981 dialog.setLocationByPlatform(true);
984 GUIUtil.setDisposableDialogOptions(dialog, button);
986 dialog.setVisible(true);
990 private class PresetNumberAxis extends NumberAxis {
991 private final double min;
992 private final double max;
994 public PresetNumberAxis(double min, double max) {
1001 protected void autoAdjustRange() {
1002 this.setRange(min, max);
1007 private String getLabel(FlightDataType type, Unit unit) {
1008 String name = type.getName();
1009 if (unit != null && !UnitGroup.UNITS_NONE.contains(unit) &&
1010 !UnitGroup.UNITS_COEFFICIENT.contains(unit) && unit.getUnit().length() > 0)
1011 name += " (" + unit.getUnit() + ")";
1017 private class ListenerCellRenderer extends JLabel implements ListCellRenderer {
1020 public Component getListCellRendererComponent(JList list, Object value,
1021 int index, boolean isSelected, boolean cellHasFocus) {
1022 String s = value.toString();
1025 // Attempt instantiating, catch any exceptions
1026 Exception ex = null;
1028 Class<?> c = Class.forName(s);
1029 @SuppressWarnings("unused")
1030 SimulationListener l = (SimulationListener) c.newInstance();
1031 } catch (Exception e) {
1036 setIcon(Icons.SIMULATION_LISTENER_OK);
1037 //// Listener instantiated successfully.
1038 setToolTipText("Listener instantiated successfully.");
1040 setIcon(Icons.SIMULATION_LISTENER_ERROR);
1041 //// <html>Unable to instantiate listener due to exception:<br>
1042 setToolTipText("<html>Unable to instantiate listener due to exception:<br>" +
1047 setBackground(list.getSelectionBackground());
1048 setForeground(list.getSelectionForeground());
1050 setBackground(list.getBackground());
1051 setForeground(list.getForeground());