Fix appearance multi-comp select

This commit is contained in:
SiboVG 2022-06-15 17:50:46 +02:00
parent d398d48d55
commit 4b614ff11a
5 changed files with 263 additions and 61 deletions

View File

@ -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
@ -29,6 +33,13 @@ public class AppearanceBuilder extends AbstractChangeSource {
private boolean batch;
/**
* List of appearance builders that will set their appearance properties to the same as the current appearance
*/
private final Map<RocketComponent, AppearanceBuilder> configListeners = new LinkedHashMap<>();
// If true, appearance change events will not be fired
private boolean bypassAppearanceChangeEvent = false;
/**
* Default constructor
* Set the builder to make appearance of null values
@ -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,8 +112,10 @@ public class AppearanceBuilder extends AbstractChangeSource {
setEdgeMode(d.getEdgeMode());
setImage(d.getImage());
}
if (!bypassAppearanceChangeEvent) {
fireChangeEvent();
}
}
/**
* Method creates another object of Appearance
@ -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;
if (!bypassAppearanceChangeEvent) {
fireChangeEvent();
}
}
/**
@ -158,9 +180,14 @@ 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;
if (!bypassAppearanceChangeEvent) {
fireChangeEvent();
}
}
/**
* Returns the opacity of the paint color, expressed in percentages, where 0 is fully transparent and 1 is fully opaque
@ -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,8 +217,10 @@ public class AppearanceBuilder extends AbstractChangeSource {
opacity = Math.max(0, Math.min(1, opacity));
this.paint.setAlpha((int) (opacity * 255));
if (!bypassAppearanceChangeEvent) {
fireChangeEvent();
}
}
/**
* gets the current offset axis U used
@ -207,9 +239,14 @@ 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;
if (!bypassAppearanceChangeEvent) {
fireChangeEvent();
}
}
/**
* gets the current offset axis V used
@ -227,9 +264,14 @@ 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;
if (!bypassAppearanceChangeEvent) {
fireChangeEvent();
}
}
/**
* sets a new offset to be used for template
@ -259,9 +301,14 @@ 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;
if (!bypassAppearanceChangeEvent) {
fireChangeEvent();
}
}
/**
* gets the current center in axis V used in template
@ -276,12 +323,17 @@ 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;
if (!bypassAppearanceChangeEvent) {
fireChangeEvent();
}
}
/**
* sets a new center for template
@ -311,9 +363,14 @@ 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;
if (!bypassAppearanceChangeEvent) {
fireChangeEvent();
}
}
/**
* gets the current scale value of axis V in template
@ -331,9 +388,14 @@ 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;
if (!bypassAppearanceChangeEvent) {
fireChangeEvent();
}
}
/**
* sets a new value of both axis for scaling in the template
@ -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;
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,9 +488,14 @@ 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;
if (!bypassAppearanceChangeEvent) {
fireChangeEvent();
}
}
/**
* gets the current Edge mode in use
@ -441,9 +513,14 @@ 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;
if (!bypassAppearanceChangeEvent) {
fireChangeEvent();
}
}
/**
* only applies change if there is no more changes comming
@ -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;
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<RocketComponent, AppearanceBuilder> getConfigListeners() {
return configListeners;
}
public void setBypassChangeEvent(boolean newValue) {
this.bypassAppearanceChangeEvent = newValue;
}
}

View File

@ -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();
}

View File

@ -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,15 +516,33 @@ 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);
}
}
}
});
materialDefault.setText(trans.get("AppearanceCfg.lbl.Usedefault"));
panel.add(materialDefault, "wrap");
@ -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
}
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();
}
});

View File

@ -101,11 +101,16 @@ public class ComponentConfigDialog extends JDialog implements ComponentChangeLis
this.setContentPane(configurator);
configurator.updateFields();
// Set the selected tab
List<RocketComponent> 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<RocketComponent> listeners = component.getConfigListeners();
if (component.checkAllClassesEqual(listeners)) {
if (listeners != null && listeners.size() > 0) {
setTitle("(" + trans.get("ComponentCfgDlg.MultiComponent") + ") " +

View File

@ -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++) {