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