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 System.out.println("status=" + status);
230 label.setIcon(Icons.SIMULATION_STATUS_ICON_MAP.get(status));
233 // Set warning marker
234 if (status == Simulation.Status.NOT_SIMULATED ||
235 status == Simulation.Status.EXTERNAL) {
241 WarningSet w = document.getSimulation(row).getSimulatedWarnings();
244 } else if (w.isEmpty()) {
245 label.setForeground(OK_COLOR);
246 label.setText(OK_TEXT);
248 label.setForeground(WARNING_COLOR);
249 label.setText(WARNING_TEXT);
257 public int getExactWidth() {
262 public Class<?> getColumnClass() {
270 public Object getValueAt(int row) {
271 if (row < 0 || row >= document.getSimulationCount())
273 return document.getSimulation(row).getName();
277 public int getDefaultWidth() {
282 //// Simulation motors
283 new Column("Motors") {
285 public Object getValueAt(int row) {
286 if (row < 0 || row >= document.getSimulationCount())
288 return document.getSimulation(row).getConfiguration()
289 .getMotorConfigurationDescription();
293 public int getDefaultWidth() {
299 new Column("Apogee") {
301 public Object getValueAt(int row) {
302 if (row < 0 || row >= document.getSimulationCount())
305 FlightData data = document.getSimulation(row).getSimulatedData();
309 return UnitGroup.UNITS_DISTANCE.getDefaultUnit().toStringUnit(
310 data.getMaxAltitude());
314 //// Maximum velocity
315 new Column("Max. velocity") {
317 public Object getValueAt(int row) {
318 if (row < 0 || row >= document.getSimulationCount())
321 FlightData data = document.getSimulation(row).getSimulatedData();
325 return UnitGroup.UNITS_VELOCITY.getDefaultUnit().toStringUnit(
326 data.getMaxVelocity());
330 //// Maximum acceleration
331 new Column("Max. acceleration") {
333 public Object getValueAt(int row) {
334 if (row < 0 || row >= document.getSimulationCount())
337 FlightData data = document.getSimulation(row).getSimulatedData();
341 return UnitGroup.UNITS_ACCELERATION.getDefaultUnit().toStringUnit(
342 data.getMaxAcceleration());
347 new Column("Time to apogee") {
349 public Object getValueAt(int row) {
350 if (row < 0 || row >= document.getSimulationCount())
353 FlightData data = document.getSimulation(row).getSimulatedData();
357 return UnitGroup.UNITS_FLIGHT_TIME.getDefaultUnit().toStringUnit(
358 data.getTimeToApogee());
363 new Column("Flight time") {
365 public Object getValueAt(int row) {
366 if (row < 0 || row >= document.getSimulationCount())
369 FlightData data = document.getSimulation(row).getSimulatedData();
373 return UnitGroup.UNITS_FLIGHT_TIME.getDefaultUnit().toStringUnit(
374 data.getFlightTime());
378 //// Ground hit velocity
379 new Column("Ground hit velocity") {
381 public Object getValueAt(int row) {
382 if (row < 0 || row >= document.getSimulationCount())
385 FlightData data = document.getSimulation(row).getSimulatedData();
389 return UnitGroup.UNITS_VELOCITY.getDefaultUnit().toStringUnit(
390 data.getGroundHitVelocity());
396 public int getRowCount() {
397 return document.getSimulationCount();
401 // Override processKeyBinding so that the JTable does not catch
402 // key bindings used in menu accelerators
403 simulationTable = new JTable(simulationTableModel) {
405 protected boolean processKeyBinding(KeyStroke ks,
412 simulationTable.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
413 simulationTable.setDefaultRenderer(Object.class, new JLabelRenderer());
414 simulationTableModel.setColumnWidths(simulationTable.getColumnModel());
417 // Mouse listener to act on double-clicks
418 simulationTable.addMouseListener(new MouseAdapter() {
420 public void mouseClicked(MouseEvent e) {
421 if (e.getButton() == MouseEvent.BUTTON1 && e.getClickCount() == 2) {
422 int selected = simulationTable.getSelectedRow();
426 selected = simulationTable.convertRowIndexToModel(selected);
427 simulationTable.clearSelection();
428 simulationTable.addRowSelectionInterval(selected, selected);
430 openDialog(document.getSimulations().get(selected),
431 SimulationEditDialog.DEFAULT);
436 document.addDocumentChangeListener(new DocumentChangeListener() {
438 public void documentChanged(DocumentChangeEvent event) {
439 if (!(event instanceof SimulationChangeEvent))
441 simulationTableModel.fireTableDataChanged();
448 // Fire table change event when the rocket changes
449 document.getRocket().addComponentChangeListener(new ComponentChangeListener() {
451 public void componentChanged(ComponentChangeEvent e) {
452 fireMaintainSelection();
457 JScrollPane scrollpane = new JScrollPane(simulationTable);
458 this.add(scrollpane, "spanx, grow, wrap rel");
464 public ListSelectionModel getSimulationListSelectionModel() {
465 return simulationTable.getSelectionModel();
468 private void openDialog(final Simulation sim, int position) {
469 new SimulationEditDialog(SwingUtilities.getWindowAncestor(this), sim, position)
471 fireMaintainSelection();
474 private void fireMaintainSelection() {
475 int[] selection = simulationTable.getSelectedRows();
476 simulationTableModel.fireTableDataChanged();
477 for (int row : selection) {
478 if (row >= simulationTableModel.getRowCount())
480 simulationTable.addRowSelectionInterval(row, row);
485 private class JLabelRenderer extends DefaultTableCellRenderer {
488 public Component getTableCellRendererComponent(JTable table,
489 Object value, boolean isSelected, boolean hasFocus, int row,
492 if (row < 0 || row >= document.getSimulationCount())
493 return super.getTableCellRendererComponent(table, value,
494 isSelected, hasFocus, row, column);
496 // A JLabel is self-contained and has set its own tool tip
497 if (value instanceof JLabel) {
498 JLabel label = (JLabel) value;
500 label.setBackground(table.getSelectionBackground());
502 label.setBackground(table.getBackground());
503 label.setOpaque(true);
505 label.setToolTipText(getSimulationToolTip(document.getSimulation(row)));
509 Component component = super.getTableCellRendererComponent(table, value,
510 isSelected, hasFocus, row, column);
512 if (component instanceof JComponent) {
513 ((JComponent) component).setToolTipText(getSimulationToolTip(
514 document.getSimulation(row)));
519 private String getSimulationToolTip(Simulation sim) {
521 FlightData data = sim.getSimulatedData();
523 tip = "<html><b>" + sim.getName() + "</b><br>";
524 switch (sim.getStatus()) {
526 tip += "<i>Up to date</i><br>";
530 tip += "<i>Data loaded from a file</i><br>";
534 tip += "<i><font color=\"red\">Data is out of date</font></i><br>";
535 tip += "Click <i><b>Run simulations</b></i> to simulate.<br>";
539 tip += "<i>Imported data</i><br>";
543 tip += "<i>Not simulated yet</i><br>";
544 tip += "Click <i><b>Run simulations</b></i> to simulate.";
549 tip += "No simulation data available.";
552 WarningSet warnings = data.getWarningSet();
554 if (warnings.isEmpty()) {
555 tip += "<font color=\"gray\">No warnings.</font>";
559 tip += "<font color=\"red\">Warnings:</font>";
560 for (Warning w : warnings) {
561 tip += "<br>" + w.toString();