1 package net.sf.openrocket.gui.dialogs;
3 import static net.sf.openrocket.unit.Unit.NOUNIT2;
4 import static net.sf.openrocket.util.Chars.ALPHA;
7 import java.awt.Component;
8 import java.awt.Dimension;
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;
17 import java.util.Vector;
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;
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;
63 public class ComponentAnalysisDialog extends JDialog implements ChangeListener {
65 private static ComponentAnalysisDialog singletonDialog = null;
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;
75 private final ColumnTableModel cpTableModel;
76 private final ColumnTableModel dragTableModel;
77 private final ColumnTableModel rollTableModel;
79 private final JList warningList;
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>();
88 public ComponentAnalysisDialog(final RocketPanel rocketPanel) {
89 super(SwingUtilities.getWindowAncestor(rocketPanel), "Component analysis");
93 JPanel panel = new JPanel(new MigLayout("fill","[][35lp::][fill][fill]"));
96 this.configuration = rocketPanel.getConfiguration();
97 this.calculator = rocketPanel.getCalculator().newInstance();
98 this.calculator.setConfiguration(configuration);
101 conditions = new FlightConditions(configuration);
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);
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() {
121 public void actionPerformed(ActionEvent e) {
125 slider.addChangeListener(new ChangeListener() {
127 public void stateChanged(ChangeEvent e) {
129 worstToggle.setSelected(false);
132 panel.add(worstToggle,"");
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");
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");
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");
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");
155 // Stage and motor selection:
157 panel.add(new JLabel("Active stages:"),"spanx, split, gapafter rel");
158 panel.add(new StageSelector(configuration),"gapafter paragraph");
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");
169 JTabbedPane tabbedPane = new JTabbedPane();
170 panel.add(tabbedPane, "spanx, growx, growy");
173 // Create the CP data table
174 cpTableModel = new ColumnTableModel(
176 new Column("Component") {
177 @Override public Object getValueAt(int row) {
178 RocketComponent c = cpData.get(row).component;
179 if (c instanceof Rocket) {
184 @Override public int getDefaultWidth() {
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);
194 new Column("Mass / " + UnitGroup.UNITS_MASS.getDefaultUnit().getUnit()) {
195 private Unit unit = UnitGroup.UNITS_MASS.getDefaultUnit();
197 public Object getValueAt(int row) {
198 return unit.toString(cpData.get(row).cg.weight);
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);
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);
214 @Override public int getRowCount() {
215 return cpData.size();
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());
225 table.setDefaultRenderer(Object.class, new CustomCellRenderer());
226 // table.setShowHorizontalLines(false);
227 // table.setShowVerticalLines(true);
229 JScrollPane scrollpane = new JScrollPane(table);
230 scrollpane.setPreferredSize(new Dimension(600,200));
232 tabbedPane.addTab("Stability", null, scrollpane, "Stability information");
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) {
246 @Override public int getDefaultWidth() {
250 new Column("<html>Pressure C<sub>D</sub>") {
251 @Override public Object getValueAt(int row) {
252 return dragData.get(row).pressureCD;
255 new Column("<html>Base C<sub>D</sub>") {
256 @Override public Object getValueAt(int row) {
257 return dragData.get(row).baseCD;
260 new Column("<html>Friction C<sub>D</sub>") {
261 @Override public Object getValueAt(int row) {
262 return dragData.get(row).frictionCD;
265 new Column("<html>Total C<sub>D</sub>") {
266 @Override public Object getValueAt(int row) {
267 return dragData.get(row).CD;
271 @Override public int getRowCount() {
272 return dragData.size();
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());
283 table.setDefaultRenderer(Object.class, new DragCellRenderer(new Color(0.5f,1.0f,0.5f)));
284 // table.setShowHorizontalLines(false);
285 // table.setShowVerticalLines(true);
287 scrollpane = new JScrollPane(table);
288 scrollpane.setPreferredSize(new Dimension(600,200));
290 tabbedPane.addTab("Drag characteristics", null, scrollpane, "Drag characteristics");
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) {
306 new Column("Roll forcing coefficient") {
307 @Override public Object getValueAt(int row) {
308 return rollData.get(row).CrollForce;
311 new Column("Roll damping coefficient") {
312 @Override public Object getValueAt(int row) {
313 return rollData.get(row).CrollDamp;
316 new Column("<html>Total C<sub>l</sub>") {
317 @Override public Object getValueAt(int row) {
318 return rollData.get(row).Croll;
322 @Override public int getRowCount() {
323 return rollData.size();
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());
334 scrollpane = new JScrollPane(table);
335 scrollpane.setPreferredSize(new Dimension(600,200));
337 tabbedPane.addTab("Roll dynamics", null, scrollpane, "Roll dynamics");
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);
354 // Remove listeners when closing window
355 this.addWindowListener(new WindowAdapter() {
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;
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);
379 panel.add(sel, "gapright para");
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);
385 panel.add(sel, "wrap");
392 // TODO: LOW: printing
393 // button = new JButton("Print");
394 // button.addActionListener(new ActionListener() {
395 // public void actionPerformed(ActionEvent e) {
398 // } catch (PrinterException e1) {
399 // JOptionPane.showMessageDialog(ComponentAnalysisDialog.this,
400 // "An error occurred while printing.", "Print error",
401 // JOptionPane.ERROR_MESSAGE);
405 // panel.add(button,"tag ok");
407 button = new JButton("Close");
408 button.addActionListener(new ActionListener() {
409 public void actionPerformed(ActionEvent e) {
410 ComponentAnalysisDialog.this.dispose();
413 panel.add(button,"span, split, tag cancel");
416 this.setLocationByPlatform(true);
417 setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
420 GUIUtil.setDisposableDialogOptions(this, null);
426 * Updates the data in the table and fires a table data change event.
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);
438 if (worstToggle.isSelected()) {
439 calculator.getWorstCP(conditions, null);
440 if (!MathUtil.equals(conditions.getTheta(), theta.getValue())) {
442 theta.setValue(conditions.getTheta()); // Fires a stateChanged event
448 Map<RocketComponent, AerodynamicForces> data = calculator.getForceAnalysis(conditions, set);
453 for (RocketComponent c: configuration) {
454 forces = data.get(c);
457 if (forces.cp != null) {
460 if (!Double.isNaN(forces.CD)) {
461 dragData.add(forces);
463 if (c instanceof FinSet) {
464 rollData.add(forces);
467 forces = data.get(configuration.getRocket());
468 if (forces != null) {
470 dragData.add(forces);
471 rollData.add(forces);
479 warningList.setListData(new String[] {
480 "<html><i><font color=\"gray\">No warnings.</font></i>"
483 warningList.setListData(new Vector<Warning>(set));
486 cpTableModel.fireTableDataChanged();
487 dragTableModel.fireTableDataChanged();
488 rollTableModel.fireTableDataChanged();
492 private class CustomCellRenderer extends JLabel implements TableCellRenderer {
493 private final Font normalFont;
494 private final Font boldFont;
496 public CustomCellRenderer() {
498 normalFont = getFont();
499 boldFont = normalFont.deriveFont(Font.BOLD);
502 public Component getTableCellRendererComponent(JTable table, Object value,
503 boolean isSelected, boolean hasFocus, int row, int column) {
505 this.setText(value.toString());
507 if ((row < 0) || (row >= cpData.size()))
510 if (cpData.get(row).component instanceof Rocket) {
511 this.setFont(boldFont);
513 this.setFont(normalFont);
521 private class DragCellRenderer extends JLabel implements TableCellRenderer {
522 private final Font normalFont;
523 private final Font boldFont;
525 private final float[] start = { 0.3333f, 0.2f, 1.0f };
526 private final float[] end = { 0.0f, 0.8f, 1.0f };
529 public DragCellRenderer(Color baseColor) {
531 normalFont = getFont();
532 boldFont = normalFont.deriveFont(Font.BOLD);
535 public Component getTableCellRendererComponent(JTable table, Object value,
536 boolean isSelected, boolean hasFocus, int row, int column) {
538 if (value instanceof Double) {
540 // A drag coefficient
541 double cd = (Double)value;
542 this.setText(String.format("%.2f (%.0f%%)", cd, 100*cd/totalCD));
544 float r = (float)(cd/1.5);
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);
550 this.setBackground(Color.getHSBColor(hue, sat, val));
551 this.setOpaque(true);
552 this.setHorizontalAlignment(SwingConstants.CENTER);
557 this.setText(value.toString());
558 this.setOpaque(false);
559 this.setHorizontalAlignment(SwingConstants.LEFT);
563 if ((row < 0) || (row >= dragData.size()))
566 if ((dragData.get(row).component instanceof Rocket) || (column == 4)){
567 this.setFont(boldFont);
569 this.setFont(normalFont);
576 ///////// Singleton implementation
578 public static void showDialog(RocketPanel rocketpanel) {
579 if (singletonDialog != null)
580 singletonDialog.dispose();
581 singletonDialog = new ComponentAnalysisDialog(rocketpanel);
582 singletonDialog.setVisible(true);
585 public static void hideDialog() {
586 if (singletonDialog != null)
587 singletonDialog.dispose();