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