package net.sf.openrocket.gui.configdialog; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.FocusEvent; import java.awt.event.FocusListener; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.Locale; import javax.swing.BorderFactory; import javax.swing.JButton; import javax.swing.JCheckBox; import javax.swing.JComboBox; import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.JSpinner; import javax.swing.JTabbedPane; import javax.swing.JTextArea; import javax.swing.JTextField; import net.miginfocom.swing.MigLayout; import net.sf.openrocket.database.ComponentPresetDatabase; import net.sf.openrocket.document.OpenRocketDocument; import net.sf.openrocket.gui.SpinnerEditor; import net.sf.openrocket.gui.adaptors.BooleanModel; import net.sf.openrocket.gui.adaptors.DoubleModel; import net.sf.openrocket.gui.adaptors.EnumModel; import net.sf.openrocket.gui.adaptors.MaterialModel; import net.sf.openrocket.gui.adaptors.PresetModel; import net.sf.openrocket.gui.components.BasicSlider; import net.sf.openrocket.gui.components.StyledLabel; import net.sf.openrocket.gui.components.StyledLabel.Style; import net.sf.openrocket.gui.components.UnitSelector; import net.sf.openrocket.gui.util.GUIUtil; import net.sf.openrocket.l10n.Translator; import net.sf.openrocket.material.Material; import net.sf.openrocket.preset.ComponentPreset; import net.sf.openrocket.rocketcomponent.ComponentAssembly; import net.sf.openrocket.rocketcomponent.ExternalComponent; import net.sf.openrocket.rocketcomponent.ExternalComponent.Finish; import net.sf.openrocket.rocketcomponent.NoseCone; import net.sf.openrocket.rocketcomponent.RocketComponent; import net.sf.openrocket.startup.Application; import net.sf.openrocket.unit.UnitGroup; import net.sf.openrocket.util.Invalidatable; public class RocketComponentConfig extends JPanel { private static final Translator trans = Application.getTranslator(); protected final OpenRocketDocument document; protected final RocketComponent component; protected final JTabbedPane tabbedPane; private final List<Invalidatable> invalidatables = new ArrayList<Invalidatable>(); private JComboBox presetComboBox; private PresetModel presetModel; protected final JTextField componentNameField; protected JTextArea commentTextArea; private final TextFieldListener textFieldListener; private JPanel buttonPanel; private JLabel infoLabel; public RocketComponentConfig(OpenRocketDocument document, RocketComponent component) { setLayout(new MigLayout("fill", "[min,align right]:10[fill, grow]")); this.document = document; this.component = component; //// Component name: JLabel label = new JLabel(trans.get("RocketCompCfg.lbl.Componentname")); //// The component name. label.setToolTipText(trans.get("RocketCompCfg.ttip.Thecomponentname")); this.add(label, "spanx, split"); componentNameField = new JTextField(15); textFieldListener = new TextFieldListener(); componentNameField.addActionListener(textFieldListener); componentNameField.addFocusListener(textFieldListener); //// The component name. componentNameField.setToolTipText(trans.get("RocketCompCfg.ttip.Thecomponentname")); this.add(componentNameField, "growx"); if (component.getPresetType() != null) { // If the component supports a preset, show the preset selection box. presetModel = new PresetModel(this, document, component); ((ComponentPresetDatabase) Application.getComponentPresetDao()).addDatabaseListener(presetModel); presetComboBox = new JComboBox(presetModel); presetComboBox.setEditable(false); this.add(presetComboBox, ""); } tabbedPane = new JTabbedPane(); this.add(tabbedPane, "newline, span, growx, growy 1, wrap"); //// Override and Mass and CG override options tabbedPane.addTab(trans.get("RocketCompCfg.tab.Override"), null, overrideTab(), trans.get("RocketCompCfg.tab.MassandCGoverride")); if (component.isMassive()) //// Appearance options tabbedPane.addTab(trans.get("RocketCompCfg.tab.Appearance"), null, new AppearancePanel(document, component), "Appearance Tool Tip"); //// Comment and Specify a comment for the component tabbedPane.addTab(trans.get("RocketCompCfg.tab.Comment"), null, commentTab(), trans.get("RocketCompCfg.tab.Specifyacomment")); addButtons(); updateFields(); } protected void addButtons(JButton... buttons) { if (buttonPanel != null) { this.remove(buttonPanel); } buttonPanel = new JPanel(new MigLayout("fill, ins 0")); //// Mass: infoLabel = new StyledLabel(" ", -1); buttonPanel.add(infoLabel, "growx"); for (JButton b : buttons) { buttonPanel.add(b, "right, gap para"); } //// Close button JButton closeButton = new JButton(trans.get("dlg.but.close")); closeButton.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent arg0) { ComponentConfigDialog.hideDialog(); } }); buttonPanel.add(closeButton, "right, gap 30lp"); updateFields(); this.add(buttonPanel, "spanx, growx"); } /** * Called when a change occurs, so that the fields can be updated if necessary. * When overriding this method, the supermethod must always be called. */ public void updateFields() { // Component name componentNameField.setText(component.getName()); // Info label StringBuilder sb = new StringBuilder(); if (component.getPresetComponent() != null) { ComponentPreset preset = component.getPresetComponent(); sb.append(preset.getManufacturer() + " " + preset.getPartNo() + " "); } if (component.isMassive()) { sb.append(trans.get("RocketCompCfg.lbl.Componentmass") + " "); sb.append(UnitGroup.UNITS_MASS.getDefaultUnit().toStringUnit( component.getComponentMass())); String overridetext = null; if (component.isMassOverridden()) { overridetext = trans.get("RocketCompCfg.lbl.overriddento") + " " + UnitGroup.UNITS_MASS.getDefaultUnit(). toStringUnit(component.getOverrideMass()) + ")"; } for (RocketComponent c = component.getParent(); c != null; c = c.getParent()) { if (c.isMassOverridden() && c.getOverrideSubcomponents()) { overridetext = trans.get("RocketCompCfg.lbl.overriddenby") + " " + c.getName() + ")"; } } if (overridetext != null) { sb.append(" " + overridetext); } infoLabel.setText(sb.toString()); } else { infoLabel.setText(""); } } protected JPanel materialPanel(Material.Type type) { ////Component material: and Component finish: return materialPanel(type, trans.get("RocketCompCfg.lbl.Componentmaterial"), trans.get("RocketCompCfg.lbl.Componentfinish"), "Material"); } protected JPanel materialPanel(Material.Type type, String partName){ return materialPanel(type, trans.get("RocketCompCfg.lbl.Componentmaterial"), trans.get("RocketCompCfg.lbl.Componentfinish"), partName); } protected JPanel materialPanel(Material.Type type, String materialString, String finishString, String partName) { JPanel subPanel = new JPanel(new MigLayout()); JLabel label = new JLabel(materialString); //// The component material affects the weight of the component. label.setToolTipText(trans.get("RocketCompCfg.lbl.ttip.componentmaterialaffects")); subPanel.add(label, "spanx 4, wrap rel"); JComboBox combo = new JComboBox(new MaterialModel(subPanel, component, type, partName)); //// The component material affects the weight of the component. combo.setToolTipText(trans.get("RocketCompCfg.combo.ttip.componentmaterialaffects")); subPanel.add(combo, "spanx 4, growx, wrap paragraph"); if (component instanceof ExternalComponent) { label = new JLabel(finishString); ////<html>The component finish affects the aerodynamic drag of the component.<br> String tip = trans.get("RocketCompCfg.lbl.longA1") //// The value indicated is the average roughness height of the surface. + trans.get("RocketCompCfg.lbl.longA2"); label.setToolTipText(tip); subPanel.add(label, "spanx 4, wmin 220lp, wrap rel"); combo = new JComboBox(new EnumModel<ExternalComponent.Finish>(component, "Finish")); combo.setToolTipText(tip); subPanel.add(combo, "spanx 4, growx, split"); //// Set for all JButton button = new JButton(trans.get("RocketCompCfg.but.Setforall")); //// Set this finish for all components of the rocket. button.setToolTipText(trans.get("RocketCompCfg.but.ttip.Setforall")); button.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { Finish f = ((ExternalComponent) component).getFinish(); try { document.startUndo("Set rocket finish"); // Do changes Iterator<RocketComponent> iter = component.getRoot().iterator(); while (iter.hasNext()) { RocketComponent c = iter.next(); if (c instanceof ExternalComponent) { ((ExternalComponent) c).setFinish(f); } } } finally { document.stopUndo(); } } }); subPanel.add(button, "wrap paragraph"); } return subPanel; } private JPanel overrideTab() { JPanel panel = new JPanel(new MigLayout("align 50% 20%, fillx, gap rel unrel", "[][65lp::][30lp::][]", "")); //// Override the mass or center of gravity of the panel.add(new StyledLabel(trans.get("RocketCompCfg.lbl.Overridemassorcenter") + " " + component.getComponentName() + ":", Style.BOLD), "spanx, wrap 20lp"); JCheckBox check; BooleanModel bm; UnitSelector us; BasicSlider bs; //// Mass bm = new BooleanModel(component, "MassOverridden"); check = new JCheckBox(bm); //// Override mass: check.setText(trans.get("RocketCompCfg.checkbox.Overridemass")); panel.add(check, "growx 1, gapright 20lp"); DoubleModel m = new DoubleModel(component, "OverrideMass", UnitGroup.UNITS_MASS, 0); JSpinner spin = new JSpinner(m.getSpinnerModel()); spin.setEditor(new SpinnerEditor(spin)); bm.addEnableComponent(spin, true); panel.add(spin, "growx 1"); us = new UnitSelector(m); bm.addEnableComponent(us, true); panel.add(us, "growx 1"); bs = new BasicSlider(m.getSliderModel(0, 0.03, 1.0)); bm.addEnableComponent(bs); panel.add(bs, "growx 5, w 100lp, wrap"); //// CG override bm = new BooleanModel(component, "CGOverridden"); check = new JCheckBox(bm); //// Override center of gravity:" check.setText(trans.get("RocketCompCfg.checkbox.Overridecenterofgrav")); panel.add(check, "growx 1, gapright 20lp"); m = new DoubleModel(component, "OverrideCGX", UnitGroup.UNITS_LENGTH, 0); // Calculate suitable length for slider DoubleModel length; if (component instanceof ComponentAssembly) { double l = 0; Iterator<RocketComponent> iterator = component.iterator(false); while (iterator.hasNext()) { RocketComponent c = iterator.next(); if (c.getRelativePosition() == RocketComponent.Position.AFTER) l += c.getLength(); } length = new DoubleModel(l); } else { length = new DoubleModel(component, "Length", UnitGroup.UNITS_LENGTH, 0); } spin = new JSpinner(m.getSpinnerModel()); spin.setEditor(new SpinnerEditor(spin)); bm.addEnableComponent(spin, true); panel.add(spin, "growx 1"); us = new UnitSelector(m); bm.addEnableComponent(us, true); panel.add(us, "growx 1"); bs = new BasicSlider(m.getSliderModel(new DoubleModel(0), length)); bm.addEnableComponent(bs); panel.add(bs, "growx 5, w 100lp, wrap 35lp"); // Override subcomponents checkbox bm = new BooleanModel(component, "OverrideSubcomponents"); check = new JCheckBox(bm); //// Override mass and CG of all subcomponents check.setText(trans.get("RocketCompCfg.checkbox.OverridemassandCG")); panel.add(check, "gap para, spanx, wrap para"); //// <html>The overridden mass does not include motors.<br> panel.add(new StyledLabel(trans.get("RocketCompCfg.lbl.longB1") + //// The center of gravity is measured from the front end of the trans.get("RocketCompCfg.lbl.longB2") + " " + component.getComponentName().toLowerCase(Locale.getDefault()) + ".", -1), "spanx, wrap, gap para, height 0::30lp"); return panel; } private JPanel commentTab() { JPanel panel = new JPanel(new MigLayout("fill","[]","[][grow]")); //// Comments on the panel.add(new StyledLabel(trans.get("RocketCompCfg.lbl.Commentsonthe") + " " + component.getComponentName() + ":", Style.BOLD), "wrap"); // TODO: LOW: Changes in comment from other sources not reflected in component commentTextArea = new JTextArea(component.getComment()); commentTextArea.setLineWrap(true); commentTextArea.setWrapStyleWord(true); commentTextArea.setEditable(true); GUIUtil.setTabToFocusing(commentTextArea); commentTextArea.addFocusListener(textFieldListener); panel.add(new JScrollPane(commentTextArea), "grow"); return panel; } protected JPanel shoulderTab() { JPanel panel = new JPanel(new MigLayout("fill")); JPanel sub; DoubleModel m, m2; DoubleModel m0 = new DoubleModel(0); BooleanModel bm; JCheckBox check; JSpinner spin; //// Fore shoulder, not for NoseCone if (!(component instanceof NoseCone)) { sub = new JPanel(new MigLayout("gap rel unrel", "[][65lp::][30lp::]", "")); //// Fore shoulder sub.setBorder(BorderFactory.createTitledBorder(trans.get("RocketCompCfg.border.Foreshoulder"))); //// Radius //// Diameter: sub.add(new JLabel(trans.get("RocketCompCfg.lbl.Diameter"))); m = new DoubleModel(component, "ForeShoulderRadius", 2, UnitGroup.UNITS_LENGTH, 0); m2 = new DoubleModel(component, "ForeRadius", 2, UnitGroup.UNITS_LENGTH); spin = new JSpinner(m.getSpinnerModel()); spin.setEditor(new SpinnerEditor(spin)); sub.add(spin, "growx"); sub.add(new UnitSelector(m), "growx"); sub.add(new BasicSlider(m.getSliderModel(m0, m2)), "w 100lp, wrap"); //// Length: sub.add(new JLabel(trans.get("RocketCompCfg.lbl.Length"))); m = new DoubleModel(component, "ForeShoulderLength", UnitGroup.UNITS_LENGTH, 0); spin = new JSpinner(m.getSpinnerModel()); spin.setEditor(new SpinnerEditor(spin)); sub.add(spin, "growx"); sub.add(new UnitSelector(m), "growx"); sub.add(new BasicSlider(m.getSliderModel(0, 0.02, 0.2)), "w 100lp, wrap"); //// Thickness: sub.add(new JLabel(trans.get("RocketCompCfg.lbl.Thickness"))); m = new DoubleModel(component, "ForeShoulderThickness", UnitGroup.UNITS_LENGTH, 0); m2 = new DoubleModel(component, "ForeShoulderRadius", UnitGroup.UNITS_LENGTH); spin = new JSpinner(m.getSpinnerModel()); spin.setEditor(new SpinnerEditor(spin)); sub.add(spin, "growx"); sub.add(new UnitSelector(m), "growx"); sub.add(new BasicSlider(m.getSliderModel(m0, m2)), "w 100lp, wrap"); //// Capped bm = new BooleanModel(component, "ForeShoulderCapped"); check = new JCheckBox(bm); //// End capped check.setText(trans.get("RocketCompCfg.checkbox.Endcapped")); //// Whether the end of the shoulder is capped. check.setToolTipText(trans.get("RocketCompCfg.ttip.Endcapped")); sub.add(check, "spanx"); panel.add(sub); } //// Aft shoulder sub = new JPanel(new MigLayout("gap rel unrel", "[][65lp::][30lp::]", "")); if (component instanceof NoseCone) //// Nose cone shoulder sub.setBorder(BorderFactory.createTitledBorder(trans.get("RocketCompCfg.title.Noseconeshoulder"))); else //// Aft shoulder sub.setBorder(BorderFactory.createTitledBorder(trans.get("RocketCompCfg.title.Aftshoulder"))); //// Radius //// Diameter: sub.add(new JLabel(trans.get("RocketCompCfg.lbl.Diameter"))); m = new DoubleModel(component, "AftShoulderRadius", 2, UnitGroup.UNITS_LENGTH, 0); m2 = new DoubleModel(component, "AftRadius", 2, UnitGroup.UNITS_LENGTH); spin = new JSpinner(m.getSpinnerModel()); spin.setEditor(new SpinnerEditor(spin)); sub.add(spin, "growx"); sub.add(new UnitSelector(m), "growx"); sub.add(new BasicSlider(m.getSliderModel(m0, m2)), "w 100lp, wrap"); //// Length: sub.add(new JLabel(trans.get("RocketCompCfg.lbl.Length"))); m = new DoubleModel(component, "AftShoulderLength", UnitGroup.UNITS_LENGTH, 0); spin = new JSpinner(m.getSpinnerModel()); spin.setEditor(new SpinnerEditor(spin)); sub.add(spin, "growx"); sub.add(new UnitSelector(m), "growx"); sub.add(new BasicSlider(m.getSliderModel(0, 0.02, 0.2)), "w 100lp, wrap"); //// Thickness: sub.add(new JLabel(trans.get("RocketCompCfg.lbl.Thickness"))); m = new DoubleModel(component, "AftShoulderThickness", UnitGroup.UNITS_LENGTH, 0); m2 = new DoubleModel(component, "AftShoulderRadius", UnitGroup.UNITS_LENGTH); spin = new JSpinner(m.getSpinnerModel()); spin.setEditor(new SpinnerEditor(spin)); sub.add(spin, "growx"); sub.add(new UnitSelector(m), "growx"); sub.add(new BasicSlider(m.getSliderModel(m0, m2)), "w 100lp, wrap"); //// Capped bm = new BooleanModel(component, "AftShoulderCapped"); check = new JCheckBox(bm); //// End capped check.setText(trans.get("RocketCompCfg.checkbox.Endcapped")); //// Whether the end of the shoulder is capped. check.setToolTipText(trans.get("RocketCompCfg.ttip.Endcapped")); sub.add(check, "spanx"); panel.add(sub); return panel; } /* * Private inner class to handle events in componentNameField. */ private class TextFieldListener implements ActionListener, FocusListener { @Override public void actionPerformed(ActionEvent e) { setName(); } @Override public void focusGained(FocusEvent e) { } @Override public void focusLost(FocusEvent e) { setName(); } private void setName() { if (!component.getName().equals(componentNameField.getText())) { component.setName(componentNameField.getText()); } if (!component.getComment().equals(commentTextArea.getText())) { component.setComment(commentTextArea.getText()); } } } protected void register(Invalidatable model) { this.invalidatables.add(model); } public void invalidateModels() { for (Invalidatable i : invalidatables) { i.invalidate(); } ((ComponentPresetDatabase) Application.getComponentPresetDao()).removeChangeListener(presetModel); } }