1 package net.sf.openrocket.gui;
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.scalefigure.RocketPanel;
48 import net.sf.openrocket.rocketcomponent.Configuration;
49 import net.sf.openrocket.rocketcomponent.FinSet;
50 import net.sf.openrocket.rocketcomponent.Rocket;
51 import net.sf.openrocket.rocketcomponent.RocketComponent;
52 import net.sf.openrocket.unit.Unit;
53 import net.sf.openrocket.unit.UnitGroup;
54 import net.sf.openrocket.util.GUIUtil;
55 import net.sf.openrocket.util.MathUtil;
56 import net.sf.openrocket.util.Prefs;
58 public class ComponentAnalysisDialog extends JDialog implements ChangeListener {
60 private static ComponentAnalysisDialog singletonDialog = null;
63 private final FlightConditions conditions;
64 private final Configuration configuration;
65 private final DoubleModel theta, aoa, mach, roll;
66 private final JToggleButton worstToggle;
67 private boolean fakeChange = false;
68 private AerodynamicCalculator calculator;
70 private final ColumnTableModel cpTableModel;
71 private final ColumnTableModel dragTableModel;
72 private final ColumnTableModel rollTableModel;
74 private final JList warningList;
77 private final List<AerodynamicForces> cpData = new ArrayList<AerodynamicForces>();
78 private final List<AerodynamicForces> dragData = new ArrayList<AerodynamicForces>();
79 private double totalCD = 0;
80 private final List<AerodynamicForces> rollData = new ArrayList<AerodynamicForces>();
83 public ComponentAnalysisDialog(final RocketPanel rocketPanel) {
84 super(SwingUtilities.getWindowAncestor(rocketPanel), "Component analysis");
88 JPanel panel = new JPanel(new MigLayout("fill","[][35lp::][fill][fill]"));
91 this.configuration = rocketPanel.getConfiguration();
92 this.calculator = rocketPanel.getCalculator().newInstance();
93 this.calculator.setConfiguration(configuration);
96 conditions = new FlightConditions(configuration);
98 rocketPanel.setCPAOA(0);
99 aoa = new DoubleModel(rocketPanel, "CPAOA", UnitGroup.UNITS_ANGLE, 0, Math.PI);
100 rocketPanel.setCPMach(Prefs.getDefaultMach());
101 mach = new DoubleModel(rocketPanel, "CPMach", UnitGroup.UNITS_COEFFICIENT, 0);
102 rocketPanel.setCPTheta(rocketPanel.getFigure().getRotation());
103 theta = new DoubleModel(rocketPanel, "CPTheta", UnitGroup.UNITS_ANGLE, 0, 2*Math.PI);
104 rocketPanel.setCPRoll(0);
105 roll = new DoubleModel(rocketPanel, "CPRoll", UnitGroup.UNITS_ROLL);
108 panel.add(new JLabel("Wind direction:"),"width 100lp!");
109 panel.add(new UnitSelector(theta,true),"width 50lp!");
110 BasicSlider slider = new BasicSlider(theta.getSliderModel(0, 2*Math.PI));
111 panel.add(slider,"growx, split 2");
112 worstToggle = new JToggleButton("Worst");
113 worstToggle.setSelected(true);
114 worstToggle.addActionListener(new ActionListener() {
116 public void actionPerformed(ActionEvent e) {
120 slider.addChangeListener(new ChangeListener() {
122 public void stateChanged(ChangeEvent e) {
124 worstToggle.setSelected(false);
127 panel.add(worstToggle,"");
130 warningList = new JList();
131 JScrollPane scrollPane = new JScrollPane(warningList);
132 scrollPane.setBorder(BorderFactory.createTitledBorder("Warnings:"));
133 panel.add(scrollPane,"gap paragraph, spany 4, width 300lp!, growy 1, height :100lp:, wrap");
136 panel.add(new JLabel("Angle of attack:"),"width 100lp!");
137 panel.add(new UnitSelector(aoa,true),"width 50lp!");
138 panel.add(new BasicSlider(aoa.getSliderModel(0, Math.PI)),"growx, wrap");
140 panel.add(new JLabel("Mach number:"),"width 100lp!");
141 panel.add(new UnitSelector(mach,true),"width 50lp!");
142 panel.add(new BasicSlider(mach.getSliderModel(0, 3)),"growx, wrap");
144 panel.add(new JLabel("Roll rate:"), "width 100lp!");
145 panel.add(new UnitSelector(roll,true),"width 50lp!");
146 panel.add(new BasicSlider(roll.getSliderModel(-20*2*Math.PI, 20*2*Math.PI)),
147 "growx, wrap paragraph");
150 // Stage and motor selection:
152 panel.add(new JLabel("Active stages:"),"spanx, split, gapafter rel");
153 panel.add(new StageSelector(configuration),"gapafter paragraph");
155 JLabel label = new JLabel("Motor configuration:");
156 label.setHorizontalAlignment(JLabel.RIGHT);
157 panel.add(label,"growx, right");
158 panel.add(new JComboBox(new MotorConfigurationModel(configuration)),"wrap");
164 JTabbedPane tabbedPane = new JTabbedPane();
165 panel.add(tabbedPane, "spanx, growx, growy");
168 // Create the CP data table
169 cpTableModel = new ColumnTableModel(
171 new Column("Component") {
172 @Override public Object getValueAt(int row) {
173 RocketComponent c = cpData.get(row).component;
174 if (c instanceof Rocket) {
179 @Override public int getDefaultWidth() {
183 new Column("CG / " + UnitGroup.UNITS_LENGTH.getDefaultUnit().getUnit()) {
184 private Unit unit = UnitGroup.UNITS_LENGTH.getDefaultUnit();
185 @Override public Object getValueAt(int row) {
186 return unit.toString(cpData.get(row).cg.x);
189 new Column("Mass / " + UnitGroup.UNITS_MASS.getDefaultUnit().getUnit()) {
190 private Unit unit = UnitGroup.UNITS_MASS.getDefaultUnit();
192 public Object getValueAt(int row) {
193 return unit.toString(cpData.get(row).cg.weight);
196 new Column("CP / " + UnitGroup.UNITS_LENGTH.getDefaultUnit().getUnit()) {
197 private Unit unit = UnitGroup.UNITS_LENGTH.getDefaultUnit();
198 @Override public Object getValueAt(int row) {
199 return unit.toString(cpData.get(row).cp.x);
202 new Column("<html>C<sub>N<sub>\u03b1</sub></sub>") {
203 @Override public Object getValueAt(int row) {
204 return NOUNIT2.toString(cpData.get(row).cp.weight);
209 @Override public int getRowCount() {
210 return cpData.size();
214 table = new JTable(cpTableModel);
215 table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
216 table.setSelectionBackground(Color.LIGHT_GRAY);
217 table.setSelectionForeground(Color.BLACK);
218 cpTableModel.setColumnWidths(table.getColumnModel());
220 table.setDefaultRenderer(Object.class, new CustomCellRenderer());
221 // table.setShowHorizontalLines(false);
222 // table.setShowVerticalLines(true);
224 JScrollPane scrollpane = new JScrollPane(table);
225 scrollpane.setPreferredSize(new Dimension(600,200));
227 tabbedPane.addTab("Stability", null, scrollpane, "Stability information");
231 // Create the drag data table
232 dragTableModel = new ColumnTableModel(
233 new Column("Component") {
234 @Override public Object getValueAt(int row) {
235 RocketComponent c = dragData.get(row).component;
236 if (c instanceof Rocket) {
241 @Override public int getDefaultWidth() {
245 new Column("<html>Pressure C<sub>D</sub>") {
246 @Override public Object getValueAt(int row) {
247 return dragData.get(row).pressureCD;
250 new Column("<html>Base C<sub>D</sub>") {
251 @Override public Object getValueAt(int row) {
252 return dragData.get(row).baseCD;
255 new Column("<html>Friction C<sub>D</sub>") {
256 @Override public Object getValueAt(int row) {
257 return dragData.get(row).frictionCD;
260 new Column("<html>Total C<sub>D</sub>") {
261 @Override public Object getValueAt(int row) {
262 return dragData.get(row).CD;
266 @Override public int getRowCount() {
267 return dragData.size();
272 table = new JTable(dragTableModel);
273 table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
274 table.setSelectionBackground(Color.LIGHT_GRAY);
275 table.setSelectionForeground(Color.BLACK);
276 dragTableModel.setColumnWidths(table.getColumnModel());
278 table.setDefaultRenderer(Object.class, new DragCellRenderer(new Color(0.5f,1.0f,0.5f)));
279 // table.setShowHorizontalLines(false);
280 // table.setShowVerticalLines(true);
282 scrollpane = new JScrollPane(table);
283 scrollpane.setPreferredSize(new Dimension(600,200));
285 tabbedPane.addTab("Drag characteristics", null, scrollpane, "Drag characteristics");
290 // Create the roll data table
291 rollTableModel = new ColumnTableModel(
292 new Column("Component") {
293 @Override public Object getValueAt(int row) {
294 RocketComponent c = rollData.get(row).component;
295 if (c instanceof Rocket) {
301 new Column("Roll forcing coefficient") {
302 @Override public Object getValueAt(int row) {
303 return rollData.get(row).CrollForce;
306 new Column("Roll damping coefficient") {
307 @Override public Object getValueAt(int row) {
308 return rollData.get(row).CrollDamp;
311 new Column("<html>Total C<sub>l</sub>") {
312 @Override public Object getValueAt(int row) {
313 return rollData.get(row).Croll;
317 @Override public int getRowCount() {
318 return rollData.size();
323 table = new JTable(rollTableModel);
324 table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
325 table.setSelectionBackground(Color.LIGHT_GRAY);
326 table.setSelectionForeground(Color.BLACK);
327 rollTableModel.setColumnWidths(table.getColumnModel());
329 scrollpane = new JScrollPane(table);
330 scrollpane.setPreferredSize(new Dimension(600,200));
332 tabbedPane.addTab("Roll dynamics", null, scrollpane, "Roll dynamics");
339 // Add the data updater to listen to changes in aoa and theta
340 mach.addChangeListener(this);
341 theta.addChangeListener(this);
342 aoa.addChangeListener(this);
343 roll.addChangeListener(this);
344 configuration.addChangeListener(this);
345 this.stateChanged(null);
349 // Remove listeners when closing window
350 this.addWindowListener(new WindowAdapter() {
352 public void windowClosed(WindowEvent e) {
353 System.out.println("Closing method called: "+this);
354 theta.removeChangeListener(ComponentAnalysisDialog.this);
355 aoa.removeChangeListener(ComponentAnalysisDialog.this);
356 mach.removeChangeListener(ComponentAnalysisDialog.this);
357 roll.removeChangeListener(ComponentAnalysisDialog.this);
358 configuration.removeChangeListener(ComponentAnalysisDialog.this);
359 System.out.println("SETTING NAN VALUES");
360 rocketPanel.setCPAOA(Double.NaN);
361 rocketPanel.setCPTheta(Double.NaN);
362 rocketPanel.setCPMach(Double.NaN);
363 rocketPanel.setCPRoll(Double.NaN);
364 singletonDialog = null;
369 panel.add(new ResizeLabel("Reference length: ", -1),
370 "span, split, gapleft para, gapright rel");
371 DoubleModel dm = new DoubleModel(conditions, "RefLength", UnitGroup.UNITS_LENGTH);
372 UnitSelector sel = new UnitSelector(dm, true);
374 panel.add(sel, "gapright para");
376 panel.add(new ResizeLabel("Reference area: ", -1), "gapright rel");
377 dm = new DoubleModel(conditions, "RefArea", UnitGroup.UNITS_AREA);
378 sel = new UnitSelector(dm, true);
380 panel.add(sel, "wrap");
387 // TODO: LOW: printing
388 // button = new JButton("Print");
389 // button.addActionListener(new ActionListener() {
390 // public void actionPerformed(ActionEvent e) {
393 // } catch (PrinterException e1) {
394 // JOptionPane.showMessageDialog(ComponentAnalysisDialog.this,
395 // "An error occurred while printing.", "Print error",
396 // JOptionPane.ERROR_MESSAGE);
400 // panel.add(button,"tag ok");
402 button = new JButton("Close");
403 button.addActionListener(new ActionListener() {
404 public void actionPerformed(ActionEvent e) {
405 ComponentAnalysisDialog.this.dispose();
408 panel.add(button,"span, split, tag cancel");
411 setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
412 GUIUtil.installEscapeCloseOperation(this);
419 * Updates the data in the table and fires a table data change event.
422 public void stateChanged(ChangeEvent e) {
423 AerodynamicForces forces;
424 WarningSet set = new WarningSet();
425 conditions.setAOA(aoa.getValue());
426 conditions.setTheta(theta.getValue());
427 conditions.setMach(mach.getValue());
428 conditions.setRollRate(roll.getValue());
429 conditions.setReference(configuration);
431 if (worstToggle.isSelected()) {
432 calculator.getWorstCP(conditions, null);
433 if (!MathUtil.equals(conditions.getTheta(), theta.getValue())) {
435 theta.setValue(conditions.getTheta()); // Fires a stateChanged event
441 Map<RocketComponent, AerodynamicForces> data = calculator.getForceAnalysis(conditions, set);
446 for (RocketComponent c: configuration) {
447 forces = data.get(c);
450 if (forces.cp != null) {
453 if (!Double.isNaN(forces.CD)) {
454 dragData.add(forces);
456 if (c instanceof FinSet) {
457 rollData.add(forces);
460 forces = data.get(configuration.getRocket());
461 if (forces != null) {
463 dragData.add(forces);
464 rollData.add(forces);
472 warningList.setListData(new String[] {
473 "<html><i><font color=\"gray\">No warnings.</font></i>"
476 warningList.setListData(new Vector<Warning>(set));
479 cpTableModel.fireTableDataChanged();
480 dragTableModel.fireTableDataChanged();
481 rollTableModel.fireTableDataChanged();
485 private class CustomCellRenderer extends JLabel implements TableCellRenderer {
486 private final Font normalFont;
487 private final Font boldFont;
489 public CustomCellRenderer() {
491 normalFont = getFont();
492 boldFont = normalFont.deriveFont(Font.BOLD);
495 public Component getTableCellRendererComponent(JTable table, Object value,
496 boolean isSelected, boolean hasFocus, int row, int column) {
498 this.setText(value.toString());
500 if ((row < 0) || (row >= cpData.size()))
503 if (cpData.get(row).component instanceof Rocket) {
504 this.setFont(boldFont);
506 this.setFont(normalFont);
514 private class DragCellRenderer extends JLabel implements TableCellRenderer {
515 private final Font normalFont;
516 private final Font boldFont;
518 private final float[] start = { 0.3333f, 0.2f, 1.0f };
519 private final float[] end = { 0.0f, 0.8f, 1.0f };
522 public DragCellRenderer(Color baseColor) {
524 normalFont = getFont();
525 boldFont = normalFont.deriveFont(Font.BOLD);
528 public Component getTableCellRendererComponent(JTable table, Object value,
529 boolean isSelected, boolean hasFocus, int row, int column) {
531 if (value instanceof Double) {
533 // A drag coefficient
534 double cd = (Double)value;
535 this.setText(String.format("%.2f (%.0f%%)", cd, 100*cd/totalCD));
537 float r = (float)(cd/1.5);
539 float hue = MathUtil.clamp(0.3333f * (1-2.0f*r), 0, 0.3333f);
540 float sat = MathUtil.clamp(0.8f*r + 0.1f*(1-r), 0, 1);
543 this.setBackground(Color.getHSBColor(hue, sat, val));
544 this.setOpaque(true);
545 this.setHorizontalAlignment(SwingConstants.CENTER);
550 this.setText(value.toString());
551 this.setOpaque(false);
552 this.setHorizontalAlignment(SwingConstants.LEFT);
556 if ((row < 0) || (row >= dragData.size()))
559 if ((dragData.get(row).component instanceof Rocket) || (column == 4)){
560 this.setFont(boldFont);
562 this.setFont(normalFont);
569 ///////// Singleton implementation
571 public static void showDialog(RocketPanel rocketpanel) {
572 if (singletonDialog != null)
573 singletonDialog.dispose();
574 singletonDialog = new ComponentAnalysisDialog(rocketpanel);
575 singletonDialog.setVisible(true);
578 public static void hideDialog() {
579 if (singletonDialog != null)
580 singletonDialog.dispose();