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