1 package net.sf.openrocket.gui.main;
5 import java.awt.Component;
6 import java.awt.event.ActionEvent;
7 import java.awt.event.ActionListener;
8 import java.awt.event.MouseAdapter;
9 import java.awt.event.MouseEvent;
10 import java.util.Arrays;
12 import javax.swing.JButton;
13 import javax.swing.JCheckBox;
14 import javax.swing.JComponent;
15 import javax.swing.JLabel;
16 import javax.swing.JOptionPane;
17 import javax.swing.JPanel;
18 import javax.swing.JScrollPane;
19 import javax.swing.JTable;
20 import javax.swing.SwingUtilities;
21 import javax.swing.table.DefaultTableCellRenderer;
23 import net.miginfocom.swing.MigLayout;
24 import net.sf.openrocket.aerodynamics.Warning;
25 import net.sf.openrocket.aerodynamics.WarningSet;
26 import net.sf.openrocket.document.OpenRocketDocument;
27 import net.sf.openrocket.document.Simulation;
28 import net.sf.openrocket.gui.adaptors.Column;
29 import net.sf.openrocket.gui.adaptors.ColumnTableModel;
30 import net.sf.openrocket.gui.components.ResizeLabel;
31 import net.sf.openrocket.rocketcomponent.ComponentChangeEvent;
32 import net.sf.openrocket.rocketcomponent.ComponentChangeListener;
33 import net.sf.openrocket.simulation.FlightData;
34 import net.sf.openrocket.unit.UnitGroup;
35 import net.sf.openrocket.util.Icons;
36 import net.sf.openrocket.util.Prefs;
38 public class SimulationPanel extends JPanel {
40 private static final Color WARNING_COLOR = Color.RED;
41 private static final String WARNING_TEXT = "\uFF01"; // Fullwidth exclamation mark
43 private static final Color OK_COLOR = new Color(60,150,0);
44 private static final String OK_TEXT = "\u2714"; // Heavy check mark
46 private static final String NAME_PREFIX = "Simulation ";
50 private final OpenRocketDocument document;
52 private final ColumnTableModel simulationTableModel;
53 private final JTable simulationTable;
56 public SimulationPanel(OpenRocketDocument doc) {
57 super(new MigLayout("fill","[grow][][][][][][grow]"));
66 //////// The simulation action buttons
68 button = new JButton("New simulation");
69 button.setToolTipText("Add a new simulation");
70 button.addActionListener(new ActionListener() {
72 public void actionPerformed(ActionEvent e) {
74 // Generate unique name for the simulation
76 for (Simulation s: document.getSimulations()) {
77 String name = s.getName();
78 if (name.startsWith(NAME_PREFIX)) {
80 maxValue = Math.max(maxValue,
81 Integer.parseInt(name.substring(NAME_PREFIX.length())));
82 } catch (NumberFormatException ignore) { }
86 Simulation sim = new Simulation(document.getRocket());
87 sim.setName(NAME_PREFIX + (maxValue+1));
89 int n = document.getSimulationCount();
90 document.addSimulation(sim);
91 simulationTableModel.fireTableDataChanged();
92 simulationTable.clearSelection();
93 simulationTable.addRowSelectionInterval(n, n);
95 openDialog(sim, SimulationEditDialog.EDIT);
98 this.add(button,"skip 1, gapright para");
100 button = new JButton("Edit simulation");
101 button.setToolTipText("Edit the selected simulation");
102 button.addActionListener(new ActionListener() {
104 public void actionPerformed(ActionEvent e) {
105 int selected = simulationTable.getSelectedRow();
107 return; // TODO: MEDIUM: "None selected" dialog
109 selected = simulationTable.convertRowIndexToModel(selected);
110 simulationTable.clearSelection();
111 simulationTable.addRowSelectionInterval(selected, selected);
113 openDialog(document.getSimulations().get(selected), SimulationEditDialog.EDIT);
116 this.add(button,"gapright para");
118 button = new JButton("Run simulations");
119 button.setToolTipText("Re-run the selected simulations");
120 button.addActionListener(new ActionListener() {
122 public void actionPerformed(ActionEvent e) {
123 int[] selection = simulationTable.getSelectedRows();
124 if (selection.length == 0)
125 return; // TODO: LOW: "None selected" dialog
127 Simulation[] sims = new Simulation[selection.length];
128 for (int i=0; i < selection.length; i++) {
129 selection[i] = simulationTable.convertRowIndexToModel(selection[i]);
130 sims[i] = document.getSimulation(selection[i]);
133 long t = System.currentTimeMillis();
134 new SimulationRunDialog(SwingUtilities.getWindowAncestor(
135 SimulationPanel.this), sims).setVisible(true);
136 System.err.println("Running took "+(System.currentTimeMillis()-t) + " ms");
137 fireMaintainSelection();
140 this.add(button,"gapright para");
142 button = new JButton("Delete simulations");
143 button.setToolTipText("Delete the selected simulations");
144 button.addActionListener(new ActionListener() {
146 public void actionPerformed(ActionEvent e) {
147 int[] selection = simulationTable.getSelectedRows();
148 if (selection.length == 0)
149 return; // TODO: LOW: "None selected" dialog
152 boolean verify = Prefs.NODE.getBoolean(Prefs.CONFIRM_DELETE_SIMULATION, true);
155 JPanel panel = new JPanel(new MigLayout());
156 JCheckBox dontAsk = new JCheckBox("Do not ask me again");
157 panel.add(dontAsk,"wrap");
158 panel.add(new ResizeLabel("You can change the default operation in the " +
161 int ret = JOptionPane.showConfirmDialog(SimulationPanel.this,
163 "Delete the selected simulations?",
164 "<html><i>This operation cannot be undone.</i>",
167 "Delete simulations",
168 JOptionPane.OK_CANCEL_OPTION,
169 JOptionPane.WARNING_MESSAGE);
170 if (ret != JOptionPane.OK_OPTION)
173 if (dontAsk.isSelected()) {
174 Prefs.NODE.putBoolean(Prefs.CONFIRM_DELETE_SIMULATION, false);
178 // Delete simulations
179 for (int i=0; i < selection.length; i++) {
180 selection[i] = simulationTable.convertRowIndexToModel(selection[i]);
182 Arrays.sort(selection);
183 for (int i=selection.length-1; i>=0; i--) {
184 document.removeSimulation(selection[i]);
186 simulationTableModel.fireTableDataChanged();
189 this.add(button,"gapright para");
192 // button = new JButton("Plot / export");
193 button = new JButton("Plot flight");
194 button.addActionListener(new ActionListener() {
196 public void actionPerformed(ActionEvent e) {
197 int selected = simulationTable.getSelectedRow();
199 return; // TODO: MEDIUM: "None selected" dialog
201 selected = simulationTable.convertRowIndexToModel(selected);
202 simulationTable.clearSelection();
203 simulationTable.addRowSelectionInterval(selected, selected);
205 openDialog(document.getSimulations().get(selected), SimulationEditDialog.PLOT);
208 this.add(button, "wrap para");
213 //////// The simulation table
215 simulationTableModel = new ColumnTableModel(
217 //// Status and warning column
219 private JLabel label = null;
221 public Object getValueAt(int row) {
222 if (row < 0 || row >= document.getSimulationCount())
225 // Initialize the label
227 label = new ResizeLabel(2f);
228 label.setIconTextGap(1);
229 // label.setFont(label.getFont().deriveFont(Font.BOLD));
232 // Set simulation status icon
233 Simulation.Status status = document.getSimulation(row).getStatus();
234 label.setIcon(Icons.SIMULATION_STATUS_ICON_MAP.get(status));
237 // Set warning marker
238 if (status == Simulation.Status.NOT_SIMULATED ||
239 status == Simulation.Status.EXTERNAL) {
245 WarningSet w = document.getSimulation(row).getSimulatedWarnings();
248 } else if (w.isEmpty()) {
249 label.setForeground(OK_COLOR);
250 label.setText(OK_TEXT);
252 label.setForeground(WARNING_COLOR);
253 label.setText(WARNING_TEXT);
259 @Override public int getExactWidth() {
262 @Override public Class<?> getColumnClass() {
269 @Override public Object getValueAt(int row) {
270 if (row < 0 || row >= document.getSimulationCount())
272 return document.getSimulation(row).getName();
275 public int getDefaultWidth() {
280 //// Simulation motors
281 new Column("Motors") {
282 @Override public Object getValueAt(int row) {
283 if (row < 0 || row >= document.getSimulationCount())
285 return document.getSimulation(row).getConfiguration()
286 .getMotorConfigurationDescription();
289 public int getDefaultWidth() {
295 new Column("Apogee") {
296 @Override public Object getValueAt(int row) {
297 if (row < 0 || row >= document.getSimulationCount())
300 FlightData data = document.getSimulation(row).getSimulatedData();
304 return UnitGroup.UNITS_DISTANCE.getDefaultUnit().toStringUnit(
305 data.getMaxAltitude());
309 //// Maximum velocity
310 new Column("Max. velocity") {
311 @Override public Object getValueAt(int row) {
312 if (row < 0 || row >= document.getSimulationCount())
315 FlightData data = document.getSimulation(row).getSimulatedData();
319 return UnitGroup.UNITS_VELOCITY.getDefaultUnit().toStringUnit(
320 data.getMaxVelocity());
324 //// Maximum acceleration
325 new Column("Max. acceleration") {
326 @Override public Object getValueAt(int row) {
327 if (row < 0 || row >= document.getSimulationCount())
330 FlightData data = document.getSimulation(row).getSimulatedData();
334 return UnitGroup.UNITS_ACCELERATION.getDefaultUnit().toStringUnit(
335 data.getMaxAcceleration());
340 new Column("Time to apogee") {
341 @Override public Object getValueAt(int row) {
342 if (row < 0 || row >= document.getSimulationCount())
345 FlightData data = document.getSimulation(row).getSimulatedData();
349 return UnitGroup.UNITS_FLIGHT_TIME.getDefaultUnit().toStringUnit(
350 data.getTimeToApogee());
355 new Column("Flight time") {
356 @Override public Object getValueAt(int row) {
357 if (row < 0 || row >= document.getSimulationCount())
360 FlightData data = document.getSimulation(row).getSimulatedData();
364 return UnitGroup.UNITS_FLIGHT_TIME.getDefaultUnit().toStringUnit(
365 data.getFlightTime());
369 //// Ground hit velocity
370 new Column("Ground hit velocity") {
371 @Override public Object getValueAt(int row) {
372 if (row < 0 || row >= document.getSimulationCount())
375 FlightData data = document.getSimulation(row).getSimulatedData();
379 return UnitGroup.UNITS_VELOCITY.getDefaultUnit().toStringUnit(
380 data.getGroundHitVelocity());
386 public int getRowCount() {
387 return document.getSimulationCount();
391 simulationTable = new JTable(simulationTableModel);
392 simulationTable.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
393 simulationTable.setDefaultRenderer(Object.class, new JLabelRenderer());
394 simulationTableModel.setColumnWidths(simulationTable.getColumnModel());
396 // Mouse listener to act on double-clicks
397 simulationTable.addMouseListener(new MouseAdapter() {
399 public void mouseClicked(MouseEvent e) {
400 if (e.getButton() == MouseEvent.BUTTON1 && e.getClickCount() == 2) {
401 int selected = simulationTable.getSelectedRow();
405 selected = simulationTable.convertRowIndexToModel(selected);
406 simulationTable.clearSelection();
407 simulationTable.addRowSelectionInterval(selected, selected);
409 openDialog(document.getSimulations().get(selected),
410 SimulationEditDialog.DEFAULT);
418 // Fire table change event when the rocket changes
419 document.getRocket().addComponentChangeListener(new ComponentChangeListener() {
421 public void componentChanged(ComponentChangeEvent e) {
422 fireMaintainSelection();
427 JScrollPane scrollpane = new JScrollPane(simulationTable);
428 this.add(scrollpane,"spanx, grow, wrap rel");
434 private void openDialog(final Simulation sim, int position) {
435 new SimulationEditDialog(SwingUtilities.getWindowAncestor(this), sim, position)
437 fireMaintainSelection();
440 private void fireMaintainSelection() {
441 int[] selection = simulationTable.getSelectedRows();
442 simulationTableModel.fireTableDataChanged();
443 for (int row: selection) {
444 simulationTable.addRowSelectionInterval(row, row);
449 private class JLabelRenderer extends DefaultTableCellRenderer {
452 public Component getTableCellRendererComponent(JTable table,
453 Object value, boolean isSelected, boolean hasFocus, int row,
456 if (row < 0 || row >= document.getSimulationCount())
457 return super.getTableCellRendererComponent(table, value,
458 isSelected, hasFocus, row, column);
460 // A JLabel is self-contained and has set its own tool tip
461 if (value instanceof JLabel) {
462 JLabel label = (JLabel)value;
464 label.setBackground(table.getSelectionBackground());
466 label.setBackground(table.getBackground());
467 label.setOpaque(true);
469 label.setToolTipText(getSimulationToolTip(document.getSimulation(row)));
473 Component component = super.getTableCellRendererComponent(table, value,
474 isSelected, hasFocus, row, column);
476 if (component instanceof JComponent) {
477 ((JComponent)component).setToolTipText(getSimulationToolTip(
478 document.getSimulation(row)));
483 private String getSimulationToolTip(Simulation sim) {
485 FlightData data = sim.getSimulatedData();
487 tip = "<html><b>" + sim.getName() + "</b><br>";
488 switch (sim.getStatus()) {
490 tip += "<i>Up to date</i><br>";
494 tip += "<i>Data loaded from a file</i><br>";
498 tip += "<i><font color=\"red\">Data is out of date</font></i><br>";
499 tip += "Click <i><b>Run simulations</b></i> to simulate.<br>";
503 tip += "<i>Imported data</i><br>";
507 tip += "<i>Not simulated yet</i><br>";
508 tip += "Click <i><b>Run simulations</b></i> to simulate.";
513 tip += "No simulation data available.";
516 WarningSet warnings = data.getWarningSet();
518 if (warnings.isEmpty()) {
519 tip += "<font color=\"gray\">No warnings.</font>";
523 tip += "<font color=\"red\">Warnings:</font>";
524 for (Warning w: warnings) {
525 tip += "<br>" + w.toString();