Initial commit
[debian/openrocket] / src / net / sf / openrocket / gui / ComponentAnalysisDialog.java
1 package net.sf.openrocket.gui;
2
3 import static net.sf.openrocket.unit.Unit.NOUNIT2;
4
5 import java.awt.Color;
6 import java.awt.Component;
7 import java.awt.Dimension;
8 import java.awt.Font;
9 import java.awt.event.ActionEvent;
10 import java.awt.event.ActionListener;
11 import java.awt.event.WindowAdapter;
12 import java.awt.event.WindowEvent;
13 import java.util.ArrayList;
14 import java.util.List;
15 import java.util.Map;
16 import java.util.Vector;
17
18 import javax.swing.BorderFactory;
19 import javax.swing.JButton;
20 import javax.swing.JComboBox;
21 import javax.swing.JDialog;
22 import javax.swing.JFrame;
23 import javax.swing.JLabel;
24 import javax.swing.JList;
25 import javax.swing.JPanel;
26 import javax.swing.JScrollPane;
27 import javax.swing.JTabbedPane;
28 import javax.swing.JTable;
29 import javax.swing.JToggleButton;
30 import javax.swing.ListSelectionModel;
31 import javax.swing.SwingConstants;
32 import javax.swing.SwingUtilities;
33 import javax.swing.event.ChangeEvent;
34 import javax.swing.event.ChangeListener;
35 import javax.swing.table.TableCellRenderer;
36
37 import net.miginfocom.swing.MigLayout;
38 import net.sf.openrocket.aerodynamics.AerodynamicCalculator;
39 import net.sf.openrocket.aerodynamics.AerodynamicForces;
40 import net.sf.openrocket.aerodynamics.FlightConditions;
41 import net.sf.openrocket.aerodynamics.Warning;
42 import net.sf.openrocket.aerodynamics.WarningSet;
43 import net.sf.openrocket.gui.adaptors.Column;
44 import net.sf.openrocket.gui.adaptors.ColumnTableModel;
45 import net.sf.openrocket.gui.adaptors.DoubleModel;
46 import net.sf.openrocket.gui.adaptors.MotorConfigurationModel;
47 import net.sf.openrocket.gui.scalefigure.RocketPanel;
48 import net.sf.openrocket.rocketcomponent.Configuration;
49 import net.sf.openrocket.rocketcomponent.FinSet;
50 import net.sf.openrocket.rocketcomponent.Rocket;
51 import net.sf.openrocket.rocketcomponent.RocketComponent;
52 import net.sf.openrocket.unit.Unit;
53 import net.sf.openrocket.unit.UnitGroup;
54 import net.sf.openrocket.util.GUIUtil;
55 import net.sf.openrocket.util.MathUtil;
56 import net.sf.openrocket.util.Prefs;
57
58 public class ComponentAnalysisDialog extends JDialog implements ChangeListener {
59
60         private static ComponentAnalysisDialog singletonDialog = null;
61
62         
63         private final FlightConditions conditions;
64         private final Configuration configuration;
65         private final DoubleModel theta, aoa, mach, roll;
66         private final JToggleButton worstToggle;
67         private boolean fakeChange = false;
68         private AerodynamicCalculator calculator;
69         
70         private final ColumnTableModel cpTableModel;
71         private final ColumnTableModel dragTableModel;
72         private final ColumnTableModel rollTableModel;
73         
74         private final JList warningList;
75         
76         
77         private final List<AerodynamicForces> cpData = new ArrayList<AerodynamicForces>();
78         private final List<AerodynamicForces> dragData = new ArrayList<AerodynamicForces>();
79         private double totalCD = 0;
80         private final List<AerodynamicForces> rollData = new ArrayList<AerodynamicForces>();
81         
82         
83         public ComponentAnalysisDialog(final RocketPanel rocketPanel) {
84                 super(SwingUtilities.getWindowAncestor(rocketPanel), "Component analysis");
85
86                 JTable table;
87
88                 JPanel panel = new JPanel(new MigLayout("fill","[][35lp::][fill][fill]"));
89                 add(panel);
90                 
91                 this.configuration = rocketPanel.getConfiguration();
92                 this.calculator = rocketPanel.getCalculator().newInstance();
93                 this.calculator.setConfiguration(configuration);
94
95                 
96                 conditions = new FlightConditions(configuration);
97                 
98                 rocketPanel.setCPAOA(0);
99                 aoa = new DoubleModel(rocketPanel, "CPAOA", UnitGroup.UNITS_ANGLE, 0, Math.PI);
100                 rocketPanel.setCPMach(Prefs.getDefaultMach());
101                 mach = new DoubleModel(rocketPanel, "CPMach", UnitGroup.UNITS_COEFFICIENT, 0);
102                 rocketPanel.setCPTheta(rocketPanel.getFigure().getRotation());
103                 theta = new DoubleModel(rocketPanel, "CPTheta", UnitGroup.UNITS_ANGLE, 0, 2*Math.PI);
104                 rocketPanel.setCPRoll(0);
105                 roll = new DoubleModel(rocketPanel, "CPRoll", UnitGroup.UNITS_ROLL);
106                 
107                 
108                 panel.add(new JLabel("Wind direction:"),"width 100lp!");
109                 panel.add(new UnitSelector(theta,true),"width 50lp!");
110                 BasicSlider slider = new BasicSlider(theta.getSliderModel(0, 2*Math.PI));
111                 panel.add(slider,"growx, split 2");
112                 worstToggle = new JToggleButton("Worst");
113                 worstToggle.setSelected(true);
114                 worstToggle.addActionListener(new ActionListener() {
115                         @Override
116                         public void actionPerformed(ActionEvent e) {
117                                 stateChanged(null);
118                         }
119                 });
120                 slider.addChangeListener(new ChangeListener() {
121                         @Override
122                         public void stateChanged(ChangeEvent e) {
123                                 if (!fakeChange)
124                                         worstToggle.setSelected(false);
125                         }
126                 });
127                 panel.add(worstToggle,"");
128                 
129                 
130                 warningList = new JList();
131                 JScrollPane scrollPane = new JScrollPane(warningList);
132                 scrollPane.setBorder(BorderFactory.createTitledBorder("Warnings:"));
133                 panel.add(scrollPane,"gap paragraph, spany 4, width 300lp!, growy 1, height :100lp:, wrap");
134                 
135                 
136                 panel.add(new JLabel("Angle of attack:"),"width 100lp!");
137                 panel.add(new UnitSelector(aoa,true),"width 50lp!");
138                 panel.add(new BasicSlider(aoa.getSliderModel(0, Math.PI)),"growx, wrap");
139                 
140                 panel.add(new JLabel("Mach number:"),"width 100lp!");
141                 panel.add(new UnitSelector(mach,true),"width 50lp!");
142                 panel.add(new BasicSlider(mach.getSliderModel(0, 3)),"growx, wrap");
143                 
144                 panel.add(new JLabel("Roll rate:"), "width 100lp!");
145                 panel.add(new UnitSelector(roll,true),"width 50lp!");
146                 panel.add(new BasicSlider(roll.getSliderModel(-20*2*Math.PI, 20*2*Math.PI)),
147                                 "growx, wrap paragraph");
148                 
149                 
150                 // Stage and motor selection:
151                 
152                 panel.add(new JLabel("Active stages:"),"spanx, split, gapafter rel");
153                 panel.add(new StageSelector(configuration),"gapafter paragraph");
154                                 
155                 JLabel label = new JLabel("Motor configuration:");
156                 label.setHorizontalAlignment(JLabel.RIGHT);
157                 panel.add(label,"growx, right");
158                 panel.add(new JComboBox(new MotorConfigurationModel(configuration)),"wrap");
159
160                 
161                 
162                 // Tabbed pane
163                 
164                 JTabbedPane tabbedPane = new JTabbedPane();
165                 panel.add(tabbedPane, "spanx, growx, growy");
166                 
167                 
168                 // Create the CP data table
169                 cpTableModel = new ColumnTableModel(
170                                 
171                                 new Column("Component") {
172                                         @Override public Object getValueAt(int row) {
173                                                 RocketComponent c = cpData.get(row).component;
174                                                 if (c instanceof Rocket) {
175                                                         return "Total";
176                                                 }
177                                                 return c.toString();
178                                         }
179                                         @Override public int getDefaultWidth() {
180                                                 return 200;
181                                         }
182                                 },
183                                 new Column("CG / " + UnitGroup.UNITS_LENGTH.getDefaultUnit().getUnit()) {
184                                         private Unit unit = UnitGroup.UNITS_LENGTH.getDefaultUnit();
185                                         @Override public Object getValueAt(int row) {
186                                                 return unit.toString(cpData.get(row).cg.x);
187                                         }
188                                 },
189                                 new Column("Mass / " + UnitGroup.UNITS_MASS.getDefaultUnit().getUnit()) {
190                                         private Unit unit = UnitGroup.UNITS_MASS.getDefaultUnit();
191                                         @Override
192                                         public Object getValueAt(int row) {
193                                                 return unit.toString(cpData.get(row).cg.weight);
194                                         }
195                                 },
196                                 new Column("CP / " + UnitGroup.UNITS_LENGTH.getDefaultUnit().getUnit()) {
197                                         private Unit unit = UnitGroup.UNITS_LENGTH.getDefaultUnit();
198                                         @Override public Object getValueAt(int row) {
199                                                 return unit.toString(cpData.get(row).cp.x);
200                                         }
201                                 },
202                                 new Column("<html>C<sub>N<sub>\u03b1</sub></sub>") {
203                                         @Override public Object getValueAt(int row) {
204                                                 return NOUNIT2.toString(cpData.get(row).cp.weight);
205                                         }
206                                 }
207                                 
208                 ) {
209                         @Override public int getRowCount() {
210                                 return cpData.size();
211                         }
212                 };
213                 
214                 table = new JTable(cpTableModel);
215                 table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
216                 table.setSelectionBackground(Color.LIGHT_GRAY);
217                 table.setSelectionForeground(Color.BLACK);
218                 cpTableModel.setColumnWidths(table.getColumnModel());
219                 
220                 table.setDefaultRenderer(Object.class, new CustomCellRenderer());
221 //              table.setShowHorizontalLines(false);
222 //              table.setShowVerticalLines(true);
223                 
224                 JScrollPane scrollpane = new JScrollPane(table);
225                 scrollpane.setPreferredSize(new Dimension(600,200));
226                 
227                 tabbedPane.addTab("Stability", null, scrollpane, "Stability information");
228                 
229                 
230                 
231                 // Create the drag data table
232                 dragTableModel = new ColumnTableModel(
233                                 new Column("Component") {
234                                         @Override public Object getValueAt(int row) {
235                                                 RocketComponent c = dragData.get(row).component;
236                                                 if (c instanceof Rocket) {
237                                                         return "Total";
238                                                 }
239                                                 return c.toString();
240                                         }
241                                         @Override public int getDefaultWidth() {
242                                                 return 200;
243                                         }
244                                 },
245                                 new Column("<html>Pressure C<sub>D</sub>") {
246                                         @Override public Object getValueAt(int row) {
247                                                 return dragData.get(row).pressureCD;
248                                         }
249                                 },
250                                 new Column("<html>Base C<sub>D</sub>") {
251                                         @Override public Object getValueAt(int row) {
252                                                 return dragData.get(row).baseCD;
253                                         }
254                                 },
255                                 new Column("<html>Friction C<sub>D</sub>") {
256                                         @Override public Object getValueAt(int row) {
257                                                 return dragData.get(row).frictionCD;
258                                         }
259                                 },
260                                 new Column("<html>Total C<sub>D</sub>") {
261                                         @Override public Object getValueAt(int row) {
262                                                 return dragData.get(row).CD;
263                                         }
264                                 }
265                 ) {
266                         @Override public int getRowCount() {
267                                 return dragData.size();
268                         }                       
269                 };
270                 
271
272                 table = new JTable(dragTableModel);
273                 table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
274                 table.setSelectionBackground(Color.LIGHT_GRAY);
275                 table.setSelectionForeground(Color.BLACK);
276                 dragTableModel.setColumnWidths(table.getColumnModel());
277                 
278                 table.setDefaultRenderer(Object.class, new DragCellRenderer(new Color(0.5f,1.0f,0.5f)));
279 //              table.setShowHorizontalLines(false);
280 //              table.setShowVerticalLines(true);
281                 
282                 scrollpane = new JScrollPane(table);
283                 scrollpane.setPreferredSize(new Dimension(600,200));
284                 
285                 tabbedPane.addTab("Drag characteristics", null, scrollpane, "Drag characteristics");
286                 
287                 
288                 
289                 
290                 // Create the roll data table
291                 rollTableModel = new ColumnTableModel(
292                                 new Column("Component") {
293                                         @Override public Object getValueAt(int row) {
294                                                 RocketComponent c = rollData.get(row).component;
295                                                 if (c instanceof Rocket) {
296                                                         return "Total";
297                                                 }
298                                                 return c.toString();
299                                         }
300                                 },
301                                 new Column("Roll forcing coefficient") {
302                                         @Override public Object getValueAt(int row) {
303                                                 return rollData.get(row).CrollForce;
304                                         }
305                                 },
306                                 new Column("Roll damping coefficient") {
307                                         @Override public Object getValueAt(int row) {
308                                                 return rollData.get(row).CrollDamp;
309                                         }
310                                 },
311                                 new Column("<html>Total C<sub>l</sub>") {
312                                         @Override public Object getValueAt(int row) {
313                                                 return rollData.get(row).Croll;
314                                         }
315                                 }
316                 ) {
317                         @Override public int getRowCount() {
318                                 return rollData.size();
319                         }                       
320                 };
321                 
322
323                 table = new JTable(rollTableModel);
324                 table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
325                 table.setSelectionBackground(Color.LIGHT_GRAY);
326                 table.setSelectionForeground(Color.BLACK);
327                 rollTableModel.setColumnWidths(table.getColumnModel());
328                 
329                 scrollpane = new JScrollPane(table);
330                 scrollpane.setPreferredSize(new Dimension(600,200));
331                 
332                 tabbedPane.addTab("Roll dynamics", null, scrollpane, "Roll dynamics");
333                 
334                 
335                 
336                 
337                 
338                 
339                 // Add the data updater to listen to changes in aoa and theta
340                 mach.addChangeListener(this);
341                 theta.addChangeListener(this);
342                 aoa.addChangeListener(this);
343                 roll.addChangeListener(this);
344                 configuration.addChangeListener(this);
345                 this.stateChanged(null);
346                 
347                 
348                 
349                 // Remove listeners when closing window
350                 this.addWindowListener(new WindowAdapter() {
351                         @Override
352                         public void windowClosed(WindowEvent e) {
353                                 System.out.println("Closing method called: "+this);
354                                 theta.removeChangeListener(ComponentAnalysisDialog.this);
355                                 aoa.removeChangeListener(ComponentAnalysisDialog.this);
356                                 mach.removeChangeListener(ComponentAnalysisDialog.this);
357                                 roll.removeChangeListener(ComponentAnalysisDialog.this);
358                                 configuration.removeChangeListener(ComponentAnalysisDialog.this);
359                                 System.out.println("SETTING NAN VALUES");
360                                 rocketPanel.setCPAOA(Double.NaN);
361                                 rocketPanel.setCPTheta(Double.NaN);
362                                 rocketPanel.setCPMach(Double.NaN);
363                                 rocketPanel.setCPRoll(Double.NaN);
364                                 singletonDialog = null;
365                         }
366                 });
367                 
368
369                 panel.add(new ResizeLabel("Reference length: ", -1), 
370                                 "span, split, gapleft para, gapright rel");
371                 DoubleModel dm = new DoubleModel(conditions, "RefLength", UnitGroup.UNITS_LENGTH);
372                 UnitSelector sel = new UnitSelector(dm, true);
373                 sel.resizeFont(-1);
374                 panel.add(sel, "gapright para");
375                 
376                 panel.add(new ResizeLabel("Reference area: ", -1), "gapright rel");
377                 dm = new DoubleModel(conditions, "RefArea", UnitGroup.UNITS_AREA);
378                 sel = new UnitSelector(dm, true);
379                 sel.resizeFont(-1);
380                 panel.add(sel, "wrap");
381                 
382                 
383
384                 // Buttons
385                 JButton button;
386                 
387                 // TODO: LOW: printing
388 //              button = new JButton("Print");
389 //              button.addActionListener(new ActionListener() {
390 //                      public void actionPerformed(ActionEvent e) {
391 //                              try {
392 //                                      table.print();
393 //                              } catch (PrinterException e1) {
394 //                                      JOptionPane.showMessageDialog(ComponentAnalysisDialog.this, 
395 //                                                      "An error occurred while printing.", "Print error",
396 //                                                      JOptionPane.ERROR_MESSAGE);
397 //                              }
398 //                      }
399 //              });
400 //              panel.add(button,"tag ok");
401                 
402                 button = new JButton("Close");
403                 button.addActionListener(new ActionListener() {
404                         public void actionPerformed(ActionEvent e) {
405                                 ComponentAnalysisDialog.this.dispose();
406                         }
407                 });
408                 panel.add(button,"span, split, tag cancel");
409                 
410
411                 setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
412                 GUIUtil.installEscapeCloseOperation(this);
413                 pack();
414         }
415         
416         
417         
418         /**
419          * Updates the data in the table and fires a table data change event.
420          */
421         @Override
422         public void stateChanged(ChangeEvent e) {
423                 AerodynamicForces forces;
424                 WarningSet set = new WarningSet();
425                 conditions.setAOA(aoa.getValue());
426                 conditions.setTheta(theta.getValue());
427                 conditions.setMach(mach.getValue());
428                 conditions.setRollRate(roll.getValue());
429                 conditions.setReference(configuration);
430                 
431                 if (worstToggle.isSelected()) {
432                         calculator.getWorstCP(conditions, null);
433                         if (!MathUtil.equals(conditions.getTheta(), theta.getValue())) {
434                                 fakeChange = true;
435                                 theta.setValue(conditions.getTheta());  // Fires a stateChanged event
436                                 fakeChange = false;
437                                 return;
438                         }
439                 }
440                 
441                 Map<RocketComponent, AerodynamicForces> data = calculator.getForceAnalysis(conditions, set);
442                 
443                 cpData.clear();
444                 dragData.clear();
445                 rollData.clear();
446                 for (RocketComponent c: configuration) {
447                         forces = data.get(c);
448                         if (forces == null)
449                                 continue;
450                         if (forces.cp != null) {
451                                 cpData.add(forces);
452                         }
453                         if (!Double.isNaN(forces.CD)) {
454                                 dragData.add(forces);
455                         }
456                         if (c instanceof FinSet) {
457                                 rollData.add(forces);
458                         }
459                 }
460                 forces = data.get(configuration.getRocket());
461                 if (forces != null) {
462                         cpData.add(forces);
463                         dragData.add(forces);
464                         rollData.add(forces);
465                         totalCD = forces.CD;
466                 } else {
467                         totalCD = 0;
468                 }
469                 
470                 // Set warnings
471                 if (set.isEmpty()) {
472                         warningList.setListData(new String[] {
473                                         "<html><i><font color=\"gray\">No warnings.</font></i>"
474                         });
475                 } else {
476                         warningList.setListData(new Vector<Warning>(set));
477                 }
478                 
479                 cpTableModel.fireTableDataChanged();
480                 dragTableModel.fireTableDataChanged();
481                 rollTableModel.fireTableDataChanged();
482         }
483         
484         
485         private class CustomCellRenderer extends JLabel implements TableCellRenderer {
486                 private final Font normalFont;
487                 private final Font boldFont;
488                 
489                 public CustomCellRenderer() {
490                         super();
491                         normalFont = getFont();
492                         boldFont = normalFont.deriveFont(Font.BOLD);
493                 }
494                 @Override
495                 public Component getTableCellRendererComponent(JTable table, Object value, 
496                                 boolean isSelected, boolean hasFocus, int row, int column) {
497                         
498                         this.setText(value.toString());
499                         
500                         if ((row < 0) || (row >= cpData.size()))
501                                         return this;
502                         
503                         if (cpData.get(row).component instanceof Rocket) {
504                                 this.setFont(boldFont);
505                         } else {
506                                 this.setFont(normalFont);
507                         }
508                         return this;
509                 }
510         }
511         
512
513         
514         private class DragCellRenderer extends JLabel implements TableCellRenderer {
515                 private final Font normalFont;
516                 private final Font boldFont;
517                 
518                 private final float[] start = { 0.3333f, 0.2f, 1.0f };
519                 private final float[] end = { 0.0f, 0.8f, 1.0f };
520                 
521                 
522                 public DragCellRenderer(Color baseColor) {
523                         super();
524                         normalFont = getFont();
525                         boldFont = normalFont.deriveFont(Font.BOLD);
526                 }
527                 @Override
528                 public Component getTableCellRendererComponent(JTable table, Object value, 
529                                 boolean isSelected, boolean hasFocus, int row, int column) {
530                         
531                         if (value instanceof Double) {
532                                 
533                                 // A drag coefficient
534                                 double cd = (Double)value;
535                                 this.setText(String.format("%.2f (%.0f%%)", cd, 100*cd/totalCD));
536
537                                 float r = (float)(cd/1.5);
538                                 
539                                 float hue = MathUtil.clamp(0.3333f * (1-2.0f*r), 0, 0.3333f);
540                                 float sat = MathUtil.clamp(0.8f*r + 0.1f*(1-r), 0, 1);
541                                 float val = 1.0f;
542                                 
543                                 this.setBackground(Color.getHSBColor(hue, sat, val));
544                                 this.setOpaque(true);
545                                 this.setHorizontalAlignment(SwingConstants.CENTER);
546                                 
547                         } else {
548                                 
549                                 // Other
550                                 this.setText(value.toString());
551                                 this.setOpaque(false);
552                                 this.setHorizontalAlignment(SwingConstants.LEFT);
553                                 
554                         }
555                         
556                         if ((row < 0) || (row >= dragData.size()))
557                                         return this;
558                         
559                         if ((dragData.get(row).component instanceof Rocket) || (column == 4)){
560                                 this.setFont(boldFont);
561                         } else {
562                                 this.setFont(normalFont);
563                         }
564                         return this;
565                 }
566         }
567         
568         
569         /////////  Singleton implementation
570         
571         public static void showDialog(RocketPanel rocketpanel) {
572                 if (singletonDialog != null)
573                         singletonDialog.dispose();
574                 singletonDialog = new ComponentAnalysisDialog(rocketpanel);
575                 singletonDialog.setVisible(true);
576         }
577         
578         public static void hideDialog() {
579                 if (singletonDialog != null)
580                         singletonDialog.dispose();
581         }
582         
583 }