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", "[grow, fill]"));
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"));
88 this.add(label, "split, gapright 10");
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, "growx, growy 0, wrap");
99 tabbedPane = new JTabbedPane();
100 this.add(tabbedPane, "growx, growy 1, wrap");
102 //// Override and Mass and CG override options
103 tabbedPane.addTab(trans.get("RocketCompCfg.tab.Override"), null, overrideTab(),
104 trans.get("RocketCompCfg.tab.MassandCGoverride"));
105 if (component.isMassive())
106 //// Figure and Figure style options
107 tabbedPane.addTab(trans.get("RocketCompCfg.tab.Figure"), null, figureTab(),
108 trans.get("RocketCompCfg.tab.Figstyleopt"));
109 //// Comment and Specify a comment for the component
110 tabbedPane.addTab(trans.get("RocketCompCfg.tab.Comment"), null, commentTab(),
111 trans.get("RocketCompCfg.tab.Specifyacomment"));
119 protected void addButtons(JButton... buttons) {
120 if (buttonPanel != null) {
121 this.remove(buttonPanel);
124 buttonPanel = new JPanel(new MigLayout("fill, ins 0"));
127 massLabel = new StyledLabel(trans.get("RocketCompCfg.lbl.Mass") + " ", -1);
128 buttonPanel.add(massLabel, "growx");
130 for (JButton b : buttons) {
131 buttonPanel.add(b, "right, gap para");
135 JButton closeButton = new JButton(trans.get("dlg.but.close"));
136 closeButton.addActionListener(new ActionListener() {
138 public void actionPerformed(ActionEvent arg0) {
139 ComponentConfigDialog.hideDialog();
142 buttonPanel.add(closeButton, "right, gap 30lp");
146 this.add(buttonPanel, "spanx, growx");
151 * Called when a change occurs, so that the fields can be updated if necessary.
152 * When overriding this method, the supermethod must always be called.
154 public void updateFields() {
156 componentNameField.setText(component.getName());
158 // Component color and "Use default color" checkbox
159 if (colorButton != null && colorDefault != null) {
160 colorButton.setIcon(new ColorIcon(getColor()));
162 if ((component.getColor() == null) != colorDefault.isSelected())
163 colorDefault.setSelected(component.getColor() == null);
167 if (component.isMassive()) {
169 String text = trans.get("RocketCompCfg.lbl.Componentmass") + " ";
170 text += UnitGroup.UNITS_MASS.getDefaultUnit().toStringUnit(
171 component.getComponentMass());
173 String overridetext = null;
174 if (component.isMassOverridden()) {
176 overridetext = trans.get("RocketCompCfg.lbl.overriddento") + " " + UnitGroup.UNITS_MASS.getDefaultUnit().
177 toStringUnit(component.getOverrideMass()) + ")";
180 for (RocketComponent c = component.getParent(); c != null; c = c.getParent()) {
181 if (c.isMassOverridden() && c.getOverrideSubcomponents()) {
183 overridetext = trans.get("RocketCompCfg.lbl.overriddenby") + " " + c.getName() + ")";
187 if (overridetext != null)
188 text = text + " " + overridetext;
190 massLabel.setText(text);
192 massLabel.setText("");
197 protected JPanel materialPanel(JPanel panel, Material.Type type) {
198 ////Component material: and Component finish:
199 return materialPanel(panel, type, trans.get("RocketCompCfg.lbl.Componentmaterial"),
200 trans.get("RocketCompCfg.lbl.Componentfinish"));
203 protected JPanel materialPanel(JPanel panel, Material.Type type,
204 String materialString, String finishString) {
206 if ( component.getPresetType() != null ) {
207 //// Body tube template
208 // FIXME: Move to proper location
209 panel.add(new JLabel(trans.get("PresetModel.lbl.select")));
210 presetModel = new PresetModel( this, component);
211 ((ComponentPresetDatabase)Application.getComponentPresetDao()).addDatabaseListener(presetModel);
212 presetComboBox = new JComboBox(presetModel);
213 presetComboBox.setEditable(false);
214 panel.add(presetComboBox, "wrap para");
217 JLabel label = new JLabel(materialString);
218 //// The component material affects the weight of the component.
219 label.setToolTipText(trans.get("RocketCompCfg.lbl.ttip.componentmaterialaffects"));
220 panel.add(label, "spanx 4, wrap rel");
222 JComboBox combo = new JComboBox(new MaterialModel(panel, component, type));
223 //// The component material affects the weight of the component.
224 combo.setToolTipText(trans.get("RocketCompCfg.combo.ttip.componentmaterialaffects"));
225 panel.add(combo, "spanx 4, growx, wrap paragraph");
228 if (component instanceof ExternalComponent) {
229 label = new JLabel(finishString);
230 ////<html>The component finish affects the aerodynamic drag of the component.<br>
231 String tip = trans.get("RocketCompCfg.lbl.longA1")
232 //// The value indicated is the average roughness height of the surface.
233 + trans.get("RocketCompCfg.lbl.longA2");
234 label.setToolTipText(tip);
235 panel.add(label, "spanx 4, wmin 220lp, wrap rel");
237 combo = new JComboBox(new EnumModel<ExternalComponent.Finish>(component, "Finish"));
238 combo.setToolTipText(tip);
239 panel.add(combo, "spanx 4, growx, split");
242 JButton button = new JButton(trans.get("RocketCompCfg.but.Setforall"));
243 //// Set this finish for all components of the rocket.
244 button.setToolTipText(trans.get("RocketCompCfg.but.ttip.Setforall"));
245 button.addActionListener(new ActionListener() {
247 public void actionPerformed(ActionEvent e) {
248 Finish f = ((ExternalComponent) component).getFinish();
250 document.startUndo("Set rocket finish");
253 Iterator<RocketComponent> iter = component.getRoot().iterator();
254 while (iter.hasNext()) {
255 RocketComponent c = iter.next();
256 if (c instanceof ExternalComponent) {
257 ((ExternalComponent) c).setFinish(f);
265 panel.add(button, "wrap paragraph");
272 private JPanel overrideTab() {
273 JPanel panel = new JPanel(new MigLayout("align 50% 20%, fillx, gap rel unrel",
274 "[][65lp::][30lp::][]", ""));
275 //// Override the mass or center of gravity of the
276 panel.add(new StyledLabel(trans.get("RocketCompCfg.lbl.Overridemassorcenter") + " " +
277 component.getComponentName() + ":", Style.BOLD), "spanx, wrap 20lp");
285 bm = new BooleanModel(component, "MassOverridden");
286 check = new JCheckBox(bm);
288 check.setText(trans.get("RocketCompCfg.checkbox.Overridemass"));
289 panel.add(check, "growx 1, gapright 20lp");
291 DoubleModel m = new DoubleModel(component, "OverrideMass", UnitGroup.UNITS_MASS, 0);
293 JSpinner spin = new JSpinner(m.getSpinnerModel());
294 spin.setEditor(new SpinnerEditor(spin));
295 bm.addEnableComponent(spin, true);
296 panel.add(spin, "growx 1");
298 us = new UnitSelector(m);
299 bm.addEnableComponent(us, true);
300 panel.add(us, "growx 1");
302 bs = new BasicSlider(m.getSliderModel(0, 0.03, 1.0));
303 bm.addEnableComponent(bs);
304 panel.add(bs, "growx 5, w 100lp, wrap");
308 bm = new BooleanModel(component, "CGOverridden");
309 check = new JCheckBox(bm);
310 //// Override center of gravity:"
311 check.setText(trans.get("RocketCompCfg.checkbox.Overridecenterofgrav"));
312 panel.add(check, "growx 1, gapright 20lp");
314 m = new DoubleModel(component, "OverrideCGX", UnitGroup.UNITS_LENGTH, 0);
315 // Calculate suitable length for slider
317 if (component instanceof ComponentAssembly) {
320 Iterator<RocketComponent> iterator = component.iterator(false);
321 while (iterator.hasNext()) {
322 RocketComponent c = iterator.next();
323 if (c.getRelativePosition() == RocketComponent.Position.AFTER)
326 length = new DoubleModel(l);
328 length = new DoubleModel(component, "Length", UnitGroup.UNITS_LENGTH, 0);
331 spin = new JSpinner(m.getSpinnerModel());
332 spin.setEditor(new SpinnerEditor(spin));
333 bm.addEnableComponent(spin, true);
334 panel.add(spin, "growx 1");
336 us = new UnitSelector(m);
337 bm.addEnableComponent(us, true);
338 panel.add(us, "growx 1");
340 bs = new BasicSlider(m.getSliderModel(new DoubleModel(0), length));
341 bm.addEnableComponent(bs);
342 panel.add(bs, "growx 5, w 100lp, wrap 35lp");
345 // Override subcomponents checkbox
346 bm = new BooleanModel(component, "OverrideSubcomponents");
347 check = new JCheckBox(bm);
348 //// Override mass and CG of all subcomponents
349 check.setText(trans.get("RocketCompCfg.checkbox.OverridemassandCG"));
350 panel.add(check, "gap para, spanx, wrap para");
352 //// <html>The overridden mass does not include motors.<br>
353 panel.add(new StyledLabel(trans.get("RocketCompCfg.lbl.longB1") +
354 //// The center of gravity is measured from the front end of the
355 trans.get("RocketCompCfg.lbl.longB2") + " " +
356 component.getComponentName().toLowerCase(Locale.getDefault()) + ".", -1),
357 "spanx, wrap, gap para, height 0::30lp");
363 private JPanel commentTab() {
364 JPanel panel = new JPanel(new MigLayout("fill"));
367 panel.add(new StyledLabel(trans.get("RocketCompCfg.lbl.Commentsonthe") + " " + component.getComponentName() + ":",
368 Style.BOLD), "wrap");
370 // TODO: LOW: Changes in comment from other sources not reflected in component
371 commentTextArea = new JTextArea(component.getComment());
372 commentTextArea.setLineWrap(true);
373 commentTextArea.setWrapStyleWord(true);
374 commentTextArea.setEditable(true);
375 GUIUtil.setTabToFocusing(commentTextArea);
376 commentTextArea.addFocusListener(textFieldListener);
378 panel.add(new JScrollPane(commentTextArea), "width 10px, height 10px, growx, growy");
385 private JPanel figureTab() {
386 JPanel panel = new JPanel(new MigLayout("align 20% 20%"));
389 panel.add(new StyledLabel(trans.get("RocketCompCfg.lbl.Figurestyle"), Style.BOLD), "wrap para");
391 //// Component color:
392 panel.add(new JLabel(trans.get("RocketCompCfg.lbl.Componentcolor")), "gapleft para, gapright 10lp");
394 colorButton = new JButton(new ColorIcon(getColor()));
395 colorButton.addActionListener(new ActionListener() {
397 public void actionPerformed(ActionEvent e) {
398 net.sf.openrocket.util.Color c = component.getColor();
400 c = Application.getPreferences().getDefaultColor(component.getClass());
404 Color awtColor = ColorConversion.toAwtColor(c);
405 awtColor = JColorChooser.showDialog(tabbedPane, trans.get("RocketCompCfg.lbl.Choosecolor"), awtColor);
406 c = ColorConversion.fromAwtColor(awtColor);
408 component.setColor(c);
412 panel.add(colorButton, "gapright 10lp");
414 //// Use default color
415 colorDefault = new JCheckBox(trans.get("RocketCompCfg.checkbox.Usedefaultcolor"));
416 if (component.getColor() == null)
417 colorDefault.setSelected(true);
418 colorDefault.addActionListener(new ActionListener() {
420 public void actionPerformed(ActionEvent e) {
421 if (colorDefault.isSelected())
422 component.setColor(null);
424 component.setColor(((SwingPreferences) Application.getPreferences()).getDefaultColor(component.getClass()));
427 panel.add(colorDefault, "wrap para");
429 //// Component line style:
430 panel.add(new JLabel(trans.get("RocketCompCfg.lbl.Complinestyle")), "gapleft para, gapright 10lp");
432 LineStyle[] list = new LineStyle[LineStyle.values().length + 1];
433 System.arraycopy(LineStyle.values(), 0, list, 1, LineStyle.values().length);
435 JComboBox combo = new JComboBox(new EnumModel<LineStyle>(component, "LineStyle",
437 list, trans.get("LineStyle.Defaultstyle")));
438 panel.add(combo, "spanx 2, growx, wrap 50lp");
440 //// Save as default style
441 JButton button = new JButton(trans.get("RocketCompCfg.but.Saveasdefstyle"));
442 button.addActionListener(new ActionListener() {
444 public void actionPerformed(ActionEvent e) {
445 if (component.getColor() != null) {
446 ((SwingPreferences) Application.getPreferences()).setDefaultColor(component.getClass(), component.getColor());
447 component.setColor(null);
449 if (component.getLineStyle() != null) {
450 Application.getPreferences().setDefaultLineStyle(component.getClass(), component.getLineStyle());
451 component.setLineStyle(null);
455 panel.add(button, "gapleft para, spanx 3, growx, wrap");
461 private Color getColor() {
462 net.sf.openrocket.util.Color c = component.getColor();
464 c = Application.getPreferences().getDefaultColor(component.getClass());
466 return ColorConversion.toAwtColor(c);
471 protected JPanel shoulderTab() {
472 JPanel panel = new JPanel(new MigLayout("fill"));
475 DoubleModel m0 = new DoubleModel(0);
481 //// Fore shoulder, not for NoseCone
483 if (!(component instanceof NoseCone)) {
484 sub = new JPanel(new MigLayout("gap rel unrel", "[][65lp::][30lp::]", ""));
487 sub.setBorder(BorderFactory.createTitledBorder(trans.get("RocketCompCfg.border.Foreshoulder")));
492 sub.add(new JLabel(trans.get("RocketCompCfg.lbl.Diameter")));
494 m = new DoubleModel(component, "ForeShoulderRadius", 2, UnitGroup.UNITS_LENGTH, 0);
495 m2 = new DoubleModel(component, "ForeRadius", 2, UnitGroup.UNITS_LENGTH);
497 spin = new JSpinner(m.getSpinnerModel());
498 spin.setEditor(new SpinnerEditor(spin));
499 sub.add(spin, "growx");
501 sub.add(new UnitSelector(m), "growx");
502 sub.add(new BasicSlider(m.getSliderModel(m0, m2)), "w 100lp, wrap");
506 sub.add(new JLabel(trans.get("RocketCompCfg.lbl.Length")));
508 m = new DoubleModel(component, "ForeShoulderLength", UnitGroup.UNITS_LENGTH, 0);
510 spin = new JSpinner(m.getSpinnerModel());
511 spin.setEditor(new SpinnerEditor(spin));
512 sub.add(spin, "growx");
514 sub.add(new UnitSelector(m), "growx");
515 sub.add(new BasicSlider(m.getSliderModel(0, 0.02, 0.2)), "w 100lp, wrap");
519 sub.add(new JLabel(trans.get("RocketCompCfg.lbl.Thickness")));
521 m = new DoubleModel(component, "ForeShoulderThickness", UnitGroup.UNITS_LENGTH, 0);
522 m2 = new DoubleModel(component, "ForeShoulderRadius", UnitGroup.UNITS_LENGTH);
524 spin = new JSpinner(m.getSpinnerModel());
525 spin.setEditor(new SpinnerEditor(spin));
526 sub.add(spin, "growx");
528 sub.add(new UnitSelector(m), "growx");
529 sub.add(new BasicSlider(m.getSliderModel(m0, m2)), "w 100lp, wrap");
533 bm = new BooleanModel(component, "ForeShoulderCapped");
534 check = new JCheckBox(bm);
536 check.setText(trans.get("RocketCompCfg.checkbox.Endcapped"));
537 //// Whether the end of the shoulder is capped.
538 check.setToolTipText(trans.get("RocketCompCfg.ttip.Endcapped"));
539 sub.add(check, "spanx");
547 sub = new JPanel(new MigLayout("gap rel unrel", "[][65lp::][30lp::]", ""));
549 if (component instanceof NoseCone)
550 //// Nose cone shoulder
551 sub.setBorder(BorderFactory.createTitledBorder(trans.get("RocketCompCfg.title.Noseconeshoulder")));
554 sub.setBorder(BorderFactory.createTitledBorder(trans.get("RocketCompCfg.title.Aftshoulder")));
559 sub.add(new JLabel(trans.get("RocketCompCfg.lbl.Diameter")));
561 m = new DoubleModel(component, "AftShoulderRadius", 2, UnitGroup.UNITS_LENGTH, 0);
562 m2 = new DoubleModel(component, "AftRadius", 2, UnitGroup.UNITS_LENGTH);
564 spin = new JSpinner(m.getSpinnerModel());
565 spin.setEditor(new SpinnerEditor(spin));
566 sub.add(spin, "growx");
568 sub.add(new UnitSelector(m), "growx");
569 sub.add(new BasicSlider(m.getSliderModel(m0, m2)), "w 100lp, wrap");
573 sub.add(new JLabel(trans.get("RocketCompCfg.lbl.Length")));
575 m = new DoubleModel(component, "AftShoulderLength", UnitGroup.UNITS_LENGTH, 0);
577 spin = new JSpinner(m.getSpinnerModel());
578 spin.setEditor(new SpinnerEditor(spin));
579 sub.add(spin, "growx");
581 sub.add(new UnitSelector(m), "growx");
582 sub.add(new BasicSlider(m.getSliderModel(0, 0.02, 0.2)), "w 100lp, wrap");
586 sub.add(new JLabel(trans.get("RocketCompCfg.lbl.Thickness")));
588 m = new DoubleModel(component, "AftShoulderThickness", UnitGroup.UNITS_LENGTH, 0);
589 m2 = new DoubleModel(component, "AftShoulderRadius", UnitGroup.UNITS_LENGTH);
591 spin = new JSpinner(m.getSpinnerModel());
592 spin.setEditor(new SpinnerEditor(spin));
593 sub.add(spin, "growx");
595 sub.add(new UnitSelector(m), "growx");
596 sub.add(new BasicSlider(m.getSliderModel(m0, m2)), "w 100lp, wrap");
600 bm = new BooleanModel(component, "AftShoulderCapped");
601 check = new JCheckBox(bm);
603 check.setText(trans.get("RocketCompCfg.checkbox.Endcapped"));
604 //// Whether the end of the shoulder is capped.
605 check.setToolTipText(trans.get("RocketCompCfg.ttip.Endcapped"));
606 sub.add(check, "spanx");
619 * Private inner class to handle events in componentNameField.
621 private class TextFieldListener implements ActionListener, FocusListener {
623 public void actionPerformed(ActionEvent e) {
628 public void focusGained(FocusEvent e) {
632 public void focusLost(FocusEvent e) {
636 private void setName() {
637 if (!component.getName().equals(componentNameField.getText())) {
638 component.setName(componentNameField.getText());
640 if (!component.getComment().equals(commentTextArea.getText())) {
641 component.setComment(commentTextArea.getText());
647 protected void register(Invalidatable model) {
648 this.invalidatables.add(model);
651 public void invalidateModels() {
652 for (Invalidatable i : invalidatables) {
655 ((ComponentPresetDatabase)Application.getComponentPresetDao()).removeChangeListener(presetModel);