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.gui.util.GUIUtil;
28 import net.sf.openrocket.gui.util.Icons;
29 import net.sf.openrocket.l10n.Translator;
30 import net.sf.openrocket.simulation.FlightDataBranch;
31 import net.sf.openrocket.simulation.FlightDataType;
32 import net.sf.openrocket.simulation.FlightEvent;
33 import net.sf.openrocket.startup.Application;
34 import net.sf.openrocket.unit.Unit;
35 import net.sf.openrocket.util.Utils;
38 * Panel that displays the simulation plot options to the user.
40 * @author Sampo Niskanen <sampo.niskanen@iki.fi>
42 public class SimulationPlotPanel extends JPanel {
43 private static final Translator trans = Application.getTranslator();
45 // TODO: LOW: Should these be somewhere else?
46 public static final int AUTO = -1;
47 public static final int LEFT = 0;
48 public static final int RIGHT = 1;
51 public static final String AUTO_NAME = trans.get("simplotpanel.AUTO_NAME");
53 public static final String LEFT_NAME = trans.get("simplotpanel.LEFT_NAME");
55 public static final String RIGHT_NAME = trans.get("simplotpanel.RIGHT_NAME");
58 private static final String CUSTOM = trans.get("simplotpanel.CUSTOM");
60 /** The "Custom" configuration - not to be used for anything other than the title. */
61 private static final PlotConfiguration CUSTOM_CONFIGURATION;
63 CUSTOM_CONFIGURATION = new PlotConfiguration(CUSTOM);
66 /** The array of presets for the combo box. */
67 private static final PlotConfiguration[] PRESET_ARRAY;
69 PRESET_ARRAY = Arrays.copyOf(PlotConfiguration.DEFAULT_CONFIGURATIONS,
70 PlotConfiguration.DEFAULT_CONFIGURATIONS.length + 1);
71 PRESET_ARRAY[PRESET_ARRAY.length - 1] = CUSTOM_CONFIGURATION;
76 /** The current default configuration, set each time a plot is made. */
77 private static PlotConfiguration defaultConfiguration =
78 PlotConfiguration.DEFAULT_CONFIGURATIONS[0].resetUnits();
81 private final Simulation simulation;
82 private final FlightDataType[] types;
83 private PlotConfiguration configuration;
86 private JComboBox configurationSelector;
88 private JComboBox domainTypeSelector;
89 private UnitSelector domainUnitSelector;
91 private JPanel typeSelectorPanel;
92 private FlightEventTableModel eventTableModel;
95 private int modifying = 0;
98 public SimulationPlotPanel(final Simulation simulation) {
99 super(new MigLayout("fill"));
101 this.simulation = simulation;
102 if (simulation.getSimulatedData() == null ||
103 simulation.getSimulatedData().getBranchCount() == 0) {
104 throw new IllegalArgumentException("Simulation contains no data.");
106 FlightDataBranch branch = simulation.getSimulatedData().getBranch(0);
107 types = branch.getTypes();
109 setConfiguration(defaultConfiguration);
111 //// Configuration selector
113 // Setup the combo box
114 configurationSelector = new JComboBox(PRESET_ARRAY);
115 for (PlotConfiguration config : PRESET_ARRAY) {
116 if (config.getName().equals(configuration.getName())) {
117 configurationSelector.setSelectedItem(config);
121 configurationSelector.addItemListener(new ItemListener() {
124 public void itemStateChanged(ItemEvent e) {
125 // We are only concerned with ItemEvent.SELECTED to update
126 // the UI when the selected item changes.
127 // TODO - this should probably be implemented as an ActionListener instead
128 // of ItemStateListener.
129 if ( e.getStateChange() == ItemEvent.DESELECTED) {
134 PlotConfiguration conf = (PlotConfiguration) configurationSelector.getSelectedItem();
135 if (conf == CUSTOM_CONFIGURATION)
138 setConfiguration(conf.clone().resetUnits());
143 //// Preset plot configurations:
144 this.add(new JLabel(trans.get("simplotpanel.lbl.Presetplotconf")), "spanx, split");
145 this.add(configurationSelector, "growx, wrap 20lp");
152 this.add(new JLabel(trans.get("simplotpanel.lbl.Xaxistype")), "spanx, split");
153 domainTypeSelector = new JComboBox(types);
154 domainTypeSelector.setSelectedItem(configuration.getDomainAxisType());
155 domainTypeSelector.addItemListener(new ItemListener() {
157 public void itemStateChanged(ItemEvent e) {
160 FlightDataType type = (FlightDataType) domainTypeSelector.getSelectedItem();
161 configuration.setDomainAxisType(type);
162 domainUnitSelector.setUnitGroup(type.getUnitGroup());
163 domainUnitSelector.setSelectedUnit(configuration.getDomainAxisUnit());
167 this.add(domainTypeSelector, "gapright para");
170 this.add(new JLabel(trans.get("simplotpanel.lbl.Unit")));
171 domainUnitSelector = new UnitSelector(configuration.getDomainAxisType().getUnitGroup());
172 domainUnitSelector.setSelectedUnit(configuration.getDomainAxisUnit());
173 domainUnitSelector.addItemListener(new ItemListener() {
175 public void itemStateChanged(ItemEvent e) {
178 configuration.setDomainAxisUnit(domainUnitSelector.getSelectedUnit());
181 this.add(domainUnitSelector, "width 40lp, gapright para");
183 //// The data will be plotted in time order even if the X axis type is not time.
184 DescriptionArea desc = new DescriptionArea(trans.get("simplotpanel.Desc"), 2, -2f);
185 desc.setViewportBorder(BorderFactory.createEmptyBorder());
186 this.add(desc, "width 1px, growx 1, wrap unrel");
190 //// Y axis selector panel
192 this.add(new JLabel(trans.get("simplotpanel.lbl.Yaxistypes")));
194 this.add(new JLabel(trans.get("simplotpanel.lbl.Flightevents")), "wrap rel");
196 typeSelectorPanel = new JPanel(new MigLayout("gapy rel"));
197 JScrollPane scroll = new JScrollPane(typeSelectorPanel);
198 this.add(scroll, "spany 2, height 10px, wmin 400lp, grow 100, gapright para");
202 eventTableModel = new FlightEventTableModel();
203 JTable table = new JTable(eventTableModel);
204 table.setTableHeader(null);
205 table.setShowVerticalLines(false);
206 table.setRowSelectionAllowed(false);
207 table.setColumnSelectionAllowed(false);
209 TableColumnModel columnModel = table.getColumnModel();
210 TableColumn col0 = columnModel.getColumn(0);
211 int w = table.getRowHeight() + 2;
213 col0.setPreferredWidth(w);
215 table.addMouseListener(new GUIUtil.BooleanTableClickListener(table));
216 this.add(new JScrollPane(table), "height 10px, width 200lp, grow 1, wrap rel");
219 //// All + None buttons
220 JButton button = new JButton(trans.get("simplotpanel.but.All"));
221 button.addActionListener(new ActionListener() {
223 public void actionPerformed(ActionEvent e) {
224 for (FlightEvent.Type t : FlightEvent.Type.values())
225 configuration.setEvent(t, true);
226 eventTableModel.fireTableDataChanged();
229 this.add(button, "split 2, gapleft para, gapright para, growx, sizegroup buttons");
232 button = new JButton(trans.get("simplotpanel.but.None"));
233 button.addActionListener(new ActionListener() {
235 public void actionPerformed(ActionEvent e) {
236 for (FlightEvent.Type t : FlightEvent.Type.values())
237 configuration.setEvent(t, false);
238 eventTableModel.fireTableDataChanged();
241 this.add(button, "gapleft para, gapright para, growx, sizegroup buttons, wrap para");
245 //// New Y axis plot type
246 button = new JButton(trans.get("simplotpanel.but.NewYaxisplottype"));
247 button.addActionListener(new ActionListener() {
249 public void actionPerformed(ActionEvent e) {
250 if (configuration.getTypeCount() >= 15) {
251 JOptionPane.showMessageDialog(SimulationPlotPanel.this,
252 //// A maximum of 15 plots is allowed.
254 trans.get("simplotpanel.OptionPane.lbl1"),
255 trans.get("simplotpanel.OptionPane.lbl2"),
256 JOptionPane.ERROR_MESSAGE);
260 // Select new type smartly
261 FlightDataType type = null;
262 for (FlightDataType t :
263 simulation.getSimulatedData().getBranch(0).getTypes()) {
265 boolean used = false;
266 if (configuration.getDomainAxisType().equals(t)) {
269 for (int i = 0; i < configuration.getTypeCount(); i++) {
270 if (configuration.getType(i).equals(t)) {
283 type = simulation.getSimulatedData().getBranch(0).getTypes()[0];
287 configuration.addPlotDataType(type);
292 this.add(button, "spanx, split");
295 this.add(new JPanel(), "growx");
298 button = new JButton(trans.get("simplotpanel.but.Plotflight"));
299 button.addActionListener(new ActionListener() {
301 public void actionPerformed(ActionEvent e) {
302 if (configuration.getTypeCount() == 0) {
303 JOptionPane.showMessageDialog(SimulationPlotPanel.this,
304 trans.get("error.noPlotSelected"),
305 trans.get("error.noPlotSelected.title"),
306 JOptionPane.ERROR_MESSAGE);
309 defaultConfiguration = configuration.clone();
310 SimulationPlotDialog.showPlot(SwingUtilities.getWindowAncestor(SimulationPlotPanel.this),
311 simulation, configuration);
314 this.add(button, "right");
321 private void setConfiguration(PlotConfiguration conf) {
323 boolean modified = false;
325 configuration = conf.clone();
326 if (!Utils.contains(types, configuration.getDomainAxisType())) {
327 configuration.setDomainAxisType(types[0]);
331 for (int i = 0; i < configuration.getTypeCount(); i++) {
332 if (!Utils.contains(types, configuration.getType(i))) {
333 configuration.removePlotDataType(i);
340 configuration.setName(CUSTOM);
346 private void setToCustom() {
348 configuration.setName(CUSTOM);
349 configurationSelector.setSelectedItem(CUSTOM_CONFIGURATION);
354 private void updatePlots() {
355 domainTypeSelector.setSelectedItem(configuration.getDomainAxisType());
356 domainUnitSelector.setUnitGroup(configuration.getDomainAxisType().getUnitGroup());
357 domainUnitSelector.setSelectedUnit(configuration.getDomainAxisUnit());
359 typeSelectorPanel.removeAll();
360 for (int i = 0; i < configuration.getTypeCount(); i++) {
361 FlightDataType type = configuration.getType(i);
362 Unit unit = configuration.getUnit(i);
363 int axis = configuration.getAxis(i);
365 typeSelectorPanel.add(new PlotTypeSelector(i, type, unit, axis), "wrap");
368 // In order to consistantly update the ui, we need to validate before repaint.
369 typeSelectorPanel.validate();
370 typeSelectorPanel.repaint();
372 eventTableModel.fireTableDataChanged();
379 * A JPanel which configures a single plot of a PlotConfiguration.
381 private class PlotTypeSelector extends JPanel {
382 private final String[] POSITIONS = { AUTO_NAME, LEFT_NAME, RIGHT_NAME };
384 private final int index;
385 private JComboBox typeSelector;
386 private UnitSelector unitSelector;
387 private JComboBox axisSelector;
390 public PlotTypeSelector(int plotIndex, FlightDataType type, Unit unit, int position) {
391 super(new MigLayout("ins 0"));
393 this.index = plotIndex;
395 typeSelector = new JComboBox(types);
396 typeSelector.setSelectedItem(type);
397 typeSelector.addItemListener(new ItemListener() {
399 public void itemStateChanged(ItemEvent e) {
402 FlightDataType type = (FlightDataType) typeSelector.getSelectedItem();
403 configuration.setPlotDataType(index, type);
404 unitSelector.setUnitGroup(type.getUnitGroup());
405 unitSelector.setSelectedUnit(configuration.getUnit(index));
409 this.add(typeSelector, "gapright para");
412 this.add(new JLabel(trans.get("simplotpanel.lbl.Unit")));
413 unitSelector = new UnitSelector(type.getUnitGroup());
415 unitSelector.setSelectedUnit(unit);
416 unitSelector.addItemListener(new ItemListener() {
418 public void itemStateChanged(ItemEvent e) {
421 Unit unit = unitSelector.getSelectedUnit();
422 configuration.setPlotDataUnit(index, unit);
425 this.add(unitSelector, "width 40lp, gapright para");
428 this.add(new JLabel(trans.get("simplotpanel.lbl.Axis")));
429 axisSelector = new JComboBox(POSITIONS);
430 if (position == LEFT)
431 axisSelector.setSelectedIndex(1);
432 else if (position == RIGHT)
433 axisSelector.setSelectedIndex(2);
435 axisSelector.setSelectedIndex(0);
436 axisSelector.addItemListener(new ItemListener() {
438 public void itemStateChanged(ItemEvent e) {
441 int axis = axisSelector.getSelectedIndex() - 1;
442 configuration.setPlotDataAxis(index, axis);
445 this.add(axisSelector);
448 JButton button = new JButton(Icons.DELETE);
449 //// Remove this plot
450 button.setToolTipText(trans.get("simplotpanel.but.ttip.Removethisplot"));
451 button.setBorderPainted(false);
452 button.addActionListener(new ActionListener() {
454 public void actionPerformed(ActionEvent e) {
455 configuration.removePlotDataType(index);
460 this.add(button, "gapright 0");
466 private class FlightEventTableModel extends AbstractTableModel {
467 private final FlightEvent.Type[] eventTypes;
469 public FlightEventTableModel() {
470 EnumSet<FlightEvent.Type> set = EnumSet.noneOf(FlightEvent.Type.class);
471 for (int i = 0; i < simulation.getSimulatedData().getBranchCount(); i++) {
472 for (FlightEvent e : simulation.getSimulatedData().getBranch(i).getEvents()) {
473 set.add(e.getType());
476 set.remove(FlightEvent.Type.ALTITUDE);
477 int count = set.size();
479 eventTypes = new FlightEvent.Type[count];
481 for (FlightEvent.Type t : FlightEvent.Type.values()) {
482 if (set.contains(t)) {
490 public int getColumnCount() {
495 public int getRowCount() {
496 return eventTypes.length;
500 public Class<?> getColumnClass(int column) {
503 return Boolean.class;
509 throw new IndexOutOfBoundsException("column=" + column);
514 public Object getValueAt(int row, int column) {
517 return new Boolean(configuration.isEventActive(eventTypes[row]));
520 return eventTypes[row].toString();
523 throw new IndexOutOfBoundsException("column=" + column);
528 public boolean isCellEditable(int row, int column) {
533 public void setValueAt(Object value, int row, int column) {
534 if (column != 0 || !(value instanceof Boolean)) {
535 throw new IllegalArgumentException("column=" + column + ", value=" + value);
538 configuration.setEvent(eventTypes[row], (Boolean) value);
539 this.fireTableCellUpdated(row, column);