1 package net.sf.openrocket.gui.plot;
3 import java.awt.event.ActionEvent;
4 import java.awt.event.ActionListener;
5 import java.awt.event.ItemEvent;
6 import java.awt.event.ItemListener;
7 import java.util.Arrays;
8 import java.util.EnumSet;
10 import javax.swing.BorderFactory;
11 import javax.swing.JButton;
12 import javax.swing.JComboBox;
13 import javax.swing.JLabel;
14 import javax.swing.JOptionPane;
15 import javax.swing.JPanel;
16 import javax.swing.JScrollPane;
17 import javax.swing.JTable;
18 import javax.swing.SwingUtilities;
19 import javax.swing.table.AbstractTableModel;
20 import javax.swing.table.TableColumn;
21 import javax.swing.table.TableColumnModel;
23 import net.miginfocom.swing.MigLayout;
24 import net.sf.openrocket.document.Simulation;
25 import net.sf.openrocket.gui.components.DescriptionArea;
26 import net.sf.openrocket.gui.components.UnitSelector;
27 import net.sf.openrocket.simulation.FlightDataBranch;
28 import net.sf.openrocket.simulation.FlightDataType;
29 import net.sf.openrocket.simulation.FlightEvent;
30 import net.sf.openrocket.unit.Unit;
31 import net.sf.openrocket.util.GUIUtil;
32 import net.sf.openrocket.util.Icons;
34 public class SimulationPlotPanel extends JPanel {
36 // TODO: LOW: Should these be somewhere else?
37 public static final int AUTO = -1;
38 public static final int LEFT = 0;
39 public static final int RIGHT = 1;
41 public static final String AUTO_NAME = "Auto";
42 public static final String LEFT_NAME = "Left";
43 public static final String RIGHT_NAME = "Right";
45 private static final String CUSTOM = "Custom";
47 /** The "Custom" configuration - not to be used for anything other than the title. */
48 private static final PlotConfiguration CUSTOM_CONFIGURATION;
50 CUSTOM_CONFIGURATION = new PlotConfiguration(CUSTOM);
53 /** The array of presets for the combo box. */
54 private static final PlotConfiguration[] PRESET_ARRAY;
56 PRESET_ARRAY = Arrays.copyOf(PlotConfiguration.DEFAULT_CONFIGURATIONS,
57 PlotConfiguration.DEFAULT_CONFIGURATIONS.length + 1);
58 PRESET_ARRAY[PRESET_ARRAY.length - 1] = CUSTOM_CONFIGURATION;
63 /** The current default configuration, set each time a plot is made. */
64 private static PlotConfiguration defaultConfiguration =
65 PlotConfiguration.DEFAULT_CONFIGURATIONS[0].resetUnits();
68 private final Simulation simulation;
69 private final FlightDataType[] types;
70 private PlotConfiguration configuration;
73 private JComboBox configurationSelector;
75 private JComboBox domainTypeSelector;
76 private UnitSelector domainUnitSelector;
78 private JPanel typeSelectorPanel;
79 private FlightEventTableModel eventTableModel;
82 private int modifying = 0;
85 public SimulationPlotPanel(final Simulation simulation) {
86 super(new MigLayout("fill"));
88 this.simulation = simulation;
89 if (simulation.getSimulatedData() == null ||
90 simulation.getSimulatedData().getBranchCount() == 0) {
91 throw new IllegalArgumentException("Simulation contains no data.");
93 FlightDataBranch branch = simulation.getSimulatedData().getBranch(0);
94 types = branch.getTypes();
96 // TODO: LOW: Revert to custom if data type is not available.
97 configuration = defaultConfiguration.clone();
100 //// Configuration selector
102 // Setup the combo box
103 configurationSelector = new JComboBox(PRESET_ARRAY);
104 for (PlotConfiguration config : PRESET_ARRAY) {
105 if (config.getName().equals(configuration.getName())) {
106 configurationSelector.setSelectedItem(config);
109 configurationSelector.addItemListener(new ItemListener() {
111 public void itemStateChanged(ItemEvent e) {
114 PlotConfiguration conf = (PlotConfiguration) configurationSelector.getSelectedItem();
115 if (conf == CUSTOM_CONFIGURATION)
118 configuration = conf.clone().resetUnits();
123 this.add(new JLabel("Preset plot configurations: "), "spanx, split");
124 this.add(configurationSelector, "growx, wrap 20lp");
131 this.add(new JLabel("X axis type:"), "spanx, split");
132 domainTypeSelector = new JComboBox(types);
133 domainTypeSelector.setSelectedItem(configuration.getDomainAxisType());
134 domainTypeSelector.addItemListener(new ItemListener() {
136 public void itemStateChanged(ItemEvent e) {
139 FlightDataType type = (FlightDataType) domainTypeSelector.getSelectedItem();
140 configuration.setDomainAxisType(type);
141 domainUnitSelector.setUnitGroup(type.getUnitGroup());
142 domainUnitSelector.setSelectedUnit(configuration.getDomainAxisUnit());
146 this.add(domainTypeSelector, "gapright para");
149 this.add(new JLabel("Unit:"));
150 domainUnitSelector = new UnitSelector(configuration.getDomainAxisType().getUnitGroup());
151 domainUnitSelector.setSelectedUnit(configuration.getDomainAxisUnit());
152 domainUnitSelector.addItemListener(new ItemListener() {
154 public void itemStateChanged(ItemEvent e) {
157 configuration.setDomainAxisUnit(domainUnitSelector.getSelectedUnit());
160 this.add(domainUnitSelector, "width 40lp, gapright para");
163 DescriptionArea desc = new DescriptionArea("The data will be plotted in time order " +
164 "even if the X axis type is not time.", 2, -2f);
165 desc.setViewportBorder(BorderFactory.createEmptyBorder());
166 this.add(desc, "width 1px, growx 1, wrap unrel");
170 //// Y axis selector panel
172 this.add(new JLabel("Y axis types:"));
174 this.add(new JLabel("Flight events:"), "wrap rel");
176 typeSelectorPanel = new JPanel(new MigLayout("gapy rel"));
177 JScrollPane scroll = new JScrollPane(typeSelectorPanel);
178 this.add(scroll, "spany 2, height 10px, grow 100, gapright para");
182 eventTableModel = new FlightEventTableModel();
183 JTable table = new JTable(eventTableModel);
184 table.setTableHeader(null);
185 table.setShowVerticalLines(false);
186 table.setRowSelectionAllowed(false);
187 table.setColumnSelectionAllowed(false);
189 TableColumnModel columnModel = table.getColumnModel();
190 TableColumn col0 = columnModel.getColumn(0);
191 int w = table.getRowHeight() + 2;
193 col0.setPreferredWidth(w);
195 table.addMouseListener(new GUIUtil.BooleanTableClickListener(table));
196 this.add(new JScrollPane(table), "height 10px, width 200lp, grow 1, wrap rel");
199 //// All + None buttons
200 JButton button = new JButton("All");
201 button.addActionListener(new ActionListener() {
203 public void actionPerformed(ActionEvent e) {
204 for (FlightEvent.Type t : FlightEvent.Type.values())
205 configuration.setEvent(t, true);
206 eventTableModel.fireTableDataChanged();
209 this.add(button, "split 2, gapleft para, gapright para, growx, sizegroup buttons");
211 button = new JButton("None");
212 button.addActionListener(new ActionListener() {
214 public void actionPerformed(ActionEvent e) {
215 for (FlightEvent.Type t : FlightEvent.Type.values())
216 configuration.setEvent(t, false);
217 eventTableModel.fireTableDataChanged();
220 this.add(button, "gapleft para, gapright para, growx, sizegroup buttons, wrap para");
225 button = new JButton("New Y axis plot type");
226 button.addActionListener(new ActionListener() {
228 public void actionPerformed(ActionEvent e) {
229 if (configuration.getTypeCount() >= 15) {
230 JOptionPane.showMessageDialog(SimulationPlotPanel.this,
231 "A maximum of 15 plots is allowed.", "Cannot add plot",
232 JOptionPane.ERROR_MESSAGE);
236 // Select new type smartly
237 FlightDataType type = null;
238 for (FlightDataType t :
239 simulation.getSimulatedData().getBranch(0).getTypes()) {
241 boolean used = false;
242 if (configuration.getDomainAxisType().equals(t)) {
245 for (int i = 0; i < configuration.getTypeCount(); i++) {
246 if (configuration.getType(i).equals(t)) {
259 type = simulation.getSimulatedData().getBranch(0).getTypes()[0];
263 configuration.addPlotDataType(type);
268 this.add(button, "spanx, split");
271 this.add(new JPanel(), "growx");
273 button = new JButton("Plot flight");
274 button.addActionListener(new ActionListener() {
276 public void actionPerformed(ActionEvent e) {
277 defaultConfiguration = configuration.clone();
278 PlotDialog.showPlot(SwingUtilities.getWindowAncestor(SimulationPlotPanel.this),
279 simulation, configuration);
282 this.add(button, "right");
289 private void setToCustom() {
291 configuration.setName(CUSTOM);
292 configurationSelector.setSelectedItem(CUSTOM_CONFIGURATION);
297 private void updatePlots() {
298 domainTypeSelector.setSelectedItem(configuration.getDomainAxisType());
299 domainUnitSelector.setUnitGroup(configuration.getDomainAxisType().getUnitGroup());
300 domainUnitSelector.setSelectedUnit(configuration.getDomainAxisUnit());
302 typeSelectorPanel.removeAll();
303 for (int i = 0; i < configuration.getTypeCount(); i++) {
304 FlightDataType type = configuration.getType(i);
305 Unit unit = configuration.getUnit(i);
306 int axis = configuration.getAxis(i);
308 typeSelectorPanel.add(new PlotTypeSelector(i, type, unit, axis), "wrap");
311 typeSelectorPanel.repaint();
313 eventTableModel.fireTableDataChanged();
320 * A JPanel which configures a single plot of a PlotConfiguration.
322 private class PlotTypeSelector extends JPanel {
323 private final String[] POSITIONS = { AUTO_NAME, LEFT_NAME, RIGHT_NAME };
325 private final int index;
326 private JComboBox typeSelector;
327 private UnitSelector unitSelector;
328 private JComboBox axisSelector;
331 public PlotTypeSelector(int index, FlightDataType type) {
332 this(index, type, null, -1);
335 public PlotTypeSelector(int plotIndex, FlightDataType type, Unit unit, int position) {
336 super(new MigLayout("ins 0"));
338 this.index = plotIndex;
340 typeSelector = new JComboBox(types);
341 typeSelector.setSelectedItem(type);
342 typeSelector.addItemListener(new ItemListener() {
344 public void itemStateChanged(ItemEvent e) {
347 FlightDataType type = (FlightDataType) typeSelector.getSelectedItem();
348 configuration.setPlotDataType(index, type);
349 unitSelector.setUnitGroup(type.getUnitGroup());
350 unitSelector.setSelectedUnit(configuration.getUnit(index));
354 this.add(typeSelector, "gapright para");
356 this.add(new JLabel("Unit:"));
357 unitSelector = new UnitSelector(type.getUnitGroup());
359 unitSelector.setSelectedUnit(unit);
360 unitSelector.addItemListener(new ItemListener() {
362 public void itemStateChanged(ItemEvent e) {
365 Unit unit = (Unit) unitSelector.getSelectedUnit();
366 configuration.setPlotDataUnit(index, unit);
369 this.add(unitSelector, "width 40lp, gapright para");
371 this.add(new JLabel("Axis:"));
372 axisSelector = new JComboBox(POSITIONS);
373 if (position == LEFT)
374 axisSelector.setSelectedIndex(1);
375 else if (position == RIGHT)
376 axisSelector.setSelectedIndex(2);
378 axisSelector.setSelectedIndex(0);
379 axisSelector.addItemListener(new ItemListener() {
381 public void itemStateChanged(ItemEvent e) {
384 int axis = axisSelector.getSelectedIndex() - 1;
385 configuration.setPlotDataAxis(index, axis);
388 this.add(axisSelector);
391 JButton button = new JButton(Icons.DELETE);
392 button.setToolTipText("Remove this plot");
393 button.setBorderPainted(false);
394 button.addActionListener(new ActionListener() {
396 public void actionPerformed(ActionEvent e) {
397 configuration.removePlotDataType(index);
402 this.add(button, "gapright 0");
408 private class FlightEventTableModel extends AbstractTableModel {
409 private final FlightEvent.Type[] eventTypes;
411 public FlightEventTableModel() {
412 EnumSet<FlightEvent.Type> set = EnumSet.noneOf(FlightEvent.Type.class);
413 for (int i = 0; i < simulation.getSimulatedData().getBranchCount(); i++) {
414 for (FlightEvent e : simulation.getSimulatedData().getBranch(i).getEvents()) {
415 set.add(e.getType());
418 set.remove(FlightEvent.Type.ALTITUDE);
419 int count = set.size();
421 eventTypes = new FlightEvent.Type[count];
423 for (FlightEvent.Type t : FlightEvent.Type.values()) {
424 if (set.contains(t)) {
432 public int getColumnCount() {
437 public int getRowCount() {
438 return eventTypes.length;
442 public Class<?> getColumnClass(int column) {
445 return Boolean.class;
451 throw new IndexOutOfBoundsException("column=" + column);
456 public Object getValueAt(int row, int column) {
459 return new Boolean(configuration.isEventActive(eventTypes[row]));
462 return eventTypes[row].toString();
465 throw new IndexOutOfBoundsException("column=" + column);
470 public boolean isCellEditable(int row, int column) {
475 public void setValueAt(Object value, int row, int column) {
476 if (column != 0 || !(value instanceof Boolean)) {
477 throw new IllegalArgumentException("column=" + column + ", value=" + value);
480 configuration.setEvent(eventTypes[row], (Boolean) value);
481 this.fireTableCellUpdated(row, column);