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