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;
12 import java.util.Locale;
14 import javax.swing.BorderFactory;
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.document.OpenRocketDocument;
29 import net.sf.openrocket.gui.SpinnerEditor;
30 import net.sf.openrocket.gui.adaptors.BooleanModel;
31 import net.sf.openrocket.gui.adaptors.DoubleModel;
32 import net.sf.openrocket.gui.adaptors.EnumModel;
33 import net.sf.openrocket.gui.adaptors.MaterialModel;
34 import net.sf.openrocket.gui.components.BasicSlider;
35 import net.sf.openrocket.gui.components.ColorIcon;
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.gui.util.ColorConversion;
40 import net.sf.openrocket.gui.util.GUIUtil;
41 import net.sf.openrocket.gui.util.SwingPreferences;
42 import net.sf.openrocket.l10n.Translator;
43 import net.sf.openrocket.material.Material;
44 import net.sf.openrocket.rocketcomponent.ComponentAssembly;
45 import net.sf.openrocket.rocketcomponent.ExternalComponent;
46 import net.sf.openrocket.rocketcomponent.ExternalComponent.Finish;
47 import net.sf.openrocket.rocketcomponent.NoseCone;
48 import net.sf.openrocket.rocketcomponent.RocketComponent;
49 import net.sf.openrocket.startup.Application;
50 import net.sf.openrocket.unit.UnitGroup;
51 import net.sf.openrocket.util.Invalidatable;
52 import net.sf.openrocket.util.LineStyle;
54 public class RocketComponentConfig extends JPanel {
56 private static final Translator trans = Application.getTranslator();
58 protected final OpenRocketDocument document;
59 protected final RocketComponent component;
60 protected final JTabbedPane tabbedPane;
62 private final List<Invalidatable> invalidatables = new ArrayList<Invalidatable>();
65 protected final JTextField componentNameField;
66 protected JTextArea commentTextArea;
67 private final TextFieldListener textFieldListener;
68 private JButton colorButton;
69 private JCheckBox colorDefault;
70 private JPanel buttonPanel;
72 private JLabel massLabel;
75 public RocketComponentConfig(OpenRocketDocument document, RocketComponent component) {
76 setLayout(new MigLayout("fill", "[grow, fill]"));
77 this.document = document;
78 this.component = component;
81 JLabel label = new JLabel(trans.get("RocketCompCfg.lbl.Componentname"));
82 //// The component name.
83 label.setToolTipText(trans.get("RocketCompCfg.ttip.Thecomponentname"));
84 this.add(label, "split, gapright 10");
86 componentNameField = new JTextField(15);
87 textFieldListener = new TextFieldListener();
88 componentNameField.addActionListener(textFieldListener);
89 componentNameField.addFocusListener(textFieldListener);
90 //// The component name.
91 componentNameField.setToolTipText(trans.get("RocketCompCfg.ttip.Thecomponentname"));
92 this.add(componentNameField, "growx, growy 0, wrap");
95 tabbedPane = new JTabbedPane();
96 this.add(tabbedPane, "growx, growy 1, wrap");
98 //// Override and Mass and CG override options
99 tabbedPane.addTab(trans.get("RocketCompCfg.tab.Override"), null, overrideTab(),
100 trans.get("RocketCompCfg.tab.MassandCGoverride"));
101 if (component.isMassive())
102 //// Figure and Figure style options
103 tabbedPane.addTab(trans.get("RocketCompCfg.tab.Figure"), null, figureTab(),
104 trans.get("RocketCompCfg.tab.Figstyleopt"));
105 //// Comment and Specify a comment for the component
106 tabbedPane.addTab(trans.get("RocketCompCfg.tab.Comment"), null, commentTab(),
107 trans.get("RocketCompCfg.tab.Specifyacomment"));
115 protected void addButtons(JButton... buttons) {
116 if (buttonPanel != null) {
117 this.remove(buttonPanel);
120 buttonPanel = new JPanel(new MigLayout("fill, ins 0"));
123 massLabel = new StyledLabel(trans.get("RocketCompCfg.lbl.Mass") + " ", -1);
124 buttonPanel.add(massLabel, "growx");
126 for (JButton b : buttons) {
127 buttonPanel.add(b, "right, gap para");
131 JButton closeButton = new JButton(trans.get("dlg.but.close"));
132 closeButton.addActionListener(new ActionListener() {
134 public void actionPerformed(ActionEvent arg0) {
135 ComponentConfigDialog.hideDialog();
138 buttonPanel.add(closeButton, "right, gap 30lp");
142 this.add(buttonPanel, "spanx, growx");
147 * Called when a change occurs, so that the fields can be updated if necessary.
148 * When overriding this method, the supermethod must always be called.
150 public void updateFields() {
152 componentNameField.setText(component.getName());
154 // Component color and "Use default color" checkbox
155 if (colorButton != null && colorDefault != null) {
156 colorButton.setIcon(new ColorIcon(getColor()));
158 if ((component.getColor() == null) != colorDefault.isSelected())
159 colorDefault.setSelected(component.getColor() == null);
163 if (component.isMassive()) {
165 String text = trans.get("RocketCompCfg.lbl.Componentmass") + " ";
166 text += UnitGroup.UNITS_MASS.getDefaultUnit().toStringUnit(
167 component.getComponentMass());
169 String overridetext = null;
170 if (component.isMassOverridden()) {
172 overridetext = trans.get("RocketCompCfg.lbl.overriddento") + " " + UnitGroup.UNITS_MASS.getDefaultUnit().
173 toStringUnit(component.getOverrideMass()) + ")";
176 for (RocketComponent c = component.getParent(); c != null; c = c.getParent()) {
177 if (c.isMassOverridden() && c.getOverrideSubcomponents()) {
179 overridetext = trans.get("RocketCompCfg.lbl.overriddenby") + " " + c.getName() + ")";
183 if (overridetext != null)
184 text = text + " " + overridetext;
186 massLabel.setText(text);
188 massLabel.setText("");
193 protected JPanel materialPanel(JPanel panel, Material.Type type) {
194 ////Component material: and Component finish:
195 return materialPanel(panel, type, trans.get("RocketCompCfg.lbl.Componentmaterial"),
196 trans.get("RocketCompCfg.lbl.Componentfinish"));
199 protected JPanel materialPanel(JPanel panel, Material.Type type,
200 String materialString, String finishString) {
201 JLabel label = new JLabel(materialString);
202 //// The component material affects the weight of the component.
203 label.setToolTipText(trans.get("RocketCompCfg.lbl.ttip.componentmaterialaffects"));
204 panel.add(label, "spanx 4, wrap rel");
206 JComboBox combo = new JComboBox(new MaterialModel(panel, component, type));
207 //// The component material affects the weight of the component.
208 combo.setToolTipText(trans.get("RocketCompCfg.combo.ttip.componentmaterialaffects"));
209 panel.add(combo, "spanx 4, growx, wrap paragraph");
212 if (component instanceof ExternalComponent) {
213 label = new JLabel(finishString);
214 ////<html>The component finish affects the aerodynamic drag of the component.<br>
215 String tip = trans.get("RocketCompCfg.lbl.longA1")
216 //// The value indicated is the average roughness height of the surface.
217 + trans.get("RocketCompCfg.lbl.longA2");
218 label.setToolTipText(tip);
219 panel.add(label, "spanx 4, wmin 220lp, wrap rel");
221 combo = new JComboBox(new EnumModel<ExternalComponent.Finish>(component, "Finish"));
222 combo.setToolTipText(tip);
223 panel.add(combo, "spanx 4, growx, split");
226 JButton button = new JButton(trans.get("RocketCompCfg.but.Setforall"));
227 //// Set this finish for all components of the rocket.
228 button.setToolTipText(trans.get("RocketCompCfg.but.ttip.Setforall"));
229 button.addActionListener(new ActionListener() {
231 public void actionPerformed(ActionEvent e) {
232 Finish f = ((ExternalComponent) component).getFinish();
234 document.startUndo("Set rocket finish");
237 Iterator<RocketComponent> iter = component.getRoot().iterator();
238 while (iter.hasNext()) {
239 RocketComponent c = iter.next();
240 if (c instanceof ExternalComponent) {
241 ((ExternalComponent) c).setFinish(f);
249 panel.add(button, "wrap paragraph");
256 private JPanel overrideTab() {
257 JPanel panel = new JPanel(new MigLayout("align 50% 20%, fillx, gap rel unrel",
258 "[][65lp::][30lp::][]", ""));
259 //// Override the mass or center of gravity of the
260 panel.add(new StyledLabel(trans.get("RocketCompCfg.lbl.Overridemassorcenter") + " " +
261 component.getComponentName() + ":", Style.BOLD), "spanx, wrap 20lp");
269 bm = new BooleanModel(component, "MassOverridden");
270 check = new JCheckBox(bm);
272 check.setText(trans.get("RocketCompCfg.checkbox.Overridemass"));
273 panel.add(check, "growx 1, gapright 20lp");
275 DoubleModel m = new DoubleModel(component, "OverrideMass", UnitGroup.UNITS_MASS, 0);
277 JSpinner spin = new JSpinner(m.getSpinnerModel());
278 spin.setEditor(new SpinnerEditor(spin));
279 bm.addEnableComponent(spin, true);
280 panel.add(spin, "growx 1");
282 us = new UnitSelector(m);
283 bm.addEnableComponent(us, true);
284 panel.add(us, "growx 1");
286 bs = new BasicSlider(m.getSliderModel(0, 0.03, 1.0));
287 bm.addEnableComponent(bs);
288 panel.add(bs, "growx 5, w 100lp, wrap");
292 bm = new BooleanModel(component, "CGOverridden");
293 check = new JCheckBox(bm);
294 //// Override center of gravity:"
295 check.setText(trans.get("RocketCompCfg.checkbox.Overridecenterofgrav"));
296 panel.add(check, "growx 1, gapright 20lp");
298 m = new DoubleModel(component, "OverrideCGX", UnitGroup.UNITS_LENGTH, 0);
299 // Calculate suitable length for slider
301 if (component instanceof ComponentAssembly) {
304 Iterator<RocketComponent> iterator = component.iterator(false);
305 while (iterator.hasNext()) {
306 RocketComponent c = iterator.next();
307 if (c.getRelativePosition() == RocketComponent.Position.AFTER)
310 length = new DoubleModel(l);
312 length = new DoubleModel(component, "Length", UnitGroup.UNITS_LENGTH, 0);
315 spin = new JSpinner(m.getSpinnerModel());
316 spin.setEditor(new SpinnerEditor(spin));
317 bm.addEnableComponent(spin, true);
318 panel.add(spin, "growx 1");
320 us = new UnitSelector(m);
321 bm.addEnableComponent(us, true);
322 panel.add(us, "growx 1");
324 bs = new BasicSlider(m.getSliderModel(new DoubleModel(0), length));
325 bm.addEnableComponent(bs);
326 panel.add(bs, "growx 5, w 100lp, wrap 35lp");
329 // Override subcomponents checkbox
330 bm = new BooleanModel(component, "OverrideSubcomponents");
331 check = new JCheckBox(bm);
332 //// Override mass and CG of all subcomponents
333 check.setText(trans.get("RocketCompCfg.checkbox.OverridemassandCG"));
334 panel.add(check, "gap para, spanx, wrap para");
336 //// <html>The overridden mass does not include motors.<br>
337 panel.add(new StyledLabel(trans.get("RocketCompCfg.lbl.longB1") +
338 //// The center of gravity is measured from the front end of the
339 trans.get("RocketCompCfg.lbl.longB2") + " " +
340 component.getComponentName().toLowerCase(Locale.getDefault()) + ".", -1),
341 "spanx, wrap, gap para, height 0::30lp");
347 private JPanel commentTab() {
348 JPanel panel = new JPanel(new MigLayout("fill"));
351 panel.add(new StyledLabel(trans.get("RocketCompCfg.lbl.Commentsonthe") + " " + component.getComponentName() + ":",
352 Style.BOLD), "wrap");
354 // TODO: LOW: Changes in comment from other sources not reflected in component
355 commentTextArea = new JTextArea(component.getComment());
356 commentTextArea.setLineWrap(true);
357 commentTextArea.setWrapStyleWord(true);
358 commentTextArea.setEditable(true);
359 GUIUtil.setTabToFocusing(commentTextArea);
360 commentTextArea.addFocusListener(textFieldListener);
362 panel.add(new JScrollPane(commentTextArea), "width 10px, height 10px, growx, growy");
369 private JPanel figureTab() {
370 JPanel panel = new JPanel(new MigLayout("align 20% 20%"));
373 panel.add(new StyledLabel(trans.get("RocketCompCfg.lbl.Figurestyle"), Style.BOLD), "wrap para");
375 //// Component color:
376 panel.add(new JLabel(trans.get("RocketCompCfg.lbl.Componentcolor")), "gapleft para, gapright 10lp");
378 colorButton = new JButton(new ColorIcon(getColor()));
379 colorButton.addActionListener(new ActionListener() {
381 public void actionPerformed(ActionEvent e) {
382 net.sf.openrocket.util.Color c = component.getColor();
384 c = Application.getPreferences().getDefaultColor(component.getClass());
388 Color awtColor = ColorConversion.toAwtColor(c);
389 awtColor = JColorChooser.showDialog(tabbedPane, trans.get("RocketCompCfg.lbl.Choosecolor"), awtColor);
390 c = ColorConversion.fromAwtColor(awtColor);
392 component.setColor(c);
396 panel.add(colorButton, "gapright 10lp");
398 //// Use default color
399 colorDefault = new JCheckBox(trans.get("RocketCompCfg.checkbox.Usedefaultcolor"));
400 if (component.getColor() == null)
401 colorDefault.setSelected(true);
402 colorDefault.addActionListener(new ActionListener() {
404 public void actionPerformed(ActionEvent e) {
405 if (colorDefault.isSelected())
406 component.setColor(null);
408 component.setColor(((SwingPreferences) Application.getPreferences()).getDefaultColor(component.getClass()));
411 panel.add(colorDefault, "wrap para");
413 //// Component line style:
414 panel.add(new JLabel(trans.get("RocketCompCfg.lbl.Complinestyle")), "gapleft para, gapright 10lp");
416 LineStyle[] list = new LineStyle[LineStyle.values().length + 1];
417 System.arraycopy(LineStyle.values(), 0, list, 1, LineStyle.values().length);
419 JComboBox combo = new JComboBox(new EnumModel<LineStyle>(component, "LineStyle",
421 list, trans.get("LineStyle.Defaultstyle")));
422 panel.add(combo, "spanx 2, growx, wrap 50lp");
424 //// Save as default style
425 JButton button = new JButton(trans.get("RocketCompCfg.but.Saveasdefstyle"));
426 button.addActionListener(new ActionListener() {
428 public void actionPerformed(ActionEvent e) {
429 if (component.getColor() != null) {
430 ((SwingPreferences) Application.getPreferences()).setDefaultColor(component.getClass(), component.getColor());
431 component.setColor(null);
433 if (component.getLineStyle() != null) {
434 Application.getPreferences().setDefaultLineStyle(component.getClass(), component.getLineStyle());
435 component.setLineStyle(null);
439 panel.add(button, "gapleft para, spanx 3, growx, wrap");
445 private Color getColor() {
446 net.sf.openrocket.util.Color c = component.getColor();
448 c = Application.getPreferences().getDefaultColor(component.getClass());
450 return ColorConversion.toAwtColor(c);
455 protected JPanel shoulderTab() {
456 JPanel panel = new JPanel(new MigLayout("fill"));
459 DoubleModel m0 = new DoubleModel(0);
465 //// Fore shoulder, not for NoseCone
467 if (!(component instanceof NoseCone)) {
468 sub = new JPanel(new MigLayout("gap rel unrel", "[][65lp::][30lp::]", ""));
471 sub.setBorder(BorderFactory.createTitledBorder(trans.get("RocketCompCfg.border.Foreshoulder")));
476 sub.add(new JLabel(trans.get("RocketCompCfg.lbl.Diameter")));
478 m = new DoubleModel(component, "ForeShoulderRadius", 2, UnitGroup.UNITS_LENGTH, 0);
479 m2 = new DoubleModel(component, "ForeRadius", 2, UnitGroup.UNITS_LENGTH);
481 spin = new JSpinner(m.getSpinnerModel());
482 spin.setEditor(new SpinnerEditor(spin));
483 sub.add(spin, "growx");
485 sub.add(new UnitSelector(m), "growx");
486 sub.add(new BasicSlider(m.getSliderModel(m0, m2)), "w 100lp, wrap");
490 sub.add(new JLabel(trans.get("RocketCompCfg.lbl.Length")));
492 m = new DoubleModel(component, "ForeShoulderLength", UnitGroup.UNITS_LENGTH, 0);
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(0, 0.02, 0.2)), "w 100lp, wrap");
503 sub.add(new JLabel(trans.get("RocketCompCfg.lbl.Thickness")));
505 m = new DoubleModel(component, "ForeShoulderThickness", UnitGroup.UNITS_LENGTH, 0);
506 m2 = new DoubleModel(component, "ForeShoulderRadius", UnitGroup.UNITS_LENGTH);
508 spin = new JSpinner(m.getSpinnerModel());
509 spin.setEditor(new SpinnerEditor(spin));
510 sub.add(spin, "growx");
512 sub.add(new UnitSelector(m), "growx");
513 sub.add(new BasicSlider(m.getSliderModel(m0, m2)), "w 100lp, wrap");
517 bm = new BooleanModel(component, "ForeShoulderCapped");
518 check = new JCheckBox(bm);
520 check.setText(trans.get("RocketCompCfg.checkbox.Endcapped"));
521 //// Whether the end of the shoulder is capped.
522 check.setToolTipText(trans.get("RocketCompCfg.ttip.Endcapped"));
523 sub.add(check, "spanx");
531 sub = new JPanel(new MigLayout("gap rel unrel", "[][65lp::][30lp::]", ""));
533 if (component instanceof NoseCone)
534 //// Nose cone shoulder
535 sub.setBorder(BorderFactory.createTitledBorder(trans.get("RocketCompCfg.title.Noseconeshoulder")));
538 sub.setBorder(BorderFactory.createTitledBorder(trans.get("RocketCompCfg.title.Aftshoulder")));
543 sub.add(new JLabel(trans.get("RocketCompCfg.lbl.Diameter")));
545 m = new DoubleModel(component, "AftShoulderRadius", 2, UnitGroup.UNITS_LENGTH, 0);
546 m2 = new DoubleModel(component, "AftRadius", 2, UnitGroup.UNITS_LENGTH);
548 spin = new JSpinner(m.getSpinnerModel());
549 spin.setEditor(new SpinnerEditor(spin));
550 sub.add(spin, "growx");
552 sub.add(new UnitSelector(m), "growx");
553 sub.add(new BasicSlider(m.getSliderModel(m0, m2)), "w 100lp, wrap");
557 sub.add(new JLabel(trans.get("RocketCompCfg.lbl.Length")));
559 m = new DoubleModel(component, "AftShoulderLength", UnitGroup.UNITS_LENGTH, 0);
561 spin = new JSpinner(m.getSpinnerModel());
562 spin.setEditor(new SpinnerEditor(spin));
563 sub.add(spin, "growx");
565 sub.add(new UnitSelector(m), "growx");
566 sub.add(new BasicSlider(m.getSliderModel(0, 0.02, 0.2)), "w 100lp, wrap");
570 sub.add(new JLabel(trans.get("RocketCompCfg.lbl.Thickness")));
572 m = new DoubleModel(component, "AftShoulderThickness", UnitGroup.UNITS_LENGTH, 0);
573 m2 = new DoubleModel(component, "AftShoulderRadius", UnitGroup.UNITS_LENGTH);
575 spin = new JSpinner(m.getSpinnerModel());
576 spin.setEditor(new SpinnerEditor(spin));
577 sub.add(spin, "growx");
579 sub.add(new UnitSelector(m), "growx");
580 sub.add(new BasicSlider(m.getSliderModel(m0, m2)), "w 100lp, wrap");
584 bm = new BooleanModel(component, "AftShoulderCapped");
585 check = new JCheckBox(bm);
587 check.setText(trans.get("RocketCompCfg.checkbox.Endcapped"));
588 //// Whether the end of the shoulder is capped.
589 check.setToolTipText(trans.get("RocketCompCfg.ttip.Endcapped"));
590 sub.add(check, "spanx");
603 * Private inner class to handle events in componentNameField.
605 private class TextFieldListener implements ActionListener, FocusListener {
607 public void actionPerformed(ActionEvent e) {
612 public void focusGained(FocusEvent e) {
616 public void focusLost(FocusEvent e) {
620 private void setName() {
621 if (!component.getName().equals(componentNameField.getText())) {
622 component.setName(componentNameField.getText());
624 if (!component.getComment().equals(commentTextArea.getText())) {
625 component.setComment(commentTextArea.getText());
631 protected void register(Invalidatable model) {
632 this.invalidatables.add(model);
635 public void invalidateModels() {
636 for (Invalidatable i : invalidatables) {