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.gui.util.GUIUtil;
54 import net.sf.openrocket.l10n.Translator;
55 import net.sf.openrocket.masscalc.BasicMassCalculator;
56 import net.sf.openrocket.masscalc.MassCalculator;
57 import net.sf.openrocket.masscalc.MassCalculator.MassCalcType;
58 import net.sf.openrocket.rocketcomponent.Configuration;
59 import net.sf.openrocket.rocketcomponent.FinSet;
60 import net.sf.openrocket.rocketcomponent.Rocket;
61 import net.sf.openrocket.rocketcomponent.RocketComponent;
62 import net.sf.openrocket.startup.Application;
63 import net.sf.openrocket.unit.Unit;
64 import net.sf.openrocket.unit.UnitGroup;
65 import net.sf.openrocket.util.Coordinate;
66 import net.sf.openrocket.util.MathUtil;
68 public class ComponentAnalysisDialog extends JDialog implements ChangeListener {
70 private static ComponentAnalysisDialog singletonDialog = null;
71 private static final Translator trans = Application.getTranslator();
74 private final FlightConditions conditions;
75 private final Configuration configuration;
76 private final DoubleModel theta, aoa, mach, roll;
77 private final JToggleButton worstToggle;
78 private boolean fakeChange = false;
79 private AerodynamicCalculator aerodynamicCalculator;
80 private final MassCalculator massCalculator = new BasicMassCalculator();
82 private final ColumnTableModel cpTableModel;
83 private final ColumnTableModel dragTableModel;
84 private final ColumnTableModel rollTableModel;
86 private final JList warningList;
89 private final List<AerodynamicForces> cpData = new ArrayList<AerodynamicForces>();
90 private final List<Coordinate> cgData = new ArrayList<Coordinate>();
91 private final List<AerodynamicForces> dragData = new ArrayList<AerodynamicForces>();
92 private double totalCD = 0;
93 private final List<AerodynamicForces> rollData = new ArrayList<AerodynamicForces>();
96 public ComponentAnalysisDialog(final RocketPanel rocketPanel) {
97 ////Component analysis
98 super(SwingUtilities.getWindowAncestor(rocketPanel),
99 trans.get("componentanalysisdlg.componentanalysis"));
103 JPanel panel = new JPanel(new MigLayout("fill", "[][35lp::][fill][fill]"));
106 this.configuration = rocketPanel.getConfiguration();
107 this.aerodynamicCalculator = rocketPanel.getAerodynamicCalculator().newInstance();
110 conditions = new FlightConditions(configuration);
112 rocketPanel.setCPAOA(0);
113 aoa = new DoubleModel(rocketPanel, "CPAOA", UnitGroup.UNITS_ANGLE, 0, Math.PI);
114 rocketPanel.setCPMach(Application.getPreferences().getDefaultMach());
115 mach = new DoubleModel(rocketPanel, "CPMach", UnitGroup.UNITS_COEFFICIENT, 0);
116 rocketPanel.setCPTheta(rocketPanel.getFigure().getRotation());
117 theta = new DoubleModel(rocketPanel, "CPTheta", UnitGroup.UNITS_ANGLE, 0, 2 * Math.PI);
118 rocketPanel.setCPRoll(0);
119 roll = new DoubleModel(rocketPanel, "CPRoll", UnitGroup.UNITS_ROLL);
122 panel.add(new JLabel(trans.get("componentanalysisdlg.lbl.winddir")), "width 100lp!");
123 panel.add(new UnitSelector(theta, true), "width 50lp!");
124 BasicSlider slider = new BasicSlider(theta.getSliderModel(0, 2 * Math.PI));
125 panel.add(slider, "growx, split 2");
127 worstToggle = new JToggleButton(trans.get("componentanalysisdlg.ToggleBut.worst"));
128 worstToggle.setSelected(true);
129 worstToggle.addActionListener(new ActionListener() {
131 public void actionPerformed(ActionEvent e) {
135 slider.addChangeListener(new ChangeListener() {
137 public void stateChanged(ChangeEvent e) {
139 worstToggle.setSelected(false);
142 panel.add(worstToggle, "");
145 warningList = new JList();
146 JScrollPane scrollPane = new JScrollPane(warningList);
148 scrollPane.setBorder(BorderFactory.createTitledBorder(trans.get("componentanalysisdlg.TitledBorder.warnings")));
149 panel.add(scrollPane, "gap paragraph, spany 4, width 300lp!, growy 1, height :100lp:, wrap");
152 panel.add(new JLabel(trans.get("componentanalysisdlg.lbl.angleofattack")), "width 100lp!");
153 panel.add(new UnitSelector(aoa, true), "width 50lp!");
154 panel.add(new BasicSlider(aoa.getSliderModel(0, Math.PI)), "growx, wrap");
157 panel.add(new JLabel(trans.get("componentanalysisdlg.lbl.machnumber")), "width 100lp!");
158 panel.add(new UnitSelector(mach, true), "width 50lp!");
159 panel.add(new BasicSlider(mach.getSliderModel(0, 3)), "growx, wrap");
162 panel.add(new JLabel(trans.get("componentanalysisdlg.lbl.rollrate")), "width 100lp!");
163 panel.add(new UnitSelector(roll, true), "width 50lp!");
164 panel.add(new BasicSlider(roll.getSliderModel(-20 * 2 * Math.PI, 20 * 2 * Math.PI)),
165 "growx, wrap paragraph");
168 // Stage and motor selection:
170 panel.add(new JLabel(trans.get("componentanalysisdlg.lbl.activestages")), "spanx, split, gapafter rel");
171 panel.add(new StageSelector(configuration), "gapafter paragraph");
173 //// Motor configuration:
174 JLabel label = new JLabel(trans.get("componentanalysisdlg.lbl.motorconf"));
175 label.setHorizontalAlignment(JLabel.RIGHT);
176 panel.add(label, "growx, right");
177 panel.add(new JComboBox(new MotorConfigurationModel(configuration)), "wrap");
183 JTabbedPane tabbedPane = new JTabbedPane();
184 panel.add(tabbedPane, "spanx, growx, growy");
187 // Create the CP data table
188 cpTableModel = new ColumnTableModel(
191 new Column(trans.get("componentanalysisdlg.TabStability.Col.Component")) {
193 public Object getValueAt(int row) {
194 RocketComponent c = cpData.get(row).getComponent();
195 if (c instanceof Rocket) {
202 public int getDefaultWidth() {
206 new Column("CG / " + UnitGroup.UNITS_LENGTH.getDefaultUnit().getUnit()) {
207 private Unit unit = UnitGroup.UNITS_LENGTH.getDefaultUnit();
210 public Object getValueAt(int row) {
211 return unit.toString(cgData.get(row).x);
214 new Column("Mass / " + UnitGroup.UNITS_MASS.getDefaultUnit().getUnit()) {
215 private Unit unit = UnitGroup.UNITS_MASS.getDefaultUnit();
218 public Object getValueAt(int row) {
219 return unit.toString(cgData.get(row).weight);
222 new Column("CP / " + UnitGroup.UNITS_LENGTH.getDefaultUnit().getUnit()) {
223 private Unit unit = UnitGroup.UNITS_LENGTH.getDefaultUnit();
226 public Object getValueAt(int row) {
227 return unit.toString(cpData.get(row).getCP().x);
230 new Column("<html>C<sub>N<sub>" + ALPHA + "</sub></sub>") {
232 public Object getValueAt(int row) {
233 return NOUNIT2.toString(cpData.get(row).getCP().weight);
239 public int getRowCount() {
240 return cpData.size();
244 table = new JTable(cpTableModel);
245 table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
246 table.setSelectionBackground(Color.LIGHT_GRAY);
247 table.setSelectionForeground(Color.BLACK);
248 cpTableModel.setColumnWidths(table.getColumnModel());
250 table.setDefaultRenderer(Object.class, new CustomCellRenderer());
251 // table.setShowHorizontalLines(false);
252 // table.setShowVerticalLines(true);
254 JScrollPane scrollpane = new JScrollPane(table);
255 scrollpane.setPreferredSize(new Dimension(600, 200));
257 //// Stability and Stability information
258 tabbedPane.addTab(trans.get("componentanalysisdlg.TabStability"),
259 null, scrollpane, trans.get("componentanalysisdlg.TabStability.ttip"));
263 // Create the drag data table
264 dragTableModel = new ColumnTableModel(
266 new Column(trans.get("componentanalysisdlg.dragTableModel.Col.Component")) {
268 public Object getValueAt(int row) {
269 RocketComponent c = dragData.get(row).getComponent();
270 if (c instanceof Rocket) {
277 public int getDefaultWidth() {
281 //// <html>Pressure C<sub>D</sub>
282 new Column(trans.get("componentanalysisdlg.dragTableModel.Col.Pressure")) {
284 public Object getValueAt(int row) {
285 return dragData.get(row).getPressureCD();
288 //// <html>Base C<sub>D</sub>
289 new Column(trans.get("componentanalysisdlg.dragTableModel.Col.Base")) {
291 public Object getValueAt(int row) {
292 return dragData.get(row).getBaseCD();
295 //// <html>Friction C<sub>D</sub>
296 new Column(trans.get("componentanalysisdlg.dragTableModel.Col.friction")) {
298 public Object getValueAt(int row) {
299 return dragData.get(row).getFrictionCD();
302 //// <html>Total C<sub>D</sub>
303 new Column(trans.get("componentanalysisdlg.dragTableModel.Col.total")) {
305 public Object getValueAt(int row) {
306 return dragData.get(row).getCD();
311 public int getRowCount() {
312 return dragData.size();
317 table = new JTable(dragTableModel);
318 table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
319 table.setSelectionBackground(Color.LIGHT_GRAY);
320 table.setSelectionForeground(Color.BLACK);
321 dragTableModel.setColumnWidths(table.getColumnModel());
323 table.setDefaultRenderer(Object.class, new DragCellRenderer(new Color(0.5f, 1.0f, 0.5f)));
324 // table.setShowHorizontalLines(false);
325 // table.setShowVerticalLines(true);
327 scrollpane = new JScrollPane(table);
328 scrollpane.setPreferredSize(new Dimension(600, 200));
330 //// Drag characteristics and Drag characteristics tooltip
331 tabbedPane.addTab(trans.get("componentanalysisdlg.dragTabchar"), null, scrollpane,
332 trans.get("componentanalysisdlg.dragTabchar.ttip"));
337 // Create the roll data table
338 rollTableModel = new ColumnTableModel(
340 new Column(trans.get("componentanalysisdlg.rollTableModel.Col.component")) {
342 public Object getValueAt(int row) {
343 RocketComponent c = rollData.get(row).getComponent();
344 if (c instanceof Rocket) {
350 //// Roll forcing coefficient
351 new Column(trans.get("componentanalysisdlg.rollTableModel.Col.rollforc")) {
353 public Object getValueAt(int row) {
354 return rollData.get(row).getCrollForce();
357 //// Roll damping coefficient
358 new Column(trans.get("componentanalysisdlg.rollTableModel.Col.rolldamp")) {
360 public Object getValueAt(int row) {
361 return rollData.get(row).getCrollDamp();
364 //// <html>Total C<sub>l</sub>
365 new Column(trans.get("componentanalysisdlg.rollTableModel.Col.total")) {
367 public Object getValueAt(int row) {
368 return rollData.get(row).getCroll();
373 public int getRowCount() {
374 return rollData.size();
379 table = new JTable(rollTableModel);
380 table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
381 table.setSelectionBackground(Color.LIGHT_GRAY);
382 table.setSelectionForeground(Color.BLACK);
383 rollTableModel.setColumnWidths(table.getColumnModel());
385 scrollpane = new JScrollPane(table);
386 scrollpane.setPreferredSize(new Dimension(600, 200));
388 //// Roll dynamics and Roll dynamics tooltip
389 tabbedPane.addTab(trans.get("componentanalysisdlg.rollTableModel"), null, scrollpane,
390 trans.get("componentanalysisdlg.rollTableModel.ttip"));
396 // Add the data updater to listen to changes in aoa and theta
397 mach.addChangeListener(this);
398 theta.addChangeListener(this);
399 aoa.addChangeListener(this);
400 roll.addChangeListener(this);
401 configuration.addChangeListener(this);
402 this.stateChanged(null);
406 // Remove listeners when closing window
407 this.addWindowListener(new WindowAdapter() {
409 public void windowClosed(WindowEvent e) {
410 System.out.println("Closing method called: " + this);
411 theta.removeChangeListener(ComponentAnalysisDialog.this);
412 aoa.removeChangeListener(ComponentAnalysisDialog.this);
413 mach.removeChangeListener(ComponentAnalysisDialog.this);
414 roll.removeChangeListener(ComponentAnalysisDialog.this);
415 configuration.removeChangeListener(ComponentAnalysisDialog.this);
416 System.out.println("SETTING NAN VALUES");
417 rocketPanel.setCPAOA(Double.NaN);
418 rocketPanel.setCPTheta(Double.NaN);
419 rocketPanel.setCPMach(Double.NaN);
420 rocketPanel.setCPRoll(Double.NaN);
421 singletonDialog = null;
425 //// Reference length:
426 panel.add(new StyledLabel(trans.get("componentanalysisdlg.lbl.reflenght"), -1),
427 "span, split, gapleft para, gapright rel");
428 DoubleModel dm = new DoubleModel(conditions, "RefLength", UnitGroup.UNITS_LENGTH);
429 UnitSelector sel = new UnitSelector(dm, true);
431 panel.add(sel, "gapright para");
434 panel.add(new StyledLabel(trans.get("componentanalysisdlg.lbl.refarea"), -1), "gapright rel");
435 dm = new DoubleModel(conditions, "RefArea", UnitGroup.UNITS_AREA);
436 sel = new UnitSelector(dm, true);
438 panel.add(sel, "wrap");
445 // TODO: LOW: printing
446 // button = new JButton("Print");
447 // button.addActionListener(new ActionListener() {
448 // public void actionPerformed(ActionEvent e) {
451 // } catch (PrinterException e1) {
452 // JOptionPane.showMessageDialog(ComponentAnalysisDialog.this,
453 // "An error occurred while printing.", "Print error",
454 // JOptionPane.ERROR_MESSAGE);
458 // panel.add(button,"tag ok");
460 //button = new JButton("Close");
462 button = new JButton(trans.get("dlg.but.close"));
463 button.addActionListener(new ActionListener() {
464 public void actionPerformed(ActionEvent e) {
465 ComponentAnalysisDialog.this.dispose();
468 panel.add(button, "span, split, tag cancel");
471 this.setLocationByPlatform(true);
472 setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
475 GUIUtil.setDisposableDialogOptions(this, null);
481 * Updates the data in the table and fires a table data change event.
484 public void stateChanged(ChangeEvent e) {
485 AerodynamicForces forces;
486 WarningSet set = new WarningSet();
487 conditions.setAOA(aoa.getValue());
488 conditions.setTheta(theta.getValue());
489 conditions.setMach(mach.getValue());
490 conditions.setRollRate(roll.getValue());
491 conditions.setReference(configuration);
493 if (worstToggle.isSelected()) {
494 aerodynamicCalculator.getWorstCP(configuration, conditions, null);
495 if (!MathUtil.equals(conditions.getTheta(), theta.getValue())) {
497 theta.setValue(conditions.getTheta()); // Fires a stateChanged event
503 Map<RocketComponent, AerodynamicForces> aeroData =
504 aerodynamicCalculator.getForceAnalysis(configuration, conditions, set);
505 Map<RocketComponent, Coordinate> massData =
506 massCalculator.getCGAnalysis(configuration, MassCalcType.LAUNCH_MASS);
513 for (RocketComponent c : configuration) {
514 forces = aeroData.get(c);
515 Coordinate cg = massData.get(c);
519 if (forces.getCP() != null) {
523 if (!Double.isNaN(forces.getCD())) {
524 dragData.add(forces);
526 if (c instanceof FinSet) {
527 rollData.add(forces);
530 forces = aeroData.get(configuration.getRocket());
531 if (forces != null) {
533 cgData.add(massData.get(configuration.getRocket()));
534 dragData.add(forces);
535 rollData.add(forces);
536 totalCD = forces.getCD();
543 warningList.setListData(new String[] {
544 "<html><i><font color=\"gray\">No warnings.</font></i>"
547 warningList.setListData(new Vector<Warning>(set));
550 cpTableModel.fireTableDataChanged();
551 dragTableModel.fireTableDataChanged();
552 rollTableModel.fireTableDataChanged();
556 private class CustomCellRenderer extends JLabel implements TableCellRenderer {
557 private final Font normalFont;
558 private final Font boldFont;
560 public CustomCellRenderer() {
562 normalFont = getFont();
563 boldFont = normalFont.deriveFont(Font.BOLD);
567 public Component getTableCellRendererComponent(JTable table, Object value,
568 boolean isSelected, boolean hasFocus, int row, int column) {
570 this.setText(value.toString());
572 if ((row < 0) || (row >= cpData.size()))
575 if (cpData.get(row).getComponent() instanceof Rocket) {
576 this.setFont(boldFont);
578 this.setFont(normalFont);
586 private class DragCellRenderer extends JLabel implements TableCellRenderer {
587 private final Font normalFont;
588 private final Font boldFont;
591 public DragCellRenderer(Color baseColor) {
593 normalFont = getFont();
594 boldFont = normalFont.deriveFont(Font.BOLD);
598 public Component getTableCellRendererComponent(JTable table, Object value,
599 boolean isSelected, boolean hasFocus, int row, int column) {
601 if (value instanceof Double) {
603 // A drag coefficient
604 double cd = (Double) value;
605 this.setText(String.format("%.2f (%.0f%%)", cd, 100 * cd / totalCD));
607 float r = (float) (cd / 1.5);
609 float hue = MathUtil.clamp(0.3333f * (1 - 2.0f * r), 0, 0.3333f);
610 float sat = MathUtil.clamp(0.8f * r + 0.1f * (1 - r), 0, 1);
613 this.setBackground(Color.getHSBColor(hue, sat, val));
614 this.setOpaque(true);
615 this.setHorizontalAlignment(SwingConstants.CENTER);
620 this.setText(value.toString());
621 this.setOpaque(false);
622 this.setHorizontalAlignment(SwingConstants.LEFT);
626 if ((row < 0) || (row >= dragData.size()))
629 if ((dragData.get(row).getComponent() instanceof Rocket) || (column == 4)) {
630 this.setFont(boldFont);
632 this.setFont(normalFont);
639 ///////// Singleton implementation
641 public static void showDialog(RocketPanel rocketpanel) {
642 if (singletonDialog != null)
643 singletonDialog.dispose();
644 singletonDialog = new ComponentAnalysisDialog(rocketpanel);
645 singletonDialog.setVisible(true);
648 public static void hideDialog() {
649 if (singletonDialog != null)
650 singletonDialog.dispose();