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), document, 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() {
314 //// Launch rod velocity
315 new Column(trans.get("simpanel.col.Velocityoffrod")) {
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.getLaunchRodVelocity());
331 new Column(trans.get("simpanel.col.Apogee")) {
333 public Object getValueAt(int row) {
334 if (row < 0 || row >= document.getSimulationCount())
337 FlightData data = document.getSimulation(row).getSimulatedData();
341 return UnitGroup.UNITS_DISTANCE.getDefaultUnit().toStringUnit(
342 data.getMaxAltitude());
346 //// Velocity at deployment
347 new Column(trans.get("simpanel.col.Velocityatdeploy")) {
349 public Object getValueAt(int row) {
350 if (row < 0 || row >= document.getSimulationCount())
353 FlightData data = document.getSimulation(row).getSimulatedData();
357 return UnitGroup.UNITS_VELOCITY.getDefaultUnit().toStringUnit(
358 data.getDeploymentVelocity());
362 //// Maximum velocity
363 new Column(trans.get("simpanel.col.Maxvelocity")) {
365 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.getMaxVelocity());
378 //// Maximum acceleration
379 new Column(trans.get("simpanel.col.Maxacceleration")) {
381 public Object getValueAt(int row) {
382 if (row < 0 || row >= document.getSimulationCount())
385 FlightData data = document.getSimulation(row).getSimulatedData();
389 return UnitGroup.UNITS_ACCELERATION.getDefaultUnit().toStringUnit(
390 data.getMaxAcceleration());
395 new Column(trans.get("simpanel.col.Timetoapogee")) {
397 public Object getValueAt(int row) {
398 if (row < 0 || row >= document.getSimulationCount())
401 FlightData data = document.getSimulation(row).getSimulatedData();
405 return UnitGroup.UNITS_FLIGHT_TIME.getDefaultUnit().toStringUnit(
406 data.getTimeToApogee());
411 new Column(trans.get("simpanel.col.Flighttime")) {
413 public Object getValueAt(int row) {
414 if (row < 0 || row >= document.getSimulationCount())
417 FlightData data = document.getSimulation(row).getSimulatedData();
421 return UnitGroup.UNITS_FLIGHT_TIME.getDefaultUnit().toStringUnit(
422 data.getFlightTime());
426 //// Ground hit velocity
427 new Column(trans.get("simpanel.col.Groundhitvelocity")) {
429 public Object getValueAt(int row) {
430 if (row < 0 || row >= document.getSimulationCount())
433 FlightData data = document.getSimulation(row).getSimulatedData();
437 return UnitGroup.UNITS_VELOCITY.getDefaultUnit().toStringUnit(
438 data.getGroundHitVelocity());
444 public int getRowCount() {
445 return document.getSimulationCount();
449 // Override processKeyBinding so that the JTable does not catch
450 // key bindings used in menu accelerators
451 simulationTable = new JTable(simulationTableModel) {
453 protected boolean processKeyBinding(KeyStroke ks,
460 simulationTable.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
461 simulationTable.setDefaultRenderer(Object.class, new JLabelRenderer());
462 simulationTableModel.setColumnWidths(simulationTable.getColumnModel());
465 // Mouse listener to act on double-clicks
466 simulationTable.addMouseListener(new MouseAdapter() {
468 public void mouseClicked(MouseEvent e) {
469 if (e.getButton() == MouseEvent.BUTTON1 && e.getClickCount() == 2) {
470 int selected = simulationTable.getSelectedRow();
474 selected = simulationTable.convertRowIndexToModel(selected);
475 simulationTable.clearSelection();
476 simulationTable.addRowSelectionInterval(selected, selected);
478 openDialog(document.getSimulations().get(selected),
479 SimulationEditDialog.DEFAULT);
484 document.addDocumentChangeListener(new DocumentChangeListener() {
486 public void documentChanged(DocumentChangeEvent event) {
487 if (!(event instanceof SimulationChangeEvent))
489 simulationTableModel.fireTableDataChanged();
496 // Fire table change event when the rocket changes
497 document.getRocket().addComponentChangeListener(new ComponentChangeListener() {
499 public void componentChanged(ComponentChangeEvent e) {
500 fireMaintainSelection();
505 JScrollPane scrollpane = new JScrollPane(simulationTable);
506 this.add(scrollpane, "spanx, grow, wrap rel");
512 public ListSelectionModel getSimulationListSelectionModel() {
513 return simulationTable.getSelectionModel();
516 private void openDialog(final Simulation sim, int position) {
517 new SimulationEditDialog(SwingUtilities.getWindowAncestor(this), document, sim, position)
519 fireMaintainSelection();
522 private void fireMaintainSelection() {
523 int[] selection = simulationTable.getSelectedRows();
524 simulationTableModel.fireTableDataChanged();
525 for (int row : selection) {
526 if (row >= simulationTableModel.getRowCount())
528 simulationTable.addRowSelectionInterval(row, row);
533 private class JLabelRenderer extends DefaultTableCellRenderer {
536 public Component getTableCellRendererComponent(JTable table,
537 Object value, boolean isSelected, boolean hasFocus, int row,
540 if (row < 0 || row >= document.getSimulationCount())
541 return super.getTableCellRendererComponent(table, value,
542 isSelected, hasFocus, row, column);
544 // A JLabel is self-contained and has set its own tool tip
545 if (value instanceof JLabel) {
546 JLabel label = (JLabel) value;
548 label.setBackground(table.getSelectionBackground());
550 label.setBackground(table.getBackground());
551 label.setOpaque(true);
553 label.setToolTipText(getSimulationToolTip(document.getSimulation(row)));
557 Component component = super.getTableCellRendererComponent(table, value,
558 isSelected, hasFocus, row, column);
560 if (component instanceof JComponent) {
561 ((JComponent) component).setToolTipText(getSimulationToolTip(
562 document.getSimulation(row)));
567 private String getSimulationToolTip(Simulation sim) {
569 FlightData data = sim.getSimulatedData();
571 tip = "<html><b>" + sim.getName() + "</b><br>";
572 switch (sim.getStatus()) {
574 tip += trans.get ("simpanel.ttip.uptodate") + "<br>";
578 tip += trans.get ("simpanel.ttip.loaded") + "<br>";
582 tip += trans.get ("simpanel.ttip.outdated") + "<br>";
586 tip += trans.get ("simpanel.ttip.external") + "<br>";
590 tip += trans.get ("simpanel.ttip.notSimulated");
595 tip += trans.get ("simpanel.ttip.noData");
598 WarningSet warnings = data.getWarningSet();
600 if (warnings.isEmpty()) {
601 tip += trans.get ("simpanel.ttip.noWarnings");
605 tip += trans.get ("simpanel.ttip.warnings");
606 for (Warning w : warnings) {
607 tip += "<br>" + w.toString();