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