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.document.OpenRocketDocument;
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.ColorIcon;
35 import net.sf.openrocket.gui.components.StyledLabel;
36 import net.sf.openrocket.gui.components.StyledLabel.Style;
37 import net.sf.openrocket.gui.components.UnitSelector;
38 import net.sf.openrocket.gui.util.ColorConversion;
39 import net.sf.openrocket.gui.util.GUIUtil;
40 import net.sf.openrocket.gui.util.SwingPreferences;
41 import net.sf.openrocket.l10n.Translator;
42 import net.sf.openrocket.material.Material;
43 import net.sf.openrocket.rocketcomponent.ComponentAssembly;
44 import net.sf.openrocket.rocketcomponent.ExternalComponent;
45 import net.sf.openrocket.rocketcomponent.ExternalComponent.Finish;
46 import net.sf.openrocket.rocketcomponent.NoseCone;
47 import net.sf.openrocket.rocketcomponent.RocketComponent;
48 import net.sf.openrocket.startup.Application;
49 import net.sf.openrocket.unit.UnitGroup;
50 import net.sf.openrocket.util.Invalidatable;
51 import net.sf.openrocket.util.LineStyle;
53 public class RocketComponentConfig extends JPanel {
55 private static final Translator trans = Application.getTranslator();
57 protected final OpenRocketDocument document;
58 protected final RocketComponent component;
59 protected final JTabbedPane tabbedPane;
61 private final List<Invalidatable> invalidatables = new ArrayList<Invalidatable>();
64 protected final JTextField componentNameField;
65 protected JTextArea commentTextArea;
66 private final TextFieldListener textFieldListener;
67 private JButton colorButton;
68 private JCheckBox colorDefault;
69 private JPanel buttonPanel;
71 private JLabel massLabel;
74 public RocketComponentConfig(OpenRocketDocument document, RocketComponent component) {
75 setLayout(new MigLayout("fill", "[grow, fill]"));
76 this.document = document;
77 this.component = component;
80 JLabel label = new JLabel(trans.get("RocketCompCfg.lbl.Componentname"));
81 //// The component name.
82 label.setToolTipText(trans.get("RocketCompCfg.ttip.Thecomponentname"));
83 this.add(label, "split, gapright 10");
85 componentNameField = new JTextField(15);
86 textFieldListener = new TextFieldListener();
87 componentNameField.addActionListener(textFieldListener);
88 componentNameField.addFocusListener(textFieldListener);
89 //// The component name.
90 componentNameField.setToolTipText(trans.get("RocketCompCfg.ttip.Thecomponentname"));
91 this.add(componentNameField, "growx, growy 0, wrap");
94 tabbedPane = new JTabbedPane();
95 this.add(tabbedPane, "growx, growy 1, wrap");
97 //// Override and Mass and CG override options
98 tabbedPane.addTab(trans.get("RocketCompCfg.tab.Override"), null, overrideTab(),
99 trans.get("RocketCompCfg.tab.MassandCGoverride"));
100 if (component.isMassive())
101 //// Figure and Figure style options
102 tabbedPane.addTab(trans.get("RocketCompCfg.tab.Figure"), null, figureTab(),
103 trans.get("RocketCompCfg.tab.Figstyleopt"));
104 //// Comment and Specify a comment for the component
105 tabbedPane.addTab(trans.get("RocketCompCfg.tab.Comment"), null, commentTab(),
106 trans.get("RocketCompCfg.tab.Specifyacomment"));
114 protected void addButtons(JButton... buttons) {
115 if (buttonPanel != null) {
116 this.remove(buttonPanel);
119 buttonPanel = new JPanel(new MigLayout("fill, ins 0"));
122 massLabel = new StyledLabel(trans.get("RocketCompCfg.lbl.Mass") + " ", -1);
123 buttonPanel.add(massLabel, "growx");
125 for (JButton b : buttons) {
126 buttonPanel.add(b, "right, gap para");
130 JButton closeButton = new JButton(trans.get("dlg.but.close"));
131 closeButton.addActionListener(new ActionListener() {
133 public void actionPerformed(ActionEvent arg0) {
134 ComponentConfigDialog.hideDialog();
137 buttonPanel.add(closeButton, "right, gap 30lp");
141 this.add(buttonPanel, "spanx, growx");
146 * Called when a change occurs, so that the fields can be updated if necessary.
147 * When overriding this method, the supermethod must always be called.
149 public void updateFields() {
151 componentNameField.setText(component.getName());
153 // Component color and "Use default color" checkbox
154 if (colorButton != null && colorDefault != null) {
155 colorButton.setIcon(new ColorIcon(getColor()));
157 if ((component.getColor() == null) != colorDefault.isSelected())
158 colorDefault.setSelected(component.getColor() == null);
162 if (component.isMassive()) {
164 String text = trans.get("RocketCompCfg.lbl.Componentmass") + " ";
165 text += UnitGroup.UNITS_MASS.getDefaultUnit().toStringUnit(
166 component.getComponentMass());
168 String overridetext = null;
169 if (component.isMassOverridden()) {
171 overridetext = trans.get("RocketCompCfg.lbl.overriddento") + " " + UnitGroup.UNITS_MASS.getDefaultUnit().
172 toStringUnit(component.getOverrideMass()) + ")";
175 for (RocketComponent c = component.getParent(); c != null; c = c.getParent()) {
176 if (c.isMassOverridden() && c.getOverrideSubcomponents()) {
178 overridetext = trans.get("RocketCompCfg.lbl.overriddenby") + " " + c.getName() + ")";
182 if (overridetext != null)
183 text = text + " " + overridetext;
185 massLabel.setText(text);
187 massLabel.setText("");
192 protected JPanel materialPanel(JPanel panel, Material.Type type) {
193 ////Component material: and Component finish:
194 return materialPanel(panel, type, trans.get("RocketCompCfg.lbl.Componentmaterial"),
195 trans.get("RocketCompCfg.lbl.Componentfinish"));
198 protected JPanel materialPanel(JPanel panel, Material.Type type,
199 String materialString, String finishString) {
200 JLabel label = new JLabel(materialString);
201 //// The component material affects the weight of the component.
202 label.setToolTipText(trans.get("RocketCompCfg.lbl.ttip.componentmaterialaffects"));
203 panel.add(label, "spanx 4, wrap rel");
205 JComboBox combo = new JComboBox(new MaterialModel(panel, component, type));
206 //// The component material affects the weight of the component.
207 combo.setToolTipText(trans.get("RocketCompCfg.combo.ttip.componentmaterialaffects"));
208 panel.add(combo, "spanx 4, growx, wrap paragraph");
211 if (component instanceof ExternalComponent) {
212 label = new JLabel(finishString);
213 ////<html>The component finish affects the aerodynamic drag of the component.<br>
214 String tip = trans.get("RocketCompCfg.lbl.longA1")
215 //// The value indicated is the average roughness height of the surface.
216 + trans.get("RocketCompCfg.lbl.longA2");
217 label.setToolTipText(tip);
218 panel.add(label, "spanx 4, wmin 220lp, wrap rel");
220 combo = new JComboBox(new EnumModel<ExternalComponent.Finish>(component, "Finish"));
221 combo.setToolTipText(tip);
222 panel.add(combo, "spanx 4, growx, split");
225 JButton button = new JButton(trans.get("RocketCompCfg.but.Setforall"));
226 //// Set this finish for all components of the rocket.
227 button.setToolTipText(trans.get("RocketCompCfg.but.ttip.Setforall"));
228 button.addActionListener(new ActionListener() {
230 public void actionPerformed(ActionEvent e) {
231 Finish f = ((ExternalComponent) component).getFinish();
233 document.startUndo("Set rocket finish");
236 Iterator<RocketComponent> iter = component.getRoot().iterator();
237 while (iter.hasNext()) {
238 RocketComponent c = iter.next();
239 if (c instanceof ExternalComponent) {
240 ((ExternalComponent) c).setFinish(f);
248 panel.add(button, "wrap paragraph");
255 private JPanel overrideTab() {
256 JPanel panel = new JPanel(new MigLayout("align 50% 20%, fillx, gap rel unrel",
257 "[][65lp::][30lp::][]", ""));
258 //// Override the mass or center of gravity of the
259 panel.add(new StyledLabel(trans.get("RocketCompCfg.lbl.Overridemassorcenter") + " " +
260 component.getComponentName() + ":", Style.BOLD), "spanx, wrap 20lp");
268 bm = new BooleanModel(component, "MassOverridden");
269 check = new JCheckBox(bm);
271 check.setText(trans.get("RocketCompCfg.checkbox.Overridemass"));
272 panel.add(check, "growx 1, gapright 20lp");
274 DoubleModel m = new DoubleModel(component, "OverrideMass", UnitGroup.UNITS_MASS, 0);
276 JSpinner spin = new JSpinner(m.getSpinnerModel());
277 spin.setEditor(new SpinnerEditor(spin));
278 bm.addEnableComponent(spin, true);
279 panel.add(spin, "growx 1");
281 us = new UnitSelector(m);
282 bm.addEnableComponent(us, true);
283 panel.add(us, "growx 1");
285 bs = new BasicSlider(m.getSliderModel(0, 0.03, 1.0));
286 bm.addEnableComponent(bs);
287 panel.add(bs, "growx 5, w 100lp, wrap");
291 bm = new BooleanModel(component, "CGOverridden");
292 check = new JCheckBox(bm);
293 //// Override center of gravity:"
294 check.setText(trans.get("RocketCompCfg.checkbox.Overridecenterofgrav"));
295 panel.add(check, "growx 1, gapright 20lp");
297 m = new DoubleModel(component, "OverrideCGX", UnitGroup.UNITS_LENGTH, 0);
298 // Calculate suitable length for slider
300 if (component instanceof ComponentAssembly) {
303 Iterator<RocketComponent> iterator = component.iterator(false);
304 while (iterator.hasNext()) {
305 RocketComponent c = iterator.next();
306 if (c.getRelativePosition() == RocketComponent.Position.AFTER)
309 length = new DoubleModel(l);
311 length = new DoubleModel(component, "Length", UnitGroup.UNITS_LENGTH, 0);
314 spin = new JSpinner(m.getSpinnerModel());
315 spin.setEditor(new SpinnerEditor(spin));
316 bm.addEnableComponent(spin, true);
317 panel.add(spin, "growx 1");
319 us = new UnitSelector(m);
320 bm.addEnableComponent(us, true);
321 panel.add(us, "growx 1");
323 bs = new BasicSlider(m.getSliderModel(new DoubleModel(0), length));
324 bm.addEnableComponent(bs);
325 panel.add(bs, "growx 5, w 100lp, wrap 35lp");
328 // Override subcomponents checkbox
329 bm = new BooleanModel(component, "OverrideSubcomponents");
330 check = new JCheckBox(bm);
331 //// Override mass and CG of all subcomponents
332 check.setText(trans.get("RocketCompCfg.checkbox.OverridemassandCG"));
333 panel.add(check, "gap para, spanx, wrap para");
335 //// <html>The overridden mass does not include motors.<br>
336 panel.add(new StyledLabel(trans.get("RocketCompCfg.lbl.longB1") +
337 //// The center of gravity is measured from the front end of the
338 trans.get("RocketCompCfg.lbl.longB2") + " " +
339 component.getComponentName().toLowerCase() + ".", -1),
340 "spanx, wrap, gap para, height 0::30lp");
346 private JPanel commentTab() {
347 JPanel panel = new JPanel(new MigLayout("fill"));
350 panel.add(new StyledLabel(trans.get("RocketCompCfg.lbl.Commentsonthe") + " " + component.getComponentName() + ":",
351 Style.BOLD), "wrap");
353 // TODO: LOW: Changes in comment from other sources not reflected in component
354 commentTextArea = new JTextArea(component.getComment());
355 commentTextArea.setLineWrap(true);
356 commentTextArea.setWrapStyleWord(true);
357 commentTextArea.setEditable(true);
358 GUIUtil.setTabToFocusing(commentTextArea);
359 commentTextArea.addFocusListener(textFieldListener);
361 panel.add(new JScrollPane(commentTextArea), "width 10px, height 10px, growx, growy");
368 private JPanel figureTab() {
369 JPanel panel = new JPanel(new MigLayout("align 20% 20%"));
372 panel.add(new StyledLabel(trans.get("RocketCompCfg.lbl.Figurestyle"), Style.BOLD), "wrap para");
374 //// Component color:
375 panel.add(new JLabel(trans.get("RocketCompCfg.lbl.Componentcolor")), "gapleft para, gapright 10lp");
377 colorButton = new JButton(new ColorIcon(getColor()));
378 colorButton.addActionListener(new ActionListener() {
380 public void actionPerformed(ActionEvent e) {
381 net.sf.openrocket.util.Color c = component.getColor();
383 c = Application.getPreferences().getDefaultColor(component.getClass());
387 Color awtColor = ColorConversion.toAwtColor(c);
388 awtColor = JColorChooser.showDialog(tabbedPane, trans.get("RocketCompCfg.lbl.Choosecolor"), awtColor);
389 c = ColorConversion.fromAwtColor(awtColor);
391 component.setColor(c);
395 panel.add(colorButton, "gapright 10lp");
397 //// Use default color
398 colorDefault = new JCheckBox(trans.get("RocketCompCfg.checkbox.Usedefaultcolor"));
399 if (component.getColor() == null)
400 colorDefault.setSelected(true);
401 colorDefault.addActionListener(new ActionListener() {
403 public void actionPerformed(ActionEvent e) {
404 if (colorDefault.isSelected())
405 component.setColor(null);
407 component.setColor(((SwingPreferences) Application.getPreferences()).getDefaultColor(component.getClass()));
410 panel.add(colorDefault, "wrap para");
412 //// Component line style:
413 panel.add(new JLabel(trans.get("RocketCompCfg.lbl.Complinestyle")), "gapleft para, gapright 10lp");
415 LineStyle[] list = new LineStyle[LineStyle.values().length + 1];
416 System.arraycopy(LineStyle.values(), 0, list, 1, LineStyle.values().length);
418 JComboBox combo = new JComboBox(new EnumModel<LineStyle>(component, "LineStyle",
420 list, trans.get("LineStyle.Defaultstyle")));
421 panel.add(combo, "spanx 2, growx, wrap 50lp");
423 //// Save as default style
424 JButton button = new JButton(trans.get("RocketCompCfg.but.Saveasdefstyle"));
425 button.addActionListener(new ActionListener() {
427 public void actionPerformed(ActionEvent e) {
428 if (component.getColor() != null) {
429 ((SwingPreferences) Application.getPreferences()).setDefaultColor(component.getClass(), component.getColor());
430 component.setColor(null);
432 if (component.getLineStyle() != null) {
433 Application.getPreferences().setDefaultLineStyle(component.getClass(), component.getLineStyle());
434 component.setLineStyle(null);
438 panel.add(button, "gapleft para, spanx 3, growx, wrap");
444 private Color getColor() {
445 net.sf.openrocket.util.Color c = component.getColor();
447 c = Application.getPreferences().getDefaultColor(component.getClass());
449 return ColorConversion.toAwtColor(c);
454 protected JPanel shoulderTab() {
455 JPanel panel = new JPanel(new MigLayout("fill"));
458 DoubleModel m0 = new DoubleModel(0);
464 //// Fore shoulder, not for NoseCone
466 if (!(component instanceof NoseCone)) {
467 sub = new JPanel(new MigLayout("gap rel unrel", "[][65lp::][30lp::]", ""));
470 sub.setBorder(BorderFactory.createTitledBorder(trans.get("RocketCompCfg.border.Foreshoulder")));
475 sub.add(new JLabel(trans.get("RocketCompCfg.lbl.Diameter")));
477 m = new DoubleModel(component, "ForeShoulderRadius", 2, UnitGroup.UNITS_LENGTH, 0);
478 m2 = new DoubleModel(component, "ForeRadius", 2, UnitGroup.UNITS_LENGTH);
480 spin = new JSpinner(m.getSpinnerModel());
481 spin.setEditor(new SpinnerEditor(spin));
482 sub.add(spin, "growx");
484 sub.add(new UnitSelector(m), "growx");
485 sub.add(new BasicSlider(m.getSliderModel(m0, m2)), "w 100lp, wrap");
489 sub.add(new JLabel(trans.get("RocketCompCfg.lbl.Length")));
491 m = new DoubleModel(component, "ForeShoulderLength", UnitGroup.UNITS_LENGTH, 0);
493 spin = new JSpinner(m.getSpinnerModel());
494 spin.setEditor(new SpinnerEditor(spin));
495 sub.add(spin, "growx");
497 sub.add(new UnitSelector(m), "growx");
498 sub.add(new BasicSlider(m.getSliderModel(0, 0.02, 0.2)), "w 100lp, wrap");
502 sub.add(new JLabel(trans.get("RocketCompCfg.lbl.Thickness")));
504 m = new DoubleModel(component, "ForeShoulderThickness", UnitGroup.UNITS_LENGTH, 0);
505 m2 = new DoubleModel(component, "ForeShoulderRadius", UnitGroup.UNITS_LENGTH);
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(m0, m2)), "w 100lp, wrap");
516 bm = new BooleanModel(component, "ForeShoulderCapped");
517 check = new JCheckBox(bm);
519 check.setText(trans.get("RocketCompCfg.checkbox.Endcapped"));
520 //// Whether the end of the shoulder is capped.
521 check.setToolTipText(trans.get("RocketCompCfg.ttip.Endcapped"));
522 sub.add(check, "spanx");
530 sub = new JPanel(new MigLayout("gap rel unrel", "[][65lp::][30lp::]", ""));
532 if (component instanceof NoseCone)
533 //// Nose cone shoulder
534 sub.setBorder(BorderFactory.createTitledBorder(trans.get("RocketCompCfg.title.Noseconeshoulder")));
537 sub.setBorder(BorderFactory.createTitledBorder(trans.get("RocketCompCfg.title.Aftshoulder")));
542 sub.add(new JLabel(trans.get("RocketCompCfg.lbl.Diameter")));
544 m = new DoubleModel(component, "AftShoulderRadius", 2, UnitGroup.UNITS_LENGTH, 0);
545 m2 = new DoubleModel(component, "AftRadius", 2, UnitGroup.UNITS_LENGTH);
547 spin = new JSpinner(m.getSpinnerModel());
548 spin.setEditor(new SpinnerEditor(spin));
549 sub.add(spin, "growx");
551 sub.add(new UnitSelector(m), "growx");
552 sub.add(new BasicSlider(m.getSliderModel(m0, m2)), "w 100lp, wrap");
556 sub.add(new JLabel(trans.get("RocketCompCfg.lbl.Length")));
558 m = new DoubleModel(component, "AftShoulderLength", UnitGroup.UNITS_LENGTH, 0);
560 spin = new JSpinner(m.getSpinnerModel());
561 spin.setEditor(new SpinnerEditor(spin));
562 sub.add(spin, "growx");
564 sub.add(new UnitSelector(m), "growx");
565 sub.add(new BasicSlider(m.getSliderModel(0, 0.02, 0.2)), "w 100lp, wrap");
569 sub.add(new JLabel(trans.get("RocketCompCfg.lbl.Thickness")));
571 m = new DoubleModel(component, "AftShoulderThickness", UnitGroup.UNITS_LENGTH, 0);
572 m2 = new DoubleModel(component, "AftShoulderRadius", UnitGroup.UNITS_LENGTH);
574 spin = new JSpinner(m.getSpinnerModel());
575 spin.setEditor(new SpinnerEditor(spin));
576 sub.add(spin, "growx");
578 sub.add(new UnitSelector(m), "growx");
579 sub.add(new BasicSlider(m.getSliderModel(m0, m2)), "w 100lp, wrap");
583 bm = new BooleanModel(component, "AftShoulderCapped");
584 check = new JCheckBox(bm);
586 check.setText(trans.get("RocketCompCfg.checkbox.Endcapped"));
587 //// Whether the end of the shoulder is capped.
588 check.setToolTipText(trans.get("RocketCompCfg.ttip.Endcapped"));
589 sub.add(check, "spanx");
602 * Private inner class to handle events in componentNameField.
604 private class TextFieldListener implements ActionListener, FocusListener {
606 public void actionPerformed(ActionEvent e) {
611 public void focusGained(FocusEvent e) {
615 public void focusLost(FocusEvent e) {
619 private void setName() {
620 if (!component.getName().equals(componentNameField.getText())) {
621 component.setName(componentNameField.getText());
623 if (!component.getComment().equals(commentTextArea.getText())) {
624 component.setComment(commentTextArea.getText());
630 protected void register(Invalidatable model) {
631 this.invalidatables.add(model);
634 public void invalidateModels() {
635 for (Invalidatable i : invalidatables) {