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.ArrayList;
12 import java.util.Iterator;
13 import java.util.List;
15 import javax.swing.BorderFactory;
16 import javax.swing.Icon;
17 import javax.swing.JButton;
18 import javax.swing.JCheckBox;
19 import javax.swing.JColorChooser;
20 import javax.swing.JComboBox;
21 import javax.swing.JLabel;
22 import javax.swing.JPanel;
23 import javax.swing.JScrollPane;
24 import javax.swing.JSpinner;
25 import javax.swing.JTabbedPane;
26 import javax.swing.JTextArea;
27 import javax.swing.JTextField;
29 import net.miginfocom.swing.MigLayout;
30 import net.sf.openrocket.gui.SpinnerEditor;
31 import net.sf.openrocket.gui.adaptors.BooleanModel;
32 import net.sf.openrocket.gui.adaptors.DoubleModel;
33 import net.sf.openrocket.gui.adaptors.EnumModel;
34 import net.sf.openrocket.gui.adaptors.MaterialModel;
35 import net.sf.openrocket.gui.components.BasicSlider;
36 import net.sf.openrocket.gui.components.StyledLabel;
37 import net.sf.openrocket.gui.components.StyledLabel.Style;
38 import net.sf.openrocket.gui.components.UnitSelector;
39 import net.sf.openrocket.material.Material;
40 import net.sf.openrocket.rocketcomponent.ComponentAssembly;
41 import net.sf.openrocket.rocketcomponent.ExternalComponent;
42 import net.sf.openrocket.rocketcomponent.ExternalComponent.Finish;
43 import net.sf.openrocket.rocketcomponent.NoseCone;
44 import net.sf.openrocket.rocketcomponent.Rocket;
45 import net.sf.openrocket.rocketcomponent.RocketComponent;
46 import net.sf.openrocket.unit.UnitGroup;
47 import net.sf.openrocket.util.GUIUtil;
48 import net.sf.openrocket.util.Invalidatable;
49 import net.sf.openrocket.util.LineStyle;
50 import net.sf.openrocket.util.Prefs;
52 public class RocketComponentConfig extends JPanel {
54 protected final RocketComponent component;
55 protected final JTabbedPane tabbedPane;
57 private final List<Invalidatable> invalidatables = new ArrayList<Invalidatable>();
60 protected final JTextField componentNameField;
61 protected JTextArea commentTextArea;
62 private final TextFieldListener textFieldListener;
63 private JButton colorButton;
64 private JCheckBox colorDefault;
65 private JPanel buttonPanel;
67 private JLabel massLabel;
70 public RocketComponentConfig(RocketComponent component) {
71 setLayout(new MigLayout("fill", "[grow, fill]"));
72 this.component = component;
74 JLabel label = new JLabel("Component name:");
75 label.setToolTipText("The component name.");
76 this.add(label, "split, gapright 10");
78 componentNameField = new JTextField(15);
79 textFieldListener = new TextFieldListener();
80 componentNameField.addActionListener(textFieldListener);
81 componentNameField.addFocusListener(textFieldListener);
82 componentNameField.setToolTipText("The component name.");
83 this.add(componentNameField, "growx, growy 0, wrap");
86 tabbedPane = new JTabbedPane();
87 this.add(tabbedPane, "growx, growy 1, wrap");
89 tabbedPane.addTab("Override", null, overrideTab(), "Mass and CG override options");
90 if (component.isMassive())
91 tabbedPane.addTab("Figure", null, figureTab(), "Figure style options");
92 tabbedPane.addTab("Comment", null, commentTab(), "Specify a comment for the component");
100 protected void addButtons(JButton... buttons) {
101 if (buttonPanel != null) {
102 this.remove(buttonPanel);
105 buttonPanel = new JPanel(new MigLayout("fill, ins 0"));
107 massLabel = new StyledLabel("Mass: ", -1);
108 buttonPanel.add(massLabel, "growx");
110 for (JButton b : buttons) {
111 buttonPanel.add(b, "right, gap para");
114 JButton closeButton = new JButton("Close");
115 closeButton.addActionListener(new ActionListener() {
117 public void actionPerformed(ActionEvent arg0) {
118 ComponentConfigDialog.hideDialog();
121 buttonPanel.add(closeButton, "right, gap 30lp");
125 this.add(buttonPanel, "spanx, growx");
130 * Called when a change occurs, so that the fields can be updated if necessary.
131 * When overriding this method, the supermethod must always be called.
133 public void updateFields() {
135 componentNameField.setText(component.getName());
137 // Component color and "Use default color" checkbox
138 if (colorButton != null && colorDefault != null) {
139 colorButton.setIcon(new ColorIcon(component.getColor()));
141 if ((component.getColor() == null) != colorDefault.isSelected())
142 colorDefault.setSelected(component.getColor() == null);
146 if (component.isMassive()) {
147 String text = "Component mass: ";
148 text += UnitGroup.UNITS_MASS.getDefaultUnit().toStringUnit(
149 component.getComponentMass());
151 String overridetext = null;
152 if (component.isMassOverridden()) {
153 overridetext = "(overridden to " + UnitGroup.UNITS_MASS.getDefaultUnit().
154 toStringUnit(component.getOverrideMass()) + ")";
157 for (RocketComponent c = component.getParent(); c != null; c = c.getParent()) {
158 if (c.isMassOverridden() && c.getOverrideSubcomponents()) {
159 overridetext = "(overridden by " + c.getName() + ")";
163 if (overridetext != null)
164 text = text + " " + overridetext;
166 massLabel.setText(text);
168 massLabel.setText("");
173 protected JPanel materialPanel(JPanel panel, Material.Type type) {
174 return materialPanel(panel, type, "Component material:", "Component finish:");
177 protected JPanel materialPanel(JPanel panel, Material.Type type,
178 String materialString, String finishString) {
179 JLabel label = new JLabel(materialString);
180 label.setToolTipText("The component material affects the weight of the component.");
181 panel.add(label, "spanx 4, wrap rel");
183 JComboBox combo = new JComboBox(new MaterialModel(panel, component, type));
184 combo.setToolTipText("The component material affects the weight of the component.");
185 panel.add(combo, "spanx 4, growx, wrap paragraph");
188 if (component instanceof ExternalComponent) {
189 label = new JLabel(finishString);
190 String tip = "<html>The component finish affects the aerodynamic drag of the "
192 + "The value indicated is the average roughness height of the surface.";
193 label.setToolTipText(tip);
194 panel.add(label, "spanx 4, wmin 220lp, wrap rel");
196 combo = new JComboBox(new EnumModel<ExternalComponent.Finish>(component, "Finish"));
197 combo.setToolTipText(tip);
198 panel.add(combo, "spanx 4, growx, split");
200 JButton button = new JButton("Set for all");
201 button.setToolTipText("Set this finish for all components of the rocket.");
202 button.addActionListener(new ActionListener() {
204 public void actionPerformed(ActionEvent e) {
205 Finish f = ((ExternalComponent) component).getFinish();
206 Rocket rocket = component.getRocket();
209 // Store previous undo description
210 String desc = ComponentConfigDialog.getUndoDescription();
211 ComponentConfigDialog.addUndoPosition("Set rocket finish");
213 Iterator<RocketComponent> iter = rocket.iterator();
214 while (iter.hasNext()) {
215 RocketComponent c = iter.next();
216 if (c instanceof ExternalComponent) {
217 ((ExternalComponent) c).setFinish(f);
220 // Restore undo description
221 ComponentConfigDialog.addUndoPosition(desc);
227 panel.add(button, "wrap paragraph");
234 private JPanel overrideTab() {
235 JPanel panel = new JPanel(new MigLayout("align 50% 20%, fillx, gap rel unrel",
236 "[][65lp::][30lp::][]", ""));
238 panel.add(new StyledLabel("Override the mass or center of gravity of the " +
239 component.getComponentName() + ":", Style.BOLD), "spanx, wrap 20lp");
247 bm = new BooleanModel(component, "MassOverridden");
248 check = new JCheckBox(bm);
249 check.setText("Override mass:");
250 panel.add(check, "growx 1, gapright 20lp");
252 DoubleModel m = new DoubleModel(component, "OverrideMass", UnitGroup.UNITS_MASS, 0);
254 JSpinner spin = new JSpinner(m.getSpinnerModel());
255 spin.setEditor(new SpinnerEditor(spin));
256 bm.addEnableComponent(spin, true);
257 panel.add(spin, "growx 1");
259 us = new UnitSelector(m);
260 bm.addEnableComponent(us, true);
261 panel.add(us, "growx 1");
263 bs = new BasicSlider(m.getSliderModel(0, 0.03, 1.0));
264 bm.addEnableComponent(bs);
265 panel.add(bs, "growx 5, w 100lp, wrap");
269 bm = new BooleanModel(component, "CGOverridden");
270 check = new JCheckBox(bm);
271 check.setText("Override center of gravity:");
272 panel.add(check, "growx 1, gapright 20lp");
274 m = new DoubleModel(component, "OverrideCGX", UnitGroup.UNITS_LENGTH, 0);
275 // Calculate suitable length for slider
277 if (component instanceof ComponentAssembly) {
280 Iterator<RocketComponent> iterator = component.iterator(false);
281 while (iterator.hasNext()) {
282 RocketComponent c = iterator.next();
283 if (c.getRelativePosition() == RocketComponent.Position.AFTER)
286 length = new DoubleModel(l);
288 length = new DoubleModel(component, "Length", UnitGroup.UNITS_LENGTH, 0);
291 spin = new JSpinner(m.getSpinnerModel());
292 spin.setEditor(new SpinnerEditor(spin));
293 bm.addEnableComponent(spin, true);
294 panel.add(spin, "growx 1");
296 us = new UnitSelector(m);
297 bm.addEnableComponent(us, true);
298 panel.add(us, "growx 1");
300 bs = new BasicSlider(m.getSliderModel(new DoubleModel(0), length));
301 bm.addEnableComponent(bs);
302 panel.add(bs, "growx 5, w 100lp, wrap 35lp");
305 // Override subcomponents checkbox
306 bm = new BooleanModel(component, "OverrideSubcomponents");
307 check = new JCheckBox(bm);
308 check.setText("Override mass and CG of all subcomponents");
309 panel.add(check, "gap para, spanx, wrap para");
312 panel.add(new StyledLabel("<html>The overridden mass does not include motors.<br>" +
313 "The center of gravity is measured from the front end of the " +
314 component.getComponentName().toLowerCase() + ".", -1),
315 "spanx, wrap, gap para, height 0::30lp");
321 private JPanel commentTab() {
322 JPanel panel = new JPanel(new MigLayout("fill"));
324 panel.add(new StyledLabel("Comments on the " + component.getComponentName() + ":",
325 Style.BOLD), "wrap");
327 // TODO: LOW: Changes in comment from other sources not reflected in component
328 commentTextArea = new JTextArea(component.getComment());
329 commentTextArea.setLineWrap(true);
330 commentTextArea.setWrapStyleWord(true);
331 commentTextArea.setEditable(true);
332 GUIUtil.setTabToFocusing(commentTextArea);
333 commentTextArea.addFocusListener(textFieldListener);
335 panel.add(new JScrollPane(commentTextArea), "width 10px, height 10px, growx, growy");
342 private JPanel figureTab() {
343 JPanel panel = new JPanel(new MigLayout("align 20% 20%"));
345 panel.add(new StyledLabel("Figure style:", Style.BOLD), "wrap para");
348 panel.add(new JLabel("Component color:"), "gapleft para, gapright 10lp");
350 colorButton = new JButton(new ColorIcon(component.getColor()));
351 colorButton.addActionListener(new ActionListener() {
353 public void actionPerformed(ActionEvent e) {
354 Color c = component.getColor();
356 c = Prefs.getDefaultColor(component.getClass());
359 c = JColorChooser.showDialog(tabbedPane, "Choose color", c);
361 component.setColor(c);
365 panel.add(colorButton, "gapright 10lp");
367 colorDefault = new JCheckBox("Use default color");
368 if (component.getColor() == null)
369 colorDefault.setSelected(true);
370 colorDefault.addActionListener(new ActionListener() {
372 public void actionPerformed(ActionEvent e) {
373 if (colorDefault.isSelected())
374 component.setColor(null);
376 component.setColor(Prefs.getDefaultColor(component.getClass()));
379 panel.add(colorDefault, "wrap para");
382 panel.add(new JLabel("Component line style:"), "gapleft para, gapright 10lp");
384 LineStyle[] list = new LineStyle[LineStyle.values().length + 1];
385 System.arraycopy(LineStyle.values(), 0, list, 1, LineStyle.values().length);
387 JComboBox combo = new JComboBox(new EnumModel<LineStyle>(component, "LineStyle",
388 list, "Default style"));
389 panel.add(combo, "spanx 2, growx, wrap 50lp");
392 JButton button = new JButton("Save as default style");
393 button.addActionListener(new ActionListener() {
395 public void actionPerformed(ActionEvent e) {
396 if (component.getColor() != null) {
397 Prefs.setDefaultColor(component.getClass(), component.getColor());
398 component.setColor(null);
400 if (component.getLineStyle() != null) {
401 Prefs.setDefaultLineStyle(component.getClass(), component.getLineStyle());
402 component.setLineStyle(null);
406 panel.add(button, "gapleft para, spanx 3, growx, wrap");
414 protected JPanel shoulderTab() {
415 JPanel panel = new JPanel(new MigLayout("fill"));
418 DoubleModel m0 = new DoubleModel(0);
424 //// Fore shoulder, not for NoseCone
426 if (!(component instanceof NoseCone)) {
427 sub = new JPanel(new MigLayout("gap rel unrel", "[][65lp::][30lp::]", ""));
429 sub.setBorder(BorderFactory.createTitledBorder("Fore shoulder"));
433 sub.add(new JLabel("Diameter:"));
435 m = new DoubleModel(component, "ForeShoulderRadius", 2, UnitGroup.UNITS_LENGTH, 0);
436 m2 = new DoubleModel(component, "ForeRadius", 2, UnitGroup.UNITS_LENGTH);
438 spin = new JSpinner(m.getSpinnerModel());
439 spin.setEditor(new SpinnerEditor(spin));
440 sub.add(spin, "growx");
442 sub.add(new UnitSelector(m), "growx");
443 sub.add(new BasicSlider(m.getSliderModel(m0, m2)), "w 100lp, wrap");
447 sub.add(new JLabel("Length:"));
449 m = new DoubleModel(component, "ForeShoulderLength", UnitGroup.UNITS_LENGTH, 0);
451 spin = new JSpinner(m.getSpinnerModel());
452 spin.setEditor(new SpinnerEditor(spin));
453 sub.add(spin, "growx");
455 sub.add(new UnitSelector(m), "growx");
456 sub.add(new BasicSlider(m.getSliderModel(0, 0.02, 0.2)), "w 100lp, wrap");
460 sub.add(new JLabel("Thickness:"));
462 m = new DoubleModel(component, "ForeShoulderThickness", UnitGroup.UNITS_LENGTH, 0);
463 m2 = new DoubleModel(component, "ForeShoulderRadius", UnitGroup.UNITS_LENGTH);
465 spin = new JSpinner(m.getSpinnerModel());
466 spin.setEditor(new SpinnerEditor(spin));
467 sub.add(spin, "growx");
469 sub.add(new UnitSelector(m), "growx");
470 sub.add(new BasicSlider(m.getSliderModel(m0, m2)), "w 100lp, wrap");
474 bm = new BooleanModel(component, "ForeShoulderCapped");
475 check = new JCheckBox(bm);
476 check.setText("End capped");
477 check.setToolTipText("Whether the end of the shoulder is capped.");
478 sub.add(check, "spanx");
486 sub = new JPanel(new MigLayout("gap rel unrel", "[][65lp::][30lp::]", ""));
488 if (component instanceof NoseCone)
489 sub.setBorder(BorderFactory.createTitledBorder("Nose cone shoulder"));
491 sub.setBorder(BorderFactory.createTitledBorder("Aft shoulder"));
495 sub.add(new JLabel("Diameter:"));
497 m = new DoubleModel(component, "AftShoulderRadius", 2, UnitGroup.UNITS_LENGTH, 0);
498 m2 = new DoubleModel(component, "AftRadius", 2, UnitGroup.UNITS_LENGTH);
500 spin = new JSpinner(m.getSpinnerModel());
501 spin.setEditor(new SpinnerEditor(spin));
502 sub.add(spin, "growx");
504 sub.add(new UnitSelector(m), "growx");
505 sub.add(new BasicSlider(m.getSliderModel(m0, m2)), "w 100lp, wrap");
509 sub.add(new JLabel("Length:"));
511 m = new DoubleModel(component, "AftShoulderLength", UnitGroup.UNITS_LENGTH, 0);
513 spin = new JSpinner(m.getSpinnerModel());
514 spin.setEditor(new SpinnerEditor(spin));
515 sub.add(spin, "growx");
517 sub.add(new UnitSelector(m), "growx");
518 sub.add(new BasicSlider(m.getSliderModel(0, 0.02, 0.2)), "w 100lp, wrap");
522 sub.add(new JLabel("Thickness:"));
524 m = new DoubleModel(component, "AftShoulderThickness", UnitGroup.UNITS_LENGTH, 0);
525 m2 = new DoubleModel(component, "AftShoulderRadius", UnitGroup.UNITS_LENGTH);
527 spin = new JSpinner(m.getSpinnerModel());
528 spin.setEditor(new SpinnerEditor(spin));
529 sub.add(spin, "growx");
531 sub.add(new UnitSelector(m), "growx");
532 sub.add(new BasicSlider(m.getSliderModel(m0, m2)), "w 100lp, wrap");
536 bm = new BooleanModel(component, "AftShoulderCapped");
537 check = new JCheckBox(bm);
538 check.setText("End capped");
539 check.setToolTipText("Whether the end of the shoulder is capped.");
540 sub.add(check, "spanx");
553 * Private inner class to handle events in componentNameField.
555 private class TextFieldListener implements ActionListener, FocusListener {
557 public void actionPerformed(ActionEvent e) {
562 public void focusGained(FocusEvent e) {
566 public void focusLost(FocusEvent e) {
570 private void setName() {
571 if (!component.getName().equals(componentNameField.getText())) {
572 component.setName(componentNameField.getText());
574 if (!component.getComment().equals(commentTextArea.getText())) {
575 component.setComment(commentTextArea.getText());
581 private class ColorIcon implements Icon {
582 private final Color color;
584 public ColorIcon(Color c) {
589 public int getIconHeight() {
594 public int getIconWidth() {
599 public void paintIcon(Component c, Graphics g, int x, int y) {
601 g.setColor(Prefs.getDefaultColor(component.getClass()));
605 g.fill3DRect(x, y, getIconWidth(), getIconHeight(), false);
610 protected void register(Invalidatable model) {
611 this.invalidatables.add(model);
614 public void invalidateModels() {
615 for (Invalidatable i : invalidatables) {