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();
326 return UnitGroup.UNITS_ACCELERATION.getDefaultUnit().toStringUnit(
327 data.getMaxAcceleration());
332 new Column("Time to apogee") {
333 @Override public Object getValueAt(int row) {
334 if (row < 0 || row >= document.getSimulationCount())
337 FlightData data = document.getSimulation(row).getSimulatedData();
341 return UnitGroup.UNITS_FLIGHT_TIME.getDefaultUnit().toStringUnit(
342 data.getTimeToApogee());
347 new Column("Flight time") {
348 @Override 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.getFlightTime());
361 //// Ground hit velocity
362 new Column("Ground hit velocity") {
363 @Override public Object getValueAt(int row) {
364 if (row < 0 || row >= document.getSimulationCount())
367 FlightData data = document.getSimulation(row).getSimulatedData();
371 return UnitGroup.UNITS_VELOCITY.getDefaultUnit().toStringUnit(
372 data.getGroundHitVelocity());
378 public int getRowCount() {
379 return document.getSimulationCount();
383 // Override processKeyBinding so that the JTable does not catch
384 // key bindings used in menu accelerators
385 simulationTable = new JTable(simulationTableModel) {
387 protected boolean processKeyBinding(KeyStroke ks,
394 simulationTable.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
395 simulationTable.setDefaultRenderer(Object.class, new JLabelRenderer());
396 simulationTableModel.setColumnWidths(simulationTable.getColumnModel());
399 // Mouse listener to act on double-clicks
400 simulationTable.addMouseListener(new MouseAdapter() {
402 public void mouseClicked(MouseEvent e) {
403 if (e.getButton() == MouseEvent.BUTTON1 && e.getClickCount() == 2) {
404 int selected = simulationTable.getSelectedRow();
408 selected = simulationTable.convertRowIndexToModel(selected);
409 simulationTable.clearSelection();
410 simulationTable.addRowSelectionInterval(selected, selected);
412 openDialog(document.getSimulations().get(selected),
413 SimulationEditDialog.DEFAULT);
418 document.addDocumentChangeListener(new DocumentChangeListener() {
420 public void documentChanged(DocumentChangeEvent event) {
421 if (!(event instanceof SimulationChangeEvent))
423 simulationTableModel.fireTableDataChanged();
430 // Fire table change event when the rocket changes
431 document.getRocket().addComponentChangeListener(new ComponentChangeListener() {
433 public void componentChanged(ComponentChangeEvent e) {
434 fireMaintainSelection();
439 JScrollPane scrollpane = new JScrollPane(simulationTable);
440 this.add(scrollpane,"spanx, grow, wrap rel");
446 public ListSelectionModel getSimulationListSelectionModel() {
447 return simulationTable.getSelectionModel();
450 private void openDialog(final Simulation sim, int position) {
451 new SimulationEditDialog(SwingUtilities.getWindowAncestor(this), sim, position)
453 fireMaintainSelection();
456 private void fireMaintainSelection() {
457 int[] selection = simulationTable.getSelectedRows();
458 simulationTableModel.fireTableDataChanged();
459 for (int row: selection) {
460 if (row >= simulationTableModel.getRowCount())
462 simulationTable.addRowSelectionInterval(row, row);
467 private class JLabelRenderer extends DefaultTableCellRenderer {
470 public Component getTableCellRendererComponent(JTable table,
471 Object value, boolean isSelected, boolean hasFocus, int row,
474 if (row < 0 || row >= document.getSimulationCount())
475 return super.getTableCellRendererComponent(table, value,
476 isSelected, hasFocus, row, column);
478 // A JLabel is self-contained and has set its own tool tip
479 if (value instanceof JLabel) {
480 JLabel label = (JLabel)value;
482 label.setBackground(table.getSelectionBackground());
484 label.setBackground(table.getBackground());
485 label.setOpaque(true);
487 label.setToolTipText(getSimulationToolTip(document.getSimulation(row)));
491 Component component = super.getTableCellRendererComponent(table, value,
492 isSelected, hasFocus, row, column);
494 if (component instanceof JComponent) {
495 ((JComponent)component).setToolTipText(getSimulationToolTip(
496 document.getSimulation(row)));
501 private String getSimulationToolTip(Simulation sim) {
503 FlightData data = sim.getSimulatedData();
505 tip = "<html><b>" + sim.getName() + "</b><br>";
506 switch (sim.getStatus()) {
508 tip += "<i>Up to date</i><br>";
512 tip += "<i>Data loaded from a file</i><br>";
516 tip += "<i><font color=\"red\">Data is out of date</font></i><br>";
517 tip += "Click <i><b>Run simulations</b></i> to simulate.<br>";
521 tip += "<i>Imported data</i><br>";
525 tip += "<i>Not simulated yet</i><br>";
526 tip += "Click <i><b>Run simulations</b></i> to simulate.";
531 tip += "No simulation data available.";
534 WarningSet warnings = data.getWarningSet();
536 if (warnings.isEmpty()) {
537 tip += "<font color=\"gray\">No warnings.</font>";
541 tip += "<font color=\"red\">Warnings:</font>";
542 for (Warning w: warnings) {
543 tip += "<br>" + w.toString();