From 4b614ff11a257aa74b2e3728df2e386ca6df714e Mon Sep 17 00:00:00 2001 From: SiboVG Date: Wed, 15 Jun 2022 17:50:46 +0200 Subject: [PATCH] Fix appearance multi-comp select --- .../appearance/AppearanceBuilder.java | 157 +++++++++++++++--- .../rocketcomponent/RocketComponent.java | 37 ++--- .../gui/configdialog/AppearancePanel.java | 108 ++++++++++-- .../configdialog/ComponentConfigDialog.java | 11 +- .../configdialog/RocketComponentConfig.java | 11 +- 5 files changed, 263 insertions(+), 61 deletions(-) diff --git a/core/src/net/sf/openrocket/appearance/AppearanceBuilder.java b/core/src/net/sf/openrocket/appearance/AppearanceBuilder.java index 230c80ebd..c77fea10c 100644 --- a/core/src/net/sf/openrocket/appearance/AppearanceBuilder.java +++ b/core/src/net/sf/openrocket/appearance/AppearanceBuilder.java @@ -1,10 +1,14 @@ package net.sf.openrocket.appearance; import net.sf.openrocket.appearance.Decal.EdgeMode; +import net.sf.openrocket.rocketcomponent.RocketComponent; import net.sf.openrocket.util.AbstractChangeSource; import net.sf.openrocket.util.Color; import net.sf.openrocket.util.Coordinate; +import java.util.LinkedHashMap; +import java.util.Map; + /** * Use this class to build an immutable Appearance object in a friendly way. Set * the various values one at a time with the setter methods and then call @@ -28,6 +32,13 @@ public class AppearanceBuilder extends AbstractChangeSource { private Decal.EdgeMode edgeMode; private boolean batch; + + /** + * List of appearance builders that will set their appearance properties to the same as the current appearance + */ + private final Map configListeners = new LinkedHashMap<>(); + // If true, appearance change events will not be fired + private boolean bypassAppearanceChangeEvent = false; /** * Default constructor @@ -59,7 +70,9 @@ public class AppearanceBuilder extends AbstractChangeSource { rotation = 0; image = null; edgeMode = EdgeMode.REPEAT; - fireChangeEvent();//shouldn't this fire change event? + if (!bypassAppearanceChangeEvent) { + fireChangeEvent(); + } } /** @@ -88,6 +101,9 @@ public class AppearanceBuilder extends AbstractChangeSource { * @param d The decal */ public void setDecal(Decal d){ + for (AppearanceBuilder listener : configListeners.values()) { + listener.setDecal(d); + } if (d != null) { setOffset(d.getOffset().x, d.getOffset().y); setCenter(d.getCenter().x, d.getCenter().y); @@ -96,7 +112,9 @@ public class AppearanceBuilder extends AbstractChangeSource { setEdgeMode(d.getEdgeMode()); setImage(d.getImage()); } - fireChangeEvent(); + if (!bypassAppearanceChangeEvent) { + fireChangeEvent(); + } } /** @@ -137,9 +155,13 @@ public class AppearanceBuilder extends AbstractChangeSource { * @param paint the new color */ public void setPaint(Color paint) { + for (AppearanceBuilder listener : configListeners.values()) { + listener.setPaint(paint); + } this.paint = paint; - fireChangeEvent(); - + if (!bypassAppearanceChangeEvent) { + fireChangeEvent(); + } } /** @@ -158,8 +180,13 @@ public class AppearanceBuilder extends AbstractChangeSource { * @param shine the new shine for template */ public void setShine(double shine) { + for (AppearanceBuilder listener : configListeners.values()) { + listener.setShine(shine); + } this.shine = shine; - fireChangeEvent(); + if (!bypassAppearanceChangeEvent) { + fireChangeEvent(); + } } /** @@ -179,6 +206,9 @@ public class AppearanceBuilder extends AbstractChangeSource { * @param opacity new opacity value expressed in a percentage, where 0 is fully transparent and 1 is fully opaque */ public void setOpacity(double opacity) { + for (AppearanceBuilder listener : configListeners.values()) { + listener.setOpacity(opacity); + } if (this.paint == null) { return; } @@ -187,7 +217,9 @@ public class AppearanceBuilder extends AbstractChangeSource { opacity = Math.max(0, Math.min(1, opacity)); this.paint.setAlpha((int) (opacity * 255)); - fireChangeEvent(); + if (!bypassAppearanceChangeEvent) { + fireChangeEvent(); + } } /** @@ -207,8 +239,13 @@ public class AppearanceBuilder extends AbstractChangeSource { * @param offsetU the new offset to be used */ public void setOffsetU(double offsetU) { + for (AppearanceBuilder listener : configListeners.values()) { + listener.setOffsetU(offsetU); + } this.offsetU = offsetU; - fireChangeEvent(); + if (!bypassAppearanceChangeEvent) { + fireChangeEvent(); + } } /** @@ -227,8 +264,13 @@ public class AppearanceBuilder extends AbstractChangeSource { * @param offsetV the new offset to be used */ public void setOffsetV(double offsetV) { + for (AppearanceBuilder listener : configListeners.values()) { + listener.setOffsetV(offsetV); + } this.offsetV = offsetV; - fireChangeEvent(); + if (!bypassAppearanceChangeEvent) { + fireChangeEvent(); + } } /** @@ -259,8 +301,13 @@ public class AppearanceBuilder extends AbstractChangeSource { * @param centerU value of axis U for center */ public void setCenterU(double centerU) { + for (AppearanceBuilder listener : configListeners.values()) { + listener.setCenterU(centerU); + } this.centerU = centerU; - fireChangeEvent(); + if (!bypassAppearanceChangeEvent) { + fireChangeEvent(); + } } /** @@ -276,11 +323,16 @@ public class AppearanceBuilder extends AbstractChangeSource { * set a new value for axis V for center in template * fires change event * - * @param centerU value of axis V for center + * @return value of axis V for center */ public void setCenterV(double centerV) { + for (AppearanceBuilder listener : configListeners.values()) { + listener.setCenterV(centerV); + } this.centerV = centerV; - fireChangeEvent(); + if (!bypassAppearanceChangeEvent) { + fireChangeEvent(); + } } /** @@ -311,8 +363,13 @@ public class AppearanceBuilder extends AbstractChangeSource { * @param scaleU new value of scalling in axis U */ public void setScaleU(double scaleU) { + for (AppearanceBuilder listener : configListeners.values()) { + listener.setScaleU(scaleU); + } this.scaleU = scaleU; - fireChangeEvent(); + if (!bypassAppearanceChangeEvent) { + fireChangeEvent(); + } } /** @@ -331,8 +388,13 @@ public class AppearanceBuilder extends AbstractChangeSource { * @param scaleV new value of scalling in axis V */ public void setScaleV(double scaleV) { + for (AppearanceBuilder listener : configListeners.values()) { + listener.setScaleV(scaleV); + } this.scaleV = scaleV; - fireChangeEvent(); + if (!bypassAppearanceChangeEvent) { + fireChangeEvent(); + } } /** @@ -379,7 +441,7 @@ public class AppearanceBuilder extends AbstractChangeSource { * sets a new value of axis Y for scalling in template * fires change event * - * @param scaleX the new value for axis Y + * @param scaleY the new value for axis Y */ public void setScaleY(double scaleY) { setScaleV(1.0 / scaleY); @@ -401,14 +463,19 @@ public class AppearanceBuilder extends AbstractChangeSource { * @param rotation the new value for rotation in template */ public void setRotation(double rotation) { + for (AppearanceBuilder listener : configListeners.values()) { + listener.setRotation(rotation); + } this.rotation = rotation; - fireChangeEvent(); + if (!bypassAppearanceChangeEvent) { + fireChangeEvent(); + } } /** * gets the current image in template * - * @param the current image in template + * @return the current image in template */ public DecalImage getImage() { return image; @@ -421,8 +488,13 @@ public class AppearanceBuilder extends AbstractChangeSource { * @param image the new image to be used as template */ public void setImage(DecalImage image) { + for (AppearanceBuilder listener : configListeners.values()) { + listener.setImage(image); + } this.image = image; - fireChangeEvent(); + if (!bypassAppearanceChangeEvent) { + fireChangeEvent(); + } } /** @@ -441,8 +513,13 @@ public class AppearanceBuilder extends AbstractChangeSource { * @param edgeMode the new edgeMode to be used */ public void setEdgeMode(Decal.EdgeMode edgeMode) { + for (AppearanceBuilder listener : configListeners.values()) { + listener.setEdgeMode(edgeMode); + } this.edgeMode = edgeMode; - fireChangeEvent(); + if (!bypassAppearanceChangeEvent) { + fireChangeEvent(); + } } /** @@ -455,15 +532,55 @@ public class AppearanceBuilder extends AbstractChangeSource { } /** - * function that garantees that chenges event only occurs after all changes are made + * function that guarantees that changes event only occurs after all changes are made * * param r the functor to be executed */ public void batch(Runnable r) { + for (AppearanceBuilder listener : configListeners.values()) { + listener.batch(r); + } batch = true; r.run(); batch = false; - fireChangeEvent(); + if (!bypassAppearanceChangeEvent) { + fireChangeEvent(); + } + } + + /** + * Add a new config listener that will undergo the same configuration changes as this AppearanceBuilder. + * @param component the component to add as a config listener + * @param ab new AppearanceBuilder config listener + * @return true if listener was successfully added, false if not + */ + public boolean addConfigListener(RocketComponent component, AppearanceBuilder ab) { + if (component == null || ab == null) { + return false; + } + configListeners.put(component, ab); + ab.setBypassChangeEvent(true); + return true; + } + + public void removeConfigListener(RocketComponent listener) { + configListeners.remove(listener); + listener.setBypassChangeEvent(false); + } + + public void clearConfigListeners() { + for (AppearanceBuilder listener : configListeners.values()) { + listener.setBypassChangeEvent(false); + } + configListeners.clear(); + } + + public Map getConfigListeners() { + return configListeners; + } + + public void setBypassChangeEvent(boolean newValue) { + this.bypassAppearanceChangeEvent = newValue; } } diff --git a/core/src/net/sf/openrocket/rocketcomponent/RocketComponent.java b/core/src/net/sf/openrocket/rocketcomponent/RocketComponent.java index 19ee93526..4971e1525 100644 --- a/core/src/net/sf/openrocket/rocketcomponent/RocketComponent.java +++ b/core/src/net/sf/openrocket/rocketcomponent/RocketComponent.java @@ -123,7 +123,7 @@ public abstract class RocketComponent implements ChangeSource, Cloneable, Iterab private Appearance appearance = null; // If true, component change events will not be fired - private boolean ignoreComponentChange = false; + private boolean bypassComponentChangeEvent = false; /** @@ -464,10 +464,6 @@ public abstract class RocketComponent implements ChangeSource, Cloneable, Iterab * @param appearance */ public void setAppearance(Appearance appearance) { - for (RocketComponent listener : configListeners) { - listener.setAppearance(appearance); - } - this.appearance = appearance; if (this.appearance != null) { Decal d = this.appearance.getTexture(); @@ -581,9 +577,9 @@ public abstract class RocketComponent implements ChangeSource, Cloneable, Iterab */ public final void setMassOverridden(boolean o) { for (RocketComponent listener : configListeners) { - listener.setIgnoreComponentChange(false); + listener.setBypassChangeEvent(false); listener.setMassOverridden(o); - listener.setIgnoreComponentChange(false); + listener.setBypassChangeEvent(false); } if (massOverridden == o) { @@ -655,9 +651,9 @@ public abstract class RocketComponent implements ChangeSource, Cloneable, Iterab */ public final void setCGOverridden(boolean o) { for (RocketComponent listener : configListeners) { - listener.setIgnoreComponentChange(false); + listener.setBypassChangeEvent(false); listener.setCGOverridden(o); - listener.setIgnoreComponentChange(true); + listener.setBypassChangeEvent(true); } if (cgOverridden == o) { @@ -806,9 +802,9 @@ public abstract class RocketComponent implements ChangeSource, Cloneable, Iterab */ public final void setName(String name) { for (RocketComponent listener : configListeners) { - listener.setIgnoreComponentChange(false); + listener.setBypassChangeEvent(false); listener.setName(name); - listener.setIgnoreComponentChange(true); + listener.setBypassChangeEvent(true); } if (this.name.equals(name)) { @@ -1954,7 +1950,7 @@ public abstract class RocketComponent implements ChangeSource, Cloneable, Iterab */ protected void fireComponentChangeEvent(ComponentChangeEvent e) { checkState(); - if (parent == null || ignoreComponentChange) { + if (parent == null || bypassComponentChangeEvent) { /* Ignore if root invalid. */ return; } @@ -1973,17 +1969,16 @@ public abstract class RocketComponent implements ChangeSource, Cloneable, Iterab fireComponentChangeEvent(new ComponentChangeEvent(this, type)); } - public void setIgnoreComponentChange(boolean newValue) { - this.ignoreComponentChange = newValue; + public void setBypassChangeEvent(boolean newValue) { + this.bypassComponentChangeEvent = newValue; } - public boolean getIgnoreComponentChange() { - return this.ignoreComponentChange; + public boolean getBypassComponentChangeEvent() { + return this.bypassComponentChangeEvent; } /** - * Add a new config listener that will undergo the same configuration changes as this.component. Listener must be - * of the same class as this.component. + * Add a new config listener that will undergo the same configuration changes as this.component. * @param listener new config listener * @return true if listener was successfully added, false if not */ @@ -1992,18 +1987,18 @@ public abstract class RocketComponent implements ChangeSource, Cloneable, Iterab return false; } configListeners.add(listener); - listener.setIgnoreComponentChange(true); + listener.setBypassChangeEvent(true); return true; } public void removeConfigListener(RocketComponent listener) { configListeners.remove(listener); - listener.setIgnoreComponentChange(false); + listener.setBypassChangeEvent(false); } public void clearConfigListeners() { for (RocketComponent listener : configListeners) { - listener.setIgnoreComponentChange(false); + listener.setBypassChangeEvent(false); } configListeners.clear(); } diff --git a/swing/src/net/sf/openrocket/gui/configdialog/AppearancePanel.java b/swing/src/net/sf/openrocket/gui/configdialog/AppearancePanel.java index 352bb55e1..669089883 100644 --- a/swing/src/net/sf/openrocket/gui/configdialog/AppearancePanel.java +++ b/swing/src/net/sf/openrocket/gui/configdialog/AppearancePanel.java @@ -3,6 +3,9 @@ package net.sf.openrocket.gui.configdialog; import java.awt.Color; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; +import java.awt.event.WindowAdapter; +import java.awt.event.WindowEvent; +import java.awt.event.WindowListener; import java.lang.reflect.Method; import java.util.EventObject; @@ -58,7 +61,7 @@ import net.sf.openrocket.util.LineStyle; import net.sf.openrocket.util.StateChangeListener; import net.sf.openrocket.gui.widgets.SelectColorButton; -public class AppearancePanel extends JPanel { +public class AppearancePanel extends JPanel implements WindowListener { private static final long serialVersionUID = 2709187552673202019L; private static final Translator trans = Application.getTranslator(); @@ -107,6 +110,34 @@ public class AppearancePanel extends JPanel { private static final JColorChooser colorChooser = new JColorChooser(); + @Override + public void windowOpened(WindowEvent e) {} + + @Override + public void windowClosing(WindowEvent e) {} + + @Override + public void windowClosed(WindowEvent e) { + if (ab != null) { + ab.clearConfigListeners(); + } + if (insideAb != null) { + insideAb.clearConfigListeners(); + } + } + + @Override + public void windowIconified(WindowEvent e) {} + + @Override + public void windowDeiconified(WindowEvent e) {} + + @Override + public void windowActivated(WindowEvent e) {} + + @Override + public void windowDeactivated(WindowEvent e) {} + private class ColorActionListener implements ActionListener { private final String valueName; private final Object o; @@ -198,23 +229,44 @@ public class AppearancePanel extends JPanel { previousUserSelectedAppearance = c.getAppearance(); if (previousUserSelectedAppearance == null) { - previousUserSelectedAppearance = new AppearanceBuilder() - .getAppearance(); + previousUserSelectedAppearance = new AppearanceBuilder().getAppearance(); ab = new AppearanceBuilder(defaultAppearance); } else { ab = new AppearanceBuilder(previousUserSelectedAppearance); } + for (RocketComponent listener : c.getConfigListeners()) { + Appearance a = listener.getAppearance(); + AppearanceBuilder appearanceBuilder = new AppearanceBuilder(a); + ab.addConfigListener(listener, appearanceBuilder); + } - if (c instanceof InsideColorComponent) { + // Check if all InsideColorComponent + boolean allInsideColor = c instanceof InsideColorComponent; + if (allInsideColor) { + for (RocketComponent listener : c.getConfigListeners()) { + if (!(listener instanceof InsideColorComponent)) { + allInsideColor = false; + break; + } + } + } + + if (allInsideColor) { previousUserSelectedInsideAppearance = ((InsideColorComponent) c).getInsideColorComponentHandler() .getInsideAppearance(); if (previousUserSelectedInsideAppearance == null) { - previousUserSelectedInsideAppearance = new AppearanceBuilder() - .getAppearance(); + previousUserSelectedInsideAppearance = new AppearanceBuilder().getAppearance(); insideAb = new AppearanceBuilder(defaultAppearance); } else { insideAb = new AppearanceBuilder(previousUserSelectedInsideAppearance); } + + for (RocketComponent listener : c.getConfigListeners()) { + Appearance a = ((InsideColorComponent) listener).getInsideColorComponentHandler() + .getInsideAppearance(); + AppearanceBuilder appearanceBuilder = new AppearanceBuilder(a); + insideAb.addConfigListener(listener, appearanceBuilder); + } } net.sf.openrocket.util.Color figureColor = c.getColor(); @@ -317,7 +369,7 @@ public class AppearancePanel extends JPanel { add(new JSeparator(SwingConstants.HORIZONTAL), "span, wrap, growx"); // Display a tabbed panel for choosing the outside and inside appearance, if the object is of type InsideColorComponent - if (c instanceof InsideColorComponent) { + if (allInsideColor) { InsideColorComponentHandler handler = ((InsideColorComponent)c).getInsideColorComponentHandler(); // Get translator keys @@ -464,13 +516,31 @@ public class AppearancePanel extends JPanel { previousUserSelectedInsideAppearance = (builder == null) ? null : builder.getAppearance(); } + + // Set the listeners' appearance to the default appearance + for (RocketComponent listener : builder.getConfigListeners().keySet()) { + builder.getConfigListeners().get(listener).setAppearance(defaultAppearance); + listener.setAppearance(null); + } + + // Set this component's appearance to the default appearance builder.setAppearance(defaultAppearance); c.setAppearance(null); } else { - if (!insideBuilder) + if (!insideBuilder) { + // Set the listeners' appearance to the previous user selected appearance + for (AppearanceBuilder listener : builder.getConfigListeners().values()) { + listener.setAppearance(previousUserSelectedAppearance); + } builder.setAppearance(previousUserSelectedAppearance); - else + } + else { + // Set the listeners' inside appearance to the previous user selected appearance + for (AppearanceBuilder listener : builder.getConfigListeners().values()) { + listener.setAppearance(previousUserSelectedInsideAppearance); + } builder.setAppearance(previousUserSelectedInsideAppearance); + } } } }); @@ -622,10 +692,24 @@ public class AppearancePanel extends JPanel { opacityModel.stateChanged(null); lastOpacity = builder.getOpacity(); } - if (!insideBuilder) + if (!insideBuilder) { + // Set the listeners' outside appearance + for (RocketComponent listener : builder.getConfigListeners().keySet()) { + listener.setAppearance(builder.getConfigListeners().get(listener).getAppearance()); + } + // Set this component's outside appearance c.setAppearance(builder.getAppearance()); - else - ((InsideColorComponent)c).getInsideColorComponentHandler().setInsideAppearance(builder.getAppearance()); + } + else { + // Set the listeners' inside appearance + for (RocketComponent listener : builder.getConfigListeners().keySet()) { + if (!(listener instanceof InsideColorComponent)) continue; + ((InsideColorComponent) listener).getInsideColorComponentHandler() + .setInsideAppearance(builder.getConfigListeners().get(listener).getAppearance()); + } + // Set this component's inside appearance + ((InsideColorComponent) c).getInsideColorComponentHandler().setInsideAppearance(builder.getAppearance()); + } decalModel.refresh(); } }); diff --git a/swing/src/net/sf/openrocket/gui/configdialog/ComponentConfigDialog.java b/swing/src/net/sf/openrocket/gui/configdialog/ComponentConfigDialog.java index 244675b15..8b88185a3 100644 --- a/swing/src/net/sf/openrocket/gui/configdialog/ComponentConfigDialog.java +++ b/swing/src/net/sf/openrocket/gui/configdialog/ComponentConfigDialog.java @@ -101,11 +101,16 @@ public class ComponentConfigDialog extends JDialog implements ComponentChangeLis this.setContentPane(configurator); configurator.updateFields(); - // Set the selected tab - configurator.setSelectedTab(previousSelectedTab); + List listeners = component.getConfigListeners(); + + // Set the default tab to 'Appearance' for a different-type multi-comp dialog (this is the most prominent use case) + if (listeners != null && listeners.size() > 0 && !component.checkAllClassesEqual(listeners)) { + configurator.setSelectedTabIndex(1); + } else { + configurator.setSelectedTab(previousSelectedTab); + } //// configuration - List listeners = component.getConfigListeners(); if (component.checkAllClassesEqual(listeners)) { if (listeners != null && listeners.size() > 0) { setTitle("(" + trans.get("ComponentCfgDlg.MultiComponent") + ") " + diff --git a/swing/src/net/sf/openrocket/gui/configdialog/RocketComponentConfig.java b/swing/src/net/sf/openrocket/gui/configdialog/RocketComponentConfig.java index eaf8965a2..d21013064 100644 --- a/swing/src/net/sf/openrocket/gui/configdialog/RocketComponentConfig.java +++ b/swing/src/net/sf/openrocket/gui/configdialog/RocketComponentConfig.java @@ -133,11 +133,6 @@ public class RocketComponentConfig extends JPanel { tabbedPane.addTab(trans.get("RocketCompCfg.tab.Comment"), null, commentTab(), trans.get("RocketCompCfg.tab.Specifyacomment")); - // Set the default tab to 'Appearance' for a different-type multi-comp dialog (this is the most prominent use case) - if (listeners != null && listeners.size() > 0 && !allSameType) { - tabbedPane.setSelectedIndex(1); - } - addButtons(); updateFields(); @@ -329,6 +324,12 @@ public class RocketComponentConfig extends JPanel { } } + public void setSelectedTabIndex(int index) { + if (tabbedPane != null) { + tabbedPane.setSelectedIndex(index); + } + } + public void setSelectedTab(String tabName) { if (tabbedPane != null) { for (int i = 0; i < tabbedPane.getTabCount(); i++) {