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.KeyEvent;
9 import java.awt.event.MouseAdapter;
10 import java.awt.event.MouseEvent;
11 import java.util.Arrays;
13 import javax.swing.JButton;
14 import javax.swing.JCheckBox;
15 import javax.swing.JComponent;
16 import javax.swing.JLabel;
17 import javax.swing.JOptionPane;
18 import javax.swing.JPanel;
19 import javax.swing.JScrollPane;
20 import javax.swing.JTable;
21 import javax.swing.KeyStroke;
22 import javax.swing.ListSelectionModel;
23 import javax.swing.SwingUtilities;
24 import javax.swing.table.DefaultTableCellRenderer;
26 import net.miginfocom.swing.MigLayout;
27 import net.sf.openrocket.aerodynamics.Warning;
28 import net.sf.openrocket.aerodynamics.WarningSet;
29 import net.sf.openrocket.document.OpenRocketDocument;
30 import net.sf.openrocket.document.Simulation;
31 import net.sf.openrocket.document.events.DocumentChangeEvent;
32 import net.sf.openrocket.document.events.DocumentChangeListener;
33 import net.sf.openrocket.document.events.SimulationChangeEvent;
34 import net.sf.openrocket.gui.adaptors.Column;
35 import net.sf.openrocket.gui.adaptors.ColumnTableModel;
36 import net.sf.openrocket.gui.components.StyledLabel;
37 import net.sf.openrocket.rocketcomponent.ComponentChangeEvent;
38 import net.sf.openrocket.rocketcomponent.ComponentChangeListener;
39 import net.sf.openrocket.simulation.FlightData;
40 import net.sf.openrocket.unit.UnitGroup;
41 import net.sf.openrocket.util.Icons;
42 import net.sf.openrocket.util.Prefs;
44 public class SimulationPanel extends JPanel {
46 private static final Color WARNING_COLOR = Color.RED;
47 private static final String WARNING_TEXT = "\uFF01"; // Fullwidth exclamation mark
49 private static final Color OK_COLOR = new Color(60,150,0);
50 private static final String OK_TEXT = "\u2714"; // Heavy check mark
54 private final OpenRocketDocument document;
56 private final ColumnTableModel simulationTableModel;
57 private final JTable simulationTable;
60 public SimulationPanel(OpenRocketDocument doc) {
61 super(new MigLayout("fill","[grow][][][][][][grow]"));
70 //////// The simulation action buttons
72 button = new JButton("New simulation");
73 button.setToolTipText("Add a new simulation");
74 button.addActionListener(new ActionListener() {
76 public void actionPerformed(ActionEvent e) {
77 Simulation sim = new Simulation(document.getRocket());
78 sim.setName(document.getNextSimulationName());
80 int n = document.getSimulationCount();
81 document.addSimulation(sim);
82 simulationTableModel.fireTableDataChanged();
83 simulationTable.clearSelection();
84 simulationTable.addRowSelectionInterval(n, n);
86 openDialog(sim, SimulationEditDialog.EDIT);
89 this.add(button,"skip 1, gapright para");
91 button = new JButton("Edit simulation");
92 button.setToolTipText("Edit the selected simulation");
93 button.addActionListener(new ActionListener() {
95 public void actionPerformed(ActionEvent e) {
96 int selected = simulationTable.getSelectedRow();
98 return; // TODO: MEDIUM: "None selected" dialog
100 selected = simulationTable.convertRowIndexToModel(selected);
101 simulationTable.clearSelection();
102 simulationTable.addRowSelectionInterval(selected, selected);
104 openDialog(document.getSimulations().get(selected), SimulationEditDialog.EDIT);
107 this.add(button,"gapright para");
109 button = new JButton("Run simulations");
110 button.setToolTipText("Re-run the selected simulations");
111 button.addActionListener(new ActionListener() {
113 public void actionPerformed(ActionEvent e) {
114 int[] selection = simulationTable.getSelectedRows();
115 if (selection.length == 0)
116 return; // TODO: LOW: "None selected" dialog
118 Simulation[] sims = new Simulation[selection.length];
119 for (int i=0; i < selection.length; i++) {
120 selection[i] = simulationTable.convertRowIndexToModel(selection[i]);
121 sims[i] = document.getSimulation(selection[i]);
124 long t = System.currentTimeMillis();
125 new SimulationRunDialog(SwingUtilities.getWindowAncestor(
126 SimulationPanel.this), sims).setVisible(true);
127 System.err.println("Running took "+(System.currentTimeMillis()-t) + " ms");
128 fireMaintainSelection();
131 this.add(button,"gapright para");
133 button = new JButton("Delete simulations");
134 button.setToolTipText("Delete the selected simulations");
135 button.addActionListener(new ActionListener() {
137 public void actionPerformed(ActionEvent e) {
138 int[] selection = simulationTable.getSelectedRows();
139 if (selection.length == 0)
140 return; // TODO: LOW: "None selected" dialog
143 boolean verify = Prefs.NODE.getBoolean(Prefs.CONFIRM_DELETE_SIMULATION, true);
146 JPanel panel = new JPanel(new MigLayout());
147 JCheckBox dontAsk = new JCheckBox("Do not ask me again");
148 panel.add(dontAsk,"wrap");
149 panel.add(new StyledLabel("You can change the default operation in the " +
152 int ret = JOptionPane.showConfirmDialog(SimulationPanel.this,
154 "Delete the selected simulations?",
155 "<html><i>This operation cannot be undone.</i>",
158 "Delete simulations",
159 JOptionPane.OK_CANCEL_OPTION,
160 JOptionPane.WARNING_MESSAGE);
161 if (ret != JOptionPane.OK_OPTION)
164 if (dontAsk.isSelected()) {
165 Prefs.NODE.putBoolean(Prefs.CONFIRM_DELETE_SIMULATION, false);
169 // Delete simulations
170 for (int i=0; i < selection.length; i++) {
171 selection[i] = simulationTable.convertRowIndexToModel(selection[i]);
173 Arrays.sort(selection);
174 for (int i=selection.length-1; i>=0; i--) {
175 document.removeSimulation(selection[i]);
177 simulationTableModel.fireTableDataChanged();
180 this.add(button,"gapright para");
183 button = new JButton("Plot / export");
184 // button = new JButton("Plot flight");
185 button.addActionListener(new ActionListener() {
187 public void actionPerformed(ActionEvent e) {
188 int selected = simulationTable.getSelectedRow();
190 return; // TODO: MEDIUM: "None selected" dialog
192 selected = simulationTable.convertRowIndexToModel(selected);
193 simulationTable.clearSelection();
194 simulationTable.addRowSelectionInterval(selected, selected);
196 openDialog(document.getSimulations().get(selected), SimulationEditDialog.PLOT);
199 this.add(button, "wrap para");
204 //////// The simulation table
206 simulationTableModel = new ColumnTableModel(
208 //// Status and warning column
210 private JLabel label = null;
212 public Object getValueAt(int row) {
213 if (row < 0 || row >= document.getSimulationCount())
216 // Initialize the label
218 label = new StyledLabel(2f);
219 label.setIconTextGap(1);
220 // label.setFont(label.getFont().deriveFont(Font.BOLD));
223 // Set simulation status icon
224 Simulation.Status status = document.getSimulation(row).getStatus();
225 System.out.println("status=" + status);
226 label.setIcon(Icons.SIMULATION_STATUS_ICON_MAP.get(status));
229 // Set warning marker
230 if (status == Simulation.Status.NOT_SIMULATED ||
231 status == Simulation.Status.EXTERNAL) {
237 WarningSet w = document.getSimulation(row).getSimulatedWarnings();
240 } else if (w.isEmpty()) {
241 label.setForeground(OK_COLOR);
242 label.setText(OK_TEXT);
244 label.setForeground(WARNING_COLOR);
245 label.setText(WARNING_TEXT);
251 @Override public int getExactWidth() {
254 @Override public Class<?> getColumnClass() {
261 @Override public Object getValueAt(int row) {
262 if (row < 0 || row >= document.getSimulationCount())
264 return document.getSimulation(row).getName();
267 public int getDefaultWidth() {
272 //// Simulation motors
273 new Column("Motors") {
274 @Override public Object getValueAt(int row) {
275 if (row < 0 || row >= document.getSimulationCount())
277 return document.getSimulation(row).getConfiguration()
278 .getMotorConfigurationDescription();
281 public int getDefaultWidth() {
287 new Column("Apogee") {
288 @Override public Object getValueAt(int row) {
289 if (row < 0 || row >= document.getSimulationCount())
292 FlightData data = document.getSimulation(row).getSimulatedData();
296 return UnitGroup.UNITS_DISTANCE.getDefaultUnit().toStringUnit(
297 data.getMaxAltitude());
301 //// Maximum velocity
302 new Column("Max. velocity") {
303 @Override public Object getValueAt(int row) {
304 if (row < 0 || row >= document.getSimulationCount())
307 FlightData data = document.getSimulation(row).getSimulatedData();
311 return UnitGroup.UNITS_VELOCITY.getDefaultUnit().toStringUnit(
312 data.getMaxVelocity());
316 //// Maximum acceleration
317 new Column("Max. acceleration") {
318 @Override public Object getValueAt(int row) {
319 if (row < 0 || row >= document.getSimulationCount())
322 FlightData data = document.getSimulation(row).getSimulatedData();
327 // TODO: CRITICAL: Show getMaxAcceleration if branches not available!
328 return UnitGroup.UNITS_ACCELERATION.getDefaultUnit().toStringUnit(
329 data.getMaxAcceleration());
334 new Column("Time to apogee") {
335 @Override public Object getValueAt(int row) {
336 if (row < 0 || row >= document.getSimulationCount())
339 FlightData data = document.getSimulation(row).getSimulatedData();
343 return UnitGroup.UNITS_FLIGHT_TIME.getDefaultUnit().toStringUnit(
344 data.getTimeToApogee());
349 new Column("Flight time") {
350 @Override public Object getValueAt(int row) {
351 if (row < 0 || row >= document.getSimulationCount())
354 FlightData data = document.getSimulation(row).getSimulatedData();
358 return UnitGroup.UNITS_FLIGHT_TIME.getDefaultUnit().toStringUnit(
359 data.getFlightTime());
363 //// Ground hit velocity
364 new Column("Ground hit velocity") {
365 @Override public Object getValueAt(int row) {
366 if (row < 0 || row >= document.getSimulationCount())
369 FlightData data = document.getSimulation(row).getSimulatedData();
373 return UnitGroup.UNITS_VELOCITY.getDefaultUnit().toStringUnit(
374 data.getGroundHitVelocity());
380 public int getRowCount() {
381 return document.getSimulationCount();
385 // Override processKeyBinding so that the JTable does not catch
386 // key bindings used in menu accelerators
387 simulationTable = new JTable(simulationTableModel) {
389 protected boolean processKeyBinding(KeyStroke ks,
396 simulationTable.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
397 simulationTable.setDefaultRenderer(Object.class, new JLabelRenderer());
398 simulationTableModel.setColumnWidths(simulationTable.getColumnModel());
401 // Mouse listener to act on double-clicks
402 simulationTable.addMouseListener(new MouseAdapter() {
404 public void mouseClicked(MouseEvent e) {
405 if (e.getButton() == MouseEvent.BUTTON1 && e.getClickCount() == 2) {
406 int selected = simulationTable.getSelectedRow();
410 selected = simulationTable.convertRowIndexToModel(selected);
411 simulationTable.clearSelection();
412 simulationTable.addRowSelectionInterval(selected, selected);
414 openDialog(document.getSimulations().get(selected),
415 SimulationEditDialog.DEFAULT);
420 document.addDocumentChangeListener(new DocumentChangeListener() {
422 public void documentChanged(DocumentChangeEvent event) {
423 if (!(event instanceof SimulationChangeEvent))
425 simulationTableModel.fireTableDataChanged();
432 // Fire table change event when the rocket changes
433 document.getRocket().addComponentChangeListener(new ComponentChangeListener() {
435 public void componentChanged(ComponentChangeEvent e) {
436 fireMaintainSelection();
441 JScrollPane scrollpane = new JScrollPane(simulationTable);
442 this.add(scrollpane,"spanx, grow, wrap rel");
448 public ListSelectionModel getSimulationListSelectionModel() {
449 return simulationTable.getSelectionModel();
452 private void openDialog(final Simulation sim, int position) {
453 new SimulationEditDialog(SwingUtilities.getWindowAncestor(this), sim, position)
455 fireMaintainSelection();
458 private void fireMaintainSelection() {
459 int[] selection = simulationTable.getSelectedRows();
460 simulationTableModel.fireTableDataChanged();
461 for (int row: selection) {
462 if (row >= simulationTableModel.getRowCount())
464 simulationTable.addRowSelectionInterval(row, row);
469 private class JLabelRenderer extends DefaultTableCellRenderer {
472 public Component getTableCellRendererComponent(JTable table,
473 Object value, boolean isSelected, boolean hasFocus, int row,
476 if (row < 0 || row >= document.getSimulationCount())
477 return super.getTableCellRendererComponent(table, value,
478 isSelected, hasFocus, row, column);
480 // A JLabel is self-contained and has set its own tool tip
481 if (value instanceof JLabel) {
482 JLabel label = (JLabel)value;
484 label.setBackground(table.getSelectionBackground());
486 label.setBackground(table.getBackground());
487 label.setOpaque(true);
489 label.setToolTipText(getSimulationToolTip(document.getSimulation(row)));
493 Component component = super.getTableCellRendererComponent(table, value,
494 isSelected, hasFocus, row, column);
496 if (component instanceof JComponent) {
497 ((JComponent)component).setToolTipText(getSimulationToolTip(
498 document.getSimulation(row)));
503 private String getSimulationToolTip(Simulation sim) {
505 FlightData data = sim.getSimulatedData();
507 tip = "<html><b>" + sim.getName() + "</b><br>";
508 switch (sim.getStatus()) {
510 tip += "<i>Up to date</i><br>";
514 tip += "<i>Data loaded from a file</i><br>";
518 tip += "<i><font color=\"red\">Data is out of date</font></i><br>";
519 tip += "Click <i><b>Run simulations</b></i> to simulate.<br>";
523 tip += "<i>Imported data</i><br>";
527 tip += "<i>Not simulated yet</i><br>";
528 tip += "Click <i><b>Run simulations</b></i> to simulate.";
533 tip += "No simulation data available.";
536 WarningSet warnings = data.getWarningSet();
538 if (warnings.isEmpty()) {
539 tip += "<font color=\"gray\">No warnings.</font>";
543 tip += "<font color=\"red\">Warnings:</font>";
544 for (Warning w: warnings) {
545 tip += "<br>" + w.toString();