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 = 2;
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());
143 tabbedPane.addTab(trans.get("simedtdlg.tab.Plotdata"), plotTab());
145 tabbedPane.addTab(trans.get("simedtdlg.tab.Exportdata"), exportTab());
147 // Select the initial tab
149 tabbedPane.setSelectedIndex(0);
150 } else if (tab == PLOT) {
151 tabbedPane.setSelectedIndex(2);
153 FlightData data = s.getSimulatedData();
154 if (data == null || data.getBranchCount() == 0)
155 tabbedPane.setSelectedIndex(0);
157 tabbedPane.setSelectedIndex(2);
160 mainPanel.add(tabbedPane, "spanx, grow, wrap");
164 mainPanel.add(new JPanel(), "spanx, split, growx");
167 //// Run simulation button
168 button = new JButton(trans.get("simedtdlg.but.runsimulation"));
169 button.addActionListener(new ActionListener() {
171 public void actionPerformed(ActionEvent e) {
172 SimulationEditDialog.this.dispose();
173 SimulationRunDialog.runSimulations(parentWindow, simulation);
176 mainPanel.add(button, "gapright para");
179 JButton close = new JButton(trans.get("dlg.but.close"));
180 close.addActionListener(new ActionListener() {
182 public void actionPerformed(ActionEvent e) {
183 SimulationEditDialog.this.dispose();
186 mainPanel.add(close, "");
192 this.setLocationByPlatform(true);
194 GUIUtil.setDisposableDialogOptions(this, button);
201 private JPanel flightConditionsTab() {
202 JPanel panel = new JPanel(new MigLayout("fill"));
211 //// Motor configuration:
212 JLabel label = new JLabel(trans.get("simedtdlg.lbl.Motorcfg"));
213 //// Select the motor configuration to use.
214 label.setToolTipText(trans.get("simedtdlg.lbl.ttip.Motorcfg"));
215 panel.add(label, "shrinkx, spanx, split 2");
217 JComboBox combo = new JComboBox(new MotorConfigurationModel(configuration));
218 //// Select the motor configuration to use.
219 combo.setToolTipText(trans.get("simedtdlg.combo.ttip.motorconf"));
220 combo.addActionListener(new ActionListener() {
222 public void actionPerformed(ActionEvent e) {
223 conditions.setMotorConfigurationID(configuration.getMotorConfigurationID());
226 panel.add(combo, "growx, wrap para");
229 //// Wind settings: Average wind speed, turbulence intensity, std. deviation
230 sub = new JPanel(new MigLayout("fill, gap rel unrel",
231 "[grow][65lp!][30lp!][75lp!]", ""));
233 sub.setBorder(BorderFactory.createTitledBorder(trans.get("simedtdlg.lbl.Wind")));
234 panel.add(sub, "growx, split 2, aligny 0, flowy, gapright para");
238 //// Average windspeed:
239 label = new JLabel(trans.get("simedtdlg.lbl.Averwindspeed"));
240 //// The average windspeed relative to the ground.
241 tip = trans.get("simedtdlg.lbl.ttip.Averwindspeed");
242 label.setToolTipText(tip);
245 m = new DoubleModel(conditions, "WindSpeedAverage", UnitGroup.UNITS_WINDSPEED, 0);
247 spin = new JSpinner(m.getSpinnerModel());
248 spin.setEditor(new SpinnerEditor(spin));
249 spin.setToolTipText(tip);
250 sub.add(spin, "w 65lp!");
252 unit = new UnitSelector(m);
253 unit.setToolTipText(tip);
254 sub.add(unit, "growx");
255 slider = new BasicSlider(m.getSliderModel(0, 10.0));
256 slider.setToolTipText(tip);
257 sub.add(slider, "w 75lp, wrap");
261 // Wind std. deviation
262 //// Standard deviation:
263 label = new JLabel(trans.get("simedtdlg.lbl.Stddeviation"));
264 //// <html>The standard deviation of the windspeed.<br>
265 //// The windspeed is within twice the standard deviation from the average for 95% of the time.
266 tip = trans.get("simedtdlg.lbl.ttip.Stddeviation");
267 label.setToolTipText(tip);
270 m = new DoubleModel(conditions, "WindSpeedDeviation", UnitGroup.UNITS_WINDSPEED, 0);
271 DoubleModel m2 = new DoubleModel(conditions, "WindSpeedAverage", 0.25,
272 UnitGroup.UNITS_COEFFICIENT, 0);
274 spin = new JSpinner(m.getSpinnerModel());
275 spin.setEditor(new SpinnerEditor(spin));
276 spin.setToolTipText(tip);
277 sub.add(spin, "w 65lp!");
279 unit = new UnitSelector(m);
280 unit.setToolTipText(tip);
281 sub.add(unit, "growx");
282 slider = new BasicSlider(m.getSliderModel(new DoubleModel(0), m2));
283 slider.setToolTipText(tip);
284 sub.add(slider, "w 75lp, wrap");
287 // Wind turbulence intensity
288 //// Turbulence intensity:
289 label = new JLabel(trans.get("simedtdlg.lbl.Turbulenceintensity"));
290 //// <html>The turbulence intensity is the standard deviation divided by the average windspeed.<br>
291 //// Typical values range from
293 tip = trans.get("simedtdlg.lbl.ttip.Turbulenceintensity1") +
294 trans.get("simedtdlg.lbl.ttip.Turbulenceintensity2") + " " +
295 UnitGroup.UNITS_RELATIVE.getDefaultUnit().toStringUnit(0.05) +
296 " " + trans.get("simedtdlg.lbl.ttip.Turbulenceintensity3") + " " +
297 UnitGroup.UNITS_RELATIVE.getDefaultUnit().toStringUnit(0.20) + ".";
298 label.setToolTipText(tip);
301 m = new DoubleModel(conditions, "WindTurbulenceIntensity", UnitGroup.UNITS_RELATIVE, 0);
303 spin = new JSpinner(m.getSpinnerModel());
304 spin.setEditor(new SpinnerEditor(spin));
305 spin.setToolTipText(tip);
306 sub.add(spin, "w 65lp!");
308 unit = new UnitSelector(m);
309 unit.setToolTipText(tip);
310 sub.add(unit, "growx");
312 final JLabel intensityLabel = new JLabel(
313 getIntensityDescription(conditions.getWindTurbulenceIntensity()));
314 intensityLabel.setToolTipText(tip);
315 sub.add(intensityLabel, "w 75lp, wrap");
316 m.addChangeListener(new ChangeListener() {
318 public void stateChanged(ChangeEvent e) {
319 intensityLabel.setText(
320 getIntensityDescription(conditions.getWindTurbulenceIntensity()));
328 //// Temperature and pressure
329 sub = new JPanel(new MigLayout("fill, gap rel unrel",
330 "[grow][65lp!][30lp!][75lp!]", ""));
331 //// Atmospheric conditions
332 sub.setBorder(BorderFactory.createTitledBorder(trans.get("simedtdlg.border.Atmoscond")));
333 panel.add(sub, "growx, aligny 0, gapright para");
336 BooleanModel isa = new BooleanModel(conditions, "ISAAtmosphere");
337 JCheckBox check = new JCheckBox(isa);
338 //// Use International Standard Atmosphere
339 check.setText(trans.get("simedtdlg.checkbox.InterStdAtmosphere"));
340 //// <html>Select to use the International Standard Atmosphere model.
341 //// <br>This model has a temperature of
342 //// and a pressure of
344 check.setToolTipText(trans.get("simedtdlg.checkbox.ttip.InterStdAtmosphere1") + " " +
345 UnitGroup.UNITS_TEMPERATURE.toStringUnit(ExtendedISAModel.STANDARD_TEMPERATURE) +
346 " " + trans.get("simedtdlg.checkbox.ttip.InterStdAtmosphere2") + " " +
347 UnitGroup.UNITS_PRESSURE.toStringUnit(ExtendedISAModel.STANDARD_PRESSURE) +
348 " " + trans.get("simedtdlg.checkbox.ttip.InterStdAtmosphere3"));
349 sub.add(check, "spanx, wrap unrel");
352 label = new JLabel(trans.get("simedtdlg.lbl.Temperature"));
353 //// The temperature at the launch site.
354 tip = trans.get("simedtdlg.lbl.ttip.Temperature");
355 label.setToolTipText(tip);
356 isa.addEnableComponent(label, false);
359 m = new DoubleModel(conditions, "LaunchTemperature", UnitGroup.UNITS_TEMPERATURE, 0);
361 spin = new JSpinner(m.getSpinnerModel());
362 spin.setEditor(new SpinnerEditor(spin));
363 spin.setToolTipText(tip);
364 isa.addEnableComponent(spin, false);
365 sub.add(spin, "w 65lp!");
367 unit = new UnitSelector(m);
368 unit.setToolTipText(tip);
369 isa.addEnableComponent(unit, false);
370 sub.add(unit, "growx");
371 slider = new BasicSlider(m.getSliderModel(253.15, 308.15)); // -20 ... 35
372 slider.setToolTipText(tip);
373 isa.addEnableComponent(slider, false);
374 sub.add(slider, "w 75lp, wrap");
379 label = new JLabel(trans.get("simedtdlg.lbl.Pressure"));
380 //// The atmospheric pressure at the launch site.
381 tip = trans.get("simedtdlg.lbl.ttip.Pressure");
382 label.setToolTipText(tip);
383 isa.addEnableComponent(label, false);
386 m = new DoubleModel(conditions, "LaunchPressure", UnitGroup.UNITS_PRESSURE, 0);
388 spin = new JSpinner(m.getSpinnerModel());
389 spin.setEditor(new SpinnerEditor(spin));
390 spin.setToolTipText(tip);
391 isa.addEnableComponent(spin, false);
392 sub.add(spin, "w 65lp!");
394 unit = new UnitSelector(m);
395 unit.setToolTipText(tip);
396 isa.addEnableComponent(unit, false);
397 sub.add(unit, "growx");
398 slider = new BasicSlider(m.getSliderModel(0.950e5, 1.050e5));
399 slider.setToolTipText(tip);
400 isa.addEnableComponent(slider, false);
401 sub.add(slider, "w 75lp, wrap");
407 //// Launch site conditions
408 sub = new JPanel(new MigLayout("fill, gap rel unrel",
409 "[grow][65lp!][30lp!][75lp!]", ""));
411 sub.setBorder(BorderFactory.createTitledBorder(trans.get("simedtdlg.lbl.Launchsite")));
412 panel.add(sub, "growx, split 2, aligny 0, flowy");
416 label = new JLabel(trans.get("simedtdlg.lbl.Latitude"));
417 //// <html>The launch site latitude affects the gravitational pull of Earth.<br>
418 //// Positive values are on the Northern hemisphere, negative values on the Southern hemisphere.
419 tip = trans.get("simedtdlg.lbl.ttip.Latitude");
420 label.setToolTipText(tip);
423 m = new DoubleModel(conditions, "LaunchLatitude", UnitGroup.UNITS_NONE, -90, 90);
425 spin = new JSpinner(m.getSpinnerModel());
426 spin.setEditor(new SpinnerEditor(spin));
427 spin.setToolTipText(tip);
428 sub.add(spin, "w 65lp!");
430 label = new JLabel(Chars.DEGREE + " N");
431 label.setToolTipText(tip);
432 sub.add(label, "growx");
433 slider = new BasicSlider(m.getSliderModel(-90, 90));
434 slider.setToolTipText(tip);
435 sub.add(slider, "w 75lp, wrap");
439 label = new JLabel(trans.get("simedtdlg.lbl.Longitude"));
440 tip = trans.get("simedtdlg.lbl.ttip.Longitude");
441 label.setToolTipText(tip);
444 m = new DoubleModel(conditions, "LaunchLongitude", UnitGroup.UNITS_NONE, -180, 180);
446 spin = new JSpinner(m.getSpinnerModel());
447 spin.setEditor(new SpinnerEditor(spin));
448 spin.setToolTipText(tip);
449 sub.add(spin, "w 65lp!");
451 label = new JLabel(Chars.DEGREE + " E");
452 label.setToolTipText(tip);
453 sub.add(label, "growx");
454 slider = new BasicSlider(m.getSliderModel(-180, 180));
455 slider.setToolTipText(tip);
456 sub.add(slider, "w 75lp, wrap");
460 label = new JLabel(trans.get("simedtdlg.lbl.Altitude"));
461 //// <html>The launch altitude above mean sea level.<br>
462 //// This affects the position of the rocket in the atmospheric model.
463 tip = trans.get("simedtdlg.lbl.ttip.Altitude");
464 label.setToolTipText(tip);
467 m = new DoubleModel(conditions, "LaunchAltitude", UnitGroup.UNITS_DISTANCE, 0);
469 spin = new JSpinner(m.getSpinnerModel());
470 spin.setEditor(new SpinnerEditor(spin));
471 spin.setToolTipText(tip);
472 sub.add(spin, "w 65lp!");
474 unit = new UnitSelector(m);
475 unit.setToolTipText(tip);
476 sub.add(unit, "growx");
477 slider = new BasicSlider(m.getSliderModel(0, 250, 1000));
478 slider.setToolTipText(tip);
479 sub.add(slider, "w 75lp, wrap");
486 sub = new JPanel(new MigLayout("fill, gap rel unrel",
487 "[grow][65lp!][30lp!][75lp!]", ""));
489 sub.setBorder(BorderFactory.createTitledBorder(trans.get("simedtdlg.border.Launchrod")));
490 panel.add(sub, "growx, aligny 0, wrap");
494 label = new JLabel(trans.get("simedtdlg.lbl.Length"));
495 //// The length of the launch rod.
496 tip = trans.get("simedtdlg.lbl.ttip.Length");
497 label.setToolTipText(tip);
500 m = new DoubleModel(conditions, "LaunchRodLength", UnitGroup.UNITS_LENGTH, 0);
502 spin = new JSpinner(m.getSpinnerModel());
503 spin.setEditor(new SpinnerEditor(spin));
504 spin.setToolTipText(tip);
505 sub.add(spin, "w 65lp!");
507 unit = new UnitSelector(m);
508 unit.setToolTipText(tip);
509 sub.add(unit, "growx");
510 slider = new BasicSlider(m.getSliderModel(0, 1, 5));
511 slider.setToolTipText(tip);
512 sub.add(slider, "w 75lp, wrap");
517 label = new JLabel(trans.get("simedtdlg.lbl.Angle"));
518 //// The angle of the launch rod from vertical.
519 tip = trans.get("simedtdlg.lbl.ttip.Angle");
520 label.setToolTipText(tip);
523 m = new DoubleModel(conditions, "LaunchRodAngle", UnitGroup.UNITS_ANGLE,
524 0, SimulationOptions.MAX_LAUNCH_ROD_ANGLE);
526 spin = new JSpinner(m.getSpinnerModel());
527 spin.setEditor(new SpinnerEditor(spin));
528 spin.setToolTipText(tip);
529 sub.add(spin, "w 65lp!");
531 unit = new UnitSelector(m);
532 unit.setToolTipText(tip);
533 sub.add(unit, "growx");
534 slider = new BasicSlider(m.getSliderModel(0, Math.PI / 9,
535 SimulationOptions.MAX_LAUNCH_ROD_ANGLE));
536 slider.setToolTipText(tip);
537 sub.add(slider, "w 75lp, wrap");
542 label = new JLabel(trans.get("simedtdlg.lbl.Direction"));
543 //// <html>Direction of the launch rod relative to the wind.<br>
544 //// = towards the wind,
546 tip = trans.get("simedtdlg.lbl.ttip.Direction1") +
547 UnitGroup.UNITS_ANGLE.toStringUnit(0) +
548 " " + trans.get("simedtdlg.lbl.ttip.Direction2") + " " +
549 UnitGroup.UNITS_ANGLE.toStringUnit(Math.PI) +
550 " " + trans.get("simedtdlg.lbl.ttip.Direction3");
551 label.setToolTipText(tip);
554 m = new DoubleModel(conditions, "LaunchRodDirection", UnitGroup.UNITS_ANGLE,
557 spin = new JSpinner(m.getSpinnerModel());
558 spin.setEditor(new SpinnerEditor(spin));
559 spin.setToolTipText(tip);
560 sub.add(spin, "w 65lp!");
562 unit = new UnitSelector(m);
563 unit.setToolTipText(tip);
564 sub.add(unit, "growx");
565 slider = new BasicSlider(m.getSliderModel(-Math.PI, Math.PI));
566 slider.setToolTipText(tip);
567 sub.add(slider, "w 75lp, wrap");
573 private String getIntensityDescription(double i) {
576 return trans.get("simedtdlg.IntensityDesc.None");
579 return trans.get("simedtdlg.IntensityDesc.Verylow");
582 return trans.get("simedtdlg.IntensityDesc.Low");
585 return trans.get("simedtdlg.IntensityDesc.Medium");
588 return trans.get("simedtdlg.IntensityDesc.High");
591 return trans.get("simedtdlg.IntensityDesc.Veryhigh");
593 return trans.get("simedtdlg.IntensityDesc.Extreme");
598 private JPanel simulationOptionsTab() {
599 JPanel panel = new JPanel(new MigLayout("fill"));
609 //// Simulation options
610 sub = new JPanel(new MigLayout("fill, gap rel unrel",
611 "[grow][65lp!][30lp!][75lp!]", ""));
612 //// Simulator options
613 sub.setBorder(BorderFactory.createTitledBorder(trans.get("simedtdlg.border.Simopt")));
614 panel.add(sub, "growx, growy, aligny 0");
617 // Separate panel for computation methods, as they use a different layout
618 subsub = new JPanel(new MigLayout("insets 0, fill"));
621 //// Calculation method:
622 tip = trans.get("simedtdlg.lbl.ttip.Calcmethod");
623 label = new JLabel(trans.get("simedtdlg.lbl.Calcmethod"));
624 label.setToolTipText(tip);
625 subsub.add(label, "gapright para");
627 //// Extended Barrowman
628 label = new JLabel(trans.get("simedtdlg.lbl.ExtBarrowman"));
629 label.setToolTipText(tip);
630 subsub.add(label, "growx, wrap para");
634 tip = trans.get("simedtdlg.lbl.ttip.Simmethod1") +
635 trans.get("simedtdlg.lbl.ttip.Simmethod2");
636 label = new JLabel(trans.get("simedtdlg.lbl.Simmethod"));
637 label.setToolTipText(tip);
638 subsub.add(label, "gapright para");
640 label = new JLabel("6-DOF Runge-Kutta 4");
641 label.setToolTipText(tip);
642 subsub.add(label, "growx, wrap para");
645 //// Geodetic calculation method:
646 label = new JLabel(trans.get("simedtdlg.lbl.GeodeticMethod"));
647 label.setToolTipText(trans.get("simedtdlg.lbl.ttip.GeodeticMethodTip"));
648 subsub.add(label, "gapright para");
650 EnumModel<GeodeticComputationStrategy> gcsModel = new EnumModel<GeodeticComputationStrategy>(conditions, "GeodeticComputation");
651 final JComboBox gcsCombo = new JComboBox(gcsModel);
652 ActionListener gcsTTipListener = new ActionListener() {
654 public void actionPerformed(ActionEvent e) {
655 GeodeticComputationStrategy gcs = (GeodeticComputationStrategy) gcsCombo.getSelectedItem();
656 gcsCombo.setToolTipText(gcs.getDescription());
659 gcsCombo.addActionListener(gcsTTipListener);
660 gcsTTipListener.actionPerformed(null);
661 subsub.add(gcsCombo, "growx, wrap para");
663 sub.add(subsub, "spanx, wrap para");
667 label = new JLabel(trans.get("simedtdlg.lbl.Timestep"));
668 tip = trans.get("simedtdlg.lbl.ttip.Timestep1") +
669 trans.get("simedtdlg.lbl.ttip.Timestep2") + " " +
670 UnitGroup.UNITS_TIME_STEP.toStringUnit(RK4SimulationStepper.RECOMMENDED_TIME_STEP) +
672 label.setToolTipText(tip);
675 m = new DoubleModel(conditions, "TimeStep", UnitGroup.UNITS_TIME_STEP, 0, 1);
677 spin = new JSpinner(m.getSpinnerModel());
678 spin.setEditor(new SpinnerEditor(spin));
679 spin.setToolTipText(tip);
680 sub.add(spin, "w 65lp!");
681 //sub.add(spin, "nogrid");
683 unit = new UnitSelector(m);
684 unit.setToolTipText(tip);
685 sub.add(unit, "w 25");
686 //sub.add(unit, "nogrid");
687 slider = new BasicSlider(m.getSliderModel(0, 0.2));
688 slider.setToolTipText(tip);
689 sub.add(slider, "w 75lp, wrap");
690 //sub.add(slider,"wrap");
695 //// Reset to default button
696 JButton button = new JButton(trans.get("simedtdlg.but.resettodefault"));
697 //// Reset the time step to its default value (
698 button.setToolTipText(trans.get("simedtdlg.but.ttip.resettodefault") +
699 UnitGroup.UNITS_SHORT_TIME.toStringUnit(RK4SimulationStepper.RECOMMENDED_TIME_STEP) +
701 button.addActionListener(new ActionListener() {
703 public void actionPerformed(ActionEvent e) {
704 conditions.setTimeStep(RK4SimulationStepper.RECOMMENDED_TIME_STEP);
705 conditions.setGeodeticComputation(GeodeticComputationStrategy.SPHERICAL);
709 sub.add(button, "align left");
714 //// Simulation listeners
715 sub = new JPanel(new MigLayout("fill, gap 0 0"));
716 //// Simulator listeners
717 sub.setBorder(BorderFactory.createTitledBorder(trans.get("simedtdlg.border.Simlist")));
718 panel.add(sub, "growx, growy");
721 DescriptionArea desc = new DescriptionArea(5);
722 //// <html><i>Simulation listeners</i> is an advanced feature that allows user-written code to listen to and interact with the simulation.
723 //// For details on writing simulation listeners, see the OpenRocket technical documentation.
724 desc.setText(trans.get("simedtdlg.txt.longA1") +
725 trans.get("simedtdlg.txt.longA2"));
726 sub.add(desc, "aligny 0, growx, wrap para");
728 //// Current listeners:
729 label = new JLabel(trans.get("simedtdlg.lbl.Curlist"));
730 sub.add(label, "spanx, wrap rel");
732 final ListenerListModel listenerModel = new ListenerListModel();
733 final JList list = new JList(listenerModel);
734 list.setCellRenderer(new ListenerCellRenderer());
735 JScrollPane scroll = new JScrollPane(list);
736 // scroll.setPreferredSize(new Dimension(1,1));
737 sub.add(scroll, "height 1px, grow, wrap rel");
740 button = new JButton(trans.get("simedtdlg.but.add"));
741 button.addActionListener(new ActionListener() {
743 public void actionPerformed(ActionEvent e) {
744 String previous = Application.getPreferences().getString("previousListenerName", "");
745 String input = (String) JOptionPane.showInputDialog(SimulationEditDialog.this,
747 //// Type the full Java class name of the simulation listener, for example:
748 "Type the full Java class name of the simulation listener, for example:",
749 "<html><tt>" + CSVSaveListener.class.getName() + "</tt>" },
750 //// Add simulation listener
751 trans.get("simedtdlg.lbl.Addsimlist"),
752 JOptionPane.QUESTION_MESSAGE,
756 if (input == null || input.equals(""))
759 Application.getPreferences().putString("previousListenerName", input);
760 simulation.getSimulationListeners().add(input);
761 listenerModel.fireContentsChanged();
764 sub.add(button, "split 2, sizegroup buttons, alignx 50%, gapright para");
767 button = new JButton(trans.get("simedtdlg.but.remove"));
768 button.addActionListener(new ActionListener() {
770 public void actionPerformed(ActionEvent e) {
771 int[] selected = list.getSelectedIndices();
772 Arrays.sort(selected);
773 for (int i = selected.length - 1; i >= 0; i--) {
774 simulation.getSimulationListeners().remove(selected[i]);
776 listenerModel.fireContentsChanged();
779 sub.add(button, "sizegroup buttons, alignx 50%");
786 private class ListenerListModel extends AbstractListModel {
788 public String getElementAt(int index) {
789 if (index < 0 || index >= getSize())
791 return simulation.getSimulationListeners().get(index);
795 public int getSize() {
796 return simulation.getSimulationListeners().size();
799 public void fireContentsChanged() {
800 super.fireContentsChanged(this, 0, getSize());
808 * A panel for plotting the previously calculated data.
810 private JPanel plotTab() {
812 // Check that data exists
813 if (simulation.getSimulatedData() == null ||
814 simulation.getSimulatedData().getBranchCount() == 0) {
815 return noDataPanel();
818 return new SimulationPlotPanel(simulation);
824 * A panel for exporting the data.
826 private JPanel exportTab() {
827 FlightData data = simulation.getSimulatedData();
829 // Check that data exists
830 if (data == null || data.getBranchCount() == 0 ||
831 data.getBranch(0).getTypes().length == 0) {
832 return noDataPanel();
835 return new SimulationExportPanel(simulation);
840 * Return a panel stating that there is no data available, and that the user
841 * should run the simulation first.
843 public static JPanel noDataPanel() {
844 JPanel panel = new JPanel(new MigLayout("fill"));
847 //// No flight data available.
848 panel.add(new JLabel(trans.get("simedtdlg.lbl.Noflightdata")),
849 "alignx 50%, aligny 100%, wrap para");
850 //// Please run the simulation first.
851 panel.add(new JLabel(trans.get("simedtdlg.lbl.runsimfirst")),
852 "alignx 50%, aligny 0%, wrap");
857 private void performPlot(PlotConfiguration config) {
859 // Fill the auto-selections
860 FlightDataBranch branch = simulation.getSimulatedData().getBranch(0);
861 PlotConfiguration filled = config.fillAutoAxes(branch);
862 List<Axis> axes = filled.getAllAxes();
865 // Create the data series for both axes
866 XYSeriesCollection[] data = new XYSeriesCollection[2];
867 data[0] = new XYSeriesCollection();
868 data[1] = new XYSeriesCollection();
871 // Get the domain axis type
872 final FlightDataType domainType = filled.getDomainAxisType();
873 final Unit domainUnit = filled.getDomainAxisUnit();
874 if (domainType == null) {
875 throw new IllegalArgumentException("Domain axis type not specified.");
877 List<Double> x = branch.get(domainType);
880 // Create the XYSeries objects from the flight data and store into the collections
881 int length = filled.getTypeCount();
882 String[] axisLabel = new String[2];
883 for (int i = 0; i < length; i++) {
885 FlightDataType type = filled.getType(i);
886 Unit unit = filled.getUnit(i);
887 int axis = filled.getAxis(i);
888 String name = getLabel(type, unit);
890 // Store data in provided units
891 List<Double> y = branch.get(type);
892 XYSeries series = new XYSeries(name, false, true);
893 for (int j = 0; j < x.size(); j++) {
894 series.add(domainUnit.toUnit(x.get(j)), unit.toUnit(y.get(j)));
896 data[axis].addSeries(series);
899 if (axisLabel[axis] == null)
900 axisLabel[axis] = type.getName();
902 axisLabel[axis] += "; " + type.getName();
906 // Create the chart using the factory to get all default settings
907 JFreeChart chart = ChartFactory.createXYLineChart(
908 //// Simulated flight
909 trans.get("simedtdlg.chart.Simflight"),
913 PlotOrientation.VERTICAL,
920 // Add the data and formatting to the plot
921 XYPlot plot = chart.getXYPlot();
923 for (int i = 0; i < 2; i++) {
924 // Check whether axis has any data
925 if (data[i].getSeriesCount() > 0) {
926 // Create and set axis
927 double min = axes.get(i).getMinValue();
928 double max = axes.get(i).getMaxValue();
929 NumberAxis axis = new PresetNumberAxis(min, max);
930 axis.setLabel(axisLabel[i]);
931 // axis.setRange(axes.get(i).getMinValue(), axes.get(i).getMaxValue());
932 plot.setRangeAxis(axisno, axis);
934 // Add data and map to the axis
935 plot.setDataset(axisno, data[i]);
936 plot.setRenderer(axisno, new StandardXYItemRenderer());
937 plot.mapDatasetToRangeAxis(axisno, axisno);
942 plot.getDomainAxis().setLabel(getLabel(domainType, domainUnit));
943 plot.addDomainMarker(new ValueMarker(0));
944 plot.addRangeMarker(new ValueMarker(0));
948 //// Simulation results
949 final JDialog dialog = new JDialog(this, trans.get("simedtdlg.dlg.Simres"));
950 dialog.setModalityType(ModalityType.DOCUMENT_MODAL);
952 JPanel panel = new JPanel(new MigLayout("fill"));
955 ChartPanel chartPanel = new ChartPanel(chart,
961 chartPanel.setMouseWheelEnabled(true);
962 chartPanel.setEnforceFileExtensions(true);
963 chartPanel.setInitialDelay(500);
965 chartPanel.setBorder(BorderFactory.createLineBorder(Color.GRAY, 1));
967 panel.add(chartPanel, "grow, wrap 20lp");
970 JButton button = new JButton(trans.get("dlg.but.close"));
971 button.addActionListener(new ActionListener() {
973 public void actionPerformed(ActionEvent e) {
974 dialog.setVisible(false);
977 panel.add(button, "right");
979 dialog.setLocationByPlatform(true);
982 GUIUtil.setDisposableDialogOptions(dialog, button);
984 dialog.setVisible(true);
988 private class PresetNumberAxis extends NumberAxis {
989 private final double min;
990 private final double max;
992 public PresetNumberAxis(double min, double max) {
999 protected void autoAdjustRange() {
1000 this.setRange(min, max);
1005 private String getLabel(FlightDataType type, Unit unit) {
1006 String name = type.getName();
1007 if (unit != null && !UnitGroup.UNITS_NONE.contains(unit) &&
1008 !UnitGroup.UNITS_COEFFICIENT.contains(unit) && unit.getUnit().length() > 0)
1009 name += " (" + unit.getUnit() + ")";
1015 private class ListenerCellRenderer extends JLabel implements ListCellRenderer {
1018 public Component getListCellRendererComponent(JList list, Object value,
1019 int index, boolean isSelected, boolean cellHasFocus) {
1020 String s = value.toString();
1023 // Attempt instantiating, catch any exceptions
1024 Exception ex = null;
1026 Class<?> c = Class.forName(s);
1027 @SuppressWarnings("unused")
1028 SimulationListener l = (SimulationListener) c.newInstance();
1029 } catch (Exception e) {
1034 setIcon(Icons.SIMULATION_LISTENER_OK);
1035 //// Listener instantiated successfully.
1036 setToolTipText("Listener instantiated successfully.");
1038 setIcon(Icons.SIMULATION_LISTENER_ERROR);
1039 //// <html>Unable to instantiate listener due to exception:<br>
1040 setToolTipText("<html>Unable to instantiate listener due to exception:<br>" +
1045 setBackground(list.getSelectionBackground());
1046 setForeground(list.getSelectionForeground());
1048 setBackground(list.getBackground());
1049 setForeground(list.getForeground());