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.database.ComponentPresetDatabase;
29 import net.sf.openrocket.document.OpenRocketDocument;
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.adaptors.PresetModel;
36 import net.sf.openrocket.gui.components.BasicSlider;
37 import net.sf.openrocket.gui.components.ColorIcon;
38 import net.sf.openrocket.gui.components.StyledLabel;
39 import net.sf.openrocket.gui.components.StyledLabel.Style;
40 import net.sf.openrocket.gui.components.UnitSelector;
41 import net.sf.openrocket.gui.util.ColorConversion;
42 import net.sf.openrocket.gui.util.GUIUtil;
43 import net.sf.openrocket.gui.util.SwingPreferences;
44 import net.sf.openrocket.l10n.Translator;
45 import net.sf.openrocket.material.Material;
46 import net.sf.openrocket.preset.ComponentPreset;
47 import net.sf.openrocket.rocketcomponent.ComponentAssembly;
48 import net.sf.openrocket.rocketcomponent.ExternalComponent;
49 import net.sf.openrocket.rocketcomponent.ExternalComponent.Finish;
50 import net.sf.openrocket.rocketcomponent.NoseCone;
51 import net.sf.openrocket.rocketcomponent.RocketComponent;
52 import net.sf.openrocket.startup.Application;
53 import net.sf.openrocket.unit.UnitGroup;
54 import net.sf.openrocket.util.Invalidatable;
55 import net.sf.openrocket.util.LineStyle;
57 public class RocketComponentConfig extends JPanel {
59 private static final Translator trans = Application.getTranslator();
61 protected final OpenRocketDocument document;
62 protected final RocketComponent component;
63 protected final JTabbedPane tabbedPane;
65 private final List<Invalidatable> invalidatables = new ArrayList<Invalidatable>();
67 private JComboBox presetComboBox;
68 private PresetModel presetModel;
70 protected final JTextField componentNameField;
71 protected JTextArea commentTextArea;
72 private final TextFieldListener textFieldListener;
73 private JButton colorButton;
74 private JCheckBox colorDefault;
75 private JPanel buttonPanel;
77 private JLabel infoLabel;
80 public RocketComponentConfig(OpenRocketDocument document, RocketComponent component) {
81 setLayout(new MigLayout("fill", "[min,align right]:10[fill, grow]"));
82 this.document = document;
83 this.component = component;
86 JLabel label = new JLabel(trans.get("RocketCompCfg.lbl.Componentname"));
87 //// The component name.
88 label.setToolTipText(trans.get("RocketCompCfg.ttip.Thecomponentname"));
89 this.add(label, "spanx, split");
91 componentNameField = new JTextField(15);
92 textFieldListener = new TextFieldListener();
93 componentNameField.addActionListener(textFieldListener);
94 componentNameField.addFocusListener(textFieldListener);
95 //// The component name.
96 componentNameField.setToolTipText(trans.get("RocketCompCfg.ttip.Thecomponentname"));
97 this.add(componentNameField, "growx");
99 if (component.getPresetType() != null) {
100 // If the component supports a preset, show the preset selection box.
101 presetModel = new PresetModel(this, document, component);
102 ((ComponentPresetDatabase) Application.getComponentPresetDao()).addDatabaseListener(presetModel);
103 presetComboBox = new JComboBox(presetModel);
104 presetComboBox.setEditable(false);
105 this.add(presetComboBox, "");
109 tabbedPane = new JTabbedPane();
110 this.add(tabbedPane, "newline, span, growx, growy 1, wrap");
112 //// Override and Mass and CG override options
113 tabbedPane.addTab(trans.get("RocketCompCfg.tab.Override"), null, overrideTab(),
114 trans.get("RocketCompCfg.tab.MassandCGoverride"));
115 if (component.isMassive())
116 //// Figure and Figure style options
117 tabbedPane.addTab(trans.get("RocketCompCfg.tab.Figure"), null, figureTab(),
118 trans.get("RocketCompCfg.tab.Figstyleopt"));
119 //// Comment and Specify a comment for the component
120 tabbedPane.addTab(trans.get("RocketCompCfg.tab.Comment"), null, commentTab(),
121 trans.get("RocketCompCfg.tab.Specifyacomment"));
129 protected void addButtons(JButton... buttons) {
130 if (buttonPanel != null) {
131 this.remove(buttonPanel);
134 buttonPanel = new JPanel(new MigLayout("fill, ins 0"));
137 infoLabel = new StyledLabel(" ", -1);
138 buttonPanel.add(infoLabel, "growx");
140 for (JButton b : buttons) {
141 buttonPanel.add(b, "right, gap para");
145 JButton closeButton = new JButton(trans.get("dlg.but.close"));
146 closeButton.addActionListener(new ActionListener() {
148 public void actionPerformed(ActionEvent arg0) {
149 ComponentConfigDialog.hideDialog();
152 buttonPanel.add(closeButton, "right, gap 30lp");
156 this.add(buttonPanel, "spanx, growx");
161 * Called when a change occurs, so that the fields can be updated if necessary.
162 * When overriding this method, the supermethod must always be called.
164 public void updateFields() {
166 componentNameField.setText(component.getName());
168 // Component color and "Use default color" checkbox
169 if (colorButton != null && colorDefault != null) {
170 colorButton.setIcon(new ColorIcon(getColor()));
172 if ((component.getColor() == null) != colorDefault.isSelected())
173 colorDefault.setSelected(component.getColor() == null);
177 StringBuilder sb = new StringBuilder();
179 if (component.getPresetComponent() != null) {
180 ComponentPreset preset = component.getPresetComponent();
181 sb.append(preset.getManufacturer() + " " + preset.getPartNo() + " ");
184 if (component.isMassive()) {
185 sb.append(trans.get("RocketCompCfg.lbl.Componentmass") + " ");
186 sb.append(UnitGroup.UNITS_MASS.getDefaultUnit().toStringUnit(
187 component.getComponentMass()));
189 String overridetext = null;
190 if (component.isMassOverridden()) {
191 overridetext = trans.get("RocketCompCfg.lbl.overriddento") + " " + UnitGroup.UNITS_MASS.getDefaultUnit().
192 toStringUnit(component.getOverrideMass()) + ")";
195 for (RocketComponent c = component.getParent(); c != null; c = c.getParent()) {
196 if (c.isMassOverridden() && c.getOverrideSubcomponents()) {
197 overridetext = trans.get("RocketCompCfg.lbl.overriddenby") + " " + c.getName() + ")";
201 if (overridetext != null) {
202 sb.append(" " + overridetext);
205 infoLabel.setText(sb.toString());
207 infoLabel.setText("");
212 protected JPanel materialPanel(JPanel panel, Material.Type type) {
213 ////Component material: and Component finish:
214 return materialPanel(panel, type, trans.get("RocketCompCfg.lbl.Componentmaterial"),
215 trans.get("RocketCompCfg.lbl.Componentfinish"));
218 protected JPanel materialPanel(JPanel panel, Material.Type type,
219 String materialString, String finishString) {
221 JLabel label = new JLabel(materialString);
222 //// The component material affects the weight of the component.
223 label.setToolTipText(trans.get("RocketCompCfg.lbl.ttip.componentmaterialaffects"));
224 panel.add(label, "spanx 4, wrap rel");
226 JComboBox combo = new JComboBox(new MaterialModel(panel, component, type));
227 //// The component material affects the weight of the component.
228 combo.setToolTipText(trans.get("RocketCompCfg.combo.ttip.componentmaterialaffects"));
229 panel.add(combo, "spanx 4, growx, wrap paragraph");
232 if (component instanceof ExternalComponent) {
233 label = new JLabel(finishString);
234 ////<html>The component finish affects the aerodynamic drag of the component.<br>
235 String tip = trans.get("RocketCompCfg.lbl.longA1")
236 //// The value indicated is the average roughness height of the surface.
237 + trans.get("RocketCompCfg.lbl.longA2");
238 label.setToolTipText(tip);
239 panel.add(label, "spanx 4, wmin 220lp, wrap rel");
241 combo = new JComboBox(new EnumModel<ExternalComponent.Finish>(component, "Finish"));
242 combo.setToolTipText(tip);
243 panel.add(combo, "spanx 4, growx, split");
246 JButton button = new JButton(trans.get("RocketCompCfg.but.Setforall"));
247 //// Set this finish for all components of the rocket.
248 button.setToolTipText(trans.get("RocketCompCfg.but.ttip.Setforall"));
249 button.addActionListener(new ActionListener() {
251 public void actionPerformed(ActionEvent e) {
252 Finish f = ((ExternalComponent) component).getFinish();
254 document.startUndo("Set rocket finish");
257 Iterator<RocketComponent> iter = component.getRoot().iterator();
258 while (iter.hasNext()) {
259 RocketComponent c = iter.next();
260 if (c instanceof ExternalComponent) {
261 ((ExternalComponent) c).setFinish(f);
269 panel.add(button, "wrap paragraph");
276 private JPanel overrideTab() {
277 JPanel panel = new JPanel(new MigLayout("align 50% 20%, fillx, gap rel unrel",
278 "[][65lp::][30lp::][]", ""));
279 //// Override the mass or center of gravity of the
280 panel.add(new StyledLabel(trans.get("RocketCompCfg.lbl.Overridemassorcenter") + " " +
281 component.getComponentName() + ":", Style.BOLD), "spanx, wrap 20lp");
289 bm = new BooleanModel(component, "MassOverridden");
290 check = new JCheckBox(bm);
292 check.setText(trans.get("RocketCompCfg.checkbox.Overridemass"));
293 panel.add(check, "growx 1, gapright 20lp");
295 DoubleModel m = new DoubleModel(component, "OverrideMass", UnitGroup.UNITS_MASS, 0);
297 JSpinner spin = new JSpinner(m.getSpinnerModel());
298 spin.setEditor(new SpinnerEditor(spin));
299 bm.addEnableComponent(spin, true);
300 panel.add(spin, "growx 1");
302 us = new UnitSelector(m);
303 bm.addEnableComponent(us, true);
304 panel.add(us, "growx 1");
306 bs = new BasicSlider(m.getSliderModel(0, 0.03, 1.0));
307 bm.addEnableComponent(bs);
308 panel.add(bs, "growx 5, w 100lp, wrap");
312 bm = new BooleanModel(component, "CGOverridden");
313 check = new JCheckBox(bm);
314 //// Override center of gravity:"
315 check.setText(trans.get("RocketCompCfg.checkbox.Overridecenterofgrav"));
316 panel.add(check, "growx 1, gapright 20lp");
318 m = new DoubleModel(component, "OverrideCGX", UnitGroup.UNITS_LENGTH, 0);
319 // Calculate suitable length for slider
321 if (component instanceof ComponentAssembly) {
324 Iterator<RocketComponent> iterator = component.iterator(false);
325 while (iterator.hasNext()) {
326 RocketComponent c = iterator.next();
327 if (c.getRelativePosition() == RocketComponent.Position.AFTER)
330 length = new DoubleModel(l);
332 length = new DoubleModel(component, "Length", UnitGroup.UNITS_LENGTH, 0);
335 spin = new JSpinner(m.getSpinnerModel());
336 spin.setEditor(new SpinnerEditor(spin));
337 bm.addEnableComponent(spin, true);
338 panel.add(spin, "growx 1");
340 us = new UnitSelector(m);
341 bm.addEnableComponent(us, true);
342 panel.add(us, "growx 1");
344 bs = new BasicSlider(m.getSliderModel(new DoubleModel(0), length));
345 bm.addEnableComponent(bs);
346 panel.add(bs, "growx 5, w 100lp, wrap 35lp");
349 // Override subcomponents checkbox
350 bm = new BooleanModel(component, "OverrideSubcomponents");
351 check = new JCheckBox(bm);
352 //// Override mass and CG of all subcomponents
353 check.setText(trans.get("RocketCompCfg.checkbox.OverridemassandCG"));
354 panel.add(check, "gap para, spanx, wrap para");
356 //// <html>The overridden mass does not include motors.<br>
357 panel.add(new StyledLabel(trans.get("RocketCompCfg.lbl.longB1") +
358 //// The center of gravity is measured from the front end of the
359 trans.get("RocketCompCfg.lbl.longB2") + " " +
360 component.getComponentName().toLowerCase(Locale.getDefault()) + ".", -1),
361 "spanx, wrap, gap para, height 0::30lp");
367 private JPanel commentTab() {
368 JPanel panel = new JPanel(new MigLayout("fill"));
371 panel.add(new StyledLabel(trans.get("RocketCompCfg.lbl.Commentsonthe") + " " + component.getComponentName() + ":",
372 Style.BOLD), "wrap");
374 // TODO: LOW: Changes in comment from other sources not reflected in component
375 commentTextArea = new JTextArea(component.getComment());
376 commentTextArea.setLineWrap(true);
377 commentTextArea.setWrapStyleWord(true);
378 commentTextArea.setEditable(true);
379 GUIUtil.setTabToFocusing(commentTextArea);
380 commentTextArea.addFocusListener(textFieldListener);
382 panel.add(new JScrollPane(commentTextArea), "width 10px, height 10px, growx, growy");
389 private JPanel figureTab() {
390 JPanel panel = new JPanel(new MigLayout("align 20% 20%"));
393 panel.add(new StyledLabel(trans.get("RocketCompCfg.lbl.Figurestyle"), Style.BOLD), "wrap para");
395 //// Component color:
396 panel.add(new JLabel(trans.get("RocketCompCfg.lbl.Componentcolor")), "gapleft para, gapright 10lp");
398 colorButton = new JButton(new ColorIcon(getColor()));
399 colorButton.addActionListener(new ActionListener() {
401 public void actionPerformed(ActionEvent e) {
402 net.sf.openrocket.util.Color c = component.getColor();
404 c = Application.getPreferences().getDefaultColor(component.getClass());
408 Color awtColor = ColorConversion.toAwtColor(c);
409 awtColor = JColorChooser.showDialog(tabbedPane, trans.get("RocketCompCfg.lbl.Choosecolor"), awtColor);
410 c = ColorConversion.fromAwtColor(awtColor);
412 component.setColor(c);
416 panel.add(colorButton, "gapright 10lp");
418 //// Use default color
419 colorDefault = new JCheckBox(trans.get("RocketCompCfg.checkbox.Usedefaultcolor"));
420 if (component.getColor() == null)
421 colorDefault.setSelected(true);
422 colorDefault.addActionListener(new ActionListener() {
424 public void actionPerformed(ActionEvent e) {
425 if (colorDefault.isSelected())
426 component.setColor(null);
428 component.setColor(((SwingPreferences) Application.getPreferences()).getDefaultColor(component.getClass()));
431 panel.add(colorDefault, "wrap para");
433 //// Component line style:
434 panel.add(new JLabel(trans.get("RocketCompCfg.lbl.Complinestyle")), "gapleft para, gapright 10lp");
436 LineStyle[] list = new LineStyle[LineStyle.values().length + 1];
437 System.arraycopy(LineStyle.values(), 0, list, 1, LineStyle.values().length);
439 JComboBox combo = new JComboBox(new EnumModel<LineStyle>(component, "LineStyle",
441 list, trans.get("LineStyle.Defaultstyle")));
442 panel.add(combo, "spanx 2, growx, wrap 50lp");
444 //// Save as default style
445 JButton button = new JButton(trans.get("RocketCompCfg.but.Saveasdefstyle"));
446 button.addActionListener(new ActionListener() {
448 public void actionPerformed(ActionEvent e) {
449 if (component.getColor() != null) {
450 ((SwingPreferences) Application.getPreferences()).setDefaultColor(component.getClass(), component.getColor());
451 component.setColor(null);
453 if (component.getLineStyle() != null) {
454 Application.getPreferences().setDefaultLineStyle(component.getClass(), component.getLineStyle());
455 component.setLineStyle(null);
459 panel.add(button, "gapleft para, spanx 3, growx, wrap");
465 private Color getColor() {
466 net.sf.openrocket.util.Color c = component.getColor();
468 c = Application.getPreferences().getDefaultColor(component.getClass());
470 return ColorConversion.toAwtColor(c);
475 protected JPanel shoulderTab() {
476 JPanel panel = new JPanel(new MigLayout("fill"));
479 DoubleModel m0 = new DoubleModel(0);
485 //// Fore shoulder, not for NoseCone
487 if (!(component instanceof NoseCone)) {
488 sub = new JPanel(new MigLayout("gap rel unrel", "[][65lp::][30lp::]", ""));
491 sub.setBorder(BorderFactory.createTitledBorder(trans.get("RocketCompCfg.border.Foreshoulder")));
496 sub.add(new JLabel(trans.get("RocketCompCfg.lbl.Diameter")));
498 m = new DoubleModel(component, "ForeShoulderRadius", 2, UnitGroup.UNITS_LENGTH, 0);
499 m2 = new DoubleModel(component, "ForeRadius", 2, UnitGroup.UNITS_LENGTH);
501 spin = new JSpinner(m.getSpinnerModel());
502 spin.setEditor(new SpinnerEditor(spin));
503 sub.add(spin, "growx");
505 sub.add(new UnitSelector(m), "growx");
506 sub.add(new BasicSlider(m.getSliderModel(m0, m2)), "w 100lp, wrap");
510 sub.add(new JLabel(trans.get("RocketCompCfg.lbl.Length")));
512 m = new DoubleModel(component, "ForeShoulderLength", UnitGroup.UNITS_LENGTH, 0);
514 spin = new JSpinner(m.getSpinnerModel());
515 spin.setEditor(new SpinnerEditor(spin));
516 sub.add(spin, "growx");
518 sub.add(new UnitSelector(m), "growx");
519 sub.add(new BasicSlider(m.getSliderModel(0, 0.02, 0.2)), "w 100lp, wrap");
523 sub.add(new JLabel(trans.get("RocketCompCfg.lbl.Thickness")));
525 m = new DoubleModel(component, "ForeShoulderThickness", UnitGroup.UNITS_LENGTH, 0);
526 m2 = new DoubleModel(component, "ForeShoulderRadius", UnitGroup.UNITS_LENGTH);
528 spin = new JSpinner(m.getSpinnerModel());
529 spin.setEditor(new SpinnerEditor(spin));
530 sub.add(spin, "growx");
532 sub.add(new UnitSelector(m), "growx");
533 sub.add(new BasicSlider(m.getSliderModel(m0, m2)), "w 100lp, wrap");
537 bm = new BooleanModel(component, "ForeShoulderCapped");
538 check = new JCheckBox(bm);
540 check.setText(trans.get("RocketCompCfg.checkbox.Endcapped"));
541 //// Whether the end of the shoulder is capped.
542 check.setToolTipText(trans.get("RocketCompCfg.ttip.Endcapped"));
543 sub.add(check, "spanx");
551 sub = new JPanel(new MigLayout("gap rel unrel", "[][65lp::][30lp::]", ""));
553 if (component instanceof NoseCone)
554 //// Nose cone shoulder
555 sub.setBorder(BorderFactory.createTitledBorder(trans.get("RocketCompCfg.title.Noseconeshoulder")));
558 sub.setBorder(BorderFactory.createTitledBorder(trans.get("RocketCompCfg.title.Aftshoulder")));
563 sub.add(new JLabel(trans.get("RocketCompCfg.lbl.Diameter")));
565 m = new DoubleModel(component, "AftShoulderRadius", 2, UnitGroup.UNITS_LENGTH, 0);
566 m2 = new DoubleModel(component, "AftRadius", 2, UnitGroup.UNITS_LENGTH);
568 spin = new JSpinner(m.getSpinnerModel());
569 spin.setEditor(new SpinnerEditor(spin));
570 sub.add(spin, "growx");
572 sub.add(new UnitSelector(m), "growx");
573 sub.add(new BasicSlider(m.getSliderModel(m0, m2)), "w 100lp, wrap");
577 sub.add(new JLabel(trans.get("RocketCompCfg.lbl.Length")));
579 m = new DoubleModel(component, "AftShoulderLength", UnitGroup.UNITS_LENGTH, 0);
581 spin = new JSpinner(m.getSpinnerModel());
582 spin.setEditor(new SpinnerEditor(spin));
583 sub.add(spin, "growx");
585 sub.add(new UnitSelector(m), "growx");
586 sub.add(new BasicSlider(m.getSliderModel(0, 0.02, 0.2)), "w 100lp, wrap");
590 sub.add(new JLabel(trans.get("RocketCompCfg.lbl.Thickness")));
592 m = new DoubleModel(component, "AftShoulderThickness", UnitGroup.UNITS_LENGTH, 0);
593 m2 = new DoubleModel(component, "AftShoulderRadius", UnitGroup.UNITS_LENGTH);
595 spin = new JSpinner(m.getSpinnerModel());
596 spin.setEditor(new SpinnerEditor(spin));
597 sub.add(spin, "growx");
599 sub.add(new UnitSelector(m), "growx");
600 sub.add(new BasicSlider(m.getSliderModel(m0, m2)), "w 100lp, wrap");
604 bm = new BooleanModel(component, "AftShoulderCapped");
605 check = new JCheckBox(bm);
607 check.setText(trans.get("RocketCompCfg.checkbox.Endcapped"));
608 //// Whether the end of the shoulder is capped.
609 check.setToolTipText(trans.get("RocketCompCfg.ttip.Endcapped"));
610 sub.add(check, "spanx");
623 * Private inner class to handle events in componentNameField.
625 private class TextFieldListener implements ActionListener, FocusListener {
627 public void actionPerformed(ActionEvent e) {
632 public void focusGained(FocusEvent e) {
636 public void focusLost(FocusEvent e) {
640 private void setName() {
641 if (!component.getName().equals(componentNameField.getText())) {
642 component.setName(componentNameField.getText());
644 if (!component.getComment().equals(commentTextArea.getText())) {
645 component.setComment(commentTextArea.getText());
651 protected void register(Invalidatable model) {
652 this.invalidatables.add(model);
655 public void invalidateModels() {
656 for (Invalidatable i : invalidatables) {
659 ((ComponentPresetDatabase) Application.getComponentPresetDao()).removeChangeListener(presetModel);