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