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