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