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