diff --git a/.github/ISSUE_TEMPLATE/bug-report.md b/.github/ISSUE_TEMPLATE/bug-report.md deleted file mode 100644 index 64b583a6d..000000000 --- a/.github/ISSUE_TEMPLATE/bug-report.md +++ /dev/null @@ -1,31 +0,0 @@ ---- -name: Bug report -about: Help us make OpenRocket better -title: '[Bug] ' -labels: bug -assignees: '' - ---- - -**Describe the bug** -A clear and concise description of what the bug is. - -**To Reproduce** -Steps to reproduce the behavior: - -**Expected behavior** -A clear and concise description of what you expected to happen. - -**Screenshots** -If applicable, add screenshots to help explain your problem. - -**ORK File** -If the problem is triggered by a particular ORK file, please zip it and attach it to this issue. - -**Platform** - - OS: [e.g. Windows10] - - OpenRocket version (e.g. 22-02) - - Graphics card (if a graphics or display-related problem) - -**Additional context** -Add any other context about the problem here. diff --git a/.github/ISSUE_TEMPLATE/bug-report.yml b/.github/ISSUE_TEMPLATE/bug-report.yml new file mode 100644 index 000000000..d45d6bb50 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug-report.yml @@ -0,0 +1,70 @@ +name: Bug report +description: Help us make OpenRocket better +title: '[Bug] ' +labels: bug +body: +- type: markdown + attributes: + value: "Thanks for taking the time to fill out this bug report!" +- type: textarea + id: description + attributes: + label: Describe the bug + description: What happened? Also tell us what you've expect to happen. + placeholder: Describe your bug in detail. + validations: + required: true +- type: textarea + id: repro + attributes: + label: To Reproduce + description: "Steps to reproduce the behavior:" + value: | + 1. + 2. + 3. + ... + validations: + required: true +- type: textarea + id: files + attributes: + label: Screenshots / .ork file + description: Provide screenshots for clarification and/or the .ork file that caused the issue. + value: | + #### Screenshot(s): + *(drag-and-drop the screenshot(s) here)* + + + #### .ork file: + *(drag-and-drop the file here as a .zip file)* + + + validations: + required: false +- type: input + id: version-openrocket + attributes: + label: OpenRocket version + description: In what version(s) of OpenRocket does this bug happen? + placeholder: 22.02, unstable... + validations: + required: true +- type: dropdown + id: platform + attributes: + label: What platform are you running on? + options: + - Windows + - macOS + - Linux + validations: + required: true +- type: textarea + id: context + attributes: + label: Additional context + description: Add any other context about the problem here. + placeholder: + validations: + required: false diff --git a/core/resources/l10n/messages.properties b/core/resources/l10n/messages.properties index 114860e9e..61f740dd0 100644 --- a/core/resources/l10n/messages.properties +++ b/core/resources/l10n/messages.properties @@ -324,6 +324,8 @@ pref.dlg.checkbox.AlwaysOpenLeftmost = Always open leftmost tab when opening a c pref.dlg.checkbox.AlwaysOpenLeftmost.ttip = If checked, a component edit dialog will always pop up with the first tab selected.
If unchecked, the previous selected tab will be used. pref.dlg.checkbox.ShowDiscardConfirmation = Show confirmation dialog for discarding component changes pref.dlg.checkbox.ShowDiscardConfirmation.ttip = If checked, you will be asked if you want really want to discard component configuration configuration changes. +pref.dlg.checkbox.ShowDiscardSimulationConfirmation = Show confirmation dialog for discarding simulation changes +pref.dlg.checkbox.ShowDiscardSimulationConfirmation.ttip = If checked, you will be asked if you want really want to discard simulation configuration configuration changes. pref.dlg.lbl.User-definedthrust = User-defined thrust curves: pref.dlg.lbl.Windspeed = Wind speed pref.dlg.Allthrustcurvefiles = All thrust curve files (*.eng; *.rse; *.zip; directories) @@ -413,6 +415,8 @@ simedtdlg.but.savedefault = Save as default simedtdlg.but.add = Add simedtdlg.but.delete = Delete simedtdlg.title.Editsim = Edit simulation +simedtdlg.title.MultiSimEdit = Multi-simulation edit +simedtdlg.title.MultiSimEdit.ttip = You are editing the following simulations:
simedtdlg.lbl.Simname = Simulation name: simedtdlg.tab.Launchcond = Launch conditions simedtdlg.tab.Simopt = Simulation options @@ -522,6 +526,12 @@ SimulationEditDialog.btn.export = Export SimulationEditDialog.btn.edit = Edit SimulationEditDialog.btn.simulate = Simulate SimulationEditDialog.btn.simulateAndPlot = Simulate & Plot +SimulationEditDialog.btn.OK.ttip = Keep changes and close the dialog +SimulationEditDialog.btn.Cancel.ttip = Discard changes and close the dialog +SimulationEditDialog.CancelOperation.msg.discardChanges = Are you sure you want to discard your changes to this simulation? +SimulationEditDialog.CancelOperation.msg.undoAdd = Are you sure you want to undo adding this simulation? +SimulationEditDialog.CancelOperation.title = Cancel operation +SimulationEditDialog.CancelOperation.checkbox.dontAskAgain = Don't ask me again GeodeticComputationStrategy.flat.name = Flat Earth GeodeticComputationStrategy.flat.desc = Perform computations with a flat Earth approximation. Sufficient for low-altitude flights. @@ -2344,6 +2354,7 @@ PhotoSettingsConfig.lbl.lightAz = Light Azimuth PhotoSettingsConfig.lbl.lightAlt = Light Altitude PhotoSettingsConfig.lbl.sky = Sky PhotoSettingsConfig.lbl.skyColor = Sky Color +PhotoSettingsConfig.lbl.skyColorOpacity = Sky Color Opacity PhotoSettingsConfig.lbl.skyImage = Sky Image PhotoSettingsConfig.lbl.skyCredit = Image Credit diff --git a/core/src/net/sf/openrocket/aerodynamics/barrowman/FinSetCalc.java b/core/src/net/sf/openrocket/aerodynamics/barrowman/FinSetCalc.java index 59072fd9b..dfd9a319f 100644 --- a/core/src/net/sf/openrocket/aerodynamics/barrowman/FinSetCalc.java +++ b/core/src/net/sf/openrocket/aerodynamics/barrowman/FinSetCalc.java @@ -234,9 +234,9 @@ public class FinSetCalc extends RocketComponentCalc { finArea = component.getPlanformArea(); ar = 2 * pow2(span) / finArea; + // Check geometry; don't consider points along fin root for this + // (doing so will cause spurious jagged fin warnings) Coordinate[] points = component.getFinPoints(); - - // Check geometry geometryWarnings.clear(); boolean down = false; for (int i = 1; i < points.length; i++) { @@ -258,8 +258,9 @@ public class FinSetCalc extends RocketComponentCalc { geometryWarnings.add(Warning.THICK_FIN, component.toString()); } - // Calculate the chord lead and trail positions and length - + // Calculate the chord lead and trail positions and length. We do need the points + // along the root for this + points = component.getFinPointsWithRoot(); Arrays.fill(chordLead, Double.POSITIVE_INFINITY); Arrays.fill(chordTrail, Double.NEGATIVE_INFINITY); Arrays.fill(chordLength, 0); diff --git a/core/src/net/sf/openrocket/document/Simulation.java b/core/src/net/sf/openrocket/document/Simulation.java index b560cd1a8..0eacf4795 100644 --- a/core/src/net/sf/openrocket/document/Simulation.java +++ b/core/src/net/sf/openrocket/document/Simulation.java @@ -268,6 +268,18 @@ public class Simulation implements ChangeSource, Cloneable { mutex.verify(); return simulationExtensions; } + + /** + * Applies the simulation extensions to the simulation. + * @param extensions the simulation extensions to apply. + */ + public void copyExtensionsFrom(List extensions) { + if (extensions == null) { + return; + } + this.simulationExtensions.clear(); + this.simulationExtensions.addAll(extensions); + } /** @@ -349,6 +361,7 @@ public class Simulation implements ChangeSource, Cloneable { */ public void syncModID() { this.simulatedConfigurationID = getActiveConfiguration().getModID(); + fireChangeEvent(); } @@ -522,6 +535,67 @@ public class Simulation implements ChangeSource, Cloneable { mutex.unlock("copy"); } } + + public Simulation clone() { + mutex.lock("clone"); + try { + Simulation clone = (Simulation) super.clone(); + + clone.mutex = SafetyMutex.newInstance(); + clone.name = this.name; + clone.configId = this.configId; + clone.simulatedConfigurationDescription = this.simulatedConfigurationDescription; + clone.simulatedConfigurationID = this.simulatedConfigurationID; + clone.options = this.options.clone(); + clone.listeners = new ArrayList<>(); + if (this.simulatedConditions != null) { + clone.simulatedConditions = this.simulatedConditions.clone(); + } else { + clone.simulatedConditions = null; + } + clone.simulationExtensions = new ArrayList<>(); + for (SimulationExtension c : this.simulationExtensions) { + clone.simulationExtensions.add(c.clone()); + } + clone.status = this.status; + clone.simulatedData = this.simulatedData != null ? this.simulatedData.clone() : this.simulatedData; + clone.simulationStepperClass = this.simulationStepperClass; + clone.aerodynamicCalculatorClass = this.aerodynamicCalculatorClass; + + return clone; + } catch (CloneNotSupportedException e) { + throw new BugException("Clone not supported, BUG", e); + } finally { + mutex.unlock("clone"); + } + } + + /** + * Load the data from the specified simulation into this simulation. + * @param simulation the simulation to load from. + */ + public void loadFrom(Simulation simulation) { + mutex.lock("loadFrom"); + try { + this.name = simulation.name; + this.configId = simulation.configId; + this.simulatedConfigurationDescription = simulation.simulatedConfigurationDescription; + this.simulatedConfigurationID = simulation.simulatedConfigurationID; + this.options.copyConditionsFrom(simulation.options); + if (simulation.simulatedConditions == null) { + this.simulatedConditions = null; + } else { + this.simulatedConditions.copyConditionsFrom(simulation.simulatedConditions); + } + copyExtensionsFrom(simulation.getSimulationExtensions()); + this.status = simulation.status; + this.simulatedData = simulation.simulatedData; + this.simulationStepperClass = simulation.simulationStepperClass; + this.aerodynamicCalculatorClass = simulation.aerodynamicCalculatorClass; + } finally { + mutex.unlock("loadFrom"); + } + } /** @@ -541,7 +615,7 @@ public class Simulation implements ChangeSource, Cloneable { final Simulation newSim = new Simulation(this.document, newRocket); newSim.name = this.name; newSim.configId = this.configId; - newSim.options.copyFrom(this.options); + newSim.options.copyConditionsFrom(this.options); newSim.simulatedConfigurationDescription = this.simulatedConfigurationDescription; for (SimulationExtension c : this.simulationExtensions) { newSim.simulationExtensions.add(c.clone()); @@ -584,15 +658,9 @@ public class Simulation implements ChangeSource, Cloneable { private class ConditionListener implements StateChangeListener { - - private Status oldStatus = null; - @Override public void stateChanged(EventObject e) { - if (getStatus() != oldStatus) { - oldStatus = getStatus(); - fireChangeEvent(); - } + fireChangeEvent(); } } diff --git a/core/src/net/sf/openrocket/file/openrocket/OpenRocketSaver.java b/core/src/net/sf/openrocket/file/openrocket/OpenRocketSaver.java index 77602523d..9169348c6 100644 --- a/core/src/net/sf/openrocket/file/openrocket/OpenRocketSaver.java +++ b/core/src/net/sf/openrocket/file/openrocket/OpenRocketSaver.java @@ -490,7 +490,6 @@ public class OpenRocketSaver extends RocketSaver { for (int i = 0; i < types.length; i++) { data.add(branch.get(types[i])); } - List timeData = branch.get(FlightDataType.TYPE_TIME); // Build the tag StringBuilder sb = new StringBuilder(); diff --git a/core/src/net/sf/openrocket/simulation/FlightData.java b/core/src/net/sf/openrocket/simulation/FlightData.java index 9f6c2386a..7a559b3f2 100644 --- a/core/src/net/sf/openrocket/simulation/FlightData.java +++ b/core/src/net/sf/openrocket/simulation/FlightData.java @@ -273,7 +273,23 @@ public class FlightData { return mutable.isMutable(); } - + public FlightData clone() { + FlightData clone = new FlightData(); + clone.warnings.addAll(warnings); + for (FlightDataBranch b : branches) { + clone.branches.add(b.clone()); + } + clone.maxAltitude = maxAltitude; + clone.maxVelocity = maxVelocity; + clone.maxAcceleration = maxAcceleration; + clone.maxMachNumber = maxMachNumber; + clone.timeToApogee = timeToApogee; + clone.flightTime = flightTime; + clone.groundHitVelocity = groundHitVelocity; + clone.launchRodVelocity = launchRodVelocity; + clone.deploymentVelocity = deploymentVelocity; + return clone; + } /** * Find the maximum acceleration before apogee. diff --git a/core/src/net/sf/openrocket/simulation/FlightDataBranch.java b/core/src/net/sf/openrocket/simulation/FlightDataBranch.java index 77bc60f56..e2547e4c4 100644 --- a/core/src/net/sf/openrocket/simulation/FlightDataBranch.java +++ b/core/src/net/sf/openrocket/simulation/FlightDataBranch.java @@ -363,5 +363,20 @@ public class FlightDataBranch implements Monitorable { public int getModID() { return modID; } + + public FlightDataBranch clone() { + FlightDataType[] types = getTypes(); + FlightDataBranch clone = new FlightDataBranch(branchName, types); + for (FlightDataType type : values.keySet()) { + clone.values.put(type, values.get(type).clone()); + } + clone.minValues.putAll(minValues); + clone.maxValues.putAll(maxValues); + clone.events.addAll(events); + clone.timeToOptimumAltitude = timeToOptimumAltitude; + clone.optimumAltitude = optimumAltitude; + clone.modID = modID; + return clone; + } } diff --git a/core/src/net/sf/openrocket/simulation/SimulationOptions.java b/core/src/net/sf/openrocket/simulation/SimulationOptions.java index 9b9a8e465..9b895aabf 100644 --- a/core/src/net/sf/openrocket/simulation/SimulationOptions.java +++ b/core/src/net/sf/openrocket/simulation/SimulationOptions.java @@ -96,6 +96,7 @@ public class SimulationOptions implements ChangeSource, Cloneable { if (MathUtil.equals(this.launchRodLength, launchRodLength)) return; this.launchRodLength = launchRodLength; + fireChangeEvent(); } @@ -104,7 +105,10 @@ public class SimulationOptions implements ChangeSource, Cloneable { } public void setLaunchIntoWind(boolean i) { + if (launchIntoWind == i) + return; launchIntoWind = i; + fireChangeEvent(); } public double getLaunchRodAngle() { @@ -404,28 +408,6 @@ public class SimulationOptions implements ChangeSource, Cloneable { } } - - public void copyFrom(SimulationOptions src) { - - this.launchAltitude = src.launchAltitude; - this.launchLatitude = src.launchLatitude; - this.launchLongitude = src.launchLongitude; - this.launchPressure = src.launchPressure; - this.launchRodAngle = src.launchRodAngle; - this.launchRodDirection = src.launchRodDirection; - this.launchRodLength = src.launchRodLength; - this.launchTemperature = src.launchTemperature; - this.maximumAngle = src.maximumAngle; - this.timeStep = src.timeStep; - this.windAverage = src.windAverage; - this.windTurbulence = src.windTurbulence; - this.windDirection = src.windDirection; - this.calculateExtras = src.calculateExtras; - this.randomSeed = src.randomSeed; - - fireChangeEvent(); - } - public void copyConditionsFrom(SimulationOptions src) { // Be a little smart about triggering the change event. // only do it if one of the "important" (user specified) parameters has really changed. @@ -442,10 +424,6 @@ public class SimulationOptions implements ChangeSource, Cloneable { isChanged = true; this.launchLongitude = src.launchLongitude; } - if (this.launchPressure != src.launchPressure) { - isChanged = true; - this.launchPressure = src.launchPressure; - } if (this.launchRodAngle != src.launchRodAngle) { isChanged = true; this.launchRodAngle = src.launchRodAngle; @@ -458,19 +436,26 @@ public class SimulationOptions implements ChangeSource, Cloneable { isChanged = true; this.launchRodLength = src.launchRodLength; } + if (this.launchIntoWind != src.launchIntoWind) { + isChanged = true; + this.launchIntoWind = src.launchIntoWind; + } + if (this.useISA != src.useISA) { + isChanged = true; + this.useISA = src.useISA; + } if (this.launchTemperature != src.launchTemperature) { isChanged = true; this.launchTemperature = src.launchTemperature; } + if (this.launchPressure != src.launchPressure) { + isChanged = true; + this.launchPressure = src.launchPressure; + } if (this.maximumAngle != src.maximumAngle) { isChanged = true; this.maximumAngle = src.maximumAngle; } - this.maximumAngle = src.maximumAngle; - if (this.timeStep != src.timeStep) { - isChanged = true; - this.timeStep = src.timeStep; - } if (this.windAverage != src.windAverage) { isChanged = true; this.windAverage = src.windAverage; @@ -487,6 +472,14 @@ public class SimulationOptions implements ChangeSource, Cloneable { isChanged = true; this.calculateExtras = src.calculateExtras; } + if (this.timeStep != src.timeStep) { + isChanged = true; + this.timeStep = src.timeStep; + } + if (this.geodeticComputation != src.geodeticComputation) { + isChanged = true; + this.geodeticComputation = src.geodeticComputation; + } if (isChanged) { // Only copy the randomSeed if something else has changed. @@ -539,6 +532,10 @@ public class SimulationOptions implements ChangeSource, Cloneable { public void removeChangeListener(StateChangeListener listener) { listeners.remove(listener); } + + public List getChangeListeners() { + return listeners; + } private final EventObject event = new EventObject(this); diff --git a/core/src/net/sf/openrocket/startup/Preferences.java b/core/src/net/sf/openrocket/startup/Preferences.java index 3bc6b357f..ade03131e 100644 --- a/core/src/net/sf/openrocket/startup/Preferences.java +++ b/core/src/net/sf/openrocket/startup/Preferences.java @@ -77,6 +77,7 @@ public abstract class Preferences implements ChangeSource { private static final String AUTO_OPEN_LAST_DESIGN = "AUTO_OPEN_LAST_DESIGN"; private static final String OPEN_LEFTMOST_DESIGN_TAB = "OPEN_LEFTMOST_DESIGN_TAB"; 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"; @@ -546,6 +547,22 @@ public abstract class Preferences implements ChangeSource { this.putBoolean(SHOW_DISCARD_CONFIRMATION, enabled); } + /** + * Answer if a confirmation dialog should be shown when canceling a simulation config operation. + * + * @return true if the confirmation dialog should be shown. + */ + public final boolean isShowDiscardSimulationConfirmation() { + return this.getBoolean(SHOW_DISCARD_SIMULATION_CONFIRMATION, true); + } + + /** + * Enable/Disable showing a confirmation warning when canceling a simulation config operation. + */ + public final void setShowDiscardSimulationConfirmation(boolean enabled) { + this.putBoolean(SHOW_DISCARD_SIMULATION_CONFIRMATION, enabled); + } + /** * Answer if the always open leftmost tab is enabled. * diff --git a/core/test/net/sf/openrocket/rocketcomponent/FreeformFinSetTest.java b/core/test/net/sf/openrocket/rocketcomponent/FreeformFinSetTest.java index 59638c8e4..a5ced6bbb 100644 --- a/core/test/net/sf/openrocket/rocketcomponent/FreeformFinSetTest.java +++ b/core/test/net/sf/openrocket/rocketcomponent/FreeformFinSetTest.java @@ -1557,4 +1557,49 @@ public class FreeformFinSetTest extends BaseTestCase { } } + /** + * Test that fins on transitions don't get NaN MAClength + */ + @Test + public void testFinsOnTransitions() { + // Rocket consisting of just a transition and a freeform fin set + final Rocket rocket = new Rocket(); + + final AxialStage stage = new AxialStage(); + rocket.addChild(stage); + + Transition trans = new Transition(); + stage.addChild(trans); + + FreeformFinSet fins = new FreeformFinSet(); + trans.addChild(fins); + + // set the finset at the beginning of the transition + fins.setAxialMethod(AxialMethod.TOP); + fins.setAxialOffset(0.0); + + // Test 1: transition getting smaller + trans.setForeRadius(trans.getForeRadius()*2.0); + fins.setPoints(new Coordinate[] { + new Coordinate(0, 0), + new Coordinate(trans.getLength(), 0), + new Coordinate(trans.getLength(), trans.getAftRadius() - trans.getForeRadius()) + }); + FinSetCalc calc = new FinSetCalc(fins); + + assertEquals(0.075, calc.getMACLength(), EPSILON); + + // Test 2: transition getting larger + trans.setForeRadius(trans.getAftRadius()); + trans.setAftRadius(trans.getAftRadius()*2.0); + fins.setPoints(new Coordinate[] { + new Coordinate(0, 0), + new Coordinate(0, trans.getAftRadius() - trans.getForeRadius()), + new Coordinate(trans.getLength(), trans.getAftRadius() - trans.getForeRadius()) + }); + calc = new FinSetCalc(fins); + + assertEquals(0.05053191489361704, calc.getMACLength(), EPSILON); + } + } diff --git a/swing/src/net/sf/openrocket/gui/configdialog/BulkheadConfig.java b/swing/src/net/sf/openrocket/gui/configdialog/BulkheadConfig.java index d1bdb5e63..4dcead2c9 100644 --- a/swing/src/net/sf/openrocket/gui/configdialog/BulkheadConfig.java +++ b/swing/src/net/sf/openrocket/gui/configdialog/BulkheadConfig.java @@ -21,8 +21,7 @@ public class BulkheadConfig extends RingComponentConfig { JPanel tab; - tab = generalTab(trans.get("BulkheadCfg.tab.Diameter"), null, null, - trans.get("BulkheadCfg.tab.Thickness")); + tab = generalTab(trans.get("BulkheadCfg.tab.Thickness"), trans.get("BulkheadCfg.tab.Diameter"), null, null); //// General and General properties tabbedPane.insertTab(trans.get("BulkheadCfg.tab.General"), null, tab, trans.get("BulkheadCfg.tab.Generalproperties"), 0); diff --git a/swing/src/net/sf/openrocket/gui/configdialog/CenteringRingConfig.java b/swing/src/net/sf/openrocket/gui/configdialog/CenteringRingConfig.java index 5ac0bcc75..c1c626761 100644 --- a/swing/src/net/sf/openrocket/gui/configdialog/CenteringRingConfig.java +++ b/swing/src/net/sf/openrocket/gui/configdialog/CenteringRingConfig.java @@ -22,9 +22,8 @@ public class CenteringRingConfig extends RingComponentConfig { JPanel tab; //// Outer diameter: and Inner diameter: and Thickness: - tab = generalTab(trans.get("CenteringRingCfg.tab.Outerdiam"), - trans.get("CenteringRingCfg.tab.Innerdiam"), null, - trans.get("CenteringRingCfg.tab.Thickness")); + tab = generalTab(trans.get("CenteringRingCfg.tab.Thickness"), trans.get("CenteringRingCfg.tab.Outerdiam"), + trans.get("CenteringRingCfg.tab.Innerdiam"), null); //// General and General properties tabbedPane.insertTab(trans.get("CenteringRingCfg.tab.General"), null, tab, trans.get("CenteringRingCfg.tab.Generalproperties"), 0); diff --git a/swing/src/net/sf/openrocket/gui/configdialog/InnerTubeConfig.java b/swing/src/net/sf/openrocket/gui/configdialog/InnerTubeConfig.java index 5c37c5a7e..1d3ef71b8 100644 --- a/swing/src/net/sf/openrocket/gui/configdialog/InnerTubeConfig.java +++ b/swing/src/net/sf/openrocket/gui/configdialog/InnerTubeConfig.java @@ -71,6 +71,19 @@ public class InnerTubeConfig extends RocketComponentConfig { //// ---------------------------- Attributes ---------------------------- + //// Length + panel.add(new JLabel(trans.get("ThicknessRingCompCfg.tab.Length"))); + m = new DoubleModel(component, "Length", UnitGroup.UNITS_LENGTH, 0); + + spin = new JSpinner(m.getSpinnerModel()); + spin.setEditor(new SpinnerEditor(spin)); + focusElement = spin; + panel.add(spin, "growx"); + order.add(((SpinnerEditor) spin.getEditor()).getTextField()); + + panel.add(new UnitSelector(m), "growx"); + panel.add(new BasicSlider(m.getSliderModel(0, 0.1, 1.0)), "w 100lp, wrap"); + //// Outer diameter panel.add(new JLabel(trans.get("ThicknessRingCompCfg.tab.Outerdiam"))); @@ -128,22 +141,6 @@ public class InnerTubeConfig extends RocketComponentConfig { panel.add(new UnitSelector(m), "growx"); panel.add(new BasicSlider(m.getSliderModel(0, 0.01)), "w 100lp, wrap"); - - //// Inner tube length - panel.add(new JLabel(trans.get("ThicknessRingCompCfg.tab.Length"))); - - //// Length - m = new DoubleModel(component, "Length", UnitGroup.UNITS_LENGTH, 0); - - spin = new JSpinner(m.getSpinnerModel()); - spin.setEditor(new SpinnerEditor(spin)); - focusElement = spin; - panel.add(spin, "growx"); - order.add(((SpinnerEditor) spin.getEditor()).getTextField()); - - panel.add(new UnitSelector(m), "growx"); - panel.add(new BasicSlider(m.getSliderModel(0, 0.1, 1.0)), "w 100lp, wrap"); - mainPanel.add(panel, "aligny 0, gapright 40lp"); //// Right side of panel ---- diff --git a/swing/src/net/sf/openrocket/gui/configdialog/RingComponentConfig.java b/swing/src/net/sf/openrocket/gui/configdialog/RingComponentConfig.java index 9638663fa..242c333b6 100644 --- a/swing/src/net/sf/openrocket/gui/configdialog/RingComponentConfig.java +++ b/swing/src/net/sf/openrocket/gui/configdialog/RingComponentConfig.java @@ -29,7 +29,7 @@ public class RingComponentConfig extends RocketComponentConfig { } - protected JPanel generalTab(String outer, String inner, String thickness, String length) { + protected JPanel generalTab(String length, String outer, String inner, String thickness) { JPanel primary = new JPanel(new MigLayout()); JPanel panel = new JPanel(new MigLayout("gap rel unrel, ins 0", "[][65lp::][30lp::]", "")); @@ -39,6 +39,24 @@ public class RingComponentConfig extends RocketComponentConfig { //// Attributes ---- + //// Length + if (length != null) { + panel.add(new JLabel(length)); + + m = new DoubleModel(component, "Length", UnitGroup.UNITS_LENGTH, 0); + + spin = new JSpinner(m.getSpinnerModel()); + spin.setEditor(new SpinnerEditor(spin)); + if (component instanceof ThicknessRingComponent) { + focusElement = spin; + } + panel.add(spin, "growx"); + order.add(((SpinnerEditor) spin.getEditor()).getTextField()); + + panel.add(new UnitSelector(m), "growx"); + panel.add(new BasicSlider(m.getSliderModel(0, 0.1, 1.0)), "w 100lp, wrap"); + } + //// Outer diameter if (outer != null) { panel.add(new JLabel(outer)); @@ -110,26 +128,6 @@ public class RingComponentConfig extends RocketComponentConfig { panel.add(new UnitSelector(m), "growx"); panel.add(new BasicSlider(m.getSliderModel(0, 0.01)), "w 100lp, wrap"); } - - - //// Inner tube length - if (length != null) { - panel.add(new JLabel(length)); - - //// Length - m = new DoubleModel(component, "Length", UnitGroup.UNITS_LENGTH, 0); - - spin = new JSpinner(m.getSpinnerModel()); - spin.setEditor(new SpinnerEditor(spin)); - if (component instanceof ThicknessRingComponent) { - focusElement = spin; - } - panel.add(spin, "growx"); - order.add(((SpinnerEditor) spin.getEditor()).getTextField()); - - panel.add(new UnitSelector(m), "growx"); - panel.add(new BasicSlider(m.getSliderModel(0, 0.1, 1.0)), "w 100lp, wrap"); - } primary.add(panel, "grow, gapright 40lp"); diff --git a/swing/src/net/sf/openrocket/gui/configdialog/SleeveConfig.java b/swing/src/net/sf/openrocket/gui/configdialog/SleeveConfig.java index d2261dfd6..340ca58a4 100644 --- a/swing/src/net/sf/openrocket/gui/configdialog/SleeveConfig.java +++ b/swing/src/net/sf/openrocket/gui/configdialog/SleeveConfig.java @@ -23,8 +23,8 @@ public class SleeveConfig extends RingComponentConfig { //// Inner diameter: //// Wall thickness: //// Length: - tab = generalTab(trans.get("SleeveCfg.tab.Outerdiam"), trans.get("SleeveCfg.tab.Innerdiam"), - trans.get("SleeveCfg.tab.Wallthickness"), trans.get("SleeveCfg.tab.Length")); + tab = generalTab(trans.get("SleeveCfg.tab.Length"), trans.get("SleeveCfg.tab.Outerdiam"), + trans.get("SleeveCfg.tab.Innerdiam"), trans.get("SleeveCfg.tab.Wallthickness")); //// General and General properties tabbedPane.insertTab(trans.get("SleeveCfg.tab.General"), null, tab, trans.get("SleeveCfg.tab.Generalproperties"), 0); diff --git a/swing/src/net/sf/openrocket/gui/configdialog/ThicknessRingComponentConfig.java b/swing/src/net/sf/openrocket/gui/configdialog/ThicknessRingComponentConfig.java index 66ce8703e..3718ec092 100644 --- a/swing/src/net/sf/openrocket/gui/configdialog/ThicknessRingComponentConfig.java +++ b/swing/src/net/sf/openrocket/gui/configdialog/ThicknessRingComponentConfig.java @@ -19,14 +19,14 @@ public class ThicknessRingComponentConfig extends RingComponentConfig { super(d, c, parent); JPanel tab; - + + //// Length: //// Outer diameter: //// Inner diameter: //// Wall thickness: - //// Length: - tab = generalTab(trans.get("ThicknessRingCompCfg.tab.Outerdiam"), + tab = generalTab(trans.get("ThicknessRingCompCfg.tab.Length"), trans.get("ThicknessRingCompCfg.tab.Outerdiam"), trans.get("ThicknessRingCompCfg.tab.Innerdiam"), - trans.get("ThicknessRingCompCfg.tab.Wallthickness"), trans.get("ThicknessRingCompCfg.tab.Length")); + trans.get("ThicknessRingCompCfg.tab.Wallthickness")); //// General and General properties tabbedPane.insertTab(trans.get("ThicknessRingCompCfg.tab.General"), null, tab, trans.get("ThicknessRingCompCfg.tab.Generalprop"), 0); 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 26bf33854..322794979 100644 --- a/swing/src/net/sf/openrocket/gui/dialogs/flightconfiguration/MotorMountConfigurationPanel.java +++ b/swing/src/net/sf/openrocket/gui/dialogs/flightconfiguration/MotorMountConfigurationPanel.java @@ -25,6 +25,8 @@ public class MotorMountConfigurationPanel extends JPanel { table.setShowVerticalLines(false); table.setRowSelectionAllowed(false); table.setColumnSelectionAllowed(false); + table.setAutoResizeMode(JTable.AUTO_RESIZE_OFF); + table.setAutoCreateColumnsFromModel(false); TableColumnModel columnModel = table.getColumnModel(); TableColumn col0 = columnModel.getColumn(0); diff --git a/swing/src/net/sf/openrocket/gui/dialogs/flightconfiguration/MotorMountTableModel.java b/swing/src/net/sf/openrocket/gui/dialogs/flightconfiguration/MotorMountTableModel.java index 3c9d7c69f..42fcfc7c8 100644 --- a/swing/src/net/sf/openrocket/gui/dialogs/flightconfiguration/MotorMountTableModel.java +++ b/swing/src/net/sf/openrocket/gui/dialogs/flightconfiguration/MotorMountTableModel.java @@ -24,7 +24,7 @@ class MotorMountTableModel extends AbstractTableModel implements ComponentChange private final Rocket rocket; /** - * @param motorConfigurationPanel + * @param rocket the rocket to select motor mounts from */ MotorMountTableModel( Rocket rocket) { this.rocket = rocket; diff --git a/swing/src/net/sf/openrocket/gui/dialogs/preferences/DesignPreferencesPanel.java b/swing/src/net/sf/openrocket/gui/dialogs/preferences/DesignPreferencesPanel.java index c91962d7c..74dcf1580 100644 --- a/swing/src/net/sf/openrocket/gui/dialogs/preferences/DesignPreferencesPanel.java +++ b/swing/src/net/sf/openrocket/gui/dialogs/preferences/DesignPreferencesPanel.java @@ -115,6 +115,19 @@ public class DesignPreferencesPanel extends PreferencesPanel { }); this.add(showDiscardConfirmation, "wrap, growx, spanx"); + // // Show confirmation dialog for discarding simulation configuration changes + final JCheckBox showDiscardSimulationConfirmation = new JCheckBox( + trans.get("pref.dlg.checkbox.ShowDiscardSimulationConfirmation")); + showDiscardSimulationConfirmation.setSelected(preferences.isShowDiscardSimulationConfirmation()); + showDiscardSimulationConfirmation.setToolTipText(trans.get("pref.dlg.checkbox.ShowDiscardSimulationConfirmation.ttip")); + showDiscardSimulationConfirmation.addItemListener(new ItemListener() { + @Override + public void itemStateChanged(ItemEvent e) { + preferences.setShowDiscardSimulationConfirmation(e.getStateChange() == ItemEvent.SELECTED); + } + }); + this.add(showDiscardSimulationConfirmation, "wrap, growx, spanx"); + // // Update flight estimates in the design window final JCheckBox updateEstimates = new JCheckBox( trans.get("pref.dlg.checkbox.Updateestimates")); 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 6a438e3d2..54de016bb 100644 --- a/swing/src/net/sf/openrocket/gui/dialogs/preferences/PreferencesDialog.java +++ b/swing/src/net/sf/openrocket/gui/dialogs/preferences/PreferencesDialog.java @@ -97,6 +97,7 @@ public class PreferencesDialog extends JDialog { if (parent != null) { parent.getRocketPanel().updateExtras(); parent.getRocketPanel().updateFigures(); + parent.getRocketPanel().updateRulers(); } } }); diff --git a/swing/src/net/sf/openrocket/gui/figure3d/photo/PhotoPanel.java b/swing/src/net/sf/openrocket/gui/figure3d/photo/PhotoPanel.java index 4a8461a9e..628d34bd9 100644 --- a/swing/src/net/sf/openrocket/gui/figure3d/photo/PhotoPanel.java +++ b/swing/src/net/sf/openrocket/gui/figure3d/photo/PhotoPanel.java @@ -156,6 +156,7 @@ public class PhotoPanel extends JPanel implements GLEventListener { final GLProfile glp = GLProfile.get(GLProfile.GL2); final GLCapabilities caps = new GLCapabilities(glp); + caps.setBackgroundOpaque(false); if (Application.getPreferences().getBoolean( Preferences.OPENGL_ENABLE_AA, true)) { @@ -169,10 +170,12 @@ public class PhotoPanel extends JPanel implements GLEventListener { Preferences.OPENGL_USE_FBO, false)) { log.trace("GL - Creating GLJPanel"); canvas = new GLJPanel(caps); + ((GLJPanel) canvas).setOpaque(false); } else { log.trace("GL - Creating GLCanvas"); canvas = new GLCanvas(caps); } + canvas.setBackground(new java.awt.Color(0, 0, 0, 0)); ((GLAutoDrawable) canvas).addGLEventListener(this); this.add(canvas, BorderLayout.CENTER); @@ -277,7 +280,7 @@ public class PhotoPanel extends JPanel implements GLEventListener { if (!imageCallbacks.isEmpty()) { BufferedImage i = (new AWTGLReadBufferUtil( - GLProfile.get(GLProfile.GL2), false)) + GLProfile.get(GLProfile.GL2), true)) // Set the second parameter to true .readPixelsToBufferedImage(drawable.getGL(), 0, 0, drawable.getSurfaceWidth(), drawable.getSurfaceHeight(), true); final Vector cbs = new Vector( @@ -298,18 +301,41 @@ public class PhotoPanel extends JPanel implements GLEventListener { out[0] = 1; out[1] = 1; out[2] = 0; + out[3] = 1; } else { out[0] = (float) color.getRed() / 255f; out[1] = (float) color.getGreen() / 255f; out[2] = (float) color.getBlue() / 255f; + out[3] = (float) color.getAlpha() / 255f; } } + /** + * Blend two colors + * @param color1 first color to blend + * @param color2 second color to blend + * @param ratio blend ratio. 0 = full color 1, 0.5 = mid-blend, 1 = full color 2 + * @return blended color + */ + private static Color blendColors(Color color1, Color color2, double ratio) { + if (ratio < 0 || ratio > 1) { + throw new IllegalArgumentException("Blend ratio must be between 0 and 1"); + } + + double inverseRatio = 1 - ratio; + + int r = (int) ((color1.getRed() * inverseRatio) + (color2.getRed() * ratio)); + int g = (int) ((color1.getGreen() * inverseRatio) + (color2.getGreen() * ratio)); + int b = (int) ((color1.getBlue() * inverseRatio) + (color2.getBlue() * ratio)); + + return new Color(r, g, b); + } + private void draw(final GLAutoDrawable drawable, float dx) { GL2 gl = drawable.getGL().getGL2(); GLU glu = new GLU(); - float[] color = new float[3]; + float[] color = new float[4]; gl.glEnable(GL.GL_MULTISAMPLE); @@ -333,8 +359,16 @@ public class PhotoPanel extends JPanel implements GLEventListener { new float[] { spc * color[0], spc * color[1], spc * color[2], 1 }, 0); - convertColor(p.getSkyColor(), color); - gl.glClearColor(color[0], color[1], color[2], 1); + // Machines that don't use off-screen rendering can't render transparent background, so we create it + // artificially by blending the sky color with white (= color that is rendered as transparent background) + if (!Application.getPreferences().getBoolean( + Preferences.OPENGL_USE_FBO, false)) { + convertColor(blendColors(p.getSkyColor(), new Color(255, 255, 255), 1-p.getSkyColorOpacity()), + color); + } else { + convertColor(p.getSkyColor(), color); + } + gl.glClearColor(color[0], color[1], color[2], color[3]); gl.glClear(GL.GL_COLOR_BUFFER_BIT | GL.GL_DEPTH_BUFFER_BIT); gl.glMatrixMode(GLMatrixFunc.GL_PROJECTION); diff --git a/swing/src/net/sf/openrocket/gui/figure3d/photo/PhotoSettings.java b/swing/src/net/sf/openrocket/gui/figure3d/photo/PhotoSettings.java index 2aecbe434..493cae0f0 100644 --- a/swing/src/net/sf/openrocket/gui/figure3d/photo/PhotoSettings.java +++ b/swing/src/net/sf/openrocket/gui/figure3d/photo/PhotoSettings.java @@ -24,6 +24,7 @@ public class PhotoSettings extends AbstractChangeSource implements FlameSettings private double ambiance = .3f; private Color skyColor = new Color(55, 95, 155); + private double skyColorOpacity = 1.0; private boolean motionBlurred = false; @@ -186,9 +187,20 @@ public class PhotoSettings extends AbstractChangeSource implements FlameSettings public void setSkyColor(Color skyColor) { this.skyColor = skyColor; + this.skyColorOpacity = skyColor.getAlpha() / 255f; fireChangeEvent(); } - + + public double getSkyColorOpacity() { + return skyColorOpacity; + } + + public void setSkyColorOpacity(double skyColorOpacity) { + this.skyColorOpacity = skyColorOpacity; + skyColor.setAlpha((int) (skyColorOpacity * 255)); + fireChangeEvent(); + } + public Color getFlameColor() { return flameColor; } diff --git a/swing/src/net/sf/openrocket/gui/figure3d/photo/PhotoSettingsConfig.java b/swing/src/net/sf/openrocket/gui/figure3d/photo/PhotoSettingsConfig.java index a758cc320..e66251414 100644 --- a/swing/src/net/sf/openrocket/gui/figure3d/photo/PhotoSettingsConfig.java +++ b/swing/src/net/sf/openrocket/gui/figure3d/photo/PhotoSettingsConfig.java @@ -256,10 +256,10 @@ public class PhotoSettingsConfig extends JTabbedPane { /// Light altitude add(new JLabel(trans.get("PhotoSettingsConfig.lbl.lightAlt"))); - DoubleModel lightAltModle = new DoubleModel(p, "LightAlt", UnitGroup.UNITS_ANGLE, -Math.PI / 2, Math.PI / 2); - add(new EditableSpinner(lightAltModle.getSpinnerModel()), "growx, split 2"); - add(new UnitSelector(lightAltModle)); - add(new BasicSlider(lightAltModle.getSliderModel(-Math.PI / 2, Math.PI / 2)), "wrap"); + DoubleModel lightAltModel = new DoubleModel(p, "LightAlt", UnitGroup.UNITS_ANGLE, -Math.PI / 2, Math.PI / 2); + add(new EditableSpinner(lightAltModel.getSpinnerModel()), "growx, split 2"); + add(new UnitSelector(lightAltModel)); + add(new BasicSlider(lightAltModel.getSliderModel(-Math.PI / 2, Math.PI / 2)), "wrap"); // Sky add(new StyledLabel(trans.get("PhotoSettingsConfig.lbl.sky"), Style.BOLD), "split, span, gapright para"); @@ -269,6 +269,17 @@ public class PhotoSettingsConfig extends JTabbedPane { add(new JLabel(trans.get("PhotoSettingsConfig.lbl.skyColor"))); add(skyColorButton, "wrap"); + /// Sky color opacity + add(new JLabel(trans.get("PhotoSettingsConfig.lbl.skyColorOpacity"))); + DoubleModel skyColorOpacityModel = new DoubleModel(p, "SkyColorOpacity", UnitGroup.UNITS_RELATIVE, 0, 1); + EditableSpinner skyColorOpacitySpinner = new EditableSpinner(skyColorOpacityModel.getSpinnerModel()); + add(skyColorOpacitySpinner, "growx, split 2"); + UnitSelector skyColorOpacityUnitSelector = new UnitSelector(skyColorOpacityModel); + add(skyColorOpacityUnitSelector); + BasicSlider skyColorOpacitySlider = new BasicSlider(skyColorOpacityModel.getSliderModel()); + add(skyColorOpacitySlider, "wrap"); + p.addChangeListener(skyColorOpacityModel); + /// Sky image add(new JLabel(trans.get("PhotoSettingsConfig.lbl.skyImage"))); @@ -293,9 +304,15 @@ public class PhotoSettingsConfig extends JTabbedPane { if (s instanceof Sky && s != noSky) { p.setSky((Sky) s); skyColorButton.setEnabled(false); + skyColorOpacitySpinner.setEnabled(false); + skyColorOpacityUnitSelector.setEnabled(false); + skyColorOpacitySlider.setEnabled(false); } else if (s == noSky) { p.setSky(null); skyColorButton.setEnabled(true); + skyColorOpacitySpinner.setEnabled(true); + skyColorOpacityUnitSelector.setEnabled(true); + skyColorOpacitySlider.setEnabled(true); } } }); diff --git a/swing/src/net/sf/openrocket/gui/main/BasicFrame.java b/swing/src/net/sf/openrocket/gui/main/BasicFrame.java index 83fea3e7c..a5530fc0d 100644 --- a/swing/src/net/sf/openrocket/gui/main/BasicFrame.java +++ b/swing/src/net/sf/openrocket/gui/main/BasicFrame.java @@ -515,12 +515,12 @@ public class BasicFrame extends JFrame { fileMenu.add(item); //// Edit - fileMenu = new JMenu(trans.get("main.menu.edit")); - fileMenu.setMnemonic(KeyEvent.VK_E); + JMenu editMenu = new JMenu(trans.get("main.menu.edit")); + editMenu.setMnemonic(KeyEvent.VK_E); //// Rocket editing - fileMenu.getAccessibleContext().setAccessibleDescription(trans.get("main.menu.edit.desc")); - menubar.add(fileMenu); + editMenu.getAccessibleContext().setAccessibleDescription(trans.get("main.menu.edit.desc")); + menubar.add(editMenu); Action action = UndoRedoAction.newUndoAction(document); item = new JMenuItem(action); @@ -530,7 +530,7 @@ public class BasicFrame extends JFrame { //// Undo the previous operation item.getAccessibleContext().setAccessibleDescription(trans.get("main.menu.edit.undo.desc")); - fileMenu.add(item); + editMenu.add(item); action = UndoRedoAction.newRedoAction(document); item = new JMenuItem(action); @@ -539,42 +539,42 @@ public class BasicFrame extends JFrame { //// Redo the previously undone operation item.getAccessibleContext().setAccessibleDescription(trans.get("main.menu.edit.redo.desc")); - fileMenu.add(item); + editMenu.add(item); - fileMenu.addSeparator(); + editMenu.addSeparator(); item = new JMenuItem(actions.getEditAction()); - fileMenu.add(item); + editMenu.add(item); item = new JMenuItem(actions.getCutAction()); - fileMenu.add(item); + editMenu.add(item); item = new JMenuItem(actions.getCopyAction()); - fileMenu.add(item); + editMenu.add(item); item = new JMenuItem(actions.getPasteAction()); - fileMenu.add(item); + editMenu.add(item); item = new JMenuItem(actions.getDuplicateAction()); - fileMenu.add(item); + editMenu.add(item); item = new JMenuItem(actions.getDeleteAction()); - fileMenu.add(item); + editMenu.add(item); - fileMenu.addSeparator(); + editMenu.addSeparator(); JMenu subMenu = new JMenu(trans.get("RocketActions.Select")); - fileMenu.add(subMenu); + editMenu.add(subMenu); item = new JMenuItem(actions.getSelectSameColorAction()); subMenu.add(item); item = new JMenuItem(actions.getDeselectAllAction()); subMenu.add(item); - fileMenu.addSeparator(); + editMenu.addSeparator(); item = new JMenuItem(actions.getScaleAction()); - fileMenu.add(item); + editMenu.add(item); //// Preferences @@ -590,7 +590,7 @@ public class BasicFrame extends JFrame { PreferencesDialog.showPreferences(BasicFrame.this); } }); - fileMenu.add(item); + editMenu.add(item); //// Edit Component Preset File if (System.getProperty("openrocket.preseteditor.fileMenu") != null) { @@ -605,13 +605,13 @@ public class BasicFrame extends JFrame { dialog.setVisible(true); } }); - fileMenu.add(item); + editMenu.add(item); } // Tools - fileMenu = new JMenu(trans.get("main.menu.tools")); - menubar.add(fileMenu); + JMenu toolsMenu = new JMenu(trans.get("main.menu.tools")); + menubar.add(toolsMenu); //// Component analysis item = new JMenuItem(trans.get("main.menu.tools.componentAnalysis"), KeyEvent.VK_C); @@ -625,7 +625,7 @@ public class BasicFrame extends JFrame { ComponentAnalysisDialog.showDialog(rocketpanel); } }); - fileMenu.add(item); + toolsMenu.add(item); //// Optimize item = new JMenuItem(trans.get("main.menu.tools.optimization"), KeyEvent.VK_O); @@ -641,7 +641,7 @@ public class BasicFrame extends JFrame { } } }); - fileMenu.add(item); + toolsMenu.add(item); //// Custom expressions item = new JMenuItem(trans.get("main.menu.tools.customExpressions"), KeyEvent.VK_E); @@ -653,7 +653,7 @@ public class BasicFrame extends JFrame { new CustomExpressionDialog(document, BasicFrame.this).setVisible(true); } }); - fileMenu.add(item); + toolsMenu.add(item); item = new JMenuItem(trans.get("PhotoFrame.title"), KeyEvent.VK_P); item.getAccessibleContext().setAccessibleDescription(trans.get("PhotoFrame.desc")); @@ -665,7 +665,7 @@ public class BasicFrame extends JFrame { pa.setVisible(true); } }); - fileMenu.add(item); + toolsMenu.add(item); //// Debug // // (shown if openrocket.debug.fileMenu is defined) @@ -1284,34 +1284,24 @@ public class BasicFrame extends JFrame { //// Handle the document OpenRocketDocument doc = null; try { - doc = worker.get(); - } catch (ExecutionException e) { - Throwable cause = e.getCause(); - if (cause instanceof FileNotFoundException) { - log.warn("File not found", cause); JOptionPane.showMessageDialog(parent, "File not found: " + displayName, "Error opening file", JOptionPane.ERROR_MESSAGE); return null; - } else if (cause instanceof RocketLoadException) { - log.warn("Error loading the file", cause); JOptionPane.showMessageDialog(parent, "Unable to open file '" + displayName + "': " + cause.getMessage(), "Error opening file", JOptionPane.ERROR_MESSAGE); return null; - } else { - throw new BugException("Unknown error when opening file", e); - } } catch (InterruptedException e) { @@ -1322,7 +1312,6 @@ public class BasicFrame extends JFrame { throw new BugException("Document loader returned null"); } - //// Show warnings WarningSet warnings = worker.getRocketLoader().getWarnings(); if (!warnings.isEmpty()) { diff --git a/swing/src/net/sf/openrocket/gui/main/ExampleDesignFileAction.java b/swing/src/net/sf/openrocket/gui/main/ExampleDesignFileAction.java index b56bc8ddf..b70b47ad8 100644 --- a/swing/src/net/sf/openrocket/gui/main/ExampleDesignFileAction.java +++ b/swing/src/net/sf/openrocket/gui/main/ExampleDesignFileAction.java @@ -107,7 +107,7 @@ public final class ExampleDesignFileAction extends JMenu { /** * When a user clicks on one of the recently used design files, open it. * - * @param file the design file name (absolute path) + * @param example the design file name (absolute path) * * @return the action to open a design file */ diff --git a/swing/src/net/sf/openrocket/gui/main/SimulationPanel.java b/swing/src/net/sf/openrocket/gui/main/SimulationPanel.java index 88efc984f..7f5aed9e0 100644 --- a/swing/src/net/sf/openrocket/gui/main/SimulationPanel.java +++ b/swing/src/net/sf/openrocket/gui/main/SimulationPanel.java @@ -297,7 +297,7 @@ public class SimulationPanel extends JPanel { simulationTable.addRowSelectionInterval(n, n); updatePreviousSelection(); - openDialog(false, sim); + openDialog(false, true, sim); } private void plotSimulation() { @@ -595,8 +595,8 @@ public class SimulationPanel extends JPanel { return simulationTable.getSelectionModel(); } - private void openDialog(boolean plotMode, final Simulation... sims) { - SimulationEditDialog d = new SimulationEditDialog(SwingUtilities.getWindowAncestor(this), document, sims); + private void openDialog(boolean plotMode, boolean isNewSimulation, final Simulation... sims) { + SimulationEditDialog d = new SimulationEditDialog(SwingUtilities.getWindowAncestor(this), document, isNewSimulation, sims); if (plotMode) { d.setPlotMode(); } @@ -605,6 +605,10 @@ public class SimulationPanel extends JPanel { takeTheSpotlight(); } + private void openDialog(boolean plotMode, final Simulation... sims) { + openDialog(plotMode, false, sims); + } + private void openDialog(final Simulation sim) { boolean plotMode = false; if (sim.hasSimulationData() && Simulation.isStatusUpToDate(sim.getStatus())) { @@ -662,7 +666,7 @@ public class SimulationPanel extends JPanel { @Override public void updateEnabledState() { - setEnabled(simulationTable.getSelectedRowCount() == 1); + setEnabled(simulationTable.getSelectedRowCount() > 0); } } diff --git a/swing/src/net/sf/openrocket/gui/main/flightconfigpanel/MotorConfigurationPanel.java b/swing/src/net/sf/openrocket/gui/main/flightconfigpanel/MotorConfigurationPanel.java index fa6480665..8f87a56f2 100644 --- a/swing/src/net/sf/openrocket/gui/main/flightconfigpanel/MotorConfigurationPanel.java +++ b/swing/src/net/sf/openrocket/gui/main/flightconfigpanel/MotorConfigurationPanel.java @@ -76,7 +76,7 @@ public class MotorConfigurationPanel extends FlightConfigurablePanel BorderFactory.createEtchedBorder(), "" + trans.get("lbl.motorMounts") + "")); - MotorMountConfigurationPanel mountConfigPanel = new MotorMountConfigurationPanel(this, rocket); + MotorMountConfigurationPanel mountConfigPanel = new MotorMountConfigurationPanel(subpanel, rocket); subpanel.add(mountConfigPanel, "grow, pushy"); this.add(subpanel, "split, growy, pushy"); } diff --git a/swing/src/net/sf/openrocket/gui/scalefigure/RocketPanel.java b/swing/src/net/sf/openrocket/gui/scalefigure/RocketPanel.java index 1b4ac81a0..bce0980f0 100644 --- a/swing/src/net/sf/openrocket/gui/scalefigure/RocketPanel.java +++ b/swing/src/net/sf/openrocket/gui/scalefigure/RocketPanel.java @@ -271,6 +271,15 @@ public class RocketPanel extends JPanel implements TreeSelectionListener, Change figure3d.updateFigure(); } + /** + * Updates the rulers of the rocket panel to the currently selected default unit. + */ + public void updateRulers() { + scrollPane.updateRulerUnit(); + scrollPane.revalidate(); + scrollPane.repaint(); + } + private void go3D() { if (is3d) return; diff --git a/swing/src/net/sf/openrocket/gui/scalefigure/ScaleScrollPane.java b/swing/src/net/sf/openrocket/gui/scalefigure/ScaleScrollPane.java index a8a38b648..db7ebad82 100644 --- a/swing/src/net/sf/openrocket/gui/scalefigure/ScaleScrollPane.java +++ b/swing/src/net/sf/openrocket/gui/scalefigure/ScaleScrollPane.java @@ -15,6 +15,7 @@ import java.awt.event.MouseEvent; import java.awt.event.MouseListener; import java.awt.event.MouseMotionListener; import java.awt.geom.Point2D; +import java.util.EventObject; import javax.swing.BorderFactory; import javax.swing.JComponent; @@ -56,6 +57,7 @@ public class ScaleScrollPane extends JScrollPane private final AbstractScaleFigure figure; private DoubleModel rulerUnit; + private UnitSelector unitSelector; private Ruler horizontalRuler; private Ruler verticalRuler; @@ -83,7 +85,7 @@ public class ScaleScrollPane extends JScrollPane this.component = component; this.figure = (AbstractScaleFigure) component; - + rulerUnit = new DoubleModel(0.0, UnitGroup.UNITS_LENGTH); rulerUnit.addChangeListener(new ChangeListener() { @Override @@ -95,10 +97,10 @@ public class ScaleScrollPane extends JScrollPane verticalRuler = new Ruler(Ruler.VERTICAL); this.setColumnHeaderView(horizontalRuler); this.setRowHeaderView(verticalRuler); - - UnitSelector selector = new UnitSelector(rulerUnit); - selector.setFont(new Font("SansSerif", Font.PLAIN, 8)); - this.setCorner(JScrollPane.UPPER_LEFT_CORNER, selector); + + unitSelector = new UnitSelector(rulerUnit); + unitSelector.setFont(new Font("SansSerif", Font.PLAIN, 8)); + this.setCorner(JScrollPane.UPPER_LEFT_CORNER, unitSelector); this.setBorder(BorderFactory.createLineBorder(Color.LIGHT_GRAY)); @@ -176,6 +178,14 @@ public class ScaleScrollPane extends JScrollPane public Unit getCurrentUnit() { return rulerUnit.getCurrentUnit(); } + + /** + * Updates the units of the ruler to the default units of the current unit group. + */ + public void updateRulerUnit() { + rulerUnit.setCurrentUnit(UnitGroup.UNITS_LENGTH.getDefaultUnit()); + unitSelector.stateChanged(new EventObject(this)); + } public String toViewportString(){ Rectangle view = this.getViewport().getViewRect(); diff --git a/swing/src/net/sf/openrocket/gui/simulation/SimulationEditDialog.java b/swing/src/net/sf/openrocket/gui/simulation/SimulationEditDialog.java index 287182c92..096bfc576 100644 --- a/swing/src/net/sf/openrocket/gui/simulation/SimulationEditDialog.java +++ b/swing/src/net/sf/openrocket/gui/simulation/SimulationEditDialog.java @@ -2,14 +2,22 @@ package net.sf.openrocket.gui.simulation; import java.awt.CardLayout; +import java.awt.Color; import java.awt.Window; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; +import java.awt.event.ItemEvent; +import java.awt.event.ItemListener; +import java.awt.event.WindowAdapter; +import java.awt.event.WindowEvent; +import java.awt.event.WindowListener; +import java.util.EventObject; import javax.swing.JButton; -import javax.swing.JComboBox; +import javax.swing.JCheckBox; import javax.swing.JDialog; import javax.swing.JLabel; +import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.JTabbedPane; import javax.swing.JTextField; @@ -21,7 +29,9 @@ import javax.swing.event.DocumentListener; import net.miginfocom.swing.MigLayout; import net.sf.openrocket.document.OpenRocketDocument; import net.sf.openrocket.document.Simulation; +import net.sf.openrocket.document.events.DocumentChangeEvent; import net.sf.openrocket.gui.components.ConfigurationComboBox; +import net.sf.openrocket.gui.components.StyledLabel; import net.sf.openrocket.gui.util.GUIUtil; import net.sf.openrocket.gui.widgets.SelectColorButton; import net.sf.openrocket.l10n.Translator; @@ -30,6 +40,8 @@ import net.sf.openrocket.rocketcomponent.FlightConfigurationId; import net.sf.openrocket.rocketcomponent.Rocket; import net.sf.openrocket.simulation.extension.SimulationExtension; import net.sf.openrocket.startup.Application; +import net.sf.openrocket.startup.Preferences; +import net.sf.openrocket.util.StateChangeListener; public class SimulationEditDialog extends JDialog { @@ -38,17 +50,37 @@ public class SimulationEditDialog extends JDialog { private final Simulation[] simulationList; private final OpenRocketDocument document; private static final Translator trans = Application.getTranslator(); + private static final Preferences preferences = Application.getPreferences(); JPanel cards; private final static String EDITMODE = "EDIT"; private final static String PLOTMODE = "PLOT"; + + private final WindowListener applyChangesToSimsListener; + private final Simulation initialSim; // A copy of the first selected simulation before it was modified + private final boolean initialIsSaved; // Whether the document was saved before the dialog was opened + private boolean isModified = false; // Whether the simulation has been modified + private final boolean isNewSimulation; // Whether you are editing a new simulation, or an existing one - public SimulationEditDialog(Window parent, final OpenRocketDocument document, Simulation... sims) { + public SimulationEditDialog(Window parent, final OpenRocketDocument document, boolean isNewSimulation, Simulation... sims) { //// Edit simulation - super(parent, trans.get("simedtdlg.title.Editsim"), JDialog.ModalityType.DOCUMENT_MODAL); + super(parent, sims.length == 1 ? trans.get("simedtdlg.title.Editsim") : trans.get("simedtdlg.title.MultiSimEdit"), + JDialog.ModalityType.DOCUMENT_MODAL); this.document = document; this.parentWindow = parent; this.simulationList = sims; + this.initialSim = simulationList[0].clone(); + this.initialIsSaved = document.isSaved(); + this.isNewSimulation = isNewSimulation; + + simulationList[0].addChangeListener(new StateChangeListener() { + @Override + public void stateChanged(EventObject e) { + isModified = true; + setTitle("* " + getTitle()); // Add component changed indicator to the title + simulationList[0].removeChangeListener(this); + } + }); this.cards = new JPanel(new CardLayout()); this.add(cards); @@ -59,6 +91,14 @@ public class SimulationEditDialog extends JDialog { this.pack(); this.setLocationByPlatform(true); + + this.applyChangesToSimsListener = new WindowAdapter() { + @Override + public void windowClosed(WindowEvent e) { + copyChangesToAllSims(); + } + }; + this.addWindowListener(applyChangesToSimsListener); GUIUtil.setDisposableDialogOptions(this, null); } @@ -72,16 +112,21 @@ public class SimulationEditDialog extends JDialog { } public void setEditMode() { + String baseTitle = simulationList.length == 1 ? + trans.get("simedtdlg.title.Editsim") : trans.get("simedtdlg.title.MultiSimEdit"); + setTitle((isModified ? "* " : "") + baseTitle); CardLayout cl = (CardLayout) (cards.getLayout()); cl.show(cards, EDITMODE); cards.validate(); + this.addWindowListener(applyChangesToSimsListener); } public void setPlotMode() { if (!allowsPlotMode()) { return; } - setTitle(trans.get("simplotpanel.title.Plotsim")); + this.removeWindowListener(applyChangesToSimsListener); + setTitle((isModified ? "* " : "") + trans.get("simplotpanel.title.Plotsim")); CardLayout cl = (CardLayout) (cards.getLayout()); cl.show(cards, PLOTMODE); cards.validate(); @@ -107,7 +152,7 @@ public class SimulationEditDialog extends JDialog { } private void buildEditCard() { - JPanel simEditPanel = new JPanel(new MigLayout("fill")); + JPanel simEditPanel = new JPanel(new MigLayout("fill, hidemode 1")); if (isSingleEdit()) { JPanel panel = new JPanel(new MigLayout("fill, ins 0")); @@ -135,7 +180,6 @@ public class SimulationEditDialog extends JDialog { String name = field.getText(); if (name == null || name.equals("")) return; - //System.out.println("Setting name:" + name); simulationList[0].setName(name); } @@ -193,12 +237,29 @@ public class SimulationEditDialog extends JDialog { } }); - simEditPanel.add(button, "spanx, split 3, align left"); + simEditPanel.add(button, "spanx, split 5, align left"); if (allowsPlotMode()) { button.setVisible(true); } else { button.setVisible(false); } + + //// Multi-simulation edit + if (simulationList.length > 1) { + StyledLabel multiSimEditLabel = new StyledLabel("", -1, StyledLabel.Style.BOLD); + multiSimEditLabel.setFontColor(new Color(170, 0, 100)); + multiSimEditLabel.setText(trans.get("simedtdlg.title.MultiSimEdit")); + StringBuilder components = new StringBuilder(trans.get("simedtdlg.title.MultiSimEdit.ttip")); + for (int i = 0; i < simulationList.length; i++) { + if (i < simulationList.length - 1) { + components.append(simulationList[i].getName()).append(", "); + } else { + components.append(simulationList[i].getName()); + } + } + multiSimEditLabel.setToolTipText(components.toString()); + simEditPanel.add(multiSimEditLabel, "align left"); + } //// Run simulation button button = new SelectColorButton(trans.get("SimulationEditDialog.btn.simulateAndPlot")); @@ -216,18 +277,49 @@ public class SimulationEditDialog extends JDialog { } } }); - simEditPanel.add(button, " align right, tag ok"); - - //// Close button - JButton close = new SelectColorButton(trans.get("dlg.but.close")); - close.addActionListener(new ActionListener() { + simEditPanel.add(button, "align right, gapright 10lp, tag ok"); + + //// Cancel button + JButton cancelButton = new SelectColorButton(trans.get("dlg.but.cancel")); + cancelButton.setToolTipText(trans.get("SimulationEditDialog.btn.Cancel.ttip")); + cancelButton.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + // Don't do anything on cancel if you are editing an existing simulation, and it is not modified + if (!isNewSimulation && !isModified) { + SimulationEditDialog.this.removeWindowListener(applyChangesToSimsListener); + SimulationEditDialog.this.dispose(); + return; + } + + // Apply the cancel operation if set to auto discard in preferences + if (!preferences.isShowDiscardSimulationConfirmation()) { + discardChanges(); + return; + } + + // Yes/No dialog: Are you sure you want to discard your changes? + JPanel msg = createCancelOperationContent(); + int resultYesNo = JOptionPane.showConfirmDialog(SimulationEditDialog.this, msg, + trans.get("SimulationEditDialog.CancelOperation.title"), JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE); + if (resultYesNo == JOptionPane.YES_OPTION) { + discardChanges(); + } + } + }); + simEditPanel.add(cancelButton, "tag ok"); + + //// Ok button + JButton okButton = new SelectColorButton(trans.get("dlg.but.ok")); + okButton.setToolTipText(trans.get("SimulationEditDialog.btn.OK.ttip")); + okButton.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { copyChangesToAllSims(); SimulationEditDialog.this.dispose(); } }); - simEditPanel.add(close, "tag ok"); + simEditPanel.add(okButton, "tag ok"); cards.add(simEditPanel, EDITMODE); } @@ -322,4 +414,47 @@ public class SimulationEditDialog extends JDialog { } } + + private JPanel createCancelOperationContent() { + JPanel panel = new JPanel(new MigLayout()); + String msg = isNewSimulation ? trans.get("SimulationEditDialog.CancelOperation.msg.undoAdd") : + trans.get("SimulationEditDialog.CancelOperation.msg.discardChanges"); + JLabel msgLabel = new JLabel(msg); + JCheckBox dontAskAgain = new JCheckBox(trans.get("SimulationEditDialog.CancelOperation.checkbox.dontAskAgain")); + dontAskAgain.setSelected(false); + dontAskAgain.addItemListener(new ItemListener() { + @Override + public void itemStateChanged(ItemEvent e) { + if (e.getStateChange() == ItemEvent.SELECTED) { + preferences.setShowDiscardSimulationConfirmation(false); + } + // Unselected state should be not be possible and thus not be handled + } + }); + + panel.add(msgLabel, "left, wrap"); + panel.add(dontAskAgain, "left, gaptop para"); + + return panel; + } + + private void discardChanges() { + if (isNewSimulation) { + document.removeSimulation(simulationList[0]); + } else { + undoSimulationChanges(); + } + document.setSaved(this.initialIsSaved); // Restore the saved state of the document + document.fireDocumentChangeEvent(new DocumentChangeEvent(this)); + + SimulationEditDialog.this.removeWindowListener(applyChangesToSimsListener); + SimulationEditDialog.this.dispose(); + } + + private void undoSimulationChanges() { + if (simulationList == null || simulationList.length == 0) { + return; + } + simulationList[0].loadFrom(initialSim); + } }