added svn:ignores
[debian/openrocket] / src / net / sf / openrocket / gui / plot / SimulationPlotPanel.java
1 package net.sf.openrocket.gui.plot;
2
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;
9
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;
22
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.FlightDataType;
29 import net.sf.openrocket.simulation.FlightEvent;
30 import net.sf.openrocket.unit.Unit;
31 import net.sf.openrocket.util.GUIUtil;
32 import net.sf.openrocket.util.Icons;
33
34 public class SimulationPlotPanel extends JPanel {
35         
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;
40         
41         public static final String AUTO_NAME = "Auto";
42         public static final String LEFT_NAME = "Left";
43         public static final String RIGHT_NAME = "Right";
44         
45         private static final String CUSTOM = "Custom";
46         
47         /** The "Custom" configuration - not to be used for anything other than the title. */
48         private static final PlotConfiguration CUSTOM_CONFIGURATION;
49         static {
50                 CUSTOM_CONFIGURATION = new PlotConfiguration(CUSTOM);
51         }
52         
53         /** The array of presets for the combo box. */
54         private static final PlotConfiguration[] PRESET_ARRAY;
55         static {
56                 PRESET_ARRAY = Arrays.copyOf(PlotConfiguration.DEFAULT_CONFIGURATIONS,
57                                 PlotConfiguration.DEFAULT_CONFIGURATIONS.length + 1);
58                 PRESET_ARRAY[PRESET_ARRAY.length - 1] = CUSTOM_CONFIGURATION;
59         }
60         
61
62
63         /** The current default configuration, set each time a plot is made. */
64         private static PlotConfiguration defaultConfiguration =
65                         PlotConfiguration.DEFAULT_CONFIGURATIONS[0].resetUnits();
66         
67
68         private final Simulation simulation;
69         private final FlightDataType[] types;
70         private PlotConfiguration configuration;
71         
72
73         private JComboBox configurationSelector;
74         
75         private JComboBox domainTypeSelector;
76         private UnitSelector domainUnitSelector;
77         
78         private JPanel typeSelectorPanel;
79         private FlightEventTableModel eventTableModel;
80         
81
82         private int modifying = 0;
83         
84         
85         public SimulationPlotPanel(final Simulation simulation) {
86                 super(new MigLayout("fill"));
87                 
88                 this.simulation = simulation;
89                 if (simulation.getSimulatedData() == null ||
90                                 simulation.getSimulatedData().getBranchCount() == 0) {
91                         throw new IllegalArgumentException("Simulation contains no data.");
92                 }
93                 FlightDataBranch branch = simulation.getSimulatedData().getBranch(0);
94                 types = branch.getTypes();
95                 
96                 // TODO: LOW: Revert to custom if data type is not available.
97                 configuration = defaultConfiguration.clone();
98                 
99
100                 ////  Configuration selector
101                 
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);
107                         }
108                 }
109                 configurationSelector.addItemListener(new ItemListener() {
110                         @Override
111                         public void itemStateChanged(ItemEvent e) {
112                                 if (modifying > 0)
113                                         return;
114                                 PlotConfiguration conf = (PlotConfiguration) configurationSelector.getSelectedItem();
115                                 if (conf == CUSTOM_CONFIGURATION)
116                                         return;
117                                 modifying++;
118                                 configuration = conf.clone().resetUnits();
119                                 updatePlots();
120                                 modifying--;
121                         }
122                 });
123                 this.add(new JLabel("Preset plot configurations: "), "spanx, split");
124                 this.add(configurationSelector, "growx, wrap 20lp");
125                 
126
127
128                 //// X axis
129                 
130
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() {
135                         @Override
136                         public void itemStateChanged(ItemEvent e) {
137                                 if (modifying > 0)
138                                         return;
139                                 FlightDataType type = (FlightDataType) domainTypeSelector.getSelectedItem();
140                                 configuration.setDomainAxisType(type);
141                                 domainUnitSelector.setUnitGroup(type.getUnitGroup());
142                                 domainUnitSelector.setSelectedUnit(configuration.getDomainAxisUnit());
143                                 setToCustom();
144                         }
145                 });
146                 this.add(domainTypeSelector, "gapright para");
147                 
148
149                 this.add(new JLabel("Unit:"));
150                 domainUnitSelector = new UnitSelector(configuration.getDomainAxisType().getUnitGroup());
151                 domainUnitSelector.setSelectedUnit(configuration.getDomainAxisUnit());
152                 domainUnitSelector.addItemListener(new ItemListener() {
153                         @Override
154                         public void itemStateChanged(ItemEvent e) {
155                                 if (modifying > 0)
156                                         return;
157                                 configuration.setDomainAxisUnit(domainUnitSelector.getSelectedUnit());
158                         }
159                 });
160                 this.add(domainUnitSelector, "width 40lp, gapright para");
161                 
162
163                 DescriptionArea desc = new DescriptionArea("The data will be plotted in time order " +
164                                 "even if the X axis type is not time.", 2, -2f);
165                 desc.setViewportBorder(BorderFactory.createEmptyBorder());
166                 this.add(desc, "width 1px, growx 1, wrap unrel");
167                 
168
169
170                 //// Y axis selector panel
171                 
172                 this.add(new JLabel("Y axis types:"));
173                 
174                 this.add(new JLabel("Flight events:"), "wrap rel");
175                 
176                 typeSelectorPanel = new JPanel(new MigLayout("gapy rel"));
177                 JScrollPane scroll = new JScrollPane(typeSelectorPanel);
178                 this.add(scroll, "spany 2, height 10px, grow 100, gapright para");
179                 
180
181                 //// Flight events
182                 eventTableModel = new FlightEventTableModel();
183                 JTable table = new JTable(eventTableModel);
184                 table.setTableHeader(null);
185                 table.setShowVerticalLines(false);
186                 table.setRowSelectionAllowed(false);
187                 table.setColumnSelectionAllowed(false);
188                 
189                 TableColumnModel columnModel = table.getColumnModel();
190                 TableColumn col0 = columnModel.getColumn(0);
191                 int w = table.getRowHeight() + 2;
192                 col0.setMinWidth(w);
193                 col0.setPreferredWidth(w);
194                 col0.setMaxWidth(w);
195                 table.addMouseListener(new GUIUtil.BooleanTableClickListener(table));
196                 this.add(new JScrollPane(table), "height 10px, width 200lp, grow 1, wrap rel");
197                 
198
199                 ////  All + None buttons
200                 JButton button = new JButton("All");
201                 button.addActionListener(new ActionListener() {
202                         @Override
203                         public void actionPerformed(ActionEvent e) {
204                                 for (FlightEvent.Type t : FlightEvent.Type.values())
205                                         configuration.setEvent(t, true);
206                                 eventTableModel.fireTableDataChanged();
207                         }
208                 });
209                 this.add(button, "split 2, gapleft para, gapright para, growx, sizegroup buttons");
210                 
211                 button = new JButton("None");
212                 button.addActionListener(new ActionListener() {
213                         @Override
214                         public void actionPerformed(ActionEvent e) {
215                                 for (FlightEvent.Type t : FlightEvent.Type.values())
216                                         configuration.setEvent(t, false);
217                                 eventTableModel.fireTableDataChanged();
218                         }
219                 });
220                 this.add(button, "gapleft para, gapright para, growx, sizegroup buttons, wrap para");
221                 
222
223
224
225                 button = new JButton("New Y axis plot type");
226                 button.addActionListener(new ActionListener() {
227                         @Override
228                         public void actionPerformed(ActionEvent e) {
229                                 if (configuration.getTypeCount() >= 15) {
230                                         JOptionPane.showMessageDialog(SimulationPlotPanel.this,
231                                                         "A maximum of 15 plots is allowed.", "Cannot add plot",
232                                                         JOptionPane.ERROR_MESSAGE);
233                                         return;
234                                 }
235                                 
236                                 // Select new type smartly
237                                 FlightDataType type = null;
238                                 for (FlightDataType t :
239                                         simulation.getSimulatedData().getBranch(0).getTypes()) {
240                                                 
241                                                 boolean used = false;
242                                                 if (configuration.getDomainAxisType().equals(t)) {
243                                                         used = true;
244                                                 } else {
245                                                         for (int i = 0; i < configuration.getTypeCount(); i++) {
246                                                                 if (configuration.getType(i).equals(t)) {
247                                                                         used = true;
248                                                                         break;
249                                                                 }
250                                                         }
251                                                 }
252                                                 
253                                                 if (!used) {
254                                                         type = t;
255                                                         break;
256                                                 }
257                                         }
258                                         if (type == null) {
259                                                 type = simulation.getSimulatedData().getBranch(0).getTypes()[0];
260                                         }
261                                         
262                                         // Add new type
263                                         configuration.addPlotDataType(type);
264                                         setToCustom();
265                                         updatePlots();
266                                 }
267                 });
268                 this.add(button, "spanx, split");
269                 
270
271                 this.add(new JPanel(), "growx");
272                 
273                 button = new JButton("Plot flight");
274                 button.addActionListener(new ActionListener() {
275                         @Override
276                         public void actionPerformed(ActionEvent e) {
277                                 defaultConfiguration = configuration.clone();
278                                 PlotDialog.showPlot(SwingUtilities.getWindowAncestor(SimulationPlotPanel.this),
279                                                 simulation, configuration);
280                         }
281                 });
282                 this.add(button, "right");
283                 
284
285                 updatePlots();
286         }
287         
288         
289         private void setToCustom() {
290                 modifying++;
291                 configuration.setName(CUSTOM);
292                 configurationSelector.setSelectedItem(CUSTOM_CONFIGURATION);
293                 modifying--;
294         }
295         
296         
297         private void updatePlots() {
298                 domainTypeSelector.setSelectedItem(configuration.getDomainAxisType());
299                 domainUnitSelector.setUnitGroup(configuration.getDomainAxisType().getUnitGroup());
300                 domainUnitSelector.setSelectedUnit(configuration.getDomainAxisUnit());
301                 
302                 typeSelectorPanel.removeAll();
303                 for (int i = 0; i < configuration.getTypeCount(); i++) {
304                         FlightDataType type = configuration.getType(i);
305                         Unit unit = configuration.getUnit(i);
306                         int axis = configuration.getAxis(i);
307                         
308                         typeSelectorPanel.add(new PlotTypeSelector(i, type, unit, axis), "wrap");
309                 }
310                 
311                 typeSelectorPanel.repaint();
312                 
313                 eventTableModel.fireTableDataChanged();
314         }
315         
316         
317
318
319         /**
320          * A JPanel which configures a single plot of a PlotConfiguration.
321          */
322         private class PlotTypeSelector extends JPanel {
323                 private final String[] POSITIONS = { AUTO_NAME, LEFT_NAME, RIGHT_NAME };
324                 
325                 private final int index;
326                 private JComboBox typeSelector;
327                 private UnitSelector unitSelector;
328                 private JComboBox axisSelector;
329                 
330                 
331                 public PlotTypeSelector(int index, FlightDataType type) {
332                         this(index, type, null, -1);
333                 }
334                 
335                 public PlotTypeSelector(int plotIndex, FlightDataType type, Unit unit, int position) {
336                         super(new MigLayout("ins 0"));
337                         
338                         this.index = plotIndex;
339                         
340                         typeSelector = new JComboBox(types);
341                         typeSelector.setSelectedItem(type);
342                         typeSelector.addItemListener(new ItemListener() {
343                                 @Override
344                                 public void itemStateChanged(ItemEvent e) {
345                                         if (modifying > 0)
346                                                 return;
347                                         FlightDataType type = (FlightDataType) typeSelector.getSelectedItem();
348                                         configuration.setPlotDataType(index, type);
349                                         unitSelector.setUnitGroup(type.getUnitGroup());
350                                         unitSelector.setSelectedUnit(configuration.getUnit(index));
351                                         setToCustom();
352                                 }
353                         });
354                         this.add(typeSelector, "gapright para");
355                         
356                         this.add(new JLabel("Unit:"));
357                         unitSelector = new UnitSelector(type.getUnitGroup());
358                         if (unit != null)
359                                 unitSelector.setSelectedUnit(unit);
360                         unitSelector.addItemListener(new ItemListener() {
361                                 @Override
362                                 public void itemStateChanged(ItemEvent e) {
363                                         if (modifying > 0)
364                                                 return;
365                                         Unit unit = (Unit) unitSelector.getSelectedUnit();
366                                         configuration.setPlotDataUnit(index, unit);
367                                 }
368                         });
369                         this.add(unitSelector, "width 40lp, gapright para");
370                         
371                         this.add(new JLabel("Axis:"));
372                         axisSelector = new JComboBox(POSITIONS);
373                         if (position == LEFT)
374                                 axisSelector.setSelectedIndex(1);
375                         else if (position == RIGHT)
376                                 axisSelector.setSelectedIndex(2);
377                         else
378                                 axisSelector.setSelectedIndex(0);
379                         axisSelector.addItemListener(new ItemListener() {
380                                 @Override
381                                 public void itemStateChanged(ItemEvent e) {
382                                         if (modifying > 0)
383                                                 return;
384                                         int axis = axisSelector.getSelectedIndex() - 1;
385                                         configuration.setPlotDataAxis(index, axis);
386                                 }
387                         });
388                         this.add(axisSelector);
389                         
390
391                         JButton button = new JButton(Icons.DELETE);
392                         button.setToolTipText("Remove this plot");
393                         button.setBorderPainted(false);
394                         button.addActionListener(new ActionListener() {
395                                 @Override
396                                 public void actionPerformed(ActionEvent e) {
397                                         configuration.removePlotDataType(index);
398                                         setToCustom();
399                                         updatePlots();
400                                 }
401                         });
402                         this.add(button, "gapright 0");
403                 }
404         }
405         
406         
407
408         private class FlightEventTableModel extends AbstractTableModel {
409                 private final FlightEvent.Type[] eventTypes;
410                 
411                 public FlightEventTableModel() {
412                         EnumSet<FlightEvent.Type> set = EnumSet.noneOf(FlightEvent.Type.class);
413                         for (int i = 0; i < simulation.getSimulatedData().getBranchCount(); i++) {
414                                 for (FlightEvent e : simulation.getSimulatedData().getBranch(i).getEvents()) {
415                                         set.add(e.getType());
416                                 }
417                         }
418                         set.remove(FlightEvent.Type.ALTITUDE);
419                         int count = set.size();
420                         
421                         eventTypes = new FlightEvent.Type[count];
422                         int pos = 0;
423                         for (FlightEvent.Type t : FlightEvent.Type.values()) {
424                                 if (set.contains(t)) {
425                                         eventTypes[pos] = t;
426                                         pos++;
427                                 }
428                         }
429                 }
430                 
431                 @Override
432                 public int getColumnCount() {
433                         return 2;
434                 }
435                 
436                 @Override
437                 public int getRowCount() {
438                         return eventTypes.length;
439                 }
440                 
441                 @Override
442                 public Class<?> getColumnClass(int column) {
443                         switch (column) {
444                         case 0:
445                                 return Boolean.class;
446                                 
447                         case 1:
448                                 return String.class;
449                                 
450                         default:
451                                 throw new IndexOutOfBoundsException("column=" + column);
452                         }
453                 }
454                 
455                 @Override
456                 public Object getValueAt(int row, int column) {
457                         switch (column) {
458                         case 0:
459                                 return new Boolean(configuration.isEventActive(eventTypes[row]));
460                                 
461                         case 1:
462                                 return eventTypes[row].toString();
463                                 
464                         default:
465                                 throw new IndexOutOfBoundsException("column=" + column);
466                         }
467                 }
468                 
469                 @Override
470                 public boolean isCellEditable(int row, int column) {
471                         return column == 0;
472                 }
473                 
474                 @Override
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);
478                         }
479                         
480                         configuration.setEvent(eventTypes[row], (Boolean) value);
481                         this.fireTableCellUpdated(row, column);
482                 }
483         }
484 }