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