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;
37 * Panel that displays the simulation plot options to the user.
39 * @author Sampo Niskanen <sampo.niskanen@iki.fi>
41 public class SimulationPlotPanel extends JPanel {
42 private static final Translator trans = Application.getTranslator();
44 // TODO: LOW: Should these be somewhere else?
45 public static final int AUTO = -1;
46 public static final int LEFT = 0;
47 public static final int RIGHT = 1;
50 public static final String AUTO_NAME = trans.get("simplotpanel.AUTO_NAME");
52 public static final String LEFT_NAME = trans.get("simplotpanel.LEFT_NAME");
54 public static final String RIGHT_NAME = trans.get("simplotpanel.RIGHT_NAME");
57 private static final String CUSTOM = trans.get("simplotpanel.CUSTOM");
59 /** The "Custom" configuration - not to be used for anything other than the title. */
60 private static final PlotConfiguration CUSTOM_CONFIGURATION;
62 CUSTOM_CONFIGURATION = new PlotConfiguration(CUSTOM);
65 /** The array of presets for the combo box. */
66 private static final PlotConfiguration[] PRESET_ARRAY;
68 PRESET_ARRAY = Arrays.copyOf(PlotConfiguration.DEFAULT_CONFIGURATIONS,
69 PlotConfiguration.DEFAULT_CONFIGURATIONS.length + 1);
70 PRESET_ARRAY[PRESET_ARRAY.length - 1] = CUSTOM_CONFIGURATION;
75 /** The current default configuration, set each time a plot is made. */
76 private static PlotConfiguration defaultConfiguration =
77 PlotConfiguration.DEFAULT_CONFIGURATIONS[0].resetUnits();
80 private final Simulation simulation;
81 private final FlightDataType[] types;
82 private PlotConfiguration configuration;
85 private JComboBox configurationSelector;
87 private JComboBox domainTypeSelector;
88 private UnitSelector domainUnitSelector;
90 private JPanel typeSelectorPanel;
91 private FlightEventTableModel eventTableModel;
94 private int modifying = 0;
97 public SimulationPlotPanel(final Simulation simulation) {
98 super(new MigLayout("fill"));
100 this.simulation = simulation;
101 if (simulation.getSimulatedData() == null ||
102 simulation.getSimulatedData().getBranchCount() == 0) {
103 throw new IllegalArgumentException("Simulation contains no data.");
105 FlightDataBranch branch = simulation.getSimulatedData().getBranch(0);
106 types = branch.getTypes();
108 // TODO: LOW: Revert to custom if data type is not available.
109 configuration = defaultConfiguration.clone();
112 //// Configuration selector
114 // Setup the combo box
115 configurationSelector = new JComboBox(PRESET_ARRAY);
116 for (PlotConfiguration config : PRESET_ARRAY) {
117 if (config.getName().equals(configuration.getName())) {
118 configurationSelector.setSelectedItem(config);
121 configurationSelector.addItemListener(new ItemListener() {
123 public void itemStateChanged(ItemEvent e) {
126 PlotConfiguration conf = (PlotConfiguration) configurationSelector.getSelectedItem();
127 if (conf == CUSTOM_CONFIGURATION)
130 configuration = conf.clone().resetUnits();
135 //// Preset plot configurations:
136 this.add(new JLabel(trans.get("simplotpanel.lbl.Presetplotconf")), "spanx, split");
137 this.add(configurationSelector, "growx, wrap 20lp");
144 this.add(new JLabel(trans.get("simplotpanel.lbl.Xaxistype")), "spanx, split");
145 domainTypeSelector = new JComboBox(types);
146 domainTypeSelector.setSelectedItem(configuration.getDomainAxisType());
147 domainTypeSelector.addItemListener(new ItemListener() {
149 public void itemStateChanged(ItemEvent e) {
152 FlightDataType type = (FlightDataType) domainTypeSelector.getSelectedItem();
153 configuration.setDomainAxisType(type);
154 domainUnitSelector.setUnitGroup(type.getUnitGroup());
155 domainUnitSelector.setSelectedUnit(configuration.getDomainAxisUnit());
159 this.add(domainTypeSelector, "gapright para");
162 this.add(new JLabel(trans.get("simplotpanel.lbl.Unit")));
163 domainUnitSelector = new UnitSelector(configuration.getDomainAxisType().getUnitGroup());
164 domainUnitSelector.setSelectedUnit(configuration.getDomainAxisUnit());
165 domainUnitSelector.addItemListener(new ItemListener() {
167 public void itemStateChanged(ItemEvent e) {
170 configuration.setDomainAxisUnit(domainUnitSelector.getSelectedUnit());
173 this.add(domainUnitSelector, "width 40lp, gapright para");
175 //// The data will be plotted in time order even if the X axis type is not time.
176 DescriptionArea desc = new DescriptionArea(trans.get("simplotpanel.Desc"), 2, -2f);
177 desc.setViewportBorder(BorderFactory.createEmptyBorder());
178 this.add(desc, "width 1px, growx 1, wrap unrel");
182 //// Y axis selector panel
184 this.add(new JLabel(trans.get("simplotpanel.lbl.Yaxistypes")));
186 this.add(new JLabel(trans.get("simplotpanel.lbl.Flightevents")), "wrap rel");
188 typeSelectorPanel = new JPanel(new MigLayout("gapy rel"));
189 JScrollPane scroll = new JScrollPane(typeSelectorPanel);
190 this.add(scroll, "spany 2, height 10px, grow 100, gapright para");
194 eventTableModel = new FlightEventTableModel();
195 JTable table = new JTable(eventTableModel);
196 table.setTableHeader(null);
197 table.setShowVerticalLines(false);
198 table.setRowSelectionAllowed(false);
199 table.setColumnSelectionAllowed(false);
201 TableColumnModel columnModel = table.getColumnModel();
202 TableColumn col0 = columnModel.getColumn(0);
203 int w = table.getRowHeight() + 2;
205 col0.setPreferredWidth(w);
207 table.addMouseListener(new GUIUtil.BooleanTableClickListener(table));
208 this.add(new JScrollPane(table), "height 10px, width 200lp, grow 1, wrap rel");
211 //// All + None buttons
212 JButton button = new JButton(trans.get("simplotpanel.but.All"));
213 button.addActionListener(new ActionListener() {
215 public void actionPerformed(ActionEvent e) {
216 for (FlightEvent.Type t : FlightEvent.Type.values())
217 configuration.setEvent(t, true);
218 eventTableModel.fireTableDataChanged();
221 this.add(button, "split 2, gapleft para, gapright para, growx, sizegroup buttons");
224 button = new JButton(trans.get("simplotpanel.but.None"));
225 button.addActionListener(new ActionListener() {
227 public void actionPerformed(ActionEvent e) {
228 for (FlightEvent.Type t : FlightEvent.Type.values())
229 configuration.setEvent(t, false);
230 eventTableModel.fireTableDataChanged();
233 this.add(button, "gapleft para, gapright para, growx, sizegroup buttons, wrap para");
237 //// New Y axis plot type
238 button = new JButton(trans.get("simplotpanel.but.NewYaxisplottype"));
239 button.addActionListener(new ActionListener() {
241 public void actionPerformed(ActionEvent e) {
242 if (configuration.getTypeCount() >= 15) {
243 JOptionPane.showMessageDialog(SimulationPlotPanel.this,
244 //// A maximum of 15 plots is allowed.
246 trans.get("simplotpanel.OptionPane.lbl1"),
247 trans.get("simplotpanel.OptionPane.lbl2"),
248 JOptionPane.ERROR_MESSAGE);
252 // Select new type smartly
253 FlightDataType type = null;
254 for (FlightDataType t :
255 simulation.getSimulatedData().getBranch(0).getTypes()) {
257 boolean used = false;
258 if (configuration.getDomainAxisType().equals(t)) {
261 for (int i = 0; i < configuration.getTypeCount(); i++) {
262 if (configuration.getType(i).equals(t)) {
275 type = simulation.getSimulatedData().getBranch(0).getTypes()[0];
279 configuration.addPlotDataType(type);
284 this.add(button, "spanx, split");
287 this.add(new JPanel(), "growx");
290 button = new JButton(trans.get("simplotpanel.but.Plotflight"));
291 button.addActionListener(new ActionListener() {
293 public void actionPerformed(ActionEvent e) {
294 defaultConfiguration = configuration.clone();
295 SimulationPlotDialog.showPlot(SwingUtilities.getWindowAncestor(SimulationPlotPanel.this),
296 simulation, configuration);
299 this.add(button, "right");
306 private void setToCustom() {
308 configuration.setName(CUSTOM);
309 configurationSelector.setSelectedItem(CUSTOM_CONFIGURATION);
314 private void updatePlots() {
315 domainTypeSelector.setSelectedItem(configuration.getDomainAxisType());
316 domainUnitSelector.setUnitGroup(configuration.getDomainAxisType().getUnitGroup());
317 domainUnitSelector.setSelectedUnit(configuration.getDomainAxisUnit());
319 typeSelectorPanel.removeAll();
320 for (int i = 0; i < configuration.getTypeCount(); i++) {
321 FlightDataType type = configuration.getType(i);
322 Unit unit = configuration.getUnit(i);
323 int axis = configuration.getAxis(i);
325 typeSelectorPanel.add(new PlotTypeSelector(i, type, unit, axis), "wrap");
328 typeSelectorPanel.repaint();
330 eventTableModel.fireTableDataChanged();
337 * A JPanel which configures a single plot of a PlotConfiguration.
339 private class PlotTypeSelector extends JPanel {
340 private final String[] POSITIONS = { AUTO_NAME, LEFT_NAME, RIGHT_NAME };
342 private final int index;
343 private JComboBox typeSelector;
344 private UnitSelector unitSelector;
345 private JComboBox axisSelector;
348 public PlotTypeSelector(int index, FlightDataType type) {
349 this(index, type, null, -1);
352 public PlotTypeSelector(int plotIndex, FlightDataType type, Unit unit, int position) {
353 super(new MigLayout("ins 0"));
355 this.index = plotIndex;
357 typeSelector = new JComboBox(types);
358 typeSelector.setSelectedItem(type);
359 typeSelector.addItemListener(new ItemListener() {
361 public void itemStateChanged(ItemEvent e) {
364 FlightDataType type = (FlightDataType) typeSelector.getSelectedItem();
365 configuration.setPlotDataType(index, type);
366 unitSelector.setUnitGroup(type.getUnitGroup());
367 unitSelector.setSelectedUnit(configuration.getUnit(index));
371 this.add(typeSelector, "gapright para");
374 this.add(new JLabel(trans.get("simplotpanel.lbl.Unit")));
375 unitSelector = new UnitSelector(type.getUnitGroup());
377 unitSelector.setSelectedUnit(unit);
378 unitSelector.addItemListener(new ItemListener() {
380 public void itemStateChanged(ItemEvent e) {
383 Unit unit = unitSelector.getSelectedUnit();
384 configuration.setPlotDataUnit(index, unit);
387 this.add(unitSelector, "width 40lp, gapright para");
390 this.add(new JLabel(trans.get("simplotpanel.lbl.Axis")));
391 axisSelector = new JComboBox(POSITIONS);
392 if (position == LEFT)
393 axisSelector.setSelectedIndex(1);
394 else if (position == RIGHT)
395 axisSelector.setSelectedIndex(2);
397 axisSelector.setSelectedIndex(0);
398 axisSelector.addItemListener(new ItemListener() {
400 public void itemStateChanged(ItemEvent e) {
403 int axis = axisSelector.getSelectedIndex() - 1;
404 configuration.setPlotDataAxis(index, axis);
407 this.add(axisSelector);
410 JButton button = new JButton(Icons.DELETE);
411 //// Remove this plot
412 button.setToolTipText(trans.get("simplotpanel.but.ttip.Removethisplot"));
413 button.setBorderPainted(false);
414 button.addActionListener(new ActionListener() {
416 public void actionPerformed(ActionEvent e) {
417 configuration.removePlotDataType(index);
422 this.add(button, "gapright 0");
428 private class FlightEventTableModel extends AbstractTableModel {
429 private final FlightEvent.Type[] eventTypes;
431 public FlightEventTableModel() {
432 EnumSet<FlightEvent.Type> set = EnumSet.noneOf(FlightEvent.Type.class);
433 for (int i = 0; i < simulation.getSimulatedData().getBranchCount(); i++) {
434 for (FlightEvent e : simulation.getSimulatedData().getBranch(i).getEvents()) {
435 set.add(e.getType());
438 set.remove(FlightEvent.Type.ALTITUDE);
439 int count = set.size();
441 eventTypes = new FlightEvent.Type[count];
443 for (FlightEvent.Type t : FlightEvent.Type.values()) {
444 if (set.contains(t)) {
452 public int getColumnCount() {
457 public int getRowCount() {
458 return eventTypes.length;
462 public Class<?> getColumnClass(int column) {
465 return Boolean.class;
471 throw new IndexOutOfBoundsException("column=" + column);
476 public Object getValueAt(int row, int column) {
479 return new Boolean(configuration.isEventActive(eventTypes[row]));
482 return eventTypes[row].toString();
485 throw new IndexOutOfBoundsException("column=" + column);
490 public boolean isCellEditable(int row, int column) {
495 public void setValueAt(Object value, int row, int column) {
496 if (column != 0 || !(value instanceof Boolean)) {
497 throw new IllegalArgumentException("column=" + column + ", value=" + value);
500 configuration.setEvent(eventTypes[row], (Boolean) value);
501 this.fireTableCellUpdated(row, column);