de867ba4c2b950b875aaff60721781e9b048ab3f
[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", "[min,align right]:10[fill, grow]"));
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);
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, "wrap");
97                 
98                 if ( component.getPresetType() != null ) {
99                         // If the component supports a preset, show the preset selection box.
100                         this.add(new JLabel(trans.get("PresetModel.lbl.select")));
101                         presetModel = new PresetModel( this, component);
102                         ((ComponentPresetDatabase)Application.getComponentPresetDao()).addDatabaseListener(presetModel);
103                         presetComboBox = new JComboBox(presetModel);
104                         presetComboBox.setEditable(false);
105                         this.add(presetComboBox, "wrap");
106                 }
107                 
108
109                 tabbedPane = new JTabbedPane();
110                 this.add(tabbedPane, "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                 massLabel = new StyledLabel(trans.get("RocketCompCfg.lbl.Mass") + " ", -1);
138                 buttonPanel.add(massLabel, "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                 // Mass label
177                 if (component.isMassive()) {
178                         //// Component mass:
179                         String text = trans.get("RocketCompCfg.lbl.Componentmass") + " ";
180                         text += UnitGroup.UNITS_MASS.getDefaultUnit().toStringUnit(
181                                         component.getComponentMass());
182                         
183                         String overridetext = null;
184                         if (component.isMassOverridden()) {
185                                 //// (overridden to 
186                                 overridetext = trans.get("RocketCompCfg.lbl.overriddento") + " " + UnitGroup.UNITS_MASS.getDefaultUnit().
187                                                 toStringUnit(component.getOverrideMass()) + ")";
188                         }
189                         
190                         for (RocketComponent c = component.getParent(); c != null; c = c.getParent()) {
191                                 if (c.isMassOverridden() && c.getOverrideSubcomponents()) {
192                                         ///// (overridden by
193                                         overridetext = trans.get("RocketCompCfg.lbl.overriddenby") + " " + c.getName() + ")";
194                                 }
195                         }
196                         
197                         if (overridetext != null)
198                                 text = text + " " + overridetext;
199                         
200                         massLabel.setText(text);
201                 } else {
202                         massLabel.setText("");
203                 }
204         }
205         
206         
207         protected JPanel materialPanel(JPanel panel, Material.Type type) {
208                 ////Component material: and Component finish:
209                 return materialPanel(panel, type, trans.get("RocketCompCfg.lbl.Componentmaterial"),
210                                 trans.get("RocketCompCfg.lbl.Componentfinish"));
211         }
212         
213         protected JPanel materialPanel(JPanel panel, Material.Type type,
214                         String materialString, String finishString) {
215                 
216                 JLabel label = new JLabel(materialString);
217                 //// The component material affects the weight of the component.
218                 label.setToolTipText(trans.get("RocketCompCfg.lbl.ttip.componentmaterialaffects"));
219                 panel.add(label, "spanx 4, wrap rel");
220                 
221                 JComboBox combo = new JComboBox(new MaterialModel(panel, component, type));
222                 //// The component material affects the weight of the component.
223                 combo.setToolTipText(trans.get("RocketCompCfg.combo.ttip.componentmaterialaffects"));
224                 panel.add(combo, "spanx 4, growx, wrap paragraph");
225                 
226                 
227                 if (component instanceof ExternalComponent) {
228                         label = new JLabel(finishString);
229                         ////<html>The component finish affects the aerodynamic drag of the component.<br>
230                         String tip = trans.get("RocketCompCfg.lbl.longA1")
231                                         //// The value indicated is the average roughness height of the surface.
232                                         + trans.get("RocketCompCfg.lbl.longA2");
233                         label.setToolTipText(tip);
234                         panel.add(label, "spanx 4, wmin 220lp, wrap rel");
235                         
236                         combo = new JComboBox(new EnumModel<ExternalComponent.Finish>(component, "Finish"));
237                         combo.setToolTipText(tip);
238                         panel.add(combo, "spanx 4, growx, split");
239                         
240                         //// Set for all
241                         JButton button = new JButton(trans.get("RocketCompCfg.but.Setforall"));
242                         //// Set this finish for all components of the rocket.
243                         button.setToolTipText(trans.get("RocketCompCfg.but.ttip.Setforall"));
244                         button.addActionListener(new ActionListener() {
245                                 @Override
246                                 public void actionPerformed(ActionEvent e) {
247                                         Finish f = ((ExternalComponent) component).getFinish();
248                                         try {
249                                                 document.startUndo("Set rocket finish");
250                                                 
251                                                 // Do changes
252                                                 Iterator<RocketComponent> iter = component.getRoot().iterator();
253                                                 while (iter.hasNext()) {
254                                                         RocketComponent c = iter.next();
255                                                         if (c instanceof ExternalComponent) {
256                                                                 ((ExternalComponent) c).setFinish(f);
257                                                         }
258                                                 }
259                                         } finally {
260                                                 document.stopUndo();
261                                         }
262                                 }
263                         });
264                         panel.add(button, "wrap paragraph");
265                 }
266                 
267                 return panel;
268         }
269         
270         
271         private JPanel overrideTab() {
272                 JPanel panel = new JPanel(new MigLayout("align 50% 20%, fillx, gap rel unrel",
273                                 "[][65lp::][30lp::][]", ""));
274                 //// Override the mass or center of gravity of the
275                 panel.add(new StyledLabel(trans.get("RocketCompCfg.lbl.Overridemassorcenter") + " " +
276                                 component.getComponentName() + ":", Style.BOLD), "spanx, wrap 20lp");
277                 
278                 JCheckBox check;
279                 BooleanModel bm;
280                 UnitSelector us;
281                 BasicSlider bs;
282                 
283                 ////  Mass
284                 bm = new BooleanModel(component, "MassOverridden");
285                 check = new JCheckBox(bm);
286                 //// Override mass:
287                 check.setText(trans.get("RocketCompCfg.checkbox.Overridemass"));
288                 panel.add(check, "growx 1, gapright 20lp");
289                 
290                 DoubleModel m = new DoubleModel(component, "OverrideMass", UnitGroup.UNITS_MASS, 0);
291                 
292                 JSpinner spin = new JSpinner(m.getSpinnerModel());
293                 spin.setEditor(new SpinnerEditor(spin));
294                 bm.addEnableComponent(spin, true);
295                 panel.add(spin, "growx 1");
296                 
297                 us = new UnitSelector(m);
298                 bm.addEnableComponent(us, true);
299                 panel.add(us, "growx 1");
300                 
301                 bs = new BasicSlider(m.getSliderModel(0, 0.03, 1.0));
302                 bm.addEnableComponent(bs);
303                 panel.add(bs, "growx 5, w 100lp, wrap");
304                 
305                 
306                 ////  CG override
307                 bm = new BooleanModel(component, "CGOverridden");
308                 check = new JCheckBox(bm);
309                 //// Override center of gravity:"
310                 check.setText(trans.get("RocketCompCfg.checkbox.Overridecenterofgrav"));
311                 panel.add(check, "growx 1, gapright 20lp");
312                 
313                 m = new DoubleModel(component, "OverrideCGX", UnitGroup.UNITS_LENGTH, 0);
314                 // Calculate suitable length for slider
315                 DoubleModel length;
316                 if (component instanceof ComponentAssembly) {
317                         double l = 0;
318                         
319                         Iterator<RocketComponent> iterator = component.iterator(false);
320                         while (iterator.hasNext()) {
321                                 RocketComponent c = iterator.next();
322                                 if (c.getRelativePosition() == RocketComponent.Position.AFTER)
323                                         l += c.getLength();
324                         }
325                         length = new DoubleModel(l);
326                 } else {
327                         length = new DoubleModel(component, "Length", UnitGroup.UNITS_LENGTH, 0);
328                 }
329                 
330                 spin = new JSpinner(m.getSpinnerModel());
331                 spin.setEditor(new SpinnerEditor(spin));
332                 bm.addEnableComponent(spin, true);
333                 panel.add(spin, "growx 1");
334                 
335                 us = new UnitSelector(m);
336                 bm.addEnableComponent(us, true);
337                 panel.add(us, "growx 1");
338                 
339                 bs = new BasicSlider(m.getSliderModel(new DoubleModel(0), length));
340                 bm.addEnableComponent(bs);
341                 panel.add(bs, "growx 5, w 100lp, wrap 35lp");
342                 
343                 
344                 // Override subcomponents checkbox
345                 bm = new BooleanModel(component, "OverrideSubcomponents");
346                 check = new JCheckBox(bm);
347                 //// Override mass and CG of all subcomponents
348                 check.setText(trans.get("RocketCompCfg.checkbox.OverridemassandCG"));
349                 panel.add(check, "gap para, spanx, wrap para");
350                 
351                 //// <html>The overridden mass does not include motors.<br>
352                 panel.add(new StyledLabel(trans.get("RocketCompCfg.lbl.longB1") +
353                                 //// The center of gravity is measured from the front end of the
354                                 trans.get("RocketCompCfg.lbl.longB2") + " " +
355                                 component.getComponentName().toLowerCase(Locale.getDefault()) + ".", -1),
356                                 "spanx, wrap, gap para, height 0::30lp");
357                 
358                 return panel;
359         }
360         
361         
362         private JPanel commentTab() {
363                 JPanel panel = new JPanel(new MigLayout("fill"));
364                 
365                 //// Comments on the
366                 panel.add(new StyledLabel(trans.get("RocketCompCfg.lbl.Commentsonthe") + " " + component.getComponentName() + ":",
367                                 Style.BOLD), "wrap");
368                 
369                 // TODO: LOW:  Changes in comment from other sources not reflected in component
370                 commentTextArea = new JTextArea(component.getComment());
371                 commentTextArea.setLineWrap(true);
372                 commentTextArea.setWrapStyleWord(true);
373                 commentTextArea.setEditable(true);
374                 GUIUtil.setTabToFocusing(commentTextArea);
375                 commentTextArea.addFocusListener(textFieldListener);
376                 
377                 panel.add(new JScrollPane(commentTextArea), "width 10px, height 10px, growx, growy");
378                 
379                 return panel;
380         }
381         
382         
383         
384         private JPanel figureTab() {
385                 JPanel panel = new JPanel(new MigLayout("align 20% 20%"));
386                 
387                 //// Figure style:
388                 panel.add(new StyledLabel(trans.get("RocketCompCfg.lbl.Figurestyle"), Style.BOLD), "wrap para");
389                 
390                 //// Component color:
391                 panel.add(new JLabel(trans.get("RocketCompCfg.lbl.Componentcolor")), "gapleft para, gapright 10lp");
392                 
393                 colorButton = new JButton(new ColorIcon(getColor()));
394                 colorButton.addActionListener(new ActionListener() {
395                         @Override
396                         public void actionPerformed(ActionEvent e) {
397                                 net.sf.openrocket.util.Color c = component.getColor();
398                                 if (c == null) {
399                                         c = Application.getPreferences().getDefaultColor(component.getClass());
400                                 }
401                                 
402                                 //// Choose color
403                                 Color awtColor = ColorConversion.toAwtColor(c);
404                                 awtColor = JColorChooser.showDialog(tabbedPane, trans.get("RocketCompCfg.lbl.Choosecolor"), awtColor);
405                                 c = ColorConversion.fromAwtColor(awtColor);
406                                 if (c != null) {
407                                         component.setColor(c);
408                                 }
409                         }
410                 });
411                 panel.add(colorButton, "gapright 10lp");
412                 
413                 //// Use default color
414                 colorDefault = new JCheckBox(trans.get("RocketCompCfg.checkbox.Usedefaultcolor"));
415                 if (component.getColor() == null)
416                         colorDefault.setSelected(true);
417                 colorDefault.addActionListener(new ActionListener() {
418                         @Override
419                         public void actionPerformed(ActionEvent e) {
420                                 if (colorDefault.isSelected())
421                                         component.setColor(null);
422                                 else
423                                         component.setColor(((SwingPreferences) Application.getPreferences()).getDefaultColor(component.getClass()));
424                         }
425                 });
426                 panel.add(colorDefault, "wrap para");
427                 
428                 //// Component line style:
429                 panel.add(new JLabel(trans.get("RocketCompCfg.lbl.Complinestyle")), "gapleft para, gapright 10lp");
430                 
431                 LineStyle[] list = new LineStyle[LineStyle.values().length + 1];
432                 System.arraycopy(LineStyle.values(), 0, list, 1, LineStyle.values().length);
433                 
434                 JComboBox combo = new JComboBox(new EnumModel<LineStyle>(component, "LineStyle",
435                                 //// Default style
436                                 list, trans.get("LineStyle.Defaultstyle")));
437                 panel.add(combo, "spanx 2, growx, wrap 50lp");
438                 
439                 //// Save as default style
440                 JButton button = new JButton(trans.get("RocketCompCfg.but.Saveasdefstyle"));
441                 button.addActionListener(new ActionListener() {
442                         @Override
443                         public void actionPerformed(ActionEvent e) {
444                                 if (component.getColor() != null) {
445                                         ((SwingPreferences) Application.getPreferences()).setDefaultColor(component.getClass(), component.getColor());
446                                         component.setColor(null);
447                                 }
448                                 if (component.getLineStyle() != null) {
449                                         Application.getPreferences().setDefaultLineStyle(component.getClass(), component.getLineStyle());
450                                         component.setLineStyle(null);
451                                 }
452                         }
453                 });
454                 panel.add(button, "gapleft para, spanx 3, growx, wrap");
455                 
456                 return panel;
457         }
458         
459         
460         private Color getColor() {
461                 net.sf.openrocket.util.Color c = component.getColor();
462                 if (c == null) {
463                         c = Application.getPreferences().getDefaultColor(component.getClass());
464                 }
465                 return ColorConversion.toAwtColor(c);
466         }
467         
468         
469         
470         protected JPanel shoulderTab() {
471                 JPanel panel = new JPanel(new MigLayout("fill"));
472                 JPanel sub;
473                 DoubleModel m, m2;
474                 DoubleModel m0 = new DoubleModel(0);
475                 BooleanModel bm;
476                 JCheckBox check;
477                 JSpinner spin;
478                 
479                 
480                 ////  Fore shoulder, not for NoseCone
481                 
482                 if (!(component instanceof NoseCone)) {
483                         sub = new JPanel(new MigLayout("gap rel unrel", "[][65lp::][30lp::]", ""));
484                         
485                         //// Fore shoulder
486                         sub.setBorder(BorderFactory.createTitledBorder(trans.get("RocketCompCfg.border.Foreshoulder")));
487                         
488                         
489                         ////  Radius
490                         //// Diameter:
491                         sub.add(new JLabel(trans.get("RocketCompCfg.lbl.Diameter")));
492                         
493                         m = new DoubleModel(component, "ForeShoulderRadius", 2, UnitGroup.UNITS_LENGTH, 0);
494                         m2 = new DoubleModel(component, "ForeRadius", 2, UnitGroup.UNITS_LENGTH);
495                         
496                         spin = new JSpinner(m.getSpinnerModel());
497                         spin.setEditor(new SpinnerEditor(spin));
498                         sub.add(spin, "growx");
499                         
500                         sub.add(new UnitSelector(m), "growx");
501                         sub.add(new BasicSlider(m.getSliderModel(m0, m2)), "w 100lp, wrap");
502                         
503                         
504                         ////  Length:
505                         sub.add(new JLabel(trans.get("RocketCompCfg.lbl.Length")));
506                         
507                         m = new DoubleModel(component, "ForeShoulderLength", UnitGroup.UNITS_LENGTH, 0);
508                         
509                         spin = new JSpinner(m.getSpinnerModel());
510                         spin.setEditor(new SpinnerEditor(spin));
511                         sub.add(spin, "growx");
512                         
513                         sub.add(new UnitSelector(m), "growx");
514                         sub.add(new BasicSlider(m.getSliderModel(0, 0.02, 0.2)), "w 100lp, wrap");
515                         
516                         
517                         ////  Thickness:
518                         sub.add(new JLabel(trans.get("RocketCompCfg.lbl.Thickness")));
519                         
520                         m = new DoubleModel(component, "ForeShoulderThickness", UnitGroup.UNITS_LENGTH, 0);
521                         m2 = new DoubleModel(component, "ForeShoulderRadius", UnitGroup.UNITS_LENGTH);
522                         
523                         spin = new JSpinner(m.getSpinnerModel());
524                         spin.setEditor(new SpinnerEditor(spin));
525                         sub.add(spin, "growx");
526                         
527                         sub.add(new UnitSelector(m), "growx");
528                         sub.add(new BasicSlider(m.getSliderModel(m0, m2)), "w 100lp, wrap");
529                         
530                         
531                         ////  Capped
532                         bm = new BooleanModel(component, "ForeShoulderCapped");
533                         check = new JCheckBox(bm);
534                         //// End capped
535                         check.setText(trans.get("RocketCompCfg.checkbox.Endcapped"));
536                         //// Whether the end of the shoulder is capped.
537                         check.setToolTipText(trans.get("RocketCompCfg.ttip.Endcapped"));
538                         sub.add(check, "spanx");
539                         
540                         
541                         panel.add(sub);
542                 }
543                 
544                 
545                 ////  Aft shoulder
546                 sub = new JPanel(new MigLayout("gap rel unrel", "[][65lp::][30lp::]", ""));
547                 
548                 if (component instanceof NoseCone)
549                         //// Nose cone shoulder
550                         sub.setBorder(BorderFactory.createTitledBorder(trans.get("RocketCompCfg.title.Noseconeshoulder")));
551                 else
552                         //// Aft shoulder
553                         sub.setBorder(BorderFactory.createTitledBorder(trans.get("RocketCompCfg.title.Aftshoulder")));
554                 
555                 
556                 ////  Radius
557                 //// Diameter:
558                 sub.add(new JLabel(trans.get("RocketCompCfg.lbl.Diameter")));
559                 
560                 m = new DoubleModel(component, "AftShoulderRadius", 2, UnitGroup.UNITS_LENGTH, 0);
561                 m2 = new DoubleModel(component, "AftRadius", 2, UnitGroup.UNITS_LENGTH);
562                 
563                 spin = new JSpinner(m.getSpinnerModel());
564                 spin.setEditor(new SpinnerEditor(spin));
565                 sub.add(spin, "growx");
566                 
567                 sub.add(new UnitSelector(m), "growx");
568                 sub.add(new BasicSlider(m.getSliderModel(m0, m2)), "w 100lp, wrap");
569                 
570                 
571                 ////  Length:
572                 sub.add(new JLabel(trans.get("RocketCompCfg.lbl.Length")));
573                 
574                 m = new DoubleModel(component, "AftShoulderLength", UnitGroup.UNITS_LENGTH, 0);
575                 
576                 spin = new JSpinner(m.getSpinnerModel());
577                 spin.setEditor(new SpinnerEditor(spin));
578                 sub.add(spin, "growx");
579                 
580                 sub.add(new UnitSelector(m), "growx");
581                 sub.add(new BasicSlider(m.getSliderModel(0, 0.02, 0.2)), "w 100lp, wrap");
582                 
583                 
584                 ////  Thickness:
585                 sub.add(new JLabel(trans.get("RocketCompCfg.lbl.Thickness")));
586                 
587                 m = new DoubleModel(component, "AftShoulderThickness", UnitGroup.UNITS_LENGTH, 0);
588                 m2 = new DoubleModel(component, "AftShoulderRadius", UnitGroup.UNITS_LENGTH);
589                 
590                 spin = new JSpinner(m.getSpinnerModel());
591                 spin.setEditor(new SpinnerEditor(spin));
592                 sub.add(spin, "growx");
593                 
594                 sub.add(new UnitSelector(m), "growx");
595                 sub.add(new BasicSlider(m.getSliderModel(m0, m2)), "w 100lp, wrap");
596                 
597                 
598                 ////  Capped
599                 bm = new BooleanModel(component, "AftShoulderCapped");
600                 check = new JCheckBox(bm);
601                 //// End capped
602                 check.setText(trans.get("RocketCompCfg.checkbox.Endcapped"));
603                 //// Whether the end of the shoulder is capped.
604                 check.setToolTipText(trans.get("RocketCompCfg.ttip.Endcapped"));
605                 sub.add(check, "spanx");
606                 
607                 
608                 panel.add(sub);
609                 
610                 
611                 return panel;
612         }
613         
614         
615         
616         
617         /*
618          * Private inner class to handle events in componentNameField.
619          */
620         private class TextFieldListener implements ActionListener, FocusListener {
621                 @Override
622                 public void actionPerformed(ActionEvent e) {
623                         setName();
624                 }
625                 
626                 @Override
627                 public void focusGained(FocusEvent e) {
628                 }
629                 
630                 @Override
631                 public void focusLost(FocusEvent e) {
632                         setName();
633                 }
634                 
635                 private void setName() {
636                         if (!component.getName().equals(componentNameField.getText())) {
637                                 component.setName(componentNameField.getText());
638                         }
639                         if (!component.getComment().equals(commentTextArea.getText())) {
640                                 component.setComment(commentTextArea.getText());
641                         }
642                 }
643         }
644         
645         
646         protected void register(Invalidatable model) {
647                 this.invalidatables.add(model);
648         }
649         
650         public void invalidateModels() {
651                 for (Invalidatable i : invalidatables) {
652                         i.invalidate();
653                 }
654                 ((ComponentPresetDatabase)Application.getComponentPresetDao()).removeChangeListener(presetModel);
655
656         }
657         
658 }