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.gui.util.Icons;
38 import net.sf.openrocket.l10n.Translator;
39 import net.sf.openrocket.logging.LogHelper;
40 import net.sf.openrocket.rocketcomponent.ComponentChangeEvent;
41 import net.sf.openrocket.rocketcomponent.ComponentChangeListener;
42 import net.sf.openrocket.simulation.FlightData;
43 import net.sf.openrocket.startup.Application;
44 import net.sf.openrocket.startup.Preferences;
45 import net.sf.openrocket.unit.UnitGroup;
47 public class SimulationPanel extends JPanel {
48 private static final LogHelper log = Application.getLogger();
49 private static final Translator trans = Application.getTranslator();
52 private static final Color WARNING_COLOR = Color.RED;
53 private static final String WARNING_TEXT = "\uFF01"; // Fullwidth exclamation mark
55 private static final Color OK_COLOR = new Color(60, 150, 0);
56 private static final String OK_TEXT = "\u2714"; // Heavy check mark
60 private final OpenRocketDocument document;
62 private final ColumnTableModel simulationTableModel;
63 private final JTable simulationTable;
66 public SimulationPanel(OpenRocketDocument doc) {
67 super(new MigLayout("fill", "[grow][][][][][][grow]"));
76 //////// The simulation action buttons
78 //// New simulation button
79 button = new JButton(trans.get("simpanel.but.newsimulation"));
80 //// Add a new simulation
81 button.setToolTipText(trans.get("simpanel.but.ttip.newsimulation"));
82 button.addActionListener(new ActionListener() {
84 public void actionPerformed(ActionEvent e) {
85 Simulation sim = new Simulation(document.getRocket());
86 sim.setName(document.getNextSimulationName());
88 int n = document.getSimulationCount();
89 document.addSimulation(sim);
90 simulationTableModel.fireTableDataChanged();
91 simulationTable.clearSelection();
92 simulationTable.addRowSelectionInterval(n, n);
94 openDialog(sim, SimulationEditDialog.EDIT);
97 this.add(button, "skip 1, gapright para");
99 //// Edit simulation button
100 button = new JButton(trans.get("simpanel.but.editsimulation"));
101 //// Edit the selected simulation
102 button.setToolTipText(trans.get("simpanel.but.ttip.editsim"));
103 button.addActionListener(new ActionListener() {
105 public void actionPerformed(ActionEvent e) {
106 int selected = simulationTable.getSelectedRow();
108 return; // TODO: MEDIUM: "None selected" dialog
110 selected = simulationTable.convertRowIndexToModel(selected);
111 simulationTable.clearSelection();
112 simulationTable.addRowSelectionInterval(selected, selected);
114 openDialog(document.getSimulations().get(selected), SimulationEditDialog.EDIT);
117 this.add(button, "gapright para");
120 button = new JButton(trans.get("simpanel.but.runsimulations"));
121 //// Re-run the selected simulations
122 button.setToolTipText(trans.get("simpanel.but.ttip.runsimu"));
123 button.addActionListener(new ActionListener() {
125 public void actionPerformed(ActionEvent e) {
126 int[] selection = simulationTable.getSelectedRows();
127 if (selection.length == 0)
128 return; // TODO: LOW: "None selected" dialog
130 Simulation[] sims = new Simulation[selection.length];
131 for (int i = 0; i < selection.length; i++) {
132 selection[i] = simulationTable.convertRowIndexToModel(selection[i]);
133 sims[i] = document.getSimulation(selection[i]);
136 long t = System.currentTimeMillis();
137 new SimulationRunDialog(SwingUtilities.getWindowAncestor(
138 SimulationPanel.this), sims).setVisible(true);
139 log.info("Running simulations took " + (System.currentTimeMillis() - t) + " ms");
140 fireMaintainSelection();
143 this.add(button, "gapright para");
145 //// Delete simulations button
146 button = new JButton(trans.get("simpanel.but.deletesimulations"));
147 //// Delete the selected simulations
148 button.setToolTipText(trans.get("simpanel.but.ttip.deletesim"));
149 button.addActionListener(new ActionListener() {
151 public void actionPerformed(ActionEvent e) {
152 int[] selection = simulationTable.getSelectedRows();
153 if (selection.length == 0)
154 return; // TODO: LOW: "None selected" dialog
157 boolean verify = Application.getPreferences().getBoolean(Preferences.CONFIRM_DELETE_SIMULATION, true);
160 JPanel panel = new JPanel(new MigLayout());
161 //// Do not ask me again
162 JCheckBox dontAsk = new JCheckBox(trans.get("simpanel.checkbox.donotask"));
163 panel.add(dontAsk, "wrap");
164 //// You can change the default operation in the preferences.
165 panel.add(new StyledLabel(trans.get("simpanel.lbl.defpref"), -2));
167 int ret = JOptionPane.showConfirmDialog(SimulationPanel.this,
169 //// Delete the selected simulations?
170 trans.get("simpanel.dlg.lbl.DeleteSim1"),
171 //// <html><i>This operation cannot be undone.</i>
172 trans.get("simpanel.dlg.lbl.DeleteSim2"),
175 //// Delete simulations
176 trans.get("simpanel.dlg.lbl.DeleteSim3"),
177 JOptionPane.OK_CANCEL_OPTION,
178 JOptionPane.WARNING_MESSAGE);
179 if (ret != JOptionPane.OK_OPTION)
182 if (dontAsk.isSelected()) {
183 Application.getPreferences().putBoolean(Preferences.CONFIRM_DELETE_SIMULATION, false);
187 // Delete simulations
188 for (int i = 0; i < selection.length; i++) {
189 selection[i] = simulationTable.convertRowIndexToModel(selection[i]);
191 Arrays.sort(selection);
192 for (int i = selection.length - 1; i >= 0; i--) {
193 document.removeSimulation(selection[i]);
195 simulationTableModel.fireTableDataChanged();
198 this.add(button, "gapright para");
200 //// Plot / export button
201 button = new JButton(trans.get("simpanel.but.plotexport"));
202 // button = new JButton("Plot flight");
203 button.addActionListener(new ActionListener() {
205 public void actionPerformed(ActionEvent e) {
206 int selected = simulationTable.getSelectedRow();
208 return; // TODO: MEDIUM: "None selected" dialog
210 selected = simulationTable.convertRowIndexToModel(selected);
211 simulationTable.clearSelection();
212 simulationTable.addRowSelectionInterval(selected, selected);
214 openDialog(document.getSimulations().get(selected), SimulationEditDialog.PLOT);
217 this.add(button, "wrap para");
222 //////// The simulation table
224 simulationTableModel = new ColumnTableModel(
226 //// Status and warning column
228 private JLabel label = null;
231 public Object getValueAt(int row) {
232 if (row < 0 || row >= document.getSimulationCount())
235 // Initialize the label
237 label = new StyledLabel(2f);
238 label.setIconTextGap(1);
239 // label.setFont(label.getFont().deriveFont(Font.BOLD));
242 // Set simulation status icon
243 Simulation.Status status = document.getSimulation(row).getStatus();
244 label.setIcon(Icons.SIMULATION_STATUS_ICON_MAP.get(status));
247 // Set warning marker
248 if (status == Simulation.Status.NOT_SIMULATED ||
249 status == Simulation.Status.EXTERNAL) {
255 WarningSet w = document.getSimulation(row).getSimulatedWarnings();
258 } else if (w.isEmpty()) {
259 label.setForeground(OK_COLOR);
260 label.setText(OK_TEXT);
262 label.setForeground(WARNING_COLOR);
263 label.setText(WARNING_TEXT);
271 public int getExactWidth() {
276 public Class<?> getColumnClass() {
283 new Column(trans.get("simpanel.col.Name")) {
285 public Object getValueAt(int row) {
286 if (row < 0 || row >= document.getSimulationCount())
288 return document.getSimulation(row).getName();
292 public int getDefaultWidth() {
297 //// Simulation motors
299 new Column(trans.get("simpanel.col.Motors")) {
301 public Object getValueAt(int row) {
302 if (row < 0 || row >= document.getSimulationCount())
304 return document.getSimulation(row).getConfiguration()
305 .getMotorConfigurationDescription();
309 public int getDefaultWidth() {
315 new Column(trans.get("simpanel.col.Apogee")) {
317 public Object getValueAt(int row) {
318 if (row < 0 || row >= document.getSimulationCount())
321 FlightData data = document.getSimulation(row).getSimulatedData();
325 return UnitGroup.UNITS_DISTANCE.getDefaultUnit().toStringUnit(
326 data.getMaxAltitude());
330 //// Maximum velocity
331 new Column(trans.get("simpanel.col.Maxvelocity")) {
333 public Object getValueAt(int row) {
334 if (row < 0 || row >= document.getSimulationCount())
337 FlightData data = document.getSimulation(row).getSimulatedData();
341 return UnitGroup.UNITS_VELOCITY.getDefaultUnit().toStringUnit(
342 data.getMaxVelocity());
346 //// Maximum acceleration
347 new Column(trans.get("simpanel.col.Maxacceleration")) {
349 public Object getValueAt(int row) {
350 if (row < 0 || row >= document.getSimulationCount())
353 FlightData data = document.getSimulation(row).getSimulatedData();
357 return UnitGroup.UNITS_ACCELERATION.getDefaultUnit().toStringUnit(
358 data.getMaxAcceleration());
363 new Column(trans.get("simpanel.col.Timetoapogee")) {
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.getTimeToApogee());
379 new Column(trans.get("simpanel.col.Flighttime")) {
381 public Object getValueAt(int row) {
382 if (row < 0 || row >= document.getSimulationCount())
385 FlightData data = document.getSimulation(row).getSimulatedData();
389 return UnitGroup.UNITS_FLIGHT_TIME.getDefaultUnit().toStringUnit(
390 data.getFlightTime());
394 //// Ground hit velocity
395 new Column(trans.get("simpanel.col.Groundhitvelocity")) {
397 public Object getValueAt(int row) {
398 if (row < 0 || row >= document.getSimulationCount())
401 FlightData data = document.getSimulation(row).getSimulatedData();
405 return UnitGroup.UNITS_VELOCITY.getDefaultUnit().toStringUnit(
406 data.getGroundHitVelocity());
412 public int getRowCount() {
413 return document.getSimulationCount();
417 // Override processKeyBinding so that the JTable does not catch
418 // key bindings used in menu accelerators
419 simulationTable = new JTable(simulationTableModel) {
421 protected boolean processKeyBinding(KeyStroke ks,
428 simulationTable.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
429 simulationTable.setDefaultRenderer(Object.class, new JLabelRenderer());
430 simulationTableModel.setColumnWidths(simulationTable.getColumnModel());
433 // Mouse listener to act on double-clicks
434 simulationTable.addMouseListener(new MouseAdapter() {
436 public void mouseClicked(MouseEvent e) {
437 if (e.getButton() == MouseEvent.BUTTON1 && e.getClickCount() == 2) {
438 int selected = simulationTable.getSelectedRow();
442 selected = simulationTable.convertRowIndexToModel(selected);
443 simulationTable.clearSelection();
444 simulationTable.addRowSelectionInterval(selected, selected);
446 openDialog(document.getSimulations().get(selected),
447 SimulationEditDialog.DEFAULT);
452 document.addDocumentChangeListener(new DocumentChangeListener() {
454 public void documentChanged(DocumentChangeEvent event) {
455 if (!(event instanceof SimulationChangeEvent))
457 simulationTableModel.fireTableDataChanged();
464 // Fire table change event when the rocket changes
465 document.getRocket().addComponentChangeListener(new ComponentChangeListener() {
467 public void componentChanged(ComponentChangeEvent e) {
468 fireMaintainSelection();
473 JScrollPane scrollpane = new JScrollPane(simulationTable);
474 this.add(scrollpane, "spanx, grow, wrap rel");
480 public ListSelectionModel getSimulationListSelectionModel() {
481 return simulationTable.getSelectionModel();
484 private void openDialog(final Simulation sim, int position) {
485 new SimulationEditDialog(SwingUtilities.getWindowAncestor(this), sim, position)
487 fireMaintainSelection();
490 private void fireMaintainSelection() {
491 int[] selection = simulationTable.getSelectedRows();
492 simulationTableModel.fireTableDataChanged();
493 for (int row : selection) {
494 if (row >= simulationTableModel.getRowCount())
496 simulationTable.addRowSelectionInterval(row, row);
501 private class JLabelRenderer extends DefaultTableCellRenderer {
504 public Component getTableCellRendererComponent(JTable table,
505 Object value, boolean isSelected, boolean hasFocus, int row,
508 if (row < 0 || row >= document.getSimulationCount())
509 return super.getTableCellRendererComponent(table, value,
510 isSelected, hasFocus, row, column);
512 // A JLabel is self-contained and has set its own tool tip
513 if (value instanceof JLabel) {
514 JLabel label = (JLabel) value;
516 label.setBackground(table.getSelectionBackground());
518 label.setBackground(table.getBackground());
519 label.setOpaque(true);
521 label.setToolTipText(getSimulationToolTip(document.getSimulation(row)));
525 Component component = super.getTableCellRendererComponent(table, value,
526 isSelected, hasFocus, row, column);
528 if (component instanceof JComponent) {
529 ((JComponent) component).setToolTipText(getSimulationToolTip(
530 document.getSimulation(row)));
535 private String getSimulationToolTip(Simulation sim) {
537 FlightData data = sim.getSimulatedData();
539 tip = "<html><b>" + sim.getName() + "</b><br>";
540 switch (sim.getStatus()) {
542 //// <i>Up to date</i><br>
543 tip += "<i>Up to date</i><br>";
547 //// <i>Data loaded from a file</i><br>
548 tip += "<i>Data loaded from a file</i><br>";
552 tip += "<i><font color=\"red\">Data is out of date</font></i><br>";
553 tip += "Click <i><b>Run simulations</b></i> to simulate.<br>";
557 tip += "<i>Imported data</i><br>";
561 tip += "<i>Not simulated yet</i><br>";
562 tip += "Click <i><b>Run simulations</b></i> to simulate.";
567 tip += "No simulation data available.";
570 WarningSet warnings = data.getWarningSet();
572 if (warnings.isEmpty()) {
573 tip += "<font color=\"gray\">No warnings.</font>";
577 tip += "<font color=\"red\">Warnings:</font>";
578 for (Warning w : warnings) {
579 tip += "<br>" + w.toString();