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.l10n.Translator;
54 import net.sf.openrocket.masscalc.BasicMassCalculator;
55 import net.sf.openrocket.masscalc.MassCalculator;
56 import net.sf.openrocket.masscalc.MassCalculator.MassCalcType;
57 import net.sf.openrocket.rocketcomponent.Configuration;
58 import net.sf.openrocket.rocketcomponent.FinSet;
59 import net.sf.openrocket.rocketcomponent.Rocket;
60 import net.sf.openrocket.rocketcomponent.RocketComponent;
61 import net.sf.openrocket.startup.Application;
62 import net.sf.openrocket.unit.Unit;
63 import net.sf.openrocket.unit.UnitGroup;
64 import net.sf.openrocket.util.Coordinate;
65 import net.sf.openrocket.util.GUIUtil;
66 import net.sf.openrocket.util.MathUtil;
67 import net.sf.openrocket.util.Prefs;
69 public class ComponentAnalysisDialog extends JDialog implements ChangeListener {
71 private static ComponentAnalysisDialog singletonDialog = null;
72 private static final Translator trans = Application.getTranslator();
75 private final FlightConditions conditions;
76 private final Configuration configuration;
77 private final DoubleModel theta, aoa, mach, roll;
78 private final JToggleButton worstToggle;
79 private boolean fakeChange = false;
80 private AerodynamicCalculator aerodynamicCalculator;
81 private final MassCalculator massCalculator = new BasicMassCalculator();
83 private final ColumnTableModel cpTableModel;
84 private final ColumnTableModel dragTableModel;
85 private final ColumnTableModel rollTableModel;
87 private final JList warningList;
90 private final List<AerodynamicForces> cpData = new ArrayList<AerodynamicForces>();
91 private final List<Coordinate> cgData = new ArrayList<Coordinate>();
92 private final List<AerodynamicForces> dragData = new ArrayList<AerodynamicForces>();
93 private double totalCD = 0;
94 private final List<AerodynamicForces> rollData = new ArrayList<AerodynamicForces>();
97 public ComponentAnalysisDialog(final RocketPanel rocketPanel) {
98 ////Component analysis
99 super(SwingUtilities.getWindowAncestor(rocketPanel),
100 trans.get("componentanalysisdlg.componentanalysis"));
104 JPanel panel = new JPanel(new MigLayout("fill", "[][35lp::][fill][fill]"));
107 this.configuration = rocketPanel.getConfiguration();
108 this.aerodynamicCalculator = rocketPanel.getAerodynamicCalculator().newInstance();
111 conditions = new FlightConditions(configuration);
113 rocketPanel.setCPAOA(0);
114 aoa = new DoubleModel(rocketPanel, "CPAOA", UnitGroup.UNITS_ANGLE, 0, Math.PI);
115 rocketPanel.setCPMach(Prefs.getDefaultMach());
116 mach = new DoubleModel(rocketPanel, "CPMach", UnitGroup.UNITS_COEFFICIENT, 0);
117 rocketPanel.setCPTheta(rocketPanel.getFigure().getRotation());
118 theta = new DoubleModel(rocketPanel, "CPTheta", UnitGroup.UNITS_ANGLE, 0, 2 * Math.PI);
119 rocketPanel.setCPRoll(0);
120 roll = new DoubleModel(rocketPanel, "CPRoll", UnitGroup.UNITS_ROLL);
123 panel.add(new JLabel(trans.get("componentanalysisdlg.lbl.winddir")), "width 100lp!");
124 panel.add(new UnitSelector(theta, true), "width 50lp!");
125 BasicSlider slider = new BasicSlider(theta.getSliderModel(0, 2 * Math.PI));
126 panel.add(slider, "growx, split 2");
128 worstToggle = new JToggleButton(trans.get("componentanalysisdlg.ToggleBut.worst"));
129 worstToggle.setSelected(true);
130 worstToggle.addActionListener(new ActionListener() {
132 public void actionPerformed(ActionEvent e) {
136 slider.addChangeListener(new ChangeListener() {
138 public void stateChanged(ChangeEvent e) {
140 worstToggle.setSelected(false);
143 panel.add(worstToggle, "");
146 warningList = new JList();
147 JScrollPane scrollPane = new JScrollPane(warningList);
149 scrollPane.setBorder(BorderFactory.createTitledBorder(trans.get("componentanalysisdlg.TitledBorder.warnings")));
150 panel.add(scrollPane, "gap paragraph, spany 4, width 300lp!, growy 1, height :100lp:, wrap");
153 panel.add(new JLabel(trans.get("componentanalysisdlg.lbl.angleofattack")), "width 100lp!");
154 panel.add(new UnitSelector(aoa, true), "width 50lp!");
155 panel.add(new BasicSlider(aoa.getSliderModel(0, Math.PI)), "growx, wrap");
158 panel.add(new JLabel(trans.get("componentanalysisdlg.lbl.machnumber")), "width 100lp!");
159 panel.add(new UnitSelector(mach, true), "width 50lp!");
160 panel.add(new BasicSlider(mach.getSliderModel(0, 3)), "growx, wrap");
163 panel.add(new JLabel(trans.get("componentanalysisdlg.lbl.rollrate")), "width 100lp!");
164 panel.add(new UnitSelector(roll, true), "width 50lp!");
165 panel.add(new BasicSlider(roll.getSliderModel(-20 * 2 * Math.PI, 20 * 2 * Math.PI)),
166 "growx, wrap paragraph");
169 // Stage and motor selection:
171 panel.add(new JLabel(trans.get("componentanalysisdlg.lbl.activestages")), "spanx, split, gapafter rel");
172 panel.add(new StageSelector(configuration), "gapafter paragraph");
174 //// Motor configuration:
175 JLabel label = new JLabel(trans.get("componentanalysisdlg.lbl.motorconf"));
176 label.setHorizontalAlignment(JLabel.RIGHT);
177 panel.add(label, "growx, right");
178 panel.add(new JComboBox(new MotorConfigurationModel(configuration)), "wrap");
184 JTabbedPane tabbedPane = new JTabbedPane();
185 panel.add(tabbedPane, "spanx, growx, growy");
188 // Create the CP data table
189 cpTableModel = new ColumnTableModel(
192 new Column(trans.get("componentanalysisdlg.TabStability.Col.Component")) {
194 public Object getValueAt(int row) {
195 RocketComponent c = cpData.get(row).getComponent();
196 if (c instanceof Rocket) {
203 public int getDefaultWidth() {
207 new Column("CG / " + UnitGroup.UNITS_LENGTH.getDefaultUnit().getUnit()) {
208 private Unit unit = UnitGroup.UNITS_LENGTH.getDefaultUnit();
211 public Object getValueAt(int row) {
212 return unit.toString(cgData.get(row).x);
215 new Column("Mass / " + UnitGroup.UNITS_MASS.getDefaultUnit().getUnit()) {
216 private Unit unit = UnitGroup.UNITS_MASS.getDefaultUnit();
219 public Object getValueAt(int row) {
220 return unit.toString(cgData.get(row).weight);
223 new Column("CP / " + UnitGroup.UNITS_LENGTH.getDefaultUnit().getUnit()) {
224 private Unit unit = UnitGroup.UNITS_LENGTH.getDefaultUnit();
227 public Object getValueAt(int row) {
228 return unit.toString(cpData.get(row).getCP().x);
231 new Column("<html>C<sub>N<sub>" + ALPHA + "</sub></sub>") {
233 public Object getValueAt(int row) {
234 return NOUNIT2.toString(cpData.get(row).getCP().weight);
240 public int getRowCount() {
241 return cpData.size();
245 table = new JTable(cpTableModel);
246 table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
247 table.setSelectionBackground(Color.LIGHT_GRAY);
248 table.setSelectionForeground(Color.BLACK);
249 cpTableModel.setColumnWidths(table.getColumnModel());
251 table.setDefaultRenderer(Object.class, new CustomCellRenderer());
252 // table.setShowHorizontalLines(false);
253 // table.setShowVerticalLines(true);
255 JScrollPane scrollpane = new JScrollPane(table);
256 scrollpane.setPreferredSize(new Dimension(600, 200));
258 //// Stability and Stability information
259 tabbedPane.addTab(trans.get("componentanalysisdlg.TabStability"),
260 null, scrollpane, trans.get("componentanalysisdlg.TabStability.ttip"));
264 // Create the drag data table
265 dragTableModel = new ColumnTableModel(
267 new Column(trans.get("componentanalysisdlg.dragTableModel.Col.Component")) {
269 public Object getValueAt(int row) {
270 RocketComponent c = dragData.get(row).getComponent();
271 if (c instanceof Rocket) {
278 public int getDefaultWidth() {
282 //// <html>Pressure C<sub>D</sub>
283 new Column(trans.get("componentanalysisdlg.dragTableModel.Col.Pressure")) {
285 public Object getValueAt(int row) {
286 return dragData.get(row).getPressureCD();
289 //// <html>Base C<sub>D</sub>
290 new Column(trans.get("componentanalysisdlg.dragTableModel.Col.Base")) {
292 public Object getValueAt(int row) {
293 return dragData.get(row).getBaseCD();
296 //// <html>Friction C<sub>D</sub>
297 new Column(trans.get("componentanalysisdlg.dragTableModel.Col.friction")) {
299 public Object getValueAt(int row) {
300 return dragData.get(row).getFrictionCD();
303 //// <html>Total C<sub>D</sub>
304 new Column(trans.get("componentanalysisdlg.dragTableModel.Col.total")) {
306 public Object getValueAt(int row) {
307 return dragData.get(row).getCD();
312 public int getRowCount() {
313 return dragData.size();
318 table = new JTable(dragTableModel);
319 table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
320 table.setSelectionBackground(Color.LIGHT_GRAY);
321 table.setSelectionForeground(Color.BLACK);
322 dragTableModel.setColumnWidths(table.getColumnModel());
324 table.setDefaultRenderer(Object.class, new DragCellRenderer(new Color(0.5f, 1.0f, 0.5f)));
325 // table.setShowHorizontalLines(false);
326 // table.setShowVerticalLines(true);
328 scrollpane = new JScrollPane(table);
329 scrollpane.setPreferredSize(new Dimension(600, 200));
331 //// Drag characteristics and Drag characteristics tooltip
332 tabbedPane.addTab(trans.get("componentanalysisdlg.dragTabchar"), null, scrollpane,
333 trans.get("componentanalysisdlg.dragTabchar.ttip"));
338 // Create the roll data table
339 rollTableModel = new ColumnTableModel(
341 new Column(trans.get("componentanalysisdlg.rollTableModel.Col.component")) {
343 public Object getValueAt(int row) {
344 RocketComponent c = rollData.get(row).getComponent();
345 if (c instanceof Rocket) {
351 //// Roll forcing coefficient
352 new Column(trans.get("componentanalysisdlg.rollTableModel.Col.rollforc")) {
354 public Object getValueAt(int row) {
355 return rollData.get(row).getCrollForce();
358 //// Roll damping coefficient
359 new Column(trans.get("componentanalysisdlg.rollTableModel.Col.rolldamp")) {
361 public Object getValueAt(int row) {
362 return rollData.get(row).getCrollDamp();
365 //// <html>Total C<sub>l</sub>
366 new Column(trans.get("componentanalysisdlg.rollTableModel.Col.total")) {
368 public Object getValueAt(int row) {
369 return rollData.get(row).getCroll();
374 public int getRowCount() {
375 return rollData.size();
380 table = new JTable(rollTableModel);
381 table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
382 table.setSelectionBackground(Color.LIGHT_GRAY);
383 table.setSelectionForeground(Color.BLACK);
384 rollTableModel.setColumnWidths(table.getColumnModel());
386 scrollpane = new JScrollPane(table);
387 scrollpane.setPreferredSize(new Dimension(600, 200));
389 //// Roll dynamics and Roll dynamics tooltip
390 tabbedPane.addTab(trans.get("componentanalysisdlg.rollTableModel"), null, scrollpane,
391 trans.get("componentanalysisdlg.rollTableModel.ttip"));
397 // Add the data updater to listen to changes in aoa and theta
398 mach.addChangeListener(this);
399 theta.addChangeListener(this);
400 aoa.addChangeListener(this);
401 roll.addChangeListener(this);
402 configuration.addChangeListener(this);
403 this.stateChanged(null);
407 // Remove listeners when closing window
408 this.addWindowListener(new WindowAdapter() {
410 public void windowClosed(WindowEvent e) {
411 System.out.println("Closing method called: " + this);
412 theta.removeChangeListener(ComponentAnalysisDialog.this);
413 aoa.removeChangeListener(ComponentAnalysisDialog.this);
414 mach.removeChangeListener(ComponentAnalysisDialog.this);
415 roll.removeChangeListener(ComponentAnalysisDialog.this);
416 configuration.removeChangeListener(ComponentAnalysisDialog.this);
417 System.out.println("SETTING NAN VALUES");
418 rocketPanel.setCPAOA(Double.NaN);
419 rocketPanel.setCPTheta(Double.NaN);
420 rocketPanel.setCPMach(Double.NaN);
421 rocketPanel.setCPRoll(Double.NaN);
422 singletonDialog = null;
426 //// Reference length:
427 panel.add(new StyledLabel(trans.get("componentanalysisdlg.lbl.reflenght"), -1),
428 "span, split, gapleft para, gapright rel");
429 DoubleModel dm = new DoubleModel(conditions, "RefLength", UnitGroup.UNITS_LENGTH);
430 UnitSelector sel = new UnitSelector(dm, true);
432 panel.add(sel, "gapright para");
435 panel.add(new StyledLabel(trans.get("componentanalysisdlg.lbl.refarea"), -1), "gapright rel");
436 dm = new DoubleModel(conditions, "RefArea", UnitGroup.UNITS_AREA);
437 sel = new UnitSelector(dm, true);
439 panel.add(sel, "wrap");
446 // TODO: LOW: printing
447 // button = new JButton("Print");
448 // button.addActionListener(new ActionListener() {
449 // public void actionPerformed(ActionEvent e) {
452 // } catch (PrinterException e1) {
453 // JOptionPane.showMessageDialog(ComponentAnalysisDialog.this,
454 // "An error occurred while printing.", "Print error",
455 // JOptionPane.ERROR_MESSAGE);
459 // panel.add(button,"tag ok");
461 //button = new JButton("Close");
463 button = new JButton(trans.get("dlg.but.close"));
464 button.addActionListener(new ActionListener() {
465 public void actionPerformed(ActionEvent e) {
466 ComponentAnalysisDialog.this.dispose();
469 panel.add(button, "span, split, tag cancel");
472 this.setLocationByPlatform(true);
473 setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
476 GUIUtil.setDisposableDialogOptions(this, null);
482 * Updates the data in the table and fires a table data change event.
485 public void stateChanged(ChangeEvent e) {
486 AerodynamicForces forces;
487 WarningSet set = new WarningSet();
488 conditions.setAOA(aoa.getValue());
489 conditions.setTheta(theta.getValue());
490 conditions.setMach(mach.getValue());
491 conditions.setRollRate(roll.getValue());
492 conditions.setReference(configuration);
494 if (worstToggle.isSelected()) {
495 aerodynamicCalculator.getWorstCP(configuration, conditions, null);
496 if (!MathUtil.equals(conditions.getTheta(), theta.getValue())) {
498 theta.setValue(conditions.getTheta()); // Fires a stateChanged event
504 Map<RocketComponent, AerodynamicForces> aeroData =
505 aerodynamicCalculator.getForceAnalysis(configuration, conditions, set);
506 Map<RocketComponent, Coordinate> massData =
507 massCalculator.getCGAnalysis(configuration, MassCalcType.LAUNCH_MASS);
514 for (RocketComponent c : configuration) {
515 forces = aeroData.get(c);
516 Coordinate cg = massData.get(c);
520 if (forces.getCP() != null) {
524 if (!Double.isNaN(forces.getCD())) {
525 dragData.add(forces);
527 if (c instanceof FinSet) {
528 rollData.add(forces);
531 forces = aeroData.get(configuration.getRocket());
532 if (forces != null) {
534 cgData.add(massData.get(configuration.getRocket()));
535 dragData.add(forces);
536 rollData.add(forces);
537 totalCD = forces.getCD();
544 warningList.setListData(new String[] {
545 "<html><i><font color=\"gray\">No warnings.</font></i>"
548 warningList.setListData(new Vector<Warning>(set));
551 cpTableModel.fireTableDataChanged();
552 dragTableModel.fireTableDataChanged();
553 rollTableModel.fireTableDataChanged();
557 private class CustomCellRenderer extends JLabel implements TableCellRenderer {
558 private final Font normalFont;
559 private final Font boldFont;
561 public CustomCellRenderer() {
563 normalFont = getFont();
564 boldFont = normalFont.deriveFont(Font.BOLD);
568 public Component getTableCellRendererComponent(JTable table, Object value,
569 boolean isSelected, boolean hasFocus, int row, int column) {
571 this.setText(value.toString());
573 if ((row < 0) || (row >= cpData.size()))
576 if (cpData.get(row).getComponent() instanceof Rocket) {
577 this.setFont(boldFont);
579 this.setFont(normalFont);
587 private class DragCellRenderer extends JLabel implements TableCellRenderer {
588 private final Font normalFont;
589 private final Font boldFont;
592 public DragCellRenderer(Color baseColor) {
594 normalFont = getFont();
595 boldFont = normalFont.deriveFont(Font.BOLD);
599 public Component getTableCellRendererComponent(JTable table, Object value,
600 boolean isSelected, boolean hasFocus, int row, int column) {
602 if (value instanceof Double) {
604 // A drag coefficient
605 double cd = (Double) value;
606 this.setText(String.format("%.2f (%.0f%%)", cd, 100 * cd / totalCD));
608 float r = (float) (cd / 1.5);
610 float hue = MathUtil.clamp(0.3333f * (1 - 2.0f * r), 0, 0.3333f);
611 float sat = MathUtil.clamp(0.8f * r + 0.1f * (1 - r), 0, 1);
614 this.setBackground(Color.getHSBColor(hue, sat, val));
615 this.setOpaque(true);
616 this.setHorizontalAlignment(SwingConstants.CENTER);
621 this.setText(value.toString());
622 this.setOpaque(false);
623 this.setHorizontalAlignment(SwingConstants.LEFT);
627 if ((row < 0) || (row >= dragData.size()))
630 if ((dragData.get(row).getComponent() instanceof Rocket) || (column == 4)) {
631 this.setFont(boldFont);
633 this.setFont(normalFont);
640 ///////// Singleton implementation
642 public static void showDialog(RocketPanel rocketpanel) {
643 if (singletonDialog != null)
644 singletonDialog.dispose();
645 singletonDialog = new ComponentAnalysisDialog(rocketpanel);
646 singletonDialog.setVisible(true);
649 public static void hideDialog() {
650 if (singletonDialog != null)
651 singletonDialog.dispose();