package net.sf.openrocket.gui.simulation; import java.awt.Window; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.ItemEvent; import java.awt.event.ItemListener; import java.util.Arrays; import java.util.EnumSet; import javax.swing.BorderFactory; import javax.swing.JButton; import javax.swing.JComboBox; import javax.swing.JDialog; import javax.swing.JLabel; import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.JTable; import javax.swing.table.AbstractTableModel; import javax.swing.table.TableColumn; import javax.swing.table.TableColumnModel; import net.miginfocom.swing.MigLayout; import net.sf.openrocket.document.Simulation; import net.sf.openrocket.gui.components.DescriptionArea; import net.sf.openrocket.gui.components.UnitSelector; import net.sf.openrocket.gui.plot.PlotConfiguration; import net.sf.openrocket.gui.plot.SimulationPlotDialog; import net.sf.openrocket.gui.util.GUIUtil; import net.sf.openrocket.gui.util.Icons; import net.sf.openrocket.l10n.Translator; import net.sf.openrocket.simulation.FlightDataBranch; import net.sf.openrocket.simulation.FlightDataType; import net.sf.openrocket.simulation.FlightEvent; import net.sf.openrocket.startup.Application; import net.sf.openrocket.unit.Unit; import net.sf.openrocket.util.Utils; /** * Panel that displays the simulation plot options to the user. * * @author Sampo Niskanen <sampo.niskanen@iki.fi> */ public class SimulationPlotPanel extends JPanel { private static final Translator trans = Application.getTranslator(); // TODO: LOW: Should these be somewhere else? public static final int AUTO = -1; public static final int LEFT = 0; public static final int RIGHT = 1; //// Auto public static final String AUTO_NAME = trans.get("simplotpanel.AUTO_NAME"); //// Left public static final String LEFT_NAME = trans.get("simplotpanel.LEFT_NAME"); //// Right public static final String RIGHT_NAME = trans.get("simplotpanel.RIGHT_NAME"); //// Custom private static final String CUSTOM = trans.get("simplotpanel.CUSTOM"); /** The "Custom" configuration - not to be used for anything other than the title. */ private static final PlotConfiguration CUSTOM_CONFIGURATION; static { CUSTOM_CONFIGURATION = new PlotConfiguration(CUSTOM); } /** The array of presets for the combo box. */ private static final PlotConfiguration[] PRESET_ARRAY; static { PRESET_ARRAY = Arrays.copyOf(PlotConfiguration.DEFAULT_CONFIGURATIONS, PlotConfiguration.DEFAULT_CONFIGURATIONS.length + 1); PRESET_ARRAY[PRESET_ARRAY.length - 1] = CUSTOM_CONFIGURATION; } /** The current default configuration, set each time a plot is made. */ private static PlotConfiguration defaultConfiguration = PlotConfiguration.DEFAULT_CONFIGURATIONS[0].resetUnits(); private final Simulation simulation; private final FlightDataType[] types; private PlotConfiguration configuration; private JComboBox configurationSelector; private JComboBox domainTypeSelector; private UnitSelector domainUnitSelector; private JPanel typeSelectorPanel; private FlightEventTableModel eventTableModel; private int modifying = 0; public SimulationPlotPanel(final Simulation simulation) { super(new MigLayout("fill")); this.simulation = simulation; if (simulation.getSimulatedData() == null || simulation.getSimulatedData().getBranchCount() == 0) { throw new IllegalArgumentException("Simulation contains no data."); } FlightDataBranch branch = simulation.getSimulatedData().getBranch(0); types = branch.getTypes(); setConfiguration(defaultConfiguration); //// Configuration selector // Setup the combo box configurationSelector = new JComboBox(PRESET_ARRAY); for (PlotConfiguration config : PRESET_ARRAY) { if (config.getName().equals(configuration.getName())) { configurationSelector.setSelectedItem(config); } } configurationSelector.addItemListener(new ItemListener() { @Override public void itemStateChanged(ItemEvent e) { // We are only concerned with ItemEvent.SELECTED to update // the UI when the selected item changes. // TODO - this should probably be implemented as an ActionListener instead // of ItemStateListener. if (e.getStateChange() == ItemEvent.DESELECTED) { return; } if (modifying > 0) return; PlotConfiguration conf = (PlotConfiguration) configurationSelector.getSelectedItem(); if (conf == CUSTOM_CONFIGURATION) return; modifying++; setConfiguration(conf.clone().resetUnits()); updatePlots(); modifying--; } }); //// Preset plot configurations: this.add(new JLabel(trans.get("simplotpanel.lbl.Presetplotconf")), "spanx, split"); this.add(configurationSelector, "growx, wrap 20lp"); //// X axis //// X axis type: this.add(new JLabel(trans.get("simplotpanel.lbl.Xaxistype")), "spanx, split"); domainTypeSelector = new JComboBox(types); domainTypeSelector.setSelectedItem(configuration.getDomainAxisType()); domainTypeSelector.addItemListener(new ItemListener() { @Override public void itemStateChanged(ItemEvent e) { if (modifying > 0) return; FlightDataType type = (FlightDataType) domainTypeSelector.getSelectedItem(); configuration.setDomainAxisType(type); domainUnitSelector.setUnitGroup(type.getUnitGroup()); domainUnitSelector.setSelectedUnit(configuration.getDomainAxisUnit()); setToCustom(); } }); this.add(domainTypeSelector, "gapright para"); //// Unit: this.add(new JLabel(trans.get("simplotpanel.lbl.Unit"))); domainUnitSelector = new UnitSelector(configuration.getDomainAxisType().getUnitGroup()); domainUnitSelector.setSelectedUnit(configuration.getDomainAxisUnit()); domainUnitSelector.addItemListener(new ItemListener() { @Override public void itemStateChanged(ItemEvent e) { if (modifying > 0) return; configuration.setDomainAxisUnit(domainUnitSelector.getSelectedUnit()); } }); this.add(domainUnitSelector, "width 40lp, gapright para"); //// The data will be plotted in time order even if the X axis type is not time. DescriptionArea desc = new DescriptionArea(trans.get("simplotpanel.Desc"), 2, -2f); desc.setViewportBorder(BorderFactory.createEmptyBorder()); this.add(desc, "width 1px, growx 1, wrap unrel"); //// Y axis selector panel //// Y axis types: this.add(new JLabel(trans.get("simplotpanel.lbl.Yaxistypes"))); //// Flight events: this.add(new JLabel(trans.get("simplotpanel.lbl.Flightevents")), "wrap rel"); typeSelectorPanel = new JPanel(new MigLayout("gapy rel")); JScrollPane scroll = new JScrollPane(typeSelectorPanel); this.add(scroll, "spany 2, height 10px, wmin 400lp, grow 100, gapright para"); //// Flight events eventTableModel = new FlightEventTableModel(); JTable table = new JTable(eventTableModel); table.setTableHeader(null); table.setShowVerticalLines(false); table.setRowSelectionAllowed(false); table.setColumnSelectionAllowed(false); TableColumnModel columnModel = table.getColumnModel(); TableColumn col0 = columnModel.getColumn(0); int w = table.getRowHeight() + 2; col0.setMinWidth(w); col0.setPreferredWidth(w); col0.setMaxWidth(w); table.addMouseListener(new GUIUtil.BooleanTableClickListener(table)); this.add(new JScrollPane(table), "height 200px, width 200lp, grow 1, wrap rel"); //// All + None buttons JButton button = new JButton(trans.get("simplotpanel.but.All")); button.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { for (FlightEvent.Type t : FlightEvent.Type.values()) configuration.setEvent(t, true); eventTableModel.fireTableDataChanged(); } }); this.add(button, "split 2, gapleft para, gapright para, growx, sizegroup buttons"); //// None button = new JButton(trans.get("simplotpanel.but.None")); button.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { for (FlightEvent.Type t : FlightEvent.Type.values()) configuration.setEvent(t, false); eventTableModel.fireTableDataChanged(); } }); this.add(button, "gapleft para, gapright para, growx, sizegroup buttons, wrap para"); //// New Y axis plot type button = new JButton(trans.get("simplotpanel.but.NewYaxisplottype")); button.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { if (configuration.getTypeCount() >= 15) { JOptionPane.showMessageDialog(SimulationPlotPanel.this, //// A maximum of 15 plots is allowed. //// Cannot add plot trans.get("simplotpanel.OptionPane.lbl1"), trans.get("simplotpanel.OptionPane.lbl2"), JOptionPane.ERROR_MESSAGE); return; } // Select new type smartly FlightDataType type = null; for (FlightDataType t : simulation.getSimulatedData().getBranch(0).getTypes()) { boolean used = false; if (configuration.getDomainAxisType().equals(t)) { used = true; } else { for (int i = 0; i < configuration.getTypeCount(); i++) { if (configuration.getType(i).equals(t)) { used = true; break; } } } if (!used) { type = t; break; } } if (type == null) { type = simulation.getSimulatedData().getBranch(0).getTypes()[0]; } // Add new type configuration.addPlotDataType(type); setToCustom(); updatePlots(); } }); this.add(button, "spanx, split"); this.add(new JPanel(), "growx"); /* //// Plot flight button = new JButton(trans.get("simplotpanel.but.Plotflight")); button.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { if (configuration.getTypeCount() == 0) { JOptionPane.showMessageDialog(SimulationPlotPanel.this, trans.get("error.noPlotSelected"), trans.get("error.noPlotSelected.title"), JOptionPane.ERROR_MESSAGE); return; } defaultConfiguration = configuration.clone(); SimulationPlotDialog.showPlot(SwingUtilities.getWindowAncestor(SimulationPlotPanel.this), simulation, configuration); } }); this.add(button, "right"); */ updatePlots(); } public JDialog doPlot(Window parent) { if (configuration.getTypeCount() == 0) { JOptionPane.showMessageDialog(SimulationPlotPanel.this, trans.get("error.noPlotSelected"), trans.get("error.noPlotSelected.title"), JOptionPane.ERROR_MESSAGE); return null; } defaultConfiguration = configuration.clone(); return SimulationPlotDialog.getPlot(parent, simulation, configuration); } private void setConfiguration(PlotConfiguration conf) { boolean modified = false; configuration = conf.clone(); if (!Utils.contains(types, configuration.getDomainAxisType())) { configuration.setDomainAxisType(types[0]); modified = true; } for (int i = 0; i < configuration.getTypeCount(); i++) { if (!Utils.contains(types, configuration.getType(i))) { configuration.removePlotDataType(i); i--; modified = true; } } if (modified) { configuration.setName(CUSTOM); } } private void setToCustom() { modifying++; configuration.setName(CUSTOM); configurationSelector.setSelectedItem(CUSTOM_CONFIGURATION); modifying--; } private void updatePlots() { domainTypeSelector.setSelectedItem(configuration.getDomainAxisType()); domainUnitSelector.setUnitGroup(configuration.getDomainAxisType().getUnitGroup()); domainUnitSelector.setSelectedUnit(configuration.getDomainAxisUnit()); typeSelectorPanel.removeAll(); for (int i = 0; i < configuration.getTypeCount(); i++) { FlightDataType type = configuration.getType(i); Unit unit = configuration.getUnit(i); int axis = configuration.getAxis(i); typeSelectorPanel.add(new PlotTypeSelector(i, type, unit, axis), "wrap"); } // In order to consistantly update the ui, we need to validate before repaint. typeSelectorPanel.validate(); typeSelectorPanel.repaint(); eventTableModel.fireTableDataChanged(); } /** * A JPanel which configures a single plot of a PlotConfiguration. */ private class PlotTypeSelector extends JPanel { private final String[] POSITIONS = { AUTO_NAME, LEFT_NAME, RIGHT_NAME }; private final int index; private JComboBox typeSelector; private UnitSelector unitSelector; private JComboBox axisSelector; public PlotTypeSelector(int plotIndex, FlightDataType type, Unit unit, int position) { super(new MigLayout("ins 0")); this.index = plotIndex; typeSelector = new JComboBox(types); typeSelector.setSelectedItem(type); typeSelector.addItemListener(new ItemListener() { @Override public void itemStateChanged(ItemEvent e) { if (modifying > 0) return; FlightDataType selectedType = (FlightDataType) typeSelector.getSelectedItem(); configuration.setPlotDataType(index, selectedType); unitSelector.setUnitGroup(selectedType.getUnitGroup()); unitSelector.setSelectedUnit(configuration.getUnit(index)); setToCustom(); } }); this.add(typeSelector, "gapright para"); //// Unit: this.add(new JLabel(trans.get("simplotpanel.lbl.Unit"))); unitSelector = new UnitSelector(type.getUnitGroup()); if (unit != null) unitSelector.setSelectedUnit(unit); unitSelector.addItemListener(new ItemListener() { @Override public void itemStateChanged(ItemEvent e) { if (modifying > 0) return; Unit selectedUnit = unitSelector.getSelectedUnit(); configuration.setPlotDataUnit(index, selectedUnit); } }); this.add(unitSelector, "width 40lp, gapright para"); //// Axis: this.add(new JLabel(trans.get("simplotpanel.lbl.Axis"))); axisSelector = new JComboBox(POSITIONS); if (position == LEFT) axisSelector.setSelectedIndex(1); else if (position == RIGHT) axisSelector.setSelectedIndex(2); else axisSelector.setSelectedIndex(0); axisSelector.addItemListener(new ItemListener() { @Override public void itemStateChanged(ItemEvent e) { if (modifying > 0) return; int axis = axisSelector.getSelectedIndex() - 1; configuration.setPlotDataAxis(index, axis); } }); this.add(axisSelector); JButton button = new JButton(Icons.DELETE); //// Remove this plot button.setToolTipText(trans.get("simplotpanel.but.ttip.Removethisplot")); button.setBorderPainted(false); button.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { configuration.removePlotDataType(index); setToCustom(); updatePlots(); } }); this.add(button, "gapright 0"); } } private class FlightEventTableModel extends AbstractTableModel { private final FlightEvent.Type[] eventTypes; public FlightEventTableModel() { EnumSet<FlightEvent.Type> set = EnumSet.noneOf(FlightEvent.Type.class); for (int i = 0; i < simulation.getSimulatedData().getBranchCount(); i++) { for (FlightEvent e : simulation.getSimulatedData().getBranch(i).getEvents()) { set.add(e.getType()); } } set.remove(FlightEvent.Type.ALTITUDE); int count = set.size(); eventTypes = new FlightEvent.Type[count]; int pos = 0; for (FlightEvent.Type t : FlightEvent.Type.values()) { if (set.contains(t)) { eventTypes[pos] = t; pos++; } } } @Override public int getColumnCount() { return 2; } @Override public int getRowCount() { return eventTypes.length; } @Override public Class<?> getColumnClass(int column) { switch (column) { case 0: return Boolean.class; case 1: return String.class; default: throw new IndexOutOfBoundsException("column=" + column); } } @Override public Object getValueAt(int row, int column) { switch (column) { case 0: return new Boolean(configuration.isEventActive(eventTypes[row])); case 1: return eventTypes[row].toString(); default: throw new IndexOutOfBoundsException("column=" + column); } } @Override public boolean isCellEditable(int row, int column) { return column == 0; } @Override public void setValueAt(Object value, int row, int column) { if (column != 0 || !(value instanceof Boolean)) { throw new IllegalArgumentException("column=" + column + ", value=" + value); } configuration.setEvent(eventTypes[row], (Boolean) value); this.fireTableCellUpdated(row, column); } } }