1 package net.sf.openrocket.gui.configdialog;
5 import java.awt.Component;
6 import java.awt.Graphics;
7 import java.awt.event.ActionEvent;
8 import java.awt.event.ActionListener;
9 import java.awt.event.FocusEvent;
10 import java.awt.event.FocusListener;
11 import java.util.Iterator;
13 import javax.swing.BorderFactory;
14 import javax.swing.Icon;
15 import javax.swing.JButton;
16 import javax.swing.JCheckBox;
17 import javax.swing.JColorChooser;
18 import javax.swing.JComboBox;
19 import javax.swing.JLabel;
20 import javax.swing.JPanel;
21 import javax.swing.JScrollPane;
22 import javax.swing.JSpinner;
23 import javax.swing.JTabbedPane;
24 import javax.swing.JTextArea;
25 import javax.swing.JTextField;
27 import net.miginfocom.swing.MigLayout;
28 import net.sf.openrocket.gui.SpinnerEditor;
29 import net.sf.openrocket.gui.adaptors.BooleanModel;
30 import net.sf.openrocket.gui.adaptors.DoubleModel;
31 import net.sf.openrocket.gui.adaptors.EnumModel;
32 import net.sf.openrocket.gui.adaptors.MaterialModel;
33 import net.sf.openrocket.gui.components.BasicSlider;
34 import net.sf.openrocket.gui.components.ResizeLabel;
35 import net.sf.openrocket.gui.components.UnitSelector;
36 import net.sf.openrocket.material.Material;
37 import net.sf.openrocket.rocketcomponent.ComponentAssembly;
38 import net.sf.openrocket.rocketcomponent.ExternalComponent;
39 import net.sf.openrocket.rocketcomponent.NoseCone;
40 import net.sf.openrocket.rocketcomponent.Rocket;
41 import net.sf.openrocket.rocketcomponent.RocketComponent;
42 import net.sf.openrocket.rocketcomponent.ExternalComponent.Finish;
43 import net.sf.openrocket.unit.UnitGroup;
44 import net.sf.openrocket.util.GUIUtil;
45 import net.sf.openrocket.util.LineStyle;
46 import net.sf.openrocket.util.Prefs;
48 public class RocketComponentConfig extends JPanel {
50 protected final RocketComponent component;
51 protected final JTabbedPane tabbedPane;
54 protected final JTextField componentNameField;
55 protected JTextArea commentTextArea;
56 private final TextFieldListener textFieldListener;
57 private JButton colorButton;
58 private JCheckBox colorDefault;
59 private JPanel buttonPanel;
61 private JLabel massLabel;
64 public RocketComponentConfig(RocketComponent component) {
65 setLayout(new MigLayout("fill","[grow, fill]"));
66 this.component = component;
68 JLabel label = new JLabel("Component name:");
69 label.setToolTipText("The component name.");
70 this.add(label,"split, gapright 10");
72 componentNameField = new JTextField(15);
73 textFieldListener = new TextFieldListener();
74 componentNameField.addActionListener(textFieldListener);
75 componentNameField.addFocusListener(textFieldListener);
76 componentNameField.setToolTipText("The component name.");
77 this.add(componentNameField,"growx, growy 0, wrap");
80 tabbedPane = new JTabbedPane();
81 this.add(tabbedPane,"growx, growy 1, wrap");
83 tabbedPane.addTab("Override", null, overrideTab(), "Mass and CG override options");
84 if (component.isMassive())
85 tabbedPane.addTab("Figure", null, figureTab(), "Figure style options");
86 tabbedPane.addTab("Comment", null, commentTab(), "Specify a comment for the component");
94 protected void addButtons(JButton... buttons) {
95 if (buttonPanel != null) {
96 this.remove(buttonPanel);
99 buttonPanel = new JPanel(new MigLayout("fill, ins 0"));
101 massLabel = new ResizeLabel("Mass: ", -1);
102 buttonPanel.add(massLabel, "growx");
104 for (JButton b: buttons) {
105 buttonPanel.add(b, "right, gap para");
108 JButton closeButton = new JButton("Close");
109 closeButton.addActionListener(new ActionListener() {
111 public void actionPerformed(ActionEvent arg0) {
112 ComponentConfigDialog.hideDialog();
115 buttonPanel.add(closeButton, "right, gap 30lp");
119 this.add(buttonPanel, "spanx, growx");
124 * Called when a change occurs, so that the fields can be updated if necessary.
125 * When overriding this method, the supermethod must always be called.
127 public void updateFields() {
129 componentNameField.setText(component.getName());
131 // Component color and "Use default color" checkbox
132 if (colorButton != null && colorDefault != null) {
133 colorButton.setIcon(new ColorIcon(component.getColor()));
135 if ((component.getColor()==null) != colorDefault.isSelected())
136 colorDefault.setSelected(component.getColor()==null);
140 if (component.isMassive()) {
141 String text = "Component mass: ";
142 text += UnitGroup.UNITS_MASS.getDefaultUnit().toStringUnit(
143 component.getComponentMass());
145 String overridetext = null;
146 if (component.isMassOverridden()) {
147 overridetext = "(overridden to " + UnitGroup.UNITS_MASS.getDefaultUnit().
148 toStringUnit(component.getOverrideMass()) + ")";
151 for (RocketComponent c = component.getParent(); c != null; c = c.getParent()) {
152 if (c.isMassOverridden() && c.getOverrideSubcomponents()) {
153 overridetext = "(overridden by " + c.getName() + ")";
157 if (overridetext != null)
158 text = text + " " + overridetext;
160 massLabel.setText(text);
162 massLabel.setText("");
167 protected JPanel materialPanel(JPanel panel, Material.Type type) {
168 return materialPanel(panel, type, "Component material:", "Component finish:");
171 protected JPanel materialPanel(JPanel panel, Material.Type type,
172 String materialString, String finishString) {
173 JLabel label = new JLabel(materialString);
174 label.setToolTipText("The component material affects the weight of the component.");
175 panel.add(label,"spanx 4, wrap rel");
177 JComboBox combo = new JComboBox(new MaterialModel(component,type));
178 combo.setToolTipText("The component material affects the weight of the component.");
179 panel.add(combo,"spanx 4, growx, wrap paragraph");
182 if (component instanceof ExternalComponent) {
183 label = new JLabel(finishString);
184 String tip = "<html>The component finish affects the aerodynamic drag of the "
186 + "The value indicated is the average roughness height of the surface.";
187 label.setToolTipText(tip);
188 panel.add(label,"spanx 4, wmin 220lp, wrap rel");
190 combo = new JComboBox(new EnumModel<ExternalComponent.Finish>(component,"Finish"));
191 combo.setToolTipText(tip);
192 panel.add(combo,"spanx 4, growx, split");
194 JButton button = new JButton("Set for all");
195 button.setToolTipText("Set this finish for all components of the rocket.");
196 button.addActionListener(new ActionListener() {
198 public void actionPerformed(ActionEvent e) {
199 Finish f = ((ExternalComponent)component).getFinish();
200 Rocket rocket = component.getRocket();
203 // Store previous undo description
204 String desc = ComponentConfigDialog.getUndoDescription();
205 ComponentConfigDialog.addUndoPosition("Set rocket finish");
207 Iterator<RocketComponent> iter = rocket.deepIterator();
208 while (iter.hasNext()) {
209 RocketComponent c = iter.next();
210 if (c instanceof ExternalComponent) {
211 ((ExternalComponent)c).setFinish(f);
214 // Restore undo description
215 ComponentConfigDialog.addUndoPosition(desc);
221 panel.add(button, "wrap paragraph");
228 private JPanel overrideTab() {
229 JPanel panel = new JPanel(new MigLayout("align 50% 20%, fillx, gap rel unrel",
230 "[][65lp::][30lp::][]",""));
232 panel.add(new JLabel("Override the mass or center of gravity of the " +
233 component.getComponentName() + ":"),"spanx, wrap 20lp");
241 bm = new BooleanModel(component, "MassOverridden");
242 check = new JCheckBox(bm);
243 check.setText("Override mass:");
244 panel.add(check, "growx 1, gapright 20lp");
246 DoubleModel m = new DoubleModel(component,"OverrideMass",UnitGroup.UNITS_MASS,0);
248 JSpinner spin = new JSpinner(m.getSpinnerModel());
249 spin.setEditor(new SpinnerEditor(spin));
250 bm.addEnableComponent(spin, true);
251 panel.add(spin,"growx 1");
253 us = new UnitSelector(m);
254 bm.addEnableComponent(us, true);
255 panel.add(us,"growx 1");
257 bs = new BasicSlider(m.getSliderModel(0, 0.03, 1.0));
258 bm.addEnableComponent(bs);
259 panel.add(bs,"growx 5, w 100lp, wrap");
263 bm = new BooleanModel(component, "CGOverridden");
264 check = new JCheckBox(bm);
265 check.setText("Override center of gravity:");
266 panel.add(check, "growx 1, gapright 20lp");
268 m = new DoubleModel(component,"OverrideCGX",UnitGroup.UNITS_LENGTH,0);
269 // Calculate suitable length for slider
271 if (component instanceof ComponentAssembly) {
274 Iterator<RocketComponent> iterator = component.deepIterator();
275 while (iterator.hasNext()) {
276 RocketComponent c = iterator.next();
277 if (c.getRelativePosition() == RocketComponent.Position.AFTER)
280 length = new DoubleModel(l);
282 length = new DoubleModel(component, "Length", UnitGroup.UNITS_LENGTH,0);
285 spin = new JSpinner(m.getSpinnerModel());
286 spin.setEditor(new SpinnerEditor(spin));
287 bm.addEnableComponent(spin, true);
288 panel.add(spin,"growx 1");
290 us = new UnitSelector(m);
291 bm.addEnableComponent(us, true);
292 panel.add(us,"growx 1");
294 bs = new BasicSlider(m.getSliderModel(new DoubleModel(0), length));
295 bm.addEnableComponent(bs);
296 panel.add(bs,"growx 5, w 100lp, wrap 35lp");
299 // Override subcomponents checkbox
300 bm = new BooleanModel(component, "OverrideSubcomponents");
301 check = new JCheckBox(bm);
302 check.setText("Override mass and CG of all subcomponents");
303 panel.add(check, "gap para, spanx, wrap para");
306 panel.add(new ResizeLabel("<html>The overridden mass does not include motors.<br>" +
307 "The center of gravity is measured from the front end of the " +
308 component.getComponentName().toLowerCase()+".", -1),
309 "spanx, wrap, gap para, height 0::30lp");
315 private JPanel commentTab() {
316 JPanel panel = new JPanel(new MigLayout("fill"));
318 panel.add(new JLabel("Comments on the "+component.getComponentName()+":"), "wrap");
320 // TODO: LOW: Changes in comment from other sources not reflected in component
321 commentTextArea = new JTextArea(component.getComment());
322 commentTextArea.setLineWrap(true);
323 commentTextArea.setWrapStyleWord(true);
324 commentTextArea.setEditable(true);
325 GUIUtil.setTabToFocusing(commentTextArea);
326 commentTextArea.addFocusListener(textFieldListener);
328 panel.add(new JScrollPane(commentTextArea), "growx, growy");
335 private JPanel figureTab() {
336 JPanel panel = new JPanel(new MigLayout("align 20% 20%"));
338 panel.add(new JLabel("Figure style:"), "wrap para");
341 panel.add(new JLabel("Component color:"), "gapleft para, gapright 10lp");
343 colorButton = new JButton(new ColorIcon(component.getColor()));
344 colorButton.addActionListener(new ActionListener() {
345 public void actionPerformed(ActionEvent e) {
346 Color c = component.getColor();
348 c = Prefs.getDefaultColor(component.getClass());
351 c = JColorChooser.showDialog(tabbedPane, "Choose color", c);
353 component.setColor(c);
357 panel.add(colorButton, "gapright 10lp");
359 colorDefault = new JCheckBox("Use default color");
360 if (component.getColor()==null)
361 colorDefault.setSelected(true);
362 colorDefault.addActionListener(new ActionListener() {
364 public void actionPerformed(ActionEvent e) {
365 if (colorDefault.isSelected())
366 component.setColor(null);
368 component.setColor(Prefs.getDefaultColor(component.getClass()));
371 panel.add(colorDefault, "wrap para");
374 panel.add(new JLabel("Component line style:"), "gapleft para, gapright 10lp");
376 LineStyle[] list = new LineStyle[LineStyle.values().length+1];
377 System.arraycopy(LineStyle.values(), 0, list, 1, LineStyle.values().length);
379 JComboBox combo = new JComboBox(new EnumModel<LineStyle>(component, "LineStyle",
380 list, "Default style"));
381 panel.add(combo, "spanx 2, growx, wrap 50lp");
384 JButton button = new JButton("Save as default style");
385 button.addActionListener(new ActionListener() {
387 public void actionPerformed(ActionEvent e) {
388 if (component.getColor() != null) {
389 Prefs.setDefaultColor(component.getClass(), component.getColor());
390 component.setColor(null);
392 if (component.getLineStyle() != null) {
393 Prefs.setDefaultLineStyle(component.getClass(), component.getLineStyle());
394 component.setLineStyle(null);
398 panel.add(button, "gapleft para, spanx 3, growx, wrap");
406 protected JPanel shoulderTab() {
407 JPanel panel = new JPanel(new MigLayout("fill"));
410 DoubleModel m0 = new DoubleModel(0);
416 //// Fore shoulder, not for NoseCone
418 if (!(component instanceof NoseCone)) {
419 sub = new JPanel(new MigLayout("gap rel unrel","[][65lp::][30lp::]",""));
421 sub.setBorder(BorderFactory.createTitledBorder("Fore shoulder"));
425 sub.add(new JLabel("Diameter:"));
427 m = new DoubleModel(component,"ForeShoulderRadius",2,UnitGroup.UNITS_LENGTH,0);
428 m2 = new DoubleModel(component,"ForeRadius",2,UnitGroup.UNITS_LENGTH);
430 spin = new JSpinner(m.getSpinnerModel());
431 spin.setEditor(new SpinnerEditor(spin));
432 sub.add(spin,"growx");
434 sub.add(new UnitSelector(m),"growx");
435 sub.add(new BasicSlider(m.getSliderModel(m0, m2)),"w 100lp, wrap");
439 sub.add(new JLabel("Length:"));
441 m = new DoubleModel(component,"ForeShoulderLength",UnitGroup.UNITS_LENGTH,0);
443 spin = new JSpinner(m.getSpinnerModel());
444 spin.setEditor(new SpinnerEditor(spin));
445 sub.add(spin,"growx");
447 sub.add(new UnitSelector(m),"growx");
448 sub.add(new BasicSlider(m.getSliderModel(0, 0.02, 0.2)),"w 100lp, wrap");
452 sub.add(new JLabel("Thickness:"));
454 m = new DoubleModel(component,"ForeShoulderThickness",UnitGroup.UNITS_LENGTH,0);
455 m2 = new DoubleModel(component,"ForeShoulderRadius",UnitGroup.UNITS_LENGTH);
457 spin = new JSpinner(m.getSpinnerModel());
458 spin.setEditor(new SpinnerEditor(spin));
459 sub.add(spin,"growx");
461 sub.add(new UnitSelector(m),"growx");
462 sub.add(new BasicSlider(m.getSliderModel(m0, m2)),"w 100lp, wrap");
466 bm = new BooleanModel(component, "ForeShoulderCapped");
467 check = new JCheckBox(bm);
468 check.setText("End capped");
469 check.setToolTipText("Whether the end of the shoulder is capped.");
470 sub.add(check, "spanx");
478 sub = new JPanel(new MigLayout("gap rel unrel","[][65lp::][30lp::]",""));
480 if (component instanceof NoseCone)
481 sub.setBorder(BorderFactory.createTitledBorder("Nose cone shoulder"));
483 sub.setBorder(BorderFactory.createTitledBorder("Aft shoulder"));
487 sub.add(new JLabel("Diameter:"));
489 m = new DoubleModel(component,"AftShoulderRadius",2,UnitGroup.UNITS_LENGTH,0);
490 m2 = new DoubleModel(component,"AftRadius",2,UnitGroup.UNITS_LENGTH);
492 spin = new JSpinner(m.getSpinnerModel());
493 spin.setEditor(new SpinnerEditor(spin));
494 sub.add(spin,"growx");
496 sub.add(new UnitSelector(m),"growx");
497 sub.add(new BasicSlider(m.getSliderModel(m0, m2)),"w 100lp, wrap");
501 sub.add(new JLabel("Length:"));
503 m = new DoubleModel(component,"AftShoulderLength",UnitGroup.UNITS_LENGTH,0);
505 spin = new JSpinner(m.getSpinnerModel());
506 spin.setEditor(new SpinnerEditor(spin));
507 sub.add(spin,"growx");
509 sub.add(new UnitSelector(m),"growx");
510 sub.add(new BasicSlider(m.getSliderModel(0, 0.02, 0.2)),"w 100lp, wrap");
514 sub.add(new JLabel("Thickness:"));
516 m = new DoubleModel(component,"AftShoulderThickness",UnitGroup.UNITS_LENGTH,0);
517 m2 = new DoubleModel(component,"AftShoulderRadius",UnitGroup.UNITS_LENGTH);
519 spin = new JSpinner(m.getSpinnerModel());
520 spin.setEditor(new SpinnerEditor(spin));
521 sub.add(spin,"growx");
523 sub.add(new UnitSelector(m),"growx");
524 sub.add(new BasicSlider(m.getSliderModel(m0, m2)),"w 100lp, wrap");
528 bm = new BooleanModel(component, "AftShoulderCapped");
529 check = new JCheckBox(bm);
530 check.setText("End capped");
531 check.setToolTipText("Whether the end of the shoulder is capped.");
532 sub.add(check, "spanx");
545 * Private inner class to handle events in componentNameField.
547 private class TextFieldListener implements ActionListener, FocusListener {
548 public void actionPerformed(ActionEvent e) {
551 public void focusGained(FocusEvent e) { }
552 public void focusLost(FocusEvent e) {
555 private void setName() {
556 if (!component.getName().equals(componentNameField.getText())) {
557 component.setName(componentNameField.getText());
559 if (!component.getComment().equals(commentTextArea.getText())) {
560 component.setComment(commentTextArea.getText());
566 private class ColorIcon implements Icon {
567 private final Color color;
569 public ColorIcon(Color c) {
574 public int getIconHeight() {
579 public int getIconWidth() {
584 public void paintIcon(Component c, Graphics g, int x, int y) {
586 g.setColor(Prefs.getDefaultColor(component.getClass()));
590 g.fill3DRect(x, y, getIconWidth(), getIconHeight(), false);