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.l10n.Translator;
28 import net.sf.openrocket.simulation.FlightDataBranch;
29 import net.sf.openrocket.simulation.FlightDataType;
30 import net.sf.openrocket.simulation.FlightEvent;
31 import net.sf.openrocket.startup.Application;
32 import net.sf.openrocket.unit.Unit;
33 import net.sf.openrocket.util.GUIUtil;
34 import net.sf.openrocket.util.Icons;
36 public class SimulationPlotPanel extends JPanel {
37 private static final Translator trans = Application.getTranslator();
39 // TODO: LOW: Should these be somewhere else?
40 public static final int AUTO = -1;
41 public static final int LEFT = 0;
42 public static final int RIGHT = 1;
44 public static final String AUTO_NAME = "Auto";
45 public static final String LEFT_NAME = "Left";
46 public static final String RIGHT_NAME = "Right";
48 private static final String CUSTOM = "Custom";
50 /** The "Custom" configuration - not to be used for anything other than the title. */
51 private static final PlotConfiguration CUSTOM_CONFIGURATION;
53 CUSTOM_CONFIGURATION = new PlotConfiguration(CUSTOM);
56 /** The array of presets for the combo box. */
57 private static final PlotConfiguration[] PRESET_ARRAY;
59 PRESET_ARRAY = Arrays.copyOf(PlotConfiguration.DEFAULT_CONFIGURATIONS,
60 PlotConfiguration.DEFAULT_CONFIGURATIONS.length + 1);
61 PRESET_ARRAY[PRESET_ARRAY.length - 1] = CUSTOM_CONFIGURATION;
66 /** The current default configuration, set each time a plot is made. */
67 private static PlotConfiguration defaultConfiguration =
68 PlotConfiguration.DEFAULT_CONFIGURATIONS[0].resetUnits();
71 private final Simulation simulation;
72 private final FlightDataType[] types;
73 private PlotConfiguration configuration;
76 private JComboBox configurationSelector;
78 private JComboBox domainTypeSelector;
79 private UnitSelector domainUnitSelector;
81 private JPanel typeSelectorPanel;
82 private FlightEventTableModel eventTableModel;
85 private int modifying = 0;
88 public SimulationPlotPanel(final Simulation simulation) {
89 super(new MigLayout("fill"));
91 this.simulation = simulation;
92 if (simulation.getSimulatedData() == null ||
93 simulation.getSimulatedData().getBranchCount() == 0) {
94 throw new IllegalArgumentException("Simulation contains no data.");
96 FlightDataBranch branch = simulation.getSimulatedData().getBranch(0);
97 types = branch.getTypes();
99 // TODO: LOW: Revert to custom if data type is not available.
100 configuration = defaultConfiguration.clone();
103 //// Configuration selector
105 // Setup the combo box
106 configurationSelector = new JComboBox(PRESET_ARRAY);
107 for (PlotConfiguration config : PRESET_ARRAY) {
108 if (config.getName().equals(configuration.getName())) {
109 configurationSelector.setSelectedItem(config);
112 configurationSelector.addItemListener(new ItemListener() {
114 public void itemStateChanged(ItemEvent e) {
117 PlotConfiguration conf = (PlotConfiguration) configurationSelector.getSelectedItem();
118 if (conf == CUSTOM_CONFIGURATION)
121 configuration = conf.clone().resetUnits();
126 //// Preset plot configurations:
127 this.add(new JLabel(trans.get("simplotpanel.lbl.Presetplotconf")), "spanx, split");
128 this.add(configurationSelector, "growx, wrap 20lp");
135 this.add(new JLabel(trans.get("simplotpanel.lbl.Xaxistype")), "spanx, split");
136 domainTypeSelector = new JComboBox(types);
137 domainTypeSelector.setSelectedItem(configuration.getDomainAxisType());
138 domainTypeSelector.addItemListener(new ItemListener() {
140 public void itemStateChanged(ItemEvent e) {
143 FlightDataType type = (FlightDataType) domainTypeSelector.getSelectedItem();
144 configuration.setDomainAxisType(type);
145 domainUnitSelector.setUnitGroup(type.getUnitGroup());
146 domainUnitSelector.setSelectedUnit(configuration.getDomainAxisUnit());
150 this.add(domainTypeSelector, "gapright para");
153 this.add(new JLabel(trans.get("simplotpanel.lbl.Unit")));
154 domainUnitSelector = new UnitSelector(configuration.getDomainAxisType().getUnitGroup());
155 domainUnitSelector.setSelectedUnit(configuration.getDomainAxisUnit());
156 domainUnitSelector.addItemListener(new ItemListener() {
158 public void itemStateChanged(ItemEvent e) {
161 configuration.setDomainAxisUnit(domainUnitSelector.getSelectedUnit());
164 this.add(domainUnitSelector, "width 40lp, gapright para");
166 //// The data will be plotted in time order even if the X axis type is not time.
167 DescriptionArea desc = new DescriptionArea(trans.get("simplotpanel.Desc"), 2, -2f);
168 desc.setViewportBorder(BorderFactory.createEmptyBorder());
169 this.add(desc, "width 1px, growx 1, wrap unrel");
173 //// Y axis selector panel
175 this.add(new JLabel(trans.get("simplotpanel.lbl.Yaxistypes")));
177 this.add(new JLabel(trans.get("simplotpanel.lbl.Flightevents")), "wrap rel");
179 typeSelectorPanel = new JPanel(new MigLayout("gapy rel"));
180 JScrollPane scroll = new JScrollPane(typeSelectorPanel);
181 this.add(scroll, "spany 2, height 10px, grow 100, gapright para");
185 eventTableModel = new FlightEventTableModel();
186 JTable table = new JTable(eventTableModel);
187 table.setTableHeader(null);
188 table.setShowVerticalLines(false);
189 table.setRowSelectionAllowed(false);
190 table.setColumnSelectionAllowed(false);
192 TableColumnModel columnModel = table.getColumnModel();
193 TableColumn col0 = columnModel.getColumn(0);
194 int w = table.getRowHeight() + 2;
196 col0.setPreferredWidth(w);
198 table.addMouseListener(new GUIUtil.BooleanTableClickListener(table));
199 this.add(new JScrollPane(table), "height 10px, width 200lp, grow 1, wrap rel");
202 //// All + None buttons
203 JButton button = new JButton(trans.get("simplotpanel.but.All"));
204 button.addActionListener(new ActionListener() {
206 public void actionPerformed(ActionEvent e) {
207 for (FlightEvent.Type t : FlightEvent.Type.values())
208 configuration.setEvent(t, true);
209 eventTableModel.fireTableDataChanged();
212 this.add(button, "split 2, gapleft para, gapright para, growx, sizegroup buttons");
215 button = new JButton(trans.get("simplotpanel.but.None"));
216 button.addActionListener(new ActionListener() {
218 public void actionPerformed(ActionEvent e) {
219 for (FlightEvent.Type t : FlightEvent.Type.values())
220 configuration.setEvent(t, false);
221 eventTableModel.fireTableDataChanged();
224 this.add(button, "gapleft para, gapright para, growx, sizegroup buttons, wrap para");
228 //// New Y axis plot type
229 button = new JButton(trans.get("simplotpanel.but.NewYaxisplottype"));
230 button.addActionListener(new ActionListener() {
232 public void actionPerformed(ActionEvent e) {
233 if (configuration.getTypeCount() >= 15) {
234 JOptionPane.showMessageDialog(SimulationPlotPanel.this,
235 //// A maximum of 15 plots is allowed.
237 trans.get("simplotpanel.OptionPane.lbl1"),
238 trans.get("simplotpanel.OptionPane.lbl2"),
239 JOptionPane.ERROR_MESSAGE);
243 // Select new type smartly
244 FlightDataType type = null;
245 for (FlightDataType t :
246 simulation.getSimulatedData().getBranch(0).getTypes()) {
248 boolean used = false;
249 if (configuration.getDomainAxisType().equals(t)) {
252 for (int i = 0; i < configuration.getTypeCount(); i++) {
253 if (configuration.getType(i).equals(t)) {
266 type = simulation.getSimulatedData().getBranch(0).getTypes()[0];
270 configuration.addPlotDataType(type);
275 this.add(button, "spanx, split");
278 this.add(new JPanel(), "growx");
281 button = new JButton(trans.get("simplotpanel.but.Plotflight"));
282 button.addActionListener(new ActionListener() {
284 public void actionPerformed(ActionEvent e) {
285 defaultConfiguration = configuration.clone();
286 PlotDialog.showPlot(SwingUtilities.getWindowAncestor(SimulationPlotPanel.this),
287 simulation, configuration);
290 this.add(button, "right");
297 private void setToCustom() {
299 configuration.setName(CUSTOM);
300 configurationSelector.setSelectedItem(CUSTOM_CONFIGURATION);
305 private void updatePlots() {
306 domainTypeSelector.setSelectedItem(configuration.getDomainAxisType());
307 domainUnitSelector.setUnitGroup(configuration.getDomainAxisType().getUnitGroup());
308 domainUnitSelector.setSelectedUnit(configuration.getDomainAxisUnit());
310 typeSelectorPanel.removeAll();
311 for (int i = 0; i < configuration.getTypeCount(); i++) {
312 FlightDataType type = configuration.getType(i);
313 Unit unit = configuration.getUnit(i);
314 int axis = configuration.getAxis(i);
316 typeSelectorPanel.add(new PlotTypeSelector(i, type, unit, axis), "wrap");
319 typeSelectorPanel.repaint();
321 eventTableModel.fireTableDataChanged();
328 * A JPanel which configures a single plot of a PlotConfiguration.
330 private class PlotTypeSelector extends JPanel {
331 private final String[] POSITIONS = { AUTO_NAME, LEFT_NAME, RIGHT_NAME };
333 private final int index;
334 private JComboBox typeSelector;
335 private UnitSelector unitSelector;
336 private JComboBox axisSelector;
339 public PlotTypeSelector(int index, FlightDataType type) {
340 this(index, type, null, -1);
343 public PlotTypeSelector(int plotIndex, FlightDataType type, Unit unit, int position) {
344 super(new MigLayout("ins 0"));
346 this.index = plotIndex;
348 typeSelector = new JComboBox(types);
349 typeSelector.setSelectedItem(type);
350 typeSelector.addItemListener(new ItemListener() {
352 public void itemStateChanged(ItemEvent e) {
355 FlightDataType type = (FlightDataType) typeSelector.getSelectedItem();
356 configuration.setPlotDataType(index, type);
357 unitSelector.setUnitGroup(type.getUnitGroup());
358 unitSelector.setSelectedUnit(configuration.getUnit(index));
362 this.add(typeSelector, "gapright para");
365 this.add(new JLabel(trans.get("simplotpanel.lbl.Unit")));
366 unitSelector = new UnitSelector(type.getUnitGroup());
368 unitSelector.setSelectedUnit(unit);
369 unitSelector.addItemListener(new ItemListener() {
371 public void itemStateChanged(ItemEvent e) {
374 Unit unit = (Unit) unitSelector.getSelectedUnit();
375 configuration.setPlotDataUnit(index, unit);
378 this.add(unitSelector, "width 40lp, gapright para");
381 this.add(new JLabel(trans.get("simplotpanel.lbl.Axis")));
382 axisSelector = new JComboBox(POSITIONS);
383 if (position == LEFT)
384 axisSelector.setSelectedIndex(1);
385 else if (position == RIGHT)
386 axisSelector.setSelectedIndex(2);
388 axisSelector.setSelectedIndex(0);
389 axisSelector.addItemListener(new ItemListener() {
391 public void itemStateChanged(ItemEvent e) {
394 int axis = axisSelector.getSelectedIndex() - 1;
395 configuration.setPlotDataAxis(index, axis);
398 this.add(axisSelector);
401 JButton button = new JButton(Icons.DELETE);
402 //// Remove this plot
403 button.setToolTipText(trans.get("simplotpanel.but.ttip.Removethisplot"));
404 button.setBorderPainted(false);
405 button.addActionListener(new ActionListener() {
407 public void actionPerformed(ActionEvent e) {
408 configuration.removePlotDataType(index);
413 this.add(button, "gapright 0");
419 private class FlightEventTableModel extends AbstractTableModel {
420 private final FlightEvent.Type[] eventTypes;
422 public FlightEventTableModel() {
423 EnumSet<FlightEvent.Type> set = EnumSet.noneOf(FlightEvent.Type.class);
424 for (int i = 0; i < simulation.getSimulatedData().getBranchCount(); i++) {
425 for (FlightEvent e : simulation.getSimulatedData().getBranch(i).getEvents()) {
426 set.add(e.getType());
429 set.remove(FlightEvent.Type.ALTITUDE);
430 int count = set.size();
432 eventTypes = new FlightEvent.Type[count];
434 for (FlightEvent.Type t : FlightEvent.Type.values()) {
435 if (set.contains(t)) {
443 public int getColumnCount() {
448 public int getRowCount() {
449 return eventTypes.length;
453 public Class<?> getColumnClass(int column) {
456 return Boolean.class;
462 throw new IndexOutOfBoundsException("column=" + column);
467 public Object getValueAt(int row, int column) {
470 return new Boolean(configuration.isEventActive(eventTypes[row]));
473 return eventTypes[row].toString();
476 throw new IndexOutOfBoundsException("column=" + column);
481 public boolean isCellEditable(int row, int column) {
486 public void setValueAt(Object value, int row, int column) {
487 if (column != 0 || !(value instanceof Boolean)) {
488 throw new IllegalArgumentException("column=" + column + ", value=" + value);
491 configuration.setEvent(eventTypes[row], (Boolean) value);
492 this.fireTableCellUpdated(row, column);