lose embedded source jars from upstream branch
[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
13 import javax.swing.BorderFactory;
14 import javax.swing.JButton;
15 import javax.swing.JCheckBox;
16 import javax.swing.JColorChooser;
17 import javax.swing.JComboBox;
18 import javax.swing.JLabel;
19 import javax.swing.JPanel;
20 import javax.swing.JScrollPane;
21 import javax.swing.JSpinner;
22 import javax.swing.JTabbedPane;
23 import javax.swing.JTextArea;
24 import javax.swing.JTextField;
25
26 import net.miginfocom.swing.MigLayout;
27 import net.sf.openrocket.document.OpenRocketDocument;
28 import net.sf.openrocket.gui.SpinnerEditor;
29 import net.sf.openrocket.gui.adaptors.BooleanModel;
30 import net.sf.openrocket.gui.adaptors.DoubleModel;
31 import net.sf.openrocket.gui.adaptors.EnumModel;
32 import net.sf.openrocket.gui.adaptors.MaterialModel;
33 import net.sf.openrocket.gui.components.BasicSlider;
34 import net.sf.openrocket.gui.components.ColorIcon;
35 import net.sf.openrocket.gui.components.StyledLabel;
36 import net.sf.openrocket.gui.components.StyledLabel.Style;
37 import net.sf.openrocket.gui.components.UnitSelector;
38 import net.sf.openrocket.gui.util.ColorConversion;
39 import net.sf.openrocket.gui.util.GUIUtil;
40 import net.sf.openrocket.gui.util.SwingPreferences;
41 import net.sf.openrocket.l10n.Translator;
42 import net.sf.openrocket.material.Material;
43 import net.sf.openrocket.rocketcomponent.ComponentAssembly;
44 import net.sf.openrocket.rocketcomponent.ExternalComponent;
45 import net.sf.openrocket.rocketcomponent.ExternalComponent.Finish;
46 import net.sf.openrocket.rocketcomponent.NoseCone;
47 import net.sf.openrocket.rocketcomponent.RocketComponent;
48 import net.sf.openrocket.startup.Application;
49 import net.sf.openrocket.unit.UnitGroup;
50 import net.sf.openrocket.util.Invalidatable;
51 import net.sf.openrocket.util.LineStyle;
52
53 public class RocketComponentConfig extends JPanel {
54         
55         private static final Translator trans = Application.getTranslator();
56         
57         protected final OpenRocketDocument document;
58         protected final RocketComponent component;
59         protected final JTabbedPane tabbedPane;
60         
61         private final List<Invalidatable> invalidatables = new ArrayList<Invalidatable>();
62         
63
64         protected final JTextField componentNameField;
65         protected JTextArea commentTextArea;
66         private final TextFieldListener textFieldListener;
67         private JButton colorButton;
68         private JCheckBox colorDefault;
69         private JPanel buttonPanel;
70         
71         private JLabel massLabel;
72         
73         
74         public RocketComponentConfig(OpenRocketDocument document, RocketComponent component) {
75                 setLayout(new MigLayout("fill", "[grow, fill]"));
76                 this.document = document;
77                 this.component = component;
78                 
79                 //// Component name:
80                 JLabel label = new JLabel(trans.get("RocketCompCfg.lbl.Componentname"));
81                 //// The component name.
82                 label.setToolTipText(trans.get("RocketCompCfg.ttip.Thecomponentname"));
83                 this.add(label, "split, gapright 10");
84                 
85                 componentNameField = new JTextField(15);
86                 textFieldListener = new TextFieldListener();
87                 componentNameField.addActionListener(textFieldListener);
88                 componentNameField.addFocusListener(textFieldListener);
89                 //// The component name.
90                 componentNameField.setToolTipText(trans.get("RocketCompCfg.ttip.Thecomponentname"));
91                 this.add(componentNameField, "growx, growy 0, wrap");
92                 
93
94                 tabbedPane = new JTabbedPane();
95                 this.add(tabbedPane, "growx, growy 1, wrap");
96                 
97                 //// Override and Mass and CG override options
98                 tabbedPane.addTab(trans.get("RocketCompCfg.tab.Override"), null, overrideTab(),
99                                 trans.get("RocketCompCfg.tab.MassandCGoverride"));
100                 if (component.isMassive())
101                         //// Figure and Figure style options
102                         tabbedPane.addTab(trans.get("RocketCompCfg.tab.Figure"), null, figureTab(),
103                                         trans.get("RocketCompCfg.tab.Figstyleopt"));
104                 //// Comment and Specify a comment for the component
105                 tabbedPane.addTab(trans.get("RocketCompCfg.tab.Comment"), null, commentTab(),
106                                 trans.get("RocketCompCfg.tab.Specifyacomment"));
107                 
108                 addButtons();
109                 
110                 updateFields();
111         }
112         
113         
114         protected void addButtons(JButton... buttons) {
115                 if (buttonPanel != null) {
116                         this.remove(buttonPanel);
117                 }
118                 
119                 buttonPanel = new JPanel(new MigLayout("fill, ins 0"));
120                 
121                 //// Mass:
122                 massLabel = new StyledLabel(trans.get("RocketCompCfg.lbl.Mass") + " ", -1);
123                 buttonPanel.add(massLabel, "growx");
124                 
125                 for (JButton b : buttons) {
126                         buttonPanel.add(b, "right, gap para");
127                 }
128                 
129                 //// Close button
130                 JButton closeButton = new JButton(trans.get("dlg.but.close"));
131                 closeButton.addActionListener(new ActionListener() {
132                         @Override
133                         public void actionPerformed(ActionEvent arg0) {
134                                 ComponentConfigDialog.hideDialog();
135                         }
136                 });
137                 buttonPanel.add(closeButton, "right, gap 30lp");
138                 
139                 updateFields();
140                 
141                 this.add(buttonPanel, "spanx, growx");
142         }
143         
144         
145         /**
146          * Called when a change occurs, so that the fields can be updated if necessary.
147          * When overriding this method, the supermethod must always be called.
148          */
149         public void updateFields() {
150                 // Component name
151                 componentNameField.setText(component.getName());
152                 
153                 // Component color and "Use default color" checkbox
154                 if (colorButton != null && colorDefault != null) {
155                         colorButton.setIcon(new ColorIcon(getColor()));
156                         
157                         if ((component.getColor() == null) != colorDefault.isSelected())
158                                 colorDefault.setSelected(component.getColor() == null);
159                 }
160                 
161                 // Mass label
162                 if (component.isMassive()) {
163                         //// Component mass:
164                         String text = trans.get("RocketCompCfg.lbl.Componentmass") + " ";
165                         text += UnitGroup.UNITS_MASS.getDefaultUnit().toStringUnit(
166                                         component.getComponentMass());
167                         
168                         String overridetext = null;
169                         if (component.isMassOverridden()) {
170                                 //// (overridden to 
171                                 overridetext = trans.get("RocketCompCfg.lbl.overriddento") + " " + UnitGroup.UNITS_MASS.getDefaultUnit().
172                                                 toStringUnit(component.getOverrideMass()) + ")";
173                         }
174                         
175                         for (RocketComponent c = component.getParent(); c != null; c = c.getParent()) {
176                                 if (c.isMassOverridden() && c.getOverrideSubcomponents()) {
177                                         ///// (overridden by
178                                         overridetext = trans.get("RocketCompCfg.lbl.overriddenby") + " " + c.getName() + ")";
179                                 }
180                         }
181                         
182                         if (overridetext != null)
183                                 text = text + " " + overridetext;
184                         
185                         massLabel.setText(text);
186                 } else {
187                         massLabel.setText("");
188                 }
189         }
190         
191         
192         protected JPanel materialPanel(JPanel panel, Material.Type type) {
193                 ////Component material: and Component finish:
194                 return materialPanel(panel, type, trans.get("RocketCompCfg.lbl.Componentmaterial"),
195                                 trans.get("RocketCompCfg.lbl.Componentfinish"));
196         }
197         
198         protected JPanel materialPanel(JPanel panel, Material.Type type,
199                         String materialString, String finishString) {
200                 JLabel label = new JLabel(materialString);
201                 //// The component material affects the weight of the component.
202                 label.setToolTipText(trans.get("RocketCompCfg.lbl.ttip.componentmaterialaffects"));
203                 panel.add(label, "spanx 4, wrap rel");
204                 
205                 JComboBox combo = new JComboBox(new MaterialModel(panel, component, type));
206                 //// The component material affects the weight of the component.
207                 combo.setToolTipText(trans.get("RocketCompCfg.combo.ttip.componentmaterialaffects"));
208                 panel.add(combo, "spanx 4, growx, wrap paragraph");
209                 
210
211                 if (component instanceof ExternalComponent) {
212                         label = new JLabel(finishString);
213                         ////<html>The component finish affects the aerodynamic drag of the component.<br>
214                         String tip = trans.get("RocketCompCfg.lbl.longA1")
215                                         //// The value indicated is the average roughness height of the surface.
216                                         + trans.get("RocketCompCfg.lbl.longA2");
217                         label.setToolTipText(tip);
218                         panel.add(label, "spanx 4, wmin 220lp, wrap rel");
219                         
220                         combo = new JComboBox(new EnumModel<ExternalComponent.Finish>(component, "Finish"));
221                         combo.setToolTipText(tip);
222                         panel.add(combo, "spanx 4, growx, split");
223                         
224                         //// Set for all
225                         JButton button = new JButton(trans.get("RocketCompCfg.but.Setforall"));
226                         //// Set this finish for all components of the rocket.
227                         button.setToolTipText(trans.get("RocketCompCfg.but.ttip.Setforall"));
228                         button.addActionListener(new ActionListener() {
229                                 @Override
230                                 public void actionPerformed(ActionEvent e) {
231                                         Finish f = ((ExternalComponent) component).getFinish();
232                                         try {
233                                                 document.startUndo("Set rocket finish");
234                                                 
235                                                 // Do changes
236                                                 Iterator<RocketComponent> iter = component.getRoot().iterator();
237                                                 while (iter.hasNext()) {
238                                                         RocketComponent c = iter.next();
239                                                         if (c instanceof ExternalComponent) {
240                                                                 ((ExternalComponent) c).setFinish(f);
241                                                         }
242                                                 }
243                                         } finally {
244                                                 document.stopUndo();
245                                         }
246                                 }
247                         });
248                         panel.add(button, "wrap paragraph");
249                 }
250                 
251                 return panel;
252         }
253         
254         
255         private JPanel overrideTab() {
256                 JPanel panel = new JPanel(new MigLayout("align 50% 20%, fillx, gap rel unrel",
257                                 "[][65lp::][30lp::][]", ""));
258                 //// Override the mass or center of gravity of the
259                 panel.add(new StyledLabel(trans.get("RocketCompCfg.lbl.Overridemassorcenter") + " " +
260                                 component.getComponentName() + ":", Style.BOLD), "spanx, wrap 20lp");
261                 
262                 JCheckBox check;
263                 BooleanModel bm;
264                 UnitSelector us;
265                 BasicSlider bs;
266                 
267                 ////  Mass
268                 bm = new BooleanModel(component, "MassOverridden");
269                 check = new JCheckBox(bm);
270                 //// Override mass:
271                 check.setText(trans.get("RocketCompCfg.checkbox.Overridemass"));
272                 panel.add(check, "growx 1, gapright 20lp");
273                 
274                 DoubleModel m = new DoubleModel(component, "OverrideMass", UnitGroup.UNITS_MASS, 0);
275                 
276                 JSpinner spin = new JSpinner(m.getSpinnerModel());
277                 spin.setEditor(new SpinnerEditor(spin));
278                 bm.addEnableComponent(spin, true);
279                 panel.add(spin, "growx 1");
280                 
281                 us = new UnitSelector(m);
282                 bm.addEnableComponent(us, true);
283                 panel.add(us, "growx 1");
284                 
285                 bs = new BasicSlider(m.getSliderModel(0, 0.03, 1.0));
286                 bm.addEnableComponent(bs);
287                 panel.add(bs, "growx 5, w 100lp, wrap");
288                 
289
290                 ////  CG override
291                 bm = new BooleanModel(component, "CGOverridden");
292                 check = new JCheckBox(bm);
293                 //// Override center of gravity:"
294                 check.setText(trans.get("RocketCompCfg.checkbox.Overridecenterofgrav"));
295                 panel.add(check, "growx 1, gapright 20lp");
296                 
297                 m = new DoubleModel(component, "OverrideCGX", UnitGroup.UNITS_LENGTH, 0);
298                 // Calculate suitable length for slider
299                 DoubleModel length;
300                 if (component instanceof ComponentAssembly) {
301                         double l = 0;
302                         
303                         Iterator<RocketComponent> iterator = component.iterator(false);
304                         while (iterator.hasNext()) {
305                                 RocketComponent c = iterator.next();
306                                 if (c.getRelativePosition() == RocketComponent.Position.AFTER)
307                                         l += c.getLength();
308                         }
309                         length = new DoubleModel(l);
310                 } else {
311                         length = new DoubleModel(component, "Length", UnitGroup.UNITS_LENGTH, 0);
312                 }
313                 
314                 spin = new JSpinner(m.getSpinnerModel());
315                 spin.setEditor(new SpinnerEditor(spin));
316                 bm.addEnableComponent(spin, true);
317                 panel.add(spin, "growx 1");
318                 
319                 us = new UnitSelector(m);
320                 bm.addEnableComponent(us, true);
321                 panel.add(us, "growx 1");
322                 
323                 bs = new BasicSlider(m.getSliderModel(new DoubleModel(0), length));
324                 bm.addEnableComponent(bs);
325                 panel.add(bs, "growx 5, w 100lp, wrap 35lp");
326                 
327
328                 // Override subcomponents checkbox
329                 bm = new BooleanModel(component, "OverrideSubcomponents");
330                 check = new JCheckBox(bm);
331                 //// Override mass and CG of all subcomponents
332                 check.setText(trans.get("RocketCompCfg.checkbox.OverridemassandCG"));
333                 panel.add(check, "gap para, spanx, wrap para");
334                 
335                 //// <html>The overridden mass does not include motors.<br>
336                 panel.add(new StyledLabel(trans.get("RocketCompCfg.lbl.longB1") +
337                                 //// The center of gravity is measured from the front end of the
338                                 trans.get("RocketCompCfg.lbl.longB2") + " " +
339                                 component.getComponentName().toLowerCase() + ".", -1),
340                                 "spanx, wrap, gap para, height 0::30lp");
341                 
342                 return panel;
343         }
344         
345         
346         private JPanel commentTab() {
347                 JPanel panel = new JPanel(new MigLayout("fill"));
348                 
349                 //// Comments on the
350                 panel.add(new StyledLabel(trans.get("RocketCompCfg.lbl.Commentsonthe") + " " + component.getComponentName() + ":",
351                                 Style.BOLD), "wrap");
352                 
353                 // TODO: LOW:  Changes in comment from other sources not reflected in component
354                 commentTextArea = new JTextArea(component.getComment());
355                 commentTextArea.setLineWrap(true);
356                 commentTextArea.setWrapStyleWord(true);
357                 commentTextArea.setEditable(true);
358                 GUIUtil.setTabToFocusing(commentTextArea);
359                 commentTextArea.addFocusListener(textFieldListener);
360                 
361                 panel.add(new JScrollPane(commentTextArea), "width 10px, height 10px, growx, growy");
362                 
363                 return panel;
364         }
365         
366         
367
368         private JPanel figureTab() {
369                 JPanel panel = new JPanel(new MigLayout("align 20% 20%"));
370                 
371                 //// Figure style:
372                 panel.add(new StyledLabel(trans.get("RocketCompCfg.lbl.Figurestyle"), Style.BOLD), "wrap para");
373                 
374                 //// Component color:
375                 panel.add(new JLabel(trans.get("RocketCompCfg.lbl.Componentcolor")), "gapleft para, gapright 10lp");
376                 
377                 colorButton = new JButton(new ColorIcon(getColor()));
378                 colorButton.addActionListener(new ActionListener() {
379                         @Override
380                         public void actionPerformed(ActionEvent e) {
381                                 net.sf.openrocket.util.Color c = component.getColor();
382                                 if (c == null) {
383                                         c = Application.getPreferences().getDefaultColor(component.getClass());
384                                 }
385                                 
386                                 //// Choose color
387                                 Color awtColor = ColorConversion.toAwtColor(c); 
388                                 awtColor = JColorChooser.showDialog(tabbedPane, trans.get("RocketCompCfg.lbl.Choosecolor"), awtColor);
389                                 c = ColorConversion.fromAwtColor(awtColor);
390                                 if (c != null) {
391                                         component.setColor(c);
392                                 }
393                         }
394                 });
395                 panel.add(colorButton, "gapright 10lp");
396                 
397                 //// Use default color
398                 colorDefault = new JCheckBox(trans.get("RocketCompCfg.checkbox.Usedefaultcolor"));
399                 if (component.getColor() == null)
400                         colorDefault.setSelected(true);
401                 colorDefault.addActionListener(new ActionListener() {
402                         @Override
403                         public void actionPerformed(ActionEvent e) {
404                                 if (colorDefault.isSelected())
405                                         component.setColor(null);
406                                 else
407                                         component.setColor(((SwingPreferences) Application.getPreferences()).getDefaultColor(component.getClass()));
408                         }
409                 });
410                 panel.add(colorDefault, "wrap para");
411                 
412                 //// Component line style:
413                 panel.add(new JLabel(trans.get("RocketCompCfg.lbl.Complinestyle")), "gapleft para, gapright 10lp");
414                 
415                 LineStyle[] list = new LineStyle[LineStyle.values().length + 1];
416                 System.arraycopy(LineStyle.values(), 0, list, 1, LineStyle.values().length);
417                 
418                 JComboBox combo = new JComboBox(new EnumModel<LineStyle>(component, "LineStyle",
419                                 //// Default style
420                                 list, trans.get("LineStyle.Defaultstyle")));
421                 panel.add(combo, "spanx 2, growx, wrap 50lp");
422                 
423                 //// Save as default style
424                 JButton button = new JButton(trans.get("RocketCompCfg.but.Saveasdefstyle"));
425                 button.addActionListener(new ActionListener() {
426                         @Override
427                         public void actionPerformed(ActionEvent e) {
428                                 if (component.getColor() != null) {
429                                         ((SwingPreferences) Application.getPreferences()).setDefaultColor(component.getClass(), component.getColor());
430                                         component.setColor(null);
431                                 }
432                                 if (component.getLineStyle() != null) {
433                                         Application.getPreferences().setDefaultLineStyle(component.getClass(), component.getLineStyle());
434                                         component.setLineStyle(null);
435                                 }
436                         }
437                 });
438                 panel.add(button, "gapleft para, spanx 3, growx, wrap");
439                 
440                 return panel;
441         }
442         
443         
444         private Color getColor() {
445                 net.sf.openrocket.util.Color c = component.getColor();
446                 if (c == null) {
447                         c = Application.getPreferences().getDefaultColor(component.getClass());
448                 }
449                 return ColorConversion.toAwtColor(c);
450         }
451         
452         
453
454         protected JPanel shoulderTab() {
455                 JPanel panel = new JPanel(new MigLayout("fill"));
456                 JPanel sub;
457                 DoubleModel m, m2;
458                 DoubleModel m0 = new DoubleModel(0);
459                 BooleanModel bm;
460                 JCheckBox check;
461                 JSpinner spin;
462                 
463
464                 ////  Fore shoulder, not for NoseCone
465                 
466                 if (!(component instanceof NoseCone)) {
467                         sub = new JPanel(new MigLayout("gap rel unrel", "[][65lp::][30lp::]", ""));
468                         
469                         //// Fore shoulder
470                         sub.setBorder(BorderFactory.createTitledBorder(trans.get("RocketCompCfg.border.Foreshoulder")));
471                         
472
473                         ////  Radius
474                         //// Diameter:
475                         sub.add(new JLabel(trans.get("RocketCompCfg.lbl.Diameter")));
476                         
477                         m = new DoubleModel(component, "ForeShoulderRadius", 2, UnitGroup.UNITS_LENGTH, 0);
478                         m2 = new DoubleModel(component, "ForeRadius", 2, UnitGroup.UNITS_LENGTH);
479                         
480                         spin = new JSpinner(m.getSpinnerModel());
481                         spin.setEditor(new SpinnerEditor(spin));
482                         sub.add(spin, "growx");
483                         
484                         sub.add(new UnitSelector(m), "growx");
485                         sub.add(new BasicSlider(m.getSliderModel(m0, m2)), "w 100lp, wrap");
486                         
487
488                         ////  Length:
489                         sub.add(new JLabel(trans.get("RocketCompCfg.lbl.Length")));
490                         
491                         m = new DoubleModel(component, "ForeShoulderLength", UnitGroup.UNITS_LENGTH, 0);
492                         
493                         spin = new JSpinner(m.getSpinnerModel());
494                         spin.setEditor(new SpinnerEditor(spin));
495                         sub.add(spin, "growx");
496                         
497                         sub.add(new UnitSelector(m), "growx");
498                         sub.add(new BasicSlider(m.getSliderModel(0, 0.02, 0.2)), "w 100lp, wrap");
499                         
500
501                         ////  Thickness:
502                         sub.add(new JLabel(trans.get("RocketCompCfg.lbl.Thickness")));
503                         
504                         m = new DoubleModel(component, "ForeShoulderThickness", UnitGroup.UNITS_LENGTH, 0);
505                         m2 = new DoubleModel(component, "ForeShoulderRadius", UnitGroup.UNITS_LENGTH);
506                         
507                         spin = new JSpinner(m.getSpinnerModel());
508                         spin.setEditor(new SpinnerEditor(spin));
509                         sub.add(spin, "growx");
510                         
511                         sub.add(new UnitSelector(m), "growx");
512                         sub.add(new BasicSlider(m.getSliderModel(m0, m2)), "w 100lp, wrap");
513                         
514
515                         ////  Capped
516                         bm = new BooleanModel(component, "ForeShoulderCapped");
517                         check = new JCheckBox(bm);
518                         //// End capped
519                         check.setText(trans.get("RocketCompCfg.checkbox.Endcapped"));
520                         //// Whether the end of the shoulder is capped.
521                         check.setToolTipText(trans.get("RocketCompCfg.ttip.Endcapped"));
522                         sub.add(check, "spanx");
523                         
524
525                         panel.add(sub);
526                 }
527                 
528
529                 ////  Aft shoulder
530                 sub = new JPanel(new MigLayout("gap rel unrel", "[][65lp::][30lp::]", ""));
531                 
532                 if (component instanceof NoseCone)
533                         //// Nose cone shoulder
534                         sub.setBorder(BorderFactory.createTitledBorder(trans.get("RocketCompCfg.title.Noseconeshoulder")));
535                 else
536                         //// Aft shoulder
537                         sub.setBorder(BorderFactory.createTitledBorder(trans.get("RocketCompCfg.title.Aftshoulder")));
538                 
539
540                 ////  Radius
541                 //// Diameter:
542                 sub.add(new JLabel(trans.get("RocketCompCfg.lbl.Diameter")));
543                 
544                 m = new DoubleModel(component, "AftShoulderRadius", 2, UnitGroup.UNITS_LENGTH, 0);
545                 m2 = new DoubleModel(component, "AftRadius", 2, UnitGroup.UNITS_LENGTH);
546                 
547                 spin = new JSpinner(m.getSpinnerModel());
548                 spin.setEditor(new SpinnerEditor(spin));
549                 sub.add(spin, "growx");
550                 
551                 sub.add(new UnitSelector(m), "growx");
552                 sub.add(new BasicSlider(m.getSliderModel(m0, m2)), "w 100lp, wrap");
553                 
554
555                 ////  Length:
556                 sub.add(new JLabel(trans.get("RocketCompCfg.lbl.Length")));
557                 
558                 m = new DoubleModel(component, "AftShoulderLength", UnitGroup.UNITS_LENGTH, 0);
559                 
560                 spin = new JSpinner(m.getSpinnerModel());
561                 spin.setEditor(new SpinnerEditor(spin));
562                 sub.add(spin, "growx");
563                 
564                 sub.add(new UnitSelector(m), "growx");
565                 sub.add(new BasicSlider(m.getSliderModel(0, 0.02, 0.2)), "w 100lp, wrap");
566                 
567
568                 ////  Thickness:
569                 sub.add(new JLabel(trans.get("RocketCompCfg.lbl.Thickness")));
570                 
571                 m = new DoubleModel(component, "AftShoulderThickness", UnitGroup.UNITS_LENGTH, 0);
572                 m2 = new DoubleModel(component, "AftShoulderRadius", UnitGroup.UNITS_LENGTH);
573                 
574                 spin = new JSpinner(m.getSpinnerModel());
575                 spin.setEditor(new SpinnerEditor(spin));
576                 sub.add(spin, "growx");
577                 
578                 sub.add(new UnitSelector(m), "growx");
579                 sub.add(new BasicSlider(m.getSliderModel(m0, m2)), "w 100lp, wrap");
580                 
581
582                 ////  Capped
583                 bm = new BooleanModel(component, "AftShoulderCapped");
584                 check = new JCheckBox(bm);
585                 //// End capped
586                 check.setText(trans.get("RocketCompCfg.checkbox.Endcapped"));
587                 //// Whether the end of the shoulder is capped.
588                 check.setToolTipText(trans.get("RocketCompCfg.ttip.Endcapped"));
589                 sub.add(check, "spanx");
590                 
591
592                 panel.add(sub);
593                 
594
595                 return panel;
596         }
597         
598         
599
600
601         /*
602          * Private inner class to handle events in componentNameField.
603          */
604         private class TextFieldListener implements ActionListener, FocusListener {
605                 @Override
606                 public void actionPerformed(ActionEvent e) {
607                         setName();
608                 }
609                 
610                 @Override
611                 public void focusGained(FocusEvent e) {
612                 }
613                 
614                 @Override
615                 public void focusLost(FocusEvent e) {
616                         setName();
617                 }
618                 
619                 private void setName() {
620                         if (!component.getName().equals(componentNameField.getText())) {
621                                 component.setName(componentNameField.getText());
622                         }
623                         if (!component.getComment().equals(commentTextArea.getText())) {
624                                 component.setComment(commentTextArea.getText());
625                         }
626                 }
627         }
628         
629         
630         protected void register(Invalidatable model) {
631                 this.invalidatables.add(model);
632         }
633         
634         public void invalidateModels() {
635                 for (Invalidatable i : invalidatables) {
636                         i.invalidate();
637                 }
638         }
639         
640 }