diff --git a/core/resources/l10n/messages.properties b/core/resources/l10n/messages.properties index addc46046..31afed57a 100644 --- a/core/resources/l10n/messages.properties +++ b/core/resources/l10n/messages.properties @@ -122,6 +122,7 @@ FileHelper.RASAERO_DESIGN_FILTER = RASAero designs (*.CDX1) FileHelper.OPEN_ROCKET_COMPONENT_FILTER = OpenRocket presets (*.orc) FileHelper.PNG_FILTER = PNG image (*.png) FileHelper.IMAGES = Image files +FileHelper.XML_FILTER = XML files (*.xml) ! About Dialog @@ -290,8 +291,12 @@ pref.dlg.tab.Simulation = Simulation pref.dlg.tab.Launch = Launch pref.dlg.tab.Miscellaneousoptions = Miscellaneous options pref.dlg.lbl.RockSimWarning = Show warning when saving in RockSim format -pref.dlg.but.clearCachedPreferences = Reset all preferences -pref.dlg.but.clearCachedPreferences.ttip = Reset all the preferences, including cached preferences (UI settings, recent files, etc.) +pref.dlg.but.resetAllPreferences = Reset all preferences +pref.dlg.but.resetAllPreferences.ttip = Reset all the preferences, including cached preferences (UI settings, recent files, etc.) +pref.dlg.but.exportPreferences = Export preferences +pref.dlg.but.exportPreferences.ttip = Export all your OpenRocket preferences to an external file +pref.dlg.but.importPreferences = Import preferences +pref.dlg.but.importPreferences.ttip = Import new OpenRocket preferences from an external file pref.dlg.clearCachedPreferences.title = Reset preferences? pref.dlg.clearCachedPreferences.message = Are you sure you want to reset all your preferences? @@ -373,6 +378,18 @@ PreferencesDialog.lbl.languageEffect = The language will change the next time yo generalprefs.lbl.language = Interface language generalprefs.languages.default = System default generalprefs.lbl.languageEffect = The language will change the next time you start OpenRocket. +generalprefs.ImportWarning.title = Reload OpenRocket +generalprefs.ImportWarning.msg = You may need to restart OpenRocket for some of the changes to take effect. + +PreferencesExporter.chooser.title = Export the Preferences File + +PreferencesImporter.chooser.title = Import a Preferences File + +PreferencesOptionPanel.title = Export settings +PreferencesOptionPanel.checkbox.userDirectories = Export user directories +PreferencesOptionPanel.checkbox.userDirectories.ttip = If unchecked, user directories (possibly sensitive information) will not be exported. +PreferencesOptionPanel.checkbox.windowInfo = Export window information (position, size\u2026) +PreferencesOptionPanel.checkbox.windowInfo.ttip = If unchecked, window information (position, size\u2026) will not be exported. ! Welcome dialog welcome.dlg.title = Welcome to OpenRocket diff --git a/core/resources/l10n/messages_ar.properties b/core/resources/l10n/messages_ar.properties index 5db732751..96b45e6ab 100644 --- a/core/resources/l10n/messages_ar.properties +++ b/core/resources/l10n/messages_ar.properties @@ -276,8 +276,8 @@ pref.dlg.tab.Simulation = محاكاة pref.dlg.tab.Launch = إطلاق pref.dlg.tab.Miscellaneousoptions = الخيارات المتنوعة pref.dlg.lbl.RockSimWarning = إظهار تحذير عند الحفظ بتنسيق روكسيم -pref.dlg.but.clearCachedPreferences = إعادة تعيين كل التفضيلات -pref.dlg.but.clearCachedPreferences.ttip = (إعدادات واجهة المستخدم والملفات الحديثة وما إلى ذلك)إعادة تعيين جميع التفضيلات ، بما في ذلك التفضيلات المخزنة مؤقتًا +pref.dlg.but.resetAllPreferences = إعادة تعيين كل التفضيلات +pref.dlg.but.resetAllPreferences.ttip = (إعدادات واجهة المستخدم والملفات الحديثة وما إلى ذلك)إعادة تعيين جميع التفضيلات ، بما في ذلك التفضيلات المخزنة مؤقتًا pref.dlg.clearCachedPreferences.title = هل تريد إعادة تعيين التفضيلات؟ pref.dlg.clearCachedPreferences.message = هل أنت متأكد أنك تريد إعادة تعيين كل تفضيلاتك؟ diff --git a/core/resources/l10n/messages_nl.properties b/core/resources/l10n/messages_nl.properties index a2de9a92c..cf6f94ddd 100644 --- a/core/resources/l10n/messages_nl.properties +++ b/core/resources/l10n/messages_nl.properties @@ -277,8 +277,8 @@ pref.dlg.tab.Simulation = Simulatie pref.dlg.tab.Launch = Lanceer pref.dlg.tab.Miscellaneousoptions = Diverse opties pref.dlg.lbl.RockSimWarning = Toon waarschuwingen bij opslaan in RockSim-formaat -pref.dlg.but.clearCachedPreferences = Alle voorkeuren opnieuw instellen -pref.dlg.but.clearCachedPreferences.ttip = Alle voorkeuren opnieuw instellen, inclusief voorkeuren in de cache (UI-instellingen, recente bestanden, enz.) +pref.dlg.but.resetAllPreferences = Alle voorkeuren opnieuw instellen +pref.dlg.but.resetAllPreferences.ttip = Alle voorkeuren opnieuw instellen, inclusief voorkeuren in de cache (UI-instellingen, recente bestanden, enz.) pref.dlg.clearCachedPreferences.title = Voorkeuren opnieuw instellen? pref.dlg.clearCachedPreferences.message = Bent u zeker dat u al uw voorkeuren opnieuw wilt instellen? diff --git a/core/src/net/sf/openrocket/startup/Preferences.java b/core/src/net/sf/openrocket/startup/Preferences.java index 8b7d01d13..7e67fa317 100644 --- a/core/src/net/sf/openrocket/startup/Preferences.java +++ b/core/src/net/sf/openrocket/startup/Preferences.java @@ -53,7 +53,8 @@ public abstract class Preferences implements ChangeSource { public static final String EXPORT_EVENT_COMMENTS = "ExportEventComments"; public static final String EXPORT_COMMENT_CHARACTER = "ExportCommentCharacter"; public static final String USER_LOCAL = "locale"; - + public static final String DEFAULT_DIRECTORY = "defaultDirectory"; + public static final String PLOT_SHOW_POINTS = "ShowPlotPoints"; private static final String IGNORE_WELCOME = "IgnoreWelcome"; @@ -73,19 +74,21 @@ public abstract class Preferences implements ChangeSource { public static final String MATCH_AFT_DIAMETER = "MatchAftDiameter"; // Node names - public static final String PREFERRED_THRUST_CURVE_MOTOR_NODE = "preferredThrustCurveMotors"; - private static final String AUTO_OPEN_LAST_DESIGN = "AUTO_OPEN_LAST_DESIGN"; - private static final String OPEN_LEFTMOST_DESIGN_TAB = "OPEN_LEFTMOST_DESIGN_TAB"; + public static final String PREFERRED_THRUST_CURVE_MOTOR_NODE = "PreferredThrustCurveMotors"; + private static final String AUTO_OPEN_LAST_DESIGN = "AutoOpenLastDesign"; + private static final String OPEN_LEFTMOST_DESIGN_TAB = "OpenLeftmostDesignTab"; private static final String SHOW_DISCARD_CONFIRMATION = "IgnoreDiscardEditingWarning"; private static final String SHOW_DISCARD_SIMULATION_CONFIRMATION = "IgnoreDiscardSimulationEditingWarning"; - public static final String MARKER_STYLE_ICON = "MARKER_STYLE_ICON"; - private static final String SHOW_MARKERS = "SHOW_MARKERS"; - private static final String SHOW_ROCKSIM_FORMAT_WARNING = "SHOW_ROCKSIM_FORMAT_WARNING"; + public static final String MARKER_STYLE_ICON = "MarkerStyleIcon"; + private static final String SHOW_MARKERS = "ShowMarkers"; + private static final String SHOW_ROCKSIM_FORMAT_WARNING = "ShowRocksimFormatWarning"; + private static final String EXPORT_USER_DIRECTORIES = "ExportUserDirectories"; + private static final String EXPORT_WINDOW_INFORMATION = "ExportWindowInformation"; //Preferences related to 3D graphics - public static final String OPENGL_ENABLED = "OpenGL_Is_Enabled"; - public static final String OPENGL_ENABLE_AA = "OpenGL_Antialiasing_Is_Enabled"; - public static final String OPENGL_USE_FBO = "OpenGL_Use_FBO"; + public static final String OPENGL_ENABLED = "OpenGLIsEnabled"; + public static final String OPENGL_ENABLE_AA = "OpenGLAntialiasingIsEnabled"; + public static final String OPENGL_USE_FBO = "OpenGLUseFBO"; public static final String ROCKET_INFO_FONT_SIZE = "RocketInfoFontSize"; @@ -232,7 +235,23 @@ public abstract class Preferences implements ChangeSource { public final void setShowRockSimFormatWarning(boolean check) { this.putBoolean(SHOW_ROCKSIM_FORMAT_WARNING, check); } - + + public final boolean getExportUserDirectories() { + return this.getBoolean(EXPORT_USER_DIRECTORIES, false); + } + + public final void setExportUserDirectories(boolean check) { + this.putBoolean(EXPORT_USER_DIRECTORIES, check); + } + + public final boolean getExportWindowInformation() { + return this.getBoolean(EXPORT_WINDOW_INFORMATION, false); + } + + public final void setExportWindowInformation(boolean check) { + this.putBoolean(EXPORT_WINDOW_INFORMATION, check); + } + public final double getDefaultMach() { return Application.getPreferences().getChoice(Preferences.DEFAULT_MACH_NUMBER, 0.9, 0.3); } diff --git a/swing/src/net/sf/openrocket/gui/components/PreferencesOptionPanel.java b/swing/src/net/sf/openrocket/gui/components/PreferencesOptionPanel.java new file mode 100644 index 000000000..a87e7a19b --- /dev/null +++ b/swing/src/net/sf/openrocket/gui/components/PreferencesOptionPanel.java @@ -0,0 +1,65 @@ +package net.sf.openrocket.gui.components; + +import net.miginfocom.swing.MigLayout; +import net.sf.openrocket.l10n.Translator; +import net.sf.openrocket.startup.Application; +import net.sf.openrocket.startup.Preferences; + +import javax.swing.BorderFactory; +import javax.swing.JCheckBox; +import javax.swing.JPanel; +import java.awt.event.ItemEvent; +import java.awt.event.ItemListener; + +/** + * A panel that adds storage options for exporting preferences. + */ +public class PreferencesOptionPanel extends JPanel { + private static final Translator trans = Application.getTranslator(); + private static final Preferences prefs = Application.getPreferences(); + + private final JCheckBox exportUserDirectories; + private final JCheckBox exportWindowInfo; + + public PreferencesOptionPanel() { + super(new MigLayout("fill, ins 0")); + + JPanel panel = new JPanel(new MigLayout("fill, ins 4lp")); + panel.setBorder(BorderFactory.createTitledBorder(trans.get("PreferencesOptionPanel.title"))); + + // Export user directories + exportUserDirectories = new JCheckBox(trans.get("PreferencesOptionPanel.checkbox.userDirectories")); + exportUserDirectories.setToolTipText(trans.get("PreferencesOptionPanel.checkbox.userDirectories.ttip")); + exportUserDirectories.setSelected(prefs.getExportUserDirectories()); + exportUserDirectories.addItemListener(new ItemListener() { + @Override + public void itemStateChanged(ItemEvent e) { + prefs.setExportUserDirectories(e.getStateChange() == ItemEvent.SELECTED); + } + }); + panel.add(exportUserDirectories, "wrap"); + + // Export window information (position, size...) + exportWindowInfo = new JCheckBox(trans.get("PreferencesOptionPanel.checkbox.windowInfo")); + exportWindowInfo.setToolTipText(trans.get("PreferencesOptionPanel.checkbox.windowInfo.ttip")); + exportWindowInfo.setSelected(prefs.getExportWindowInformation()); + exportWindowInfo.addItemListener(new ItemListener() { + @Override + public void itemStateChanged(ItemEvent e) { + prefs.setExportWindowInformation(e.getStateChange() == ItemEvent.SELECTED); + } + }); + panel.add(exportWindowInfo, "wrap 10lp"); + + + this.add(panel, "growx, north"); + } + + public boolean isIgnoreUserDirectories() { + return !exportUserDirectories.isSelected(); + } + + public boolean isIgnoreWindowInformation() { + return !exportWindowInfo.isSelected(); + } +} diff --git a/swing/src/net/sf/openrocket/gui/configdialog/FreeformFinSetConfig.java b/swing/src/net/sf/openrocket/gui/configdialog/FreeformFinSetConfig.java index 9f898774c..cce14deba 100644 --- a/swing/src/net/sf/openrocket/gui/configdialog/FreeformFinSetConfig.java +++ b/swing/src/net/sf/openrocket/gui/configdialog/FreeformFinSetConfig.java @@ -303,6 +303,7 @@ public class FreeformFinSetConfig extends FinSetConfig { } FreeformFinSetConfig.writeCSVFile(table, selectedFile); + ((SwingPreferences) Application.getPreferences()).setDefaultDirectory(chooser.getCurrentDirectory()); } } }); @@ -400,6 +401,7 @@ public class FreeformFinSetConfig extends FinSetConfig { trans.get("CustomFinImport.error.title"), JOptionPane.ERROR_MESSAGE); } finally { document.stopUndo(); + ((SwingPreferences) Application.getPreferences()).setDefaultDirectory(chooser.getCurrentDirectory()); } } } diff --git a/swing/src/net/sf/openrocket/gui/customexpression/CustomExpressionPanel.java b/swing/src/net/sf/openrocket/gui/customexpression/CustomExpressionPanel.java index 272044bc4..394daec86 100644 --- a/swing/src/net/sf/openrocket/gui/customexpression/CustomExpressionPanel.java +++ b/swing/src/net/sf/openrocket/gui/customexpression/CustomExpressionPanel.java @@ -18,6 +18,7 @@ import javax.swing.JScrollPane; import javax.swing.SwingUtilities; import javax.swing.filechooser.FileNameExtensionFilter; +import net.sf.openrocket.gui.util.SwingPreferences; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -110,6 +111,7 @@ public class CustomExpressionPanel extends JPanel { log.info(Markers.USER_MARKER, "Error opening document to import expressions from."); } updateExpressions(); + ((SwingPreferences) Application.getPreferences()).setDefaultDirectory(fc.getCurrentDirectory()); } } }); diff --git a/swing/src/net/sf/openrocket/gui/dialogs/DecalNotFoundDialog.java b/swing/src/net/sf/openrocket/gui/dialogs/DecalNotFoundDialog.java index e14c05a15..e726e0015 100644 --- a/swing/src/net/sf/openrocket/gui/dialogs/DecalNotFoundDialog.java +++ b/swing/src/net/sf/openrocket/gui/dialogs/DecalNotFoundDialog.java @@ -43,6 +43,7 @@ public abstract class DecalNotFoundDialog { if (resultFileChooser == JFileChooser.APPROVE_OPTION) { File file = chooser.getSelectedFile(); decex.getDecal().setDecalFile(file); + ((SwingPreferences) Application.getPreferences()).setDefaultDirectory(chooser.getCurrentDirectory()); } } return (resultYesNo == JOptionPane.YES_OPTION) && (resultFileChooser == JFileChooser.APPROVE_OPTION); diff --git a/swing/src/net/sf/openrocket/gui/dialogs/EditDecalDialog.java b/swing/src/net/sf/openrocket/gui/dialogs/EditDecalDialog.java index 8ed3bf885..678e0edbc 100644 --- a/swing/src/net/sf/openrocket/gui/dialogs/EditDecalDialog.java +++ b/swing/src/net/sf/openrocket/gui/dialogs/EditDecalDialog.java @@ -20,6 +20,7 @@ import javax.swing.event.ChangeListener; import net.miginfocom.swing.MigLayout; import net.sf.openrocket.gui.util.GUIUtil; +import net.sf.openrocket.gui.util.SwingPreferences; import net.sf.openrocket.l10n.Translator; import net.sf.openrocket.startup.Application; import net.sf.openrocket.gui.widgets.SelectColorButton; @@ -75,6 +76,7 @@ public class EditDecalDialog extends JDialog { int action = fc.showOpenDialog(owner); if (action == JFileChooser.APPROVE_OPTION) { commandText.setText(fc.getSelectedFile().getAbsolutePath()); + ((SwingPreferences) Application.getPreferences()).setDefaultDirectory(fc.getCurrentDirectory()); } } @@ -109,6 +111,7 @@ public class EditDecalDialog extends JDialog { int action = fc.showOpenDialog(owner); if (action == JFileChooser.APPROVE_OPTION) { commandText.setText(fc.getSelectedFile().getAbsolutePath()); + ((SwingPreferences) Application.getPreferences()).setDefaultDirectory(fc.getCurrentDirectory()); } } diff --git a/swing/src/net/sf/openrocket/gui/dialogs/preferences/GeneralPreferencesPanel.java b/swing/src/net/sf/openrocket/gui/dialogs/preferences/GeneralPreferencesPanel.java index de73270f9..fceb57731 100644 --- a/swing/src/net/sf/openrocket/gui/dialogs/preferences/GeneralPreferencesPanel.java +++ b/swing/src/net/sf/openrocket/gui/dialogs/preferences/GeneralPreferencesPanel.java @@ -19,6 +19,7 @@ import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.JProgressBar; import javax.swing.JTextField; +import javax.swing.SwingUtilities; import javax.swing.Timer; import javax.swing.event.DocumentEvent; import javax.swing.event.DocumentListener; @@ -35,8 +36,11 @@ import net.sf.openrocket.gui.dialogs.UpdateInfoDialog; import net.sf.openrocket.gui.util.GUIUtil; import net.sf.openrocket.gui.util.SimpleFileFilter; import net.sf.openrocket.gui.util.SwingPreferences; +import net.sf.openrocket.gui.util.PreferencesExporter; +import net.sf.openrocket.gui.util.PreferencesImporter; import net.sf.openrocket.l10n.L10N; import net.sf.openrocket.logging.Markers; +import net.sf.openrocket.startup.Application; import net.sf.openrocket.startup.Preferences; import net.sf.openrocket.util.BuildProperties; import net.sf.openrocket.util.Named; @@ -46,7 +50,7 @@ import net.sf.openrocket.gui.widgets.SelectColorButton; @SuppressWarnings("serial") public class GeneralPreferencesPanel extends PreferencesPanel { - public GeneralPreferencesPanel(JDialog parent) { + public GeneralPreferencesPanel(PreferencesDialog parent) { super(parent, new MigLayout("fillx, ins 30lp n n n")); @@ -240,21 +244,69 @@ public class GeneralPreferencesPanel extends PreferencesPanel { }); this.add(rocksimWarningDialogBox,"spanx, wrap"); - //// Clear cached preferences - final JButton clearCachedPreferences = new SelectColorButton(trans.get("pref.dlg.but.clearCachedPreferences")); - clearCachedPreferences.setToolTipText(trans.get("pref.dlg.but.clearCachedPreferences.ttip")); - clearCachedPreferences.addActionListener(new ActionListener() { + // Preference buttons + JPanel buttonPanel = new JPanel(new MigLayout("fillx, ins 0")); + + //// Export preferences + final JButton exportPreferences = new SelectColorButton(trans.get("pref.dlg.but.exportPreferences")); + exportPreferences.setToolTipText(trans.get("pref.dlg.but.exportPreferences.ttip")); + exportPreferences.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + PreferencesExporter.exportPreferences(parent, preferences.getPreferences()); + } + }); + buttonPanel.add(exportPreferences); + + //// Import preferences + final JButton importPreferences = new SelectColorButton(trans.get("pref.dlg.but.importPreferences")); + importPreferences.setToolTipText(trans.get("pref.dlg.but.importPreferences.ttip")); + importPreferences.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + boolean imported = PreferencesImporter.importPreferences(parent); + if (imported) { + SwingUtilities.invokeLater(new Runnable() { + @Override + public void run() { + JOptionPane.showMessageDialog(parent, + trans.get("generalprefs.ImportWarning.msg"), + trans.get("generalprefs.ImportWarning.title"), + JOptionPane.WARNING_MESSAGE); + PreferencesDialog.showPreferences(parent.getParentFrame()); // Refresh the preferences dialog + } + }); + } + } + }); + buttonPanel.add(importPreferences); + + //// Reset all preferences + final JButton resetAllPreferences = new SelectColorButton(trans.get("pref.dlg.but.resetAllPreferences")); + resetAllPreferences.setToolTipText(trans.get("pref.dlg.but.resetAllPreferences.ttip")); + resetAllPreferences.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { int resultYesNo = JOptionPane.showConfirmDialog(parent, trans.get("pref.dlg.clearCachedPreferences.message"), trans.get("pref.dlg.clearCachedPreferences.title"), JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE); if (resultYesNo == JOptionPane.YES_OPTION) { preferences.clearPreferences(); + SwingUtilities.invokeLater(new Runnable() { + @Override + public void run() { + JOptionPane.showMessageDialog(parent, + trans.get("generalprefs.ImportWarning.msg"), + trans.get("generalprefs.ImportWarning.title"), + JOptionPane.WARNING_MESSAGE); + PreferencesDialog.showPreferences(parent.getParentFrame()); // Refresh the preferences dialog + } + }); } } }); - this.add(clearCachedPreferences, "spanx, pushy, bottom, wrap"); + buttonPanel.add(resetAllPreferences, "pushx, right, wrap"); + this.add(buttonPanel, "spanx, growx, pushy, bottom, wrap"); } diff --git a/swing/src/net/sf/openrocket/gui/dialogs/preferences/GraphicsPreferencesPanel.java b/swing/src/net/sf/openrocket/gui/dialogs/preferences/GraphicsPreferencesPanel.java index b0b241b9b..3f16a27d3 100644 --- a/swing/src/net/sf/openrocket/gui/dialogs/preferences/GraphicsPreferencesPanel.java +++ b/swing/src/net/sf/openrocket/gui/dialogs/preferences/GraphicsPreferencesPanel.java @@ -29,6 +29,8 @@ import net.sf.openrocket.gui.adaptors.BooleanModel; import net.sf.openrocket.gui.components.StyledLabel; import net.sf.openrocket.gui.components.StyledLabel.Style; import net.sf.openrocket.gui.util.GUIUtil; +import net.sf.openrocket.gui.util.SwingPreferences; +import net.sf.openrocket.startup.Application; import net.sf.openrocket.startup.Preferences; import net.sf.openrocket.gui.widgets.SelectColorButton; @@ -119,6 +121,7 @@ public class GraphicsPreferencesPanel extends PreferencesPanel { String commandLine = fc.getSelectedFile().getAbsolutePath(); commandText.setText(commandLine); preferences.setDecalEditorPreference(false, commandLine); + ((SwingPreferences) Application.getPreferences()).setDefaultDirectory(fc.getCurrentDirectory()); } } diff --git a/swing/src/net/sf/openrocket/gui/dialogs/preferences/PreferencesDialog.java b/swing/src/net/sf/openrocket/gui/dialogs/preferences/PreferencesDialog.java index 54de016bb..4dadc0f14 100644 --- a/swing/src/net/sf/openrocket/gui/dialogs/preferences/PreferencesDialog.java +++ b/swing/src/net/sf/openrocket/gui/dialogs/preferences/PreferencesDialog.java @@ -33,11 +33,15 @@ public class PreferencesDialog extends JDialog { private final SwingPreferences preferences = (SwingPreferences) Application .getPreferences(); + private BasicFrame parentFrame; + private PreferencesDialog(BasicFrame parent) { // // Preferences super(parent, trans.get("pref.dlg.title.Preferences"), Dialog.ModalityType.APPLICATION_MODAL); + this.parentFrame = parent; + JPanel panel = new JPanel(new MigLayout("fill, gap unrel", "[grow]", "[grow][]")); @@ -105,6 +109,10 @@ public class PreferencesDialog extends JDialog { GUIUtil.setDisposableDialogOptions(this, close); } + public BasicFrame getParentFrame() { + return parentFrame; + } + // ////// Singleton implementation //////// private static PreferencesDialog dialog = null; diff --git a/swing/src/net/sf/openrocket/gui/main/ExportDecalDialog.java b/swing/src/net/sf/openrocket/gui/main/ExportDecalDialog.java index 18e3c4ba1..932547cc5 100644 --- a/swing/src/net/sf/openrocket/gui/main/ExportDecalDialog.java +++ b/swing/src/net/sf/openrocket/gui/main/ExportDecalDialog.java @@ -90,6 +90,7 @@ public class ExportDecalDialog extends JDialog { export(selectedDecal, selectedFile); // If the user doesn't confirm over write, then leave this dialog open. ExportDecalDialog.this.dispose(); + ((SwingPreferences) Application.getPreferences()).setDefaultDirectory(chooser.getCurrentDirectory()); } } } diff --git a/swing/src/net/sf/openrocket/gui/preset/PresetEditorDialog.java b/swing/src/net/sf/openrocket/gui/preset/PresetEditorDialog.java index 3818ebe17..b10331a21 100644 --- a/swing/src/net/sf/openrocket/gui/preset/PresetEditorDialog.java +++ b/swing/src/net/sf/openrocket/gui/preset/PresetEditorDialog.java @@ -44,6 +44,7 @@ import javax.swing.border.EmptyBorder; import javax.swing.filechooser.FileNameExtensionFilter; import javax.swing.text.JTextComponent; +import net.sf.openrocket.gui.util.SwingPreferences; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -389,6 +390,7 @@ public class PresetEditorDialog extends JDialog implements ItemListener { File file = imageChooser.getSelectedFile(); ncImage = scaleImage(new ImageIcon(file.getAbsolutePath()).getImage(), 155); ncImageBtn.setIcon(ncImage); + ((SwingPreferences) Application.getPreferences()).setDefaultDirectory(imageChooser.getCurrentDirectory()); } } }); @@ -1318,6 +1320,7 @@ public class PresetEditorDialog extends JDialog implements ItemListener { */ private JFileChooser createImageChooser() { final JFileChooser chooser = new JFileChooser(); + chooser.setCurrentDirectory(((SwingPreferences) Application.getPreferences()).getDefaultDirectory()); ImagePreviewPanel preview = new ImagePreviewPanel(); chooser.setAccessory(preview); chooser.addPropertyChangeListener(preview); diff --git a/swing/src/net/sf/openrocket/gui/util/DummyFrameMenuOSX.java b/swing/src/net/sf/openrocket/gui/util/DummyFrameMenuOSX.java index 9a4a84a33..4b9f1537b 100644 --- a/swing/src/net/sf/openrocket/gui/util/DummyFrameMenuOSX.java +++ b/swing/src/net/sf/openrocket/gui/util/DummyFrameMenuOSX.java @@ -1,11 +1,6 @@ package net.sf.openrocket.gui.util; -import net.sf.openrocket.gui.dialogs.AboutDialog; -import net.sf.openrocket.gui.dialogs.LicenseDialog; -import net.sf.openrocket.gui.help.tours.GuidedTourSelectionDialog; import net.sf.openrocket.gui.main.BasicFrame; -import net.sf.openrocket.gui.main.ExampleDesignFileAction; -import net.sf.openrocket.gui.main.MRUDesignFileAction; import net.sf.openrocket.l10n.Translator; import net.sf.openrocket.logging.Markers; import net.sf.openrocket.startup.Application; diff --git a/swing/src/net/sf/openrocket/gui/util/FileHelper.java b/swing/src/net/sf/openrocket/gui/util/FileHelper.java index 9ea309450..c2bc914d6 100644 --- a/swing/src/net/sf/openrocket/gui/util/FileHelper.java +++ b/swing/src/net/sf/openrocket/gui/util/FileHelper.java @@ -64,6 +64,10 @@ public final class FileHelper { public static final FileFilter PNG_FILTER = new SimpleFileFilter(trans.get("FileHelper.PNG_FILTER"), ".png"); + /** File filter for XML files (*.xml) */ + public static final FileFilter XML_FILTER = + new SimpleFileFilter(trans.get("FileHelper.XML_FILTER"), ".xml"); + diff --git a/swing/src/net/sf/openrocket/gui/util/PreferencesExporter.java b/swing/src/net/sf/openrocket/gui/util/PreferencesExporter.java new file mode 100644 index 000000000..ab40b7114 --- /dev/null +++ b/swing/src/net/sf/openrocket/gui/util/PreferencesExporter.java @@ -0,0 +1,202 @@ +package net.sf.openrocket.gui.util; + +import net.sf.openrocket.arch.SystemInfo; +import net.sf.openrocket.gui.components.PreferencesOptionPanel; +import net.sf.openrocket.gui.main.MRUDesignFile; +import net.sf.openrocket.gui.widgets.SaveFileChooser; +import net.sf.openrocket.l10n.Translator; +import net.sf.openrocket.startup.Application; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.NodeList; +import org.xml.sax.SAXException; + +import javax.swing.JFileChooser; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.transform.OutputKeys; +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerException; +import javax.xml.transform.TransformerFactory; +import javax.xml.transform.dom.DOMSource; +import javax.xml.transform.stream.StreamResult; +import java.awt.Dimension; +import java.awt.Window; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; +import java.util.prefs.BackingStoreException; +import java.util.prefs.Preferences; + +public abstract class PreferencesExporter { + private static final Translator trans = Application.getTranslator(); + private static final Logger log = LoggerFactory.getLogger(PreferencesExporter.class); + private static final net.sf.openrocket.startup.Preferences prefs = Application.getPreferences(); + + private static final List keysToIgnore = new ArrayList<>(); // Preference keys to ignore when exporting user directories (= keys that export user directories) + private static final List prefixKeysToIgnore = new ArrayList<>(); // Preference keys to ignore when exporting user directories (= keys that start with these prefixes), e.g. + private static final List nodesToIgnore = new ArrayList<>(); // Preferences nodes that should not be exported + + public static boolean exportPreferences(Window parent, Preferences preferences) { + JFileChooser chooser = new SaveFileChooser(); + chooser.setDialogTitle(trans.get("PreferencesExporter.chooser.title")); + chooser.setAcceptAllFileFilterUsed(false); + chooser.setFileFilter(FileHelper.XML_FILTER); + chooser.setCurrentDirectory(((SwingPreferences) Application.getPreferences()).getDefaultDirectory()); + PreferencesOptionPanel options = new PreferencesOptionPanel(); + chooser.setAccessory(options); + + // TODO: update this dynamically instead of hard-coded values + // The macOS file chooser has an issue where it does not update its size when the accessory is added. + if (SystemInfo.getPlatform() == SystemInfo.Platform.MAC_OS) { + Dimension currentSize = chooser.getPreferredSize(); + Dimension newSize = new Dimension((int) (1.35 * currentSize.width), (int) (1.2 * currentSize.height)); + chooser.setPreferredSize(newSize); + } + + //// Ensures No Problems When Choosing File + if (chooser.showSaveDialog(parent) != JFileChooser.APPROVE_OPTION) { + log.info("Cancelled export of preferences."); + return false; + } + + ((SwingPreferences) prefs).setDefaultDirectory(chooser.getCurrentDirectory()); + + File file = chooser.getSelectedFile(); + if (file == null) { + log.info("No file selected to export preferences to."); + return false; + } + + final File newFile = FileHelper.forceExtension(file, "xml"); + if (!FileHelper.confirmWrite(newFile, parent)) { + log.info("Cancelled export of preferences."); + return false; + } + + // Decide which keys/nodes to ignore + boolean ignoreUserDirectories = options.isIgnoreUserDirectories(); + boolean ignoreWindowInformation = options.isIgnoreWindowInformation(); + fillIgnoreKeys(ignoreUserDirectories, ignoreWindowInformation); + + try (FileOutputStream fos = new FileOutputStream(newFile)) { + if (keysToIgnore.isEmpty() && nodesToIgnore.isEmpty() && prefixKeysToIgnore.isEmpty()) { + // Export all preferences + preferences.exportSubtree(fos); + } else { + // Export all preferences except user directories + exportFilteredPreferences(preferences, fos); + } + log.info("Preferences exported successfully."); + } catch (IOException | BackingStoreException e) { + log.warn("Error while importing preferences: " + e.getMessage()); + } + + return true; + } + + private static void fillIgnoreKeys(boolean ignoreUserDirectories, boolean ignoreWindowInformation) { + keysToIgnore.clear(); + prefixKeysToIgnore.clear(); + nodesToIgnore.clear(); + + if (ignoreUserDirectories) { + keysToIgnore.add(net.sf.openrocket.startup.Preferences.USER_THRUST_CURVES_KEY); + keysToIgnore.add(net.sf.openrocket.startup.Preferences.DEFAULT_DIRECTORY); + prefixKeysToIgnore.add(MRUDesignFile.MRU_FILE_LIST_PROPERTY); + } + + if (ignoreWindowInformation) { + nodesToIgnore.add(SwingPreferences.NODE_WINDOWS); + nodesToIgnore.add(SwingPreferences.NODE_TABLES); + } + + keysToIgnore.add(SwingPreferences.UPDATE_PLATFORM); // Don't export platform-specific settings + } + + private static void exportFilteredPreferences(Preferences preferences, FileOutputStream fos) throws BackingStoreException, IOException { + // Filter out user directories + Preferences root = Preferences.userRoot(); + String originalNodeName = ((SwingPreferences) prefs).getNodename(); + String nodeName = originalNodeName + "-temp"; + if (root.nodeExists(nodeName)) { + root.node(nodeName).removeNode(); + } + Preferences tempPrefs = root.node(nodeName); + + // Fill in all parameters to the temporary preferences, except for user directories + copyFilteredPreferences(preferences, tempPrefs, nodesToIgnore, keysToIgnore, prefixKeysToIgnore); + + // Export the filtered preferences + try { + // Export the filtered preferences to a temporary file + Path tempFile = Files.createTempFile("ORprefs_" + System.currentTimeMillis(), ".xml"); + try (FileOutputStream tempFos = new FileOutputStream(tempFile.toFile())) { + tempPrefs.exportSubtree(tempFos); + } + + // Read and parse the temporary file + DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); + Document doc; + try (FileInputStream tempFis = new FileInputStream(tempFile.toFile())) { + doc = factory.newDocumentBuilder().parse(tempFis); + } + + // Find and rename the node + NodeList nodeList = doc.getElementsByTagName("node"); + for (int i = 0; i < nodeList.getLength(); i++) { + Element element = (Element) nodeList.item(i); + if (element.getAttribute("name").equals(nodeName)) { + element.setAttribute("name", ((SwingPreferences) prefs).getNodename()); + break; + } + } + + // Create a transformer to write the XML document to the FileOutputStream + Transformer transformer = TransformerFactory.newInstance().newTransformer(); + + // Set output properties to include the correct DOCTYPE declaration + transformer.setOutputProperty(OutputKeys.DOCTYPE_SYSTEM, "http://java.sun.com/dtd/preferences.dtd"); + transformer.setOutputProperty(OutputKeys.STANDALONE, "no"); + + // Write the XML document to the FileOutputStream + DOMSource source = new DOMSource(doc); + StreamResult result = new StreamResult(fos); + transformer.transform(source, result); + + // Clean up the temporary file + Files.deleteIfExists(tempFile); + } catch (ParserConfigurationException | TransformerException | SAXException e) { + e.printStackTrace(); + } finally { + root.node(nodeName).removeNode(); + } + } + + private static void copyFilteredPreferences(Preferences src, Preferences dest, + List nodesToIgnore, List keysToIgnore, List prefixKeysToIgnore) throws BackingStoreException { + for (String key : src.keys()) { + if (keysToIgnore.contains(key) + || prefixKeysToIgnore.stream().anyMatch(key::startsWith)) { + continue; + } + dest.put(key, src.get(key, null)); + } + + for (String childNodeName : src.childrenNames()) { + if (nodesToIgnore.contains(childNodeName)) { + continue; + } + Preferences srcChild = src.node(childNodeName); + Preferences destChild = dest.node(childNodeName); + copyFilteredPreferences(srcChild, destChild, nodesToIgnore, keysToIgnore, prefixKeysToIgnore); + } + } +} diff --git a/swing/src/net/sf/openrocket/gui/util/PreferencesImporter.java b/swing/src/net/sf/openrocket/gui/util/PreferencesImporter.java new file mode 100644 index 000000000..32a4044db --- /dev/null +++ b/swing/src/net/sf/openrocket/gui/util/PreferencesImporter.java @@ -0,0 +1,45 @@ +package net.sf.openrocket.gui.util; + +import net.sf.openrocket.l10n.Translator; +import net.sf.openrocket.startup.Application; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.swing.JFileChooser; +import java.awt.Window; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.util.prefs.InvalidPreferencesFormatException; +import java.util.prefs.Preferences; + +public abstract class PreferencesImporter { + private static final Translator trans = Application.getTranslator(); + private static final Logger log = LoggerFactory.getLogger(PreferencesImporter.class); + + public static boolean importPreferences(Window parent) { + final JFileChooser chooser = new JFileChooser(); + chooser.setDialogTitle(trans.get("PreferencesImporter.chooser.title")); + chooser.setCurrentDirectory(((SwingPreferences) Application.getPreferences()).getDefaultDirectory()); + chooser.setFileFilter(FileHelper.XML_FILTER); + chooser.setAcceptAllFileFilterUsed(false); + + int returnVal = chooser.showOpenDialog(parent); + if (returnVal != JFileChooser.APPROVE_OPTION) { + log.info("Cancelled import of preferences."); + return false; + } + + ((SwingPreferences) Application.getPreferences()).setDefaultDirectory(chooser.getCurrentDirectory()); + + File importFile = chooser.getSelectedFile(); + try (FileInputStream fis = new FileInputStream(importFile)) { + Preferences.importPreferences(fis); + log.info("Preferences imported successfully."); + } catch (IOException | InvalidPreferencesFormatException e) { + log.warn("Error while importing preferences: " + e.getMessage()); + } + + return true; + } +} diff --git a/swing/src/net/sf/openrocket/gui/util/SwingPreferences.java b/swing/src/net/sf/openrocket/gui/util/SwingPreferences.java index bf30f6c39..dc7146bac 100644 --- a/swing/src/net/sf/openrocket/gui/util/SwingPreferences.java +++ b/swing/src/net/sf/openrocket/gui/util/SwingPreferences.java @@ -38,7 +38,11 @@ public class SwingPreferences extends net.sf.openrocket.startup.Preferences { private static final Logger log = LoggerFactory.getLogger(SwingPreferences.class); private static final String SPLIT_CHARACTER = "|"; - + + + public static final String NODE_WINDOWS = "windows"; + public static final String NODE_TABLES = "tables"; + public static final String UPDATE_PLATFORM = "UpdatePlatform"; private static final List SUPPORTED_LOCALES; static { @@ -88,10 +92,16 @@ public class SwingPreferences extends net.sf.openrocket.startup.Preferences { PREFNODE = root.node(NODENAME); } - + public String getNodename() { + return NODENAME; + } ////////////////////// + + public Preferences getPreferences() { + return PREFNODE; + } public void clearPreferences() { try { @@ -114,7 +124,22 @@ public class SwingPreferences extends net.sf.openrocket.startup.Preferences { private void storeVersion() { PREFNODE.put("OpenRocketVersion", BuildProperties.getVersion()); } - + + /** + * Checks if a certain key exists in the node + * @param node node to check the keys of. + * @param key key to check + * @return true if the key is stored in the preferences, false otherwise + */ + private boolean keyExists(Preferences node, String key) { + try { + return Arrays.asList(node.keys()).contains(key); + } catch (BackingStoreException e) { + e.printStackTrace(); + return false; + } + } + /** * Return a string preference. * @@ -124,12 +149,28 @@ public class SwingPreferences extends net.sf.openrocket.startup.Preferences { */ @Override public String getString(String key, String def) { + if (!keyExists(PREFNODE, key) && key != null && def != null) { + PREFNODE.put(key, def); + try { + PREFNODE.flush(); + } catch (BackingStoreException e) { + e.printStackTrace(); + } + } return PREFNODE.get(key, def); } @Override public String getString(String directory, String key, String defaultValue) { Preferences p = PREFNODE.node(directory); + if (!keyExists(p, key) && key != null && defaultValue != null) { + p.put(key, defaultValue); + try { + p.flush(); + } catch (BackingStoreException e) { + e.printStackTrace(); + } + } return p.get(key, defaultValue); } @@ -169,6 +210,16 @@ public class SwingPreferences extends net.sf.openrocket.startup.Preferences { */ @Override public boolean getBoolean(String key, boolean def) { + // Check if the key exists + if (!keyExists(PREFNODE, key) && key != null) { + // Save the default value + PREFNODE.putBoolean(key, def); + try { + PREFNODE.flush(); + } catch (BackingStoreException e) { + e.printStackTrace(); + } + } return PREFNODE.getBoolean(key, def); } @@ -183,9 +234,17 @@ public class SwingPreferences extends net.sf.openrocket.startup.Preferences { PREFNODE.putBoolean(key, value); storeVersion(); } - + @Override public int getInt(String key, int defaultValue) { + if (!keyExists(PREFNODE, key) && key != null) { + PREFNODE.putInt(key, defaultValue); + try { + PREFNODE.flush(); + } catch (BackingStoreException e) { + e.printStackTrace(); + } + } return PREFNODE.getInt(key, defaultValue); } @@ -194,9 +253,17 @@ public class SwingPreferences extends net.sf.openrocket.startup.Preferences { PREFNODE.putInt(key, value); storeVersion(); } - + @Override public double getDouble(String key, double defaultValue) { + if (!keyExists(PREFNODE, key) && key != null) { + PREFNODE.putDouble(key, defaultValue); + try { + PREFNODE.flush(); + } catch (BackingStoreException e) { + e.printStackTrace(); + } + } return PREFNODE.getDouble(key, defaultValue); } @@ -228,7 +295,7 @@ public class SwingPreferences extends net.sf.openrocket.startup.Preferences { } public File getDefaultDirectory() { - String file = getString("defaultDirectory", null); + String file = getString(net.sf.openrocket.startup.Preferences.DEFAULT_DIRECTORY, null); if (file == null) return null; return new File(file); @@ -241,7 +308,7 @@ public class SwingPreferences extends net.sf.openrocket.startup.Preferences { } else { d = dir.getAbsolutePath(); } - putString("defaultDirectory", d); + putString(net.sf.openrocket.startup.Preferences.DEFAULT_DIRECTORY, d); storeVersion(); } @@ -262,13 +329,22 @@ public class SwingPreferences extends net.sf.openrocket.startup.Preferences { return compdir; } + /** + * Set the operating system that the software updater will use to redirect you to an installer download link. + * @param platform the operating system to use + */ public void setUpdatePlatform(UpdatePlatform platform) { if (platform == null) return; - putString("UpdatePlatform", platform.name()); + putString(UPDATE_PLATFORM, platform.name()); } + /** + * Get the operating system that will be selected when asking for a software update. + * E.g. "Windows" will cause the software updater to default to letting you download a Windows installer. + * @return the operating system that is used + */ public UpdatePlatform getUpdatePlatform() { - String p = getString("UpdatePlatform", SystemInfo.getPlatform().name()); + String p = getString(UPDATE_PLATFORM, SystemInfo.getPlatform().name()); if (p == null) return null; return UpdatePlatform.valueOf(p); } @@ -372,7 +448,7 @@ public class SwingPreferences extends net.sf.openrocket.startup.Preferences { public Point getWindowPosition(Class c) { int x, y; - String pref = PREFNODE.node("windows").get("position." + c.getCanonicalName(), null); + String pref = PREFNODE.node(NODE_WINDOWS).get("position." + c.getCanonicalName(), null); if (pref == null) return null; @@ -390,7 +466,7 @@ public class SwingPreferences extends net.sf.openrocket.startup.Preferences { } public void setWindowPosition(Class c, Point p) { - PREFNODE.node("windows").put("position." + c.getCanonicalName(), "" + p.x + "," + p.y); + PREFNODE.node(NODE_WINDOWS).put("position." + c.getCanonicalName(), "" + p.x + "," + p.y); storeVersion(); } @@ -399,7 +475,7 @@ public class SwingPreferences extends net.sf.openrocket.startup.Preferences { public Dimension getWindowSize(Class c) { int x, y; - String pref = PREFNODE.node("windows").get("size." + c.getCanonicalName(), null); + String pref = PREFNODE.node(NODE_WINDOWS).get("size." + c.getCanonicalName(), null); if (pref == null) return null; @@ -418,22 +494,22 @@ public class SwingPreferences extends net.sf.openrocket.startup.Preferences { public boolean isWindowMaximized(Class c) { - String pref = PREFNODE.node("windows").get("size." + c.getCanonicalName(), null); + String pref = PREFNODE.node(NODE_WINDOWS).get("size." + c.getCanonicalName(), null); return "max".equals(pref); } public void setWindowSize(Class c, Dimension d) { - PREFNODE.node("windows").put("size." + c.getCanonicalName(), "" + d.width + "," + d.height); + PREFNODE.node(NODE_WINDOWS).put("size." + c.getCanonicalName(), "" + d.width + "," + d.height); storeVersion(); } public void setWindowMaximized(Class c) { - PREFNODE.node("windows").put("size." + c.getCanonicalName(), "max"); + PREFNODE.node(NODE_WINDOWS).put("size." + c.getCanonicalName(), "max"); storeVersion(); } public Integer getTableColumnWidth(String keyName, int columnIdx) { - String pref = PREFNODE.node("tables").get( + String pref = PREFNODE.node(NODE_TABLES).get( "cw." + keyName + "." + columnIdx, null); if (pref == null) return null; @@ -451,7 +527,7 @@ public class SwingPreferences extends net.sf.openrocket.startup.Preferences { } public void setTableColumnWidth(String keyName, int columnIdx, Integer width) { - PREFNODE.node("tables").put( + PREFNODE.node(NODE_TABLES).put( "cw." + keyName + "." + columnIdx, width.toString()); storeVersion(); } diff --git a/swing/src/net/sf/openrocket/utils/ComponentPresetEditor.java b/swing/src/net/sf/openrocket/utils/ComponentPresetEditor.java index a70cfc878..a1baf53f3 100644 --- a/swing/src/net/sf/openrocket/utils/ComponentPresetEditor.java +++ b/swing/src/net/sf/openrocket/utils/ComponentPresetEditor.java @@ -342,6 +342,8 @@ public class ComponentPresetEditor extends JPanel implements PresetResultListene log.info(Markers.USER_MARKER, "User decided not to open, option=" + option); return false; } + + ((SwingPreferences) Application.getPreferences()).setDefaultDirectory(chooser.getCurrentDirectory()); File file = chooser.getSelectedFile(); try {