create changelog entry
[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), document, 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                                 //// Launch rod velocity
315                                 new Column(trans.get("simpanel.col.Velocityoffrod")) {
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_VELOCITY.getDefaultUnit().toStringUnit(
326                                                                 data.getLaunchRodVelocity());
327                                         }
328                                 },
329
330                                 //// Apogee
331                                 new Column(trans.get("simpanel.col.Apogee")) {
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_DISTANCE.getDefaultUnit().toStringUnit(
342                                                                 data.getMaxAltitude());
343                                         }
344                                 },
345
346                                 //// Velocity at deployment
347                                 new Column(trans.get("simpanel.col.Velocityatdeploy")) {
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_VELOCITY.getDefaultUnit().toStringUnit(
358                                                                 data.getDeploymentVelocity());
359                                         }
360                                 },
361
362                                 //// Maximum velocity
363                                 new Column(trans.get("simpanel.col.Maxvelocity")) {
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_VELOCITY.getDefaultUnit().toStringUnit(
374                                                                 data.getMaxVelocity());
375                                         }
376                                 },
377
378                                 //// Maximum acceleration
379                                 new Column(trans.get("simpanel.col.Maxacceleration")) {
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_ACCELERATION.getDefaultUnit().toStringUnit(
390                                                                 data.getMaxAcceleration());
391                                         }
392                                 },
393
394                                 //// Time to apogee
395                                 new Column(trans.get("simpanel.col.Timetoapogee")) {
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_FLIGHT_TIME.getDefaultUnit().toStringUnit(
406                                                                 data.getTimeToApogee());
407                                         }
408                                 },
409
410                                 //// Flight time
411                                 new Column(trans.get("simpanel.col.Flighttime")) {
412                                         @Override
413                                         public Object getValueAt(int row) {
414                                                 if (row < 0 || row >= document.getSimulationCount())
415                                                         return null;
416                                                 
417                                                 FlightData data = document.getSimulation(row).getSimulatedData();
418                                                 if (data == null)
419                                                         return null;
420                                                 
421                                                 return UnitGroup.UNITS_FLIGHT_TIME.getDefaultUnit().toStringUnit(
422                                                                 data.getFlightTime());
423                                         }
424                                 },
425
426                                 //// Ground hit velocity
427                                 new Column(trans.get("simpanel.col.Groundhitvelocity")) {
428                                         @Override
429                                         public Object getValueAt(int row) {
430                                                 if (row < 0 || row >= document.getSimulationCount())
431                                                         return null;
432                                                 
433                                                 FlightData data = document.getSimulation(row).getSimulatedData();
434                                                 if (data == null)
435                                                         return null;
436                                                 
437                                                 return UnitGroup.UNITS_VELOCITY.getDefaultUnit().toStringUnit(
438                                                                 data.getGroundHitVelocity());
439                                         }
440                                 }
441
442                 ) {
443                         @Override
444                         public int getRowCount() {
445                                 return document.getSimulationCount();
446                         }
447                 };
448                 
449                 // Override processKeyBinding so that the JTable does not catch
450                 // key bindings used in menu accelerators
451                 simulationTable = new JTable(simulationTableModel) {
452                         @Override
453                         protected boolean processKeyBinding(KeyStroke ks,
454                                         KeyEvent e,
455                                         int condition,
456                                         boolean pressed) {
457                                 return false;
458                         }
459                 };
460                 simulationTable.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
461                 simulationTable.setDefaultRenderer(Object.class, new JLabelRenderer());
462                 simulationTableModel.setColumnWidths(simulationTable.getColumnModel());
463                 
464
465                 // Mouse listener to act on double-clicks
466                 simulationTable.addMouseListener(new MouseAdapter() {
467                         @Override
468                         public void mouseClicked(MouseEvent e) {
469                                 if (e.getButton() == MouseEvent.BUTTON1 && e.getClickCount() == 2) {
470                                         int selected = simulationTable.getSelectedRow();
471                                         if (selected < 0)
472                                                 return;
473                                         
474                                         selected = simulationTable.convertRowIndexToModel(selected);
475                                         simulationTable.clearSelection();
476                                         simulationTable.addRowSelectionInterval(selected, selected);
477                                         
478                                         openDialog(document.getSimulations().get(selected),
479                                                         SimulationEditDialog.DEFAULT);
480                                 }
481                         }
482                 });
483                 
484                 document.addDocumentChangeListener(new DocumentChangeListener() {
485                         @Override
486                         public void documentChanged(DocumentChangeEvent event) {
487                                 if (!(event instanceof SimulationChangeEvent))
488                                         return;
489                                 simulationTableModel.fireTableDataChanged();
490                         }
491                 });
492                 
493
494
495
496                 // Fire table change event when the rocket changes
497                 document.getRocket().addComponentChangeListener(new ComponentChangeListener() {
498                         @Override
499                         public void componentChanged(ComponentChangeEvent e) {
500                                 fireMaintainSelection();
501                         }
502                 });
503                 
504
505                 JScrollPane scrollpane = new JScrollPane(simulationTable);
506                 this.add(scrollpane, "spanx, grow, wrap rel");
507                 
508
509         }
510         
511         
512         public ListSelectionModel getSimulationListSelectionModel() {
513                 return simulationTable.getSelectionModel();
514         }
515         
516         private void openDialog(final Simulation sim, int position) {
517                 new SimulationEditDialog(SwingUtilities.getWindowAncestor(this), document, sim, position)
518                                 .setVisible(true);
519                 fireMaintainSelection();
520         }
521         
522         private void fireMaintainSelection() {
523                 int[] selection = simulationTable.getSelectedRows();
524                 simulationTableModel.fireTableDataChanged();
525                 for (int row : selection) {
526                         if (row >= simulationTableModel.getRowCount())
527                                 break;
528                         simulationTable.addRowSelectionInterval(row, row);
529                 }
530         }
531         
532         
533         private class JLabelRenderer extends DefaultTableCellRenderer {
534                 
535                 @Override
536                 public Component getTableCellRendererComponent(JTable table,
537                                 Object value, boolean isSelected, boolean hasFocus, int row,
538                                 int column) {
539                         
540                         if (row < 0 || row >= document.getSimulationCount())
541                                 return super.getTableCellRendererComponent(table, value,
542                                                 isSelected, hasFocus, row, column);
543                         
544                         // A JLabel is self-contained and has set its own tool tip
545                         if (value instanceof JLabel) {
546                                 JLabel label = (JLabel) value;
547                                 if (isSelected)
548                                         label.setBackground(table.getSelectionBackground());
549                                 else
550                                         label.setBackground(table.getBackground());
551                                 label.setOpaque(true);
552                                 
553                                 label.setToolTipText(getSimulationToolTip(document.getSimulation(row)));
554                                 return label;
555                         }
556                         
557                         Component component = super.getTableCellRendererComponent(table, value,
558                                         isSelected, hasFocus, row, column);
559                         
560                         if (component instanceof JComponent) {
561                                 ((JComponent) component).setToolTipText(getSimulationToolTip(
562                                                 document.getSimulation(row)));
563                         }
564                         return component;
565                 }
566                 
567                 private String getSimulationToolTip(Simulation sim) {
568                         String tip;
569                         FlightData data = sim.getSimulatedData();
570                         
571                         tip = "<html><b>" + sim.getName() + "</b><br>";
572                         switch (sim.getStatus()) {
573                         case UPTODATE:
574                                 tip += trans.get ("simpanel.ttip.uptodate") + "<br>";
575                                 break;
576                         
577                         case LOADED:
578                                 tip += trans.get ("simpanel.ttip.loaded") + "<br>";
579                                 break;
580                         
581                         case OUTDATED:
582                                 tip += trans.get ("simpanel.ttip.outdated") + "<br>";
583                                 break;
584                         
585                         case EXTERNAL:
586                                 tip += trans.get ("simpanel.ttip.external") + "<br>";
587                                 return tip;
588                                 
589                         case NOT_SIMULATED:
590                                 tip += trans.get ("simpanel.ttip.notSimulated");
591                                 return tip;
592                         }
593                         
594                         if (data == null) {
595                                 tip += trans.get ("simpanel.ttip.noData");
596                                 return tip;
597                         }
598                         WarningSet warnings = data.getWarningSet();
599                         
600                         if (warnings.isEmpty()) {
601                                 tip += trans.get ("simpanel.ttip.noWarnings");
602                                 return tip;
603                         }
604                         
605                         tip += trans.get ("simpanel.ttip.warnings");
606                         for (Warning w : warnings) {
607                                 tip += "<br>" + w.toString();
608                         }
609                         
610                         return tip;
611                 }
612                 
613         }
614 }