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.FlightEvent;
29 import net.sf.openrocket.simulation.FlightDataBranch.Type;
30 import net.sf.openrocket.unit.Unit;
31 import net.sf.openrocket.util.GUIUtil;
32 import net.sf.openrocket.util.Icons;
33 import net.sf.openrocket.util.Pair;
35 public class SimulationPlotPanel extends JPanel {
37 // TODO: LOW: Should these be somewhere else?
38 public static final int AUTO = -1;
39 public static final int LEFT = 0;
40 public static final int RIGHT = 1;
42 public static final String AUTO_NAME = "Auto";
43 public static final String LEFT_NAME = "Left";
44 public static final String RIGHT_NAME = "Right";
46 private static final String CUSTOM = "Custom";
48 /** The "Custom" configuration - not to be used for anything other than the title. */
49 private static final PlotConfiguration CUSTOM_CONFIGURATION;
51 CUSTOM_CONFIGURATION = new PlotConfiguration(CUSTOM);
54 /** The array of presets for the combo box. */
55 private static final PlotConfiguration[] PRESET_ARRAY;
57 PRESET_ARRAY = Arrays.copyOf(PlotConfiguration.DEFAULT_CONFIGURATIONS,
58 PlotConfiguration.DEFAULT_CONFIGURATIONS.length + 1);
59 PRESET_ARRAY[PRESET_ARRAY.length-1] = CUSTOM_CONFIGURATION;
64 /** The current default configuration, set each time a plot is made. */
65 private static PlotConfiguration defaultConfiguration =
66 PlotConfiguration.DEFAULT_CONFIGURATIONS[0].resetUnits();
69 private final Simulation simulation;
70 private final FlightDataBranch.Type[] types;
71 private PlotConfiguration configuration;
74 private JComboBox configurationSelector;
76 private JComboBox domainTypeSelector;
77 private UnitSelector domainUnitSelector;
79 private JPanel typeSelectorPanel;
80 private FlightEventTableModel eventTableModel;
83 private int modifying = 0;
86 public SimulationPlotPanel(final Simulation simulation) {
87 super(new MigLayout("fill"));
89 this.simulation = simulation;
90 if (simulation.getSimulatedData() == null ||
91 simulation.getSimulatedData().getBranchCount()==0) {
92 throw new IllegalArgumentException("Simulation contains no data.");
94 FlightDataBranch branch = simulation.getSimulatedData().getBranch(0);
95 types = branch.getTypes();
97 // TODO: LOW: Revert to custom if data type is not available.
98 configuration = defaultConfiguration.clone();
101 //// Configuration selector
103 // Setup the combo box
104 configurationSelector = new JComboBox(PRESET_ARRAY);
105 for (PlotConfiguration config: PRESET_ARRAY) {
106 if (config.getName().equals(configuration.getName())) {
107 configurationSelector.setSelectedItem(config);
110 configurationSelector.addItemListener(new ItemListener() {
112 public void itemStateChanged(ItemEvent e) {
115 PlotConfiguration conf = (PlotConfiguration)configurationSelector.getSelectedItem();
116 if (conf == CUSTOM_CONFIGURATION)
119 configuration = conf.clone().resetUnits();
124 this.add(new JLabel("Preset plot configurations: "), "spanx, split");
125 this.add(configurationSelector,"growx, wrap 20lp");
132 this.add(new JLabel("X axis type:"), "spanx, split");
133 domainTypeSelector = new JComboBox(types);
134 domainTypeSelector.setSelectedItem(configuration.getDomainAxisType());
135 domainTypeSelector.addItemListener(new ItemListener() {
137 public void itemStateChanged(ItemEvent e) {
140 FlightDataBranch.Type type = (Type) domainTypeSelector.getSelectedItem();
141 configuration.setDomainAxisType(type);
142 domainUnitSelector.setUnitGroup(type.getUnitGroup());
143 domainUnitSelector.setSelectedUnit(configuration.getDomainAxisUnit());
147 this.add(domainTypeSelector, "gapright para");
150 this.add(new JLabel("Unit:"));
151 domainUnitSelector = new UnitSelector(configuration.getDomainAxisType().getUnitGroup());
152 domainUnitSelector.setSelectedUnit(configuration.getDomainAxisUnit());
153 domainUnitSelector.addItemListener(new ItemListener() {
155 public void itemStateChanged(ItemEvent e) {
158 configuration.setDomainAxisUnit(domainUnitSelector.getSelectedUnit());
161 this.add(domainUnitSelector, "width 40lp, gapright para");
164 DescriptionArea desc = new DescriptionArea("The data will be plotted in time order " +
165 "even if the X axis type is not time.", 2, -2f);
166 desc.setViewportBorder(BorderFactory.createEmptyBorder());
167 this.add(desc, "width 1px, growx 1, wrap unrel");
171 //// Y axis selector panel
173 this.add(new JLabel("Y axis types:"));
175 this.add(new JLabel("Flight events:"), "wrap rel");
177 typeSelectorPanel = new JPanel(new MigLayout("gapy rel"));
178 JScrollPane scroll = new JScrollPane(typeSelectorPanel);
179 this.add(scroll, "spany 2, height 10px, grow 100, gapright para");
183 eventTableModel = new FlightEventTableModel();
184 JTable table = new JTable(eventTableModel);
185 table.setTableHeader(null);
186 table.setShowVerticalLines(false);
187 table.setRowSelectionAllowed(false);
188 table.setColumnSelectionAllowed(false);
190 TableColumnModel columnModel = table.getColumnModel();
191 TableColumn col0 = columnModel.getColumn(0);
192 int w = table.getRowHeight() + 2;
194 col0.setPreferredWidth(w);
196 table.addMouseListener(new GUIUtil.BooleanTableClickListener(table));
197 this.add(new JScrollPane(table), "height 10px, width 200lp, grow 1, wrap rel");
200 //// All + None buttons
201 JButton button = new JButton("All");
202 button.addActionListener(new ActionListener() {
204 public void actionPerformed(ActionEvent e) {
205 for (FlightEvent.Type t: FlightEvent.Type.values())
206 configuration.setEvent(t, true);
207 eventTableModel.fireTableDataChanged();
210 this.add(button, "split 2, gapleft para, gapright para, growx, sizegroup buttons");
212 button = new JButton("None");
213 button.addActionListener(new ActionListener() {
215 public void actionPerformed(ActionEvent e) {
216 for (FlightEvent.Type t: FlightEvent.Type.values())
217 configuration.setEvent(t, false);
218 eventTableModel.fireTableDataChanged();
221 this.add(button, "gapleft para, gapright para, growx, sizegroup buttons, wrap para");
226 button = new JButton("New Y axis plot type");
227 button.addActionListener(new ActionListener() {
229 public void actionPerformed(ActionEvent e) {
230 if (configuration.getTypeCount() >= 15) {
231 JOptionPane.showMessageDialog(SimulationPlotPanel.this,
232 "A maximum of 15 plots is allowed.", "Cannot add plot",
233 JOptionPane.ERROR_MESSAGE);
237 // Select new type smartly
238 FlightDataBranch.Type type = null;
239 for (FlightDataBranch.Type t:
240 simulation.getSimulatedData().getBranch(0).getTypes()) {
242 boolean used = false;
243 if (configuration.getDomainAxisType().equals(t)) {
246 for (int i=0; i < configuration.getTypeCount(); i++) {
247 if (configuration.getType(i).equals(t)) {
260 type = simulation.getSimulatedData().getBranch(0).getTypes()[0];
264 configuration.addPlotDataType(type);
269 this.add(button, "spanx, split");
272 this.add(new JPanel(), "growx");
274 button = new JButton("Plot flight");
275 button.addActionListener(new ActionListener() {
277 public void actionPerformed(ActionEvent e) {
278 defaultConfiguration = configuration.clone();
279 PlotDialog.showPlot(SwingUtilities.getWindowAncestor(SimulationPlotPanel.this),
280 simulation, configuration);
283 this.add(button, "right");
290 private void setToCustom() {
292 configuration.setName(CUSTOM);
293 configurationSelector.setSelectedItem(CUSTOM_CONFIGURATION);
298 private void updatePlots() {
299 domainTypeSelector.setSelectedItem(configuration.getDomainAxisType());
300 domainUnitSelector.setUnitGroup(configuration.getDomainAxisType().getUnitGroup());
301 domainUnitSelector.setSelectedUnit(configuration.getDomainAxisUnit());
303 typeSelectorPanel.removeAll();
304 for (int i=0; i < configuration.getTypeCount(); i++) {
305 FlightDataBranch.Type type = configuration.getType(i);
306 Unit unit = configuration.getUnit(i);
307 int axis = configuration.getAxis(i);
309 typeSelectorPanel.add(new PlotTypeSelector(i, type, unit, axis), "wrap");
312 typeSelectorPanel.repaint();
314 eventTableModel.fireTableDataChanged();
321 * A JPanel which configures a single plot of a PlotConfiguration.
323 private class PlotTypeSelector extends JPanel {
324 private final String[] POSITIONS = { AUTO_NAME, LEFT_NAME, RIGHT_NAME };
326 private final int index;
327 private JComboBox typeSelector;
328 private UnitSelector unitSelector;
329 private JComboBox axisSelector;
332 public PlotTypeSelector(int index, FlightDataBranch.Type type) {
333 this (index, type, null, -1);
336 public PlotTypeSelector(int plotIndex, FlightDataBranch.Type type, Unit unit, int position) {
337 super(new MigLayout("ins 0"));
339 this.index = plotIndex;
341 typeSelector = new JComboBox(types);
342 typeSelector.setSelectedItem(type);
343 typeSelector.addItemListener(new ItemListener() {
345 public void itemStateChanged(ItemEvent e) {
348 FlightDataBranch.Type type = (Type) typeSelector.getSelectedItem();
349 configuration.setPlotDataType(index, type);
350 unitSelector.setUnitGroup(type.getUnitGroup());
351 unitSelector.setSelectedUnit(configuration.getUnit(index));
355 this.add(typeSelector, "gapright para");
357 this.add(new JLabel("Unit:"));
358 unitSelector = new UnitSelector(type.getUnitGroup());
360 unitSelector.setSelectedUnit(unit);
361 unitSelector.addItemListener(new ItemListener() {
363 public void itemStateChanged(ItemEvent e) {
366 Unit unit = (Unit) unitSelector.getSelectedUnit();
367 configuration.setPlotDataUnit(index, unit);
370 this.add(unitSelector, "width 40lp, gapright para");
372 this.add(new JLabel("Axis:"));
373 axisSelector = new JComboBox(POSITIONS);
374 if (position == LEFT)
375 axisSelector.setSelectedIndex(1);
376 else if (position == RIGHT)
377 axisSelector.setSelectedIndex(2);
379 axisSelector.setSelectedIndex(0);
380 axisSelector.addItemListener(new ItemListener() {
382 public void itemStateChanged(ItemEvent e) {
385 int axis = axisSelector.getSelectedIndex() - 1;
386 configuration.setPlotDataAxis(index, axis);
389 this.add(axisSelector);
392 JButton button = new JButton(Icons.DELETE);
393 button.setToolTipText("Remove this plot");
394 button.setBorderPainted(false);
395 button.addActionListener(new ActionListener() {
397 public void actionPerformed(ActionEvent e) {
398 configuration.removePlotDataType(index);
403 this.add(button, "gapright 0");
409 private class FlightEventTableModel extends AbstractTableModel {
410 private final FlightEvent.Type[] eventTypes;
412 public FlightEventTableModel() {
413 EnumSet<FlightEvent.Type> set = EnumSet.noneOf(FlightEvent.Type.class);
414 for (int i=0; i < simulation.getSimulatedData().getBranchCount(); i++) {
415 for (Pair<Double,FlightEvent> e:
416 simulation.getSimulatedData().getBranch(i).getEvents()) {
417 set.add(e.getV().getType());
420 set.remove(FlightEvent.Type.ALTITUDE);
421 int count = set.size();
423 eventTypes = new FlightEvent.Type[count];
425 for (FlightEvent.Type t: FlightEvent.Type.values()) {
426 if (set.contains(t)) {
434 public int getColumnCount() {
439 public int getRowCount() {
440 return eventTypes.length;
444 public Class<?> getColumnClass(int column) {
447 return Boolean.class;
453 throw new IndexOutOfBoundsException("column="+column);
458 public Object getValueAt(int row, int column) {
461 return new Boolean(configuration.isEventActive(eventTypes[row]));
464 return eventTypes[row].toString();
467 throw new IndexOutOfBoundsException("column="+column);
472 public boolean isCellEditable(int row, int column) {
477 public void setValueAt(Object value, int row, int column) {
478 if (column != 0 || !(value instanceof Boolean)) {
479 throw new IllegalArgumentException("column="+column+", value="+value);
482 configuration.setEvent(eventTypes[row], (Boolean)value);
483 this.fireTableCellUpdated(row, column);