updates for 0.9.4
[debian/openrocket] / src / net / sf / openrocket / gui / plot / PlotPanel.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.JButton;
11 import javax.swing.JComboBox;
12 import javax.swing.JLabel;
13 import javax.swing.JOptionPane;
14 import javax.swing.JPanel;
15 import javax.swing.JScrollPane;
16 import javax.swing.JTable;
17 import javax.swing.SwingUtilities;
18 import javax.swing.table.AbstractTableModel;
19 import javax.swing.table.TableColumn;
20 import javax.swing.table.TableColumnModel;
21
22 import net.miginfocom.swing.MigLayout;
23 import net.sf.openrocket.document.Simulation;
24 import net.sf.openrocket.gui.components.StyledLabel;
25 import net.sf.openrocket.gui.components.UnitSelector;
26 import net.sf.openrocket.simulation.FlightDataBranch;
27 import net.sf.openrocket.simulation.FlightEvent;
28 import net.sf.openrocket.simulation.FlightDataBranch.Type;
29 import net.sf.openrocket.unit.Unit;
30 import net.sf.openrocket.util.GUIUtil;
31 import net.sf.openrocket.util.Icons;
32 import net.sf.openrocket.util.Pair;
33
34 public class PlotPanel 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 FlightDataBranch.Type[] 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 PlotPanel(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 30lp");
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                                 FlightDataBranch.Type type = (Type) 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                 StyledLabel desc = new StyledLabel("<html><p>The data will be plotted in time order " +
164                                 "even if the X axis type is not time.", -2);
165                 this.add(desc, "width :0px:, growx, wrap para");
166                 
167                 
168                 
169                 //// Y axis selector panel
170                 
171                 this.add(new JLabel("Y axis types:"));
172                 
173                 this.add(new JLabel("Flight events:"), "wrap rel");
174                 
175                 typeSelectorPanel = new JPanel(new MigLayout("gapy rel"));
176                 JScrollPane scroll = new JScrollPane(typeSelectorPanel);
177                 this.add(scroll, "spany 2, height 10px, grow 100, gapright para");
178                 
179                 
180                 //// Flight events
181                 eventTableModel = new FlightEventTableModel();
182                 JTable table = new JTable(eventTableModel);
183                 table.setTableHeader(null);
184                 table.setShowVerticalLines(false);
185                 table.setRowSelectionAllowed(false);
186                 table.setColumnSelectionAllowed(false);
187                 
188                 TableColumnModel columnModel = table.getColumnModel();
189                 TableColumn col0 = columnModel.getColumn(0);
190                 int w = table.getRowHeight() + 2;
191                 col0.setMinWidth(w);
192                 col0.setPreferredWidth(w);
193                 col0.setMaxWidth(w);
194                 table.addMouseListener(new GUIUtil.BooleanTableClickListener(table));
195                 this.add(new JScrollPane(table), "height 1px, width 200lp, grow 1, wrap rel");
196                 
197                 
198                 ////  All + None buttons
199                 JButton button = new JButton("All");
200                 button.addActionListener(new ActionListener() {
201                         @Override
202                         public void actionPerformed(ActionEvent e) {
203                                 for (FlightEvent.Type t: FlightEvent.Type.values())
204                                         configuration.setEvent(t, true);
205                                 eventTableModel.fireTableDataChanged();
206                         }
207                 });
208                 this.add(button, "split 2, gapleft para, gapright para, growx, sizegroup buttons");
209                 
210                 button = new JButton("None");
211                 button.addActionListener(new ActionListener() {
212                         @Override
213                         public void actionPerformed(ActionEvent e) {
214                                 for (FlightEvent.Type t: FlightEvent.Type.values())
215                                         configuration.setEvent(t, false);
216                                 eventTableModel.fireTableDataChanged();
217                         }
218                 });
219                 this.add(button, "gapleft para, gapright para, growx, sizegroup buttons, wrap para");
220                 
221                 
222                 
223                 
224                 button = new JButton("New Y axis plot type");
225                 button.addActionListener(new ActionListener() {
226                         @Override
227                         public void actionPerformed(ActionEvent e) {
228                                 if (configuration.getTypeCount() >= 15) {
229                                         JOptionPane.showMessageDialog(PlotPanel.this, 
230                                                         "A maximum of 15 plots is allowed.", "Cannot add plot", 
231                                                         JOptionPane.ERROR_MESSAGE);
232                                         return;
233                                 }
234
235                                 // Select new type smartly
236                                 FlightDataBranch.Type type = null;
237                                 for (FlightDataBranch.Type t: 
238                                         simulation.getSimulatedData().getBranch(0).getTypes()) {
239                                         
240                                         boolean used = false;
241                                         if (configuration.getDomainAxisType().equals(t)) {
242                                                 used = true;
243                                         } else {
244                                                 for (int i=0; i < configuration.getTypeCount(); i++) {
245                                                         if (configuration.getType(i).equals(t)) {
246                                                                 used = true;
247                                                                 break;
248                                                         }
249                                                 }
250                                         }
251                                         
252                                         if (!used) {
253                                                 type = t;
254                                                 break;
255                                         }
256                                 }
257                                 if (type == null) {
258                                         type = simulation.getSimulatedData().getBranch(0).getTypes()[0];
259                                 }
260                                 
261                                 // Add new type
262                                 configuration.addPlotDataType(type);
263                                 setToCustom();
264                                 updatePlots();
265                         }
266                 });
267                 this.add(button, "spanx, split");
268                 
269                 
270                 this.add(new JPanel(), "growx");
271                 
272                 button = new JButton("Plot flight");
273                 button.addActionListener(new ActionListener() {
274                         @Override
275                         public void actionPerformed(ActionEvent e) {
276                                 defaultConfiguration = configuration.clone();
277                                 PlotDialog.showPlot(SwingUtilities.getWindowAncestor(PlotPanel.this), 
278                                                 simulation, configuration);
279                         }
280                 });
281                 this.add(button, "right");
282
283                 
284                 updatePlots();
285         }
286         
287         
288         private void setToCustom() {
289                 modifying++;
290                 configuration.setName(CUSTOM);
291                 configurationSelector.setSelectedItem(CUSTOM_CONFIGURATION);
292                 modifying--;
293         }
294         
295         
296         private void updatePlots() {
297                 domainTypeSelector.setSelectedItem(configuration.getDomainAxisType());
298                 domainUnitSelector.setUnitGroup(configuration.getDomainAxisType().getUnitGroup());
299                 domainUnitSelector.setSelectedUnit(configuration.getDomainAxisUnit());
300                 
301                 typeSelectorPanel.removeAll();
302                 for (int i=0; i < configuration.getTypeCount(); i++) {
303                         FlightDataBranch.Type type = configuration.getType(i);
304                         Unit unit = configuration.getUnit(i);
305                         int axis = configuration.getAxis(i);
306                         
307                         typeSelectorPanel.add(new PlotTypeSelector(i, type, unit, axis), "wrap");
308                 }
309                 
310                 typeSelectorPanel.repaint();
311                 
312                 eventTableModel.fireTableDataChanged();
313         }
314         
315         
316         
317         
318         /**
319          * A JPanel which configures a single plot of a PlotConfiguration.
320          */
321         private class PlotTypeSelector extends JPanel {
322                 private final String[] POSITIONS = { AUTO_NAME, LEFT_NAME, RIGHT_NAME };
323                 
324                 private final int index;
325                 private JComboBox typeSelector;
326                 private UnitSelector unitSelector;
327                 private JComboBox axisSelector;
328                 
329                 
330                 public PlotTypeSelector(int index, FlightDataBranch.Type type) {
331                         this (index, type, null, -1);
332                 }
333                 
334                 public PlotTypeSelector(int plotIndex, FlightDataBranch.Type type, Unit unit, int position) {
335                         super(new MigLayout("ins 0"));
336                         
337                         this.index = plotIndex;
338                         
339                         typeSelector = new JComboBox(types);
340                         typeSelector.setSelectedItem(type);
341                         typeSelector.addItemListener(new ItemListener() {
342                                 @Override
343                                 public void itemStateChanged(ItemEvent e) {
344                                         if (modifying > 0)
345                                                 return;
346                                         FlightDataBranch.Type type = (Type) typeSelector.getSelectedItem();
347                                         configuration.setPlotDataType(index, type);
348                                         unitSelector.setUnitGroup(type.getUnitGroup());
349                                         unitSelector.setSelectedUnit(configuration.getUnit(index));
350                                         setToCustom();
351                                 }
352                         });
353                         this.add(typeSelector, "gapright para");
354                         
355                         this.add(new JLabel("Unit:"));
356                         unitSelector = new UnitSelector(type.getUnitGroup());
357                         if (unit != null)
358                                 unitSelector.setSelectedUnit(unit);
359                         unitSelector.addItemListener(new ItemListener() {
360                                 @Override
361                                 public void itemStateChanged(ItemEvent e) {
362                                         if (modifying > 0)
363                                                 return;
364                                         Unit unit = (Unit) unitSelector.getSelectedUnit();
365                                         configuration.setPlotDataUnit(index, unit);
366                                 }
367                         });
368                         this.add(unitSelector, "width 40lp, gapright para");
369                         
370                         this.add(new JLabel("Axis:"));
371                         axisSelector = new JComboBox(POSITIONS);
372                         if (position == LEFT)
373                                 axisSelector.setSelectedIndex(1);
374                         else if (position == RIGHT)
375                                 axisSelector.setSelectedIndex(2);
376                         else
377                                 axisSelector.setSelectedIndex(0);
378                         axisSelector.addItemListener(new ItemListener() {
379                                 @Override
380                                 public void itemStateChanged(ItemEvent e) {
381                                         if (modifying > 0)
382                                                 return;
383                                         int axis = axisSelector.getSelectedIndex() - 1;
384                                         configuration.setPlotDataAxis(index, axis);
385                                 }
386                         });
387                         this.add(axisSelector);
388                         
389                         
390                         JButton button = new JButton(Icons.DELETE);
391                         button.setToolTipText("Remove this plot");
392                         button.setBorderPainted(false);
393                         button.addActionListener(new ActionListener() {
394                                 @Override
395                                 public void actionPerformed(ActionEvent e) {
396                                         configuration.removePlotDataType(index);
397                                         setToCustom();
398                                         updatePlots();
399                                 }
400                         });
401                         this.add(button, "gapright 0");
402                 }
403         }
404         
405         
406         
407         private class FlightEventTableModel extends AbstractTableModel {
408                 private final FlightEvent.Type[] eventTypes;
409                 
410                 public FlightEventTableModel() {
411                         EnumSet<FlightEvent.Type> set = EnumSet.noneOf(FlightEvent.Type.class);
412                         for (int i=0; i < simulation.getSimulatedData().getBranchCount(); i++) {
413                                 for (Pair<Double,FlightEvent> e:
414                                         simulation.getSimulatedData().getBranch(i).getEvents()) {
415                                         set.add(e.getV().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 }