diff --git a/core/resources/l10n/messages.properties b/core/resources/l10n/messages.properties index 4d6cbbf11..0a7a07f20 100644 --- a/core/resources/l10n/messages.properties +++ b/core/resources/l10n/messages.properties @@ -1194,6 +1194,7 @@ InnerTubeCfg.tab.ttip.Radialpos = Radial position InnerTubeCfg.lbl.Selectclustercfg = Select cluster configuration: InnerTubeCfg.lbl.TubeSep = Tube separation: InnerTubeCfg.lbl.ttip.TubeSep = The separation of the tubes, 1.0 = touching each other +InnerTubeCfg.lbl.ttip.TubeSepAbs = The separation of the tubes, 0 = touching each other InnerTubeCfg.lbl.Rotation = Rotation: InnerTubeCfg.lbl.ttip.Rotation = Rotation angle of the cluster configuration InnerTubeCfg.lbl.Rotangle = Rotation angle of the cluster configuration @@ -1202,6 +1203,10 @@ InnerTubeCfg.lbl.longA1 = Split the cluster into separate components.
InnerTubeCfg.lbl.longA2 = This also duplicates all components attached to this inner tube. InnerTubeCfg.but.Resetsettings = Reset settings InnerTubeCfg.but.ttip.Resetsettings = Reset the separation and rotation to the default values +InnerTubeCfg.radioBut.Relative = Relative +InnerTubeCfg.radioBut.Relative.ttip = The separation is measured relative to the outer diameter of the inner tube +InnerTubeCfg.radioBut.Absolute = Absolute +InnerTubeCfg.radioBut.Absolute.ttip = The separation is measured in length units ! LaunchLugConfig LaunchLugCfg.lbl.Length = Length: diff --git a/core/src/net/sf/openrocket/rocketcomponent/InnerTube.java b/core/src/net/sf/openrocket/rocketcomponent/InnerTube.java index 1e17bb335..e9477baf2 100644 --- a/core/src/net/sf/openrocket/rocketcomponent/InnerTube.java +++ b/core/src/net/sf/openrocket/rocketcomponent/InnerTube.java @@ -170,6 +170,11 @@ public class InnerTube extends ThicknessRingComponent implements AxialPositionab " Please set setClusterConfiguration(ClusterConfiguration) instead.", new UnsupportedOperationException("InnerTube.setInstanceCount(..) on an"+this.getClass().getSimpleName())); } + + @Override + public boolean isAfter(){ + return false; + } /** * Get the cluster scaling. A value of 1.0 indicates that the tubes are packed @@ -177,14 +182,10 @@ public class InnerTube extends ThicknessRingComponent implements AxialPositionab * pack inside each other. */ public double getClusterScale() { + mutex.verify(); return clusterScale; } - @Override - public boolean isAfter(){ - return false; - } - /** * Set the cluster scaling. * @see #getClusterScale() @@ -203,6 +204,23 @@ public class InnerTube extends ThicknessRingComponent implements AxialPositionab clusterScale = scale; fireComponentChangeEvent(new ComponentChangeEvent(this, ComponentChangeEvent.MASS_CHANGE)); } + + /** + * Get the cluster scaling as an absolute distance measurement. A value of 0 indicates that the tubes are packed + * touching each other, larger values separate the tubes and smaller values pack inside each other. + */ + public double getClusterScaleAbsolute() { + return (getClusterScale() - 1) * getOuterRadius() * 2; + } + + /** + * Set the absolute cluster scaling (in terms of distance). + * @see #getClusterScaleAbsolute() + */ + public void setClusterScaleAbsolute(double scale) { + double scaleRel = scale / (getOuterRadius() * 2) + 1; + setClusterScale(scaleRel); + } diff --git a/swing/src/net/sf/openrocket/gui/configdialog/InnerTubeConfig.java b/swing/src/net/sf/openrocket/gui/configdialog/InnerTubeConfig.java index 1d3ef71b8..b0386fad3 100644 --- a/swing/src/net/sf/openrocket/gui/configdialog/InnerTubeConfig.java +++ b/swing/src/net/sf/openrocket/gui/configdialog/InnerTubeConfig.java @@ -9,6 +9,8 @@ import java.awt.Rectangle; import java.awt.RenderingHints; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; +import java.awt.event.ItemEvent; +import java.awt.event.ItemListener; import java.awt.event.MouseEvent; import java.awt.event.MouseListener; import java.awt.geom.Ellipse2D; @@ -17,12 +19,14 @@ import java.util.EventObject; import java.util.List; import javax.swing.BorderFactory; +import javax.swing.ButtonGroup; import javax.swing.JButton; import javax.swing.JCheckBox; import javax.swing.JComponent; import javax.swing.JDialog; import javax.swing.JLabel; import javax.swing.JPanel; +import javax.swing.JRadioButton; import javax.swing.JSpinner; import javax.swing.SwingUtilities; import javax.swing.border.BevelBorder; @@ -46,6 +50,7 @@ import net.sf.openrocket.rocketcomponent.MotorMount; import net.sf.openrocket.rocketcomponent.RingComponent; import net.sf.openrocket.rocketcomponent.RocketComponent; import net.sf.openrocket.startup.Application; +import net.sf.openrocket.startup.Preferences; import net.sf.openrocket.unit.UnitGroup; import net.sf.openrocket.util.BugException; import net.sf.openrocket.util.Coordinate; @@ -55,6 +60,9 @@ import net.sf.openrocket.util.StateChangeListener; public class InnerTubeConfig extends RocketComponentConfig { private static final long serialVersionUID = 7900041420864324470L; private static final Translator trans = Application.getTranslator(); + private static final Preferences prefs = Application.getPreferences(); + + private static final String PREF_SEPARATION_RELATIVE = "InnerTubeSeparationRelative"; public InnerTubeConfig(OpenRocketDocument d, RocketComponent c, JDialog parent) { @@ -279,29 +287,88 @@ public class InnerTubeConfig extends RocketComponentConfig { //// The separation of the tubes, 1.0 = touching each other l.setToolTipText(trans.get("InnerTubeCfg.lbl.ttip.TubeSep")); subPanel.add(l); - DoubleModel dm = new DoubleModel(component, "ClusterScale", 1, UnitGroup.UNITS_NONE, 0); - JSpinner spin = new JSpinner(dm.getSpinnerModel()); - spin.setEditor(new SpinnerEditor(spin)); - //// The separation of the tubes, 1.0 = touching each other - spin.setToolTipText(trans.get("InnerTubeCfg.lbl.ttip.TubeSep")); - subPanel.add(spin, "growx"); - order.add(((SpinnerEditor) spin.getEditor()).getTextField()); + //// Models + final boolean useRelativeSeparation = prefs.getBoolean(PREF_SEPARATION_RELATIVE, true); + final DoubleModel clusterScaleModelRel = new DoubleModel(component, "ClusterScale", 1, UnitGroup.UNITS_NONE, 0); + final DoubleModel clusterScaleModelAbs = new DoubleModel(component, "ClusterScaleAbsolute", 1, UnitGroup.UNITS_LENGTH); + final DoubleModel clusterScaleModel = useRelativeSeparation ? clusterScaleModelRel : clusterScaleModelAbs; - BasicSlider bs = new BasicSlider(dm.getSliderModel(0, 1, 4)); - //// The separation of the tubes, 1.0 = touching each other - bs.setToolTipText(trans.get("InnerTubeCfg.lbl.ttip.TubeSep")); - subPanel.add(bs, "skip,w 100lp, wrap"); + final String clusterScaleTtipRel = trans.get("InnerTubeCfg.lbl.ttip.TubeSep"); + final String clusterScaleTtipAbs = trans.get("InnerTubeCfg.lbl.ttip.TubeSepAbs"); + final String clusterScaleTtip = useRelativeSeparation ? clusterScaleTtipRel : clusterScaleTtipAbs; + + JSpinner clusterScaleSpin = new JSpinner(clusterScaleModel.getSpinnerModel()); + clusterScaleSpin.setEditor(new SpinnerEditor(clusterScaleSpin)); + clusterScaleSpin.setToolTipText(clusterScaleTtip); + subPanel.add(clusterScaleSpin, "growx"); + order.add(((SpinnerEditor) clusterScaleSpin.getEditor()).getTextField()); + + UnitSelector clusterScaleUnit = new UnitSelector(clusterScaleModel); + subPanel.add(clusterScaleUnit, "growx"); + + BasicSlider clusterScaleBs = new BasicSlider(clusterScaleModel.getSliderModel(0, 1, 4)); + subPanel.add(clusterScaleBs, "w 100lp, wrap"); + + // Relative/absolute separation + JRadioButton rbRel = new JRadioButton(trans.get("InnerTubeCfg.radioBut.Relative")); + JRadioButton rbAbs = new JRadioButton(trans.get("InnerTubeCfg.radioBut.Absolute")); + rbRel.setToolTipText(trans.get("InnerTubeCfg.radioBut.Relative.ttip")); + rbAbs.setToolTipText(trans.get("InnerTubeCfg.radioBut.Absolute.ttip")); + ButtonGroup bg = new ButtonGroup(); + bg.add(rbRel); + bg.add(rbAbs); + subPanel.add(rbRel, "skip, spanx, split 2"); + subPanel.add(rbAbs, "wrap"); + + rbRel.addItemListener(new ItemListener() { + @Override + public void itemStateChanged(ItemEvent e) { + if (e.getStateChange() == ItemEvent.DESELECTED) + return; + clusterScaleSpin.setModel(clusterScaleModelRel.getSpinnerModel()); + clusterScaleSpin.setEditor(new SpinnerEditor(clusterScaleSpin)); + clusterScaleUnit.setModel(clusterScaleModelRel); + clusterScaleBs.setModel(clusterScaleModelRel.getSliderModel(0, 1, 4)); + clusterScaleSpin.setToolTipText(clusterScaleTtipRel); + + prefs.putBoolean(PREF_SEPARATION_RELATIVE, false); + } + }); + rbAbs.addItemListener(new ItemListener() { + @Override + public void itemStateChanged(ItemEvent e) { + if (e.getStateChange() == ItemEvent.DESELECTED) + return; + DoubleModel radiusModelMin = new DoubleModel(component, "OuterRadius", -2, UnitGroup.UNITS_LENGTH); + DoubleModel radiusModelMax = new DoubleModel(component, "OuterRadius", 6, UnitGroup.UNITS_LENGTH); + + clusterScaleSpin.setModel(clusterScaleModelAbs.getSpinnerModel()); + clusterScaleSpin.setEditor(new SpinnerEditor(clusterScaleSpin)); + clusterScaleUnit.setModel(clusterScaleModelAbs); + clusterScaleBs.setModel(clusterScaleModelAbs.getSliderModel(radiusModelMin, radiusModelMax)); + clusterScaleSpin.setToolTipText(clusterScaleTtipAbs); + + prefs.putBoolean(PREF_SEPARATION_RELATIVE, false); + } + }); + + // Select the button by default + if (prefs.getBoolean(PREF_SEPARATION_RELATIVE, true)) { + rbRel.setSelected(true); + } else { + rbAbs.setSelected(true); + } // Rotation: l = new JLabel(trans.get("InnerTubeCfg.lbl.Rotation")); //// Rotation angle of the cluster configuration l.setToolTipText(trans.get("InnerTubeCfg.lbl.ttip.Rotation")); subPanel.add(l); - dm = new DoubleModel(component, "ClusterRotation", 1, UnitGroup.UNITS_ANGLE, + DoubleModel dm = new DoubleModel(component, "ClusterRotation", 1, UnitGroup.UNITS_ANGLE, -Math.PI, Math.PI); - spin = new JSpinner(dm.getSpinnerModel()); + JSpinner spin = new JSpinner(dm.getSpinnerModel()); spin.setEditor(new SpinnerEditor(spin)); //// Rotation angle of the cluster configuration spin.setToolTipText(trans.get("InnerTubeCfg.lbl.ttip.Rotation")); @@ -309,7 +376,7 @@ public class InnerTubeConfig extends RocketComponentConfig { order.add(((SpinnerEditor) spin.getEditor()).getTextField()); subPanel.add(new UnitSelector(dm), "growx"); - bs = new BasicSlider(dm.getSliderModel()); + BasicSlider bs = new BasicSlider(dm.getSliderModel()); //// Rotation angle of the cluster configuration bs.setToolTipText(trans.get("InnerTubeCfg.lbl.ttip.Rotation")); subPanel.add(bs, "w 100lp, wrap para");