Move the preset drop down into RocketComponentConfig materials section. Only display...
[debian/openrocket] / core / src / net / sf / openrocket / gui / configdialog / RocketComponentConfig.java
1 package net.sf.openrocket.gui.configdialog;
2
3
4 import java.awt.Color;
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;
13
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;
26
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;
55
56 public class RocketComponentConfig extends JPanel {
57         
58         private static final Translator trans = Application.getTranslator();
59         
60         protected final OpenRocketDocument document;
61         protected final RocketComponent component;
62         protected final JTabbedPane tabbedPane;
63         
64         private final List<Invalidatable> invalidatables = new ArrayList<Invalidatable>();
65         
66         private JComboBox presetComboBox;
67         private PresetModel presetModel;
68
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;
75         
76         private JLabel massLabel;
77         
78         
79         public RocketComponentConfig(OpenRocketDocument document, RocketComponent component) {
80                 setLayout(new MigLayout("fill", "[grow, fill]"));
81                 this.document = document;
82                 this.component = component;
83                 
84                 //// Component name:
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");
89                 
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");
97                 
98                 
99                 tabbedPane = new JTabbedPane();
100                 this.add(tabbedPane, "growx, growy 1, wrap");
101                 
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"));
112                 
113                 addButtons();
114                 
115                 updateFields();
116         }
117         
118         
119         protected void addButtons(JButton... buttons) {
120                 if (buttonPanel != null) {
121                         this.remove(buttonPanel);
122                 }
123                 
124                 buttonPanel = new JPanel(new MigLayout("fill, ins 0"));
125                 
126                 //// Mass:
127                 massLabel = new StyledLabel(trans.get("RocketCompCfg.lbl.Mass") + " ", -1);
128                 buttonPanel.add(massLabel, "growx");
129                 
130                 for (JButton b : buttons) {
131                         buttonPanel.add(b, "right, gap para");
132                 }
133                 
134                 //// Close button
135                 JButton closeButton = new JButton(trans.get("dlg.but.close"));
136                 closeButton.addActionListener(new ActionListener() {
137                         @Override
138                         public void actionPerformed(ActionEvent arg0) {
139                                 ComponentConfigDialog.hideDialog();
140                         }
141                 });
142                 buttonPanel.add(closeButton, "right, gap 30lp");
143                 
144                 updateFields();
145                 
146                 this.add(buttonPanel, "spanx, growx");
147         }
148         
149         
150         /**
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.
153          */
154         public void updateFields() {
155                 // Component name
156                 componentNameField.setText(component.getName());
157                 
158                 // Component color and "Use default color" checkbox
159                 if (colorButton != null && colorDefault != null) {
160                         colorButton.setIcon(new ColorIcon(getColor()));
161                         
162                         if ((component.getColor() == null) != colorDefault.isSelected())
163                                 colorDefault.setSelected(component.getColor() == null);
164                 }
165                 
166                 // Mass label
167                 if (component.isMassive()) {
168                         //// Component mass:
169                         String text = trans.get("RocketCompCfg.lbl.Componentmass") + " ";
170                         text += UnitGroup.UNITS_MASS.getDefaultUnit().toStringUnit(
171                                         component.getComponentMass());
172                         
173                         String overridetext = null;
174                         if (component.isMassOverridden()) {
175                                 //// (overridden to 
176                                 overridetext = trans.get("RocketCompCfg.lbl.overriddento") + " " + UnitGroup.UNITS_MASS.getDefaultUnit().
177                                                 toStringUnit(component.getOverrideMass()) + ")";
178                         }
179                         
180                         for (RocketComponent c = component.getParent(); c != null; c = c.getParent()) {
181                                 if (c.isMassOverridden() && c.getOverrideSubcomponents()) {
182                                         ///// (overridden by
183                                         overridetext = trans.get("RocketCompCfg.lbl.overriddenby") + " " + c.getName() + ")";
184                                 }
185                         }
186                         
187                         if (overridetext != null)
188                                 text = text + " " + overridetext;
189                         
190                         massLabel.setText(text);
191                 } else {
192                         massLabel.setText("");
193                 }
194         }
195         
196         
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"));
201         }
202         
203         protected JPanel materialPanel(JPanel panel, Material.Type type,
204                         String materialString, String finishString) {
205                 
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");
215                 }
216                 
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");
221                 
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");
226                 
227                 
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");
236                         
237                         combo = new JComboBox(new EnumModel<ExternalComponent.Finish>(component, "Finish"));
238                         combo.setToolTipText(tip);
239                         panel.add(combo, "spanx 4, growx, split");
240                         
241                         //// Set for all
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() {
246                                 @Override
247                                 public void actionPerformed(ActionEvent e) {
248                                         Finish f = ((ExternalComponent) component).getFinish();
249                                         try {
250                                                 document.startUndo("Set rocket finish");
251                                                 
252                                                 // Do changes
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);
258                                                         }
259                                                 }
260                                         } finally {
261                                                 document.stopUndo();
262                                         }
263                                 }
264                         });
265                         panel.add(button, "wrap paragraph");
266                 }
267                 
268                 return panel;
269         }
270         
271         
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");
278                 
279                 JCheckBox check;
280                 BooleanModel bm;
281                 UnitSelector us;
282                 BasicSlider bs;
283                 
284                 ////  Mass
285                 bm = new BooleanModel(component, "MassOverridden");
286                 check = new JCheckBox(bm);
287                 //// Override mass:
288                 check.setText(trans.get("RocketCompCfg.checkbox.Overridemass"));
289                 panel.add(check, "growx 1, gapright 20lp");
290                 
291                 DoubleModel m = new DoubleModel(component, "OverrideMass", UnitGroup.UNITS_MASS, 0);
292                 
293                 JSpinner spin = new JSpinner(m.getSpinnerModel());
294                 spin.setEditor(new SpinnerEditor(spin));
295                 bm.addEnableComponent(spin, true);
296                 panel.add(spin, "growx 1");
297                 
298                 us = new UnitSelector(m);
299                 bm.addEnableComponent(us, true);
300                 panel.add(us, "growx 1");
301                 
302                 bs = new BasicSlider(m.getSliderModel(0, 0.03, 1.0));
303                 bm.addEnableComponent(bs);
304                 panel.add(bs, "growx 5, w 100lp, wrap");
305                 
306                 
307                 ////  CG override
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");
313                 
314                 m = new DoubleModel(component, "OverrideCGX", UnitGroup.UNITS_LENGTH, 0);
315                 // Calculate suitable length for slider
316                 DoubleModel length;
317                 if (component instanceof ComponentAssembly) {
318                         double l = 0;
319                         
320                         Iterator<RocketComponent> iterator = component.iterator(false);
321                         while (iterator.hasNext()) {
322                                 RocketComponent c = iterator.next();
323                                 if (c.getRelativePosition() == RocketComponent.Position.AFTER)
324                                         l += c.getLength();
325                         }
326                         length = new DoubleModel(l);
327                 } else {
328                         length = new DoubleModel(component, "Length", UnitGroup.UNITS_LENGTH, 0);
329                 }
330                 
331                 spin = new JSpinner(m.getSpinnerModel());
332                 spin.setEditor(new SpinnerEditor(spin));
333                 bm.addEnableComponent(spin, true);
334                 panel.add(spin, "growx 1");
335                 
336                 us = new UnitSelector(m);
337                 bm.addEnableComponent(us, true);
338                 panel.add(us, "growx 1");
339                 
340                 bs = new BasicSlider(m.getSliderModel(new DoubleModel(0), length));
341                 bm.addEnableComponent(bs);
342                 panel.add(bs, "growx 5, w 100lp, wrap 35lp");
343                 
344                 
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");
351                 
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");
358                 
359                 return panel;
360         }
361         
362         
363         private JPanel commentTab() {
364                 JPanel panel = new JPanel(new MigLayout("fill"));
365                 
366                 //// Comments on the
367                 panel.add(new StyledLabel(trans.get("RocketCompCfg.lbl.Commentsonthe") + " " + component.getComponentName() + ":",
368                                 Style.BOLD), "wrap");
369                 
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);
377                 
378                 panel.add(new JScrollPane(commentTextArea), "width 10px, height 10px, growx, growy");
379                 
380                 return panel;
381         }
382         
383         
384         
385         private JPanel figureTab() {
386                 JPanel panel = new JPanel(new MigLayout("align 20% 20%"));
387                 
388                 //// Figure style:
389                 panel.add(new StyledLabel(trans.get("RocketCompCfg.lbl.Figurestyle"), Style.BOLD), "wrap para");
390                 
391                 //// Component color:
392                 panel.add(new JLabel(trans.get("RocketCompCfg.lbl.Componentcolor")), "gapleft para, gapright 10lp");
393                 
394                 colorButton = new JButton(new ColorIcon(getColor()));
395                 colorButton.addActionListener(new ActionListener() {
396                         @Override
397                         public void actionPerformed(ActionEvent e) {
398                                 net.sf.openrocket.util.Color c = component.getColor();
399                                 if (c == null) {
400                                         c = Application.getPreferences().getDefaultColor(component.getClass());
401                                 }
402                                 
403                                 //// Choose color
404                                 Color awtColor = ColorConversion.toAwtColor(c);
405                                 awtColor = JColorChooser.showDialog(tabbedPane, trans.get("RocketCompCfg.lbl.Choosecolor"), awtColor);
406                                 c = ColorConversion.fromAwtColor(awtColor);
407                                 if (c != null) {
408                                         component.setColor(c);
409                                 }
410                         }
411                 });
412                 panel.add(colorButton, "gapright 10lp");
413                 
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() {
419                         @Override
420                         public void actionPerformed(ActionEvent e) {
421                                 if (colorDefault.isSelected())
422                                         component.setColor(null);
423                                 else
424                                         component.setColor(((SwingPreferences) Application.getPreferences()).getDefaultColor(component.getClass()));
425                         }
426                 });
427                 panel.add(colorDefault, "wrap para");
428                 
429                 //// Component line style:
430                 panel.add(new JLabel(trans.get("RocketCompCfg.lbl.Complinestyle")), "gapleft para, gapright 10lp");
431                 
432                 LineStyle[] list = new LineStyle[LineStyle.values().length + 1];
433                 System.arraycopy(LineStyle.values(), 0, list, 1, LineStyle.values().length);
434                 
435                 JComboBox combo = new JComboBox(new EnumModel<LineStyle>(component, "LineStyle",
436                                 //// Default style
437                                 list, trans.get("LineStyle.Defaultstyle")));
438                 panel.add(combo, "spanx 2, growx, wrap 50lp");
439                 
440                 //// Save as default style
441                 JButton button = new JButton(trans.get("RocketCompCfg.but.Saveasdefstyle"));
442                 button.addActionListener(new ActionListener() {
443                         @Override
444                         public void actionPerformed(ActionEvent e) {
445                                 if (component.getColor() != null) {
446                                         ((SwingPreferences) Application.getPreferences()).setDefaultColor(component.getClass(), component.getColor());
447                                         component.setColor(null);
448                                 }
449                                 if (component.getLineStyle() != null) {
450                                         Application.getPreferences().setDefaultLineStyle(component.getClass(), component.getLineStyle());
451                                         component.setLineStyle(null);
452                                 }
453                         }
454                 });
455                 panel.add(button, "gapleft para, spanx 3, growx, wrap");
456                 
457                 return panel;
458         }
459         
460         
461         private Color getColor() {
462                 net.sf.openrocket.util.Color c = component.getColor();
463                 if (c == null) {
464                         c = Application.getPreferences().getDefaultColor(component.getClass());
465                 }
466                 return ColorConversion.toAwtColor(c);
467         }
468         
469         
470         
471         protected JPanel shoulderTab() {
472                 JPanel panel = new JPanel(new MigLayout("fill"));
473                 JPanel sub;
474                 DoubleModel m, m2;
475                 DoubleModel m0 = new DoubleModel(0);
476                 BooleanModel bm;
477                 JCheckBox check;
478                 JSpinner spin;
479                 
480                 
481                 ////  Fore shoulder, not for NoseCone
482                 
483                 if (!(component instanceof NoseCone)) {
484                         sub = new JPanel(new MigLayout("gap rel unrel", "[][65lp::][30lp::]", ""));
485                         
486                         //// Fore shoulder
487                         sub.setBorder(BorderFactory.createTitledBorder(trans.get("RocketCompCfg.border.Foreshoulder")));
488                         
489                         
490                         ////  Radius
491                         //// Diameter:
492                         sub.add(new JLabel(trans.get("RocketCompCfg.lbl.Diameter")));
493                         
494                         m = new DoubleModel(component, "ForeShoulderRadius", 2, UnitGroup.UNITS_LENGTH, 0);
495                         m2 = new DoubleModel(component, "ForeRadius", 2, UnitGroup.UNITS_LENGTH);
496                         
497                         spin = new JSpinner(m.getSpinnerModel());
498                         spin.setEditor(new SpinnerEditor(spin));
499                         sub.add(spin, "growx");
500                         
501                         sub.add(new UnitSelector(m), "growx");
502                         sub.add(new BasicSlider(m.getSliderModel(m0, m2)), "w 100lp, wrap");
503                         
504                         
505                         ////  Length:
506                         sub.add(new JLabel(trans.get("RocketCompCfg.lbl.Length")));
507                         
508                         m = new DoubleModel(component, "ForeShoulderLength", UnitGroup.UNITS_LENGTH, 0);
509                         
510                         spin = new JSpinner(m.getSpinnerModel());
511                         spin.setEditor(new SpinnerEditor(spin));
512                         sub.add(spin, "growx");
513                         
514                         sub.add(new UnitSelector(m), "growx");
515                         sub.add(new BasicSlider(m.getSliderModel(0, 0.02, 0.2)), "w 100lp, wrap");
516                         
517                         
518                         ////  Thickness:
519                         sub.add(new JLabel(trans.get("RocketCompCfg.lbl.Thickness")));
520                         
521                         m = new DoubleModel(component, "ForeShoulderThickness", UnitGroup.UNITS_LENGTH, 0);
522                         m2 = new DoubleModel(component, "ForeShoulderRadius", UnitGroup.UNITS_LENGTH);
523                         
524                         spin = new JSpinner(m.getSpinnerModel());
525                         spin.setEditor(new SpinnerEditor(spin));
526                         sub.add(spin, "growx");
527                         
528                         sub.add(new UnitSelector(m), "growx");
529                         sub.add(new BasicSlider(m.getSliderModel(m0, m2)), "w 100lp, wrap");
530                         
531                         
532                         ////  Capped
533                         bm = new BooleanModel(component, "ForeShoulderCapped");
534                         check = new JCheckBox(bm);
535                         //// End capped
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");
540                         
541                         
542                         panel.add(sub);
543                 }
544                 
545                 
546                 ////  Aft shoulder
547                 sub = new JPanel(new MigLayout("gap rel unrel", "[][65lp::][30lp::]", ""));
548                 
549                 if (component instanceof NoseCone)
550                         //// Nose cone shoulder
551                         sub.setBorder(BorderFactory.createTitledBorder(trans.get("RocketCompCfg.title.Noseconeshoulder")));
552                 else
553                         //// Aft shoulder
554                         sub.setBorder(BorderFactory.createTitledBorder(trans.get("RocketCompCfg.title.Aftshoulder")));
555                 
556                 
557                 ////  Radius
558                 //// Diameter:
559                 sub.add(new JLabel(trans.get("RocketCompCfg.lbl.Diameter")));
560                 
561                 m = new DoubleModel(component, "AftShoulderRadius", 2, UnitGroup.UNITS_LENGTH, 0);
562                 m2 = new DoubleModel(component, "AftRadius", 2, UnitGroup.UNITS_LENGTH);
563                 
564                 spin = new JSpinner(m.getSpinnerModel());
565                 spin.setEditor(new SpinnerEditor(spin));
566                 sub.add(spin, "growx");
567                 
568                 sub.add(new UnitSelector(m), "growx");
569                 sub.add(new BasicSlider(m.getSliderModel(m0, m2)), "w 100lp, wrap");
570                 
571                 
572                 ////  Length:
573                 sub.add(new JLabel(trans.get("RocketCompCfg.lbl.Length")));
574                 
575                 m = new DoubleModel(component, "AftShoulderLength", UnitGroup.UNITS_LENGTH, 0);
576                 
577                 spin = new JSpinner(m.getSpinnerModel());
578                 spin.setEditor(new SpinnerEditor(spin));
579                 sub.add(spin, "growx");
580                 
581                 sub.add(new UnitSelector(m), "growx");
582                 sub.add(new BasicSlider(m.getSliderModel(0, 0.02, 0.2)), "w 100lp, wrap");
583                 
584                 
585                 ////  Thickness:
586                 sub.add(new JLabel(trans.get("RocketCompCfg.lbl.Thickness")));
587                 
588                 m = new DoubleModel(component, "AftShoulderThickness", UnitGroup.UNITS_LENGTH, 0);
589                 m2 = new DoubleModel(component, "AftShoulderRadius", UnitGroup.UNITS_LENGTH);
590                 
591                 spin = new JSpinner(m.getSpinnerModel());
592                 spin.setEditor(new SpinnerEditor(spin));
593                 sub.add(spin, "growx");
594                 
595                 sub.add(new UnitSelector(m), "growx");
596                 sub.add(new BasicSlider(m.getSliderModel(m0, m2)), "w 100lp, wrap");
597                 
598                 
599                 ////  Capped
600                 bm = new BooleanModel(component, "AftShoulderCapped");
601                 check = new JCheckBox(bm);
602                 //// End capped
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");
607                 
608                 
609                 panel.add(sub);
610                 
611                 
612                 return panel;
613         }
614         
615         
616         
617         
618         /*
619          * Private inner class to handle events in componentNameField.
620          */
621         private class TextFieldListener implements ActionListener, FocusListener {
622                 @Override
623                 public void actionPerformed(ActionEvent e) {
624                         setName();
625                 }
626                 
627                 @Override
628                 public void focusGained(FocusEvent e) {
629                 }
630                 
631                 @Override
632                 public void focusLost(FocusEvent e) {
633                         setName();
634                 }
635                 
636                 private void setName() {
637                         if (!component.getName().equals(componentNameField.getText())) {
638                                 component.setName(componentNameField.getText());
639                         }
640                         if (!component.getComment().equals(commentTextArea.getText())) {
641                                 component.setComment(commentTextArea.getText());
642                         }
643                 }
644         }
645         
646         
647         protected void register(Invalidatable model) {
648                 this.invalidatables.add(model);
649         }
650         
651         public void invalidateModels() {
652                 for (Invalidatable i : invalidatables) {
653                         i.invalidate();
654                 }
655                 ((ComponentPresetDatabase)Application.getComponentPresetDao()).removeChangeListener(presetModel);
656
657         }
658         
659 }