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.StyledLabel;
35 import net.sf.openrocket.gui.components.UnitSelector;
36 import net.sf.openrocket.gui.components.StyledLabel.Style;
37 import net.sf.openrocket.material.Material;
38 import net.sf.openrocket.rocketcomponent.ComponentAssembly;
39 import net.sf.openrocket.rocketcomponent.ExternalComponent;
40 import net.sf.openrocket.rocketcomponent.NoseCone;
41 import net.sf.openrocket.rocketcomponent.Rocket;
42 import net.sf.openrocket.rocketcomponent.RocketComponent;
43 import net.sf.openrocket.rocketcomponent.ExternalComponent.Finish;
44 import net.sf.openrocket.unit.UnitGroup;
45 import net.sf.openrocket.util.GUIUtil;
46 import net.sf.openrocket.util.LineStyle;
47 import net.sf.openrocket.util.Prefs;
49 public class RocketComponentConfig extends JPanel {
51 protected final RocketComponent component;
52 protected final JTabbedPane tabbedPane;
55 protected final JTextField componentNameField;
56 protected JTextArea commentTextArea;
57 private final TextFieldListener textFieldListener;
58 private JButton colorButton;
59 private JCheckBox colorDefault;
60 private JPanel buttonPanel;
62 private JLabel massLabel;
65 public RocketComponentConfig(RocketComponent component) {
66 setLayout(new MigLayout("fill","[grow, fill]"));
67 this.component = component;
69 JLabel label = new JLabel("Component name:");
70 label.setToolTipText("The component name.");
71 this.add(label,"split, gapright 10");
73 componentNameField = new JTextField(15);
74 textFieldListener = new TextFieldListener();
75 componentNameField.addActionListener(textFieldListener);
76 componentNameField.addFocusListener(textFieldListener);
77 componentNameField.setToolTipText("The component name.");
78 this.add(componentNameField,"growx, growy 0, wrap");
81 tabbedPane = new JTabbedPane();
82 this.add(tabbedPane,"growx, growy 1, wrap");
84 tabbedPane.addTab("Override", null, overrideTab(), "Mass and CG override options");
85 if (component.isMassive())
86 tabbedPane.addTab("Figure", null, figureTab(), "Figure style options");
87 tabbedPane.addTab("Comment", null, commentTab(), "Specify a comment for the component");
95 protected void addButtons(JButton... buttons) {
96 if (buttonPanel != null) {
97 this.remove(buttonPanel);
100 buttonPanel = new JPanel(new MigLayout("fill, ins 0"));
102 massLabel = new StyledLabel("Mass: ", -1);
103 buttonPanel.add(massLabel, "growx");
105 for (JButton b: buttons) {
106 buttonPanel.add(b, "right, gap para");
109 JButton closeButton = new JButton("Close");
110 closeButton.addActionListener(new ActionListener() {
112 public void actionPerformed(ActionEvent arg0) {
113 ComponentConfigDialog.hideDialog();
116 buttonPanel.add(closeButton, "right, gap 30lp");
120 this.add(buttonPanel, "spanx, growx");
125 * Called when a change occurs, so that the fields can be updated if necessary.
126 * When overriding this method, the supermethod must always be called.
128 public void updateFields() {
130 componentNameField.setText(component.getName());
132 // Component color and "Use default color" checkbox
133 if (colorButton != null && colorDefault != null) {
134 colorButton.setIcon(new ColorIcon(component.getColor()));
136 if ((component.getColor()==null) != colorDefault.isSelected())
137 colorDefault.setSelected(component.getColor()==null);
141 if (component.isMassive()) {
142 String text = "Component mass: ";
143 text += UnitGroup.UNITS_MASS.getDefaultUnit().toStringUnit(
144 component.getComponentMass());
146 String overridetext = null;
147 if (component.isMassOverridden()) {
148 overridetext = "(overridden to " + UnitGroup.UNITS_MASS.getDefaultUnit().
149 toStringUnit(component.getOverrideMass()) + ")";
152 for (RocketComponent c = component.getParent(); c != null; c = c.getParent()) {
153 if (c.isMassOverridden() && c.getOverrideSubcomponents()) {
154 overridetext = "(overridden by " + c.getName() + ")";
158 if (overridetext != null)
159 text = text + " " + overridetext;
161 massLabel.setText(text);
163 massLabel.setText("");
168 protected JPanel materialPanel(JPanel panel, Material.Type type) {
169 return materialPanel(panel, type, "Component material:", "Component finish:");
172 protected JPanel materialPanel(JPanel panel, Material.Type type,
173 String materialString, String finishString) {
174 JLabel label = new JLabel(materialString);
175 label.setToolTipText("The component material affects the weight of the component.");
176 panel.add(label,"spanx 4, wrap rel");
178 JComboBox combo = new JComboBox(new MaterialModel(panel, component, type));
179 combo.setToolTipText("The component material affects the weight of the component.");
180 panel.add(combo,"spanx 4, growx, wrap paragraph");
183 if (component instanceof ExternalComponent) {
184 label = new JLabel(finishString);
185 String tip = "<html>The component finish affects the aerodynamic drag of the "
187 + "The value indicated is the average roughness height of the surface.";
188 label.setToolTipText(tip);
189 panel.add(label,"spanx 4, wmin 220lp, wrap rel");
191 combo = new JComboBox(new EnumModel<ExternalComponent.Finish>(component,"Finish"));
192 combo.setToolTipText(tip);
193 panel.add(combo,"spanx 4, growx, split");
195 JButton button = new JButton("Set for all");
196 button.setToolTipText("Set this finish for all components of the rocket.");
197 button.addActionListener(new ActionListener() {
199 public void actionPerformed(ActionEvent e) {
200 Finish f = ((ExternalComponent)component).getFinish();
201 Rocket rocket = component.getRocket();
204 // Store previous undo description
205 String desc = ComponentConfigDialog.getUndoDescription();
206 ComponentConfigDialog.addUndoPosition("Set rocket finish");
208 Iterator<RocketComponent> iter = rocket.deepIterator();
209 while (iter.hasNext()) {
210 RocketComponent c = iter.next();
211 if (c instanceof ExternalComponent) {
212 ((ExternalComponent)c).setFinish(f);
215 // Restore undo description
216 ComponentConfigDialog.addUndoPosition(desc);
222 panel.add(button, "wrap paragraph");
229 private JPanel overrideTab() {
230 JPanel panel = new JPanel(new MigLayout("align 50% 20%, fillx, gap rel unrel",
231 "[][65lp::][30lp::][]",""));
233 panel.add(new StyledLabel("Override the mass or center of gravity of the " +
234 component.getComponentName() + ":", Style.BOLD),"spanx, wrap 20lp");
242 bm = new BooleanModel(component, "MassOverridden");
243 check = new JCheckBox(bm);
244 check.setText("Override mass:");
245 panel.add(check, "growx 1, gapright 20lp");
247 DoubleModel m = new DoubleModel(component,"OverrideMass",UnitGroup.UNITS_MASS,0);
249 JSpinner spin = new JSpinner(m.getSpinnerModel());
250 spin.setEditor(new SpinnerEditor(spin));
251 bm.addEnableComponent(spin, true);
252 panel.add(spin,"growx 1");
254 us = new UnitSelector(m);
255 bm.addEnableComponent(us, true);
256 panel.add(us,"growx 1");
258 bs = new BasicSlider(m.getSliderModel(0, 0.03, 1.0));
259 bm.addEnableComponent(bs);
260 panel.add(bs,"growx 5, w 100lp, wrap");
264 bm = new BooleanModel(component, "CGOverridden");
265 check = new JCheckBox(bm);
266 check.setText("Override center of gravity:");
267 panel.add(check, "growx 1, gapright 20lp");
269 m = new DoubleModel(component,"OverrideCGX",UnitGroup.UNITS_LENGTH,0);
270 // Calculate suitable length for slider
272 if (component instanceof ComponentAssembly) {
275 Iterator<RocketComponent> iterator = component.deepIterator();
276 while (iterator.hasNext()) {
277 RocketComponent c = iterator.next();
278 if (c.getRelativePosition() == RocketComponent.Position.AFTER)
281 length = new DoubleModel(l);
283 length = new DoubleModel(component, "Length", UnitGroup.UNITS_LENGTH,0);
286 spin = new JSpinner(m.getSpinnerModel());
287 spin.setEditor(new SpinnerEditor(spin));
288 bm.addEnableComponent(spin, true);
289 panel.add(spin,"growx 1");
291 us = new UnitSelector(m);
292 bm.addEnableComponent(us, true);
293 panel.add(us,"growx 1");
295 bs = new BasicSlider(m.getSliderModel(new DoubleModel(0), length));
296 bm.addEnableComponent(bs);
297 panel.add(bs,"growx 5, w 100lp, wrap 35lp");
300 // Override subcomponents checkbox
301 bm = new BooleanModel(component, "OverrideSubcomponents");
302 check = new JCheckBox(bm);
303 check.setText("Override mass and CG of all subcomponents");
304 panel.add(check, "gap para, spanx, wrap para");
307 panel.add(new StyledLabel("<html>The overridden mass does not include motors.<br>" +
308 "The center of gravity is measured from the front end of the " +
309 component.getComponentName().toLowerCase()+".", -1),
310 "spanx, wrap, gap para, height 0::30lp");
316 private JPanel commentTab() {
317 JPanel panel = new JPanel(new MigLayout("fill"));
319 panel.add(new StyledLabel("Comments on the "+component.getComponentName()+":",
320 Style.BOLD), "wrap");
322 // TODO: LOW: Changes in comment from other sources not reflected in component
323 commentTextArea = new JTextArea(component.getComment());
324 commentTextArea.setLineWrap(true);
325 commentTextArea.setWrapStyleWord(true);
326 commentTextArea.setEditable(true);
327 GUIUtil.setTabToFocusing(commentTextArea);
328 commentTextArea.addFocusListener(textFieldListener);
330 panel.add(new JScrollPane(commentTextArea), "width 10px, height 10px, growx, growy");
337 private JPanel figureTab() {
338 JPanel panel = new JPanel(new MigLayout("align 20% 20%"));
340 panel.add(new StyledLabel("Figure style:", Style.BOLD), "wrap para");
343 panel.add(new JLabel("Component color:"), "gapleft para, gapright 10lp");
345 colorButton = new JButton(new ColorIcon(component.getColor()));
346 colorButton.addActionListener(new ActionListener() {
347 public void actionPerformed(ActionEvent e) {
348 Color c = component.getColor();
350 c = Prefs.getDefaultColor(component.getClass());
353 c = JColorChooser.showDialog(tabbedPane, "Choose color", c);
355 component.setColor(c);
359 panel.add(colorButton, "gapright 10lp");
361 colorDefault = new JCheckBox("Use default color");
362 if (component.getColor()==null)
363 colorDefault.setSelected(true);
364 colorDefault.addActionListener(new ActionListener() {
366 public void actionPerformed(ActionEvent e) {
367 if (colorDefault.isSelected())
368 component.setColor(null);
370 component.setColor(Prefs.getDefaultColor(component.getClass()));
373 panel.add(colorDefault, "wrap para");
376 panel.add(new JLabel("Component line style:"), "gapleft para, gapright 10lp");
378 LineStyle[] list = new LineStyle[LineStyle.values().length+1];
379 System.arraycopy(LineStyle.values(), 0, list, 1, LineStyle.values().length);
381 JComboBox combo = new JComboBox(new EnumModel<LineStyle>(component, "LineStyle",
382 list, "Default style"));
383 panel.add(combo, "spanx 2, growx, wrap 50lp");
386 JButton button = new JButton("Save as default style");
387 button.addActionListener(new ActionListener() {
389 public void actionPerformed(ActionEvent e) {
390 if (component.getColor() != null) {
391 Prefs.setDefaultColor(component.getClass(), component.getColor());
392 component.setColor(null);
394 if (component.getLineStyle() != null) {
395 Prefs.setDefaultLineStyle(component.getClass(), component.getLineStyle());
396 component.setLineStyle(null);
400 panel.add(button, "gapleft para, spanx 3, growx, wrap");
408 protected JPanel shoulderTab() {
409 JPanel panel = new JPanel(new MigLayout("fill"));
412 DoubleModel m0 = new DoubleModel(0);
418 //// Fore shoulder, not for NoseCone
420 if (!(component instanceof NoseCone)) {
421 sub = new JPanel(new MigLayout("gap rel unrel","[][65lp::][30lp::]",""));
423 sub.setBorder(BorderFactory.createTitledBorder("Fore shoulder"));
427 sub.add(new JLabel("Diameter:"));
429 m = new DoubleModel(component,"ForeShoulderRadius",2,UnitGroup.UNITS_LENGTH,0);
430 m2 = new DoubleModel(component,"ForeRadius",2,UnitGroup.UNITS_LENGTH);
432 spin = new JSpinner(m.getSpinnerModel());
433 spin.setEditor(new SpinnerEditor(spin));
434 sub.add(spin,"growx");
436 sub.add(new UnitSelector(m),"growx");
437 sub.add(new BasicSlider(m.getSliderModel(m0, m2)),"w 100lp, wrap");
441 sub.add(new JLabel("Length:"));
443 m = new DoubleModel(component,"ForeShoulderLength",UnitGroup.UNITS_LENGTH,0);
445 spin = new JSpinner(m.getSpinnerModel());
446 spin.setEditor(new SpinnerEditor(spin));
447 sub.add(spin,"growx");
449 sub.add(new UnitSelector(m),"growx");
450 sub.add(new BasicSlider(m.getSliderModel(0, 0.02, 0.2)),"w 100lp, wrap");
454 sub.add(new JLabel("Thickness:"));
456 m = new DoubleModel(component,"ForeShoulderThickness",UnitGroup.UNITS_LENGTH,0);
457 m2 = new DoubleModel(component,"ForeShoulderRadius",UnitGroup.UNITS_LENGTH);
459 spin = new JSpinner(m.getSpinnerModel());
460 spin.setEditor(new SpinnerEditor(spin));
461 sub.add(spin,"growx");
463 sub.add(new UnitSelector(m),"growx");
464 sub.add(new BasicSlider(m.getSliderModel(m0, m2)),"w 100lp, wrap");
468 bm = new BooleanModel(component, "ForeShoulderCapped");
469 check = new JCheckBox(bm);
470 check.setText("End capped");
471 check.setToolTipText("Whether the end of the shoulder is capped.");
472 sub.add(check, "spanx");
480 sub = new JPanel(new MigLayout("gap rel unrel","[][65lp::][30lp::]",""));
482 if (component instanceof NoseCone)
483 sub.setBorder(BorderFactory.createTitledBorder("Nose cone shoulder"));
485 sub.setBorder(BorderFactory.createTitledBorder("Aft shoulder"));
489 sub.add(new JLabel("Diameter:"));
491 m = new DoubleModel(component,"AftShoulderRadius",2,UnitGroup.UNITS_LENGTH,0);
492 m2 = new DoubleModel(component,"AftRadius",2,UnitGroup.UNITS_LENGTH);
494 spin = new JSpinner(m.getSpinnerModel());
495 spin.setEditor(new SpinnerEditor(spin));
496 sub.add(spin,"growx");
498 sub.add(new UnitSelector(m),"growx");
499 sub.add(new BasicSlider(m.getSliderModel(m0, m2)),"w 100lp, wrap");
503 sub.add(new JLabel("Length:"));
505 m = new DoubleModel(component,"AftShoulderLength",UnitGroup.UNITS_LENGTH,0);
507 spin = new JSpinner(m.getSpinnerModel());
508 spin.setEditor(new SpinnerEditor(spin));
509 sub.add(spin,"growx");
511 sub.add(new UnitSelector(m),"growx");
512 sub.add(new BasicSlider(m.getSliderModel(0, 0.02, 0.2)),"w 100lp, wrap");
516 sub.add(new JLabel("Thickness:"));
518 m = new DoubleModel(component,"AftShoulderThickness",UnitGroup.UNITS_LENGTH,0);
519 m2 = new DoubleModel(component,"AftShoulderRadius",UnitGroup.UNITS_LENGTH);
521 spin = new JSpinner(m.getSpinnerModel());
522 spin.setEditor(new SpinnerEditor(spin));
523 sub.add(spin,"growx");
525 sub.add(new UnitSelector(m),"growx");
526 sub.add(new BasicSlider(m.getSliderModel(m0, m2)),"w 100lp, wrap");
530 bm = new BooleanModel(component, "AftShoulderCapped");
531 check = new JCheckBox(bm);
532 check.setText("End capped");
533 check.setToolTipText("Whether the end of the shoulder is capped.");
534 sub.add(check, "spanx");
547 * Private inner class to handle events in componentNameField.
549 private class TextFieldListener implements ActionListener, FocusListener {
550 public void actionPerformed(ActionEvent e) {
553 public void focusGained(FocusEvent e) { }
554 public void focusLost(FocusEvent e) {
557 private void setName() {
558 if (!component.getName().equals(componentNameField.getText())) {
559 component.setName(componentNameField.getText());
561 if (!component.getComment().equals(commentTextArea.getText())) {
562 component.setComment(commentTextArea.getText());
568 private class ColorIcon implements Icon {
569 private final Color color;
571 public ColorIcon(Color c) {
576 public int getIconHeight() {
581 public int getIconWidth() {
586 public void paintIcon(Component c, Graphics g, int x, int y) {
588 g.setColor(Prefs.getDefaultColor(component.getClass()));
592 g.fill3DRect(x, y, getIconWidth(), getIconHeight(), false);