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.JButton;
11 import javax.swing.JComboBox;
12 import javax.swing.JLabel;
13 import javax.swing.JOptionPane;
14 import javax.swing.JPanel;
15 import javax.swing.JScrollPane;
16 import javax.swing.JTable;
17 import javax.swing.SwingUtilities;
18 import javax.swing.table.AbstractTableModel;
19 import javax.swing.table.TableColumn;
20 import javax.swing.table.TableColumnModel;
22 import net.miginfocom.swing.MigLayout;
23 import net.sf.openrocket.document.Simulation;
24 import net.sf.openrocket.gui.components.StyledLabel;
25 import net.sf.openrocket.gui.components.UnitSelector;
26 import net.sf.openrocket.simulation.FlightDataBranch;
27 import net.sf.openrocket.simulation.FlightEvent;
28 import net.sf.openrocket.simulation.FlightDataBranch.Type;
29 import net.sf.openrocket.unit.Unit;
30 import net.sf.openrocket.util.GUIUtil;
31 import net.sf.openrocket.util.Icons;
32 import net.sf.openrocket.util.Pair;
34 public class PlotPanel 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 FlightDataBranch.Type[] 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 PlotPanel(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 30lp");
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 FlightDataBranch.Type type = (Type) 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 StyledLabel desc = new StyledLabel("<html><p>The data will be plotted in time order " +
164 "even if the X axis type is not time.", -2);
165 this.add(desc, "width :0px:, growx, wrap para");
169 //// Y axis selector panel
171 this.add(new JLabel("Y axis types:"));
173 this.add(new JLabel("Flight events:"), "wrap rel");
175 typeSelectorPanel = new JPanel(new MigLayout("gapy rel"));
176 JScrollPane scroll = new JScrollPane(typeSelectorPanel);
177 this.add(scroll, "spany 2, height 10px, grow 100, gapright para");
181 eventTableModel = new FlightEventTableModel();
182 JTable table = new JTable(eventTableModel);
183 table.setTableHeader(null);
184 table.setShowVerticalLines(false);
185 table.setRowSelectionAllowed(false);
186 table.setColumnSelectionAllowed(false);
188 TableColumnModel columnModel = table.getColumnModel();
189 TableColumn col0 = columnModel.getColumn(0);
190 int w = table.getRowHeight() + 2;
192 col0.setPreferredWidth(w);
194 table.addMouseListener(new GUIUtil.BooleanTableClickListener(table));
195 this.add(new JScrollPane(table), "height 1px, width 200lp, grow 1, wrap rel");
198 //// All + None buttons
199 JButton button = new JButton("All");
200 button.addActionListener(new ActionListener() {
202 public void actionPerformed(ActionEvent e) {
203 for (FlightEvent.Type t: FlightEvent.Type.values())
204 configuration.setEvent(t, true);
205 eventTableModel.fireTableDataChanged();
208 this.add(button, "split 2, gapleft para, gapright para, growx, sizegroup buttons");
210 button = new JButton("None");
211 button.addActionListener(new ActionListener() {
213 public void actionPerformed(ActionEvent e) {
214 for (FlightEvent.Type t: FlightEvent.Type.values())
215 configuration.setEvent(t, false);
216 eventTableModel.fireTableDataChanged();
219 this.add(button, "gapleft para, gapright para, growx, sizegroup buttons, wrap para");
224 button = new JButton("New Y axis plot type");
225 button.addActionListener(new ActionListener() {
227 public void actionPerformed(ActionEvent e) {
228 if (configuration.getTypeCount() >= 15) {
229 JOptionPane.showMessageDialog(PlotPanel.this,
230 "A maximum of 15 plots is allowed.", "Cannot add plot",
231 JOptionPane.ERROR_MESSAGE);
235 // Select new type smartly
236 FlightDataBranch.Type type = null;
237 for (FlightDataBranch.Type t:
238 simulation.getSimulatedData().getBranch(0).getTypes()) {
240 boolean used = false;
241 if (configuration.getDomainAxisType().equals(t)) {
244 for (int i=0; i < configuration.getTypeCount(); i++) {
245 if (configuration.getType(i).equals(t)) {
258 type = simulation.getSimulatedData().getBranch(0).getTypes()[0];
262 configuration.addPlotDataType(type);
267 this.add(button, "spanx, split");
270 this.add(new JPanel(), "growx");
272 button = new JButton("Plot flight");
273 button.addActionListener(new ActionListener() {
275 public void actionPerformed(ActionEvent e) {
276 defaultConfiguration = configuration.clone();
277 PlotDialog.showPlot(SwingUtilities.getWindowAncestor(PlotPanel.this),
278 simulation, configuration);
281 this.add(button, "right");
288 private void setToCustom() {
290 configuration.setName(CUSTOM);
291 configurationSelector.setSelectedItem(CUSTOM_CONFIGURATION);
296 private void updatePlots() {
297 domainTypeSelector.setSelectedItem(configuration.getDomainAxisType());
298 domainUnitSelector.setUnitGroup(configuration.getDomainAxisType().getUnitGroup());
299 domainUnitSelector.setSelectedUnit(configuration.getDomainAxisUnit());
301 typeSelectorPanel.removeAll();
302 for (int i=0; i < configuration.getTypeCount(); i++) {
303 FlightDataBranch.Type type = configuration.getType(i);
304 Unit unit = configuration.getUnit(i);
305 int axis = configuration.getAxis(i);
307 typeSelectorPanel.add(new PlotTypeSelector(i, type, unit, axis), "wrap");
310 typeSelectorPanel.repaint();
312 eventTableModel.fireTableDataChanged();
319 * A JPanel which configures a single plot of a PlotConfiguration.
321 private class PlotTypeSelector extends JPanel {
322 private final String[] POSITIONS = { AUTO_NAME, LEFT_NAME, RIGHT_NAME };
324 private final int index;
325 private JComboBox typeSelector;
326 private UnitSelector unitSelector;
327 private JComboBox axisSelector;
330 public PlotTypeSelector(int index, FlightDataBranch.Type type) {
331 this (index, type, null, -1);
334 public PlotTypeSelector(int plotIndex, FlightDataBranch.Type type, Unit unit, int position) {
335 super(new MigLayout("ins 0"));
337 this.index = plotIndex;
339 typeSelector = new JComboBox(types);
340 typeSelector.setSelectedItem(type);
341 typeSelector.addItemListener(new ItemListener() {
343 public void itemStateChanged(ItemEvent e) {
346 FlightDataBranch.Type type = (Type) typeSelector.getSelectedItem();
347 configuration.setPlotDataType(index, type);
348 unitSelector.setUnitGroup(type.getUnitGroup());
349 unitSelector.setSelectedUnit(configuration.getUnit(index));
353 this.add(typeSelector, "gapright para");
355 this.add(new JLabel("Unit:"));
356 unitSelector = new UnitSelector(type.getUnitGroup());
358 unitSelector.setSelectedUnit(unit);
359 unitSelector.addItemListener(new ItemListener() {
361 public void itemStateChanged(ItemEvent e) {
364 Unit unit = (Unit) unitSelector.getSelectedUnit();
365 configuration.setPlotDataUnit(index, unit);
368 this.add(unitSelector, "width 40lp, gapright para");
370 this.add(new JLabel("Axis:"));
371 axisSelector = new JComboBox(POSITIONS);
372 if (position == LEFT)
373 axisSelector.setSelectedIndex(1);
374 else if (position == RIGHT)
375 axisSelector.setSelectedIndex(2);
377 axisSelector.setSelectedIndex(0);
378 axisSelector.addItemListener(new ItemListener() {
380 public void itemStateChanged(ItemEvent e) {
383 int axis = axisSelector.getSelectedIndex() - 1;
384 configuration.setPlotDataAxis(index, axis);
387 this.add(axisSelector);
390 JButton button = new JButton(Icons.DELETE);
391 button.setToolTipText("Remove this plot");
392 button.setBorderPainted(false);
393 button.addActionListener(new ActionListener() {
395 public void actionPerformed(ActionEvent e) {
396 configuration.removePlotDataType(index);
401 this.add(button, "gapright 0");
407 private class FlightEventTableModel extends AbstractTableModel {
408 private final FlightEvent.Type[] eventTypes;
410 public FlightEventTableModel() {
411 EnumSet<FlightEvent.Type> set = EnumSet.noneOf(FlightEvent.Type.class);
412 for (int i=0; i < simulation.getSimulatedData().getBranchCount(); i++) {
413 for (Pair<Double,FlightEvent> e:
414 simulation.getSimulatedData().getBranch(i).getEvents()) {
415 set.add(e.getV().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);