1 package net.sf.openrocket.gui.dialogs;
3 import static net.sf.openrocket.unit.Unit.NOUNIT2;
6 import java.awt.Component;
7 import java.awt.Dimension;
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;
16 import java.util.Vector;
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;
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;
62 public class ComponentAnalysisDialog extends JDialog implements ChangeListener {
64 private static ComponentAnalysisDialog singletonDialog = null;
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;
74 private final ColumnTableModel cpTableModel;
75 private final ColumnTableModel dragTableModel;
76 private final ColumnTableModel rollTableModel;
78 private final JList warningList;
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>();
87 public ComponentAnalysisDialog(final RocketPanel rocketPanel) {
88 super(SwingUtilities.getWindowAncestor(rocketPanel), "Component analysis");
92 JPanel panel = new JPanel(new MigLayout("fill","[][35lp::][fill][fill]"));
95 this.configuration = rocketPanel.getConfiguration();
96 this.calculator = rocketPanel.getCalculator().newInstance();
97 this.calculator.setConfiguration(configuration);
100 conditions = new FlightConditions(configuration);
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);
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() {
120 public void actionPerformed(ActionEvent e) {
124 slider.addChangeListener(new ChangeListener() {
126 public void stateChanged(ChangeEvent e) {
128 worstToggle.setSelected(false);
131 panel.add(worstToggle,"");
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");
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");
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");
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");
154 // Stage and motor selection:
156 panel.add(new JLabel("Active stages:"),"spanx, split, gapafter rel");
157 panel.add(new StageSelector(configuration),"gapafter paragraph");
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");
168 JTabbedPane tabbedPane = new JTabbedPane();
169 panel.add(tabbedPane, "spanx, growx, growy");
172 // Create the CP data table
173 cpTableModel = new ColumnTableModel(
175 new Column("Component") {
176 @Override public Object getValueAt(int row) {
177 RocketComponent c = cpData.get(row).component;
178 if (c instanceof Rocket) {
183 @Override public int getDefaultWidth() {
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);
193 new Column("Mass / " + UnitGroup.UNITS_MASS.getDefaultUnit().getUnit()) {
194 private Unit unit = UnitGroup.UNITS_MASS.getDefaultUnit();
196 public Object getValueAt(int row) {
197 return unit.toString(cpData.get(row).cg.weight);
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);
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);
213 @Override public int getRowCount() {
214 return cpData.size();
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());
224 table.setDefaultRenderer(Object.class, new CustomCellRenderer());
225 // table.setShowHorizontalLines(false);
226 // table.setShowVerticalLines(true);
228 JScrollPane scrollpane = new JScrollPane(table);
229 scrollpane.setPreferredSize(new Dimension(600,200));
231 tabbedPane.addTab("Stability", null, scrollpane, "Stability information");
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) {
245 @Override public int getDefaultWidth() {
249 new Column("<html>Pressure C<sub>D</sub>") {
250 @Override public Object getValueAt(int row) {
251 return dragData.get(row).pressureCD;
254 new Column("<html>Base C<sub>D</sub>") {
255 @Override public Object getValueAt(int row) {
256 return dragData.get(row).baseCD;
259 new Column("<html>Friction C<sub>D</sub>") {
260 @Override public Object getValueAt(int row) {
261 return dragData.get(row).frictionCD;
264 new Column("<html>Total C<sub>D</sub>") {
265 @Override public Object getValueAt(int row) {
266 return dragData.get(row).CD;
270 @Override public int getRowCount() {
271 return dragData.size();
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());
282 table.setDefaultRenderer(Object.class, new DragCellRenderer(new Color(0.5f,1.0f,0.5f)));
283 // table.setShowHorizontalLines(false);
284 // table.setShowVerticalLines(true);
286 scrollpane = new JScrollPane(table);
287 scrollpane.setPreferredSize(new Dimension(600,200));
289 tabbedPane.addTab("Drag characteristics", null, scrollpane, "Drag characteristics");
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) {
305 new Column("Roll forcing coefficient") {
306 @Override public Object getValueAt(int row) {
307 return rollData.get(row).CrollForce;
310 new Column("Roll damping coefficient") {
311 @Override public Object getValueAt(int row) {
312 return rollData.get(row).CrollDamp;
315 new Column("<html>Total C<sub>l</sub>") {
316 @Override public Object getValueAt(int row) {
317 return rollData.get(row).Croll;
321 @Override public int getRowCount() {
322 return rollData.size();
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());
333 scrollpane = new JScrollPane(table);
334 scrollpane.setPreferredSize(new Dimension(600,200));
336 tabbedPane.addTab("Roll dynamics", null, scrollpane, "Roll dynamics");
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);
353 // Remove listeners when closing window
354 this.addWindowListener(new WindowAdapter() {
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;
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);
378 panel.add(sel, "gapright para");
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);
384 panel.add(sel, "wrap");
391 // TODO: LOW: printing
392 // button = new JButton("Print");
393 // button.addActionListener(new ActionListener() {
394 // public void actionPerformed(ActionEvent e) {
397 // } catch (PrinterException e1) {
398 // JOptionPane.showMessageDialog(ComponentAnalysisDialog.this,
399 // "An error occurred while printing.", "Print error",
400 // JOptionPane.ERROR_MESSAGE);
404 // panel.add(button,"tag ok");
406 button = new JButton("Close");
407 button.addActionListener(new ActionListener() {
408 public void actionPerformed(ActionEvent e) {
409 ComponentAnalysisDialog.this.dispose();
412 panel.add(button,"span, split, tag cancel");
415 this.setLocationByPlatform(true);
416 setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
417 GUIUtil.installEscapeCloseOperation(this);
424 * Updates the data in the table and fires a table data change event.
427 public void stateChanged(ChangeEvent e) {
428 AerodynamicForces forces;
429 WarningSet set = new WarningSet();
430 conditions.setAOA(aoa.getValue());
431 conditions.setTheta(theta.getValue());
432 conditions.setMach(mach.getValue());
433 conditions.setRollRate(roll.getValue());
434 conditions.setReference(configuration);
436 if (worstToggle.isSelected()) {
437 calculator.getWorstCP(conditions, null);
438 if (!MathUtil.equals(conditions.getTheta(), theta.getValue())) {
440 theta.setValue(conditions.getTheta()); // Fires a stateChanged event
446 Map<RocketComponent, AerodynamicForces> data = calculator.getForceAnalysis(conditions, set);
451 for (RocketComponent c: configuration) {
452 forces = data.get(c);
455 if (forces.cp != null) {
458 if (!Double.isNaN(forces.CD)) {
459 dragData.add(forces);
461 if (c instanceof FinSet) {
462 rollData.add(forces);
465 forces = data.get(configuration.getRocket());
466 if (forces != null) {
468 dragData.add(forces);
469 rollData.add(forces);
477 warningList.setListData(new String[] {
478 "<html><i><font color=\"gray\">No warnings.</font></i>"
481 warningList.setListData(new Vector<Warning>(set));
484 cpTableModel.fireTableDataChanged();
485 dragTableModel.fireTableDataChanged();
486 rollTableModel.fireTableDataChanged();
490 private class CustomCellRenderer extends JLabel implements TableCellRenderer {
491 private final Font normalFont;
492 private final Font boldFont;
494 public CustomCellRenderer() {
496 normalFont = getFont();
497 boldFont = normalFont.deriveFont(Font.BOLD);
500 public Component getTableCellRendererComponent(JTable table, Object value,
501 boolean isSelected, boolean hasFocus, int row, int column) {
503 this.setText(value.toString());
505 if ((row < 0) || (row >= cpData.size()))
508 if (cpData.get(row).component instanceof Rocket) {
509 this.setFont(boldFont);
511 this.setFont(normalFont);
519 private class DragCellRenderer extends JLabel implements TableCellRenderer {
520 private final Font normalFont;
521 private final Font boldFont;
523 private final float[] start = { 0.3333f, 0.2f, 1.0f };
524 private final float[] end = { 0.0f, 0.8f, 1.0f };
527 public DragCellRenderer(Color baseColor) {
529 normalFont = getFont();
530 boldFont = normalFont.deriveFont(Font.BOLD);
533 public Component getTableCellRendererComponent(JTable table, Object value,
534 boolean isSelected, boolean hasFocus, int row, int column) {
536 if (value instanceof Double) {
538 // A drag coefficient
539 double cd = (Double)value;
540 this.setText(String.format("%.2f (%.0f%%)", cd, 100*cd/totalCD));
542 float r = (float)(cd/1.5);
544 float hue = MathUtil.clamp(0.3333f * (1-2.0f*r), 0, 0.3333f);
545 float sat = MathUtil.clamp(0.8f*r + 0.1f*(1-r), 0, 1);
548 this.setBackground(Color.getHSBColor(hue, sat, val));
549 this.setOpaque(true);
550 this.setHorizontalAlignment(SwingConstants.CENTER);
555 this.setText(value.toString());
556 this.setOpaque(false);
557 this.setHorizontalAlignment(SwingConstants.LEFT);
561 if ((row < 0) || (row >= dragData.size()))
564 if ((dragData.get(row).component instanceof Rocket) || (column == 4)){
565 this.setFont(boldFont);
567 this.setFont(normalFont);
574 ///////// Singleton implementation
576 public static void showDialog(RocketPanel rocketpanel) {
577 if (singletonDialog != null)
578 singletonDialog.dispose();
579 singletonDialog = new ComponentAnalysisDialog(rocketpanel);
580 singletonDialog.setVisible(true);
583 public static void hideDialog() {
584 if (singletonDialog != null)
585 singletonDialog.dispose();