Merge pull request #2163 from SiboVG/issue-2158

[#2158] Add ok/cancel button for simulation editing
This commit is contained in:
Sibo Van Gool 2023-04-01 09:44:51 +02:00 committed by GitHub
commit d64a8b931c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 272 additions and 47 deletions

View File

@ -318,6 +318,8 @@ pref.dlg.checkbox.AlwaysOpenLeftmost = Always open leftmost tab when opening a c
pref.dlg.checkbox.AlwaysOpenLeftmost.ttip = <html>If checked, a component edit dialog will always pop up with the first tab selected.<br>If unchecked, the previous selected tab will be used.</html>
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)
@ -516,6 +518,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 = <html>Are you sure you want to <b>discard</b> your <b>changes</b> to this simulation?</html>
SimulationEditDialog.CancelOperation.msg.undoAdd = <html>Are you sure you want to <b>undo adding</b> this simulation?</html>
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.

View File

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

View File

@ -488,7 +488,6 @@ public class OpenRocketSaver extends RocketSaver {
for (int i = 0; i < types.length; i++) {
data.add(branch.get(types[i]));
}
List<Double> timeData = branch.get(FlightDataType.TYPE_TIME);
// Build the <databranch> tag
StringBuilder sb = new StringBuilder();

View File

@ -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.

View File

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

View File

@ -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.
@ -550,6 +532,10 @@ public class SimulationOptions implements ChangeSource, Cloneable {
public void removeChangeListener(StateChangeListener listener) {
listeners.remove(listener);
}
public List<EventListener> getChangeListeners() {
return listeners;
}
private final EventObject event = new EventObject(this);

View File

@ -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_ROCKSIM_FORMAT_WARNING = "SHOW_ROCKSIM_FORMAT_WARNING";
@ -537,6 +538,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.
*

View File

@ -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"));

View File

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

View File

@ -5,14 +5,18 @@ import java.awt.CardLayout;
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;
@ -24,6 +28,7 @@ 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.util.GUIUtil;
import net.sf.openrocket.gui.widgets.SelectColorButton;
@ -33,6 +38,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 {
@ -41,19 +48,36 @@ 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 WindowListener applyChangesToSimsListener;
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);
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);
@ -85,6 +109,7 @@ public class SimulationEditDialog extends JDialog {
}
public void setEditMode() {
setTitle((isModified ? "* " : "") + trans.get("simedtdlg.title.Editsim"));
CardLayout cl = (CardLayout) (cards.getLayout());
cl.show(cards, EDITMODE);
cards.validate();
@ -96,7 +121,7 @@ public class SimulationEditDialog extends JDialog {
return;
}
this.removeWindowListener(applyChangesToSimsListener);
setTitle(trans.get("simplotpanel.title.Plotsim"));
setTitle((isModified ? "* " : "") + trans.get("simplotpanel.title.Plotsim"));
CardLayout cl = (CardLayout) (cards.getLayout());
cl.show(cards, PLOTMODE);
cards.validate();
@ -150,7 +175,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);
}
@ -208,7 +232,7 @@ public class SimulationEditDialog extends JDialog {
}
});
simEditPanel.add(button, "spanx, split 3, align left");
simEditPanel.add(button, "spanx, split 4, align left");
if (allowsPlotMode()) {
button.setVisible(true);
} else {
@ -231,17 +255,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);
}
@ -336,4 +392,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);
}
}