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