diff --git a/core/resources/l10n/messages.properties b/core/resources/l10n/messages.properties index d9183914e..2e3bbdc1c 100644 --- a/core/resources/l10n/messages.properties +++ b/core/resources/l10n/messages.properties @@ -127,6 +127,7 @@ FileHelper.RASAERO_DESIGN_FILTER = RASAero designs (*.CDX1) FileHelper.OPEN_ROCKET_COMPONENT_FILTER = OpenRocket presets (*.orc) FileHelper.PNG_FILTER = PNG image (*.png) FileHelper.IMAGES = Image files +FileHelper.XML_FILTER = XML files (*.xml) ! About Dialog @@ -296,8 +297,12 @@ pref.dlg.tab.Launch = Launch pref.dlg.tab.Miscellaneousoptions = Miscellaneous options pref.dlg.lbl.RASAeroWarning = Show warning when saving in RASAero format pref.dlg.lbl.RockSimWarning = Show warning when saving in RockSim format -pref.dlg.but.clearCachedPreferences = Reset all preferences -pref.dlg.but.clearCachedPreferences.ttip = Reset all the preferences, including cached preferences (UI settings, recent files, etc.) +pref.dlg.but.resetAllPreferences = Reset all preferences +pref.dlg.but.resetAllPreferences.ttip = Reset all the preferences, including cached preferences (UI settings, recent files, etc.) +pref.dlg.but.exportPreferences = Export preferences +pref.dlg.but.exportPreferences.ttip = Export all your OpenRocket preferences to an external file +pref.dlg.but.importPreferences = Import preferences +pref.dlg.but.importPreferences.ttip = Import new OpenRocket preferences from an external file pref.dlg.clearCachedPreferences.title = Reset preferences? pref.dlg.clearCachedPreferences.message = Are you sure you want to reset all your preferences? @@ -379,6 +384,18 @@ PreferencesDialog.lbl.languageEffect = The language will change the next time yo generalprefs.lbl.language = Interface language generalprefs.languages.default = System default generalprefs.lbl.languageEffect = The language will change the next time you start OpenRocket. +generalprefs.ImportWarning.title = Reload OpenRocket +generalprefs.ImportWarning.msg = You may need to restart OpenRocket for some of the changes to take effect. + +PreferencesExporter.chooser.title = Export the Preferences File + +PreferencesImporter.chooser.title = Import a Preferences File + +PreferencesOptionPanel.title = Export settings +PreferencesOptionPanel.checkbox.userDirectories = Export user directories +PreferencesOptionPanel.checkbox.userDirectories.ttip = If unchecked, user directories (possibly sensitive information) will not be exported. +PreferencesOptionPanel.checkbox.windowInfo = Export window information (position, size\u2026) +PreferencesOptionPanel.checkbox.windowInfo.ttip = If unchecked, window information (position, size\u2026) will not be exported. ! Welcome dialog welcome.dlg.title = Welcome to OpenRocket @@ -2352,11 +2369,11 @@ IgnitionSelectionDialog.opt.default = Change all configurations using the defaul IgnitionSelectionDialog.opt.override = Override for the {0} flight configuration only DeploymentSelectionDialog.opt.title = Which flight configurations are affected: -DeploymentSelectionDialog.opt.default = Change all configuration using the default deployment event +DeploymentSelectionDialog.opt.default = Change all configurations using the default deployment event DeploymentSelectionDialog.opt.override = Override for the {0} flight configuration only SeparationSelectionDialog.opt.title = Which flight configurations are affected: -SeparationSelectionDialog.opt.default = Change all configuration using the default separation event +SeparationSelectionDialog.opt.default = Change all configurations using the default separation event SeparationSelectionDialog.opt.override = Override for the {0} flight configuration only MotorConfigurationPanel.description = Select the motors and motor ignition events of the selected flight configuration.
Motor mounts: Select which components function as motor mounts.
Motor configurations: Select the motor and ignition event for each motor mount. diff --git a/core/resources/l10n/messages_ar.properties b/core/resources/l10n/messages_ar.properties index 5db732751..96b45e6ab 100644 --- a/core/resources/l10n/messages_ar.properties +++ b/core/resources/l10n/messages_ar.properties @@ -276,8 +276,8 @@ pref.dlg.tab.Simulation = محاكاة pref.dlg.tab.Launch = إطلاق pref.dlg.tab.Miscellaneousoptions = الخيارات المتنوعة pref.dlg.lbl.RockSimWarning = إظهار تحذير عند الحفظ بتنسيق روكسيم -pref.dlg.but.clearCachedPreferences = إعادة تعيين كل التفضيلات -pref.dlg.but.clearCachedPreferences.ttip = (إعدادات واجهة المستخدم والملفات الحديثة وما إلى ذلك)إعادة تعيين جميع التفضيلات ، بما في ذلك التفضيلات المخزنة مؤقتًا +pref.dlg.but.resetAllPreferences = إعادة تعيين كل التفضيلات +pref.dlg.but.resetAllPreferences.ttip = (إعدادات واجهة المستخدم والملفات الحديثة وما إلى ذلك)إعادة تعيين جميع التفضيلات ، بما في ذلك التفضيلات المخزنة مؤقتًا pref.dlg.clearCachedPreferences.title = هل تريد إعادة تعيين التفضيلات؟ pref.dlg.clearCachedPreferences.message = هل أنت متأكد أنك تريد إعادة تعيين كل تفضيلاتك؟ diff --git a/core/resources/l10n/messages_nl.properties b/core/resources/l10n/messages_nl.properties index 580477fbf..8fb96b539 100644 --- a/core/resources/l10n/messages_nl.properties +++ b/core/resources/l10n/messages_nl.properties @@ -278,8 +278,8 @@ pref.dlg.tab.Launch = Lanceer pref.dlg.tab.Miscellaneousoptions = Diverse opties pref.dlg.lbl.RASAeroWarning = Toon waarschuwingen bij opslaan in RASAero-formaat pref.dlg.lbl.RockSimWarning = Toon waarschuwingen bij opslaan in RockSim-formaat -pref.dlg.but.clearCachedPreferences = Alle voorkeuren opnieuw instellen -pref.dlg.but.clearCachedPreferences.ttip = Alle voorkeuren opnieuw instellen, inclusief voorkeuren in de cache (UI-instellingen, recente bestanden, enz.) +pref.dlg.but.resetAllPreferences = Alle voorkeuren opnieuw instellen +pref.dlg.but.resetAllPreferences.ttip = Alle voorkeuren opnieuw instellen, inclusief voorkeuren in de cache (UI-instellingen, recente bestanden, enz.) pref.dlg.clearCachedPreferences.title = Voorkeuren opnieuw instellen? pref.dlg.clearCachedPreferences.message = Bent u zeker dat u al uw voorkeuren opnieuw wilt instellen? diff --git a/core/src/net/sf/openrocket/file/motor/RASPMotorLoader.java b/core/src/net/sf/openrocket/file/motor/RASPMotorLoader.java index 21aba82e5..5fc375f3c 100644 --- a/core/src/net/sf/openrocket/file/motor/RASPMotorLoader.java +++ b/core/src/net/sf/openrocket/file/motor/RASPMotorLoader.java @@ -99,7 +99,8 @@ public class RASPMotorLoader extends AbstractMotorLoader { // desig diam len delays prop.w tot.w manufacturer pieces = split(line); if (pieces.length != 7) { - throw new IOException("Illegal file format."); + throw new IOException("Illegal file format. Motor header line must contain 7 fields:
" + + "  designation diameter length delays propellantWeight totalWeight manufacturer"); } designation = pieces[0]; @@ -145,7 +146,8 @@ public class RASPMotorLoader extends AbstractMotorLoader { thrust.add(Double.parseDouble(buf[1])); } else { - throw new IOException("Illegal file format."); + throw new IOException("Illegal file format.
" + + "Data should only have 2 entries: a time and thrust value."); } } @@ -163,7 +165,8 @@ public class RASPMotorLoader extends AbstractMotorLoader { } catch (NumberFormatException e) { - throw new IOException("Illegal file format."); + throw new IOException("Illegal file format. Could not convert value to a number.Verify that each number is correctly formatted."); } diff --git a/core/src/net/sf/openrocket/file/openrocket/importt/DocumentConfig.java b/core/src/net/sf/openrocket/file/openrocket/importt/DocumentConfig.java index 73b1f0b16..82428f119 100644 --- a/core/src/net/sf/openrocket/file/openrocket/importt/DocumentConfig.java +++ b/core/src/net/sf/openrocket/file/openrocket/importt/DocumentConfig.java @@ -389,7 +389,7 @@ class DocumentConfig { // MassObject setters.put("MassObject:packedlength", new DoubleSetter( - Reflection.findMethod(MassObject.class, "setLengthNoAuto", double.class))); + Reflection.findMethod(MassObject.class, "setLength", double.class))); setters.put("MassObject:packedradius", new DoubleSetter( Reflection.findMethod(MassObject.class, "setRadius", double.class), "auto", " ", diff --git a/core/src/net/sf/openrocket/file/openrocket/savers/BodyTubeSaver.java b/core/src/net/sf/openrocket/file/openrocket/savers/BodyTubeSaver.java index d6aa0c85c..e4d7fb45d 100644 --- a/core/src/net/sf/openrocket/file/openrocket/savers/BodyTubeSaver.java +++ b/core/src/net/sf/openrocket/file/openrocket/savers/BodyTubeSaver.java @@ -23,7 +23,7 @@ public class BodyTubeSaver extends SymmetricComponentSaver { net.sf.openrocket.rocketcomponent.BodyTube tube = (net.sf.openrocket.rocketcomponent.BodyTube) c; if (tube.isOuterRadiusAutomatic()) { - elements.add("auto " + tube.getOuterRadiusNoAutomatic() + ""); + elements.add("auto " + tube.getOuterRadius() + ""); } else elements.add("" + tube.getOuterRadius() + ""); diff --git a/core/src/net/sf/openrocket/file/openrocket/savers/MassObjectSaver.java b/core/src/net/sf/openrocket/file/openrocket/savers/MassObjectSaver.java index 9842dc26e..3cbf57605 100644 --- a/core/src/net/sf/openrocket/file/openrocket/savers/MassObjectSaver.java +++ b/core/src/net/sf/openrocket/file/openrocket/savers/MassObjectSaver.java @@ -13,11 +13,11 @@ public class MassObjectSaver extends InternalComponentSaver { MassObject mass = (MassObject) c; - elements.add("" + mass.getLengthNoAuto() + ""); + elements.add("" + mass.getLength() + ""); if (mass.isRadiusAutomatic()) { - elements.add("auto " + mass.getRadiusNoAuto() + ""); + elements.add("auto " + mass.getRadius() + ""); } else { - elements.add("" + mass.getRadiusNoAuto() + ""); + elements.add("" + mass.getRadius() + ""); } elements.add("" + mass.getRadialPosition() + ""); elements.add("" + (mass.getRadialDirection() * 180.0 / Math.PI) diff --git a/core/src/net/sf/openrocket/file/openrocket/savers/NoseConeSaver.java b/core/src/net/sf/openrocket/file/openrocket/savers/NoseConeSaver.java index e5c24e571..9c39cca72 100644 --- a/core/src/net/sf/openrocket/file/openrocket/savers/NoseConeSaver.java +++ b/core/src/net/sf/openrocket/file/openrocket/savers/NoseConeSaver.java @@ -27,7 +27,7 @@ public class NoseConeSaver extends TransitionSaver { super.addParams(c, elements); if (noseCone.isBaseRadiusAutomatic()) - elements.add("auto " + noseCone.getBaseRadiusNoAutomatic() + ""); + elements.add("auto " + noseCone.getBaseRadius() + ""); else elements.add("" + noseCone.getBaseRadius() + ""); diff --git a/core/src/net/sf/openrocket/file/openrocket/savers/TransitionSaver.java b/core/src/net/sf/openrocket/file/openrocket/savers/TransitionSaver.java index b3b2ae8d6..331a95303 100644 --- a/core/src/net/sf/openrocket/file/openrocket/savers/TransitionSaver.java +++ b/core/src/net/sf/openrocket/file/openrocket/savers/TransitionSaver.java @@ -46,12 +46,12 @@ public class TransitionSaver extends SymmetricComponentSaver { } if (trans.isForeRadiusAutomatic()) - elements.add("auto " + trans.getForeRadiusNoAutomatic() + ""); + elements.add("auto " + trans.getForeRadius() + ""); else elements.add("" + trans.getForeRadius() + ""); if (trans.isAftRadiusAutomatic()) - elements.add("auto " + trans.getAftRadiusNoAutomatic() + ""); + elements.add("auto " + trans.getAftRadius() + ""); else elements.add("" + trans.getAftRadius() + ""); diff --git a/core/src/net/sf/openrocket/models/atmosphere/InterpolatingAtmosphericModel.java b/core/src/net/sf/openrocket/models/atmosphere/InterpolatingAtmosphericModel.java index 780adc300..3b34fb9aa 100644 --- a/core/src/net/sf/openrocket/models/atmosphere/InterpolatingAtmosphericModel.java +++ b/core/src/net/sf/openrocket/models/atmosphere/InterpolatingAtmosphericModel.java @@ -36,6 +36,10 @@ public abstract class InterpolatingAtmosphericModel implements AtmosphericModel int n = (int) (altitude / DELTA); double d = (altitude - n * DELTA) / DELTA; AtmosphericConditions c = new AtmosphericConditions(); + // TODO: LOW: levels[n] returned null in some cases, see GitHub issue #2180 for more information + if (levels[n] == null) { + computeLayers(); + } c.setTemperature(levels[n].getTemperature() * (1 - d) + levels[n + 1].getTemperature() * d); c.setPressure(levels[n].getPressure() * (1 - d) + levels[n + 1].getPressure() * d); diff --git a/core/src/net/sf/openrocket/rocketcomponent/BodyTube.java b/core/src/net/sf/openrocket/rocketcomponent/BodyTube.java index 0064356a9..5b42fdcda 100644 --- a/core/src/net/sf/openrocket/rocketcomponent/BodyTube.java +++ b/core/src/net/sf/openrocket/rocketcomponent/BodyTube.java @@ -75,35 +75,35 @@ public class BodyTube extends SymmetricComponent implements BoxBounded, MotorMou @Override public double getOuterRadius() { if (autoRadius) { - // Return auto radius from front or rear - double r = -1; - SymmetricComponent c = this.getPreviousSymmetricComponent(); - // Don't use the radius of a component who already has its auto diameter enabled - if (c != null && !c.usesNextCompAutomatic()) { - r = c.getFrontAutoRadius(); - refComp = c; - } - if (r < 0) { - c = this.getNextSymmetricComponent(); - // Don't use the radius of a component who already has its auto diameter enabled - if (c != null && !c.usesPreviousCompAutomatic()) { - r = c.getRearAutoRadius(); - refComp = c; - } - } - if (r < 0) - r = DEFAULT_RADIUS; - return r; + outerRadius = getAutoOuterRadius(); } return outerRadius; } /** - * Return the outer radius that was manually entered, so not the value that the component received from automatic - * outer radius. + * Returns the automatic outer radius, taken from the previous/next component. Returns the default radius if there + * is no previous/next component. */ - public double getOuterRadiusNoAutomatic() { - return outerRadius; + private double getAutoOuterRadius() { + // Return auto radius from front or rear + double r = -1; + SymmetricComponent c = this.getPreviousSymmetricComponent(); + // Don't use the radius of a component who already has its auto diameter enabled + if (c != null && !c.usesNextCompAutomatic()) { + r = c.getFrontAutoRadius(); + refComp = c; + } + if (r < 0) { + c = this.getNextSymmetricComponent(); + // Don't use the radius of a component who already has its auto diameter enabled + if (c != null && !c.usesPreviousCompAutomatic()) { + r = c.getRearAutoRadius(); + refComp = c; + } + } + if (r < 0) + r = DEFAULT_RADIUS; + return r; } /** diff --git a/core/src/net/sf/openrocket/rocketcomponent/MassObject.java b/core/src/net/sf/openrocket/rocketcomponent/MassObject.java index 3d9fa5e6f..d370e3b8e 100644 --- a/core/src/net/sf/openrocket/rocketcomponent/MassObject.java +++ b/core/src/net/sf/openrocket/rocketcomponent/MassObject.java @@ -23,6 +23,7 @@ public abstract class MassObject extends InternalComponent { protected double radius; private boolean autoRadius = false; + private double volume; // (Packed) volume of the object private double radialPosition; private double radialDirection; @@ -40,6 +41,7 @@ public abstract class MassObject extends InternalComponent { this.length = length; this.radius = radius; + updateVolume(radius); this.setAxialMethod( AxialMethod.TOP); this.setAxialOffset(0.0); @@ -50,40 +52,18 @@ public abstract class MassObject extends InternalComponent { return false; } + private void updateVolume(double radius) { + volume = Math.pow(radius, 2) * length; // Math.PI left out, not needed + } + @Override public double getLength() { - if (this.autoRadius) { - // Calculate the volume using the non auto radius and the non auto length, and transform that back - // to the auto radius situation to get the auto radius length (the volume in both situations is the same). - double volume = Math.pow(this.radius, 2) * this.length; // Math.PI left out, not needed - return volume / Math.pow(getRadius(), 2); + if (autoRadius) { + length = getAutoLength(); } return length; } - public double getLengthNoAuto() { - return length; - } - - /** - * Set the length, ignoring the auto radius setting. - * @param length new length - */ - public void setLengthNoAuto(double length) { - for (RocketComponent listener : configListeners) { - if (listener instanceof MassObject) { - ((MassObject) listener).setLengthNoAuto(length); - } - } - - length = Math.max(length, 0); - if (MathUtil.equals(this.length, length)) { - return; - } - this.length = length; - fireComponentChangeEvent(ComponentChangeEvent.MASS_CHANGE); - } - public void setLength(double length) { for (RocketComponent listener : configListeners) { if (listener instanceof MassObject) { @@ -92,45 +72,54 @@ public abstract class MassObject extends InternalComponent { } length = Math.max(length, 0); - if (this.autoRadius) { - // Calculate the volume using the auto radius and the new "auto" length, and transform that back - // to the non auto radius situation to set this.length (the volume in both situations is the same). - double volume = Math.pow(getRadius(), 2) * length; // Math.PI left out, not needed - double newLength = volume / Math.pow(this.radius, 2); - if (MathUtil.equals(this.length, newLength)) - return; - this.length = newLength; - } else { - if (MathUtil.equals(this.length, length)) { - return; - } - this.length = length; + + if (MathUtil.equals(this.length, length)) { + return; } + this.length = length; + updateVolume(autoRadius ? getAutoRadius() : radius); + fireComponentChangeEvent(ComponentChangeEvent.MASS_CHANGE); } + + /** + * Calculate the length from the current volume. + */ + private double getAutoLength() { + // Calculate the volume using the auto radius and the new "auto" length, and transform that back + // to the non auto radius situation to set this.length (the volume in both situations is the same). + return volume / Math.pow(radius, 2); + } public double getRadius() { if (autoRadius) { - if (parent == null) { - return radius; - } - if (parent instanceof NoseCone) { - return ((NoseCone) parent).getAftRadius(); - } else if (parent instanceof Transition) { - double foreRadius = ((Transition) parent).getForeRadius(); - double aftRadius = ((Transition) parent).getAftRadius(); - return (Math.max(foreRadius, aftRadius)); - } else if (parent instanceof BodyComponent) { - return ((BodyComponent) parent).getInnerRadius(); - } else if (parent instanceof RingComponent) { - return ((RingComponent) parent).getInnerRadius(); - } + radius = getAutoRadius(); + length = getAutoLength(); } return radius; } - public double getRadiusNoAuto() { + /** + * Return the radius determined by its parent component. + * @return the radius determined by its parent component + */ + public double getAutoRadius() { + if (parent == null) { + return radius; + } + if (parent instanceof NoseCone) { + return ((NoseCone) parent).getAftRadius(); + } else if (parent instanceof Transition) { + double foreRadius = ((Transition) parent).getForeRadius(); + double aftRadius = ((Transition) parent).getAftRadius(); + return (Math.max(foreRadius, aftRadius)); + } else if (parent instanceof BodyComponent) { + return ((BodyComponent) parent).getInnerRadius(); + } else if (parent instanceof RingComponent) { + return ((RingComponent) parent).getInnerRadius(); + } + return radius; } @@ -148,6 +137,7 @@ public abstract class MassObject extends InternalComponent { this.autoRadius = false; this.radius = radius; + updateVolume(radius); fireComponentChangeEvent(ComponentChangeEvent.MASS_CHANGE); } @@ -167,10 +157,6 @@ public abstract class MassObject extends InternalComponent { autoRadius = auto; - // Set the length - double volume = (Math.PI * Math.pow(getRadius(), 2) * length); - length = volume / (Math.PI * Math.pow(getRadius(), 2)); - fireComponentChangeEvent(ComponentChangeEvent.BOTH_CHANGE); } diff --git a/core/src/net/sf/openrocket/rocketcomponent/NoseCone.java b/core/src/net/sf/openrocket/rocketcomponent/NoseCone.java index 5e174bc24..ea76f4be1 100644 --- a/core/src/net/sf/openrocket/rocketcomponent/NoseCone.java +++ b/core/src/net/sf/openrocket/rocketcomponent/NoseCone.java @@ -52,14 +52,6 @@ public class NoseCone extends Transition implements InsideColorComponent { return isFlipped ? getForeRadius() : getAftRadius(); } - /** - * Returns the raw base radius of the nose cone (independent of whether the nose cone is flipped or not). - * This method should be used over {@link #getAftRadiusNoAutomatic()} because it works for both normal and flipped nose cones. - */ - public double getBaseRadiusNoAutomatic() { - return isFlipped ? getForeRadiusNoAutomatic() : getAftRadiusNoAutomatic(); - } - /** * Sets the base radius of the nose cone (independent of whether the nose cone is flipped or not). * This method should be used over {@link #setAftRadius(double)} because it works for both normal and flipped nose cones. @@ -203,7 +195,7 @@ public class NoseCone extends Transition implements InsideColorComponent { boolean previousByPass = isBypassComponentChangeEvent(); setBypassChangeEvent(true); if (flipped) { - setForeRadius(getAftRadiusNoAutomatic()); + setForeRadius(getAftRadius()); setForeRadiusAutomatic(isAftRadiusAutomatic(), sanityCheck); setForeShoulderLength(getAftShoulderLength()); setForeShoulderRadius(getAftShoulderRadius()); @@ -212,7 +204,7 @@ public class NoseCone extends Transition implements InsideColorComponent { resetAftRadius(); } else { - setAftRadius(getForeRadiusNoAutomatic()); + setAftRadius(getForeRadius()); setAftRadiusAutomatic(isForeRadiusAutomatic(), sanityCheck); setAftShoulderLength(getForeShoulderLength()); setAftShoulderRadius(getForeShoulderRadius()); diff --git a/core/src/net/sf/openrocket/rocketcomponent/RocketComponent.java b/core/src/net/sf/openrocket/rocketcomponent/RocketComponent.java index 313022179..01b5b6820 100644 --- a/core/src/net/sf/openrocket/rocketcomponent/RocketComponent.java +++ b/core/src/net/sf/openrocket/rocketcomponent/RocketComponent.java @@ -1338,8 +1338,19 @@ public abstract class RocketComponent implements ChangeSource, Cloneable, Iterab if( 0 == thisIndex ) { this.position = this.position.setX(0.); }else if( 0 < thisIndex ) { - RocketComponent referenceComponent = parent.getChild( thisIndex - 1 ); - + int idx = thisIndex - 1; + RocketComponent referenceComponent = parent.getChild(idx); + while (!getRocket().getSelectedConfiguration().isComponentActive(referenceComponent) && idx > 0) { + idx--; + referenceComponent = parent.getChild(idx); + } + + // If previous components are inactive, set this as the new reference point + if (!getRocket().getSelectedConfiguration().isComponentActive(referenceComponent)) { + this.position = this.position.setX(0.); + return; + } + double refLength = referenceComponent.getLength(); double refRelX = referenceComponent.getPosition().x; @@ -1668,6 +1679,22 @@ public abstract class RocketComponent implements ChangeSource, Cloneable, Iterab /////////// Children handling /////////// + /** + * Adds a child to the rocket component tree. The component is added to the end + * of the component's child list. This is a helper method that calls + * {@link #addChild(RocketComponent,int)}. + * + * @param component The component to add. + * @param trackStage If component is a stage, this check will decide whether the rocket should track that stage (add it to the stageList etc.) + * @throws IllegalArgumentException if the component is already part of some + * component tree. + * @see #addChild(RocketComponent,int) + */ + public final void addChild(RocketComponent component, boolean trackStage) { + checkState(); + addChild(component, children.size(), trackStage); + } + /** * Adds a child to the rocket component tree. The component is added to the end * of the component's child list. This is a helper method that calls @@ -1679,10 +1706,9 @@ public abstract class RocketComponent implements ChangeSource, Cloneable, Iterab * @see #addChild(RocketComponent,int) */ public final void addChild(RocketComponent component) { - checkState(); - addChild(component, children.size()); + addChild(component, true); } - + /** * Adds a child to the rocket component tree. The component is added to * the given position of the component's child list. @@ -1692,28 +1718,29 @@ public abstract class RocketComponent implements ChangeSource, Cloneable, Iterab * * @param component The component to add. * @param index Position to add component to. + * @param trackStage If component is a stage, this check will decide whether the rocket should track that stage (add it to the stageList etc.) * @throws IllegalArgumentException If the component is already part of * some component tree. */ - public void addChild(RocketComponent component, int index) { + public void addChild(RocketComponent component, int index, boolean trackStage) { checkState(); - + if (component.parent != null) { throw new IllegalArgumentException("component " + component.getComponentName() + " is already in a tree"); } - + // Ensure that the no loops are created in component tree [A -> X -> Y -> B, B.addChild(A)] if (this.getRoot().equals(component)) { throw new IllegalStateException("Component " + component.getComponentName() + " is a parent of " + this.getComponentName() + ", attempting to create cycle in tree."); } - + if (!isCompatible(component)) { throw new IllegalStateException("Component: " + component.getComponentName() + " not currently compatible with component: " + getComponentName()); } - + children.add(index, component); component.parent = this; if (this.massOverridden && this.overrideSubcomponentsMass) { @@ -1746,18 +1773,48 @@ public abstract class RocketComponent implements ChangeSource, Cloneable, Iterab child.CDOverriddenBy = component.CDOverriddenBy; } } - - if (component instanceof AxialStage) { + + if (trackStage && (component instanceof AxialStage)) { AxialStage nStage = (AxialStage) component; this.getRocket().trackStage(nStage); } - + this.checkComponentStructure(); component.checkComponentStructure(); - + fireAddRemoveEvent(component); } + /** + * Adds a child to the rocket component tree. The component is added to + * the given position of the component's child list. + *

+ * This method may be overridden to enforce more strict component addition rules. + * The tests should be performed first and then this method called. + * + * @param component The component to add. + * @param index Position to add component to. + * @throws IllegalArgumentException If the component is already part of + * some component tree. + */ + public void addChild(RocketComponent component, int index) { + addChild(component, index, true); + } + + /** + * Removes a child from the rocket component tree. + * (redirect to the removed-by-component + * + * @param n remove the n'th child. + * @param trackStage If component is a stage, this check will decide whether the rocket should track that stage (remove it to the stageList etc.) + * @throws IndexOutOfBoundsException if n is out of bounds + */ + public final void removeChild(int n, boolean trackStage) { + checkState(); + RocketComponent component = this.getChild(n); + this.removeChild(component, trackStage); + } + /** * Removes a child from the rocket component tree. * (redirect to the removed-by-component @@ -1766,9 +1823,7 @@ public abstract class RocketComponent implements ChangeSource, Cloneable, Iterab * @throws IndexOutOfBoundsException if n is out of bounds */ public final void removeChild(int n) { - checkState(); - RocketComponent component = this.getChild(n); - this.removeChild(component); + removeChild(n, true); } /** @@ -1776,9 +1831,10 @@ public abstract class RocketComponent implements ChangeSource, Cloneable, Iterab * is not present as a child. * * @param component the component to remove + * @param trackStage If component is a stage, this check will decide whether the rocket should track that stage (remove it to the stageList etc.) * @return whether the component was a child */ - public final boolean removeChild(RocketComponent component) { + public final boolean removeChild(RocketComponent component, boolean trackStage) { checkState(); component.checkComponentStructure(); @@ -1800,15 +1856,17 @@ public abstract class RocketComponent implements ChangeSource, Cloneable, Iterab c.CDOverriddenBy = null; } } - - if (component instanceof AxialStage) { - AxialStage stage = (AxialStage) component; - this.getRocket().forgetStage(stage); - } - // Remove sub-stages of the removed component - for (AxialStage stage : component.getSubStages()) { - this.getRocket().forgetStage(stage); + if (trackStage) { + if (component instanceof AxialStage) { + AxialStage stage = (AxialStage) component; + this.getRocket().forgetStage(stage); + } + + // Remove sub-stages of the removed component + for (AxialStage stage : component.getSubStages()) { + this.getRocket().forgetStage(stage); + } } this.checkComponentStructure(); @@ -1821,6 +1879,17 @@ public abstract class RocketComponent implements ChangeSource, Cloneable, Iterab } return false; } + + /** + * Removes a child from the rocket component tree. Does nothing if the component + * is not present as a child. + * + * @param component the component to remove + * @return whether the component was a child + */ + public final boolean removeChild(RocketComponent component) { + return removeChild(component, true); + } diff --git a/core/src/net/sf/openrocket/rocketcomponent/Transition.java b/core/src/net/sf/openrocket/rocketcomponent/Transition.java index 5fb9822eb..a518844bf 100644 --- a/core/src/net/sf/openrocket/rocketcomponent/Transition.java +++ b/core/src/net/sf/openrocket/rocketcomponent/Transition.java @@ -77,7 +77,7 @@ public class Transition extends SymmetricComponent implements InsideColorCompone @Override public double getForeRadius() { if (isForeRadiusAutomatic()) { - return getAutoForeRadius(); + foreRadius = getAutoForeRadius(); } return foreRadius; } @@ -95,14 +95,6 @@ public class Transition extends SymmetricComponent implements InsideColorCompone } } - /** - * Return the fore radius that was manually entered, so not the value that the component received from automatic - * fore radius. - */ - public double getForeRadiusNoAutomatic() { - return foreRadius; - } - /** * Set the new fore radius, with option to clamp the thickness to the new radius if it's too large. * @param radius new radius @@ -174,7 +166,7 @@ public class Transition extends SymmetricComponent implements InsideColorCompone @Override public double getAftRadius() { if (isAftRadiusAutomatic()) { - return getAutoAftRadius(); + aftRadius = getAutoAftRadius(); } return aftRadius; } @@ -192,14 +184,6 @@ public class Transition extends SymmetricComponent implements InsideColorCompone } } - /** - * Return the aft radius that was manually entered, so not the value that the component received from automatic - * zft radius. - */ - public double getAftRadiusNoAutomatic() { - return aftRadius; - } - /** * Set the new aft radius, with option to clamp the thickness to the new radius if it's too large. * @param radius new radius diff --git a/core/src/net/sf/openrocket/startup/Preferences.java b/core/src/net/sf/openrocket/startup/Preferences.java index ade03131e..0830222ae 100644 --- a/core/src/net/sf/openrocket/startup/Preferences.java +++ b/core/src/net/sf/openrocket/startup/Preferences.java @@ -53,7 +53,8 @@ public abstract class Preferences implements ChangeSource { public static final String EXPORT_EVENT_COMMENTS = "ExportEventComments"; public static final String EXPORT_COMMENT_CHARACTER = "ExportCommentCharacter"; public static final String USER_LOCAL = "locale"; - + public static final String DEFAULT_DIRECTORY = "defaultDirectory"; + public static final String PLOT_SHOW_POINTS = "ShowPlotPoints"; private static final String IGNORE_WELCOME = "IgnoreWelcome"; @@ -73,20 +74,22 @@ public abstract class Preferences implements ChangeSource { public static final String MATCH_AFT_DIAMETER = "MatchAftDiameter"; // Node names - public static final String PREFERRED_THRUST_CURVE_MOTOR_NODE = "preferredThrustCurveMotors"; - private static final String AUTO_OPEN_LAST_DESIGN = "AUTO_OPEN_LAST_DESIGN"; - private static final String OPEN_LEFTMOST_DESIGN_TAB = "OPEN_LEFTMOST_DESIGN_TAB"; + public static final String PREFERRED_THRUST_CURVE_MOTOR_NODE = "PreferredThrustCurveMotors"; + private static final String AUTO_OPEN_LAST_DESIGN = "AutoOpenLastDesign"; + private static final String OPEN_LEFTMOST_DESIGN_TAB = "OpenLeftmostDesignTab"; private static final String SHOW_DISCARD_CONFIRMATION = "IgnoreDiscardEditingWarning"; private static final String SHOW_DISCARD_SIMULATION_CONFIRMATION = "IgnoreDiscardSimulationEditingWarning"; public static final String MARKER_STYLE_ICON = "MARKER_STYLE_ICON"; private static final String SHOW_MARKERS = "SHOW_MARKERS"; private static final String SHOW_RASAERO_FORMAT_WARNING = "SHOW_RASAERO_FORMAT_WARNING"; private static final String SHOW_ROCKSIM_FORMAT_WARNING = "SHOW_ROCKSIM_FORMAT_WARNING"; + private static final String EXPORT_USER_DIRECTORIES = "ExportUserDirectories"; + private static final String EXPORT_WINDOW_INFORMATION = "ExportWindowInformation"; //Preferences related to 3D graphics - public static final String OPENGL_ENABLED = "OpenGL_Is_Enabled"; - public static final String OPENGL_ENABLE_AA = "OpenGL_Antialiasing_Is_Enabled"; - public static final String OPENGL_USE_FBO = "OpenGL_Use_FBO"; + public static final String OPENGL_ENABLED = "OpenGLIsEnabled"; + public static final String OPENGL_ENABLE_AA = "OpenGLAntialiasingIsEnabled"; + public static final String OPENGL_USE_FBO = "OpenGLUseFBO"; public static final String ROCKET_INFO_FONT_SIZE = "RocketInfoFontSize"; @@ -241,7 +244,23 @@ public abstract class Preferences implements ChangeSource { public final void setShowRockSimFormatWarning(boolean check) { this.putBoolean(SHOW_ROCKSIM_FORMAT_WARNING, check); } - + + public final boolean getExportUserDirectories() { + return this.getBoolean(EXPORT_USER_DIRECTORIES, false); + } + + public final void setExportUserDirectories(boolean check) { + this.putBoolean(EXPORT_USER_DIRECTORIES, check); + } + + public final boolean getExportWindowInformation() { + return this.getBoolean(EXPORT_WINDOW_INFORMATION, false); + } + + public final void setExportWindowInformation(boolean check) { + this.putBoolean(EXPORT_WINDOW_INFORMATION, check); + } + public final double getDefaultMach() { return Application.getPreferences().getChoice(Preferences.DEFAULT_MACH_NUMBER, 0.9, 0.3); } diff --git a/core/test/net/sf/openrocket/masscalc/MassCalculatorTest.java b/core/test/net/sf/openrocket/masscalc/MassCalculatorTest.java index dd27bcc7c..c7a01a770 100644 --- a/core/test/net/sf/openrocket/masscalc/MassCalculatorTest.java +++ b/core/test/net/sf/openrocket/masscalc/MassCalculatorTest.java @@ -998,7 +998,7 @@ public class MassCalculatorTest extends BaseTestCase { double expTotalMass = overrideMass; assertEquals(" Booster Launch Mass is incorrect: ", expTotalMass, calcTotalMass, EPSILON); - double expCMx = 6.484; + double expCMx = 5.92; Coordinate expCM = new Coordinate(expCMx, 0, 0, expTotalMass); assertEquals(" Booster Launch CM.x is incorrect: ", expCM.x, boosterSetCM.x, EPSILON); assertEquals(" Booster Launch CM.y is incorrect: ", expCM.y, boosterSetCM.y, EPSILON); @@ -1052,7 +1052,7 @@ public class MassCalculatorTest extends BaseTestCase { double expTotalMass = 3.3565872; assertEquals(" Booster Launch Mass is incorrect: ", expTotalMass, boosterData.getMass(), EPSILON); - double expCMx = 0.847508988; + double expCMx = 0.2835089882645608; Coordinate expCM = new Coordinate(expCMx, 0, 0, expTotalMass); assertEquals(" Booster Launch CM.x is incorrect: ", expCM.x, boosterCM.x, EPSILON); assertEquals(" Booster Launch CM.y is incorrect: ", expCM.y, boosterCM.y, EPSILON); @@ -1099,7 +1099,7 @@ public class MassCalculatorTest extends BaseTestCase { double calcTotalMass = structure.getMass(); assertEquals(" Booster Launch Mass is incorrect: ", expMass, calcTotalMass, EPSILON); - final double expCMx = 1.1191303646438673; + final double expCMx = 0.5551303646438673; Coordinate expCM = new Coordinate(expCMx, 0, 0, expMass); assertEquals(" Booster Launch CM.x is incorrect: ", expCM.x, structure.getCM().x, EPSILON); assertEquals(" Booster Launch CM.y is incorrect: ", expCM.y, structure.getCM().y, EPSILON); diff --git a/core/test/net/sf/openrocket/rocketcomponent/MassObjectTest.java b/core/test/net/sf/openrocket/rocketcomponent/MassObjectTest.java index 72458ce78..a4df5b6eb 100644 --- a/core/test/net/sf/openrocket/rocketcomponent/MassObjectTest.java +++ b/core/test/net/sf/openrocket/rocketcomponent/MassObjectTest.java @@ -21,20 +21,14 @@ public class MassObjectTest extends BaseTestCase { mo.setLength(0.1); Assert.assertEquals(String.format(" No auto %s incorrect radius", mo.getClass().getName()), 0.1, mo.getRadius(), MathUtil.EPSILON); - Assert.assertEquals(String.format(" No auto %s incorrect no auto radius", mo.getClass().getName()), - 0.1, mo.getRadiusNoAuto(),MathUtil.EPSILON); Assert.assertEquals(String.format(" No auto %s incorrect length", mo.getClass().getName()), 0.1, mo.getLength(), MathUtil.EPSILON); - Assert.assertEquals(String.format(" No auto %s incorrect no auto length", mo.getClass().getName()), - 0.1, mo.getLengthNoAuto(), MathUtil.EPSILON); Assert.assertEquals(String.format(" No auto %s incorrect CG", mo.getClass().getName()), 0.05, mo.getComponentCG().x, MathUtil.EPSILON); - mo.setLengthNoAuto(0.1); + mo.setLength(0.1); Assert.assertEquals(String.format(" No auto 2 %s incorrect length", mo.getClass().getName()), 0.1, mo.getLength(), MathUtil.EPSILON); - Assert.assertEquals(String.format(" No auto 2 %s incorrect no auto length", mo.getClass().getName()), - 0.1, mo.getLengthNoAuto(), MathUtil.EPSILON); Assert.assertEquals(String.format(" No auto %s incorrect CG", mo.getClass().getName()), 0.05, mo.getComponentCG().x, MathUtil.EPSILON); @@ -46,12 +40,8 @@ public class MassObjectTest extends BaseTestCase { mo.setRadiusAutomatic(true); Assert.assertEquals(String.format(" Auto 1 %s incorrect radius", mo.getClass().getName()), 0.05, mo.getRadius(), MathUtil.EPSILON); - Assert.assertEquals(String.format(" Auto 1 %s incorrect no auto radius", mo.getClass().getName()), - 0.1, mo.getRadiusNoAuto(), MathUtil.EPSILON); Assert.assertEquals(String.format(" Auto 1 %s incorrect length", mo.getClass().getName()), 0.4, mo.getLength(), MathUtil.EPSILON); - Assert.assertEquals(String.format(" Auto 1 %s incorrect no auto length", mo.getClass().getName()), - 0.1, mo.getLengthNoAuto(), MathUtil.EPSILON); Assert.assertEquals(String.format(" Auto 1 %s incorrect CG", mo.getClass().getName()), 0.2, mo.getComponentCG().x, MathUtil.EPSILON); @@ -59,12 +49,8 @@ public class MassObjectTest extends BaseTestCase { parent.setInnerRadius(0.1); Assert.assertEquals(String.format(" Auto 2 %s incorrect radius", mo.getClass().getName()), 0.1, mo.getRadius(), MathUtil.EPSILON); - Assert.assertEquals(String.format(" Auto 2 %s incorrect no auto radius", mo.getClass().getName()), - 0.1, mo.getRadiusNoAuto(), MathUtil.EPSILON); Assert.assertEquals(String.format(" Auto 2 %s incorrect length", mo.getClass().getName()), 0.1, mo.getLength(), MathUtil.EPSILON); - Assert.assertEquals(String.format(" Auto 2 %s incorrect no auto length", mo.getClass().getName()), - 0.1, mo.getLengthNoAuto(), MathUtil.EPSILON); Assert.assertEquals(String.format(" Auto 2 %s incorrect CG", mo.getClass().getName()), 0.05, mo.getComponentCG().x, MathUtil.EPSILON); @@ -73,26 +59,18 @@ public class MassObjectTest extends BaseTestCase { mo.setLength(0.075); Assert.assertEquals(String.format(" Auto 3 %s incorrect radius", mo.getClass().getName()), 0.075, mo.getRadius(), MathUtil.EPSILON); - Assert.assertEquals(String.format(" Auto 3 %s incorrect no auto radius", mo.getClass().getName()), - 0.1, mo.getRadiusNoAuto(), MathUtil.EPSILON); Assert.assertEquals(String.format(" Auto 3 %s incorrect length", mo.getClass().getName()), 0.075, mo.getLength(), MathUtil.EPSILON); - Assert.assertEquals(String.format(" Auto 3 %s incorrect no auto length", mo.getClass().getName()), - 0.0422, mo.getLengthNoAuto(), 0.001); Assert.assertEquals(String.format(" Auto 3 %s incorrect CG", mo.getClass().getName()), 0.0375, mo.getComponentCG().x, MathUtil.EPSILON); - mo.setLengthNoAuto(0.05); + mo.setLength(0.05); Assert.assertEquals(String.format(" Auto 4 %s incorrect radius", mo.getClass().getName()), 0.075, mo.getRadius(), MathUtil.EPSILON); - Assert.assertEquals(String.format(" Auto 4 %s incorrect no auto radius", mo.getClass().getName()), - 0.1, mo.getRadiusNoAuto(), MathUtil.EPSILON); Assert.assertEquals(String.format(" Auto 4 %s incorrect length", mo.getClass().getName()), - 0.0889, mo.getLength(), 0.001); - Assert.assertEquals(String.format(" Auto 4 %s incorrect no auto length", mo.getClass().getName()), - 0.05, mo.getLengthNoAuto(), MathUtil.EPSILON); + 0.05, mo.getLength(), 0.001); Assert.assertEquals(String.format(" Auto 4 %s incorrect CG", mo.getClass().getName()), - 0.044444444444, mo.getComponentCG().x, MathUtil.EPSILON); + 0.025, mo.getComponentCG().x, MathUtil.EPSILON); } } } diff --git a/core/test/net/sf/openrocket/rocketcomponent/NoseConeTest.java b/core/test/net/sf/openrocket/rocketcomponent/NoseConeTest.java index f8076445b..4757e52b9 100644 --- a/core/test/net/sf/openrocket/rocketcomponent/NoseConeTest.java +++ b/core/test/net/sf/openrocket/rocketcomponent/NoseConeTest.java @@ -31,8 +31,8 @@ public class NoseConeTest extends BaseTestCase { assertEquals(0.06, noseCone.getLength(), EPSILON); assertEquals(0.1, noseCone.getAftRadius(), EPSILON); assertEquals(0.1, noseCone.getBaseRadius(), EPSILON); - assertEquals(0.1, noseCone.getAftRadiusNoAutomatic(), EPSILON); - assertEquals(0.1, noseCone.getBaseRadiusNoAutomatic(), EPSILON); + assertEquals(0.1, noseCone.getAftRadius(), EPSILON); + assertEquals(0.1, noseCone.getBaseRadius(), EPSILON); assertEquals(0.01, noseCone.getAftShoulderLength(), EPSILON); assertEquals(0.01, noseCone.getShoulderLength(), EPSILON); assertEquals(0.05, noseCone.getAftShoulderRadius(), EPSILON); @@ -44,7 +44,7 @@ public class NoseConeTest extends BaseTestCase { assertFalse(noseCone.isAftRadiusAutomatic()); assertEquals(0, noseCone.getForeRadius(), EPSILON); - assertEquals(0, noseCone.getForeRadiusNoAutomatic(), EPSILON); + assertEquals(0, noseCone.getForeRadius(), EPSILON); assertEquals(0, noseCone.getForeShoulderLength(), EPSILON); assertEquals(0, noseCone.getForeShoulderRadius(), EPSILON); assertEquals(0, noseCone.getForeShoulderThickness(), EPSILON); @@ -60,8 +60,8 @@ public class NoseConeTest extends BaseTestCase { assertEquals(0.2, noseCone.getAftRadius(), EPSILON); assertEquals(0.2, noseCone.getBaseRadius(), EPSILON); - assertEquals(0.2, noseCone.getAftRadiusNoAutomatic(), EPSILON); - assertEquals(0.2, noseCone.getBaseRadiusNoAutomatic(), EPSILON); + assertEquals(0.2, noseCone.getAftRadius(), EPSILON); + assertEquals(0.2, noseCone.getBaseRadius(), EPSILON); assertEquals(0.03, noseCone.getAftShoulderLength(), EPSILON); assertEquals(0.03, noseCone.getShoulderLength(), EPSILON); assertEquals(0.04, noseCone.getAftShoulderRadius(), EPSILON); @@ -73,7 +73,7 @@ public class NoseConeTest extends BaseTestCase { assertFalse(noseCone.isAftRadiusAutomatic()); assertEquals(0, noseCone.getForeRadius(), EPSILON); - assertEquals(0, noseCone.getForeRadiusNoAutomatic(), EPSILON); + assertEquals(0, noseCone.getForeRadius(), EPSILON); assertEquals(0, noseCone.getForeShoulderLength(), EPSILON); assertEquals(0, noseCone.getForeShoulderRadius(), EPSILON); assertEquals(0, noseCone.getForeShoulderThickness(), EPSILON); @@ -99,8 +99,8 @@ public class NoseConeTest extends BaseTestCase { assertEquals(0.06, noseCone.getLength(), EPSILON); assertEquals(0.1, noseCone.getForeRadius(), EPSILON); assertEquals(0.1, noseCone.getBaseRadius(), EPSILON); - assertEquals(0.1, noseCone.getForeRadiusNoAutomatic(), EPSILON); - assertEquals(0.1, noseCone.getBaseRadiusNoAutomatic(), EPSILON); + assertEquals(0.1, noseCone.getForeRadius(), EPSILON); + assertEquals(0.1, noseCone.getBaseRadius(), EPSILON); assertEquals(0.01, noseCone.getForeShoulderLength(), EPSILON); assertEquals(0.01, noseCone.getShoulderLength(), EPSILON); assertEquals(0.05, noseCone.getForeShoulderRadius(), EPSILON); @@ -112,7 +112,7 @@ public class NoseConeTest extends BaseTestCase { assertFalse(noseCone.isForeRadiusAutomatic()); assertEquals(0, noseCone.getAftRadius(), EPSILON); - assertEquals(0, noseCone.getAftRadiusNoAutomatic(), EPSILON); + assertEquals(0, noseCone.getAftRadius(), EPSILON); assertEquals(0, noseCone.getAftShoulderLength(), EPSILON); assertEquals(0, noseCone.getAftShoulderRadius(), EPSILON); assertEquals(0, noseCone.getAftShoulderThickness(), EPSILON); @@ -128,8 +128,8 @@ public class NoseConeTest extends BaseTestCase { assertEquals(0.2, noseCone.getForeRadius(), EPSILON); assertEquals(0.2, noseCone.getBaseRadius(), EPSILON); - assertEquals(0.2, noseCone.getForeRadiusNoAutomatic(), EPSILON); - assertEquals(0.2, noseCone.getBaseRadiusNoAutomatic(), EPSILON); + assertEquals(0.2, noseCone.getForeRadius(), EPSILON); + assertEquals(0.2, noseCone.getBaseRadius(), EPSILON); assertEquals(0.03, noseCone.getForeShoulderLength(), EPSILON); assertEquals(0.03, noseCone.getShoulderLength(), EPSILON); assertEquals(0.04, noseCone.getForeShoulderRadius(), EPSILON); @@ -141,7 +141,7 @@ public class NoseConeTest extends BaseTestCase { assertFalse(noseCone.isForeRadiusAutomatic()); assertEquals(0, noseCone.getAftRadius(), EPSILON); - assertEquals(0, noseCone.getAftRadiusNoAutomatic(), EPSILON); + assertEquals(0, noseCone.getAftRadius(), EPSILON); assertEquals(0, noseCone.getAftShoulderLength(), EPSILON); assertEquals(0, noseCone.getAftShoulderRadius(), EPSILON); assertEquals(0, noseCone.getAftShoulderThickness(), EPSILON); @@ -153,8 +153,8 @@ public class NoseConeTest extends BaseTestCase { assertEquals(0.2, noseCone.getAftRadius(), EPSILON); assertEquals(0.2, noseCone.getBaseRadius(), EPSILON); - assertEquals(0.2, noseCone.getAftRadiusNoAutomatic(), EPSILON); - assertEquals(0.2, noseCone.getBaseRadiusNoAutomatic(), EPSILON); + assertEquals(0.2, noseCone.getAftRadius(), EPSILON); + assertEquals(0.2, noseCone.getBaseRadius(), EPSILON); assertEquals(0.03, noseCone.getAftShoulderLength(), EPSILON); assertEquals(0.03, noseCone.getShoulderLength(), EPSILON); assertEquals(0.04, noseCone.getAftShoulderRadius(), EPSILON); @@ -166,7 +166,7 @@ public class NoseConeTest extends BaseTestCase { assertFalse(noseCone.isAftRadiusAutomatic()); assertEquals(0, noseCone.getForeRadius(), EPSILON); - assertEquals(0, noseCone.getForeRadiusNoAutomatic(), EPSILON); + assertEquals(0, noseCone.getForeRadius(), EPSILON); assertEquals(0, noseCone.getForeShoulderLength(), EPSILON); assertEquals(0, noseCone.getForeShoulderRadius(), EPSILON); assertEquals(0, noseCone.getForeShoulderThickness(), EPSILON); @@ -180,7 +180,7 @@ public class NoseConeTest extends BaseTestCase { AxialStage stage = rocket.getStage(0); NoseCone noseCone = new NoseCone(Transition.Shape.CONICAL, 0.06, 0.01); - BodyTube tube1 = new BodyTube(0.06, 0.02); + BodyTube tube1 = new BodyTube(0.06, 0.023); tube1.setOuterRadiusAutomatic(false); BodyTube tube2 = new BodyTube(0.06, 0.03); tube2.setOuterRadiusAutomatic(false); @@ -197,11 +197,11 @@ public class NoseConeTest extends BaseTestCase { assertFalse(noseCone.isAftRadiusAutomatic()); assertFalse(noseCone.isBaseRadiusAutomatic()); assertFalse(noseCone.isForeRadiusAutomatic()); - assertEquals(noseCone.getAftRadius(), noseCone.getAftRadiusNoAutomatic(), EPSILON); - assertEquals(noseCone.getBaseRadius(), noseCone.getBaseRadiusNoAutomatic(), EPSILON); - assertEquals(noseCone.getForeRadius(), noseCone.getForeRadiusNoAutomatic(), EPSILON); - assertEquals(0.01, noseCone.getAftRadiusNoAutomatic(), EPSILON); - assertEquals(0, noseCone.getForeRadiusNoAutomatic(), EPSILON); + assertEquals(noseCone.getAftRadius(), noseCone.getAftRadius(), EPSILON); + assertEquals(noseCone.getBaseRadius(), noseCone.getBaseRadius(), EPSILON); + assertEquals(noseCone.getForeRadius(), noseCone.getForeRadius(), EPSILON); + assertEquals(0.01, noseCone.getAftRadius(), EPSILON); + assertEquals(0, noseCone.getForeRadius(), EPSILON); noseCone.setAftRadiusAutomatic(true, true); assertFalse(noseCone.isAftRadiusAutomatic()); @@ -222,10 +222,10 @@ public class NoseConeTest extends BaseTestCase { assertFalse(noseCone.isAftRadiusAutomatic()); assertFalse(noseCone.isBaseRadiusAutomatic()); assertFalse(noseCone.isForeRadiusAutomatic()); - assertEquals(noseCone.getAftRadius(), noseCone.getAftRadiusNoAutomatic(), EPSILON); - assertEquals(noseCone.getBaseRadius(), noseCone.getBaseRadiusNoAutomatic(), EPSILON); - assertEquals(noseCone.getForeRadius(), noseCone.getForeRadiusNoAutomatic(), EPSILON); - assertEquals(0, noseCone.getForeRadiusNoAutomatic(), EPSILON); + assertEquals(noseCone.getAftRadius(), noseCone.getAftRadius(), EPSILON); + assertEquals(noseCone.getBaseRadius(), noseCone.getBaseRadius(), EPSILON); + assertEquals(noseCone.getForeRadius(), noseCone.getForeRadius(), EPSILON); + assertEquals(0, noseCone.getForeRadius(), EPSILON); noseCone.setAftRadiusAutomatic(true, true); assertFalse(noseCone.usesPreviousCompAutomatic()); @@ -238,24 +238,24 @@ public class NoseConeTest extends BaseTestCase { assertTrue(noseCone.isBaseRadiusAutomatic()); assertFalse(noseCone.isForeRadiusAutomatic()); assertEquals(tube1.getForeRadius(), noseCone.getAftRadius(), EPSILON); - assertEquals(0.01, noseCone.getAftRadiusNoAutomatic(), EPSILON); + assertEquals(0.023, noseCone.getAftRadius(), EPSILON); assertEquals(tube1.getForeRadius(), noseCone.getBaseRadius(), EPSILON); - assertEquals(0.01, noseCone.getBaseRadiusNoAutomatic(), EPSILON); - assertEquals(noseCone.getForeRadius(), noseCone.getForeRadiusNoAutomatic(), EPSILON); - assertEquals(0, noseCone.getForeRadiusNoAutomatic(), EPSILON); + assertEquals(0.023, noseCone.getBaseRadius(), EPSILON); + assertEquals(noseCone.getForeRadius(), noseCone.getForeRadius(), EPSILON); + assertEquals(0, noseCone.getForeRadius(), EPSILON); noseCone.setAftRadiusAutomatic(false, true); - assertEquals(noseCone.getAftRadius(), noseCone.getAftRadiusNoAutomatic(), EPSILON); - assertEquals(noseCone.getBaseRadius(), noseCone.getBaseRadiusNoAutomatic(), EPSILON); - assertEquals(noseCone.getForeRadius(), noseCone.getForeRadiusNoAutomatic(), EPSILON); - assertEquals(0, noseCone.getForeRadiusNoAutomatic(), EPSILON); + assertEquals(noseCone.getAftRadius(), noseCone.getAftRadius(), EPSILON); + assertEquals(noseCone.getBaseRadius(), noseCone.getBaseRadius(), EPSILON); + assertEquals(noseCone.getForeRadius(), noseCone.getForeRadius(), EPSILON); + assertEquals(0, noseCone.getForeRadius(), EPSILON); noseCone.setBaseRadiusAutomatic(true); - assertEquals(0.01, noseCone.getAftRadiusNoAutomatic(), EPSILON); + assertEquals(0.023, noseCone.getAftRadius(), EPSILON); assertEquals(tube1.getForeRadius(), noseCone.getBaseRadius(), EPSILON); - assertEquals(0.01, noseCone.getBaseRadiusNoAutomatic(), EPSILON); - assertEquals(noseCone.getForeRadius(), noseCone.getForeRadiusNoAutomatic(), EPSILON); - assertEquals(0, noseCone.getForeRadiusNoAutomatic(), EPSILON); + assertEquals(0.023, noseCone.getBaseRadius(), EPSILON); + assertEquals(noseCone.getForeRadius(), noseCone.getForeRadius(), EPSILON); + assertEquals(0, noseCone.getForeRadius(), EPSILON); noseCone.setForeRadiusAutomatic(true, true); assertFalse(noseCone.isForeRadiusAutomatic()); @@ -274,11 +274,11 @@ public class NoseConeTest extends BaseTestCase { assertTrue(noseCone.isBaseRadiusAutomatic()); assertFalse(noseCone.isForeRadiusAutomatic()); assertEquals(tube1.getForeRadius(), noseCone.getAftRadius(), EPSILON); - assertEquals(0.01, noseCone.getAftRadiusNoAutomatic(), EPSILON); + assertEquals(0.023, noseCone.getAftRadius(), EPSILON); assertEquals(tube1.getForeRadius(), noseCone.getBaseRadius(), EPSILON); - assertEquals(0.01, noseCone.getBaseRadiusNoAutomatic(), EPSILON); - assertEquals(noseCone.getForeRadius(), noseCone.getForeRadiusNoAutomatic(), EPSILON); - assertEquals(0, noseCone.getForeRadiusNoAutomatic(), EPSILON); + assertEquals(0.023, noseCone.getBaseRadius(), EPSILON); + assertEquals(noseCone.getForeRadius(), noseCone.getForeRadius(), EPSILON); + assertEquals(0, noseCone.getForeRadius(), EPSILON); // Do a flip noseCone.setFlipped(true); @@ -311,11 +311,11 @@ public class NoseConeTest extends BaseTestCase { assertFalse(noseCone.isAftRadiusAutomatic()); assertFalse(noseCone.isBaseRadiusAutomatic()); assertFalse(noseCone.isForeRadiusAutomatic()); - assertEquals(noseCone.getAftRadius(), noseCone.getAftRadiusNoAutomatic(), EPSILON); - assertEquals(noseCone.getBaseRadius(), noseCone.getBaseRadiusNoAutomatic(), EPSILON); - assertEquals(noseCone.getForeRadius(), noseCone.getForeRadiusNoAutomatic(), EPSILON); - assertEquals(0, noseCone.getAftRadiusNoAutomatic(), EPSILON); - assertEquals(0.01, noseCone.getForeRadiusNoAutomatic(), EPSILON); + assertEquals(noseCone.getAftRadius(), noseCone.getAftRadius(), EPSILON); + assertEquals(noseCone.getBaseRadius(), noseCone.getBaseRadius(), EPSILON); + assertEquals(noseCone.getForeRadius(), noseCone.getForeRadius(), EPSILON); + assertEquals(0, noseCone.getAftRadius(), EPSILON); + assertEquals(0.01, noseCone.getForeRadius(), EPSILON); noseCone.setAftRadiusAutomatic(true, true); assertFalse(noseCone.isAftRadiusAutomatic()); @@ -336,10 +336,10 @@ public class NoseConeTest extends BaseTestCase { assertFalse(noseCone.isAftRadiusAutomatic()); assertFalse(noseCone.isBaseRadiusAutomatic()); assertFalse(noseCone.isForeRadiusAutomatic()); - assertEquals(noseCone.getAftRadius(), noseCone.getAftRadiusNoAutomatic(), EPSILON); - assertEquals(noseCone.getBaseRadius(), noseCone.getBaseRadiusNoAutomatic(), EPSILON); - assertEquals(noseCone.getForeRadius(), noseCone.getForeRadiusNoAutomatic(), EPSILON); - assertEquals(0, noseCone.getAftRadiusNoAutomatic(), EPSILON); + assertEquals(noseCone.getAftRadius(), noseCone.getAftRadius(), EPSILON); + assertEquals(noseCone.getBaseRadius(), noseCone.getBaseRadius(), EPSILON); + assertEquals(noseCone.getForeRadius(), noseCone.getForeRadius(), EPSILON); + assertEquals(0, noseCone.getAftRadius(), EPSILON); noseCone.setBaseRadiusAutomatic(true); assertTrue(noseCone.usesPreviousCompAutomatic()); @@ -352,25 +352,25 @@ public class NoseConeTest extends BaseTestCase { assertTrue(noseCone.isBaseRadiusAutomatic()); assertTrue(noseCone.isForeRadiusAutomatic()); assertEquals(tube1.getAftRadius(), noseCone.getForeRadius(), EPSILON); - assertEquals(0.01, noseCone.getForeRadiusNoAutomatic(), EPSILON); + assertEquals(0.02, noseCone.getForeRadius(), EPSILON); assertEquals(tube1.getAftRadius(), noseCone.getBaseRadius(), EPSILON); - assertEquals(0.01, noseCone.getBaseRadiusNoAutomatic(), EPSILON); - assertEquals(noseCone.getAftRadius(), noseCone.getAftRadiusNoAutomatic(), EPSILON); - assertEquals(0, noseCone.getAftRadiusNoAutomatic(), EPSILON); + assertEquals(0.02, noseCone.getBaseRadius(), EPSILON); + assertEquals(noseCone.getAftRadius(), noseCone.getAftRadius(), EPSILON); + assertEquals(0, noseCone.getAftRadius(), EPSILON); noseCone.setForeRadiusAutomatic(false, true); - assertEquals(noseCone.getAftRadius(), noseCone.getAftRadiusNoAutomatic(), EPSILON); - assertEquals(noseCone.getBaseRadius(), noseCone.getBaseRadiusNoAutomatic(), EPSILON); - assertEquals(noseCone.getForeRadius(), noseCone.getForeRadiusNoAutomatic(), EPSILON); - assertEquals(0.01, noseCone.getForeRadiusNoAutomatic(), EPSILON); + assertEquals(noseCone.getAftRadius(), noseCone.getAftRadius(), EPSILON); + assertEquals(noseCone.getBaseRadius(), noseCone.getBaseRadius(), EPSILON); + assertEquals(noseCone.getForeRadius(), noseCone.getForeRadius(), EPSILON); + assertEquals(0.02, noseCone.getForeRadius(), EPSILON); noseCone.setBaseRadiusAutomatic(true); assertEquals(tube1.getAftRadius(), noseCone.getForeRadius(), EPSILON); - assertEquals(0.01, noseCone.getForeRadiusNoAutomatic(), EPSILON); + assertEquals(0.02, noseCone.getForeRadius(), EPSILON); assertEquals(tube1.getAftRadius(), noseCone.getBaseRadius(), EPSILON); - assertEquals(0.01, noseCone.getBaseRadiusNoAutomatic(), EPSILON); - assertEquals(noseCone.getAftRadius(), noseCone.getAftRadiusNoAutomatic(), EPSILON); - assertEquals(0, noseCone.getAftRadiusNoAutomatic(), EPSILON); + assertEquals(0.02, noseCone.getBaseRadius(), EPSILON); + assertEquals(noseCone.getAftRadius(), noseCone.getAftRadius(), EPSILON); + assertEquals(0, noseCone.getAftRadius(), EPSILON); assertTrue(noseCone.isForeRadiusAutomatic()); assertTrue(noseCone.isBaseRadiusAutomatic()); @@ -389,10 +389,10 @@ public class NoseConeTest extends BaseTestCase { assertTrue(noseCone.isBaseRadiusAutomatic()); assertTrue(noseCone.isForeRadiusAutomatic()); assertEquals(tube1.getAftRadius(), noseCone.getForeRadius(), EPSILON); - assertEquals(0.01, noseCone.getForeRadiusNoAutomatic(), EPSILON); + assertEquals(0.02, noseCone.getForeRadius(), EPSILON); assertEquals(tube1.getForeRadius(), noseCone.getBaseRadius(), EPSILON); - assertEquals(0.01, noseCone.getBaseRadiusNoAutomatic(), EPSILON); - assertEquals(noseCone.getAftRadius(), noseCone.getAftRadiusNoAutomatic(), EPSILON); + assertEquals(0.02, noseCone.getBaseRadius(), EPSILON); + assertEquals(noseCone.getAftRadius(), noseCone.getAftRadius(), EPSILON); assertEquals(0, noseCone.getAftRadius(), EPSILON); } } diff --git a/core/test/net/sf/openrocket/simulation/DisableStageTest.java b/core/test/net/sf/openrocket/simulation/DisableStageTest.java index 42b0f77b7..d0eb1e8d4 100644 --- a/core/test/net/sf/openrocket/simulation/DisableStageTest.java +++ b/core/test/net/sf/openrocket/simulation/DisableStageTest.java @@ -42,7 +42,7 @@ public class DisableStageTest extends BaseTestCase { } } - //// Test re-enableing the stage. + //// Test re-enabling the stage. Rocket rocketOriginal = TestRockets.makeEstesAlphaIII(); Simulation simOriginal = new Simulation(rocketOriginal); @@ -81,7 +81,7 @@ public class DisableStageTest extends BaseTestCase { compareSims(simRemoved, simDisabled, delta); - //// Test re-enableing the stage. + //// Test re-enabling the stage. Rocket rocketOriginal = TestRockets.makeBeta(); Simulation simOriginal = new Simulation(rocketOriginal); simOriginal.setFlightConfigurationId(TestRockets.TEST_FCID_1); @@ -173,7 +173,7 @@ public class DisableStageTest extends BaseTestCase { compareSims(simRemoved, simDisabled, delta); - //// Test re-enableing the stage. + //// Test re-enabling the stage. Rocket rocketOriginal = TestRockets.makeFalcon9Heavy(); TestRockets.addCoreFins(rocketOriginal); @@ -232,7 +232,7 @@ public class DisableStageTest extends BaseTestCase { } } - //// Test re-enableing the stage. + //// Test re-enabling the stage. Rocket rocketOriginal = TestRockets.makeFalcon9Heavy(); TestRockets.addCoreFins(rocketOriginal); diff --git a/swing/src/net/sf/openrocket/database/MotorDatabaseLoader.java b/swing/src/net/sf/openrocket/database/MotorDatabaseLoader.java index 279f0cf92..24fd7e4c4 100644 --- a/swing/src/net/sf/openrocket/database/MotorDatabaseLoader.java +++ b/swing/src/net/sf/openrocket/database/MotorDatabaseLoader.java @@ -27,6 +27,7 @@ import net.sf.openrocket.util.Pair; import javax.swing.JDialog; import javax.swing.JOptionPane; +import javax.swing.SwingUtilities; /** * An asynchronous database loader that loads the internal thrust curves @@ -140,21 +141,26 @@ public class MotorDatabaseLoader extends AsynchronousDatabaseLoader { */ private void loadFile(GeneralMotorLoader loader, Pair f) { try { - List motors = loader.load(f.getV(), f.getU().getName()); try { + List motors = loader.load(f.getV(), f.getU().getName()); addMotorsFromBuilders(motors); } - catch (IllegalArgumentException e) { + catch (IllegalArgumentException | IOException e) { Translator trans = Application.getTranslator(); String fullPath = f.getU().getPath(); String message = "

" + e.getMessage() + ".

" + MessageFormat.format( trans.get("MotorDbLoaderDlg.message1"), fullPath) + "
" + trans.get("MotorDbLoaderDlg.message2") + "

"; - JOptionPane pane = new JOptionPane(message, JOptionPane.WARNING_MESSAGE); - JDialog dialog = pane.createDialog(null, trans.get("MotorDbLoaderDlg.title")); - dialog.setModalityType(Dialog.ModalityType.MODELESS); - dialog.setAlwaysOnTop(true); - dialog.setVisible(true); + SwingUtilities.invokeLater(new Runnable() { + @Override + public void run() { + JOptionPane pane = new JOptionPane(message, JOptionPane.WARNING_MESSAGE); + JDialog dialog = pane.createDialog(null, trans.get("MotorDbLoaderDlg.title")); + dialog.setModalityType(Dialog.ModalityType.MODELESS); + dialog.setAlwaysOnTop(true); + dialog.setVisible(true); + } + }); } f.getV().close(); } catch (Exception e) { diff --git a/swing/src/net/sf/openrocket/gui/components/PreferencesOptionPanel.java b/swing/src/net/sf/openrocket/gui/components/PreferencesOptionPanel.java new file mode 100644 index 000000000..a87e7a19b --- /dev/null +++ b/swing/src/net/sf/openrocket/gui/components/PreferencesOptionPanel.java @@ -0,0 +1,65 @@ +package net.sf.openrocket.gui.components; + +import net.miginfocom.swing.MigLayout; +import net.sf.openrocket.l10n.Translator; +import net.sf.openrocket.startup.Application; +import net.sf.openrocket.startup.Preferences; + +import javax.swing.BorderFactory; +import javax.swing.JCheckBox; +import javax.swing.JPanel; +import java.awt.event.ItemEvent; +import java.awt.event.ItemListener; + +/** + * A panel that adds storage options for exporting preferences. + */ +public class PreferencesOptionPanel extends JPanel { + private static final Translator trans = Application.getTranslator(); + private static final Preferences prefs = Application.getPreferences(); + + private final JCheckBox exportUserDirectories; + private final JCheckBox exportWindowInfo; + + public PreferencesOptionPanel() { + super(new MigLayout("fill, ins 0")); + + JPanel panel = new JPanel(new MigLayout("fill, ins 4lp")); + panel.setBorder(BorderFactory.createTitledBorder(trans.get("PreferencesOptionPanel.title"))); + + // Export user directories + exportUserDirectories = new JCheckBox(trans.get("PreferencesOptionPanel.checkbox.userDirectories")); + exportUserDirectories.setToolTipText(trans.get("PreferencesOptionPanel.checkbox.userDirectories.ttip")); + exportUserDirectories.setSelected(prefs.getExportUserDirectories()); + exportUserDirectories.addItemListener(new ItemListener() { + @Override + public void itemStateChanged(ItemEvent e) { + prefs.setExportUserDirectories(e.getStateChange() == ItemEvent.SELECTED); + } + }); + panel.add(exportUserDirectories, "wrap"); + + // Export window information (position, size...) + exportWindowInfo = new JCheckBox(trans.get("PreferencesOptionPanel.checkbox.windowInfo")); + exportWindowInfo.setToolTipText(trans.get("PreferencesOptionPanel.checkbox.windowInfo.ttip")); + exportWindowInfo.setSelected(prefs.getExportWindowInformation()); + exportWindowInfo.addItemListener(new ItemListener() { + @Override + public void itemStateChanged(ItemEvent e) { + prefs.setExportWindowInformation(e.getStateChange() == ItemEvent.SELECTED); + } + }); + panel.add(exportWindowInfo, "wrap 10lp"); + + + this.add(panel, "growx, north"); + } + + public boolean isIgnoreUserDirectories() { + return !exportUserDirectories.isSelected(); + } + + public boolean isIgnoreWindowInformation() { + return !exportWindowInfo.isSelected(); + } +} diff --git a/swing/src/net/sf/openrocket/gui/configdialog/FreeformFinSetConfig.java b/swing/src/net/sf/openrocket/gui/configdialog/FreeformFinSetConfig.java index 9f898774c..cce14deba 100644 --- a/swing/src/net/sf/openrocket/gui/configdialog/FreeformFinSetConfig.java +++ b/swing/src/net/sf/openrocket/gui/configdialog/FreeformFinSetConfig.java @@ -303,6 +303,7 @@ public class FreeformFinSetConfig extends FinSetConfig { } FreeformFinSetConfig.writeCSVFile(table, selectedFile); + ((SwingPreferences) Application.getPreferences()).setDefaultDirectory(chooser.getCurrentDirectory()); } } }); @@ -400,6 +401,7 @@ public class FreeformFinSetConfig extends FinSetConfig { trans.get("CustomFinImport.error.title"), JOptionPane.ERROR_MESSAGE); } finally { document.stopUndo(); + ((SwingPreferences) Application.getPreferences()).setDefaultDirectory(chooser.getCurrentDirectory()); } } } diff --git a/swing/src/net/sf/openrocket/gui/customexpression/CustomExpressionPanel.java b/swing/src/net/sf/openrocket/gui/customexpression/CustomExpressionPanel.java index 272044bc4..394daec86 100644 --- a/swing/src/net/sf/openrocket/gui/customexpression/CustomExpressionPanel.java +++ b/swing/src/net/sf/openrocket/gui/customexpression/CustomExpressionPanel.java @@ -18,6 +18,7 @@ import javax.swing.JScrollPane; import javax.swing.SwingUtilities; import javax.swing.filechooser.FileNameExtensionFilter; +import net.sf.openrocket.gui.util.SwingPreferences; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -110,6 +111,7 @@ public class CustomExpressionPanel extends JPanel { log.info(Markers.USER_MARKER, "Error opening document to import expressions from."); } updateExpressions(); + ((SwingPreferences) Application.getPreferences()).setDefaultDirectory(fc.getCurrentDirectory()); } } }); diff --git a/swing/src/net/sf/openrocket/gui/dialogs/DecalNotFoundDialog.java b/swing/src/net/sf/openrocket/gui/dialogs/DecalNotFoundDialog.java index e14c05a15..e726e0015 100644 --- a/swing/src/net/sf/openrocket/gui/dialogs/DecalNotFoundDialog.java +++ b/swing/src/net/sf/openrocket/gui/dialogs/DecalNotFoundDialog.java @@ -43,6 +43,7 @@ public abstract class DecalNotFoundDialog { if (resultFileChooser == JFileChooser.APPROVE_OPTION) { File file = chooser.getSelectedFile(); decex.getDecal().setDecalFile(file); + ((SwingPreferences) Application.getPreferences()).setDefaultDirectory(chooser.getCurrentDirectory()); } } return (resultYesNo == JOptionPane.YES_OPTION) && (resultFileChooser == JFileChooser.APPROVE_OPTION); diff --git a/swing/src/net/sf/openrocket/gui/dialogs/EditDecalDialog.java b/swing/src/net/sf/openrocket/gui/dialogs/EditDecalDialog.java index 8ed3bf885..678e0edbc 100644 --- a/swing/src/net/sf/openrocket/gui/dialogs/EditDecalDialog.java +++ b/swing/src/net/sf/openrocket/gui/dialogs/EditDecalDialog.java @@ -20,6 +20,7 @@ import javax.swing.event.ChangeListener; import net.miginfocom.swing.MigLayout; import net.sf.openrocket.gui.util.GUIUtil; +import net.sf.openrocket.gui.util.SwingPreferences; import net.sf.openrocket.l10n.Translator; import net.sf.openrocket.startup.Application; import net.sf.openrocket.gui.widgets.SelectColorButton; @@ -75,6 +76,7 @@ public class EditDecalDialog extends JDialog { int action = fc.showOpenDialog(owner); if (action == JFileChooser.APPROVE_OPTION) { commandText.setText(fc.getSelectedFile().getAbsolutePath()); + ((SwingPreferences) Application.getPreferences()).setDefaultDirectory(fc.getCurrentDirectory()); } } @@ -109,6 +111,7 @@ public class EditDecalDialog extends JDialog { int action = fc.showOpenDialog(owner); if (action == JFileChooser.APPROVE_OPTION) { commandText.setText(fc.getSelectedFile().getAbsolutePath()); + ((SwingPreferences) Application.getPreferences()).setDefaultDirectory(fc.getCurrentDirectory()); } } diff --git a/swing/src/net/sf/openrocket/gui/dialogs/ScaleDialog.java b/swing/src/net/sf/openrocket/gui/dialogs/ScaleDialog.java index 111f16378..d1a95308d 100644 --- a/swing/src/net/sf/openrocket/gui/dialogs/ScaleDialog.java +++ b/swing/src/net/sf/openrocket/gui/dialogs/ScaleDialog.java @@ -136,10 +136,8 @@ public class ScaleDialog extends JDialog { SCALERS_NO_OFFSET.put(FreeformFinSet.class, list); // MassObject - list = new ArrayList<>(1); - list.add(new MassObjectScaler()); - SCALERS_NO_OFFSET.put(MassObject.class, list); addScaler(MassObject.class, "Radius", "isRadiusAutomatic", SCALERS_NO_OFFSET); + addScaler(MassObject.class, "Length", SCALERS_NO_OFFSET); addScaler(MassObject.class, "RadialPosition", SCALERS_OFFSET); // MassComponent @@ -720,25 +718,6 @@ public class ScaleDialog extends JDialog { } } - - private static class MassObjectScaler implements Scaler { - @Override - public void scale(RocketComponent component, double multiplier, boolean scaleMass) { - if (scaleMass) { - MassObject c = (MassObject) component; - if (c.isRadiusAutomatic()) { - double volume = Math.PI * Math.pow(c.getRadiusNoAuto(), 2) * c.getLengthNoAuto(); - double scaledVolume = volume * MathUtil.pow3(multiplier); - c.setRadius(c.getRadiusNoAuto() * multiplier); - c.setLengthNoAuto(scaledVolume / (Math.PI * Math.pow(c.getRadiusNoAuto(), 2))); - c.setRadiusAutomatic(true); - } else { - c.setLength(c.getLength() * multiplier); - } - } - } - - } private static class FreeformFinSetScaler implements Scaler { diff --git a/swing/src/net/sf/openrocket/gui/dialogs/flightconfiguration/MotorMountConfigurationPanel.java b/swing/src/net/sf/openrocket/gui/dialogs/flightconfiguration/MotorMountConfigurationPanel.java index 322794979..fc944e220 100644 --- a/swing/src/net/sf/openrocket/gui/dialogs/flightconfiguration/MotorMountConfigurationPanel.java +++ b/swing/src/net/sf/openrocket/gui/dialogs/flightconfiguration/MotorMountConfigurationPanel.java @@ -25,7 +25,7 @@ public class MotorMountConfigurationPanel extends JPanel { table.setShowVerticalLines(false); table.setRowSelectionAllowed(false); table.setColumnSelectionAllowed(false); - table.setAutoResizeMode(JTable.AUTO_RESIZE_OFF); + table.setAutoResizeMode(JTable.AUTO_RESIZE_LAST_COLUMN); table.setAutoCreateColumnsFromModel(false); TableColumnModel columnModel = table.getColumnModel(); diff --git a/swing/src/net/sf/openrocket/gui/dialogs/preferences/GeneralPreferencesPanel.java b/swing/src/net/sf/openrocket/gui/dialogs/preferences/GeneralPreferencesPanel.java index f4893800a..7cfba5735 100644 --- a/swing/src/net/sf/openrocket/gui/dialogs/preferences/GeneralPreferencesPanel.java +++ b/swing/src/net/sf/openrocket/gui/dialogs/preferences/GeneralPreferencesPanel.java @@ -19,6 +19,7 @@ import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.JProgressBar; import javax.swing.JTextField; +import javax.swing.SwingUtilities; import javax.swing.Timer; import javax.swing.event.DocumentEvent; import javax.swing.event.DocumentListener; @@ -35,8 +36,11 @@ import net.sf.openrocket.gui.dialogs.UpdateInfoDialog; import net.sf.openrocket.gui.util.GUIUtil; import net.sf.openrocket.gui.util.SimpleFileFilter; import net.sf.openrocket.gui.util.SwingPreferences; +import net.sf.openrocket.gui.util.PreferencesExporter; +import net.sf.openrocket.gui.util.PreferencesImporter; import net.sf.openrocket.l10n.L10N; import net.sf.openrocket.logging.Markers; +import net.sf.openrocket.startup.Application; import net.sf.openrocket.startup.Preferences; import net.sf.openrocket.util.BuildProperties; import net.sf.openrocket.util.Named; @@ -46,7 +50,7 @@ import net.sf.openrocket.gui.widgets.SelectColorButton; @SuppressWarnings("serial") public class GeneralPreferencesPanel extends PreferencesPanel { - public GeneralPreferencesPanel(JDialog parent) { + public GeneralPreferencesPanel(PreferencesDialog parent) { super(parent, new MigLayout("fillx, ins 30lp n n n")); @@ -251,21 +255,69 @@ public class GeneralPreferencesPanel extends PreferencesPanel { }); this.add(rocksimWarningDialogBox,"spanx, wrap"); - //// Clear cached preferences - final JButton clearCachedPreferences = new SelectColorButton(trans.get("pref.dlg.but.clearCachedPreferences")); - clearCachedPreferences.setToolTipText(trans.get("pref.dlg.but.clearCachedPreferences.ttip")); - clearCachedPreferences.addActionListener(new ActionListener() { + // Preference buttons + JPanel buttonPanel = new JPanel(new MigLayout("fillx, ins 0")); + + //// Export preferences + final JButton exportPreferences = new SelectColorButton(trans.get("pref.dlg.but.exportPreferences")); + exportPreferences.setToolTipText(trans.get("pref.dlg.but.exportPreferences.ttip")); + exportPreferences.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + PreferencesExporter.exportPreferences(parent, preferences.getPreferences()); + } + }); + buttonPanel.add(exportPreferences); + + //// Import preferences + final JButton importPreferences = new SelectColorButton(trans.get("pref.dlg.but.importPreferences")); + importPreferences.setToolTipText(trans.get("pref.dlg.but.importPreferences.ttip")); + importPreferences.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + boolean imported = PreferencesImporter.importPreferences(parent); + if (imported) { + SwingUtilities.invokeLater(new Runnable() { + @Override + public void run() { + JOptionPane.showMessageDialog(parent, + trans.get("generalprefs.ImportWarning.msg"), + trans.get("generalprefs.ImportWarning.title"), + JOptionPane.WARNING_MESSAGE); + PreferencesDialog.showPreferences(parent.getParentFrame()); // Refresh the preferences dialog + } + }); + } + } + }); + buttonPanel.add(importPreferences); + + //// Reset all preferences + final JButton resetAllPreferences = new SelectColorButton(trans.get("pref.dlg.but.resetAllPreferences")); + resetAllPreferences.setToolTipText(trans.get("pref.dlg.but.resetAllPreferences.ttip")); + resetAllPreferences.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { int resultYesNo = JOptionPane.showConfirmDialog(parent, trans.get("pref.dlg.clearCachedPreferences.message"), trans.get("pref.dlg.clearCachedPreferences.title"), JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE); if (resultYesNo == JOptionPane.YES_OPTION) { preferences.clearPreferences(); + SwingUtilities.invokeLater(new Runnable() { + @Override + public void run() { + JOptionPane.showMessageDialog(parent, + trans.get("generalprefs.ImportWarning.msg"), + trans.get("generalprefs.ImportWarning.title"), + JOptionPane.WARNING_MESSAGE); + PreferencesDialog.showPreferences(parent.getParentFrame()); // Refresh the preferences dialog + } + }); } } }); - this.add(clearCachedPreferences, "spanx, pushy, bottom, wrap"); + buttonPanel.add(resetAllPreferences, "pushx, right, wrap"); + this.add(buttonPanel, "spanx, growx, pushy, bottom, wrap"); } diff --git a/swing/src/net/sf/openrocket/gui/dialogs/preferences/GraphicsPreferencesPanel.java b/swing/src/net/sf/openrocket/gui/dialogs/preferences/GraphicsPreferencesPanel.java index b0b241b9b..3f16a27d3 100644 --- a/swing/src/net/sf/openrocket/gui/dialogs/preferences/GraphicsPreferencesPanel.java +++ b/swing/src/net/sf/openrocket/gui/dialogs/preferences/GraphicsPreferencesPanel.java @@ -29,6 +29,8 @@ import net.sf.openrocket.gui.adaptors.BooleanModel; import net.sf.openrocket.gui.components.StyledLabel; import net.sf.openrocket.gui.components.StyledLabel.Style; import net.sf.openrocket.gui.util.GUIUtil; +import net.sf.openrocket.gui.util.SwingPreferences; +import net.sf.openrocket.startup.Application; import net.sf.openrocket.startup.Preferences; import net.sf.openrocket.gui.widgets.SelectColorButton; @@ -119,6 +121,7 @@ public class GraphicsPreferencesPanel extends PreferencesPanel { String commandLine = fc.getSelectedFile().getAbsolutePath(); commandText.setText(commandLine); preferences.setDecalEditorPreference(false, commandLine); + ((SwingPreferences) Application.getPreferences()).setDefaultDirectory(fc.getCurrentDirectory()); } } diff --git a/swing/src/net/sf/openrocket/gui/dialogs/preferences/PreferencesDialog.java b/swing/src/net/sf/openrocket/gui/dialogs/preferences/PreferencesDialog.java index 54de016bb..4dadc0f14 100644 --- a/swing/src/net/sf/openrocket/gui/dialogs/preferences/PreferencesDialog.java +++ b/swing/src/net/sf/openrocket/gui/dialogs/preferences/PreferencesDialog.java @@ -33,11 +33,15 @@ public class PreferencesDialog extends JDialog { private final SwingPreferences preferences = (SwingPreferences) Application .getPreferences(); + private BasicFrame parentFrame; + private PreferencesDialog(BasicFrame parent) { // // Preferences super(parent, trans.get("pref.dlg.title.Preferences"), Dialog.ModalityType.APPLICATION_MODAL); + this.parentFrame = parent; + JPanel panel = new JPanel(new MigLayout("fill, gap unrel", "[grow]", "[grow][]")); @@ -105,6 +109,10 @@ public class PreferencesDialog extends JDialog { GUIUtil.setDisposableDialogOptions(this, close); } + public BasicFrame getParentFrame() { + return parentFrame; + } + // ////// Singleton implementation //////// private static PreferencesDialog dialog = null; diff --git a/swing/src/net/sf/openrocket/gui/main/ExportDecalDialog.java b/swing/src/net/sf/openrocket/gui/main/ExportDecalDialog.java index 18e3c4ba1..932547cc5 100644 --- a/swing/src/net/sf/openrocket/gui/main/ExportDecalDialog.java +++ b/swing/src/net/sf/openrocket/gui/main/ExportDecalDialog.java @@ -90,6 +90,7 @@ public class ExportDecalDialog extends JDialog { export(selectedDecal, selectedFile); // If the user doesn't confirm over write, then leave this dialog open. ExportDecalDialog.this.dispose(); + ((SwingPreferences) Application.getPreferences()).setDefaultDirectory(chooser.getCurrentDirectory()); } } } diff --git a/swing/src/net/sf/openrocket/gui/main/OpenRocketClipboard.java b/swing/src/net/sf/openrocket/gui/main/OpenRocketClipboard.java index cda0a7e85..7302d8b43 100644 --- a/swing/src/net/sf/openrocket/gui/main/OpenRocketClipboard.java +++ b/swing/src/net/sf/openrocket/gui/main/OpenRocketClipboard.java @@ -101,7 +101,7 @@ public final class OpenRocketClipboard { if (someChildrenSelected) { for (RocketComponent child : component.getChildren()) { if (!clipboardComponents.contains(child)) { - component.removeChild(child); + component.removeChild(child, false); } else { clipboardComponents.remove(child); filterClipboardComponents(child.getChildren()); diff --git a/swing/src/net/sf/openrocket/gui/main/RocketActions.java b/swing/src/net/sf/openrocket/gui/main/RocketActions.java index 200244723..2bfa6e9a6 100644 --- a/swing/src/net/sf/openrocket/gui/main/RocketActions.java +++ b/swing/src/net/sf/openrocket/gui/main/RocketActions.java @@ -337,7 +337,7 @@ public class RocketActions { RocketComponent originalParent = components.get(i).getParent(); int originalParentIdx = components.indexOf(originalParent); - result.get(originalParentIdx).addChild(result.get(i)); + result.get(originalParentIdx).addChild(result.get(i), false); } else if (RocketComponent.listContainsParent(components, components.get(i))){ RocketComponent originalParent = components.get(i); while (originalParent != components.get(i)) { @@ -346,7 +346,7 @@ public class RocketActions { } } int originalParentIdx = components.indexOf(originalParent); - result.get(originalParentIdx).addChild(result.get(i)); + result.get(originalParentIdx).addChild(result.get(i), false); } } diff --git a/swing/src/net/sf/openrocket/gui/preset/PresetEditorDialog.java b/swing/src/net/sf/openrocket/gui/preset/PresetEditorDialog.java index 3818ebe17..b10331a21 100644 --- a/swing/src/net/sf/openrocket/gui/preset/PresetEditorDialog.java +++ b/swing/src/net/sf/openrocket/gui/preset/PresetEditorDialog.java @@ -44,6 +44,7 @@ import javax.swing.border.EmptyBorder; import javax.swing.filechooser.FileNameExtensionFilter; import javax.swing.text.JTextComponent; +import net.sf.openrocket.gui.util.SwingPreferences; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -389,6 +390,7 @@ public class PresetEditorDialog extends JDialog implements ItemListener { File file = imageChooser.getSelectedFile(); ncImage = scaleImage(new ImageIcon(file.getAbsolutePath()).getImage(), 155); ncImageBtn.setIcon(ncImage); + ((SwingPreferences) Application.getPreferences()).setDefaultDirectory(imageChooser.getCurrentDirectory()); } } }); @@ -1318,6 +1320,7 @@ public class PresetEditorDialog extends JDialog implements ItemListener { */ private JFileChooser createImageChooser() { final JFileChooser chooser = new JFileChooser(); + chooser.setCurrentDirectory(((SwingPreferences) Application.getPreferences()).getDefaultDirectory()); ImagePreviewPanel preview = new ImagePreviewPanel(); chooser.setAccessory(preview); chooser.addPropertyChangeListener(preview); diff --git a/swing/src/net/sf/openrocket/gui/util/DummyFrameMenuOSX.java b/swing/src/net/sf/openrocket/gui/util/DummyFrameMenuOSX.java index 9a4a84a33..4b9f1537b 100644 --- a/swing/src/net/sf/openrocket/gui/util/DummyFrameMenuOSX.java +++ b/swing/src/net/sf/openrocket/gui/util/DummyFrameMenuOSX.java @@ -1,11 +1,6 @@ package net.sf.openrocket.gui.util; -import net.sf.openrocket.gui.dialogs.AboutDialog; -import net.sf.openrocket.gui.dialogs.LicenseDialog; -import net.sf.openrocket.gui.help.tours.GuidedTourSelectionDialog; import net.sf.openrocket.gui.main.BasicFrame; -import net.sf.openrocket.gui.main.ExampleDesignFileAction; -import net.sf.openrocket.gui.main.MRUDesignFileAction; import net.sf.openrocket.l10n.Translator; import net.sf.openrocket.logging.Markers; import net.sf.openrocket.startup.Application; diff --git a/swing/src/net/sf/openrocket/gui/util/FileHelper.java b/swing/src/net/sf/openrocket/gui/util/FileHelper.java index 9ea309450..c2bc914d6 100644 --- a/swing/src/net/sf/openrocket/gui/util/FileHelper.java +++ b/swing/src/net/sf/openrocket/gui/util/FileHelper.java @@ -64,6 +64,10 @@ public final class FileHelper { public static final FileFilter PNG_FILTER = new SimpleFileFilter(trans.get("FileHelper.PNG_FILTER"), ".png"); + /** File filter for XML files (*.xml) */ + public static final FileFilter XML_FILTER = + new SimpleFileFilter(trans.get("FileHelper.XML_FILTER"), ".xml"); + diff --git a/swing/src/net/sf/openrocket/gui/util/PreferencesExporter.java b/swing/src/net/sf/openrocket/gui/util/PreferencesExporter.java new file mode 100644 index 000000000..ab40b7114 --- /dev/null +++ b/swing/src/net/sf/openrocket/gui/util/PreferencesExporter.java @@ -0,0 +1,202 @@ +package net.sf.openrocket.gui.util; + +import net.sf.openrocket.arch.SystemInfo; +import net.sf.openrocket.gui.components.PreferencesOptionPanel; +import net.sf.openrocket.gui.main.MRUDesignFile; +import net.sf.openrocket.gui.widgets.SaveFileChooser; +import net.sf.openrocket.l10n.Translator; +import net.sf.openrocket.startup.Application; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.NodeList; +import org.xml.sax.SAXException; + +import javax.swing.JFileChooser; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.transform.OutputKeys; +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerException; +import javax.xml.transform.TransformerFactory; +import javax.xml.transform.dom.DOMSource; +import javax.xml.transform.stream.StreamResult; +import java.awt.Dimension; +import java.awt.Window; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; +import java.util.prefs.BackingStoreException; +import java.util.prefs.Preferences; + +public abstract class PreferencesExporter { + private static final Translator trans = Application.getTranslator(); + private static final Logger log = LoggerFactory.getLogger(PreferencesExporter.class); + private static final net.sf.openrocket.startup.Preferences prefs = Application.getPreferences(); + + private static final List keysToIgnore = new ArrayList<>(); // Preference keys to ignore when exporting user directories (= keys that export user directories) + private static final List prefixKeysToIgnore = new ArrayList<>(); // Preference keys to ignore when exporting user directories (= keys that start with these prefixes), e.g. + private static final List nodesToIgnore = new ArrayList<>(); // Preferences nodes that should not be exported + + public static boolean exportPreferences(Window parent, Preferences preferences) { + JFileChooser chooser = new SaveFileChooser(); + chooser.setDialogTitle(trans.get("PreferencesExporter.chooser.title")); + chooser.setAcceptAllFileFilterUsed(false); + chooser.setFileFilter(FileHelper.XML_FILTER); + chooser.setCurrentDirectory(((SwingPreferences) Application.getPreferences()).getDefaultDirectory()); + PreferencesOptionPanel options = new PreferencesOptionPanel(); + chooser.setAccessory(options); + + // TODO: update this dynamically instead of hard-coded values + // The macOS file chooser has an issue where it does not update its size when the accessory is added. + if (SystemInfo.getPlatform() == SystemInfo.Platform.MAC_OS) { + Dimension currentSize = chooser.getPreferredSize(); + Dimension newSize = new Dimension((int) (1.35 * currentSize.width), (int) (1.2 * currentSize.height)); + chooser.setPreferredSize(newSize); + } + + //// Ensures No Problems When Choosing File + if (chooser.showSaveDialog(parent) != JFileChooser.APPROVE_OPTION) { + log.info("Cancelled export of preferences."); + return false; + } + + ((SwingPreferences) prefs).setDefaultDirectory(chooser.getCurrentDirectory()); + + File file = chooser.getSelectedFile(); + if (file == null) { + log.info("No file selected to export preferences to."); + return false; + } + + final File newFile = FileHelper.forceExtension(file, "xml"); + if (!FileHelper.confirmWrite(newFile, parent)) { + log.info("Cancelled export of preferences."); + return false; + } + + // Decide which keys/nodes to ignore + boolean ignoreUserDirectories = options.isIgnoreUserDirectories(); + boolean ignoreWindowInformation = options.isIgnoreWindowInformation(); + fillIgnoreKeys(ignoreUserDirectories, ignoreWindowInformation); + + try (FileOutputStream fos = new FileOutputStream(newFile)) { + if (keysToIgnore.isEmpty() && nodesToIgnore.isEmpty() && prefixKeysToIgnore.isEmpty()) { + // Export all preferences + preferences.exportSubtree(fos); + } else { + // Export all preferences except user directories + exportFilteredPreferences(preferences, fos); + } + log.info("Preferences exported successfully."); + } catch (IOException | BackingStoreException e) { + log.warn("Error while importing preferences: " + e.getMessage()); + } + + return true; + } + + private static void fillIgnoreKeys(boolean ignoreUserDirectories, boolean ignoreWindowInformation) { + keysToIgnore.clear(); + prefixKeysToIgnore.clear(); + nodesToIgnore.clear(); + + if (ignoreUserDirectories) { + keysToIgnore.add(net.sf.openrocket.startup.Preferences.USER_THRUST_CURVES_KEY); + keysToIgnore.add(net.sf.openrocket.startup.Preferences.DEFAULT_DIRECTORY); + prefixKeysToIgnore.add(MRUDesignFile.MRU_FILE_LIST_PROPERTY); + } + + if (ignoreWindowInformation) { + nodesToIgnore.add(SwingPreferences.NODE_WINDOWS); + nodesToIgnore.add(SwingPreferences.NODE_TABLES); + } + + keysToIgnore.add(SwingPreferences.UPDATE_PLATFORM); // Don't export platform-specific settings + } + + private static void exportFilteredPreferences(Preferences preferences, FileOutputStream fos) throws BackingStoreException, IOException { + // Filter out user directories + Preferences root = Preferences.userRoot(); + String originalNodeName = ((SwingPreferences) prefs).getNodename(); + String nodeName = originalNodeName + "-temp"; + if (root.nodeExists(nodeName)) { + root.node(nodeName).removeNode(); + } + Preferences tempPrefs = root.node(nodeName); + + // Fill in all parameters to the temporary preferences, except for user directories + copyFilteredPreferences(preferences, tempPrefs, nodesToIgnore, keysToIgnore, prefixKeysToIgnore); + + // Export the filtered preferences + try { + // Export the filtered preferences to a temporary file + Path tempFile = Files.createTempFile("ORprefs_" + System.currentTimeMillis(), ".xml"); + try (FileOutputStream tempFos = new FileOutputStream(tempFile.toFile())) { + tempPrefs.exportSubtree(tempFos); + } + + // Read and parse the temporary file + DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); + Document doc; + try (FileInputStream tempFis = new FileInputStream(tempFile.toFile())) { + doc = factory.newDocumentBuilder().parse(tempFis); + } + + // Find and rename the node + NodeList nodeList = doc.getElementsByTagName("node"); + for (int i = 0; i < nodeList.getLength(); i++) { + Element element = (Element) nodeList.item(i); + if (element.getAttribute("name").equals(nodeName)) { + element.setAttribute("name", ((SwingPreferences) prefs).getNodename()); + break; + } + } + + // Create a transformer to write the XML document to the FileOutputStream + Transformer transformer = TransformerFactory.newInstance().newTransformer(); + + // Set output properties to include the correct DOCTYPE declaration + transformer.setOutputProperty(OutputKeys.DOCTYPE_SYSTEM, "http://java.sun.com/dtd/preferences.dtd"); + transformer.setOutputProperty(OutputKeys.STANDALONE, "no"); + + // Write the XML document to the FileOutputStream + DOMSource source = new DOMSource(doc); + StreamResult result = new StreamResult(fos); + transformer.transform(source, result); + + // Clean up the temporary file + Files.deleteIfExists(tempFile); + } catch (ParserConfigurationException | TransformerException | SAXException e) { + e.printStackTrace(); + } finally { + root.node(nodeName).removeNode(); + } + } + + private static void copyFilteredPreferences(Preferences src, Preferences dest, + List nodesToIgnore, List keysToIgnore, List prefixKeysToIgnore) throws BackingStoreException { + for (String key : src.keys()) { + if (keysToIgnore.contains(key) + || prefixKeysToIgnore.stream().anyMatch(key::startsWith)) { + continue; + } + dest.put(key, src.get(key, null)); + } + + for (String childNodeName : src.childrenNames()) { + if (nodesToIgnore.contains(childNodeName)) { + continue; + } + Preferences srcChild = src.node(childNodeName); + Preferences destChild = dest.node(childNodeName); + copyFilteredPreferences(srcChild, destChild, nodesToIgnore, keysToIgnore, prefixKeysToIgnore); + } + } +} diff --git a/swing/src/net/sf/openrocket/gui/util/PreferencesImporter.java b/swing/src/net/sf/openrocket/gui/util/PreferencesImporter.java new file mode 100644 index 000000000..32a4044db --- /dev/null +++ b/swing/src/net/sf/openrocket/gui/util/PreferencesImporter.java @@ -0,0 +1,45 @@ +package net.sf.openrocket.gui.util; + +import net.sf.openrocket.l10n.Translator; +import net.sf.openrocket.startup.Application; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.swing.JFileChooser; +import java.awt.Window; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.util.prefs.InvalidPreferencesFormatException; +import java.util.prefs.Preferences; + +public abstract class PreferencesImporter { + private static final Translator trans = Application.getTranslator(); + private static final Logger log = LoggerFactory.getLogger(PreferencesImporter.class); + + public static boolean importPreferences(Window parent) { + final JFileChooser chooser = new JFileChooser(); + chooser.setDialogTitle(trans.get("PreferencesImporter.chooser.title")); + chooser.setCurrentDirectory(((SwingPreferences) Application.getPreferences()).getDefaultDirectory()); + chooser.setFileFilter(FileHelper.XML_FILTER); + chooser.setAcceptAllFileFilterUsed(false); + + int returnVal = chooser.showOpenDialog(parent); + if (returnVal != JFileChooser.APPROVE_OPTION) { + log.info("Cancelled import of preferences."); + return false; + } + + ((SwingPreferences) Application.getPreferences()).setDefaultDirectory(chooser.getCurrentDirectory()); + + File importFile = chooser.getSelectedFile(); + try (FileInputStream fis = new FileInputStream(importFile)) { + Preferences.importPreferences(fis); + log.info("Preferences imported successfully."); + } catch (IOException | InvalidPreferencesFormatException e) { + log.warn("Error while importing preferences: " + e.getMessage()); + } + + return true; + } +} diff --git a/swing/src/net/sf/openrocket/gui/util/SwingPreferences.java b/swing/src/net/sf/openrocket/gui/util/SwingPreferences.java index bf30f6c39..51be68159 100644 --- a/swing/src/net/sf/openrocket/gui/util/SwingPreferences.java +++ b/swing/src/net/sf/openrocket/gui/util/SwingPreferences.java @@ -2,7 +2,11 @@ package net.sf.openrocket.gui.util; import java.awt.Color; import java.awt.Dimension; +import java.awt.GraphicsConfiguration; +import java.awt.GraphicsDevice; +import java.awt.GraphicsEnvironment; import java.awt.Point; +import java.awt.Rectangle; import java.io.File; import java.util.ArrayList; import java.util.Arrays; @@ -38,7 +42,11 @@ public class SwingPreferences extends net.sf.openrocket.startup.Preferences { private static final Logger log = LoggerFactory.getLogger(SwingPreferences.class); private static final String SPLIT_CHARACTER = "|"; - + + + public static final String NODE_WINDOWS = "windows"; + public static final String NODE_TABLES = "tables"; + public static final String UPDATE_PLATFORM = "UpdatePlatform"; private static final List SUPPORTED_LOCALES; static { @@ -88,10 +96,16 @@ public class SwingPreferences extends net.sf.openrocket.startup.Preferences { PREFNODE = root.node(NODENAME); } - + public String getNodename() { + return NODENAME; + } ////////////////////// + + public Preferences getPreferences() { + return PREFNODE; + } public void clearPreferences() { try { @@ -114,7 +128,22 @@ public class SwingPreferences extends net.sf.openrocket.startup.Preferences { private void storeVersion() { PREFNODE.put("OpenRocketVersion", BuildProperties.getVersion()); } - + + /** + * Checks if a certain key exists in the node + * @param node node to check the keys of. + * @param key key to check + * @return true if the key is stored in the preferences, false otherwise + */ + private boolean keyExists(Preferences node, String key) { + try { + return Arrays.asList(node.keys()).contains(key); + } catch (BackingStoreException e) { + e.printStackTrace(); + return false; + } + } + /** * Return a string preference. * @@ -124,12 +153,28 @@ public class SwingPreferences extends net.sf.openrocket.startup.Preferences { */ @Override public String getString(String key, String def) { + if (!keyExists(PREFNODE, key) && key != null && def != null) { + PREFNODE.put(key, def); + try { + PREFNODE.flush(); + } catch (BackingStoreException e) { + e.printStackTrace(); + } + } return PREFNODE.get(key, def); } @Override public String getString(String directory, String key, String defaultValue) { Preferences p = PREFNODE.node(directory); + if (!keyExists(p, key) && key != null && defaultValue != null) { + p.put(key, defaultValue); + try { + p.flush(); + } catch (BackingStoreException e) { + e.printStackTrace(); + } + } return p.get(key, defaultValue); } @@ -169,6 +214,16 @@ public class SwingPreferences extends net.sf.openrocket.startup.Preferences { */ @Override public boolean getBoolean(String key, boolean def) { + // Check if the key exists + if (!keyExists(PREFNODE, key) && key != null) { + // Save the default value + PREFNODE.putBoolean(key, def); + try { + PREFNODE.flush(); + } catch (BackingStoreException e) { + e.printStackTrace(); + } + } return PREFNODE.getBoolean(key, def); } @@ -183,9 +238,17 @@ public class SwingPreferences extends net.sf.openrocket.startup.Preferences { PREFNODE.putBoolean(key, value); storeVersion(); } - + @Override public int getInt(String key, int defaultValue) { + if (!keyExists(PREFNODE, key) && key != null) { + PREFNODE.putInt(key, defaultValue); + try { + PREFNODE.flush(); + } catch (BackingStoreException e) { + e.printStackTrace(); + } + } return PREFNODE.getInt(key, defaultValue); } @@ -194,9 +257,17 @@ public class SwingPreferences extends net.sf.openrocket.startup.Preferences { PREFNODE.putInt(key, value); storeVersion(); } - + @Override public double getDouble(String key, double defaultValue) { + if (!keyExists(PREFNODE, key) && key != null) { + PREFNODE.putDouble(key, defaultValue); + try { + PREFNODE.flush(); + } catch (BackingStoreException e) { + e.printStackTrace(); + } + } return PREFNODE.getDouble(key, defaultValue); } @@ -228,7 +299,7 @@ public class SwingPreferences extends net.sf.openrocket.startup.Preferences { } public File getDefaultDirectory() { - String file = getString("defaultDirectory", null); + String file = getString(net.sf.openrocket.startup.Preferences.DEFAULT_DIRECTORY, null); if (file == null) return null; return new File(file); @@ -241,7 +312,7 @@ public class SwingPreferences extends net.sf.openrocket.startup.Preferences { } else { d = dir.getAbsolutePath(); } - putString("defaultDirectory", d); + putString(net.sf.openrocket.startup.Preferences.DEFAULT_DIRECTORY, d); storeVersion(); } @@ -262,13 +333,22 @@ public class SwingPreferences extends net.sf.openrocket.startup.Preferences { return compdir; } + /** + * Set the operating system that the software updater will use to redirect you to an installer download link. + * @param platform the operating system to use + */ public void setUpdatePlatform(UpdatePlatform platform) { if (platform == null) return; - putString("UpdatePlatform", platform.name()); + putString(UPDATE_PLATFORM, platform.name()); } + /** + * Get the operating system that will be selected when asking for a software update. + * E.g. "Windows" will cause the software updater to default to letting you download a Windows installer. + * @return the operating system that is used + */ public UpdatePlatform getUpdatePlatform() { - String p = getString("UpdatePlatform", SystemInfo.getPlatform().name()); + String p = getString(UPDATE_PLATFORM, SystemInfo.getPlatform().name()); if (p == null) return null; return UpdatePlatform.valueOf(p); } @@ -372,7 +452,7 @@ public class SwingPreferences extends net.sf.openrocket.startup.Preferences { public Point getWindowPosition(Class c) { int x, y; - String pref = PREFNODE.node("windows").get("position." + c.getCanonicalName(), null); + String pref = PREFNODE.node(NODE_WINDOWS).get("position." + c.getCanonicalName(), null); if (pref == null) return null; @@ -386,20 +466,46 @@ public class SwingPreferences extends net.sf.openrocket.startup.Preferences { } catch (NumberFormatException e) { return null; } - return new Point(x, y); + + // If position was on a screen that is not available anymore + Point p = new Point(x, y); + if (!isPointOnScreen(p)) { + return null; + } + + return p; } public void setWindowPosition(Class c, Point p) { - PREFNODE.node("windows").put("position." + c.getCanonicalName(), "" + p.x + "," + p.y); + PREFNODE.node(NODE_WINDOWS).put("position." + c.getCanonicalName(), "" + p.x + "," + p.y); storeVersion(); } - - + + /** + * Checks whether the point is present on any of the current monitor screens. + * Can return false if point was e.g. referenced on a secondary monitor that doesn't exist anymore. + * @param p point to check + * @return true if point is present on any of the current screens, false otherwise + */ + private boolean isPointOnScreen(Point p) { + GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment(); + GraphicsDevice[] screens = ge.getScreenDevices(); + + for (GraphicsDevice screen : screens) { + GraphicsConfiguration gc = screen.getDefaultConfiguration(); + Rectangle bounds = gc.getBounds(); + if (bounds.contains(p)) { + return true; + } + } + return false; + } + public Dimension getWindowSize(Class c) { int x, y; - String pref = PREFNODE.node("windows").get("size." + c.getCanonicalName(), null); + String pref = PREFNODE.node(NODE_WINDOWS).get("size." + c.getCanonicalName(), null); if (pref == null) return null; @@ -418,22 +524,22 @@ public class SwingPreferences extends net.sf.openrocket.startup.Preferences { public boolean isWindowMaximized(Class c) { - String pref = PREFNODE.node("windows").get("size." + c.getCanonicalName(), null); + String pref = PREFNODE.node(NODE_WINDOWS).get("size." + c.getCanonicalName(), null); return "max".equals(pref); } public void setWindowSize(Class c, Dimension d) { - PREFNODE.node("windows").put("size." + c.getCanonicalName(), "" + d.width + "," + d.height); + PREFNODE.node(NODE_WINDOWS).put("size." + c.getCanonicalName(), "" + d.width + "," + d.height); storeVersion(); } public void setWindowMaximized(Class c) { - PREFNODE.node("windows").put("size." + c.getCanonicalName(), "max"); + PREFNODE.node(NODE_WINDOWS).put("size." + c.getCanonicalName(), "max"); storeVersion(); } public Integer getTableColumnWidth(String keyName, int columnIdx) { - String pref = PREFNODE.node("tables").get( + String pref = PREFNODE.node(NODE_TABLES).get( "cw." + keyName + "." + columnIdx, null); if (pref == null) return null; @@ -451,7 +557,7 @@ public class SwingPreferences extends net.sf.openrocket.startup.Preferences { } public void setTableColumnWidth(String keyName, int columnIdx, Integer width) { - PREFNODE.node("tables").put( + PREFNODE.node(NODE_TABLES).put( "cw." + keyName + "." + columnIdx, width.toString()); storeVersion(); } diff --git a/swing/src/net/sf/openrocket/utils/ComponentPresetEditor.java b/swing/src/net/sf/openrocket/utils/ComponentPresetEditor.java index a70cfc878..a1baf53f3 100644 --- a/swing/src/net/sf/openrocket/utils/ComponentPresetEditor.java +++ b/swing/src/net/sf/openrocket/utils/ComponentPresetEditor.java @@ -342,6 +342,8 @@ public class ComponentPresetEditor extends JPanel implements PresetResultListene log.info(Markers.USER_MARKER, "User decided not to open, option=" + option); return false; } + + ((SwingPreferences) Application.getPreferences()).setDefaultDirectory(chooser.getCurrentDirectory()); File file = chooser.getSelectedFile(); try {