diff --git a/core/resources/l10n/messages.properties b/core/resources/l10n/messages.properties index f84c5e7b3..e4d779394 100644 --- a/core/resources/l10n/messages.properties +++ b/core/resources/l10n/messages.properties @@ -809,6 +809,10 @@ AppearanceCfg.lbl.texture.offset = Offset: AppearanceCfg.lbl.texture.center = Center: AppearanceCfg.lbl.texture.rotation = Rotation: AppearanceCfg.lbl.texture.repeat = Repeat: +AppearanceCfg.lbl.InsideSameAsOutside = Same as outside +AppearanceCfg.lbl.ttip.InsideSameAsOutside = Use the same appearance for the inside as for the outside +AppearanceCfg.lbl.EdgesSameAsInside = Use inside appearance for edges +AppearanceCfg.lbl.ttip.EdgesSameAsInside = Use the inside appearance (checked) or outside appearance (unchecked) for the edges ! Texture Wrap Modes TextureWrap.Repeat = Repeat @@ -871,6 +875,8 @@ RocketCompCfg.border.Foreshoulder = Fore shoulder !RocketCompCfg.lbl.Length = Length: RocketCompCfg.lbl.InstanceCount = Instance Count RocketCompCfg.lbl.InstanceSeparation = Instance Separation +RocketCompCfg.tab.Outside = Outside +RocketCompCfg.tab.Inside = Inside ! BulkheadConfig BulkheadCfg.tab.Diameter = Diameter: diff --git a/core/src/net/sf/openrocket/rocketcomponent/BodyTube.java b/core/src/net/sf/openrocket/rocketcomponent/BodyTube.java index 4428dfac4..837dd5cf0 100644 --- a/core/src/net/sf/openrocket/rocketcomponent/BodyTube.java +++ b/core/src/net/sf/openrocket/rocketcomponent/BodyTube.java @@ -1,19 +1,17 @@ package net.sf.openrocket.rocketcomponent; -import java.util.ArrayList; -import java.util.Collection; +import java.util.EventObject; import java.util.Iterator; +import net.sf.openrocket.appearance.Appearance; +import net.sf.openrocket.appearance.Decal; import net.sf.openrocket.l10n.Translator; import net.sf.openrocket.motor.Motor; import net.sf.openrocket.motor.MotorConfiguration; import net.sf.openrocket.motor.MotorConfigurationSet; import net.sf.openrocket.preset.ComponentPreset; import net.sf.openrocket.startup.Application; -import net.sf.openrocket.util.BoundingBox; -import net.sf.openrocket.util.BugException; -import net.sf.openrocket.util.Coordinate; -import net.sf.openrocket.util.MathUtil; +import net.sf.openrocket.util.*; /** @@ -22,7 +20,7 @@ import net.sf.openrocket.util.MathUtil; * @author Sampo Niskanen */ -public class BodyTube extends SymmetricComponent implements BoxBounded, MotorMount, Coaxial { +public class BodyTube extends SymmetricComponent implements BoxBounded, MotorMount, Coaxial, InsideColorComponent { private static final Translator trans = Application.getTranslator(); private double outerRadius = 0; @@ -33,6 +31,11 @@ public class BodyTube extends SymmetricComponent implements BoxBounded, MotorMou private boolean isActingMount = false; private MotorConfigurationSet motors; + + // Settings for inside/edge appearance + private Appearance insideAppearance = null; + private boolean insideSameAsOutside = true; + private boolean edgesSameAsInside = true; public BodyTube() { this(8 * DEFAULT_RADIUS, DEFAULT_RADIUS); @@ -455,4 +458,49 @@ public class BodyTube extends SymmetricComponent implements BoxBounded, MotorMou public ClusterConfiguration getClusterConfiguration() { return ClusterConfiguration.SINGLE; } + + @Override + public Appearance getInsideAppearance() { + return this.insideAppearance; + } + + @Override + public void setInsideAppearance(Appearance appearance) { + this.insideAppearance = appearance; + if (this.insideAppearance != null) { + Decal d = this.insideAppearance.getTexture(); + if (d != null) { + d.getImage().addChangeListener(new StateChangeListener() { + + @Override + public void stateChanged(EventObject e) { + fireComponentChangeEvent(ComponentChangeEvent.TEXTURE_CHANGE); + } + + }); + } + } + // CHECK - should this be a TEXTURE_CHANGE and not NONFUNCTIONAL_CHANGE? + fireComponentChangeEvent(ComponentChangeEvent.NONFUNCTIONAL_CHANGE); + } + + @Override + public boolean isEdgesSameAsInside() { + return this.edgesSameAsInside; + } + + @Override + public void setEdgesSameAsInside(boolean newState) { + this.edgesSameAsInside = newState; + } + + @Override + public boolean isInsideSameAsOutside() { + return this.insideSameAsOutside; + } + + @Override + public void setInsideSameAsOutside(boolean newState) { + this.insideSameAsOutside = newState; + } } diff --git a/core/src/net/sf/openrocket/rocketcomponent/InsideColorComponent.java b/core/src/net/sf/openrocket/rocketcomponent/InsideColorComponent.java new file mode 100644 index 000000000..3605cc322 --- /dev/null +++ b/core/src/net/sf/openrocket/rocketcomponent/InsideColorComponent.java @@ -0,0 +1,68 @@ +package net.sf.openrocket.rocketcomponent; + +import net.sf.openrocket.appearance.Appearance; +import net.sf.openrocket.appearance.Decal; +import net.sf.openrocket.rocketcomponent.ComponentChangeEvent; +import net.sf.openrocket.rocketcomponent.RocketComponent; +import net.sf.openrocket.rocketcomponent.position.AxialMethod; +import net.sf.openrocket.util.StateChangeListener; + +import java.util.EventObject; + +/** + * This is a marker interface which, if applied to a component, will mark that component as having the possibility to + * have a different inside and outside color. This will cause the appearance editor of that component to have a separate + * section for the inside and outside color and will consequently also render the inside and outside of that component + * (in a 3D figure) differently. + * + * @author Sibo Van Gool + */ +public interface InsideColorComponent { + /** + * Get the realistic inside appearance of this component. + * null = use the default for this material + * + * @return The component's realistic inner appearance, or null + */ + Appearance getInsideAppearance(); + + /** + * Set the realistic inside appearance of this component. + * Use null for default. + * + * @param appearance the inner appearance to be set + */ + void setInsideAppearance(Appearance appearance); + + /** + * Checks whether the component uses for the edges the same appearance as the inside (return true) or as the + * outside (return false) + * + * @return true if edges should use the same appearance as the inside, + * false if edges should use the same appearance as the outside + */ + boolean isEdgesSameAsInside(); + + /** + * Sets the new state for edgesUseInsideAppearance to newState + * + * @param newState new edgesUseInsideAppearance value + */ + void setEdgesSameAsInside(boolean newState); + + /** + * Checks whether the component should use the same appearance for the inside as the outside (return true) or as the + * outside (return false) + * + * @return true if edges should use the same appearance as the inside, + * false if edges should use the same appearance as the outside + */ + boolean isInsideSameAsOutside(); + + /** + * Sets the new state for insideSameAsOutside to newState + * + * @param newState new edgesUseInsideAppearance value + */ + void setInsideSameAsOutside(boolean newState); +} diff --git a/core/src/net/sf/openrocket/rocketcomponent/LaunchLug.java b/core/src/net/sf/openrocket/rocketcomponent/LaunchLug.java index 92ef86e72..cbd2bd28f 100644 --- a/core/src/net/sf/openrocket/rocketcomponent/LaunchLug.java +++ b/core/src/net/sf/openrocket/rocketcomponent/LaunchLug.java @@ -2,7 +2,10 @@ package net.sf.openrocket.rocketcomponent; import java.util.ArrayList; import java.util.Collection; +import java.util.EventObject; +import net.sf.openrocket.appearance.Appearance; +import net.sf.openrocket.appearance.Decal; import net.sf.openrocket.l10n.Translator; import net.sf.openrocket.preset.ComponentPreset; import net.sf.openrocket.preset.ComponentPreset.Type; @@ -11,10 +14,10 @@ import net.sf.openrocket.startup.Application; import net.sf.openrocket.util.BoundingBox; import net.sf.openrocket.util.Coordinate; import net.sf.openrocket.util.MathUtil; +import net.sf.openrocket.util.StateChangeListener; - -public class LaunchLug extends ExternalComponent implements AnglePositionable, BoxBounded, Coaxial, LineInstanceable { +public class LaunchLug extends ExternalComponent implements AnglePositionable, BoxBounded, Coaxial, LineInstanceable, InsideColorComponent { private static final Translator trans = Application.getTranslator(); @@ -26,6 +29,11 @@ public class LaunchLug extends ExternalComponent implements AnglePositionable, B private int instanceCount = 1; private double instanceSeparation = 0; // front-front along the positive rocket axis. i.e. [1,0,0]; + + // Settings for inside/edge appearance + private Appearance insideAppearance = null; + private boolean insideSameAsOutside = true; + private boolean edgesSameAsInside = true; public LaunchLug() { super(AxialMethod.MIDDLE); @@ -279,4 +287,49 @@ public class LaunchLug extends ExternalComponent implements AnglePositionable, B public void setAngleMethod(AngleMethod newMethod) { // no-op } + + @Override + public Appearance getInsideAppearance() { + return this.insideAppearance; + } + + @Override + public void setInsideAppearance(Appearance appearance) { + this.insideAppearance = appearance; + if (this.insideAppearance != null) { + Decal d = this.insideAppearance.getTexture(); + if (d != null) { + d.getImage().addChangeListener(new StateChangeListener() { + + @Override + public void stateChanged(EventObject e) { + fireComponentChangeEvent(ComponentChangeEvent.TEXTURE_CHANGE); + } + + }); + } + } + // CHECK - should this be a TEXTURE_CHANGE and not NONFUNCTIONAL_CHANGE? + fireComponentChangeEvent(ComponentChangeEvent.NONFUNCTIONAL_CHANGE); + } + + @Override + public boolean isEdgesSameAsInside() { + return this.edgesSameAsInside; + } + + @Override + public void setEdgesSameAsInside(boolean newState) { + this.edgesSameAsInside = newState; + } + + @Override + public boolean isInsideSameAsOutside() { + return this.insideSameAsOutside; + } + + @Override + public void setInsideSameAsOutside(boolean newState) { + this.insideSameAsOutside = newState; + } } diff --git a/core/src/net/sf/openrocket/rocketcomponent/NoseCone.java b/core/src/net/sf/openrocket/rocketcomponent/NoseCone.java index f58f40bd2..3ca204336 100644 --- a/core/src/net/sf/openrocket/rocketcomponent/NoseCone.java +++ b/core/src/net/sf/openrocket/rocketcomponent/NoseCone.java @@ -1,9 +1,14 @@ package net.sf.openrocket.rocketcomponent; +import net.sf.openrocket.appearance.Appearance; +import net.sf.openrocket.appearance.Decal; import net.sf.openrocket.l10n.Translator; import net.sf.openrocket.preset.ComponentPreset; import net.sf.openrocket.preset.ComponentPreset.Type; import net.sf.openrocket.startup.Application; +import net.sf.openrocket.util.StateChangeListener; + +import java.util.EventObject; /** * Rocket nose cones of various types. Implemented as a transition with the @@ -12,9 +17,13 @@ import net.sf.openrocket.startup.Application; * @author Sampo Niskanen */ -public class NoseCone extends Transition { +public class NoseCone extends Transition implements InsideColorComponent { private static final Translator trans = Application.getTranslator(); - + + // Settings for inside/edge appearance + private Appearance insideAppearance = null; + private boolean insideSameAsOutside = true; + private boolean edgesSameAsInside = true; /********* Constructors **********/ public NoseCone() { @@ -135,5 +144,49 @@ public class NoseCone extends Transition { //// Nose cone return trans.get("NoseCone.NoseCone"); } - + + @Override + public Appearance getInsideAppearance() { + return this.insideAppearance; + } + + @Override + public void setInsideAppearance(Appearance appearance) { + this.insideAppearance = appearance; + if (this.insideAppearance != null) { + Decal d = this.insideAppearance.getTexture(); + if (d != null) { + d.getImage().addChangeListener(new StateChangeListener() { + + @Override + public void stateChanged(EventObject e) { + fireComponentChangeEvent(ComponentChangeEvent.TEXTURE_CHANGE); + } + + }); + } + } + // CHECK - should this be a TEXTURE_CHANGE and not NONFUNCTIONAL_CHANGE? + fireComponentChangeEvent(ComponentChangeEvent.NONFUNCTIONAL_CHANGE); + } + + @Override + public boolean isEdgesSameAsInside() { + return this.edgesSameAsInside; + } + + @Override + public void setEdgesSameAsInside(boolean newState) { + this.edgesSameAsInside = newState; + } + + @Override + public boolean isInsideSameAsOutside() { + return this.insideSameAsOutside; + } + + @Override + public void setInsideSameAsOutside(boolean newState) { + this.insideSameAsOutside = newState; + } } diff --git a/core/src/net/sf/openrocket/rocketcomponent/Transition.java b/core/src/net/sf/openrocket/rocketcomponent/Transition.java index d89f6de69..1d446a147 100644 --- a/core/src/net/sf/openrocket/rocketcomponent/Transition.java +++ b/core/src/net/sf/openrocket/rocketcomponent/Transition.java @@ -5,16 +5,20 @@ import static net.sf.openrocket.util.MathUtil.pow2; import static net.sf.openrocket.util.MathUtil.pow3; import java.util.Collection; +import java.util.EventObject; +import net.sf.openrocket.appearance.Appearance; +import net.sf.openrocket.appearance.Decal; import net.sf.openrocket.l10n.Translator; import net.sf.openrocket.preset.ComponentPreset; import net.sf.openrocket.preset.ComponentPreset.Type; import net.sf.openrocket.startup.Application; import net.sf.openrocket.util.Coordinate; import net.sf.openrocket.util.MathUtil; +import net.sf.openrocket.util.StateChangeListener; -public class Transition extends SymmetricComponent { +public class Transition extends SymmetricComponent implements InsideColorComponent { private static final Translator trans = Application.getTranslator(); private static final double CLIP_PRECISION = 0.0001; @@ -40,6 +44,11 @@ public class Transition extends SymmetricComponent { // Used to cache the clip length private double clipLength = -1; + // Settings for inside/edge appearance + private Appearance insideAppearance = null; + private boolean insideSameAsOutside = true; + private boolean edgesSameAsInside = true; + public Transition() { super(); @@ -938,4 +947,48 @@ public class Transition extends SymmetricComponent { } } + @Override + public Appearance getInsideAppearance() { + return this.insideAppearance; + } + + @Override + public void setInsideAppearance(Appearance appearance) { + this.insideAppearance = appearance; + if (this.insideAppearance != null) { + Decal d = this.insideAppearance.getTexture(); + if (d != null) { + d.getImage().addChangeListener(new StateChangeListener() { + + @Override + public void stateChanged(EventObject e) { + fireComponentChangeEvent(ComponentChangeEvent.TEXTURE_CHANGE); + } + + }); + } + } + // CHECK - should this be a TEXTURE_CHANGE and not NONFUNCTIONAL_CHANGE? + fireComponentChangeEvent(ComponentChangeEvent.NONFUNCTIONAL_CHANGE); + } + + @Override + public boolean isEdgesSameAsInside() { + return this.edgesSameAsInside; + } + + @Override + public void setEdgesSameAsInside(boolean newState) { + this.edgesSameAsInside = newState; + } + + @Override + public boolean isInsideSameAsOutside() { + return this.insideSameAsOutside; + } + + @Override + public void setInsideSameAsOutside(boolean newState) { + this.insideSameAsOutside = newState; + } } diff --git a/core/src/net/sf/openrocket/rocketcomponent/TubeFinSet.java b/core/src/net/sf/openrocket/rocketcomponent/TubeFinSet.java index 820e6896d..92aca3409 100644 --- a/core/src/net/sf/openrocket/rocketcomponent/TubeFinSet.java +++ b/core/src/net/sf/openrocket/rocketcomponent/TubeFinSet.java @@ -2,8 +2,11 @@ package net.sf.openrocket.rocketcomponent; import java.util.ArrayList; import java.util.Collection; +import java.util.EventObject; import java.util.List; +import net.sf.openrocket.appearance.Appearance; +import net.sf.openrocket.appearance.Decal; import net.sf.openrocket.l10n.Translator; import net.sf.openrocket.preset.ComponentPreset; import net.sf.openrocket.preset.ComponentPreset.Type; @@ -12,12 +15,9 @@ import net.sf.openrocket.rocketcomponent.position.AxialMethod; import net.sf.openrocket.rocketcomponent.position.AxialPositionable; import net.sf.openrocket.rocketcomponent.position.RadiusMethod; import net.sf.openrocket.startup.Application; -import net.sf.openrocket.util.BoundingBox; -import net.sf.openrocket.util.Coordinate; -import net.sf.openrocket.util.MathUtil; -import net.sf.openrocket.util.Transformation; +import net.sf.openrocket.util.*; -public class TubeFinSet extends ExternalComponent implements AxialPositionable, BoxBounded, RingInstanceable { +public class TubeFinSet extends ExternalComponent implements AxialPositionable, BoxBounded, RingInstanceable, InsideColorComponent { private static final Translator trans = Application.getTranslator(); private final static double DEFAULT_RADIUS = 0.025; @@ -27,6 +27,11 @@ public class TubeFinSet extends ExternalComponent implements AxialPositionable, protected double thickness = 0.002; private AngleMethod angleMethod = AngleMethod.FIXED; protected RadiusMethod radiusMethod = RadiusMethod.RELATIVE; + + // Settings for inside/edge appearance + private Appearance insideAppearance = null; + private boolean insideSameAsOutside = true; + private boolean edgesSameAsInside = true; /** * Rotation angle of the first fin. Zero corresponds to the positive y-axis. @@ -452,5 +457,50 @@ public class TubeFinSet extends ExternalComponent implements AxialPositionable, // TODO Auto-generated method stub } + + @Override + public Appearance getInsideAppearance() { + return this.insideAppearance; + } + + @Override + public void setInsideAppearance(Appearance appearance) { + this.insideAppearance = appearance; + if (this.insideAppearance != null) { + Decal d = this.insideAppearance.getTexture(); + if (d != null) { + d.getImage().addChangeListener(new StateChangeListener() { + + @Override + public void stateChanged(EventObject e) { + fireComponentChangeEvent(ComponentChangeEvent.TEXTURE_CHANGE); + } + + }); + } + } + // CHECK - should this be a TEXTURE_CHANGE and not NONFUNCTIONAL_CHANGE? + fireComponentChangeEvent(ComponentChangeEvent.NONFUNCTIONAL_CHANGE); + } + + @Override + public boolean isEdgesSameAsInside() { + return this.edgesSameAsInside; + } + + @Override + public void setEdgesSameAsInside(boolean newState) { + this.edgesSameAsInside = newState; + } + + @Override + public boolean isInsideSameAsOutside() { + return this.insideSameAsOutside; + } + + @Override + public void setInsideSameAsOutside(boolean newState) { + this.insideSameAsOutside = newState; + } } diff --git a/swing/src/net/sf/openrocket/gui/configdialog/AppearancePanel.java b/swing/src/net/sf/openrocket/gui/configdialog/AppearancePanel.java index b2d00a6b8..22d63fe73 100644 --- a/swing/src/net/sf/openrocket/gui/configdialog/AppearancePanel.java +++ b/swing/src/net/sf/openrocket/gui/configdialog/AppearancePanel.java @@ -6,19 +6,7 @@ import java.awt.event.ActionListener; import java.lang.reflect.Method; import java.util.EventObject; -import javax.swing.JButton; -import javax.swing.JCheckBox; -import javax.swing.JColorChooser; -import javax.swing.JComboBox; -import javax.swing.JDialog; -import javax.swing.JLabel; -import javax.swing.JOptionPane; -import javax.swing.JPanel; -import javax.swing.JSeparator; -import javax.swing.JSlider; -import javax.swing.JSpinner; -import javax.swing.SwingConstants; -import javax.swing.SwingUtilities; +import javax.swing.*; import javax.swing.colorchooser.ColorSelectionModel; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; @@ -45,6 +33,8 @@ import net.sf.openrocket.gui.util.EditDecalHelper; import net.sf.openrocket.gui.util.EditDecalHelper.EditDecalHelperException; import net.sf.openrocket.gui.util.SwingPreferences; import net.sf.openrocket.l10n.Translator; +import net.sf.openrocket.rocketcomponent.ComponentChangeEvent; +import net.sf.openrocket.rocketcomponent.InsideColorComponent; import net.sf.openrocket.rocketcomponent.RocketComponent; import net.sf.openrocket.startup.Application; import net.sf.openrocket.unit.GeneralUnit; @@ -58,15 +48,18 @@ public class AppearancePanel extends JPanel { private static final Translator trans = Application.getTranslator(); - private EditDecalHelper editDecalHelper = Application.getInjector() + final private EditDecalHelper editDecalHelper = Application.getInjector() .getInstance(EditDecalHelper.class); - private AppearanceBuilder ab; + // Outside and inside appearance builder + final private AppearanceBuilder ab; + private AppearanceBuilder insideAb; // We hang on to the user selected appearance when switching to default // appearance. // this appearance is restored if the user unchecks the "default" button. private Appearance previousUserSelectedAppearance = null; + private Appearance previousUserSelectedInsideAppearance = null; // We cache the default appearance for this component to make switching // faster. @@ -105,7 +98,7 @@ public class AppearancePanel extends JPanel { } /** - Changes the color of the selected color to + Changes the color of the selected component to @param color: color to change the component to */ private void changeComponentColor(Color color) { @@ -182,8 +175,9 @@ public class AppearancePanel extends JPanel { final RocketComponent c) { super(new MigLayout("fill", "[150][grow][150][grow]")); - previousUserSelectedAppearance = c.getAppearance(); defaultAppearance = DefaultAppearance.getDefaultAppearance(c); + + previousUserSelectedAppearance = c.getAppearance(); if (previousUserSelectedAppearance == null) { previousUserSelectedAppearance = new AppearanceBuilder() .getAppearance(); @@ -192,6 +186,17 @@ public class AppearancePanel extends JPanel { ab = new AppearanceBuilder(previousUserSelectedAppearance); } + if (c instanceof InsideColorComponent) { + previousUserSelectedInsideAppearance = ((InsideColorComponent) c).getInsideAppearance(); + if (previousUserSelectedInsideAppearance == null) { + previousUserSelectedInsideAppearance = new AppearanceBuilder() + .getAppearance(); + insideAb = new AppearanceBuilder(defaultAppearance); + } else { + insideAb = new AppearanceBuilder(previousUserSelectedInsideAppearance); + } + } + net.sf.openrocket.util.Color figureColor = c.getColor(); if (figureColor == null) { figureColor = Application.getPreferences().getDefaultColor( @@ -200,19 +205,9 @@ public class AppearancePanel extends JPanel { final JButton figureColorButton = new JButton( new ColorIcon(figureColor)); - final JButton colorButton = new JButton(new ColorIcon(ab.getPaint())); - - final DecalModel decalModel = new DecalModel(this, document, ab); - final JComboBox textureDropDown = new JComboBox(decalModel); - ab.addChangeListener(new StateChangeListener() { @Override - public void stateChanged(EventObject e) { - figureColorButton.setIcon(new ColorIcon(c.getColor())); - colorButton.setIcon(new ColorIcon(ab.getPaint())); - c.setAppearance(ab.getAppearance()); - decalModel.refresh(); - } + public void stateChanged(EventObject e) { figureColorButton.setIcon(new ColorIcon(c.getColor())); } }); c.addChangeListener(new StateChangeListener() { @@ -229,9 +224,7 @@ public class AppearancePanel extends JPanel { figureColorButton .addActionListener(new ColorActionListener(c, "Color")); - colorButton.addActionListener(new ColorActionListener(ab, "Paint")); - BooleanModel mDefault = new BooleanModel(c.getAppearance() == null); BooleanModel fDefault = new BooleanModel(c.getColor() == null); {// Style Header Row @@ -285,7 +278,6 @@ public class AppearancePanel extends JPanel { } {// Line Style - add(new JLabel(trans.get("RocketCompCfg.lbl.Complinestyle"))); LineStyle[] list = new LineStyle[LineStyle.values().length + 1]; @@ -304,148 +296,241 @@ public class AppearancePanel extends JPanel { add(new JSeparator(SwingConstants.HORIZONTAL), "span, wrap, growx"); - {// Texture Header Row - add(new StyledLabel(trans.get("AppearanceCfg.lbl.Appearance"), - Style.BOLD)); - final JCheckBox materialDefault = new JCheckBox(mDefault); - materialDefault.addActionListener(new ActionListener() { + // Display a tabbed panel for choosing the outside and inside appearance, if the object is of type InsideColorComponent + if (c instanceof InsideColorComponent) { + JTabbedPane tabbedPane = new JTabbedPane(); + JPanel outsidePanel = new JPanel(new MigLayout("fill", "[150][grow][150][grow]")); + JPanel insidePanel = new JPanel(new MigLayout("fill", "[150][grow][150][grow]")); + + appearanceSection(document, c, false, outsidePanel); + appearanceSection(document, c, true, insidePanel); + + tabbedPane.addTab(trans.get("RocketCompCfg.tab.Outside"), null, outsidePanel, + "Outside Tool Tip"); + tabbedPane.addTab(trans.get("RocketCompCfg.tab.Inside"), null, insidePanel, + "Inside Tool Tip"); + add(tabbedPane, "span 4, growx, wrap"); + + // Checkbox to set edges the same as inside/outside + BooleanModel b = new BooleanModel(((InsideColorComponent) c).isEdgesSameAsInside()); + JCheckBox edges = new JCheckBox(b); + edges.setText(trans.get("AppearanceCfg.lbl.EdgesSameAsInside")); + edges.setToolTipText(trans.get("AppearanceCfg.lbl.ttip.EdgesSameAsInside")); + add(edges, "wrap"); + + edges.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { - if (materialDefault.isSelected()) { - previousUserSelectedAppearance = (ab == null) ? null - : ab.getAppearance(); - ab.setAppearance(defaultAppearance); - } else { - ab.setAppearance(previousUserSelectedAppearance); - } + ((InsideColorComponent) c).setEdgesSameAsInside(edges.isSelected()); + c.fireComponentChangeEvent(ComponentChangeEvent.NONFUNCTIONAL_CHANGE); } }); - materialDefault.setText(trans.get("AppearanceCfg.lbl.Usedefault")); - add(materialDefault, "wrap"); } + else + appearanceSection(document, c, false, this); + } - {// Texture File - add(new JLabel(trans.get("AppearanceCfg.lbl.Texture"))); - JPanel p = new JPanel(new MigLayout("fill, ins 0", "[grow][]")); - mDefault.addEnableComponent(textureDropDown, false); - p.add(textureDropDown, "grow"); - add(p, "span 3, growx, wrap"); - final JButton editBtn = new JButton( - trans.get("AppearanceCfg.but.edit")); - editBtn.setEnabled(ab.getImage() != null); - // Enable the editBtn only when the appearance builder has an Image - // assigned to it. - ab.addChangeListener(new StateChangeListener() { - @Override - public void stateChanged(EventObject e) { - editBtn.setEnabled(ab.getImage() != null); + /** + * + * @param document + * @param c + * @param insideBuilder flag to check whether you are on the inside builder (true) or outside builder + * @param panel + */ + private void appearanceSection(OpenRocketDocument document, RocketComponent c, + boolean insideBuilder, JPanel panel) { + AppearanceBuilder builder; + BooleanModel mDefault; + if (!insideBuilder) { + builder = ab; + mDefault = new BooleanModel(c.getAppearance() == null); + } + else if (c instanceof InsideColorComponent) { + builder = insideAb; + mDefault = new BooleanModel(((InsideColorComponent)c).getInsideAppearance() == null); + } + else return; + + DecalModel decalModel = new DecalModel(panel, document, builder); + JComboBox textureDropDown = new JComboBox(decalModel); + + JButton colorButton = new JButton(new ColorIcon(builder.getPaint())); + + builder.addChangeListener(new StateChangeListener() { + @Override + public void stateChanged(EventObject e) { + colorButton.setIcon(new ColorIcon(builder.getPaint())); + if (!insideBuilder) + c.setAppearance(builder.getAppearance()); + else + ((InsideColorComponent)c).setInsideAppearance(builder.getAppearance()); + decalModel.refresh(); + } + }); + + colorButton.addActionListener(new ColorActionListener(builder, "Paint")); + + // Texture Header Row + panel.add(new StyledLabel(trans.get("AppearanceCfg.lbl.Appearance"), + Style.BOLD)); + JCheckBox materialDefault = new JCheckBox(mDefault); + materialDefault.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + if (materialDefault.isSelected()) { + if (!insideBuilder) { + previousUserSelectedAppearance = (builder == null) ? null + : builder.getAppearance(); + } + else { + previousUserSelectedInsideAppearance = (builder == null) ? null + : builder.getAppearance(); + } + builder.setAppearance(defaultAppearance); + } else { + if (!insideBuilder) + builder.setAppearance(previousUserSelectedAppearance); + else + builder.setAppearance(previousUserSelectedInsideAppearance); } - }); - editBtn.addActionListener(new ActionListener() { + } + }); + materialDefault.setText(trans.get("AppearanceCfg.lbl.Usedefault")); + if (insideBuilder) + panel.add(materialDefault); + else + panel.add(materialDefault, "wrap"); + // Custom inside color + if (insideBuilder) { + BooleanModel b = new BooleanModel(((InsideColorComponent) c).isInsideSameAsOutside()); + JCheckBox customInside = new JCheckBox(b); + customInside.setText(trans.get("AppearanceCfg.lbl.InsideSameAsOutside")); + customInside.setToolTipText(trans.get("AppearanceCfg.lbl.ttip.InsideSameAsOutside")); + customInside.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { - try { - DecalImage newImage = editDecalHelper.editDecal( - SwingUtilities - .getWindowAncestor(AppearancePanel.this), - document, c, ab.getImage()); - ab.setImage(newImage); - } catch (EditDecalHelperException ex) { - JOptionPane.showMessageDialog(AppearancePanel.this, - ex.getMessage(), "", JOptionPane.ERROR_MESSAGE); - } + ((InsideColorComponent) c).setInsideSameAsOutside(customInside.isSelected()); + c.fireComponentChangeEvent(ComponentChangeEvent.NONFUNCTIONAL_CHANGE); } - }); - p.add(editBtn); + panel.add(customInside, "wrap"); } - { // Color - add(new JLabel(trans.get("AppearanceCfg.lbl.color.Color"))); - mDefault.addEnableComponent(colorButton, false); - add(colorButton); - } + // Texture File + panel.add(new JLabel(trans.get("AppearanceCfg.lbl.Texture"))); + JPanel p = new JPanel(new MigLayout("fill, ins 0", "[grow][]")); + mDefault.addEnableComponent(textureDropDown, false); + p.add(textureDropDown, "grow"); + panel.add(p, "span 3, growx, wrap"); + JButton editBtn = new JButton( + trans.get("AppearanceCfg.but.edit")); + editBtn.setEnabled(builder.getImage() != null); + // Enable the editBtn only when the appearance builder has an Image + // assigned to it. + builder.addChangeListener(new StateChangeListener() { + @Override + public void stateChanged(EventObject e) { + editBtn.setEnabled(builder.getImage() != null); + } + }); - { // Scale - add(new JLabel(trans.get("AppearanceCfg.lbl.texture.scale"))); + editBtn.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + try { + DecalImage newImage = editDecalHelper.editDecal( + SwingUtilities + .getWindowAncestor(panel), + document, c, builder.getImage()); + builder.setImage(newImage); + } catch (EditDecalHelperException ex) { + JOptionPane.showMessageDialog(panel, + ex.getMessage(), "", JOptionPane.ERROR_MESSAGE); + } + } - add(new JLabel("x:"), "split 4"); - JSpinner scaleU = new JSpinner(new DoubleModel(ab, "ScaleX", - TEXTURE_UNIT).getSpinnerModel()); - scaleU.setEditor(new SpinnerEditor(scaleU)); - mDefault.addEnableComponent(scaleU, false); - add(scaleU, "w 40"); + }); + p.add(editBtn); - add(new JLabel("y:")); - JSpinner scaleV = new JSpinner(new DoubleModel(ab, "ScaleY", - TEXTURE_UNIT).getSpinnerModel()); - scaleV.setEditor(new SpinnerEditor(scaleV)); - mDefault.addEnableComponent(scaleV, false); - add(scaleV, "wrap, w 40"); - } + // Color + panel.add(new JLabel(trans.get("AppearanceCfg.lbl.color.Color"))); + mDefault.addEnableComponent(colorButton, false); + panel.add(colorButton); - {// Shine - add(new JLabel(trans.get("AppearanceCfg.lbl.shine"))); - DoubleModel shineModel = new DoubleModel(ab, "Shine", - UnitGroup.UNITS_RELATIVE); - JSpinner spin = new JSpinner(shineModel.getSpinnerModel()); - spin.setEditor(new SpinnerEditor(spin)); - JSlider slide = new JSlider(shineModel.getSliderModel(0, 1)); - UnitSelector unit = new UnitSelector(shineModel); + // Scale + panel.add(new JLabel(trans.get("AppearanceCfg.lbl.texture.scale"))); - mDefault.addEnableComponent(slide, false); - mDefault.addEnableComponent(spin, false); - mDefault.addEnableComponent(unit, false); + panel.add(new JLabel("x:"), "split 4"); + JSpinner scaleU = new JSpinner(new DoubleModel(builder, "ScaleX", + TEXTURE_UNIT).getSpinnerModel()); + scaleU.setEditor(new SpinnerEditor(scaleU)); + mDefault.addEnableComponent(scaleU, false); + panel.add(scaleU, "w 40"); - add(spin, "split 3, w 50"); - add(unit); - add(slide, "w 50"); - } + panel.add(new JLabel("y:")); + JSpinner scaleV = new JSpinner(new DoubleModel(builder, "ScaleY", + TEXTURE_UNIT).getSpinnerModel()); + scaleV.setEditor(new SpinnerEditor(scaleV)); + mDefault.addEnableComponent(scaleV, false); + panel.add(scaleV, "wrap, w 40"); - { // Offset - add(new JLabel(trans.get("AppearanceCfg.lbl.texture.offset"))); + // Shine + panel.add(new JLabel(trans.get("AppearanceCfg.lbl.shine"))); + DoubleModel shineModel = new DoubleModel(builder, "Shine", + UnitGroup.UNITS_RELATIVE); + JSpinner spin = new JSpinner(shineModel.getSpinnerModel()); + spin.setEditor(new SpinnerEditor(spin)); + JSlider slide = new JSlider(shineModel.getSliderModel(0, 1)); + UnitSelector unit = new UnitSelector(shineModel); - add(new JLabel("x:"), "split 4"); - JSpinner offsetU = new JSpinner(new DoubleModel(ab, "OffsetU", - TEXTURE_UNIT).getSpinnerModel()); - offsetU.setEditor(new SpinnerEditor(offsetU)); - mDefault.addEnableComponent(offsetU, false); - add(offsetU, "w 40"); + mDefault.addEnableComponent(slide, false); + mDefault.addEnableComponent(spin, false); + mDefault.addEnableComponent(unit, false); - add(new JLabel("y:")); - JSpinner offsetV = new JSpinner(new DoubleModel(ab, "OffsetV", - TEXTURE_UNIT).getSpinnerModel()); - offsetV.setEditor(new SpinnerEditor(offsetV)); - mDefault.addEnableComponent(offsetV, false); - add(offsetV, "wrap, w 40"); - } + panel.add(spin, "split 3, w 50"); + panel.add(unit); + panel.add(slide, "w 50"); - { // Repeat - add(new JLabel(trans.get("AppearanceCfg.lbl.texture.repeat"))); - EdgeMode[] list = new EdgeMode[EdgeMode.values().length]; - System.arraycopy(EdgeMode.values(), 0, list, 0, - EdgeMode.values().length); - JComboBox combo = new JComboBox(new EnumModel(ab, - "EdgeMode", list)); - mDefault.addEnableComponent(combo, false); - add(combo); - } + // Offset + panel.add(new JLabel(trans.get("AppearanceCfg.lbl.texture.offset"))); - { // Rotation - add(new JLabel(trans.get("AppearanceCfg.lbl.texture.rotation"))); - DoubleModel rotationModel = new DoubleModel(ab, "Rotation", - UnitGroup.UNITS_ANGLE); - JSpinner rotation = new JSpinner(rotationModel.getSpinnerModel()); - rotation.setEditor(new SpinnerEditor(rotation)); - mDefault.addEnableComponent(rotation, false); - add(rotation, "split 3, w 50"); - add(new UnitSelector(rotationModel)); - BasicSlider bs = new BasicSlider(rotationModel.getSliderModel( - -Math.PI, Math.PI)); - mDefault.addEnableComponent(bs, false); - add(bs, "w 50, wrap"); - } + panel.add(new JLabel("x:"), "split 4"); + JSpinner offsetU = new JSpinner(new DoubleModel(builder, "OffsetU", + TEXTURE_UNIT).getSpinnerModel()); + offsetU.setEditor(new SpinnerEditor(offsetU)); + mDefault.addEnableComponent(offsetU, false); + panel.add(offsetU, "w 40"); + panel.add(new JLabel("y:")); + JSpinner offsetV = new JSpinner(new DoubleModel(builder, "OffsetV", + TEXTURE_UNIT).getSpinnerModel()); + offsetV.setEditor(new SpinnerEditor(offsetV)); + mDefault.addEnableComponent(offsetV, false); + panel.add(offsetV, "wrap, w 40"); + + // Repeat + panel.add(new JLabel(trans.get("AppearanceCfg.lbl.texture.repeat"))); + EdgeMode[] list = new EdgeMode[EdgeMode.values().length]; + System.arraycopy(EdgeMode.values(), 0, list, 0, + EdgeMode.values().length); + JComboBox combo = new JComboBox(new EnumModel(builder, + "EdgeMode", list)); + mDefault.addEnableComponent(combo, false); + panel.add(combo); + + // Rotation + panel.add(new JLabel(trans.get("AppearanceCfg.lbl.texture.rotation"))); + DoubleModel rotationModel = new DoubleModel(builder, "Rotation", + UnitGroup.UNITS_ANGLE); + JSpinner rotation = new JSpinner(rotationModel.getSpinnerModel()); + rotation.setEditor(new SpinnerEditor(rotation)); + mDefault.addEnableComponent(rotation, false); + panel.add(rotation, "split 3, w 50"); + panel.add(new UnitSelector(rotationModel)); + BasicSlider bs = new BasicSlider(rotationModel.getSliderModel( + -Math.PI, Math.PI)); + mDefault.addEnableComponent(bs, false); + panel.add(bs, "w 50, wrap"); } } diff --git a/swing/src/net/sf/openrocket/gui/figure3d/RealisticRenderer.java b/swing/src/net/sf/openrocket/gui/figure3d/RealisticRenderer.java index e584db68f..e799f886c 100644 --- a/swing/src/net/sf/openrocket/gui/figure3d/RealisticRenderer.java +++ b/swing/src/net/sf/openrocket/gui/figure3d/RealisticRenderer.java @@ -14,6 +14,7 @@ import net.sf.openrocket.document.OpenRocketDocument; import net.sf.openrocket.gui.figure3d.geometry.Geometry; import net.sf.openrocket.gui.figure3d.geometry.Geometry.Surface; import net.sf.openrocket.motor.Motor; +import net.sf.openrocket.rocketcomponent.InsideColorComponent; import net.sf.openrocket.rocketcomponent.RocketComponent; import net.sf.openrocket.util.Color; @@ -90,10 +91,24 @@ public class RealisticRenderer extends RocketRenderer { @Override public void renderComponent(final GL2 gl, Geometry geom, final float alpha) { - Appearance app = getAppearance( geom.getComponent() ); - render(gl, geom, Surface.INSIDE, app, true, alpha); + RocketComponent c = geom.getComponent(); + Appearance app = getAppearance(c); + if (c instanceof InsideColorComponent) { + Appearance innerApp = getInsideAppearance(c); + if (((InsideColorComponent) c).isInsideSameAsOutside()) innerApp = app; + + render(gl, geom, Surface.INSIDE, innerApp, true, alpha); + if (((InsideColorComponent) c).isEdgesSameAsInside()) + render(gl, geom, Surface.EDGES, innerApp, false, alpha); + else + render(gl, geom, Surface.EDGES, app, false, alpha); + } + else { + render(gl, geom, Surface.INSIDE, app, true, alpha); + render(gl, geom, Surface.EDGES, app, false, alpha); + } render(gl, geom, Surface.OUTSIDE, app, true, alpha); - render(gl, geom, Surface.EDGES, app, false, alpha); + } protected float[] convertColor(Appearance a, float alpha) { @@ -190,6 +205,19 @@ public class RealisticRenderer extends RocketRenderer { } return ret; } + + protected Appearance getInsideAppearance(RocketComponent c) { + if (c instanceof InsideColorComponent) { + Appearance ret = ((InsideColorComponent)c).getInsideAppearance(); + if (ret == null) { + ret = DefaultAppearance.getDefaultAppearance(c); + } + return ret; + } + else { + return DefaultAppearance.getDefaultAppearance(c); + } + } private int toEdgeMode(Decal.EdgeMode m) { switch (m) {