1 package net.sf.openrocket.gui.configdialog;
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;
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;
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.l10n.Translator;
40 import net.sf.openrocket.material.Material;
41 import net.sf.openrocket.rocketcomponent.ComponentAssembly;
42 import net.sf.openrocket.rocketcomponent.ExternalComponent;
43 import net.sf.openrocket.rocketcomponent.ExternalComponent.Finish;
44 import net.sf.openrocket.rocketcomponent.NoseCone;
45 import net.sf.openrocket.rocketcomponent.Rocket;
46 import net.sf.openrocket.rocketcomponent.RocketComponent;
47 import net.sf.openrocket.startup.Application;
48 import net.sf.openrocket.unit.UnitGroup;
49 import net.sf.openrocket.util.GUIUtil;
50 import net.sf.openrocket.util.Invalidatable;
51 import net.sf.openrocket.util.LineStyle;
52 import net.sf.openrocket.util.Prefs;
54 public class RocketComponentConfig extends JPanel {
56 private static final Translator trans = Application.getTranslator();
58 protected final RocketComponent component;
59 protected final JTabbedPane tabbedPane;
61 private final List<Invalidatable> invalidatables = new ArrayList<Invalidatable>();
64 protected final JTextField componentNameField;
65 protected JTextArea commentTextArea;
66 private final TextFieldListener textFieldListener;
67 private JButton colorButton;
68 private JCheckBox colorDefault;
69 private JPanel buttonPanel;
71 private JLabel massLabel;
74 public RocketComponentConfig(RocketComponent component) {
75 setLayout(new MigLayout("fill", "[grow, fill]"));
76 this.component = component;
79 JLabel label = new JLabel(trans.get("RocketCompCfg.lbl.Componentname"));
80 //// The component name.
81 label.setToolTipText(trans.get("RocketCompCfg.ttip.Thecomponentname"));
82 this.add(label, "split, gapright 10");
84 componentNameField = new JTextField(15);
85 textFieldListener = new TextFieldListener();
86 componentNameField.addActionListener(textFieldListener);
87 componentNameField.addFocusListener(textFieldListener);
88 //// The component name.
89 componentNameField.setToolTipText(trans.get("RocketCompCfg.ttip.Thecomponentname"));
90 this.add(componentNameField, "growx, growy 0, wrap");
93 tabbedPane = new JTabbedPane();
94 this.add(tabbedPane, "growx, growy 1, wrap");
96 //// Override and Mass and CG override options
97 tabbedPane.addTab(trans.get("RocketCompCfg.tab.Override"), null, overrideTab(),
98 trans.get("RocketCompCfg.tab.MassandCGoverride"));
99 if (component.isMassive())
100 //// Figure and Figure style options
101 tabbedPane.addTab(trans.get("RocketCompCfg.tab.Figure"), null, figureTab(),
102 trans.get("RocketCompCfg.tab.Figstyleopt"));
103 //// Comment and Specify a comment for the component
104 tabbedPane.addTab(trans.get("RocketCompCfg.tab.Comment"), null, commentTab(),
105 trans.get("RocketCompCfg.tab.Specifyacomment"));
113 protected void addButtons(JButton... buttons) {
114 if (buttonPanel != null) {
115 this.remove(buttonPanel);
118 buttonPanel = new JPanel(new MigLayout("fill, ins 0"));
121 massLabel = new StyledLabel(trans.get("RocketCompCfg.lbl.Mass") + " ", -1);
122 buttonPanel.add(massLabel, "growx");
124 for (JButton b : buttons) {
125 buttonPanel.add(b, "right, gap para");
129 JButton closeButton = new JButton(trans.get("dlg.but.close"));
130 closeButton.addActionListener(new ActionListener() {
132 public void actionPerformed(ActionEvent arg0) {
133 ComponentConfigDialog.hideDialog();
136 buttonPanel.add(closeButton, "right, gap 30lp");
140 this.add(buttonPanel, "spanx, growx");
145 * Called when a change occurs, so that the fields can be updated if necessary.
146 * When overriding this method, the supermethod must always be called.
148 public void updateFields() {
150 componentNameField.setText(component.getName());
152 // Component color and "Use default color" checkbox
153 if (colorButton != null && colorDefault != null) {
154 colorButton.setIcon(new ColorIcon(component.getColor()));
156 if ((component.getColor() == null) != colorDefault.isSelected())
157 colorDefault.setSelected(component.getColor() == null);
161 if (component.isMassive()) {
163 String text = trans.get("RocketCompCfg.lbl.Componentmass") + " ";
164 text += UnitGroup.UNITS_MASS.getDefaultUnit().toStringUnit(
165 component.getComponentMass());
167 String overridetext = null;
168 if (component.isMassOverridden()) {
170 overridetext = trans.get("RocketCompCfg.lbl.overriddento")+" " + UnitGroup.UNITS_MASS.getDefaultUnit().
171 toStringUnit(component.getOverrideMass()) + ")";
174 for (RocketComponent c = component.getParent(); c != null; c = c.getParent()) {
175 if (c.isMassOverridden() && c.getOverrideSubcomponents()) {
177 overridetext = trans.get("RocketCompCfg.lbl.overriddenby")+" " + c.getName() + ")";
181 if (overridetext != null)
182 text = text + " " + overridetext;
184 massLabel.setText(text);
186 massLabel.setText("");
191 protected JPanel materialPanel(JPanel panel, Material.Type type) {
192 ////Component material: and Component finish:
193 return materialPanel(panel, type, trans.get("RocketCompCfg.lbl.Componentmaterial"),
194 trans.get("RocketCompCfg.lbl.Componentfinish"));
197 protected JPanel materialPanel(JPanel panel, Material.Type type,
198 String materialString, String finishString) {
199 JLabel label = new JLabel(materialString);
200 //// The component material affects the weight of the component.
201 label.setToolTipText(trans.get("RocketCompCfg.lbl.ttip.componentmaterialaffects"));
202 panel.add(label, "spanx 4, wrap rel");
204 JComboBox combo = new JComboBox(new MaterialModel(panel, component, type));
205 //// The component material affects the weight of the component.
206 combo.setToolTipText(trans.get("RocketCompCfg.combo.ttip.componentmaterialaffects"));
207 panel.add(combo, "spanx 4, growx, wrap paragraph");
210 if (component instanceof ExternalComponent) {
211 label = new JLabel(finishString);
212 ////<html>The component finish affects the aerodynamic drag of the component.<br>
213 String tip = trans.get("RocketCompCfg.lbl.longA1")
214 //// The value indicated is the average roughness height of the surface.
215 + trans.get("RocketCompCfg.lbl.longA2");
216 label.setToolTipText(tip);
217 panel.add(label, "spanx 4, wmin 220lp, wrap rel");
219 combo = new JComboBox(new EnumModel<ExternalComponent.Finish>(component, "Finish"));
220 combo.setToolTipText(tip);
221 panel.add(combo, "spanx 4, growx, split");
224 JButton button = new JButton(trans.get("RocketCompCfg.but.Setforall"));
225 //// Set this finish for all components of the rocket.
226 button.setToolTipText(trans.get("RocketCompCfg.but.ttip.Setforall"));
227 button.addActionListener(new ActionListener() {
229 public void actionPerformed(ActionEvent e) {
230 Finish f = ((ExternalComponent) component).getFinish();
231 Rocket rocket = component.getRocket();
234 // Store previous undo description
235 String desc = ComponentConfigDialog.getUndoDescription();
236 ComponentConfigDialog.addUndoPosition("Set rocket finish");
238 Iterator<RocketComponent> iter = rocket.iterator();
239 while (iter.hasNext()) {
240 RocketComponent c = iter.next();
241 if (c instanceof ExternalComponent) {
242 ((ExternalComponent) c).setFinish(f);
245 // Restore undo description
246 ComponentConfigDialog.addUndoPosition(desc);
252 panel.add(button, "wrap paragraph");
259 private JPanel overrideTab() {
260 JPanel panel = new JPanel(new MigLayout("align 50% 20%, fillx, gap rel unrel",
261 "[][65lp::][30lp::][]", ""));
262 //// Override the mass or center of gravity of the
263 panel.add(new StyledLabel(trans.get("RocketCompCfg.lbl.Overridemassorcenter") + " " +
264 component.getComponentName() + ":", Style.BOLD), "spanx, wrap 20lp");
272 bm = new BooleanModel(component, "MassOverridden");
273 check = new JCheckBox(bm);
275 check.setText(trans.get("RocketCompCfg.checkbox.Overridemass"));
276 panel.add(check, "growx 1, gapright 20lp");
278 DoubleModel m = new DoubleModel(component, "OverrideMass", UnitGroup.UNITS_MASS, 0);
280 JSpinner spin = new JSpinner(m.getSpinnerModel());
281 spin.setEditor(new SpinnerEditor(spin));
282 bm.addEnableComponent(spin, true);
283 panel.add(spin, "growx 1");
285 us = new UnitSelector(m);
286 bm.addEnableComponent(us, true);
287 panel.add(us, "growx 1");
289 bs = new BasicSlider(m.getSliderModel(0, 0.03, 1.0));
290 bm.addEnableComponent(bs);
291 panel.add(bs, "growx 5, w 100lp, wrap");
295 bm = new BooleanModel(component, "CGOverridden");
296 check = new JCheckBox(bm);
297 //// Override center of gravity:"
298 check.setText(trans.get("RocketCompCfg.checkbox.Overridecenterofgrav"));
299 panel.add(check, "growx 1, gapright 20lp");
301 m = new DoubleModel(component, "OverrideCGX", UnitGroup.UNITS_LENGTH, 0);
302 // Calculate suitable length for slider
304 if (component instanceof ComponentAssembly) {
307 Iterator<RocketComponent> iterator = component.iterator(false);
308 while (iterator.hasNext()) {
309 RocketComponent c = iterator.next();
310 if (c.getRelativePosition() == RocketComponent.Position.AFTER)
313 length = new DoubleModel(l);
315 length = new DoubleModel(component, "Length", UnitGroup.UNITS_LENGTH, 0);
318 spin = new JSpinner(m.getSpinnerModel());
319 spin.setEditor(new SpinnerEditor(spin));
320 bm.addEnableComponent(spin, true);
321 panel.add(spin, "growx 1");
323 us = new UnitSelector(m);
324 bm.addEnableComponent(us, true);
325 panel.add(us, "growx 1");
327 bs = new BasicSlider(m.getSliderModel(new DoubleModel(0), length));
328 bm.addEnableComponent(bs);
329 panel.add(bs, "growx 5, w 100lp, wrap 35lp");
332 // Override subcomponents checkbox
333 bm = new BooleanModel(component, "OverrideSubcomponents");
334 check = new JCheckBox(bm);
335 //// Override mass and CG of all subcomponents
336 check.setText(trans.get("RocketCompCfg.checkbox.OverridemassandCG"));
337 panel.add(check, "gap para, spanx, wrap para");
339 //// <html>The overridden mass does not include motors.<br>
340 panel.add(new StyledLabel(trans.get("RocketCompCfg.lbl.longB1") +
341 //// The center of gravity is measured from the front end of the
342 trans.get("RocketCompCfg.lbl.longB2") +" " +
343 component.getComponentName().toLowerCase() + ".", -1),
344 "spanx, wrap, gap para, height 0::30lp");
350 private JPanel commentTab() {
351 JPanel panel = new JPanel(new MigLayout("fill"));
354 panel.add(new StyledLabel(trans.get("RocketCompCfg.lbl.Commentsonthe") +" " + component.getComponentName() + ":",
355 Style.BOLD), "wrap");
357 // TODO: LOW: Changes in comment from other sources not reflected in component
358 commentTextArea = new JTextArea(component.getComment());
359 commentTextArea.setLineWrap(true);
360 commentTextArea.setWrapStyleWord(true);
361 commentTextArea.setEditable(true);
362 GUIUtil.setTabToFocusing(commentTextArea);
363 commentTextArea.addFocusListener(textFieldListener);
365 panel.add(new JScrollPane(commentTextArea), "width 10px, height 10px, growx, growy");
372 private JPanel figureTab() {
373 JPanel panel = new JPanel(new MigLayout("align 20% 20%"));
376 panel.add(new StyledLabel(trans.get("RocketCompCfg.lbl.Figurestyle"), Style.BOLD), "wrap para");
378 //// Component color:
379 panel.add(new JLabel(trans.get("RocketCompCfg.lbl.Componentcolor")), "gapleft para, gapright 10lp");
381 colorButton = new JButton(new ColorIcon(component.getColor()));
382 colorButton.addActionListener(new ActionListener() {
384 public void actionPerformed(ActionEvent e) {
385 Color c = component.getColor();
387 c = Prefs.getDefaultColor(component.getClass());
391 c = JColorChooser.showDialog(tabbedPane, trans.get("RocketCompCfg.lbl.Choosecolor"), c);
393 component.setColor(c);
397 panel.add(colorButton, "gapright 10lp");
399 //// Use default color
400 colorDefault = new JCheckBox(trans.get("RocketCompCfg.checkbox.Usedefaultcolor"));
401 if (component.getColor() == null)
402 colorDefault.setSelected(true);
403 colorDefault.addActionListener(new ActionListener() {
405 public void actionPerformed(ActionEvent e) {
406 if (colorDefault.isSelected())
407 component.setColor(null);
409 component.setColor(Prefs.getDefaultColor(component.getClass()));
412 panel.add(colorDefault, "wrap para");
414 //// Component line style:
415 panel.add(new JLabel(trans.get("RocketCompCfg.lbl.Complinestyle")), "gapleft para, gapright 10lp");
417 LineStyle[] list = new LineStyle[LineStyle.values().length + 1];
418 System.arraycopy(LineStyle.values(), 0, list, 1, LineStyle.values().length);
420 JComboBox combo = new JComboBox(new EnumModel<LineStyle>(component, "LineStyle",
421 list, "Default style"));
422 panel.add(combo, "spanx 2, growx, wrap 50lp");
424 //// Save as default style
425 JButton button = new JButton(trans.get("RocketCompCfg.but.Saveasdefstyle"));
426 button.addActionListener(new ActionListener() {
428 public void actionPerformed(ActionEvent e) {
429 if (component.getColor() != null) {
430 Prefs.setDefaultColor(component.getClass(), component.getColor());
431 component.setColor(null);
433 if (component.getLineStyle() != null) {
434 Prefs.setDefaultLineStyle(component.getClass(), component.getLineStyle());
435 component.setLineStyle(null);
439 panel.add(button, "gapleft para, spanx 3, growx, wrap");
447 protected JPanel shoulderTab() {
448 JPanel panel = new JPanel(new MigLayout("fill"));
451 DoubleModel m0 = new DoubleModel(0);
457 //// Fore shoulder, not for NoseCone
459 if (!(component instanceof NoseCone)) {
460 sub = new JPanel(new MigLayout("gap rel unrel", "[][65lp::][30lp::]", ""));
463 sub.setBorder(BorderFactory.createTitledBorder(trans.get("RocketCompCfg.border.Foreshoulder")));
468 sub.add(new JLabel(trans.get("RocketCompCfg.lbl.Diameter")));
470 m = new DoubleModel(component, "ForeShoulderRadius", 2, UnitGroup.UNITS_LENGTH, 0);
471 m2 = new DoubleModel(component, "ForeRadius", 2, UnitGroup.UNITS_LENGTH);
473 spin = new JSpinner(m.getSpinnerModel());
474 spin.setEditor(new SpinnerEditor(spin));
475 sub.add(spin, "growx");
477 sub.add(new UnitSelector(m), "growx");
478 sub.add(new BasicSlider(m.getSliderModel(m0, m2)), "w 100lp, wrap");
482 sub.add(new JLabel(trans.get("RocketCompCfg.lbl.Length")));
484 m = new DoubleModel(component, "ForeShoulderLength", UnitGroup.UNITS_LENGTH, 0);
486 spin = new JSpinner(m.getSpinnerModel());
487 spin.setEditor(new SpinnerEditor(spin));
488 sub.add(spin, "growx");
490 sub.add(new UnitSelector(m), "growx");
491 sub.add(new BasicSlider(m.getSliderModel(0, 0.02, 0.2)), "w 100lp, wrap");
495 sub.add(new JLabel(trans.get("RocketCompCfg.lbl.Thickness")));
497 m = new DoubleModel(component, "ForeShoulderThickness", UnitGroup.UNITS_LENGTH, 0);
498 m2 = new DoubleModel(component, "ForeShoulderRadius", UnitGroup.UNITS_LENGTH);
500 spin = new JSpinner(m.getSpinnerModel());
501 spin.setEditor(new SpinnerEditor(spin));
502 sub.add(spin, "growx");
504 sub.add(new UnitSelector(m), "growx");
505 sub.add(new BasicSlider(m.getSliderModel(m0, m2)), "w 100lp, wrap");
509 bm = new BooleanModel(component, "ForeShoulderCapped");
510 check = new JCheckBox(bm);
512 check.setText(trans.get("RocketCompCfg.checkbox.Endcapped"));
513 //// Whether the end of the shoulder is capped.
514 check.setToolTipText(trans.get("RocketCompCfg.ttip.Endcapped"));
515 sub.add(check, "spanx");
523 sub = new JPanel(new MigLayout("gap rel unrel", "[][65lp::][30lp::]", ""));
525 if (component instanceof NoseCone)
526 //// Nose cone shoulder
527 sub.setBorder(BorderFactory.createTitledBorder(trans.get("RocketCompCfg.title.Noseconeshoulder")));
530 sub.setBorder(BorderFactory.createTitledBorder(trans.get("RocketCompCfg.title.Aftshoulder")));
535 sub.add(new JLabel(trans.get("RocketCompCfg.lbl.Diameter")));
537 m = new DoubleModel(component, "AftShoulderRadius", 2, UnitGroup.UNITS_LENGTH, 0);
538 m2 = new DoubleModel(component, "AftRadius", 2, UnitGroup.UNITS_LENGTH);
540 spin = new JSpinner(m.getSpinnerModel());
541 spin.setEditor(new SpinnerEditor(spin));
542 sub.add(spin, "growx");
544 sub.add(new UnitSelector(m), "growx");
545 sub.add(new BasicSlider(m.getSliderModel(m0, m2)), "w 100lp, wrap");
549 sub.add(new JLabel(trans.get("RocketCompCfg.lbl.Length")));
551 m = new DoubleModel(component, "AftShoulderLength", UnitGroup.UNITS_LENGTH, 0);
553 spin = new JSpinner(m.getSpinnerModel());
554 spin.setEditor(new SpinnerEditor(spin));
555 sub.add(spin, "growx");
557 sub.add(new UnitSelector(m), "growx");
558 sub.add(new BasicSlider(m.getSliderModel(0, 0.02, 0.2)), "w 100lp, wrap");
562 sub.add(new JLabel(trans.get("RocketCompCfg.lbl.Thickness")));
564 m = new DoubleModel(component, "AftShoulderThickness", UnitGroup.UNITS_LENGTH, 0);
565 m2 = new DoubleModel(component, "AftShoulderRadius", UnitGroup.UNITS_LENGTH);
567 spin = new JSpinner(m.getSpinnerModel());
568 spin.setEditor(new SpinnerEditor(spin));
569 sub.add(spin, "growx");
571 sub.add(new UnitSelector(m), "growx");
572 sub.add(new BasicSlider(m.getSliderModel(m0, m2)), "w 100lp, wrap");
576 bm = new BooleanModel(component, "AftShoulderCapped");
577 check = new JCheckBox(bm);
579 check.setText(trans.get("RocketCompCfg.checkbox.Endcapped"));
580 //// Whether the end of the shoulder is capped.
581 check.setToolTipText(trans.get("RocketCompCfg.ttip.Endcapped"));
582 sub.add(check, "spanx");
595 * Private inner class to handle events in componentNameField.
597 private class TextFieldListener implements ActionListener, FocusListener {
599 public void actionPerformed(ActionEvent e) {
604 public void focusGained(FocusEvent e) {
608 public void focusLost(FocusEvent e) {
612 private void setName() {
613 if (!component.getName().equals(componentNameField.getText())) {
614 component.setName(componentNameField.getText());
616 if (!component.getComment().equals(commentTextArea.getText())) {
617 component.setComment(commentTextArea.getText());
623 private class ColorIcon implements Icon {
624 private final Color color;
626 public ColorIcon(Color c) {
631 public int getIconHeight() {
636 public int getIconWidth() {
641 public void paintIcon(Component c, Graphics g, int x, int y) {
643 g.setColor(Prefs.getDefaultColor(component.getClass()));
647 g.fill3DRect(x, y, getIconWidth(), getIconHeight(), false);
652 protected void register(Invalidatable model) {
653 this.invalidatables.add(model);
656 public void invalidateModels() {
657 for (Invalidatable i : invalidatables) {