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.logging.LogHelper;
38 import net.sf.openrocket.rocketcomponent.ComponentChangeEvent;
39 import net.sf.openrocket.rocketcomponent.ComponentChangeListener;
40 import net.sf.openrocket.simulation.FlightData;
41 import net.sf.openrocket.startup.Application;
42 import net.sf.openrocket.unit.UnitGroup;
43 import net.sf.openrocket.util.Icons;
44 import net.sf.openrocket.util.Prefs;
46 public class SimulationPanel extends JPanel {
47 private static final LogHelper log = Application.getLogger();
49 private static final Color WARNING_COLOR = Color.RED;
50 private static final String WARNING_TEXT = "\uFF01"; // Fullwidth exclamation mark
52 private static final Color OK_COLOR = new Color(60, 150, 0);
53 private static final String OK_TEXT = "\u2714"; // Heavy check mark
57 private final OpenRocketDocument document;
59 private final ColumnTableModel simulationTableModel;
60 private final JTable simulationTable;
63 public SimulationPanel(OpenRocketDocument doc) {
64 super(new MigLayout("fill", "[grow][][][][][][grow]"));
73 //////// The simulation action buttons
75 button = new JButton("New simulation");
76 button.setToolTipText("Add a new simulation");
77 button.addActionListener(new ActionListener() {
79 public void actionPerformed(ActionEvent e) {
80 Simulation sim = new Simulation(document.getRocket());
81 sim.setName(document.getNextSimulationName());
83 int n = document.getSimulationCount();
84 document.addSimulation(sim);
85 simulationTableModel.fireTableDataChanged();
86 simulationTable.clearSelection();
87 simulationTable.addRowSelectionInterval(n, n);
89 openDialog(sim, SimulationEditDialog.EDIT);
92 this.add(button, "skip 1, gapright para");
94 button = new JButton("Edit simulation");
95 button.setToolTipText("Edit the selected simulation");
96 button.addActionListener(new ActionListener() {
98 public void actionPerformed(ActionEvent e) {
99 int selected = simulationTable.getSelectedRow();
101 return; // TODO: MEDIUM: "None selected" dialog
103 selected = simulationTable.convertRowIndexToModel(selected);
104 simulationTable.clearSelection();
105 simulationTable.addRowSelectionInterval(selected, selected);
107 openDialog(document.getSimulations().get(selected), SimulationEditDialog.EDIT);
110 this.add(button, "gapright para");
112 button = new JButton("Run simulations");
113 button.setToolTipText("Re-run the selected simulations");
114 button.addActionListener(new ActionListener() {
116 public void actionPerformed(ActionEvent e) {
117 int[] selection = simulationTable.getSelectedRows();
118 if (selection.length == 0)
119 return; // TODO: LOW: "None selected" dialog
121 Simulation[] sims = new Simulation[selection.length];
122 for (int i = 0; i < selection.length; i++) {
123 selection[i] = simulationTable.convertRowIndexToModel(selection[i]);
124 sims[i] = document.getSimulation(selection[i]);
127 long t = System.currentTimeMillis();
128 new SimulationRunDialog(SwingUtilities.getWindowAncestor(
129 SimulationPanel.this), sims).setVisible(true);
130 log.info("Running simulations took " + (System.currentTimeMillis() - t) + " ms");
131 fireMaintainSelection();
134 this.add(button, "gapright para");
136 button = new JButton("Delete simulations");
137 button.setToolTipText("Delete the selected simulations");
138 button.addActionListener(new ActionListener() {
140 public void actionPerformed(ActionEvent e) {
141 int[] selection = simulationTable.getSelectedRows();
142 if (selection.length == 0)
143 return; // TODO: LOW: "None selected" dialog
146 boolean verify = Prefs.NODE.getBoolean(Prefs.CONFIRM_DELETE_SIMULATION, true);
149 JPanel panel = new JPanel(new MigLayout());
150 JCheckBox dontAsk = new JCheckBox("Do not ask me again");
151 panel.add(dontAsk, "wrap");
152 panel.add(new StyledLabel("You can change the default operation in the " +
153 "preferences.", -2));
155 int ret = JOptionPane.showConfirmDialog(SimulationPanel.this,
157 "Delete the selected simulations?",
158 "<html><i>This operation cannot be undone.</i>",
161 "Delete simulations",
162 JOptionPane.OK_CANCEL_OPTION,
163 JOptionPane.WARNING_MESSAGE);
164 if (ret != JOptionPane.OK_OPTION)
167 if (dontAsk.isSelected()) {
168 Prefs.NODE.putBoolean(Prefs.CONFIRM_DELETE_SIMULATION, false);
172 // Delete simulations
173 for (int i = 0; i < selection.length; i++) {
174 selection[i] = simulationTable.convertRowIndexToModel(selection[i]);
176 Arrays.sort(selection);
177 for (int i = selection.length - 1; i >= 0; i--) {
178 document.removeSimulation(selection[i]);
180 simulationTableModel.fireTableDataChanged();
183 this.add(button, "gapright para");
186 button = new JButton("Plot / export");
187 // button = new JButton("Plot flight");
188 button.addActionListener(new ActionListener() {
190 public void actionPerformed(ActionEvent e) {
191 int selected = simulationTable.getSelectedRow();
193 return; // TODO: MEDIUM: "None selected" dialog
195 selected = simulationTable.convertRowIndexToModel(selected);
196 simulationTable.clearSelection();
197 simulationTable.addRowSelectionInterval(selected, selected);
199 openDialog(document.getSimulations().get(selected), SimulationEditDialog.PLOT);
202 this.add(button, "wrap para");
207 //////// The simulation table
209 simulationTableModel = new ColumnTableModel(
211 //// Status and warning column
213 private JLabel label = null;
216 public Object getValueAt(int row) {
217 if (row < 0 || row >= document.getSimulationCount())
220 // Initialize the label
222 label = new StyledLabel(2f);
223 label.setIconTextGap(1);
224 // label.setFont(label.getFont().deriveFont(Font.BOLD));
227 // Set simulation status icon
228 Simulation.Status status = document.getSimulation(row).getStatus();
229 label.setIcon(Icons.SIMULATION_STATUS_ICON_MAP.get(status));
232 // Set warning marker
233 if (status == Simulation.Status.NOT_SIMULATED ||
234 status == Simulation.Status.EXTERNAL) {
240 WarningSet w = document.getSimulation(row).getSimulatedWarnings();
243 } else if (w.isEmpty()) {
244 label.setForeground(OK_COLOR);
245 label.setText(OK_TEXT);
247 label.setForeground(WARNING_COLOR);
248 label.setText(WARNING_TEXT);
256 public int getExactWidth() {
261 public Class<?> getColumnClass() {
269 public Object getValueAt(int row) {
270 if (row < 0 || row >= document.getSimulationCount())
272 return document.getSimulation(row).getName();
276 public int getDefaultWidth() {
281 //// Simulation motors
282 new Column("Motors") {
284 public Object getValueAt(int row) {
285 if (row < 0 || row >= document.getSimulationCount())
287 return document.getSimulation(row).getConfiguration()
288 .getMotorConfigurationDescription();
292 public int getDefaultWidth() {
298 new Column("Apogee") {
300 public Object getValueAt(int row) {
301 if (row < 0 || row >= document.getSimulationCount())
304 FlightData data = document.getSimulation(row).getSimulatedData();
308 return UnitGroup.UNITS_DISTANCE.getDefaultUnit().toStringUnit(
309 data.getMaxAltitude());
313 //// Maximum velocity
314 new Column("Max. velocity") {
316 public Object getValueAt(int row) {
317 if (row < 0 || row >= document.getSimulationCount())
320 FlightData data = document.getSimulation(row).getSimulatedData();
324 return UnitGroup.UNITS_VELOCITY.getDefaultUnit().toStringUnit(
325 data.getMaxVelocity());
329 //// Maximum acceleration
330 new Column("Max. acceleration") {
332 public Object getValueAt(int row) {
333 if (row < 0 || row >= document.getSimulationCount())
336 FlightData data = document.getSimulation(row).getSimulatedData();
340 return UnitGroup.UNITS_ACCELERATION.getDefaultUnit().toStringUnit(
341 data.getMaxAcceleration());
346 new Column("Time to apogee") {
348 public Object getValueAt(int row) {
349 if (row < 0 || row >= document.getSimulationCount())
352 FlightData data = document.getSimulation(row).getSimulatedData();
356 return UnitGroup.UNITS_FLIGHT_TIME.getDefaultUnit().toStringUnit(
357 data.getTimeToApogee());
362 new Column("Flight time") {
364 public Object getValueAt(int row) {
365 if (row < 0 || row >= document.getSimulationCount())
368 FlightData data = document.getSimulation(row).getSimulatedData();
372 return UnitGroup.UNITS_FLIGHT_TIME.getDefaultUnit().toStringUnit(
373 data.getFlightTime());
377 //// Ground hit velocity
378 new Column("Ground hit velocity") {
380 public Object getValueAt(int row) {
381 if (row < 0 || row >= document.getSimulationCount())
384 FlightData data = document.getSimulation(row).getSimulatedData();
388 return UnitGroup.UNITS_VELOCITY.getDefaultUnit().toStringUnit(
389 data.getGroundHitVelocity());
395 public int getRowCount() {
396 return document.getSimulationCount();
400 // Override processKeyBinding so that the JTable does not catch
401 // key bindings used in menu accelerators
402 simulationTable = new JTable(simulationTableModel) {
404 protected boolean processKeyBinding(KeyStroke ks,
411 simulationTable.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
412 simulationTable.setDefaultRenderer(Object.class, new JLabelRenderer());
413 simulationTableModel.setColumnWidths(simulationTable.getColumnModel());
416 // Mouse listener to act on double-clicks
417 simulationTable.addMouseListener(new MouseAdapter() {
419 public void mouseClicked(MouseEvent e) {
420 if (e.getButton() == MouseEvent.BUTTON1 && e.getClickCount() == 2) {
421 int selected = simulationTable.getSelectedRow();
425 selected = simulationTable.convertRowIndexToModel(selected);
426 simulationTable.clearSelection();
427 simulationTable.addRowSelectionInterval(selected, selected);
429 openDialog(document.getSimulations().get(selected),
430 SimulationEditDialog.DEFAULT);
435 document.addDocumentChangeListener(new DocumentChangeListener() {
437 public void documentChanged(DocumentChangeEvent event) {
438 if (!(event instanceof SimulationChangeEvent))
440 simulationTableModel.fireTableDataChanged();
447 // Fire table change event when the rocket changes
448 document.getRocket().addComponentChangeListener(new ComponentChangeListener() {
450 public void componentChanged(ComponentChangeEvent e) {
451 fireMaintainSelection();
456 JScrollPane scrollpane = new JScrollPane(simulationTable);
457 this.add(scrollpane, "spanx, grow, wrap rel");
463 public ListSelectionModel getSimulationListSelectionModel() {
464 return simulationTable.getSelectionModel();
467 private void openDialog(final Simulation sim, int position) {
468 new SimulationEditDialog(SwingUtilities.getWindowAncestor(this), sim, position)
470 fireMaintainSelection();
473 private void fireMaintainSelection() {
474 int[] selection = simulationTable.getSelectedRows();
475 simulationTableModel.fireTableDataChanged();
476 for (int row : selection) {
477 if (row >= simulationTableModel.getRowCount())
479 simulationTable.addRowSelectionInterval(row, row);
484 private class JLabelRenderer extends DefaultTableCellRenderer {
487 public Component getTableCellRendererComponent(JTable table,
488 Object value, boolean isSelected, boolean hasFocus, int row,
491 if (row < 0 || row >= document.getSimulationCount())
492 return super.getTableCellRendererComponent(table, value,
493 isSelected, hasFocus, row, column);
495 // A JLabel is self-contained and has set its own tool tip
496 if (value instanceof JLabel) {
497 JLabel label = (JLabel) value;
499 label.setBackground(table.getSelectionBackground());
501 label.setBackground(table.getBackground());
502 label.setOpaque(true);
504 label.setToolTipText(getSimulationToolTip(document.getSimulation(row)));
508 Component component = super.getTableCellRendererComponent(table, value,
509 isSelected, hasFocus, row, column);
511 if (component instanceof JComponent) {
512 ((JComponent) component).setToolTipText(getSimulationToolTip(
513 document.getSimulation(row)));
518 private String getSimulationToolTip(Simulation sim) {
520 FlightData data = sim.getSimulatedData();
522 tip = "<html><b>" + sim.getName() + "</b><br>";
523 switch (sim.getStatus()) {
525 tip += "<i>Up to date</i><br>";
529 tip += "<i>Data loaded from a file</i><br>";
533 tip += "<i><font color=\"red\">Data is out of date</font></i><br>";
534 tip += "Click <i><b>Run simulations</b></i> to simulate.<br>";
538 tip += "<i>Imported data</i><br>";
542 tip += "<i>Not simulated yet</i><br>";
543 tip += "Click <i><b>Run simulations</b></i> to simulate.";
548 tip += "No simulation data available.";
551 WarningSet warnings = data.getWarningSet();
553 if (warnings.isEmpty()) {
554 tip += "<font color=\"gray\">No warnings.</font>";
558 tip += "<font color=\"red\">Warnings:</font>";
559 for (Warning w : warnings) {
560 tip += "<br>" + w.toString();