diff --git a/core/resources/l10n/messages.properties b/core/resources/l10n/messages.properties
index 4e03dcac9..96db981a6 100644
--- a/core/resources/l10n/messages.properties
+++ b/core/resources/l10n/messages.properties
@@ -380,6 +380,8 @@ pref.dlg.DescriptionArea.Adddirectories = Add directories, RASP motor files (*.e
PreferencesDialog.lbl.language = Interface language:
PreferencesDialog.languages.default = System default
PreferencesDialog.lbl.languageEffect = The language will change the next time you start OpenRocket.
+PreferencesDialog.CancelOperation.title = Discard Preference Changes
+PreferencesDialog.CancelOperation.msg.discardChanges = Are you sure you want to discard the preference changes?
generalprefs.lbl.language = Interface language
generalprefs.languages.default = System default
diff --git a/core/src/net/sf/openrocket/startup/Preferences.java b/core/src/net/sf/openrocket/startup/Preferences.java
index 0830222ae..a283de06a 100644
--- a/core/src/net/sf/openrocket/startup/Preferences.java
+++ b/core/src/net/sf/openrocket/startup/Preferences.java
@@ -79,6 +79,7 @@ public abstract class Preferences implements ChangeSource {
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";
+ private static final String SHOW_DISCARD_PREFERENCES_CONFIRMATION = "IgnoreDiscardPreferencesWarning";
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";
@@ -582,6 +583,22 @@ public abstract class Preferences implements ChangeSource {
this.putBoolean(SHOW_DISCARD_SIMULATION_CONFIRMATION, enabled);
}
+ /**
+ * Answer if a confirmation dialog should be shown when canceling preferences changes.
+ *
+ * @return true if the confirmation dialog should be shown.
+ */
+ public final boolean isShowDiscardPreferencesConfirmation() {
+ return this.getBoolean(SHOW_DISCARD_PREFERENCES_CONFIRMATION, true);
+ }
+
+ /**
+ * Enable/Disable showing a confirmation warning when canceling preferences changes.
+ */
+ public final void setShowDiscardPreferencesConfirmation(boolean enabled) {
+ this.putBoolean(SHOW_DISCARD_PREFERENCES_CONFIRMATION, enabled);
+ }
+
/**
* Answer if the always open leftmost tab is enabled.
*
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 4dadc0f14..cb846d7ac 100644
--- a/swing/src/net/sf/openrocket/gui/dialogs/preferences/PreferencesDialog.java
+++ b/swing/src/net/sf/openrocket/gui/dialogs/preferences/PreferencesDialog.java
@@ -1,21 +1,31 @@
package net.sf.openrocket.gui.dialogs.preferences;
import java.awt.Dialog;
-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.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.util.prefs.BackingStoreException;
import javax.swing.JButton;
+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 net.miginfocom.swing.MigLayout;
import net.sf.openrocket.gui.main.BasicFrame;
import net.sf.openrocket.gui.util.GUIUtil;
+import net.sf.openrocket.gui.util.PreferencesExporter;
+import net.sf.openrocket.gui.util.PreferencesImporter;
import net.sf.openrocket.gui.util.SwingPreferences;
import net.sf.openrocket.l10n.Translator;
import net.sf.openrocket.startup.Application;
@@ -35,6 +45,9 @@ public class PreferencesDialog extends JDialog {
private BasicFrame parentFrame;
+ private boolean storePreferences = true;
+ private File initPrefsFile = null;
+
private PreferencesDialog(BasicFrame parent) {
// // Preferences
super(parent, trans.get("pref.dlg.title.Preferences"),
@@ -42,6 +55,9 @@ public class PreferencesDialog extends JDialog {
this.parentFrame = parent;
+ // First store the initial preferences
+ initPrefsFile = storeInitPreferences();
+
JPanel panel = new JPanel(new MigLayout("fill, gap unrel", "[grow]",
"[grow][]"));
@@ -78,16 +94,42 @@ public class PreferencesDialog extends JDialog {
// tabbedPane.addTab(trans.get("pref.dlg.tab.Colors"),
// new DisplayPreferencesPanel());
- // Close button
- JButton close = new SelectColorButton(trans.get("dlg.but.close"));
- close.addActionListener(new ActionListener() {
+
+ //// 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 arg0) {
- PreferencesDialog.this.setVisible(false);
- PreferencesDialog.this.dispose();
+ public void actionPerformed(ActionEvent e) {
+ // Apply the cancel operation if set to auto discard in preferences
+ if (!preferences.isShowDiscardPreferencesConfirmation()) {
+ closeDialog(false);
+ return;
+ }
+
+ // Yes/No dialog: Are you sure you want to discard your changes?
+ JPanel msg = createCancelOperationContent();
+ int resultYesNo = JOptionPane.showConfirmDialog(PreferencesDialog.this, msg,
+ trans.get("PreferencesDialog.CancelOperation.title"), JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE);
+ if (resultYesNo == JOptionPane.YES_OPTION) {
+ closeDialog(false);
+ }
}
});
- panel.add(close, "span, right, tag close");
+ panel.add(cancelButton, "span, split 2, right, tag cancel");
+
+ //// 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) {
+ closeDialog(true);
+ }
+ });
+ panel.add(okButton, "tag ok");
+
+
this.setContentPane(panel);
pack();
@@ -96,7 +138,25 @@ public class PreferencesDialog extends JDialog {
this.addWindowListener(new WindowAdapter() {
@Override
public void windowClosed(WindowEvent e) {
+ // We don't want to lose the preference for the confirmation dialog
+ boolean isShowDiscardConfirmation = preferences.isShowDiscardPreferencesConfirmation();
+
+ // Reload initial preferences
+ if (!storePreferences) {
+ loadInitPreferences();
+ }
+
+ // Store the preference for showing the confirmation dialog
+ preferences.setShowDiscardPreferencesConfirmation(isShowDiscardConfirmation);
+
+ // Delete the init prefs
+ if (initPrefsFile != null) {
+ initPrefsFile.delete();
+ }
+
+ // Ensure the units are properly stored
preferences.storeDefaultUnits();
+
// Make sure unit change applies to the rocket figure
if (parent != null) {
parent.getRocketPanel().updateExtras();
@@ -106,13 +166,72 @@ public class PreferencesDialog extends JDialog {
}
});
- GUIUtil.setDisposableDialogOptions(this, close);
+ GUIUtil.setDisposableDialogOptions(this, okButton);
}
public BasicFrame getParentFrame() {
return parentFrame;
}
+ private void closeDialog(boolean storeChanges) {
+ storePreferences = storeChanges;
+ PreferencesDialog.this.setVisible(false);
+ PreferencesDialog.this.dispose();
+ }
+
+ /**
+ * Store the intial preferences in a temporary file, and return that file.
+ * @return the file containing the initial preferences, or null if something went wrong
+ */
+ private File storeInitPreferences() {
+ try {
+ File outputFile = Files.createTempFile("ORInitPrefs_" + System.currentTimeMillis(), ".xml").toFile();
+ try (FileOutputStream outputFos = new FileOutputStream(outputFile)) {
+ PreferencesExporter.exportPreferencesToFile(preferences.getPreferences(), outputFos, false);
+ log.debug("Initial preferences stored in temporary file: " + outputFile.getAbsolutePath());
+ } catch (BackingStoreException e) {
+ log.error("Could not store initial preferences", e);
+ return null;
+ }
+ return outputFile;
+ } catch (IOException e) {
+ log.error("Could not create temporary preferences file", e);
+ return null;
+ }
+ }
+
+ /**
+ * Loads the initial stored preferences back (restores preferences).
+ */
+ private void loadInitPreferences() {
+ if (initPrefsFile == null) {
+ return;
+ }
+ PreferencesImporter.importPreferences(initPrefsFile);
+ }
+
+ private JPanel createCancelOperationContent() {
+ JPanel panel = new JPanel(new MigLayout());
+ String msg = trans.get("PreferencesDialog.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.setShowDiscardPreferencesConfirmation(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;
+ }
+
// ////// Singleton implementation ////////
private static PreferencesDialog dialog = null;
diff --git a/swing/src/net/sf/openrocket/gui/util/PreferencesExporter.java b/swing/src/net/sf/openrocket/gui/util/PreferencesExporter.java
index ab40b7114..a69257c2d 100644
--- a/swing/src/net/sf/openrocket/gui/util/PreferencesExporter.java
+++ b/swing/src/net/sf/openrocket/gui/util/PreferencesExporter.java
@@ -121,25 +121,32 @@ public abstract class PreferencesExporter {
keysToIgnore.add(SwingPreferences.UPDATE_PLATFORM); // Don't export platform-specific settings
}
- private static void exportFilteredPreferences(Preferences preferences, FileOutputStream fos) throws BackingStoreException, IOException {
+ public static void exportPreferencesToFile(Preferences preferences, FileOutputStream fos, boolean filterPreferences)
+ throws BackingStoreException, IOException {
+ // If no filtering is required, just export the preferences
+ if (!filterPreferences) {
+ preferences.exportSubtree(fos);
+ return;
+ }
+
// 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();
+ String filteredPrefsNodeName = originalNodeName + "-filtered";
+ if (root.nodeExists(filteredPrefsNodeName)) {
+ root.node(filteredPrefsNodeName).removeNode();
}
- Preferences tempPrefs = root.node(nodeName);
+ Preferences filteredPrefs = root.node(filteredPrefsNodeName);
// Fill in all parameters to the temporary preferences, except for user directories
- copyFilteredPreferences(preferences, tempPrefs, nodesToIgnore, keysToIgnore, prefixKeysToIgnore);
+ copyFilteredPreferences(preferences, filteredPrefs, nodesToIgnore, keysToIgnore, prefixKeysToIgnore);
// Export the filtered preferences
try {
// Export the filtered preferences to a temporary file
- Path tempFile = Files.createTempFile("ORprefs_" + System.currentTimeMillis(), ".xml");
+ Path tempFile = Files.createTempFile("ORPrefs_" + System.currentTimeMillis(), ".xml");
try (FileOutputStream tempFos = new FileOutputStream(tempFile.toFile())) {
- tempPrefs.exportSubtree(tempFos);
+ filteredPrefs.exportSubtree(tempFos);
}
// Read and parse the temporary file
@@ -149,11 +156,11 @@ public abstract class PreferencesExporter {
doc = factory.newDocumentBuilder().parse(tempFis);
}
- // Find and rename the node
+ // Find and rename the filtered prefs 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)) {
+ if (element.getAttribute("name").equals(filteredPrefsNodeName)) {
element.setAttribute("name", ((SwingPreferences) prefs).getNodename());
break;
}
@@ -176,10 +183,14 @@ public abstract class PreferencesExporter {
} catch (ParserConfigurationException | TransformerException | SAXException e) {
e.printStackTrace();
} finally {
- root.node(nodeName).removeNode();
+ root.node(filteredPrefsNodeName).removeNode();
}
}
+ private static void exportFilteredPreferences(Preferences preferences, FileOutputStream fos) throws BackingStoreException, IOException {
+ exportPreferencesToFile(preferences, fos, true);
+ }
+
private static void copyFilteredPreferences(Preferences src, Preferences dest,
List nodesToIgnore, List keysToIgnore, List prefixKeysToIgnore) throws BackingStoreException {
for (String key : src.keys()) {
diff --git a/swing/src/net/sf/openrocket/gui/util/PreferencesImporter.java b/swing/src/net/sf/openrocket/gui/util/PreferencesImporter.java
index 32a4044db..e2e8114f2 100644
--- a/swing/src/net/sf/openrocket/gui/util/PreferencesImporter.java
+++ b/swing/src/net/sf/openrocket/gui/util/PreferencesImporter.java
@@ -17,6 +17,11 @@ public abstract class PreferencesImporter {
private static final Translator trans = Application.getTranslator();
private static final Logger log = LoggerFactory.getLogger(PreferencesImporter.class);
+ /**
+ * Import the preferences by first showing a file chooser dialog to locate the preferences file.
+ * @param parent The parent window for the file chooser dialog.
+ * @return true if the preferences were imported successfully, false otherwise.
+ */
public static boolean importPreferences(Window parent) {
final JFileChooser chooser = new JFileChooser();
chooser.setDialogTitle(trans.get("PreferencesImporter.chooser.title"));
@@ -33,13 +38,22 @@ public abstract class PreferencesImporter {
((SwingPreferences) Application.getPreferences()).setDefaultDirectory(chooser.getCurrentDirectory());
File importFile = chooser.getSelectedFile();
+ return importPreferences(importFile);
+ }
+
+ /**
+ * Import preferences from an XML file.
+ * @param importFile The XML file to import preferences from.
+ * @return true if the preferences were imported successfully, false otherwise.
+ */
+ public static boolean importPreferences(File importFile) {
try (FileInputStream fis = new FileInputStream(importFile)) {
Preferences.importPreferences(fis);
log.info("Preferences imported successfully.");
+ return true;
} catch (IOException | InvalidPreferencesFormatException e) {
log.warn("Error while importing preferences: " + e.getMessage());
}
-
- return true;
+ return false;
}
}