1 package net.sf.openrocket.gui.configdialog;
5 import java.awt.event.ActionEvent;
6 import java.awt.event.ActionListener;
7 import java.awt.event.FocusEvent;
8 import java.awt.event.FocusListener;
9 import java.util.ArrayList;
10 import java.util.Iterator;
11 import java.util.List;
13 import javax.swing.BorderFactory;
14 import javax.swing.JButton;
15 import javax.swing.JCheckBox;
16 import javax.swing.JColorChooser;
17 import javax.swing.JComboBox;
18 import javax.swing.JLabel;
19 import javax.swing.JPanel;
20 import javax.swing.JScrollPane;
21 import javax.swing.JSpinner;
22 import javax.swing.JTabbedPane;
23 import javax.swing.JTextArea;
24 import javax.swing.JTextField;
26 import net.miginfocom.swing.MigLayout;
27 import net.sf.openrocket.gui.SpinnerEditor;
28 import net.sf.openrocket.gui.adaptors.BooleanModel;
29 import net.sf.openrocket.gui.adaptors.DoubleModel;
30 import net.sf.openrocket.gui.adaptors.EnumModel;
31 import net.sf.openrocket.gui.adaptors.MaterialModel;
32 import net.sf.openrocket.gui.components.BasicSlider;
33 import net.sf.openrocket.gui.components.ColorIcon;
34 import net.sf.openrocket.gui.components.StyledLabel;
35 import net.sf.openrocket.gui.components.StyledLabel.Style;
36 import net.sf.openrocket.gui.components.UnitSelector;
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.ExternalComponent.Finish;
41 import net.sf.openrocket.rocketcomponent.NoseCone;
42 import net.sf.openrocket.rocketcomponent.Rocket;
43 import net.sf.openrocket.rocketcomponent.RocketComponent;
44 import net.sf.openrocket.unit.UnitGroup;
45 import net.sf.openrocket.util.GUIUtil;
46 import net.sf.openrocket.util.Invalidatable;
47 import net.sf.openrocket.util.LineStyle;
48 import net.sf.openrocket.util.Prefs;
50 public class RocketComponentConfig extends JPanel {
52 protected final RocketComponent component;
53 protected final JTabbedPane tabbedPane;
55 private final List<Invalidatable> invalidatables = new ArrayList<Invalidatable>();
58 protected final JTextField componentNameField;
59 protected JTextArea commentTextArea;
60 private final TextFieldListener textFieldListener;
61 private JButton colorButton;
62 private JCheckBox colorDefault;
63 private JPanel buttonPanel;
65 private JLabel massLabel;
68 public RocketComponentConfig(RocketComponent component) {
69 setLayout(new MigLayout("fill", "[grow, fill]"));
70 this.component = component;
72 JLabel label = new JLabel("Component name:");
73 label.setToolTipText("The component name.");
74 this.add(label, "split, gapright 10");
76 componentNameField = new JTextField(15);
77 textFieldListener = new TextFieldListener();
78 componentNameField.addActionListener(textFieldListener);
79 componentNameField.addFocusListener(textFieldListener);
80 componentNameField.setToolTipText("The component name.");
81 this.add(componentNameField, "growx, growy 0, wrap");
84 tabbedPane = new JTabbedPane();
85 this.add(tabbedPane, "growx, growy 1, wrap");
87 tabbedPane.addTab("Override", null, overrideTab(), "Mass and CG override options");
88 if (component.isMassive())
89 tabbedPane.addTab("Figure", null, figureTab(), "Figure style options");
90 tabbedPane.addTab("Comment", null, commentTab(), "Specify a comment for the component");
98 protected void addButtons(JButton... buttons) {
99 if (buttonPanel != null) {
100 this.remove(buttonPanel);
103 buttonPanel = new JPanel(new MigLayout("fill, ins 0"));
105 massLabel = new StyledLabel("Mass: ", -1);
106 buttonPanel.add(massLabel, "growx");
108 for (JButton b : buttons) {
109 buttonPanel.add(b, "right, gap para");
112 JButton closeButton = new JButton("Close");
113 closeButton.addActionListener(new ActionListener() {
115 public void actionPerformed(ActionEvent arg0) {
116 ComponentConfigDialog.hideDialog();
119 buttonPanel.add(closeButton, "right, gap 30lp");
123 this.add(buttonPanel, "spanx, growx");
128 * Called when a change occurs, so that the fields can be updated if necessary.
129 * When overriding this method, the supermethod must always be called.
131 public void updateFields() {
133 componentNameField.setText(component.getName());
135 // Component color and "Use default color" checkbox
136 if (colorButton != null && colorDefault != null) {
137 colorButton.setIcon(new ColorIcon(getColor()));
139 if ((component.getColor() == null) != colorDefault.isSelected())
140 colorDefault.setSelected(component.getColor() == null);
144 if (component.isMassive()) {
145 String text = "Component mass: ";
146 text += UnitGroup.UNITS_MASS.getDefaultUnit().toStringUnit(
147 component.getComponentMass());
149 String overridetext = null;
150 if (component.isMassOverridden()) {
151 overridetext = "(overridden to " + UnitGroup.UNITS_MASS.getDefaultUnit().
152 toStringUnit(component.getOverrideMass()) + ")";
155 for (RocketComponent c = component.getParent(); c != null; c = c.getParent()) {
156 if (c.isMassOverridden() && c.getOverrideSubcomponents()) {
157 overridetext = "(overridden by " + c.getName() + ")";
161 if (overridetext != null)
162 text = text + " " + overridetext;
164 massLabel.setText(text);
166 massLabel.setText("");
171 protected JPanel materialPanel(JPanel panel, Material.Type type) {
172 return materialPanel(panel, type, "Component material:", "Component finish:");
175 protected JPanel materialPanel(JPanel panel, Material.Type type,
176 String materialString, String finishString) {
177 JLabel label = new JLabel(materialString);
178 label.setToolTipText("The component material affects the weight of the component.");
179 panel.add(label, "spanx 4, wrap rel");
181 JComboBox combo = new JComboBox(new MaterialModel(panel, component, type));
182 combo.setToolTipText("The component material affects the weight of the component.");
183 panel.add(combo, "spanx 4, growx, wrap paragraph");
186 if (component instanceof ExternalComponent) {
187 label = new JLabel(finishString);
188 String tip = "<html>The component finish affects the aerodynamic drag of the "
190 + "The value indicated is the average roughness height of the surface.";
191 label.setToolTipText(tip);
192 panel.add(label, "spanx 4, wmin 220lp, wrap rel");
194 combo = new JComboBox(new EnumModel<ExternalComponent.Finish>(component, "Finish"));
195 combo.setToolTipText(tip);
196 panel.add(combo, "spanx 4, growx, split");
198 JButton button = new JButton("Set for all");
199 button.setToolTipText("Set this finish for all components of the rocket.");
200 button.addActionListener(new ActionListener() {
202 public void actionPerformed(ActionEvent e) {
203 Finish f = ((ExternalComponent) component).getFinish();
204 Rocket rocket = component.getRocket();
207 // Store previous undo description
208 String desc = ComponentConfigDialog.getUndoDescription();
209 ComponentConfigDialog.addUndoPosition("Set rocket finish");
211 Iterator<RocketComponent> iter = rocket.iterator();
212 while (iter.hasNext()) {
213 RocketComponent c = iter.next();
214 if (c instanceof ExternalComponent) {
215 ((ExternalComponent) c).setFinish(f);
218 // Restore undo description
219 ComponentConfigDialog.addUndoPosition(desc);
225 panel.add(button, "wrap paragraph");
232 private JPanel overrideTab() {
233 JPanel panel = new JPanel(new MigLayout("align 50% 20%, fillx, gap rel unrel",
234 "[][65lp::][30lp::][]", ""));
236 panel.add(new StyledLabel("Override the mass or center of gravity of the " +
237 component.getComponentName() + ":", Style.BOLD), "spanx, wrap 20lp");
245 bm = new BooleanModel(component, "MassOverridden");
246 check = new JCheckBox(bm);
247 check.setText("Override mass:");
248 panel.add(check, "growx 1, gapright 20lp");
250 DoubleModel m = new DoubleModel(component, "OverrideMass", UnitGroup.UNITS_MASS, 0);
252 JSpinner spin = new JSpinner(m.getSpinnerModel());
253 spin.setEditor(new SpinnerEditor(spin));
254 bm.addEnableComponent(spin, true);
255 panel.add(spin, "growx 1");
257 us = new UnitSelector(m);
258 bm.addEnableComponent(us, true);
259 panel.add(us, "growx 1");
261 bs = new BasicSlider(m.getSliderModel(0, 0.03, 1.0));
262 bm.addEnableComponent(bs);
263 panel.add(bs, "growx 5, w 100lp, wrap");
267 bm = new BooleanModel(component, "CGOverridden");
268 check = new JCheckBox(bm);
269 check.setText("Override center of gravity:");
270 panel.add(check, "growx 1, gapright 20lp");
272 m = new DoubleModel(component, "OverrideCGX", UnitGroup.UNITS_LENGTH, 0);
273 // Calculate suitable length for slider
275 if (component instanceof ComponentAssembly) {
278 Iterator<RocketComponent> iterator = component.iterator(false);
279 while (iterator.hasNext()) {
280 RocketComponent c = iterator.next();
281 if (c.getRelativePosition() == RocketComponent.Position.AFTER)
284 length = new DoubleModel(l);
286 length = new DoubleModel(component, "Length", UnitGroup.UNITS_LENGTH, 0);
289 spin = new JSpinner(m.getSpinnerModel());
290 spin.setEditor(new SpinnerEditor(spin));
291 bm.addEnableComponent(spin, true);
292 panel.add(spin, "growx 1");
294 us = new UnitSelector(m);
295 bm.addEnableComponent(us, true);
296 panel.add(us, "growx 1");
298 bs = new BasicSlider(m.getSliderModel(new DoubleModel(0), length));
299 bm.addEnableComponent(bs);
300 panel.add(bs, "growx 5, w 100lp, wrap 35lp");
303 // Override subcomponents checkbox
304 bm = new BooleanModel(component, "OverrideSubcomponents");
305 check = new JCheckBox(bm);
306 check.setText("Override mass and CG of all subcomponents");
307 panel.add(check, "gap para, spanx, wrap para");
310 panel.add(new StyledLabel("<html>The overridden mass does not include motors.<br>" +
311 "The center of gravity is measured from the front end of the " +
312 component.getComponentName().toLowerCase() + ".", -1),
313 "spanx, wrap, gap para, height 0::30lp");
319 private JPanel commentTab() {
320 JPanel panel = new JPanel(new MigLayout("fill"));
322 panel.add(new StyledLabel("Comments on the " + component.getComponentName() + ":",
323 Style.BOLD), "wrap");
325 // TODO: LOW: Changes in comment from other sources not reflected in component
326 commentTextArea = new JTextArea(component.getComment());
327 commentTextArea.setLineWrap(true);
328 commentTextArea.setWrapStyleWord(true);
329 commentTextArea.setEditable(true);
330 GUIUtil.setTabToFocusing(commentTextArea);
331 commentTextArea.addFocusListener(textFieldListener);
333 panel.add(new JScrollPane(commentTextArea), "width 10px, height 10px, growx, growy");
340 private JPanel figureTab() {
341 JPanel panel = new JPanel(new MigLayout("align 20% 20%"));
343 panel.add(new StyledLabel("Figure style:", Style.BOLD), "wrap para");
346 panel.add(new JLabel("Component color:"), "gapleft para, gapright 10lp");
348 colorButton = new JButton(new ColorIcon(getColor()));
349 colorButton.addActionListener(new ActionListener() {
351 public void actionPerformed(ActionEvent e) {
352 Color c = component.getColor();
354 c = Prefs.getDefaultColor(component.getClass());
357 c = JColorChooser.showDialog(tabbedPane, "Choose color", c);
359 component.setColor(c);
363 panel.add(colorButton, "gapright 10lp");
365 colorDefault = new JCheckBox("Use default color");
366 if (component.getColor() == null)
367 colorDefault.setSelected(true);
368 colorDefault.addActionListener(new ActionListener() {
370 public void actionPerformed(ActionEvent e) {
371 if (colorDefault.isSelected())
372 component.setColor(null);
374 component.setColor(Prefs.getDefaultColor(component.getClass()));
377 panel.add(colorDefault, "wrap para");
380 panel.add(new JLabel("Component line style:"), "gapleft para, gapright 10lp");
382 LineStyle[] list = new LineStyle[LineStyle.values().length + 1];
383 System.arraycopy(LineStyle.values(), 0, list, 1, LineStyle.values().length);
385 JComboBox combo = new JComboBox(new EnumModel<LineStyle>(component, "LineStyle",
386 list, "Default style"));
387 panel.add(combo, "spanx 2, growx, wrap 50lp");
390 JButton button = new JButton("Save as default style");
391 button.addActionListener(new ActionListener() {
393 public void actionPerformed(ActionEvent e) {
394 if (component.getColor() != null) {
395 Prefs.setDefaultColor(component.getClass(), component.getColor());
396 component.setColor(null);
398 if (component.getLineStyle() != null) {
399 Prefs.setDefaultLineStyle(component.getClass(), component.getLineStyle());
400 component.setLineStyle(null);
404 panel.add(button, "gapleft para, spanx 3, growx, wrap");
410 private Color getColor() {
411 Color c = component.getColor();
413 c = Prefs.getDefaultColor(component.getClass());
420 protected JPanel shoulderTab() {
421 JPanel panel = new JPanel(new MigLayout("fill"));
424 DoubleModel m0 = new DoubleModel(0);
430 //// Fore shoulder, not for NoseCone
432 if (!(component instanceof NoseCone)) {
433 sub = new JPanel(new MigLayout("gap rel unrel", "[][65lp::][30lp::]", ""));
435 sub.setBorder(BorderFactory.createTitledBorder("Fore shoulder"));
439 sub.add(new JLabel("Diameter:"));
441 m = new DoubleModel(component, "ForeShoulderRadius", 2, UnitGroup.UNITS_LENGTH, 0);
442 m2 = new DoubleModel(component, "ForeRadius", 2, UnitGroup.UNITS_LENGTH);
444 spin = new JSpinner(m.getSpinnerModel());
445 spin.setEditor(new SpinnerEditor(spin));
446 sub.add(spin, "growx");
448 sub.add(new UnitSelector(m), "growx");
449 sub.add(new BasicSlider(m.getSliderModel(m0, m2)), "w 100lp, wrap");
453 sub.add(new JLabel("Length:"));
455 m = new DoubleModel(component, "ForeShoulderLength", UnitGroup.UNITS_LENGTH, 0);
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(0, 0.02, 0.2)), "w 100lp, wrap");
466 sub.add(new JLabel("Thickness:"));
468 m = new DoubleModel(component, "ForeShoulderThickness", UnitGroup.UNITS_LENGTH, 0);
469 m2 = new DoubleModel(component, "ForeShoulderRadius", UnitGroup.UNITS_LENGTH);
471 spin = new JSpinner(m.getSpinnerModel());
472 spin.setEditor(new SpinnerEditor(spin));
473 sub.add(spin, "growx");
475 sub.add(new UnitSelector(m), "growx");
476 sub.add(new BasicSlider(m.getSliderModel(m0, m2)), "w 100lp, wrap");
480 bm = new BooleanModel(component, "ForeShoulderCapped");
481 check = new JCheckBox(bm);
482 check.setText("End capped");
483 check.setToolTipText("Whether the end of the shoulder is capped.");
484 sub.add(check, "spanx");
492 sub = new JPanel(new MigLayout("gap rel unrel", "[][65lp::][30lp::]", ""));
494 if (component instanceof NoseCone)
495 sub.setBorder(BorderFactory.createTitledBorder("Nose cone shoulder"));
497 sub.setBorder(BorderFactory.createTitledBorder("Aft shoulder"));
501 sub.add(new JLabel("Diameter:"));
503 m = new DoubleModel(component, "AftShoulderRadius", 2, UnitGroup.UNITS_LENGTH, 0);
504 m2 = new DoubleModel(component, "AftRadius", 2, UnitGroup.UNITS_LENGTH);
506 spin = new JSpinner(m.getSpinnerModel());
507 spin.setEditor(new SpinnerEditor(spin));
508 sub.add(spin, "growx");
510 sub.add(new UnitSelector(m), "growx");
511 sub.add(new BasicSlider(m.getSliderModel(m0, m2)), "w 100lp, wrap");
515 sub.add(new JLabel("Length:"));
517 m = new DoubleModel(component, "AftShoulderLength", UnitGroup.UNITS_LENGTH, 0);
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(0, 0.02, 0.2)), "w 100lp, wrap");
528 sub.add(new JLabel("Thickness:"));
530 m = new DoubleModel(component, "AftShoulderThickness", UnitGroup.UNITS_LENGTH, 0);
531 m2 = new DoubleModel(component, "AftShoulderRadius", UnitGroup.UNITS_LENGTH);
533 spin = new JSpinner(m.getSpinnerModel());
534 spin.setEditor(new SpinnerEditor(spin));
535 sub.add(spin, "growx");
537 sub.add(new UnitSelector(m), "growx");
538 sub.add(new BasicSlider(m.getSliderModel(m0, m2)), "w 100lp, wrap");
542 bm = new BooleanModel(component, "AftShoulderCapped");
543 check = new JCheckBox(bm);
544 check.setText("End capped");
545 check.setToolTipText("Whether the end of the shoulder is capped.");
546 sub.add(check, "spanx");
559 * Private inner class to handle events in componentNameField.
561 private class TextFieldListener implements ActionListener, FocusListener {
563 public void actionPerformed(ActionEvent e) {
568 public void focusGained(FocusEvent e) {
572 public void focusLost(FocusEvent e) {
576 private void setName() {
577 if (!component.getName().equals(componentNameField.getText())) {
578 component.setName(componentNameField.getText());
580 if (!component.getComment().equals(commentTextArea.getText())) {
581 component.setComment(commentTextArea.getText());
587 protected void register(Invalidatable model) {
588 this.invalidatables.add(model);
591 public void invalidateModels() {
592 for (Invalidatable i : invalidatables) {