diff --git a/core/resources/l10n/messages.properties b/core/resources/l10n/messages.properties index 3e3be7610..e0794ee1d 100644 --- a/core/resources/l10n/messages.properties +++ b/core/resources/l10n/messages.properties @@ -978,6 +978,12 @@ RocketCompCfg.tab.Outside = Outside RocketCompCfg.tab.Inside = Inside RocketCompCfg.tab.RightSide = Right Side RocketCompCfg.tab.LeftSide = Left Side +RocketCompCfg.btn.OK.ttip = Keep changes and close the dialog +RocketCompCfg.btn.Cancel.ttip = Discard changes and close the dialog +RocketCompCfg.CancelOperation.msg.discardChanges = Are you sure you want to discard your changes to this component? +RocketCompCfg.CancelOperation.msg.undoAdd = Are you sure you want to undo adding this component? +RocketCompCfg.CancelOperation.title = Cancel operation +RocketCompCfg.CancelOperation.checkbox.dontAskAgain = Don't ask me again RocketCompCfg.btn.ComponentInfo.ttip = Show/hide informative text about this component. ! ComponentInfo diff --git a/core/src/net/sf/openrocket/rocketcomponent/AxialStage.java b/core/src/net/sf/openrocket/rocketcomponent/AxialStage.java index df2285c37..504969e27 100644 --- a/core/src/net/sf/openrocket/rocketcomponent/AxialStage.java +++ b/core/src/net/sf/openrocket/rocketcomponent/AxialStage.java @@ -241,6 +241,7 @@ public class AxialStage extends ComponentAssembly implements FlightConfigurableC @Override public void clearConfigListeners() { super.clearConfigListeners(); + // StageSeparationConfiguration also has config listeners, so clear them as well StageSeparationConfiguration thisConfig = getSeparationConfiguration(); thisConfig.clearConfigListeners(); } diff --git a/core/src/net/sf/openrocket/rocketcomponent/BodyTube.java b/core/src/net/sf/openrocket/rocketcomponent/BodyTube.java index 212aadd48..40b292044 100644 --- a/core/src/net/sf/openrocket/rocketcomponent/BodyTube.java +++ b/core/src/net/sf/openrocket/rocketcomponent/BodyTube.java @@ -549,6 +549,7 @@ public class BodyTube extends SymmetricComponent implements BoxBounded, MotorMou @Override public void clearConfigListeners() { super.clearConfigListeners(); + // The motor config also has listeners, so clear them as well getDefaultMotorConfig().clearConfigListeners(); } } diff --git a/core/src/net/sf/openrocket/rocketcomponent/InnerTube.java b/core/src/net/sf/openrocket/rocketcomponent/InnerTube.java index 272de11ac..581d8583c 100644 --- a/core/src/net/sf/openrocket/rocketcomponent/InnerTube.java +++ b/core/src/net/sf/openrocket/rocketcomponent/InnerTube.java @@ -482,6 +482,7 @@ public class InnerTube extends ThicknessRingComponent implements AxialPositionab @Override public void clearConfigListeners() { super.clearConfigListeners(); + // The motor config also has listeners, so clear them as well getDefaultMotorConfig().clearConfigListeners(); } diff --git a/core/src/net/sf/openrocket/rocketcomponent/NoseCone.java b/core/src/net/sf/openrocket/rocketcomponent/NoseCone.java index b5a2d6e80..d35f1928d 100644 --- a/core/src/net/sf/openrocket/rocketcomponent/NoseCone.java +++ b/core/src/net/sf/openrocket/rocketcomponent/NoseCone.java @@ -1,14 +1,9 @@ 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 @@ -195,10 +190,17 @@ public class NoseCone extends Transition implements InsideColorComponent { * @param sanityCheck whether to check if the auto radius parameter can be used for the new nose cone orientation */ public void setFlipped(boolean flipped, boolean sanityCheck) { + for (RocketComponent listener : configListeners) { + if (listener instanceof NoseCone) { + ((NoseCone) listener).setFlipped(flipped, sanityCheck); + } + } + if (isFlipped == flipped) { return; } + boolean previousByPass = isBypassComponentChangeEvent(); setBypassChangeEvent(true); if (flipped) { setForeRadius(getAftRadiusNoAutomatic()); @@ -219,7 +221,7 @@ public class NoseCone extends Transition implements InsideColorComponent { resetForeRadius(); } - setBypassChangeEvent(false); + setBypassChangeEvent(previousByPass); isFlipped = flipped; fireComponentChangeEvent(ComponentChangeEvent.BOTH_CHANGE); diff --git a/core/src/net/sf/openrocket/rocketcomponent/RecoveryDevice.java b/core/src/net/sf/openrocket/rocketcomponent/RecoveryDevice.java index 3b0c0bbde..9fc15a8e4 100644 --- a/core/src/net/sf/openrocket/rocketcomponent/RecoveryDevice.java +++ b/core/src/net/sf/openrocket/rocketcomponent/RecoveryDevice.java @@ -177,6 +177,7 @@ public abstract class RecoveryDevice extends MassObject implements FlightConfigu @Override public void clearConfigListeners() { super.clearConfigListeners(); + // The DeploymentConfiguration also has listeners, so clear them as well DeploymentConfiguration thisConfig = getDeploymentConfigurations().getDefault(); thisConfig.clearConfigListeners(); } diff --git a/core/src/net/sf/openrocket/rocketcomponent/Rocket.java b/core/src/net/sf/openrocket/rocketcomponent/Rocket.java index ae6dfe554..f1363f25f 100644 --- a/core/src/net/sf/openrocket/rocketcomponent/Rocket.java +++ b/core/src/net/sf/openrocket/rocketcomponent/Rocket.java @@ -406,37 +406,37 @@ public class Rocket extends ComponentAssembly { * and therefore fires an UNDO_EVENT, masked with all applicable mass/aerodynamic/tree * changes. */ - public void loadFrom(Rocket r) { + public void loadFrom(Rocket source) { // Store list of components to invalidate after event has been fired - List toInvalidate = this.copyFrom(r); + List toInvalidate = this.copyFrom(source); int type = ComponentChangeEvent.UNDO_CHANGE | ComponentChangeEvent.NONFUNCTIONAL_CHANGE; - if (this.massModID != r.massModID) + if (this.massModID != source.massModID) type |= ComponentChangeEvent.MASS_CHANGE; - if (this.aeroModID != r.aeroModID) + if (this.aeroModID != source.aeroModID) type |= ComponentChangeEvent.AERODYNAMIC_CHANGE; // Loading a rocket is always a tree change since the component objects change type |= ComponentChangeEvent.TREE_CHANGE; - this.modID = r.modID; - this.massModID = r.massModID; - this.aeroModID = r.aeroModID; - this.treeModID = r.treeModID; - this.functionalModID = r.functionalModID; - this.refType = r.refType; - this.customReferenceLength = r.customReferenceLength; - this.stageMap = r.stageMap; + this.modID = source.modID; + this.massModID = source.massModID; + this.aeroModID = source.aeroModID; + this.treeModID = source.treeModID; + this.functionalModID = source.functionalModID; + this.refType = source.refType; + this.customReferenceLength = source.customReferenceLength; + this.stageMap = source.stageMap; // these flight configurations need to reference the _this_ Rocket: this.configSet.reset(); this.configSet.setDefault(new FlightConfiguration(this)); - for (FlightConfigurationId key : r.configSet.map.keySet()) { + for (FlightConfigurationId key : source.configSet.map.keySet()) { this.configSet.set(key, new FlightConfiguration(this, key)); } - this.selectedConfiguration = this.configSet.get(r.getSelectedConfiguration().getId()); + this.selectedConfiguration = this.configSet.get(source.getSelectedConfiguration().getId()); - this.perfectFinish = r.perfectFinish; + this.perfectFinish = source.perfectFinish; this.checkComponentStructure(); @@ -453,25 +453,6 @@ public class Rocket extends ComponentAssembly { /////// Implement the ComponentChangeListener lists - /** - * Creates a new EventListenerList for this component. This is necessary when cloning - * the structure. - */ - public void resetListeners() { - // System.out.println("RESETTING LISTENER LIST of Rocket "+this); - listenerList = new HashSet(); - } - - - public void printListeners() { - System.out.println("" + this + " has " + listenerList.size() + " listeners:"); - int i = 0; - for (EventListener l : listenerList) { - System.out.println(" " + (i) + ": " + l); - i++; - } - } - @Override public void addComponentChangeListener(ComponentChangeListener l) { checkState(); @@ -497,10 +478,6 @@ public class Rocket extends ComponentAssembly { try { checkState(); - { // vvvv DEVEL vvvv - //System.err.println("fireEvent@rocket."); - } // ^^^^ DEVEL ^^^^ - // Update modification ID's only for normal (not undo/redo) events if (!cce.isUndoChange()) { modID = UniqueID.next(); diff --git a/core/src/net/sf/openrocket/rocketcomponent/RocketComponent.java b/core/src/net/sf/openrocket/rocketcomponent/RocketComponent.java index df7e618c6..e3f1cdcd4 100644 --- a/core/src/net/sf/openrocket/rocketcomponent/RocketComponent.java +++ b/core/src/net/sf/openrocket/rocketcomponent/RocketComponent.java @@ -448,6 +448,7 @@ public abstract class RocketComponent implements ChangeSource, Cloneable, Iterab } // Make sure the config listeners aren't cloned clone.configListeners = new LinkedList<>(); + clone.bypassComponentChangeEvent = false; return clone; } @@ -603,7 +604,7 @@ public abstract class RocketComponent implements ChangeSource, Cloneable, Iterab for (RocketComponent listener : configListeners) { listener.setBypassChangeEvent(false); listener.setMassOverridden(o); - listener.setBypassChangeEvent(false); + listener.setBypassChangeEvent(true); } if (massOverridden == o) { @@ -2277,7 +2278,11 @@ public abstract class RocketComponent implements ChangeSource, Cloneable, Iterab this.bypassComponentChangeEvent = newValue; } - public boolean getBypassComponentChangeEvent() { + /** + * Returns whether the current component if ignoring ComponentChangeEvents. + * @return true if the component is ignoring ComponentChangeEvents. + */ + public boolean isBypassComponentChangeEvent() { return this.bypassComponentChangeEvent; } @@ -2287,7 +2292,18 @@ public abstract class RocketComponent implements ChangeSource, Cloneable, Iterab * @return true if listener was successfully added, false if not */ public boolean addConfigListener(RocketComponent listener) { - if (listener == null || configListeners.contains(listener) || listener == this) { + if (isBypassComponentChangeEvent()) { + // This is a precaution. If you are multi-comp editing and the current component is bypassing events, + // the editing will be REALLY weird, see GitHub issue #1956. + throw new IllegalStateException("Cannot add config listener while bypassing events"); + } + if (listener == null) { + return false; + } + if (listener.getConfigListeners().size() > 0) { + throw new IllegalArgumentException("Listener already has config listeners"); + } + if (configListeners.contains(listener) || listener == this) { return false; } configListeners.add(listener); @@ -2555,6 +2571,7 @@ public abstract class RocketComponent implements ChangeSource, Cloneable, Iterab this.displayOrder_side = src.displayOrder_side; this.displayOrder_back = src.displayOrder_back; this.configListeners = new LinkedList<>(); + this.bypassComponentChangeEvent = false; if (this instanceof InsideColorComponent && src instanceof InsideColorComponent) { InsideColorComponentHandler icch = new InsideColorComponentHandler(this); icch.copyFrom(((InsideColorComponent) src).getInsideColorComponentHandler()); diff --git a/swing/src/net/sf/openrocket/gui/configdialog/AxialStageConfig.java b/swing/src/net/sf/openrocket/gui/configdialog/AxialStageConfig.java index 611e67920..56d89d8fc 100644 --- a/swing/src/net/sf/openrocket/gui/configdialog/AxialStageConfig.java +++ b/swing/src/net/sf/openrocket/gui/configdialog/AxialStageConfig.java @@ -16,7 +16,6 @@ import net.sf.openrocket.gui.components.StyledLabel; import net.sf.openrocket.gui.components.StyledLabel.Style; import net.sf.openrocket.l10n.Translator; import net.sf.openrocket.rocketcomponent.AxialStage; -import net.sf.openrocket.rocketcomponent.FlightConfiguration; import net.sf.openrocket.rocketcomponent.RocketComponent; import net.sf.openrocket.rocketcomponent.StageSeparationConfiguration; import net.sf.openrocket.rocketcomponent.StageSeparationConfiguration.SeparationEvent; @@ -38,7 +37,9 @@ public class AxialStageConfig extends ComponentAssemblyConfig { } // Apply the custom focus travel policy to this config dialog - order.add(closeButton); // Make sure the close button is the last component + //// Make sure the cancel & ok button is the last component + order.add(cancelButton); + order.add(okButton); CustomFocusTraversalPolicy policy = new CustomFocusTraversalPolicy(order); parent.setFocusTraversalPolicy(policy); } diff --git a/swing/src/net/sf/openrocket/gui/configdialog/BodyTubeConfig.java b/swing/src/net/sf/openrocket/gui/configdialog/BodyTubeConfig.java index 58835dec6..128bde727 100644 --- a/swing/src/net/sf/openrocket/gui/configdialog/BodyTubeConfig.java +++ b/swing/src/net/sf/openrocket/gui/configdialog/BodyTubeConfig.java @@ -121,7 +121,9 @@ public class BodyTubeConfig extends RocketComponentConfig { trans.get("BodyTubecfg.tab.Motormountconf"), 1); // Apply the custom focus travel policy to this config dialog - order.add(closeButton); // Make sure the close button is the last component + //// Make sure the cancel & ok button is the last component + order.add(cancelButton); + order.add(okButton); CustomFocusTraversalPolicy policy = new CustomFocusTraversalPolicy(order); parent.setFocusTraversalPolicy(policy); } diff --git a/swing/src/net/sf/openrocket/gui/configdialog/BulkheadConfig.java b/swing/src/net/sf/openrocket/gui/configdialog/BulkheadConfig.java index 1659a721c..d1bdb5e63 100644 --- a/swing/src/net/sf/openrocket/gui/configdialog/BulkheadConfig.java +++ b/swing/src/net/sf/openrocket/gui/configdialog/BulkheadConfig.java @@ -29,7 +29,9 @@ public class BulkheadConfig extends RingComponentConfig { tabbedPane.setSelectedIndex(0); // Apply the custom focus travel policy to this panel - order.add(closeButton); // Make sure the close button is the last component + //// Make sure the cancel & ok button is the last component + order.add(cancelButton); + order.add(okButton); CustomFocusTraversalPolicy policy = new CustomFocusTraversalPolicy(order); parent.setFocusTraversalPolicy(policy); } diff --git a/swing/src/net/sf/openrocket/gui/configdialog/CenteringRingConfig.java b/swing/src/net/sf/openrocket/gui/configdialog/CenteringRingConfig.java index 548bf4b86..5ac0bcc75 100644 --- a/swing/src/net/sf/openrocket/gui/configdialog/CenteringRingConfig.java +++ b/swing/src/net/sf/openrocket/gui/configdialog/CenteringRingConfig.java @@ -31,7 +31,9 @@ public class CenteringRingConfig extends RingComponentConfig { tabbedPane.setSelectedIndex(0); // Apply the custom focus travel policy to this panel - order.add(closeButton); // Make sure the close button is the last component + //// Make sure the cancel & ok button is the last component + order.add(cancelButton); + order.add(okButton); CustomFocusTraversalPolicy policy = new CustomFocusTraversalPolicy(order); parent.setFocusTraversalPolicy(policy); } diff --git a/swing/src/net/sf/openrocket/gui/configdialog/ComponentConfigDialog.java b/swing/src/net/sf/openrocket/gui/configdialog/ComponentConfigDialog.java index e71f0f1c4..c3c953faf 100644 --- a/swing/src/net/sf/openrocket/gui/configdialog/ComponentConfigDialog.java +++ b/swing/src/net/sf/openrocket/gui/configdialog/ComponentConfigDialog.java @@ -1,15 +1,11 @@ package net.sf.openrocket.gui.configdialog; -import java.awt.GraphicsDevice; -import java.awt.GraphicsEnvironment; -import java.awt.Rectangle; import java.awt.Window; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; -import java.util.Arrays; import java.util.List; import javax.swing.JDialog; @@ -48,16 +44,19 @@ public class ComponentConfigDialog extends JDialog implements ComponentChangeLis private OpenRocketDocument document = null; protected RocketComponent component = null; private RocketComponentConfig configurator = null; - protected static boolean clearConfigListeners = true; + private boolean isModified = false; + private final boolean isNewComponent; + public static boolean clearConfigListeners = true; private static String previousSelectedTab = null; // Name of the previous selected tab private final Window parent; private static final Translator trans = Application.getTranslator(); - private ComponentConfigDialog(Window parent, OpenRocketDocument document, RocketComponent component) { + private ComponentConfigDialog(Window parent, OpenRocketDocument document, RocketComponent component, boolean isNewComponent) { super(parent); this.parent = parent; + this.isNewComponent = isNewComponent; setComponent(document, component); @@ -104,8 +103,10 @@ public class ComponentConfigDialog extends JDialog implements ComponentChangeLis this.document = document; this.component = component; this.document.getRocket().addComponentChangeListener(this); + this.isModified = false; configurator = getDialogContents(); + configurator.setNewComponent(isNewComponent); this.setContentPane(configurator); configurator.updateFields(); @@ -172,10 +173,8 @@ public class ComponentConfigDialog extends JDialog implements ComponentChangeLis @Override public void componentChanged(ComponentChangeEvent e) { if (e.isTreeChange() || e.isUndoChange()) { - // Hide dialog in case of tree or undo change disposeDialog(); - } else { /* * TODO: HIGH: The line below has caused a NullPointerException (without null check) @@ -183,8 +182,13 @@ public class ComponentConfigDialog extends JDialog implements ComponentChangeLis * root cause should be analyzed. * [Openrocket-bugs] 2009-12-12 19:23:22 Automatic bug report for OpenRocket 0.9.5 */ - if (configurator != null) + if (configurator != null) { configurator.updateFields(); + } + if (!this.isModified) { + setTitle("*" + getTitle()); + this.isModified = true; + } } } @@ -261,7 +265,7 @@ public class ComponentConfigDialog extends JDialog implements ComponentChangeLis previousSelectedTab = null; } - dialog = new ComponentConfigDialog(parent, document, component); + dialog = new ComponentConfigDialog(parent, document, component, isNewComponent); dialog.setVisible(true); if (parent instanceof BasicFrame && BasicFrame.getStartupFrame() == parent) { WindowLocationUtil.moveIfOutsideOfParentMonitor(dialog, parent); @@ -335,6 +339,13 @@ public class ComponentConfigDialog extends JDialog implements ComponentChangeLis return (dialog != null) && (dialog.isVisible()); } + /** + * Returns true if the current component has been modified or not. + */ + public boolean isModified() { + return isModified; + } + public int getSelectedTabIndex() { return configurator.getSelectedTabIndex(); } diff --git a/swing/src/net/sf/openrocket/gui/configdialog/EllipticalFinSetConfig.java b/swing/src/net/sf/openrocket/gui/configdialog/EllipticalFinSetConfig.java index add84adfb..6d765b36f 100644 --- a/swing/src/net/sf/openrocket/gui/configdialog/EllipticalFinSetConfig.java +++ b/swing/src/net/sf/openrocket/gui/configdialog/EllipticalFinSetConfig.java @@ -201,7 +201,9 @@ public class EllipticalFinSetConfig extends FinSetConfig { tabbedPane.setSelectedIndex(0); // Apply the custom focus travel policy to this config dialog - order.add(closeButton); // Make sure the close button is the last component + //// Make sure the cancel & ok button is the last component + order.add(cancelButton); + order.add(okButton); CustomFocusTraversalPolicy policy = new CustomFocusTraversalPolicy(order); parent.setFocusTraversalPolicy(policy); } diff --git a/swing/src/net/sf/openrocket/gui/configdialog/FreeformFinSetConfig.java b/swing/src/net/sf/openrocket/gui/configdialog/FreeformFinSetConfig.java index 5bae5b08f..d83c8470d 100644 --- a/swing/src/net/sf/openrocket/gui/configdialog/FreeformFinSetConfig.java +++ b/swing/src/net/sf/openrocket/gui/configdialog/FreeformFinSetConfig.java @@ -107,7 +107,9 @@ public class FreeformFinSetConfig extends FinSetConfig { addFinSetButtons(); // Apply the custom focus travel policy to this panel - order.add(closeButton); // Make sure the close button is the last component + //// Make sure the cancel & ok button is the last component + order.add(cancelButton); + order.add(okButton); CustomFocusTraversalPolicy policy = new CustomFocusTraversalPolicy(order); parent.setFocusTraversalPolicy(policy); } diff --git a/swing/src/net/sf/openrocket/gui/configdialog/InnerTubeConfig.java b/swing/src/net/sf/openrocket/gui/configdialog/InnerTubeConfig.java index 45c984dd5..669b816e7 100644 --- a/swing/src/net/sf/openrocket/gui/configdialog/InnerTubeConfig.java +++ b/swing/src/net/sf/openrocket/gui/configdialog/InnerTubeConfig.java @@ -207,7 +207,9 @@ public class InnerTubeConfig extends RocketComponentConfig { tabbedPane.setSelectedIndex(0); // Apply the custom focus travel policy to this config dialog - order.add(closeButton); // Make sure the close button is the last component + //// Make sure the cancel & ok button is the last component + order.add(cancelButton); + order.add(okButton); CustomFocusTraversalPolicy policy = new CustomFocusTraversalPolicy(order); parent.setFocusTraversalPolicy(policy); } diff --git a/swing/src/net/sf/openrocket/gui/configdialog/LaunchLugConfig.java b/swing/src/net/sf/openrocket/gui/configdialog/LaunchLugConfig.java index 37d93a0fd..22f062436 100644 --- a/swing/src/net/sf/openrocket/gui/configdialog/LaunchLugConfig.java +++ b/swing/src/net/sf/openrocket/gui/configdialog/LaunchLugConfig.java @@ -162,7 +162,9 @@ public class LaunchLugConfig extends RocketComponentConfig { tabbedPane.setSelectedIndex(0); // Apply the custom focus travel policy to this config dialog - order.add(closeButton); // Make sure the close button is the last component + //// Make sure the cancel & ok button is the last component + order.add(cancelButton); + order.add(okButton); CustomFocusTraversalPolicy policy = new CustomFocusTraversalPolicy(order); parent.setFocusTraversalPolicy(policy); } diff --git a/swing/src/net/sf/openrocket/gui/configdialog/MassComponentConfig.java b/swing/src/net/sf/openrocket/gui/configdialog/MassComponentConfig.java index 5bb30f602..3934ee940 100644 --- a/swing/src/net/sf/openrocket/gui/configdialog/MassComponentConfig.java +++ b/swing/src/net/sf/openrocket/gui/configdialog/MassComponentConfig.java @@ -166,7 +166,9 @@ public class MassComponentConfig extends RocketComponentConfig { tabbedPane.setSelectedIndex(0); // Apply the custom focus travel policy to this config dialog - order.add(closeButton); // Make sure the close button is the last component + //// Make sure the cancel & ok button is the last component + order.add(cancelButton); + order.add(okButton); CustomFocusTraversalPolicy policy = new CustomFocusTraversalPolicy(order); parent.setFocusTraversalPolicy(policy); } diff --git a/swing/src/net/sf/openrocket/gui/configdialog/NoseConeConfig.java b/swing/src/net/sf/openrocket/gui/configdialog/NoseConeConfig.java index 7829ebf69..828cc96a1 100644 --- a/swing/src/net/sf/openrocket/gui/configdialog/NoseConeConfig.java +++ b/swing/src/net/sf/openrocket/gui/configdialog/NoseConeConfig.java @@ -188,7 +188,9 @@ public class NoseConeConfig extends RocketComponentConfig { tabbedPane.setSelectedIndex(0); // Apply the custom focus travel policy to this config dialog - order.add(closeButton); // Make sure the close button is the last component + //// Make sure the cancel & ok button is the last component + order.add(cancelButton); + order.add(okButton); CustomFocusTraversalPolicy policy = new CustomFocusTraversalPolicy(order); parent.setFocusTraversalPolicy(policy); } diff --git a/swing/src/net/sf/openrocket/gui/configdialog/ParachuteConfig.java b/swing/src/net/sf/openrocket/gui/configdialog/ParachuteConfig.java index 74f9f85e6..62259f074 100644 --- a/swing/src/net/sf/openrocket/gui/configdialog/ParachuteConfig.java +++ b/swing/src/net/sf/openrocket/gui/configdialog/ParachuteConfig.java @@ -290,7 +290,9 @@ public class ParachuteConfig extends RecoveryDeviceConfig { tabbedPane.setSelectedIndex(0); // Apply the custom focus travel policy to this config dialog - order.add(closeButton); // Make sure the close button is the last component + //// Make sure the cancel & ok button is the last component + order.add(cancelButton); + order.add(okButton); CustomFocusTraversalPolicy policy = new CustomFocusTraversalPolicy(order); parent.setFocusTraversalPolicy(policy); } diff --git a/swing/src/net/sf/openrocket/gui/configdialog/RailButtonConfig.java b/swing/src/net/sf/openrocket/gui/configdialog/RailButtonConfig.java index da9c6af8c..1f9b25eef 100644 --- a/swing/src/net/sf/openrocket/gui/configdialog/RailButtonConfig.java +++ b/swing/src/net/sf/openrocket/gui/configdialog/RailButtonConfig.java @@ -35,7 +35,9 @@ public class RailButtonConfig extends RocketComponentConfig { tabbedPane.setSelectedIndex(0); // Apply the custom focus travel policy to this panel - order.add(closeButton); // Make sure the close button is the last component + //// Make sure the cancel & ok button is the last component + order.add(cancelButton); + order.add(okButton); CustomFocusTraversalPolicy policy = new CustomFocusTraversalPolicy(order); parent.setFocusTraversalPolicy(policy); } diff --git a/swing/src/net/sf/openrocket/gui/configdialog/RocketComponentConfig.java b/swing/src/net/sf/openrocket/gui/configdialog/RocketComponentConfig.java index b3ae5c6ab..697d9b8fe 100644 --- a/swing/src/net/sf/openrocket/gui/configdialog/RocketComponentConfig.java +++ b/swing/src/net/sf/openrocket/gui/configdialog/RocketComponentConfig.java @@ -22,6 +22,7 @@ import javax.swing.JCheckBox; import javax.swing.JComboBox; import javax.swing.JDialog; import javax.swing.JLabel; +import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.JSpinner; @@ -56,18 +57,24 @@ import net.sf.openrocket.preset.ComponentPreset; import net.sf.openrocket.rocketcomponent.*; import net.sf.openrocket.rocketcomponent.position.AxialMethod; import net.sf.openrocket.startup.Application; +import net.sf.openrocket.startup.Preferences; import net.sf.openrocket.unit.UnitGroup; import net.sf.openrocket.util.Invalidatable; public class RocketComponentConfig extends JPanel { private static final long serialVersionUID = -2925484062132243982L; + // Preference key + private static final String IGNORE_DISCARD_EDITING_WARNING = "IgnoreDiscardEditingWarning"; + private static final Translator trans = Application.getTranslator(); + private static final Preferences preferences = Application.getPreferences(); protected final OpenRocketDocument document; protected final RocketComponent component; protected final JTabbedPane tabbedPane; - protected final JDialog parent; + protected final ComponentConfigDialog parent; + protected boolean isNewComponent = false; // Checks whether this config dialog is editing an existing component, or a new one private final List invalidatables = new ArrayList(); protected final List order = new ArrayList<>(); // Component traversal order @@ -84,7 +91,8 @@ public class RocketComponentConfig extends JPanel { private IconToggleButton infoBtn; private JPanel buttonPanel; - protected JButton closeButton; + protected JButton okButton; + protected JButton cancelButton; private AppearancePanel appearancePanel = null; private JLabel infoLabel; @@ -98,7 +106,11 @@ public class RocketComponentConfig extends JPanel { this.document = document; this.component = component; - this.parent = parent; + if (parent instanceof ComponentConfigDialog) { + this.parent = (ComponentConfigDialog) parent; + } else { + this.parent = null; + } // Check the listeners for the same type and massive status allSameType = true; @@ -257,21 +269,75 @@ public class RocketComponentConfig extends JPanel { for (JButton b : buttons) { buttonPanel.add(b, "right, gap para"); } - - //// Close button - this.closeButton = new SelectColorButton(trans.get("dlg.but.close")); - closeButton.addActionListener(new ActionListener() { + + //// Cancel button + this.cancelButton = new SelectColorButton(trans.get("dlg.but.cancel")); + this.cancelButton.setToolTipText(trans.get("RocketCompCfg.btn.Cancel.ttip")); + cancelButton.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent arg0) { + if (preferences.getBoolean(IGNORE_DISCARD_EDITING_WARNING, false)) { + ComponentConfigDialog.clearConfigListeners = false; // Undo action => config listeners of new component will be cleared + ComponentConfigDialog.disposeDialog(); + document.undo(); + return; + } + if (!isNewComponent && parent != null && !parent.isModified()) { + ComponentConfigDialog.disposeDialog(); + return; + } + + // Yes/No dialog: Are you sure you want to discard your changes? + JPanel msg = createCancelOperationContent(); + int resultYesNo = JOptionPane.showConfirmDialog(RocketComponentConfig.this, msg, + trans.get("RocketCompCfg.CancelOperation.title"), JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE); + if (resultYesNo == JOptionPane.YES_OPTION) { + ComponentConfigDialog.clearConfigListeners = false; // Undo action => config listeners of new component will be cleared + ComponentConfigDialog.disposeDialog(); + document.undo(); + } + } + }); + buttonPanel.add(cancelButton, "split 2, right, gapleft 30lp"); + + //// Ok button + this.okButton = new SelectColorButton(trans.get("dlg.but.ok")); + this.okButton.setToolTipText(trans.get("RocketCompCfg.btn.OK.ttip")); + okButton.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent arg0) { ComponentConfigDialog.disposeDialog(); } }); - buttonPanel.add(closeButton, "right, gap 30lp"); - + buttonPanel.add(okButton); + updateFields(); this.add(buttonPanel, "newline, spanx, growx"); } + + private JPanel createCancelOperationContent() { + JPanel panel = new JPanel(new MigLayout()); + String msg = isNewComponent ? trans.get("RocketCompCfg.CancelOperation.msg.undoAdd") : + trans.get("RocketCompCfg.CancelOperation.msg.discardChanges"); + JLabel msgLabel = new JLabel(msg); + JCheckBox dontAskAgain = new JCheckBox(trans.get("RocketCompCfg.CancelOperation.checkbox.dontAskAgain")); + dontAskAgain.setSelected(false); + dontAskAgain.addItemListener(new ItemListener() { + @Override + public void itemStateChanged(ItemEvent e) { + if (e.getStateChange() == ItemEvent.SELECTED) { + preferences.putBoolean(IGNORE_DISCARD_EDITING_WARNING, true); + } + // Unselected state should be impossible + } + }); + + panel.add(msgLabel, "left, wrap"); + panel.add(dontAskAgain, "left, gaptop para"); + + return panel; + } /** @@ -854,6 +920,14 @@ public class RocketComponentConfig extends JPanel { panel.add(sub); } + /** + * Sets whether this dialog is editing a new component (true), or an existing one (false). + * @param newComponent true if this dialog is editing a new component. + */ + public void setNewComponent(boolean newComponent) { + isNewComponent = newComponent; + } + /* * Private inner class to handle events in componentNameField. */ diff --git a/swing/src/net/sf/openrocket/gui/configdialog/ShockCordConfig.java b/swing/src/net/sf/openrocket/gui/configdialog/ShockCordConfig.java index 40222576f..752d7995e 100644 --- a/swing/src/net/sf/openrocket/gui/configdialog/ShockCordConfig.java +++ b/swing/src/net/sf/openrocket/gui/configdialog/ShockCordConfig.java @@ -142,7 +142,9 @@ public class ShockCordConfig extends RocketComponentConfig { tabbedPane.setSelectedIndex(0); // Apply the custom focus travel policy to this config dialog - order.add(closeButton); // Make sure the close button is the last component + //// Make sure the cancel & ok button is the last component + order.add(cancelButton); + order.add(okButton); CustomFocusTraversalPolicy policy = new CustomFocusTraversalPolicy(order); parent.setFocusTraversalPolicy(policy); } diff --git a/swing/src/net/sf/openrocket/gui/configdialog/SleeveConfig.java b/swing/src/net/sf/openrocket/gui/configdialog/SleeveConfig.java index 4545660c9..d2261dfd6 100644 --- a/swing/src/net/sf/openrocket/gui/configdialog/SleeveConfig.java +++ b/swing/src/net/sf/openrocket/gui/configdialog/SleeveConfig.java @@ -31,7 +31,9 @@ public class SleeveConfig extends RingComponentConfig { tabbedPane.setSelectedIndex(0); // Apply the custom focus travel policy to this panel - order.add(closeButton); // Make sure the close button is the last component + //// Make sure the cancel & ok button is the last component + order.add(cancelButton); + order.add(okButton); CustomFocusTraversalPolicy policy = new CustomFocusTraversalPolicy(order); parent.setFocusTraversalPolicy(policy); } diff --git a/swing/src/net/sf/openrocket/gui/configdialog/StreamerConfig.java b/swing/src/net/sf/openrocket/gui/configdialog/StreamerConfig.java index e05cc0f6f..d3519c75d 100644 --- a/swing/src/net/sf/openrocket/gui/configdialog/StreamerConfig.java +++ b/swing/src/net/sf/openrocket/gui/configdialog/StreamerConfig.java @@ -277,7 +277,9 @@ public class StreamerConfig extends RecoveryDeviceConfig { tabbedPane.setSelectedIndex(0); // Apply the custom focus travel policy to this config dialog - order.add(closeButton); // Make sure the close button is the last component + //// Make sure the cancel & ok button is the last component + order.add(cancelButton); + order.add(okButton); CustomFocusTraversalPolicy policy = new CustomFocusTraversalPolicy(order); parent.setFocusTraversalPolicy(policy); } diff --git a/swing/src/net/sf/openrocket/gui/configdialog/ThicknessRingComponentConfig.java b/swing/src/net/sf/openrocket/gui/configdialog/ThicknessRingComponentConfig.java index b204435a3..66ce8703e 100644 --- a/swing/src/net/sf/openrocket/gui/configdialog/ThicknessRingComponentConfig.java +++ b/swing/src/net/sf/openrocket/gui/configdialog/ThicknessRingComponentConfig.java @@ -33,7 +33,9 @@ public class ThicknessRingComponentConfig extends RingComponentConfig { tabbedPane.setSelectedIndex(0); // Apply the custom focus travel policy to this panel - order.add(closeButton); // Make sure the close button is the last component + //// Make sure the cancel & ok button is the last component + order.add(cancelButton); + order.add(okButton); CustomFocusTraversalPolicy policy = new CustomFocusTraversalPolicy(order); parent.setFocusTraversalPolicy(policy); } diff --git a/swing/src/net/sf/openrocket/gui/configdialog/TransitionConfig.java b/swing/src/net/sf/openrocket/gui/configdialog/TransitionConfig.java index b94fae163..f9050d396 100644 --- a/swing/src/net/sf/openrocket/gui/configdialog/TransitionConfig.java +++ b/swing/src/net/sf/openrocket/gui/configdialog/TransitionConfig.java @@ -24,7 +24,6 @@ import net.sf.openrocket.gui.components.UnitSelector; import net.sf.openrocket.l10n.Translator; import net.sf.openrocket.material.Material; import net.sf.openrocket.rocketcomponent.RocketComponent; -import net.sf.openrocket.rocketcomponent.SymmetricComponent; import net.sf.openrocket.rocketcomponent.Transition; import net.sf.openrocket.startup.Application; import net.sf.openrocket.unit.UnitGroup; @@ -206,7 +205,9 @@ public class TransitionConfig extends RocketComponentConfig { tabbedPane.setSelectedIndex(0); // Apply the custom focus travel policy to this config dialog - order.add(closeButton); // Make sure the close button is the last component + //// Make sure the cancel & ok button is the last component + order.add(cancelButton); + order.add(okButton); CustomFocusTraversalPolicy policy = new CustomFocusTraversalPolicy(order); parent.setFocusTraversalPolicy(policy); } diff --git a/swing/src/net/sf/openrocket/gui/configdialog/TrapezoidFinSetConfig.java b/swing/src/net/sf/openrocket/gui/configdialog/TrapezoidFinSetConfig.java index df54e7f2f..0744aa169 100644 --- a/swing/src/net/sf/openrocket/gui/configdialog/TrapezoidFinSetConfig.java +++ b/swing/src/net/sf/openrocket/gui/configdialog/TrapezoidFinSetConfig.java @@ -252,7 +252,9 @@ public class TrapezoidFinSetConfig extends FinSetConfig { addFinSetButtons(); // Apply the custom focus travel policy to this config dialog - order.add(closeButton); // Make sure the close button is the last component + //// Make sure the cancel & ok button is the last component + order.add(cancelButton); + order.add(okButton); CustomFocusTraversalPolicy policy = new CustomFocusTraversalPolicy(order); parent.setFocusTraversalPolicy(policy); } diff --git a/swing/src/net/sf/openrocket/gui/configdialog/TubeFinSetConfig.java b/swing/src/net/sf/openrocket/gui/configdialog/TubeFinSetConfig.java index 9ce17a3a9..e0ea77134 100644 --- a/swing/src/net/sf/openrocket/gui/configdialog/TubeFinSetConfig.java +++ b/swing/src/net/sf/openrocket/gui/configdialog/TubeFinSetConfig.java @@ -179,7 +179,9 @@ public class TubeFinSetConfig extends RocketComponentConfig { tabbedPane.setSelectedIndex(0); // Apply the custom focus travel policy to this config dialog - order.add(closeButton); // Make sure the close button is the last component + //// Make sure the cancel & ok button is the last component + order.add(cancelButton); + order.add(okButton); CustomFocusTraversalPolicy policy = new CustomFocusTraversalPolicy(order); parent.setFocusTraversalPolicy(policy); } diff --git a/swing/src/net/sf/openrocket/gui/main/DesignPanel.java b/swing/src/net/sf/openrocket/gui/main/DesignPanel.java index dcb300261..b30a605ce 100644 --- a/swing/src/net/sf/openrocket/gui/main/DesignPanel.java +++ b/swing/src/net/sf/openrocket/gui/main/DesignPanel.java @@ -105,6 +105,7 @@ public class DesignPanel extends JSplitPane { // Double-click if ((e.getButton() == MouseEvent.BUTTON1) && (e.getClickCount() == 2) && !ComponentConfigDialog.isDialogVisible()) { RocketComponent component = (RocketComponent) selPath.getLastPathComponent(); + component.clearConfigListeners(); // Multi-component edit if shift/meta key is pressed if ((e.isShiftDown() || e.isMetaDown()) && tree.getSelectionPaths() != null) { diff --git a/swing/src/net/sf/openrocket/gui/main/RocketActions.java b/swing/src/net/sf/openrocket/gui/main/RocketActions.java index c8c93a0f2..497f30bcb 100644 --- a/swing/src/net/sf/openrocket/gui/main/RocketActions.java +++ b/swing/src/net/sf/openrocket/gui/main/RocketActions.java @@ -984,6 +984,7 @@ public class RocketActions { ComponentConfigDialog.disposeDialog(); RocketComponent component = components.get(0); + component.clearConfigListeners(); if (components.size() > 1) { for (int i = 1; i < components.size(); i++) { RocketComponent listener = components.get(i); diff --git a/swing/src/net/sf/openrocket/gui/scalefigure/RocketPanel.java b/swing/src/net/sf/openrocket/gui/scalefigure/RocketPanel.java index 386aa4de6..aa7ef052e 100644 --- a/swing/src/net/sf/openrocket/gui/scalefigure/RocketPanel.java +++ b/swing/src/net/sf/openrocket/gui/scalefigure/RocketPanel.java @@ -636,6 +636,7 @@ public class RocketPanel extends JPanel implements TreeSelectionListener, Change if (event.isShiftDown() || event.isMetaDown()) { List paths = new ArrayList<>(Arrays.asList(selectionModel.getSelectionPaths())); RocketComponent component = selectedComponents.get(selectedComponents.size() - 1); + component.clearConfigListeners(); // Make sure the clicked component is selected for (RocketComponent c : clicked) {