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.rocketcomponent.ComponentAssembly;
47 import net.sf.openrocket.rocketcomponent.ExternalComponent;
48 import net.sf.openrocket.rocketcomponent.ExternalComponent.Finish;
49 import net.sf.openrocket.rocketcomponent.NoseCone;
50 import net.sf.openrocket.rocketcomponent.RocketComponent;
51 import net.sf.openrocket.startup.Application;
52 import net.sf.openrocket.unit.UnitGroup;
53 import net.sf.openrocket.util.Invalidatable;
54 import net.sf.openrocket.util.LineStyle;
56 public class RocketComponentConfig extends JPanel {
58 private static final Translator trans = Application.getTranslator();
60 protected final OpenRocketDocument document;
61 protected final RocketComponent component;
62 protected final JTabbedPane tabbedPane;
64 private final List<Invalidatable> invalidatables = new ArrayList<Invalidatable>();
66 private JComboBox presetComboBox;
67 private PresetModel presetModel;
69 protected final JTextField componentNameField;
70 protected JTextArea commentTextArea;
71 private final TextFieldListener textFieldListener;
72 private JButton colorButton;
73 private JCheckBox colorDefault;
74 private JPanel buttonPanel;
76 private JLabel massLabel;
79 public RocketComponentConfig(OpenRocketDocument document, RocketComponent component) {
80 setLayout(new MigLayout("fill", "[min,align right]:10[fill, grow]"));
81 this.document = document;
82 this.component = component;
85 JLabel label = new JLabel(trans.get("RocketCompCfg.lbl.Componentname"));
86 //// The component name.
87 label.setToolTipText(trans.get("RocketCompCfg.ttip.Thecomponentname"));
90 componentNameField = new JTextField(15);
91 textFieldListener = new TextFieldListener();
92 componentNameField.addActionListener(textFieldListener);
93 componentNameField.addFocusListener(textFieldListener);
94 //// The component name.
95 componentNameField.setToolTipText(trans.get("RocketCompCfg.ttip.Thecomponentname"));
96 this.add(componentNameField, "wrap");
98 if ( component.getPresetType() != null ) {
99 // If the component supports a preset, show the preset selection box.
100 this.add(new JLabel(trans.get("PresetModel.lbl.select")));
101 presetModel = new PresetModel( this, component);
102 ((ComponentPresetDatabase)Application.getComponentPresetDao()).addDatabaseListener(presetModel);
103 presetComboBox = new JComboBox(presetModel);
104 presetComboBox.setEditable(false);
105 this.add(presetComboBox, "wrap");
109 tabbedPane = new JTabbedPane();
110 this.add(tabbedPane, "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 massLabel = new StyledLabel(trans.get("RocketCompCfg.lbl.Mass") + " ", -1);
138 buttonPanel.add(massLabel, "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 if (component.isMassive()) {
179 String text = trans.get("RocketCompCfg.lbl.Componentmass") + " ";
180 text += UnitGroup.UNITS_MASS.getDefaultUnit().toStringUnit(
181 component.getComponentMass());
183 String overridetext = null;
184 if (component.isMassOverridden()) {
186 overridetext = trans.get("RocketCompCfg.lbl.overriddento") + " " + UnitGroup.UNITS_MASS.getDefaultUnit().
187 toStringUnit(component.getOverrideMass()) + ")";
190 for (RocketComponent c = component.getParent(); c != null; c = c.getParent()) {
191 if (c.isMassOverridden() && c.getOverrideSubcomponents()) {
193 overridetext = trans.get("RocketCompCfg.lbl.overriddenby") + " " + c.getName() + ")";
197 if (overridetext != null)
198 text = text + " " + overridetext;
200 massLabel.setText(text);
202 massLabel.setText("");
207 protected JPanel materialPanel(JPanel panel, Material.Type type) {
208 ////Component material: and Component finish:
209 return materialPanel(panel, type, trans.get("RocketCompCfg.lbl.Componentmaterial"),
210 trans.get("RocketCompCfg.lbl.Componentfinish"));
213 protected JPanel materialPanel(JPanel panel, Material.Type type,
214 String materialString, String finishString) {
216 JLabel label = new JLabel(materialString);
217 //// The component material affects the weight of the component.
218 label.setToolTipText(trans.get("RocketCompCfg.lbl.ttip.componentmaterialaffects"));
219 panel.add(label, "spanx 4, wrap rel");
221 JComboBox combo = new JComboBox(new MaterialModel(panel, component, type));
222 //// The component material affects the weight of the component.
223 combo.setToolTipText(trans.get("RocketCompCfg.combo.ttip.componentmaterialaffects"));
224 panel.add(combo, "spanx 4, growx, wrap paragraph");
227 if (component instanceof ExternalComponent) {
228 label = new JLabel(finishString);
229 ////<html>The component finish affects the aerodynamic drag of the component.<br>
230 String tip = trans.get("RocketCompCfg.lbl.longA1")
231 //// The value indicated is the average roughness height of the surface.
232 + trans.get("RocketCompCfg.lbl.longA2");
233 label.setToolTipText(tip);
234 panel.add(label, "spanx 4, wmin 220lp, wrap rel");
236 combo = new JComboBox(new EnumModel<ExternalComponent.Finish>(component, "Finish"));
237 combo.setToolTipText(tip);
238 panel.add(combo, "spanx 4, growx, split");
241 JButton button = new JButton(trans.get("RocketCompCfg.but.Setforall"));
242 //// Set this finish for all components of the rocket.
243 button.setToolTipText(trans.get("RocketCompCfg.but.ttip.Setforall"));
244 button.addActionListener(new ActionListener() {
246 public void actionPerformed(ActionEvent e) {
247 Finish f = ((ExternalComponent) component).getFinish();
249 document.startUndo("Set rocket finish");
252 Iterator<RocketComponent> iter = component.getRoot().iterator();
253 while (iter.hasNext()) {
254 RocketComponent c = iter.next();
255 if (c instanceof ExternalComponent) {
256 ((ExternalComponent) c).setFinish(f);
264 panel.add(button, "wrap paragraph");
271 private JPanel overrideTab() {
272 JPanel panel = new JPanel(new MigLayout("align 50% 20%, fillx, gap rel unrel",
273 "[][65lp::][30lp::][]", ""));
274 //// Override the mass or center of gravity of the
275 panel.add(new StyledLabel(trans.get("RocketCompCfg.lbl.Overridemassorcenter") + " " +
276 component.getComponentName() + ":", Style.BOLD), "spanx, wrap 20lp");
284 bm = new BooleanModel(component, "MassOverridden");
285 check = new JCheckBox(bm);
287 check.setText(trans.get("RocketCompCfg.checkbox.Overridemass"));
288 panel.add(check, "growx 1, gapright 20lp");
290 DoubleModel m = new DoubleModel(component, "OverrideMass", UnitGroup.UNITS_MASS, 0);
292 JSpinner spin = new JSpinner(m.getSpinnerModel());
293 spin.setEditor(new SpinnerEditor(spin));
294 bm.addEnableComponent(spin, true);
295 panel.add(spin, "growx 1");
297 us = new UnitSelector(m);
298 bm.addEnableComponent(us, true);
299 panel.add(us, "growx 1");
301 bs = new BasicSlider(m.getSliderModel(0, 0.03, 1.0));
302 bm.addEnableComponent(bs);
303 panel.add(bs, "growx 5, w 100lp, wrap");
307 bm = new BooleanModel(component, "CGOverridden");
308 check = new JCheckBox(bm);
309 //// Override center of gravity:"
310 check.setText(trans.get("RocketCompCfg.checkbox.Overridecenterofgrav"));
311 panel.add(check, "growx 1, gapright 20lp");
313 m = new DoubleModel(component, "OverrideCGX", UnitGroup.UNITS_LENGTH, 0);
314 // Calculate suitable length for slider
316 if (component instanceof ComponentAssembly) {
319 Iterator<RocketComponent> iterator = component.iterator(false);
320 while (iterator.hasNext()) {
321 RocketComponent c = iterator.next();
322 if (c.getRelativePosition() == RocketComponent.Position.AFTER)
325 length = new DoubleModel(l);
327 length = new DoubleModel(component, "Length", UnitGroup.UNITS_LENGTH, 0);
330 spin = new JSpinner(m.getSpinnerModel());
331 spin.setEditor(new SpinnerEditor(spin));
332 bm.addEnableComponent(spin, true);
333 panel.add(spin, "growx 1");
335 us = new UnitSelector(m);
336 bm.addEnableComponent(us, true);
337 panel.add(us, "growx 1");
339 bs = new BasicSlider(m.getSliderModel(new DoubleModel(0), length));
340 bm.addEnableComponent(bs);
341 panel.add(bs, "growx 5, w 100lp, wrap 35lp");
344 // Override subcomponents checkbox
345 bm = new BooleanModel(component, "OverrideSubcomponents");
346 check = new JCheckBox(bm);
347 //// Override mass and CG of all subcomponents
348 check.setText(trans.get("RocketCompCfg.checkbox.OverridemassandCG"));
349 panel.add(check, "gap para, spanx, wrap para");
351 //// <html>The overridden mass does not include motors.<br>
352 panel.add(new StyledLabel(trans.get("RocketCompCfg.lbl.longB1") +
353 //// The center of gravity is measured from the front end of the
354 trans.get("RocketCompCfg.lbl.longB2") + " " +
355 component.getComponentName().toLowerCase(Locale.getDefault()) + ".", -1),
356 "spanx, wrap, gap para, height 0::30lp");
362 private JPanel commentTab() {
363 JPanel panel = new JPanel(new MigLayout("fill"));
366 panel.add(new StyledLabel(trans.get("RocketCompCfg.lbl.Commentsonthe") + " " + component.getComponentName() + ":",
367 Style.BOLD), "wrap");
369 // TODO: LOW: Changes in comment from other sources not reflected in component
370 commentTextArea = new JTextArea(component.getComment());
371 commentTextArea.setLineWrap(true);
372 commentTextArea.setWrapStyleWord(true);
373 commentTextArea.setEditable(true);
374 GUIUtil.setTabToFocusing(commentTextArea);
375 commentTextArea.addFocusListener(textFieldListener);
377 panel.add(new JScrollPane(commentTextArea), "width 10px, height 10px, growx, growy");
384 private JPanel figureTab() {
385 JPanel panel = new JPanel(new MigLayout("align 20% 20%"));
388 panel.add(new StyledLabel(trans.get("RocketCompCfg.lbl.Figurestyle"), Style.BOLD), "wrap para");
390 //// Component color:
391 panel.add(new JLabel(trans.get("RocketCompCfg.lbl.Componentcolor")), "gapleft para, gapright 10lp");
393 colorButton = new JButton(new ColorIcon(getColor()));
394 colorButton.addActionListener(new ActionListener() {
396 public void actionPerformed(ActionEvent e) {
397 net.sf.openrocket.util.Color c = component.getColor();
399 c = Application.getPreferences().getDefaultColor(component.getClass());
403 Color awtColor = ColorConversion.toAwtColor(c);
404 awtColor = JColorChooser.showDialog(tabbedPane, trans.get("RocketCompCfg.lbl.Choosecolor"), awtColor);
405 c = ColorConversion.fromAwtColor(awtColor);
407 component.setColor(c);
411 panel.add(colorButton, "gapright 10lp");
413 //// Use default color
414 colorDefault = new JCheckBox(trans.get("RocketCompCfg.checkbox.Usedefaultcolor"));
415 if (component.getColor() == null)
416 colorDefault.setSelected(true);
417 colorDefault.addActionListener(new ActionListener() {
419 public void actionPerformed(ActionEvent e) {
420 if (colorDefault.isSelected())
421 component.setColor(null);
423 component.setColor(((SwingPreferences) Application.getPreferences()).getDefaultColor(component.getClass()));
426 panel.add(colorDefault, "wrap para");
428 //// Component line style:
429 panel.add(new JLabel(trans.get("RocketCompCfg.lbl.Complinestyle")), "gapleft para, gapright 10lp");
431 LineStyle[] list = new LineStyle[LineStyle.values().length + 1];
432 System.arraycopy(LineStyle.values(), 0, list, 1, LineStyle.values().length);
434 JComboBox combo = new JComboBox(new EnumModel<LineStyle>(component, "LineStyle",
436 list, trans.get("LineStyle.Defaultstyle")));
437 panel.add(combo, "spanx 2, growx, wrap 50lp");
439 //// Save as default style
440 JButton button = new JButton(trans.get("RocketCompCfg.but.Saveasdefstyle"));
441 button.addActionListener(new ActionListener() {
443 public void actionPerformed(ActionEvent e) {
444 if (component.getColor() != null) {
445 ((SwingPreferences) Application.getPreferences()).setDefaultColor(component.getClass(), component.getColor());
446 component.setColor(null);
448 if (component.getLineStyle() != null) {
449 Application.getPreferences().setDefaultLineStyle(component.getClass(), component.getLineStyle());
450 component.setLineStyle(null);
454 panel.add(button, "gapleft para, spanx 3, growx, wrap");
460 private Color getColor() {
461 net.sf.openrocket.util.Color c = component.getColor();
463 c = Application.getPreferences().getDefaultColor(component.getClass());
465 return ColorConversion.toAwtColor(c);
470 protected JPanel shoulderTab() {
471 JPanel panel = new JPanel(new MigLayout("fill"));
474 DoubleModel m0 = new DoubleModel(0);
480 //// Fore shoulder, not for NoseCone
482 if (!(component instanceof NoseCone)) {
483 sub = new JPanel(new MigLayout("gap rel unrel", "[][65lp::][30lp::]", ""));
486 sub.setBorder(BorderFactory.createTitledBorder(trans.get("RocketCompCfg.border.Foreshoulder")));
491 sub.add(new JLabel(trans.get("RocketCompCfg.lbl.Diameter")));
493 m = new DoubleModel(component, "ForeShoulderRadius", 2, UnitGroup.UNITS_LENGTH, 0);
494 m2 = new DoubleModel(component, "ForeRadius", 2, UnitGroup.UNITS_LENGTH);
496 spin = new JSpinner(m.getSpinnerModel());
497 spin.setEditor(new SpinnerEditor(spin));
498 sub.add(spin, "growx");
500 sub.add(new UnitSelector(m), "growx");
501 sub.add(new BasicSlider(m.getSliderModel(m0, m2)), "w 100lp, wrap");
505 sub.add(new JLabel(trans.get("RocketCompCfg.lbl.Length")));
507 m = new DoubleModel(component, "ForeShoulderLength", UnitGroup.UNITS_LENGTH, 0);
509 spin = new JSpinner(m.getSpinnerModel());
510 spin.setEditor(new SpinnerEditor(spin));
511 sub.add(spin, "growx");
513 sub.add(new UnitSelector(m), "growx");
514 sub.add(new BasicSlider(m.getSliderModel(0, 0.02, 0.2)), "w 100lp, wrap");
518 sub.add(new JLabel(trans.get("RocketCompCfg.lbl.Thickness")));
520 m = new DoubleModel(component, "ForeShoulderThickness", UnitGroup.UNITS_LENGTH, 0);
521 m2 = new DoubleModel(component, "ForeShoulderRadius", UnitGroup.UNITS_LENGTH);
523 spin = new JSpinner(m.getSpinnerModel());
524 spin.setEditor(new SpinnerEditor(spin));
525 sub.add(spin, "growx");
527 sub.add(new UnitSelector(m), "growx");
528 sub.add(new BasicSlider(m.getSliderModel(m0, m2)), "w 100lp, wrap");
532 bm = new BooleanModel(component, "ForeShoulderCapped");
533 check = new JCheckBox(bm);
535 check.setText(trans.get("RocketCompCfg.checkbox.Endcapped"));
536 //// Whether the end of the shoulder is capped.
537 check.setToolTipText(trans.get("RocketCompCfg.ttip.Endcapped"));
538 sub.add(check, "spanx");
546 sub = new JPanel(new MigLayout("gap rel unrel", "[][65lp::][30lp::]", ""));
548 if (component instanceof NoseCone)
549 //// Nose cone shoulder
550 sub.setBorder(BorderFactory.createTitledBorder(trans.get("RocketCompCfg.title.Noseconeshoulder")));
553 sub.setBorder(BorderFactory.createTitledBorder(trans.get("RocketCompCfg.title.Aftshoulder")));
558 sub.add(new JLabel(trans.get("RocketCompCfg.lbl.Diameter")));
560 m = new DoubleModel(component, "AftShoulderRadius", 2, UnitGroup.UNITS_LENGTH, 0);
561 m2 = new DoubleModel(component, "AftRadius", 2, UnitGroup.UNITS_LENGTH);
563 spin = new JSpinner(m.getSpinnerModel());
564 spin.setEditor(new SpinnerEditor(spin));
565 sub.add(spin, "growx");
567 sub.add(new UnitSelector(m), "growx");
568 sub.add(new BasicSlider(m.getSliderModel(m0, m2)), "w 100lp, wrap");
572 sub.add(new JLabel(trans.get("RocketCompCfg.lbl.Length")));
574 m = new DoubleModel(component, "AftShoulderLength", UnitGroup.UNITS_LENGTH, 0);
576 spin = new JSpinner(m.getSpinnerModel());
577 spin.setEditor(new SpinnerEditor(spin));
578 sub.add(spin, "growx");
580 sub.add(new UnitSelector(m), "growx");
581 sub.add(new BasicSlider(m.getSliderModel(0, 0.02, 0.2)), "w 100lp, wrap");
585 sub.add(new JLabel(trans.get("RocketCompCfg.lbl.Thickness")));
587 m = new DoubleModel(component, "AftShoulderThickness", UnitGroup.UNITS_LENGTH, 0);
588 m2 = new DoubleModel(component, "AftShoulderRadius", UnitGroup.UNITS_LENGTH);
590 spin = new JSpinner(m.getSpinnerModel());
591 spin.setEditor(new SpinnerEditor(spin));
592 sub.add(spin, "growx");
594 sub.add(new UnitSelector(m), "growx");
595 sub.add(new BasicSlider(m.getSliderModel(m0, m2)), "w 100lp, wrap");
599 bm = new BooleanModel(component, "AftShoulderCapped");
600 check = new JCheckBox(bm);
602 check.setText(trans.get("RocketCompCfg.checkbox.Endcapped"));
603 //// Whether the end of the shoulder is capped.
604 check.setToolTipText(trans.get("RocketCompCfg.ttip.Endcapped"));
605 sub.add(check, "spanx");
618 * Private inner class to handle events in componentNameField.
620 private class TextFieldListener implements ActionListener, FocusListener {
622 public void actionPerformed(ActionEvent e) {
627 public void focusGained(FocusEvent e) {
631 public void focusLost(FocusEvent e) {
635 private void setName() {
636 if (!component.getName().equals(componentNameField.getText())) {
637 component.setName(componentNameField.getText());
639 if (!component.getComment().equals(commentTextArea.getText())) {
640 component.setComment(commentTextArea.getText());
646 protected void register(Invalidatable model) {
647 this.invalidatables.add(model);
650 public void invalidateModels() {
651 for (Invalidatable i : invalidatables) {
654 ((ComponentPresetDatabase)Application.getComponentPresetDao()).removeChangeListener(presetModel);