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.MotorConfigurationModel;
38 import net.sf.openrocket.gui.components.BasicSlider;
39 import net.sf.openrocket.gui.components.DescriptionArea;
40 import net.sf.openrocket.gui.components.SimulationExportPanel;
41 import net.sf.openrocket.gui.components.UnitSelector;
42 import net.sf.openrocket.gui.plot.Axis;
43 import net.sf.openrocket.gui.plot.PlotConfiguration;
44 import net.sf.openrocket.gui.plot.SimulationPlotPanel;
45 import net.sf.openrocket.l10n.Translator;
46 import net.sf.openrocket.models.atmosphere.ExtendedISAModel;
47 import net.sf.openrocket.rocketcomponent.Configuration;
48 import net.sf.openrocket.simulation.FlightData;
49 import net.sf.openrocket.simulation.FlightDataBranch;
50 import net.sf.openrocket.simulation.RK4SimulationStepper;
51 import net.sf.openrocket.simulation.GUISimulationConditions;
52 import net.sf.openrocket.simulation.FlightDataType;
53 import net.sf.openrocket.simulation.listeners.SimulationListener;
54 import net.sf.openrocket.simulation.listeners.example.CSVSaveListener;
55 import net.sf.openrocket.startup.Application;
56 import net.sf.openrocket.unit.Unit;
57 import net.sf.openrocket.unit.UnitGroup;
58 import net.sf.openrocket.util.Chars;
59 import net.sf.openrocket.util.GUIUtil;
60 import net.sf.openrocket.util.Icons;
61 import net.sf.openrocket.util.Prefs;
63 import org.jfree.chart.ChartFactory;
64 import org.jfree.chart.ChartPanel;
65 import org.jfree.chart.JFreeChart;
66 import org.jfree.chart.axis.NumberAxis;
67 import org.jfree.chart.plot.PlotOrientation;
68 import org.jfree.chart.plot.ValueMarker;
69 import org.jfree.chart.plot.XYPlot;
70 import org.jfree.chart.renderer.xy.StandardXYItemRenderer;
71 import org.jfree.data.xy.XYSeries;
72 import org.jfree.data.xy.XYSeriesCollection;
75 public class SimulationEditDialog extends JDialog {
77 public static final int DEFAULT = -1;
78 public static final int EDIT = 1;
79 public static final int PLOT = 2;
82 private final Window parentWindow;
83 private final Simulation simulation;
84 private final GUISimulationConditions conditions;
85 private final Configuration configuration;
86 private static final Translator trans = Application.getTranslator();
89 public SimulationEditDialog(Window parent, Simulation s) {
93 public SimulationEditDialog(Window parent, Simulation s, int tab) {
95 super(parent, trans.get("simedtdlg.title.Editsim"), JDialog.ModalityType.DOCUMENT_MODAL);
97 this.parentWindow = parent;
99 this.conditions = simulation.getConditions();
100 configuration = simulation.getConfiguration();
102 JPanel mainPanel = new JPanel(new MigLayout("fill","[grow, fill]"));
104 //// Simulation name:
105 mainPanel.add(new JLabel(trans.get("simedtdlg.lbl.Simname") + " "), "span, split 2, shrink");
106 final JTextField field = new JTextField(simulation.getName());
107 field.getDocument().addDocumentListener(new DocumentListener() {
109 public void changedUpdate(DocumentEvent e) {
113 public void insertUpdate(DocumentEvent e) {
117 public void removeUpdate(DocumentEvent e) {
120 private void setText() {
121 String name = field.getText();
122 if (name == null || name.equals(""))
124 System.out.println("Setting name:"+name);
125 simulation.setName(name);
129 mainPanel.add(field, "shrinky, growx, wrap");
131 JTabbedPane tabbedPane = new JTabbedPane();
133 //// Launch conditions
134 tabbedPane.addTab(trans.get("simedtdlg.tab.Launchcond"), flightConditionsTab());
135 //// Simulation options
136 tabbedPane.addTab(trans.get("simedtdlg.tab.Simopt"), simulationOptionsTab());
138 tabbedPane.addTab(trans.get("simedtdlg.tab.Plotdata"), plotTab());
140 tabbedPane.addTab(trans.get("simedtdlg.tab.Exportdata"), exportTab());
142 // Select the initial tab
144 tabbedPane.setSelectedIndex(0);
145 } else if (tab == PLOT) {
146 tabbedPane.setSelectedIndex(2);
148 FlightData data = s.getSimulatedData();
149 if (data == null || data.getBranchCount() == 0)
150 tabbedPane.setSelectedIndex(0);
152 tabbedPane.setSelectedIndex(2);
155 mainPanel.add(tabbedPane, "spanx, grow, wrap");
159 mainPanel.add(new JPanel(), "spanx, split, growx");
162 //// Run simulation button
163 button = new JButton(trans.get("simedtdlg.but.runsimulation"));
164 button.addActionListener(new ActionListener() {
166 public void actionPerformed(ActionEvent e) {
167 SimulationEditDialog.this.dispose();
168 SimulationRunDialog.runSimulations(parentWindow, simulation);
171 mainPanel.add(button, "gapright para");
174 JButton close = new JButton(trans.get("dlg.but.close"));
175 close.addActionListener(new ActionListener() {
177 public void actionPerformed(ActionEvent e) {
178 SimulationEditDialog.this.dispose();
181 mainPanel.add(close, "");
187 this.setLocationByPlatform(true);
189 GUIUtil.setDisposableDialogOptions(this, button);
196 private JPanel flightConditionsTab() {
197 JPanel panel = new JPanel(new MigLayout("fill"));
206 //// Motor configuration:
207 JLabel label = new JLabel(trans.get("simedtdlg.lbl.Motorcfg"));
208 //// Select the motor configuration to use.
209 label.setToolTipText(trans.get("simedtdlg.lbl.ttip.Motorcfg"));
210 panel.add(label, "shrinkx, spanx, split 2");
212 JComboBox combo = new JComboBox(new MotorConfigurationModel(configuration));
213 //// Select the motor configuration to use.
214 combo.setToolTipText(trans.get("simedtdlg.combo.ttip.motorconf"));
215 combo.addActionListener(new ActionListener() {
217 public void actionPerformed(ActionEvent e) {
218 conditions.setMotorConfigurationID(configuration.getMotorConfigurationID());
221 panel.add(combo, "growx, wrap para");
224 //// Wind settings: Average wind speed, turbulence intensity, std. deviation
225 sub = new JPanel(new MigLayout("fill, gap rel unrel",
226 "[grow][65lp!][30lp!][75lp!]",""));
228 sub.setBorder(BorderFactory.createTitledBorder(trans.get("simedtdlg.lbl.Wind")));
229 panel.add(sub, "growx, split 2, aligny 0, flowy, gapright para");
233 //// Average windspeed:
234 label = new JLabel(trans.get("simedtdlg.lbl.Averwindspeed"));
235 //// The average windspeed relative to the ground.
236 tip = trans.get("simedtdlg.lbl.ttip.Averwindspeed");
237 label.setToolTipText(tip);
240 m = new DoubleModel(conditions,"WindSpeedAverage", UnitGroup.UNITS_VELOCITY,0);
242 spin = new JSpinner(m.getSpinnerModel());
243 spin.setEditor(new SpinnerEditor(spin));
244 spin.setToolTipText(tip);
245 sub.add(spin,"w 65lp!");
247 unit = new UnitSelector(m);
248 unit.setToolTipText(tip);
249 sub.add(unit,"growx");
250 slider = new BasicSlider(m.getSliderModel(0, 10.0));
251 slider.setToolTipText(tip);
252 sub.add(slider,"w 75lp, wrap");
256 // Wind std. deviation
257 //// Standard deviation:
258 label = new JLabel(trans.get("simedtdlg.lbl.Stddeviation"));
259 //// <html>The standard deviation of the windspeed.<br>
260 //// The windspeed is within twice the standard deviation from the average for 95% of the time.
261 tip = trans.get("simedtdlg.lbl.ttip.Stddeviation");
262 label.setToolTipText(tip);
265 m = new DoubleModel(conditions,"WindSpeedDeviation", UnitGroup.UNITS_VELOCITY,0);
266 DoubleModel m2 = new DoubleModel(conditions,"WindSpeedAverage", 0.25,
267 UnitGroup.UNITS_COEFFICIENT,0);
269 spin = new JSpinner(m.getSpinnerModel());
270 spin.setEditor(new SpinnerEditor(spin));
271 spin.setToolTipText(tip);
272 sub.add(spin,"w 65lp!");
274 unit = new UnitSelector(m);
275 unit.setToolTipText(tip);
276 sub.add(unit,"growx");
277 slider = new BasicSlider(m.getSliderModel(new DoubleModel(0), m2));
278 slider.setToolTipText(tip);
279 sub.add(slider,"w 75lp, wrap");
282 // Wind turbulence intensity
283 //// Turbulence intensity:
284 label = new JLabel(trans.get("simedtdlg.lbl.Turbulenceintensity"));
285 //// <html>The turbulence intensity is the standard deviation divided by the average windspeed.<br>
286 //// Typical values range from
288 tip = trans.get("simedtdlg.lbl.ttip.Turbulenceintensity1") +
289 trans.get("simedtdlg.lbl.ttip.Turbulenceintensity2") + " "+
290 UnitGroup.UNITS_RELATIVE.getDefaultUnit().toStringUnit(0.05) +
291 " " + trans.get("simedtdlg.lbl.ttip.Turbulenceintensity3") +" " +
292 UnitGroup.UNITS_RELATIVE.getDefaultUnit().toStringUnit(0.20) + ".";
293 label.setToolTipText(tip);
296 m = new DoubleModel(conditions,"WindTurbulenceIntensity", UnitGroup.UNITS_RELATIVE,0);
298 spin = new JSpinner(m.getSpinnerModel());
299 spin.setEditor(new SpinnerEditor(spin));
300 spin.setToolTipText(tip);
301 sub.add(spin,"w 65lp!");
303 unit = new UnitSelector(m);
304 unit.setToolTipText(tip);
305 sub.add(unit,"growx");
307 final JLabel intensityLabel = new JLabel(
308 getIntensityDescription(conditions.getWindTurbulenceIntensity()));
309 intensityLabel.setToolTipText(tip);
310 sub.add(intensityLabel,"w 75lp, wrap");
311 m.addChangeListener(new ChangeListener() {
313 public void stateChanged(ChangeEvent e) {
314 intensityLabel.setText(
315 getIntensityDescription(conditions.getWindTurbulenceIntensity()));
323 //// Temperature and pressure
324 sub = new JPanel(new MigLayout("fill, gap rel unrel",
325 "[grow][65lp!][30lp!][75lp!]",""));
326 //// Atmospheric conditions
327 sub.setBorder(BorderFactory.createTitledBorder(trans.get("simedtdlg.border.Atmoscond")));
328 panel.add(sub, "growx, aligny 0, gapright para");
331 BooleanModel isa = new BooleanModel(conditions, "ISAAtmosphere");
332 JCheckBox check = new JCheckBox(isa);
333 //// Use International Standard Atmosphere
334 check.setText(trans.get("simedtdlg.checkbox.InterStdAtmosphere"));
335 //// <html>Select to use the International Standard Atmosphere model.
336 //// <br>This model has a temperature of
337 //// and a pressure of
339 check.setToolTipText(trans.get("simedtdlg.checkbox.ttip.InterStdAtmosphere1") +" " +
340 UnitGroup.UNITS_TEMPERATURE.toStringUnit(ExtendedISAModel.STANDARD_TEMPERATURE)+
341 " " + trans.get("simedtdlg.checkbox.ttip.InterStdAtmosphere2") + " " +
342 UnitGroup.UNITS_PRESSURE.toStringUnit(ExtendedISAModel.STANDARD_PRESSURE) +
343 " " + trans.get("simedtdlg.checkbox.ttip.InterStdAtmosphere3"));
344 sub.add(check, "spanx, wrap unrel");
347 label = new JLabel(trans.get("simedtdlg.lbl.Temperature"));
348 //// The temperature at the launch site.
349 tip = trans.get("simedtdlg.lbl.ttip.Temperature");
350 label.setToolTipText(tip);
351 isa.addEnableComponent(label, false);
354 m = new DoubleModel(conditions,"LaunchTemperature", UnitGroup.UNITS_TEMPERATURE,0);
356 spin = new JSpinner(m.getSpinnerModel());
357 spin.setEditor(new SpinnerEditor(spin));
358 spin.setToolTipText(tip);
359 isa.addEnableComponent(spin, false);
360 sub.add(spin,"w 65lp!");
362 unit = new UnitSelector(m);
363 unit.setToolTipText(tip);
364 isa.addEnableComponent(unit, false);
365 sub.add(unit,"growx");
366 slider = new BasicSlider(m.getSliderModel(253.15, 308.15)); // -20 ... 35
367 slider.setToolTipText(tip);
368 isa.addEnableComponent(slider, false);
369 sub.add(slider,"w 75lp, wrap");
374 label = new JLabel(trans.get("simedtdlg.lbl.Pressure"));
375 //// The atmospheric pressure at the launch site.
376 tip = trans.get("simedtdlg.lbl.ttip.Pressure");
377 label.setToolTipText(tip);
378 isa.addEnableComponent(label, false);
381 m = new DoubleModel(conditions,"LaunchPressure", UnitGroup.UNITS_PRESSURE,0);
383 spin = new JSpinner(m.getSpinnerModel());
384 spin.setEditor(new SpinnerEditor(spin));
385 spin.setToolTipText(tip);
386 isa.addEnableComponent(spin, false);
387 sub.add(spin,"w 65lp!");
389 unit = new UnitSelector(m);
390 unit.setToolTipText(tip);
391 isa.addEnableComponent(unit, false);
392 sub.add(unit,"growx");
393 slider = new BasicSlider(m.getSliderModel(0.950e5, 1.050e5));
394 slider.setToolTipText(tip);
395 isa.addEnableComponent(slider, false);
396 sub.add(slider,"w 75lp, wrap");
402 //// Launch site conditions
403 sub = new JPanel(new MigLayout("fill, gap rel unrel",
404 "[grow][65lp!][30lp!][75lp!]",""));
406 sub.setBorder(BorderFactory.createTitledBorder(trans.get("simedtdlg.lbl.Launchsite")));
407 panel.add(sub, "growx, split 2, aligny 0, flowy");
411 label = new JLabel(trans.get("simedtdlg.lbl.Latitude"));
412 //// <html>The launch site latitude affects the gravitational pull of Earth.<br>
413 //// Positive values are on the Northern hemisphere, negative values on the Southern hemisphere.
414 tip = trans.get("simedtdlg.lbl.ttip.Latitude");
415 label.setToolTipText(tip);
418 m = new DoubleModel(conditions,"LaunchLatitude", UnitGroup.UNITS_NONE, -90, 90);
420 spin = new JSpinner(m.getSpinnerModel());
421 spin.setEditor(new SpinnerEditor(spin));
422 spin.setToolTipText(tip);
423 sub.add(spin,"w 65lp!");
425 label = new JLabel(Chars.DEGREE + " N");
426 label.setToolTipText(tip);
427 sub.add(label,"growx");
428 slider = new BasicSlider(m.getSliderModel(-90, 90));
429 slider.setToolTipText(tip);
430 sub.add(slider,"w 75lp, wrap");
435 label = new JLabel(trans.get("simedtdlg.lbl.Altitude"));
436 //// <html>The launch altitude above mean sea level.<br>
437 //// This affects the position of the rocket in the atmospheric model.
438 tip = trans.get("simedtdlg.lbl.ttip.Altitude");
439 label.setToolTipText(tip);
442 m = new DoubleModel(conditions,"LaunchAltitude", UnitGroup.UNITS_DISTANCE,0);
444 spin = new JSpinner(m.getSpinnerModel());
445 spin.setEditor(new SpinnerEditor(spin));
446 spin.setToolTipText(tip);
447 sub.add(spin,"w 65lp!");
449 unit = new UnitSelector(m);
450 unit.setToolTipText(tip);
451 sub.add(unit,"growx");
452 slider = new BasicSlider(m.getSliderModel(0, 250, 1000));
453 slider.setToolTipText(tip);
454 sub.add(slider,"w 75lp, wrap");
461 sub = new JPanel(new MigLayout("fill, gap rel unrel",
462 "[grow][65lp!][30lp!][75lp!]",""));
464 sub.setBorder(BorderFactory.createTitledBorder(trans.get("simedtdlg.border.Launchrod")));
465 panel.add(sub, "growx, aligny 0, wrap");
469 label = new JLabel(trans.get("simedtdlg.lbl.Length"));
470 //// The length of the launch rod.
471 tip = trans.get("simedtdlg.lbl.ttip.Length");
472 label.setToolTipText(tip);
475 m = new DoubleModel(conditions,"LaunchRodLength", UnitGroup.UNITS_LENGTH, 0);
477 spin = new JSpinner(m.getSpinnerModel());
478 spin.setEditor(new SpinnerEditor(spin));
479 spin.setToolTipText(tip);
480 sub.add(spin,"w 65lp!");
482 unit = new UnitSelector(m);
483 unit.setToolTipText(tip);
484 sub.add(unit,"growx");
485 slider = new BasicSlider(m.getSliderModel(0, 1, 5));
486 slider.setToolTipText(tip);
487 sub.add(slider,"w 75lp, wrap");
492 label = new JLabel(trans.get("simedtdlg.lbl.Angle"));
493 //// The angle of the launch rod from vertical.
494 tip = trans.get("simedtdlg.lbl.ttip.Angle");
495 label.setToolTipText(tip);
498 m = new DoubleModel(conditions,"LaunchRodAngle", UnitGroup.UNITS_ANGLE,
499 0, GUISimulationConditions.MAX_LAUNCH_ROD_ANGLE);
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, Math.PI/9,
510 GUISimulationConditions.MAX_LAUNCH_ROD_ANGLE));
511 slider.setToolTipText(tip);
512 sub.add(slider,"w 75lp, wrap");
517 label = new JLabel(trans.get("simedtdlg.lbl.Direction"));
518 //// <html>Direction of the launch rod relative to the wind.<br>
519 //// = towards the wind,
521 tip = trans.get("simedtdlg.lbl.ttip.Direction1") +
522 UnitGroup.UNITS_ANGLE.toStringUnit(0) +
523 " " + trans.get("simedtdlg.lbl.ttip.Direction2") + " "+
524 UnitGroup.UNITS_ANGLE.toStringUnit(Math.PI) +
525 " " + trans.get("simedtdlg.lbl.ttip.Direction3");
526 label.setToolTipText(tip);
529 m = new DoubleModel(conditions,"LaunchRodDirection", UnitGroup.UNITS_ANGLE,
532 spin = new JSpinner(m.getSpinnerModel());
533 spin.setEditor(new SpinnerEditor(spin));
534 spin.setToolTipText(tip);
535 sub.add(spin,"w 65lp!");
537 unit = new UnitSelector(m);
538 unit.setToolTipText(tip);
539 sub.add(unit,"growx");
540 slider = new BasicSlider(m.getSliderModel(-Math.PI, Math.PI));
541 slider.setToolTipText(tip);
542 sub.add(slider,"w 75lp, wrap");
548 private String getIntensityDescription(double i) {
551 return trans.get("simedtdlg.IntensityDesc.None");
554 return trans.get("simedtdlg.IntensityDesc.Verylow");
557 return trans.get("simedtdlg.IntensityDesc.Low");
560 return trans.get("simedtdlg.IntensityDesc.Medium");
563 return trans.get("simedtdlg.IntensityDesc.High");
566 return trans.get("simedtdlg.IntensityDesc.Veryhigh");
568 return trans.get("simedtdlg.IntensityDesc.Extreme");
573 private JPanel simulationOptionsTab() {
574 JPanel panel = new JPanel(new MigLayout("fill"));
584 //// Simulation options
585 sub = new JPanel(new MigLayout("fill, gap rel unrel",
586 "[grow][65lp!][30lp!][75lp!]",""));
587 //// Simulator options
588 sub.setBorder(BorderFactory.createTitledBorder(trans.get("simedtdlg.border.Simopt")));
589 panel.add(sub, "w 330lp!, growy, aligny 0");
592 // Calculation method
593 //// <html>The Extended Barrowman method calculates aerodynamic forces according <br>
594 //// to the Barrowman equations extended to accommodate more components.
595 tip = trans.get("simedtdlg.lbl.ttip.Calcmethod");
597 //// Calculation method:
598 label = new JLabel(trans.get("simedtdlg.lbl.Calcmethod"));
599 label.setToolTipText(tip);
600 sub.add(label, "gaptop unrel, gapright para, spanx, split 2, w 150lp!");
602 //// Extended Barrowman
603 label = new JLabel(trans.get("simedtdlg.lbl.ExtBarrowman"));
604 label.setToolTipText(tip);
605 sub.add(label, "growx, wrap para");
609 //// <html>The six degree-of-freedom simulator allows the rocket total freedom during flight.<br>
610 //// Integration is performed using a 4<sup>th</sup> order Runge-Kutta 4 numerical integration.
611 tip = trans.get("simedtdlg.lbl.ttip.Simmethod1") +
612 trans.get("simedtdlg.lbl.ttip.Simmethod2");
614 //// Simulation method:
615 label = new JLabel(trans.get("simedtdlg.lbl.Simmethod"));
616 label.setToolTipText(tip);
617 sub.add(label, "gaptop unrel, gapright para, spanx, split 2, w 150lp!");
619 label = new JLabel("6-DOF Runge-Kutta 4");
620 label.setToolTipText(tip);
621 sub.add(label, "growx, wrap 35lp");
626 label = new JLabel(trans.get("simedtdlg.lbl.Timestep"));
627 //// <html>The time between simulation steps.<br>
628 //// A smaller time step results in a more accurate but slower simulation.<br>
629 //// The 4<sup>th</sup> order simulation method is quite accurate with a time step of
630 tip = trans.get("simedtdlg.lbl.ttip.Timestep1") +
631 trans.get("simedtdlg.lbl.ttip.Timestep2") + " " +
632 UnitGroup.UNITS_TIME_STEP.toStringUnit(RK4SimulationStepper.RECOMMENDED_TIME_STEP) +
634 label.setToolTipText(tip);
637 m = new DoubleModel(conditions,"TimeStep", UnitGroup.UNITS_TIME_STEP, 0, 1);
639 spin = new JSpinner(m.getSpinnerModel());
640 spin.setEditor(new SpinnerEditor(spin));
641 spin.setToolTipText(tip);
642 sub.add(spin,"w 65lp!");
644 unit = new UnitSelector(m);
645 unit.setToolTipText(tip);
646 sub.add(unit,"growx");
647 slider = new BasicSlider(m.getSliderModel(0, 0.2));
648 slider.setToolTipText(tip);
649 sub.add(slider,"w 75lp, wrap");
653 // Maximum angle step
655 label = new JLabel("Max. angle step:");
657 "This defines the maximum angle the rocket will turn during one time step.<br>"+
658 "Smaller values result in a more accurate but possibly slower simulation.<br>"+
659 "A recommended value is " +
660 UnitGroup.UNITS_ANGLE.toStringUnit(RK4Simulator.RECOMMENDED_ANGLE_STEP) + ".";
661 label.setToolTipText(tip);
664 m = new DoubleModel(conditions,"MaximumStepAngle", UnitGroup.UNITS_ANGLE,
665 1*Math.PI/180, Math.PI/9);
667 spin = new JSpinner(m.getSpinnerModel());
668 spin.setEditor(new SpinnerEditor(spin));
669 spin.setToolTipText(tip);
670 sub.add(spin,"w 65lp!");
672 unit = new UnitSelector(m);
673 unit.setToolTipText(tip);
674 sub.add(unit,"growx");
675 slider = new BasicSlider(m.getSliderModel(0, Math.toRadians(10)));
676 slider.setToolTipText(tip);
677 sub.add(slider,"w 75lp, wrap para");
680 //// Reset to default button
681 JButton button = new JButton(trans.get("simedtdlg.but.resettodefault"));
682 //// Reset the time step to its default value (
683 button.setToolTipText(trans.get("simedtdlg.but.ttip.resettodefault") +
684 UnitGroup.UNITS_SHORT_TIME.toStringUnit(RK4SimulationStepper.RECOMMENDED_TIME_STEP) +
686 button.addActionListener(new ActionListener() {
688 public void actionPerformed(ActionEvent e) {
689 conditions.setTimeStep(RK4SimulationStepper.RECOMMENDED_TIME_STEP);
693 // button.setToolTipText("<html>Reset the step value to its default:<br>" +
695 // UnitGroup.UNITS_SHORT_TIME.toStringUnit(RK4Simulator.RECOMMENDED_TIME_STEP) +
696 // "; maximum angle step " +
697 // UnitGroup.UNITS_ANGLE.toStringUnit(RK4Simulator.RECOMMENDED_ANGLE_STEP) + ".");
698 sub.add(button, "spanx, tag right, wrap para");
703 //// Simulation listeners
704 sub = new JPanel(new MigLayout("fill, gap 0 0"));
705 //// Simulator listeners
706 sub.setBorder(BorderFactory.createTitledBorder(trans.get("simedtdlg.border.Simlist")));
707 panel.add(sub, "growx, growy");
710 DescriptionArea desc = new DescriptionArea(5);
711 //// <html><i>Simulation listeners</i> is an advanced feature that allows user-written code to listen to and interact with the simulation.
712 //// For details on writing simulation listeners, see the OpenRocket technical documentation.
713 desc.setText(trans.get("simedtdlg.txt.longA1") +
714 trans.get("simedtdlg.txt.longA2"));
715 sub.add(desc, "aligny 0, growx, wrap para");
717 //// Current listeners:
718 label = new JLabel(trans.get("simedtdlg.lbl.Curlist"));
719 sub.add(label, "spanx, wrap rel");
721 final ListenerListModel listenerModel = new ListenerListModel();
722 final JList list = new JList(listenerModel);
723 list.setCellRenderer(new ListenerCellRenderer());
724 JScrollPane scroll = new JScrollPane(list);
725 // scroll.setPreferredSize(new Dimension(1,1));
726 sub.add(scroll, "height 1px, grow, wrap rel");
729 button = new JButton(trans.get("simedtdlg.but.add"));
730 button.addActionListener(new ActionListener() {
732 public void actionPerformed(ActionEvent e) {
733 String previous = Prefs.NODE.get("previousListenerName", "");
734 String input = (String)JOptionPane.showInputDialog(SimulationEditDialog.this,
736 //// Type the full Java class name of the simulation listener, for example:
737 "Type the full Java class name of the simulation listener, for example:",
738 "<html><tt>" + CSVSaveListener.class.getName() + "</tt>" },
739 //// Add simulation listener
740 trans.get("simedtdlg.lbl.Addsimlist"),
741 JOptionPane.QUESTION_MESSAGE,
745 if (input == null || input.equals(""))
748 Prefs.NODE.put("previousListenerName", input);
749 simulation.getSimulationListeners().add(input);
750 listenerModel.fireContentsChanged();
753 sub.add(button, "split 2, sizegroup buttons, alignx 50%, gapright para");
756 button = new JButton(trans.get("simedtdlg.but.remove"));
757 button.addActionListener(new ActionListener() {
759 public void actionPerformed(ActionEvent e) {
760 int[] selected = list.getSelectedIndices();
761 Arrays.sort(selected);
762 for (int i=selected.length-1; i>=0; i--) {
763 simulation.getSimulationListeners().remove(selected[i]);
765 listenerModel.fireContentsChanged();
768 sub.add(button, "sizegroup buttons, alignx 50%");
775 private class ListenerListModel extends AbstractListModel {
777 public String getElementAt(int index) {
778 if (index < 0 || index >= getSize())
780 return simulation.getSimulationListeners().get(index);
783 public int getSize() {
784 return simulation.getSimulationListeners().size();
786 public void fireContentsChanged() {
787 super.fireContentsChanged(this, 0, getSize());
795 * A panel for plotting the previously calculated data.
797 private JPanel plotTab() {
799 // Check that data exists
800 if (simulation.getSimulatedData() == null ||
801 simulation.getSimulatedData().getBranchCount() == 0) {
802 return noDataPanel();
805 return new SimulationPlotPanel(simulation);
811 * A panel for exporting the data.
813 private JPanel exportTab() {
814 FlightData data = simulation.getSimulatedData();
816 // Check that data exists
817 if (data == null || data.getBranchCount() == 0 ||
818 data.getBranch(0).getTypes().length == 0) {
819 return noDataPanel();
822 return new SimulationExportPanel(simulation);
830 * Return a panel stating that there is no data available, and that the user
831 * should run the simulation first.
833 public static JPanel noDataPanel() {
834 JPanel panel = new JPanel(new MigLayout("fill"));
837 //// No flight data available.
838 panel.add(new JLabel(trans.get("simedtdlg.lbl.Noflightdata")),
839 "alignx 50%, aligny 100%, wrap para");
840 //// Please run the simulation first.
841 panel.add(new JLabel(trans.get("simedtdlg.lbl.runsimfirst")),
842 "alignx 50%, aligny 0%, wrap");
847 private void performPlot(PlotConfiguration config) {
849 // Fill the auto-selections
850 FlightDataBranch branch = simulation.getSimulatedData().getBranch(0);
851 PlotConfiguration filled = config.fillAutoAxes(branch);
852 List<Axis> axes = filled.getAllAxes();
855 // Create the data series for both axes
856 XYSeriesCollection[] data = new XYSeriesCollection[2];
857 data[0] = new XYSeriesCollection();
858 data[1] = new XYSeriesCollection();
861 // Get the domain axis type
862 final FlightDataType domainType = filled.getDomainAxisType();
863 final Unit domainUnit = filled.getDomainAxisUnit();
864 if (domainType == null) {
865 throw new IllegalArgumentException("Domain axis type not specified.");
867 List<Double> x = branch.get(domainType);
870 // Create the XYSeries objects from the flight data and store into the collections
871 int length = filled.getTypeCount();
872 String[] axisLabel = new String[2];
873 for (int i = 0; i < length; i++) {
875 FlightDataType type = filled.getType(i);
876 Unit unit = filled.getUnit(i);
877 int axis = filled.getAxis(i);
878 String name = getLabel(type, unit);
880 // Store data in provided units
881 List<Double> y = branch.get(type);
882 XYSeries series = new XYSeries(name, false, true);
883 for (int j=0; j<x.size(); j++) {
884 series.add(domainUnit.toUnit(x.get(j)), unit.toUnit(y.get(j)));
886 data[axis].addSeries(series);
889 if (axisLabel[axis] == null)
890 axisLabel[axis] = type.getName();
892 axisLabel[axis] += "; " + type.getName();
896 // Create the chart using the factory to get all default settings
897 JFreeChart chart = ChartFactory.createXYLineChart(
898 //// Simulated flight
899 trans.get("simedtdlg.chart.Simflight"),
903 PlotOrientation.VERTICAL,
910 // Add the data and formatting to the plot
911 XYPlot plot = chart.getXYPlot();
913 for (int i=0; i<2; i++) {
914 // Check whether axis has any data
915 if (data[i].getSeriesCount() > 0) {
916 // Create and set axis
917 double min = axes.get(i).getMinValue();
918 double max = axes.get(i).getMaxValue();
919 NumberAxis axis = new PresetNumberAxis(min, max);
920 axis.setLabel(axisLabel[i]);
921 // axis.setRange(axes.get(i).getMinValue(), axes.get(i).getMaxValue());
922 plot.setRangeAxis(axisno, axis);
924 // Add data and map to the axis
925 plot.setDataset(axisno, data[i]);
926 plot.setRenderer(axisno, new StandardXYItemRenderer());
927 plot.mapDatasetToRangeAxis(axisno, axisno);
932 plot.getDomainAxis().setLabel(getLabel(domainType,domainUnit));
933 plot.addDomainMarker(new ValueMarker(0));
934 plot.addRangeMarker(new ValueMarker(0));
938 //// Simulation results
939 final JDialog dialog = new JDialog(this, trans.get("simedtdlg.dlg.Simres"));
940 dialog.setModalityType(ModalityType.DOCUMENT_MODAL);
942 JPanel panel = new JPanel(new MigLayout("fill"));
945 ChartPanel chartPanel = new ChartPanel(chart,
951 chartPanel.setMouseWheelEnabled(true);
952 chartPanel.setEnforceFileExtensions(true);
953 chartPanel.setInitialDelay(500);
955 chartPanel.setBorder(BorderFactory.createLineBorder(Color.GRAY, 1));
957 panel.add(chartPanel, "grow, wrap 20lp");
960 JButton button = new JButton(trans.get("dlg.but.close"));
961 button.addActionListener(new ActionListener() {
963 public void actionPerformed(ActionEvent e) {
964 dialog.setVisible(false);
967 panel.add(button, "right");
969 dialog.setLocationByPlatform(true);
972 GUIUtil.setDisposableDialogOptions(dialog, button);
974 dialog.setVisible(true);
978 private class PresetNumberAxis extends NumberAxis {
979 private final double min;
980 private final double max;
982 public PresetNumberAxis(double min, double max) {
989 protected void autoAdjustRange() {
990 this.setRange(min, max);
995 private String getLabel(FlightDataType type, Unit unit) {
996 String name = type.getName();
997 if (unit != null && !UnitGroup.UNITS_NONE.contains(unit) &&
998 !UnitGroup.UNITS_COEFFICIENT.contains(unit) && unit.getUnit().length() > 0)
999 name += " ("+unit.getUnit() + ")";
1005 private class ListenerCellRenderer extends JLabel implements ListCellRenderer {
1007 public Component getListCellRendererComponent(JList list, Object value,
1008 int index, boolean isSelected, boolean cellHasFocus) {
1009 String s = value.toString();
1012 // Attempt instantiating, catch any exceptions
1013 Exception ex = null;
1015 Class<?> c = Class.forName(s);
1016 @SuppressWarnings("unused")
1017 SimulationListener l = (SimulationListener)c.newInstance();
1018 } catch (Exception e) {
1023 setIcon(Icons.SIMULATION_LISTENER_OK);
1024 //// Listener instantiated successfully.
1025 setToolTipText("Listener instantiated successfully.");
1027 setIcon(Icons.SIMULATION_LISTENER_ERROR);
1028 //// <html>Unable to instantiate listener due to exception:<br>
1029 setToolTipText("<html>Unable to instantiate listener due to exception:<br>" +
1034 setBackground(list.getSelectionBackground());
1035 setForeground(list.getSelectionForeground());
1037 setBackground(list.getBackground());
1038 setForeground(list.getForeground());