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