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