moving to core/
[debian/openrocket] / core / src / net / sf / openrocket / gui / main / SimulationPanel.java
1 package net.sf.openrocket.gui.main;
2
3
4 import java.awt.Color;
5 import java.awt.Component;
6 import java.awt.event.ActionEvent;
7 import java.awt.event.ActionListener;
8 import java.awt.event.KeyEvent;
9 import java.awt.event.MouseAdapter;
10 import java.awt.event.MouseEvent;
11 import java.util.Arrays;
12
13 import javax.swing.JButton;
14 import javax.swing.JCheckBox;
15 import javax.swing.JComponent;
16 import javax.swing.JLabel;
17 import javax.swing.JOptionPane;
18 import javax.swing.JPanel;
19 import javax.swing.JScrollPane;
20 import javax.swing.JTable;
21 import javax.swing.KeyStroke;
22 import javax.swing.ListSelectionModel;
23 import javax.swing.SwingUtilities;
24 import javax.swing.table.DefaultTableCellRenderer;
25
26 import net.miginfocom.swing.MigLayout;
27 import net.sf.openrocket.aerodynamics.Warning;
28 import net.sf.openrocket.aerodynamics.WarningSet;
29 import net.sf.openrocket.document.OpenRocketDocument;
30 import net.sf.openrocket.document.Simulation;
31 import net.sf.openrocket.document.events.DocumentChangeEvent;
32 import net.sf.openrocket.document.events.DocumentChangeListener;
33 import net.sf.openrocket.document.events.SimulationChangeEvent;
34 import net.sf.openrocket.gui.adaptors.Column;
35 import net.sf.openrocket.gui.adaptors.ColumnTableModel;
36 import net.sf.openrocket.gui.components.StyledLabel;
37 import net.sf.openrocket.gui.util.Icons;
38 import net.sf.openrocket.l10n.Translator;
39 import net.sf.openrocket.logging.LogHelper;
40 import net.sf.openrocket.rocketcomponent.ComponentChangeEvent;
41 import net.sf.openrocket.rocketcomponent.ComponentChangeListener;
42 import net.sf.openrocket.simulation.FlightData;
43 import net.sf.openrocket.startup.Application;
44 import net.sf.openrocket.startup.Preferences;
45 import net.sf.openrocket.unit.UnitGroup;
46
47 public class SimulationPanel extends JPanel {
48         private static final LogHelper log = Application.getLogger();
49         private static final Translator trans = Application.getTranslator();
50
51         
52         private static final Color WARNING_COLOR = Color.RED;
53         private static final String WARNING_TEXT = "\uFF01"; // Fullwidth exclamation mark
54         
55         private static final Color OK_COLOR = new Color(60, 150, 0);
56         private static final String OK_TEXT = "\u2714"; // Heavy check mark
57         
58
59
60         private final OpenRocketDocument document;
61         
62         private final ColumnTableModel simulationTableModel;
63         private final JTable simulationTable;
64         
65         
66         public SimulationPanel(OpenRocketDocument doc) {
67                 super(new MigLayout("fill", "[grow][][][][][][grow]"));
68                 
69                 JButton button;
70                 
71
72                 this.document = doc;
73                 
74
75
76                 ////////  The simulation action buttons
77                 
78                 //// New simulation button
79                 button = new JButton(trans.get("simpanel.but.newsimulation"));
80                 //// Add a new simulation
81                 button.setToolTipText(trans.get("simpanel.but.ttip.newsimulation"));
82                 button.addActionListener(new ActionListener() {
83                         @Override
84                         public void actionPerformed(ActionEvent e) {
85                                 Simulation sim = new Simulation(document.getRocket());
86                                 sim.setName(document.getNextSimulationName());
87                                 
88                                 int n = document.getSimulationCount();
89                                 document.addSimulation(sim);
90                                 simulationTableModel.fireTableDataChanged();
91                                 simulationTable.clearSelection();
92                                 simulationTable.addRowSelectionInterval(n, n);
93                                 
94                                 openDialog(sim, SimulationEditDialog.EDIT);
95                         }
96                 });
97                 this.add(button, "skip 1, gapright para");
98                 
99                 //// Edit simulation button
100                 button = new JButton(trans.get("simpanel.but.editsimulation"));
101                 //// Edit the selected simulation
102                 button.setToolTipText(trans.get("simpanel.but.ttip.editsim"));
103                 button.addActionListener(new ActionListener() {
104                         @Override
105                         public void actionPerformed(ActionEvent e) {
106                                 int selected = simulationTable.getSelectedRow();
107                                 if (selected < 0)
108                                         return; // TODO: MEDIUM: "None selected" dialog
109                                         
110                                 selected = simulationTable.convertRowIndexToModel(selected);
111                                 simulationTable.clearSelection();
112                                 simulationTable.addRowSelectionInterval(selected, selected);
113                                 
114                                 openDialog(document.getSimulations().get(selected), SimulationEditDialog.EDIT);
115                         }
116                 });
117                 this.add(button, "gapright para");
118                 
119                 //// Run simulations
120                 button = new JButton(trans.get("simpanel.but.runsimulations"));
121                 //// Re-run the selected simulations
122                 button.setToolTipText(trans.get("simpanel.but.ttip.runsimu"));
123                 button.addActionListener(new ActionListener() {
124                         @Override
125                         public void actionPerformed(ActionEvent e) {
126                                 int[] selection = simulationTable.getSelectedRows();
127                                 if (selection.length == 0)
128                                         return; // TODO: LOW: "None selected" dialog
129                                         
130                                 Simulation[] sims = new Simulation[selection.length];
131                                 for (int i = 0; i < selection.length; i++) {
132                                         selection[i] = simulationTable.convertRowIndexToModel(selection[i]);
133                                         sims[i] = document.getSimulation(selection[i]);
134                                 }
135                                 
136                                 long t = System.currentTimeMillis();
137                                 new SimulationRunDialog(SwingUtilities.getWindowAncestor(
138                                                         SimulationPanel.this), sims).setVisible(true);
139                                 log.info("Running simulations took " + (System.currentTimeMillis() - t) + " ms");
140                                 fireMaintainSelection();
141                         }
142                 });
143                 this.add(button, "gapright para");
144                 
145                 //// Delete simulations button
146                 button = new JButton(trans.get("simpanel.but.deletesimulations"));
147                 //// Delete the selected simulations
148                 button.setToolTipText(trans.get("simpanel.but.ttip.deletesim"));
149                 button.addActionListener(new ActionListener() {
150                         @Override
151                         public void actionPerformed(ActionEvent e) {
152                                 int[] selection = simulationTable.getSelectedRows();
153                                 if (selection.length == 0)
154                                         return; // TODO: LOW: "None selected" dialog
155                                         
156                                 // Verify deletion
157                                 boolean verify = Application.getPreferences().getBoolean(Preferences.CONFIRM_DELETE_SIMULATION, true);
158                                 if (verify) {
159                                         
160                                         JPanel panel = new JPanel(new MigLayout());
161                                         //// Do not ask me again
162                                         JCheckBox dontAsk = new JCheckBox(trans.get("simpanel.checkbox.donotask"));
163                                         panel.add(dontAsk, "wrap");
164                                         //// You can change the default operation in the preferences.
165                                         panel.add(new StyledLabel(trans.get("simpanel.lbl.defpref"), -2));
166                                         
167                                         int ret = JOptionPane.showConfirmDialog(SimulationPanel.this,
168                                                                 new Object[] {
169                                                                                 //// Delete the selected simulations?
170                                                                                 trans.get("simpanel.dlg.lbl.DeleteSim1"),
171                                                                                 //// <html><i>This operation cannot be undone.</i>
172                                                                                 trans.get("simpanel.dlg.lbl.DeleteSim2"),
173                                                                                 "",
174                                                                                 panel },
175                                                                 //// Delete simulations
176                                                                                 trans.get("simpanel.dlg.lbl.DeleteSim3"),
177                                                                 JOptionPane.OK_CANCEL_OPTION,
178                                                                 JOptionPane.WARNING_MESSAGE);
179                                         if (ret != JOptionPane.OK_OPTION)
180                                                 return;
181                                         
182                                         if (dontAsk.isSelected()) {
183                                                 Application.getPreferences().putBoolean(Preferences.CONFIRM_DELETE_SIMULATION, false);
184                                         }
185                                 }
186                                 
187                                 // Delete simulations
188                                 for (int i = 0; i < selection.length; i++) {
189                                         selection[i] = simulationTable.convertRowIndexToModel(selection[i]);
190                                 }
191                                 Arrays.sort(selection);
192                                 for (int i = selection.length - 1; i >= 0; i--) {
193                                         document.removeSimulation(selection[i]);
194                                 }
195                                 simulationTableModel.fireTableDataChanged();
196                         }
197                 });
198                 this.add(button, "gapright para");
199                 
200                 //// Plot / export button
201                 button = new JButton(trans.get("simpanel.but.plotexport"));
202                 //              button = new JButton("Plot flight");
203                 button.addActionListener(new ActionListener() {
204                         @Override
205                         public void actionPerformed(ActionEvent e) {
206                                 int selected = simulationTable.getSelectedRow();
207                                 if (selected < 0)
208                                         return; // TODO: MEDIUM: "None selected" dialog
209                                         
210                                 selected = simulationTable.convertRowIndexToModel(selected);
211                                 simulationTable.clearSelection();
212                                 simulationTable.addRowSelectionInterval(selected, selected);
213                                 
214                                 openDialog(document.getSimulations().get(selected), SimulationEditDialog.PLOT);
215                         }
216                 });
217                 this.add(button, "wrap para");
218                 
219
220
221
222                 ////////  The simulation table
223                 
224                 simulationTableModel = new ColumnTableModel(
225
226                 ////  Status and warning column
227                                 new Column("") {
228                                         private JLabel label = null;
229                                         
230                                         @Override
231                                         public Object getValueAt(int row) {
232                                                 if (row < 0 || row >= document.getSimulationCount())
233                                                         return null;
234                                                 
235                                                 // Initialize the label
236                                                 if (label == null) {
237                                                         label = new StyledLabel(2f);
238                                                         label.setIconTextGap(1);
239                                                         //                                                      label.setFont(label.getFont().deriveFont(Font.BOLD));
240                                                 }
241                                                 
242                                                 // Set simulation status icon
243                                                 Simulation.Status status = document.getSimulation(row).getStatus();
244                                                 label.setIcon(Icons.SIMULATION_STATUS_ICON_MAP.get(status));
245                                                 
246
247                                                 // Set warning marker
248                                                 if (status == Simulation.Status.NOT_SIMULATED ||
249                                                                 status == Simulation.Status.EXTERNAL) {
250                                                         
251                                                         label.setText("");
252                                                         
253                                                 } else {
254                                                         
255                                                         WarningSet w = document.getSimulation(row).getSimulatedWarnings();
256                                                         if (w == null) {
257                                                                 label.setText("");
258                                                         } else if (w.isEmpty()) {
259                                                                 label.setForeground(OK_COLOR);
260                                                                 label.setText(OK_TEXT);
261                                                         } else {
262                                                                 label.setForeground(WARNING_COLOR);
263                                                                 label.setText(WARNING_TEXT);
264                                                         }
265                                                 }
266                                                 
267                                                 return label;
268                                         }
269                                         
270                                         @Override
271                                         public int getExactWidth() {
272                                                 return 36;
273                                         }
274                                         
275                                         @Override
276                                         public Class<?> getColumnClass() {
277                                                 return JLabel.class;
278                                         }
279                                 },
280
281                                 //// Simulation name
282                                 //// Name
283                                 new Column(trans.get("simpanel.col.Name")) {
284                                         @Override
285                                         public Object getValueAt(int row) {
286                                                 if (row < 0 || row >= document.getSimulationCount())
287                                                         return null;
288                                                 return document.getSimulation(row).getName();
289                                         }
290                                         
291                                         @Override
292                                         public int getDefaultWidth() {
293                                                 return 125;
294                                         }
295                                 },
296
297                                 //// Simulation motors
298                                 //// Motors
299                                 new Column(trans.get("simpanel.col.Motors")) {
300                                         @Override
301                                         public Object getValueAt(int row) {
302                                                 if (row < 0 || row >= document.getSimulationCount())
303                                                         return null;
304                                                 return document.getSimulation(row).getConfiguration()
305                                                                 .getMotorConfigurationDescription();
306                                         }
307                                         
308                                         @Override
309                                         public int getDefaultWidth() {
310                                                 return 125;
311                                         }
312                                 },
313
314                                 //// Apogee
315                                 new Column(trans.get("simpanel.col.Apogee")) {
316                                         @Override
317                                         public Object getValueAt(int row) {
318                                                 if (row < 0 || row >= document.getSimulationCount())
319                                                         return null;
320                                                 
321                                                 FlightData data = document.getSimulation(row).getSimulatedData();
322                                                 if (data == null)
323                                                         return null;
324                                                 
325                                                 return UnitGroup.UNITS_DISTANCE.getDefaultUnit().toStringUnit(
326                                                                 data.getMaxAltitude());
327                                         }
328                                 },
329
330                                 //// Maximum velocity
331                                 new Column(trans.get("simpanel.col.Maxvelocity")) {
332                                         @Override
333                                         public Object getValueAt(int row) {
334                                                 if (row < 0 || row >= document.getSimulationCount())
335                                                         return null;
336                                                 
337                                                 FlightData data = document.getSimulation(row).getSimulatedData();
338                                                 if (data == null)
339                                                         return null;
340                                                 
341                                                 return UnitGroup.UNITS_VELOCITY.getDefaultUnit().toStringUnit(
342                                                                 data.getMaxVelocity());
343                                         }
344                                 },
345
346                                 //// Maximum acceleration
347                                 new Column(trans.get("simpanel.col.Maxacceleration")) {
348                                         @Override
349                                         public Object getValueAt(int row) {
350                                                 if (row < 0 || row >= document.getSimulationCount())
351                                                         return null;
352                                                 
353                                                 FlightData data = document.getSimulation(row).getSimulatedData();
354                                                 if (data == null)
355                                                         return null;
356                                                 
357                                                 return UnitGroup.UNITS_ACCELERATION.getDefaultUnit().toStringUnit(
358                                                                 data.getMaxAcceleration());
359                                         }
360                                 },
361
362                                 //// Time to apogee
363                                 new Column(trans.get("simpanel.col.Timetoapogee")) {
364                                         @Override
365                                         public Object getValueAt(int row) {
366                                                 if (row < 0 || row >= document.getSimulationCount())
367                                                         return null;
368                                                 
369                                                 FlightData data = document.getSimulation(row).getSimulatedData();
370                                                 if (data == null)
371                                                         return null;
372                                                 
373                                                 return UnitGroup.UNITS_FLIGHT_TIME.getDefaultUnit().toStringUnit(
374                                                                 data.getTimeToApogee());
375                                         }
376                                 },
377
378                                 //// Flight time
379                                 new Column(trans.get("simpanel.col.Flighttime")) {
380                                         @Override
381                                         public Object getValueAt(int row) {
382                                                 if (row < 0 || row >= document.getSimulationCount())
383                                                         return null;
384                                                 
385                                                 FlightData data = document.getSimulation(row).getSimulatedData();
386                                                 if (data == null)
387                                                         return null;
388                                                 
389                                                 return UnitGroup.UNITS_FLIGHT_TIME.getDefaultUnit().toStringUnit(
390                                                                 data.getFlightTime());
391                                         }
392                                 },
393
394                                 //// Ground hit velocity
395                                 new Column(trans.get("simpanel.col.Groundhitvelocity")) {
396                                         @Override
397                                         public Object getValueAt(int row) {
398                                                 if (row < 0 || row >= document.getSimulationCount())
399                                                         return null;
400                                                 
401                                                 FlightData data = document.getSimulation(row).getSimulatedData();
402                                                 if (data == null)
403                                                         return null;
404                                                 
405                                                 return UnitGroup.UNITS_VELOCITY.getDefaultUnit().toStringUnit(
406                                                                 data.getGroundHitVelocity());
407                                         }
408                                 }
409
410                 ) {
411                         @Override
412                         public int getRowCount() {
413                                 return document.getSimulationCount();
414                         }
415                 };
416                 
417                 // Override processKeyBinding so that the JTable does not catch
418                 // key bindings used in menu accelerators
419                 simulationTable = new JTable(simulationTableModel) {
420                         @Override
421                         protected boolean processKeyBinding(KeyStroke ks,
422                                         KeyEvent e,
423                                         int condition,
424                                         boolean pressed) {
425                                 return false;
426                         }
427                 };
428                 simulationTable.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
429                 simulationTable.setDefaultRenderer(Object.class, new JLabelRenderer());
430                 simulationTableModel.setColumnWidths(simulationTable.getColumnModel());
431                 
432
433                 // Mouse listener to act on double-clicks
434                 simulationTable.addMouseListener(new MouseAdapter() {
435                         @Override
436                         public void mouseClicked(MouseEvent e) {
437                                 if (e.getButton() == MouseEvent.BUTTON1 && e.getClickCount() == 2) {
438                                         int selected = simulationTable.getSelectedRow();
439                                         if (selected < 0)
440                                                 return;
441                                         
442                                         selected = simulationTable.convertRowIndexToModel(selected);
443                                         simulationTable.clearSelection();
444                                         simulationTable.addRowSelectionInterval(selected, selected);
445                                         
446                                         openDialog(document.getSimulations().get(selected),
447                                                         SimulationEditDialog.DEFAULT);
448                                 }
449                         }
450                 });
451                 
452                 document.addDocumentChangeListener(new DocumentChangeListener() {
453                         @Override
454                         public void documentChanged(DocumentChangeEvent event) {
455                                 if (!(event instanceof SimulationChangeEvent))
456                                         return;
457                                 simulationTableModel.fireTableDataChanged();
458                         }
459                 });
460                 
461
462
463
464                 // Fire table change event when the rocket changes
465                 document.getRocket().addComponentChangeListener(new ComponentChangeListener() {
466                         @Override
467                         public void componentChanged(ComponentChangeEvent e) {
468                                 fireMaintainSelection();
469                         }
470                 });
471                 
472
473                 JScrollPane scrollpane = new JScrollPane(simulationTable);
474                 this.add(scrollpane, "spanx, grow, wrap rel");
475                 
476
477         }
478         
479         
480         public ListSelectionModel getSimulationListSelectionModel() {
481                 return simulationTable.getSelectionModel();
482         }
483         
484         private void openDialog(final Simulation sim, int position) {
485                 new SimulationEditDialog(SwingUtilities.getWindowAncestor(this), sim, position)
486                                 .setVisible(true);
487                 fireMaintainSelection();
488         }
489         
490         private void fireMaintainSelection() {
491                 int[] selection = simulationTable.getSelectedRows();
492                 simulationTableModel.fireTableDataChanged();
493                 for (int row : selection) {
494                         if (row >= simulationTableModel.getRowCount())
495                                 break;
496                         simulationTable.addRowSelectionInterval(row, row);
497                 }
498         }
499         
500         
501         private class JLabelRenderer extends DefaultTableCellRenderer {
502                 
503                 @Override
504                 public Component getTableCellRendererComponent(JTable table,
505                                 Object value, boolean isSelected, boolean hasFocus, int row,
506                                 int column) {
507                         
508                         if (row < 0 || row >= document.getSimulationCount())
509                                 return super.getTableCellRendererComponent(table, value,
510                                                 isSelected, hasFocus, row, column);
511                         
512                         // A JLabel is self-contained and has set its own tool tip
513                         if (value instanceof JLabel) {
514                                 JLabel label = (JLabel) value;
515                                 if (isSelected)
516                                         label.setBackground(table.getSelectionBackground());
517                                 else
518                                         label.setBackground(table.getBackground());
519                                 label.setOpaque(true);
520                                 
521                                 label.setToolTipText(getSimulationToolTip(document.getSimulation(row)));
522                                 return label;
523                         }
524                         
525                         Component component = super.getTableCellRendererComponent(table, value,
526                                         isSelected, hasFocus, row, column);
527                         
528                         if (component instanceof JComponent) {
529                                 ((JComponent) component).setToolTipText(getSimulationToolTip(
530                                                 document.getSimulation(row)));
531                         }
532                         return component;
533                 }
534                 
535                 private String getSimulationToolTip(Simulation sim) {
536                         String tip;
537                         FlightData data = sim.getSimulatedData();
538                         
539                         tip = "<html><b>" + sim.getName() + "</b><br>";
540                         switch (sim.getStatus()) {
541                         case UPTODATE:
542                                 //// <i>Up to date</i><br>
543                                 tip += "<i>Up to date</i><br>";
544                                 break;
545                         
546                         case LOADED:
547                                 //// <i>Data loaded from a file</i><br>
548                                 tip += "<i>Data loaded from a file</i><br>";
549                                 break;
550                         
551                         case OUTDATED:
552                                 tip += "<i><font color=\"red\">Data is out of date</font></i><br>";
553                                 tip += "Click <i><b>Run simulations</b></i> to simulate.<br>";
554                                 break;
555                         
556                         case EXTERNAL:
557                                 tip += "<i>Imported data</i><br>";
558                                 return tip;
559                                 
560                         case NOT_SIMULATED:
561                                 tip += "<i>Not simulated yet</i><br>";
562                                 tip += "Click <i><b>Run simulations</b></i> to simulate.";
563                                 return tip;
564                         }
565                         
566                         if (data == null) {
567                                 tip += "No simulation data available.";
568                                 return tip;
569                         }
570                         WarningSet warnings = data.getWarningSet();
571                         
572                         if (warnings.isEmpty()) {
573                                 tip += "<font color=\"gray\">No warnings.</font>";
574                                 return tip;
575                         }
576                         
577                         tip += "<font color=\"red\">Warnings:</font>";
578                         for (Warning w : warnings) {
579                                 tip += "<br>" + w.toString();
580                         }
581                         
582                         return tip;
583                 }
584                 
585         }
586 }