package net.sf.openrocket.gui.main; import java.awt.Color; import java.awt.Component; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.KeyEvent; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.util.Arrays; import java.util.Comparator; import javax.swing.JButton; import javax.swing.JCheckBox; import javax.swing.JComponent; import javax.swing.JLabel; import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.JTable; import javax.swing.KeyStroke; import javax.swing.ListSelectionModel; import javax.swing.SwingUtilities; import javax.swing.table.DefaultTableCellRenderer; import net.miginfocom.swing.MigLayout; import net.sf.openrocket.aerodynamics.Warning; import net.sf.openrocket.aerodynamics.WarningSet; import net.sf.openrocket.document.OpenRocketDocument; import net.sf.openrocket.document.Simulation; import net.sf.openrocket.document.Simulation.Status; import net.sf.openrocket.document.events.DocumentChangeEvent; import net.sf.openrocket.document.events.DocumentChangeListener; import net.sf.openrocket.document.events.SimulationChangeEvent; import net.sf.openrocket.formatting.RocketDescriptor; import net.sf.openrocket.gui.adaptors.Column; import net.sf.openrocket.gui.adaptors.ColumnTable; import net.sf.openrocket.gui.adaptors.ColumnTableModel; import net.sf.openrocket.gui.adaptors.ColumnTableRowSorter; import net.sf.openrocket.gui.adaptors.ValueColumn; import net.sf.openrocket.gui.components.StyledLabel; import net.sf.openrocket.gui.simulation.SimulationEditDialog; import net.sf.openrocket.gui.simulation.SimulationRunDialog; import net.sf.openrocket.gui.simulation.SimulationWarningDialog; import net.sf.openrocket.gui.util.Icons; import net.sf.openrocket.gui.util.SwingPreferences; import net.sf.openrocket.l10n.Translator; import net.sf.openrocket.rocketcomponent.ComponentChangeEvent; import net.sf.openrocket.rocketcomponent.ComponentChangeListener; import net.sf.openrocket.rocketcomponent.Configuration; import net.sf.openrocket.simulation.FlightData; import net.sf.openrocket.simulation.FlightEvent; import net.sf.openrocket.startup.Application; import net.sf.openrocket.startup.Preferences; import net.sf.openrocket.unit.UnitGroup; import net.sf.openrocket.util.AlphanumComparator; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class SimulationPanel extends JPanel { private static final Logger log = LoggerFactory.getLogger(SimulationPanel.class); private static final Translator trans = Application.getTranslator(); private static final Color WARNING_COLOR = Color.RED; private static final String WARNING_TEXT = "\uFF01"; // Fullwidth exclamation mark private static final Color OK_COLOR = new Color(60, 150, 0); private static final String OK_TEXT = "\u2714"; // Heavy check mark private RocketDescriptor descriptor = Application.getInjector().getInstance(RocketDescriptor.class); private final OpenRocketDocument document; private final ColumnTableModel simulationTableModel; private final JTable simulationTable; private final JButton editButton; private final JButton runButton; private final JButton deleteButton; private final JButton plotButton; public SimulationPanel(OpenRocketDocument doc) { super(new MigLayout("fill", "[grow][][][][][][grow]")); this.document = doc; //////// The simulation action buttons //// New simulation button { JButton button = new JButton(trans.get("simpanel.but.newsimulation")); //// Add a new simulation button.setToolTipText(trans.get("simpanel.but.ttip.newsimulation")); button.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { Simulation sim = new Simulation(document.getRocket()); sim.setName(document.getNextSimulationName()); int n = document.getSimulationCount(); document.addSimulation(sim); simulationTableModel.fireTableDataChanged(); simulationTable.clearSelection(); simulationTable.addRowSelectionInterval(n, n); openDialog(false, sim); } }); this.add(button, "skip 1, gapright para"); } //// Edit simulation button editButton = new JButton(trans.get("simpanel.but.editsimulation")); //// Edit the selected simulation editButton.setToolTipText(trans.get("simpanel.but.ttip.editsim")); editButton.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { int[] selection = simulationTable.getSelectedRows(); if (selection.length == 0) { return; } Simulation[] sims = new Simulation[selection.length]; for (int i = 0; i < selection.length; i++) { selection[i] = simulationTable.convertRowIndexToModel(selection[i]); sims[i] = document.getSimulation(selection[i]); } openDialog(false, sims); } }); this.add(editButton, "gapright para"); //// Run simulations runButton = new JButton(trans.get("simpanel.but.runsimulations")); //// Re-run the selected simulations runButton.setToolTipText(trans.get("simpanel.but.ttip.runsimu")); runButton.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { int[] selection = simulationTable.getSelectedRows(); if (selection.length == 0) { return; } Simulation[] sims = new Simulation[selection.length]; for (int i = 0; i < selection.length; i++) { selection[i] = simulationTable.convertRowIndexToModel(selection[i]); sims[i] = document.getSimulation(selection[i]); } long t = System.currentTimeMillis(); new SimulationRunDialog(SwingUtilities.getWindowAncestor( SimulationPanel.this), document, sims).setVisible(true); log.info("Running simulations took " + (System.currentTimeMillis() - t) + " ms"); fireMaintainSelection(); } }); this.add(runButton, "gapright para"); //// Delete simulations button deleteButton = new JButton(trans.get("simpanel.but.deletesimulations")); //// Delete the selected simulations deleteButton.setToolTipText(trans.get("simpanel.but.ttip.deletesim")); deleteButton.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { int[] selection = simulationTable.getSelectedRows(); if (selection.length == 0) { return; } // Verify deletion boolean verify = Application.getPreferences().getBoolean(Preferences.CONFIRM_DELETE_SIMULATION, true); if (verify) { JPanel panel = new JPanel(new MigLayout()); //// Do not ask me again JCheckBox dontAsk = new JCheckBox(trans.get("simpanel.checkbox.donotask")); panel.add(dontAsk, "wrap"); //// You can change the default operation in the preferences. panel.add(new StyledLabel(trans.get("simpanel.lbl.defpref"), -2)); int ret = JOptionPane.showConfirmDialog(SimulationPanel.this, new Object[] { //// Delete the selected simulations? trans.get("simpanel.dlg.lbl.DeleteSim1"), //// <html><i>This operation cannot be undone.</i> trans.get("simpanel.dlg.lbl.DeleteSim2"), "", panel }, //// Delete simulations trans.get("simpanel.dlg.lbl.DeleteSim3"), JOptionPane.OK_CANCEL_OPTION, JOptionPane.WARNING_MESSAGE); if (ret != JOptionPane.OK_OPTION) return; if (dontAsk.isSelected()) { Application.getPreferences().putBoolean(Preferences.CONFIRM_DELETE_SIMULATION, false); } } // Delete simulations for (int i = 0; i < selection.length; i++) { selection[i] = simulationTable.convertRowIndexToModel(selection[i]); } Arrays.sort(selection); for (int i = selection.length - 1; i >= 0; i--) { document.removeSimulation(selection[i]); } simulationTableModel.fireTableDataChanged(); } }); this.add(deleteButton, "gapright para"); //// Plot / export button plotButton = new JButton(trans.get("simpanel.but.plotexport")); // button = new JButton("Plot flight"); plotButton.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { int selected = simulationTable.getSelectedRow(); if (selected < 0) { return; } selected = simulationTable.convertRowIndexToModel(selected); simulationTable.clearSelection(); simulationTable.addRowSelectionInterval(selected, selected); Simulation sim = document.getSimulations().get(selected); if (!sim.hasSimulationData()) { new SimulationRunDialog(SwingUtilities.getWindowAncestor( SimulationPanel.this), document, sim).setVisible(true); } fireMaintainSelection(); openDialog(true, sim); } }); this.add(plotButton, "wrap para"); //////// The simulation table simulationTableModel = new ColumnTableModel( //// Status and warning column new Column("") { private JLabel label = null; @Override public Object getValueAt(int row) { if (row < 0 || row >= document.getSimulationCount()) return null; // Initialize the label if (label == null) { label = new StyledLabel(2f); label.setIconTextGap(1); // label.setFont(label.getFont().deriveFont(Font.BOLD)); } // Set simulation status icon Simulation.Status status = document.getSimulation(row).getStatus(); label.setIcon(Icons.SIMULATION_STATUS_ICON_MAP.get(status)); // Set warning marker if (status == Simulation.Status.NOT_SIMULATED || status == Simulation.Status.EXTERNAL) { label.setText(""); } else { WarningSet w = document.getSimulation(row).getSimulatedWarnings(); if (w == null) { label.setText(""); } else if (w.isEmpty()) { label.setForeground(OK_COLOR); label.setText(OK_TEXT); } else { label.setForeground(WARNING_COLOR); label.setText(WARNING_TEXT); } } return label; } @Override public int getExactWidth() { return 36; } @Override public Class<?> getColumnClass() { return JLabel.class; } }, //// Simulation name //// Name new Column(trans.get("simpanel.col.Name")) { @Override public Object getValueAt(int row) { if (row < 0 || row >= document.getSimulationCount()) return null; return document.getSimulation(row).getName(); } @Override public int getDefaultWidth() { return 125; } @Override public Comparator getComparator() { return new AlphanumComparator(); } }, //// Simulation configuration new Column(trans.get("simpanel.col.Configuration")) { @Override public Object getValueAt(int row) { if (row < 0 || row >= document.getSimulationCount()) return null; Configuration c = document.getSimulation(row).getConfiguration(); return descriptor.format(c.getRocket(), c.getFlightConfigurationID()); } @Override public int getDefaultWidth() { return 125; } }, //// Launch rod velocity new ValueColumn(trans.get("simpanel.col.Velocityoffrod"), UnitGroup.UNITS_VELOCITY) { @Override public Double valueAt(int row) { if (row < 0 || row >= document.getSimulationCount()) return null; FlightData data = document.getSimulation(row).getSimulatedData(); if (data == null) return null; return data.getLaunchRodVelocity(); } }, //// Apogee new ValueColumn(trans.get("simpanel.col.Apogee"), UnitGroup.UNITS_DISTANCE) { @Override public Double valueAt(int row) { if (row < 0 || row >= document.getSimulationCount()) return null; FlightData data = document.getSimulation(row).getSimulatedData(); if (data == null) return null; return data.getMaxAltitude(); } }, //// Velocity at deployment new ValueColumn(trans.get("simpanel.col.Velocityatdeploy"), UnitGroup.UNITS_VELOCITY) { @Override public Double valueAt(int row) { if (row < 0 || row >= document.getSimulationCount()) return null; FlightData data = document.getSimulation(row).getSimulatedData(); if (data == null) return null; return data.getDeploymentVelocity(); } }, //// Deployment Time from Apogee new ValueColumn(trans.get("simpanel.col.OptimumCoastTime"), trans.get("simpanel.col.OptimumCoastTime.ttip"), UnitGroup.UNITS_SHORT_TIME) { @Override public Double valueAt(int row) { if (row < 0 || row >= document.getSimulationCount()) return null; FlightData data = document.getSimulation(row).getSimulatedData(); if (data == null || data.getBranchCount() == 0) return null; double val = data.getBranch(0).getOptimumDelay(); if ( Double.isNaN(val) ) { return null; } return val; } }, //// Maximum velocity new ValueColumn(trans.get("simpanel.col.Maxvelocity"), UnitGroup.UNITS_VELOCITY) { @Override public Double valueAt(int row) { if (row < 0 || row >= document.getSimulationCount()) return null; FlightData data = document.getSimulation(row).getSimulatedData(); if (data == null) return null; return data.getMaxVelocity(); } }, //// Maximum acceleration new ValueColumn(trans.get("simpanel.col.Maxacceleration"), UnitGroup.UNITS_ACCELERATION) { @Override public Double valueAt(int row) { if (row < 0 || row >= document.getSimulationCount()) return null; FlightData data = document.getSimulation(row).getSimulatedData(); if (data == null) return null; return data.getMaxAcceleration(); } }, //// Time to apogee new ValueColumn(trans.get("simpanel.col.Timetoapogee"), UnitGroup.UNITS_FLIGHT_TIME) { @Override public Double valueAt(int row) { if (row < 0 || row >= document.getSimulationCount()) return null; FlightData data = document.getSimulation(row).getSimulatedData(); if (data == null) return null; return data.getTimeToApogee(); } }, //// Flight time new ValueColumn(trans.get("simpanel.col.Flighttime"), UnitGroup.UNITS_FLIGHT_TIME) { @Override public Double valueAt(int row) { if (row < 0 || row >= document.getSimulationCount()) return null; FlightData data = document.getSimulation(row).getSimulatedData(); if (data == null) return null; return data.getFlightTime(); } }, //// Ground hit velocity new ValueColumn(trans.get("simpanel.col.Groundhitvelocity"), UnitGroup.UNITS_VELOCITY) { @Override public Double valueAt(int row) { if (row < 0 || row >= document.getSimulationCount()) return null; FlightData data = document.getSimulation(row).getSimulatedData(); if (data == null) return null; return data.getGroundHitVelocity(); } } ) { @Override public int getRowCount() { return document.getSimulationCount(); } }; // Override processKeyBinding so that the JTable does not catch // key bindings used in menu accelerators simulationTable = new ColumnTable(simulationTableModel) { @Override protected boolean processKeyBinding(KeyStroke ks, KeyEvent e, int condition, boolean pressed) { return false; } }; ColumnTableRowSorter simulationTableSorter = new ColumnTableRowSorter(simulationTableModel); simulationTable.setRowSorter(simulationTableSorter); simulationTable.setAutoResizeMode(JTable.AUTO_RESIZE_OFF); simulationTable.setDefaultRenderer(Object.class, new JLabelRenderer()); simulationTableModel.setColumnWidths(simulationTable.getColumnModel()); // Mouse listener to act on double-clicks simulationTable.addMouseListener(new MouseAdapter() { @Override public void mouseClicked(MouseEvent e) { if (e.getButton() == MouseEvent.BUTTON1 && e.getClickCount() == 2) { int selectedRow = simulationTable.getSelectedRow(); if (selectedRow < 0) { return; } int selected = simulationTable.convertRowIndexToModel(selectedRow); int column = simulationTable.columnAtPoint(e.getPoint()); if (column == 0) { SimulationWarningDialog.showWarningDialog(SimulationPanel.this, document.getSimulations().get(selected)); } else { simulationTable.clearSelection(); simulationTable.addRowSelectionInterval(selectedRow, selectedRow); openDialog(document.getSimulations().get(selected)); } } else { updateButtonStates(); } } }); document.addDocumentChangeListener(new DocumentChangeListener() { @Override public void documentChanged(DocumentChangeEvent event) { if (!(event instanceof SimulationChangeEvent)) return; simulationTableModel.fireTableDataChanged(); } }); // Fire table change event when the rocket changes document.getRocket().addComponentChangeListener(new ComponentChangeListener() { @Override public void componentChanged(ComponentChangeEvent e) { fireMaintainSelection(); } }); JScrollPane scrollpane = new JScrollPane(simulationTable); this.add(scrollpane, "spanx, grow, wrap rel"); updateButtonStates(); } private void updateButtonStates() { int[] selection = simulationTable.getSelectedRows(); if (selection.length == 0) { editButton.setEnabled(false); runButton.setEnabled(false); deleteButton.setEnabled(false); plotButton.setEnabled(false); } else { if (selection.length > 1) { plotButton.setEnabled(false); } else { plotButton.setEnabled(true); } editButton.setEnabled(true); runButton.setEnabled(true); deleteButton.setEnabled(true); } } /// when the simulation tab is selected this run outdated simulated if appropriate. public void activating(){ if( ((Preferences) Application.getPreferences()).getAutoRunSimulations()){ int nSims = simulationTable.getRowCount(); int outdated = 0; if (nSims == 0) { return; } for (int i = 0; i < nSims; i++) { Simulation.Status s = document.getSimulation(simulationTable.convertRowIndexToModel(i)).getStatus(); if((s==Simulation.Status.NOT_SIMULATED) || (s==Simulation.Status.OUTDATED)){ outdated++; } } if(outdated>0){ Simulation[] sims = new Simulation[outdated]; int index=0; for (int i = 0; i < nSims; i++) { int t = simulationTable.convertRowIndexToModel(i); Simulation s = document.getSimulation(t); if((s.getStatus()==Status.NOT_SIMULATED)||(s.getStatus()==Status.OUTDATED)){ sims[index] = s; index++; } } long t = System.currentTimeMillis(); new SimulationRunDialog(SwingUtilities.getWindowAncestor( SimulationPanel.this), document, sims).setVisible(true); log.info("Running simulations took " + (System.currentTimeMillis() - t) + " ms"); fireMaintainSelection(); } } } public ListSelectionModel getSimulationListSelectionModel() { return simulationTable.getSelectionModel(); } private void openDialog(boolean plotMode, final Simulation... sims) { SimulationEditDialog d = new SimulationEditDialog(SwingUtilities.getWindowAncestor(this), document, sims); if (plotMode) { d.setPlotMode(); } d.setVisible(true); fireMaintainSelection(); } private void openDialog(final Simulation sim) { boolean plotMode = false; if (sim.hasSimulationData() && (sim.getStatus() == Status.UPTODATE || sim.getStatus() == Status.EXTERNAL)) { plotMode = true; } openDialog(plotMode, sim); } private void fireMaintainSelection() { int[] selection = simulationTable.getSelectedRows(); simulationTableModel.fireTableDataChanged(); for (int row : selection) { if (row >= simulationTableModel.getRowCount()) break; simulationTable.addRowSelectionInterval(row, row); } } private enum SimulationTableColumns { } private class JLabelRenderer extends DefaultTableCellRenderer { @Override public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { if (row < 0 || row >= document.getSimulationCount()) return super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column); row = table.getRowSorter().convertRowIndexToModel(row); // A JLabel is self-contained and has set its own tool tip if (value instanceof JLabel) { JLabel label = (JLabel) value; if (isSelected) label.setBackground(table.getSelectionBackground()); else label.setBackground(table.getBackground()); label.setOpaque(true); label.setToolTipText(getSimulationToolTip(document.getSimulation(row))); return label; } Component component = super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column); if (component instanceof JComponent) { ((JComponent) component).setToolTipText(getSimulationToolTip( document.getSimulation(row))); } return component; } private String getSimulationToolTip(Simulation sim) { String tip; FlightData data = sim.getSimulatedData(); tip = "<html><b>" + sim.getName() + "</b><br>"; switch (sim.getStatus()) { case UPTODATE: tip += trans.get("simpanel.ttip.uptodate") + "<br>"; break; case LOADED: tip += trans.get("simpanel.ttip.loaded") + "<br>"; break; case OUTDATED: tip += trans.get("simpanel.ttip.outdated") + "<br>"; break; case EXTERNAL: tip += trans.get("simpanel.ttip.external") + "<br>"; return tip; case NOT_SIMULATED: tip += trans.get("simpanel.ttip.notSimulated"); return tip; } if (data == null) { tip += trans.get("simpanel.ttip.noData"); return tip; } WarningSet warnings = data.getWarningSet(); if (warnings.isEmpty()) { tip += trans.get("simpanel.ttip.noWarnings"); return tip; } tip += trans.get("simpanel.ttip.warnings"); for (Warning w : warnings) { tip += "<br>" + w.toString(); } return tip; } } }