From 4dd3bce26d12f082861fc9269c2d0e1719dfab2f Mon Sep 17 00:00:00 2001 From: SiboVG Date: Fri, 6 Sep 2024 01:25:09 +0200 Subject: [PATCH] [#1081] Add ability to choose user-defined component presets location --- .../ComponentPresetDatabaseLoader.java | 63 ++++++++---- .../preferences/ApplicationPreferences.java | 75 +++++++++++++- .../main/resources/l10n/messages.properties | 3 + .../preferences/GeneralPreferencesPanel.java | 97 ++++++++++++++++++- .../swing/utils/ComponentPresetEditor.java | 4 +- 5 files changed, 214 insertions(+), 28 deletions(-) diff --git a/core/src/main/java/info/openrocket/core/database/ComponentPresetDatabaseLoader.java b/core/src/main/java/info/openrocket/core/database/ComponentPresetDatabaseLoader.java index 2bdf5b514..e6f78cd0f 100644 --- a/core/src/main/java/info/openrocket/core/database/ComponentPresetDatabaseLoader.java +++ b/core/src/main/java/info/openrocket/core/database/ComponentPresetDatabaseLoader.java @@ -1,6 +1,7 @@ package info.openrocket.core.database; import java.io.File; +import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.util.Collection; @@ -28,7 +29,7 @@ public class ComponentPresetDatabaseLoader extends AsynchronousDatabaseLoader { private static final String SYSTEM_PRESET_DIR = "datafiles/components"; private int fileCount = 0; private int presetCount = 0; - + /** the database is immutable*/ private final ComponentPresetDatabase componentPresetDao = new ComponentPresetDatabase(); @@ -58,28 +59,28 @@ public class ComponentPresetDatabaseLoader extends AsynchronousDatabaseLoader { } /** - * loads the user defined defined components into the database + * loads the user defined component presets into the database * uses the directory defined in the preferences */ private void loadUserComponents() { + log.info("Starting reading user-defined component presets"); SimpleFileFilter orcFilter = new SimpleFileFilter("", false, "orc"); - FileIterator iterator; - try { - iterator = new DirectoryIterator( - Application.getPreferences().getDefaultUserComponentDirectory(), - orcFilter, - true); - } catch (IOException ioex) { - log.debug("Error opening UserComponentDirectory", ioex); - return; - } - while (iterator.hasNext()) { - Pair f = iterator.next(); - Collection presets = loadFile(f.getU().getName(), f.getV()); - componentPresetDao.addAll(presets); - fileCount++; - presetCount += presets.size(); + int initialCount = presetCount; + for (File file : (Application.getPreferences()).getUserComponentPresetFiles()) { + if (file.isFile()) { + try { + InputStream stream = new FileInputStream(file); + loadFile(file.getName(), stream); + } catch (IOException e) { + log.warn("Error opening file " + file, e); + } + } else if (file.isDirectory()) { + loadDirectory(orcFilter, file); + } else { + log.warn("User-defined motor file " + file + " is neither file nor directory"); + } } + log.info("Ending reading user-defined component presets, presetCount=" + (presetCount-initialCount)); } /** @@ -90,7 +91,7 @@ public class ComponentPresetDatabaseLoader extends AsynchronousDatabaseLoader { log.info("Loading component presets from " + SYSTEM_PRESET_DIR); FileIterator iterator = DirectoryIterator.findDirectory(SYSTEM_PRESET_DIR, new SimpleFileFilter("", false, "orc")); - if(iterator == null) + if (iterator == null) return; while (iterator.hasNext()) { @@ -115,6 +116,28 @@ public class ComponentPresetDatabaseLoader extends AsynchronousDatabaseLoader { OpenRocketComponentLoader loader = new OpenRocketComponentLoader(); Collection presets = loader.load(stream, fileName); return presets; - + } + + /** + * loads an entire directory of component presets + * + * @param fileFilter the supported extensions of files + * @param file the directory file object + */ + private void loadDirectory(SimpleFileFilter fileFilter, File file) { + FileIterator iterator; + try { + iterator = new DirectoryIterator(file, fileFilter, true); + } catch (IOException ioex) { + log.debug("Error opening UserComponentDirectory", ioex); + return; + } + while (iterator.hasNext()) { + Pair f = iterator.next(); + Collection presets = loadFile(f.getU().getName(), f.getV()); + componentPresetDao.addAll(presets); + fileCount++; + presetCount += presets.size(); + } } } diff --git a/core/src/main/java/info/openrocket/core/preferences/ApplicationPreferences.java b/core/src/main/java/info/openrocket/core/preferences/ApplicationPreferences.java index 8666e5f5a..888518cb9 100644 --- a/core/src/main/java/info/openrocket/core/preferences/ApplicationPreferences.java +++ b/core/src/main/java/info/openrocket/core/preferences/ApplicationPreferences.java @@ -45,6 +45,7 @@ public abstract class ApplicationPreferences implements ChangeSource, ORPreferen public static final String BODY_COMPONENT_INSERT_POSITION_KEY = "BodyComponentInsertPosition"; public static final String STAGE_INSERT_POSITION_KEY = "StageInsertPosition"; public static final String USER_THRUST_CURVES_KEY = "UserThrustCurves"; + public static final String USER_COMPONENT_PRESETS_KEY = "UserComponentPresets"; public static final String DEFAULT_MACH_NUMBER = "DefaultMachNumber"; @@ -1018,7 +1019,57 @@ public abstract class ApplicationPreferences implements ChangeSource, ORPreferen return null; } - public File getDefaultUserComponentDirectory() { + /** + * Return a list of files/directories to be loaded as custom component presets. + *

+ * If this property has not been set, the directory "Components" in the user + * application directory will be used. The directory will be created if it does not + * exist. + * + * @return a list of files to load as component presets. + */ + public List getUserComponentPresetFiles() { + List list = new ArrayList<>(); + + String files = getString(USER_COMPONENT_PRESETS_KEY, null); + if (files == null) { + // Default to application directory + File cpdir = getDefaultUserComponentFile(); + if (!cpdir.isDirectory()) { + cpdir.mkdirs(); + } + list.add(cpdir); + } else { + for (String file : files.split("\\" + SPLIT_CHARACTER)) { + file = file.trim(); + if (file.length() > 0) { + list.add(new File(file)); + } + } + } + + return list; + } + + /** + * Returns the files/directories to be loaded as custom component presets, formatting as a string. If there are multiple + * locations, they are separated by a semicolon. + * + * @return a list of files to load as component presets, formatted as a semicolon separated string. + */ + public String getUserComponentPresetFilesAsString() { + List files = getUserComponentPresetFiles(); + StringBuilder sb = new StringBuilder(); + for (File file : files) { + if (!sb.isEmpty()) { + sb.append(";"); + } + sb.append(file.getAbsolutePath()); + } + return sb.toString(); + } + + public File getDefaultUserComponentFile() { File compdir = new File(SystemInfo.getUserApplicationDirectory(), "Components"); if (!compdir.isDirectory()) { @@ -1034,6 +1085,28 @@ public abstract class ApplicationPreferences implements ChangeSource, ORPreferen return compdir; } + /** + * Set the list of files/directories to be loaded as custom component presets. + * + * @param files the files to load, or null to reset to default value. + */ + public void setUserComponentPresetFiles(List files) { + if (files == null) { + putString(USER_COMPONENT_PRESETS_KEY, null); + return; + } + + StringBuilder str = new StringBuilder(); + + for (File file : files) { + if (!str.isEmpty()) { + str.append(SPLIT_CHARACTER); + } + str.append(file.getAbsolutePath()); + } + putString(USER_COMPONENT_PRESETS_KEY, str.toString()); + } + /** * Return a list of files/directories to be loaded as custom thrust curves. *

diff --git a/core/src/main/resources/l10n/messages.properties b/core/src/main/resources/l10n/messages.properties index 8ea9ca2cc..b684653bc 100644 --- a/core/src/main/resources/l10n/messages.properties +++ b/core/src/main/resources/l10n/messages.properties @@ -357,11 +357,14 @@ pref.dlg.checkbox.ShowDiscardConfirmation.ttip = If checked, you will be asked i 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.User-definedComponentPreset = User-defined component presets: pref.dlg.lbl.Windspeed = Wind speed pref.dlg.Allthrustcurvefiles = All thrust curve files (*.eng; *.rse; *.zip; directories) pref.dlg.RASPfiles = RASP motor files (*.eng) pref.dlg.RockSimfiles = RockSim engine files (*.rse) pref.dlg.ZIParchives = ZIP archives (*.zip) +pref.dlg.AllComponentPresetfiles = All component preset files (*.orc; directories) +pref.dlg.ORCfiles = OpenRocket component files (*.orc) pref.dlg.checkbox.Checkupdates = Always check for software updates at startup pref.dlg.checkbox.Checkupdates.ttip = Check for software updates every time you start up OpenRocket pref.dlg.checkbox.CheckBetaupdates = Also check for pre-releases diff --git a/swing/src/main/java/info/openrocket/swing/gui/dialogs/preferences/GeneralPreferencesPanel.java b/swing/src/main/java/info/openrocket/swing/gui/dialogs/preferences/GeneralPreferencesPanel.java index 92f8f9b5f..e171f6454 100644 --- a/swing/src/main/java/info/openrocket/swing/gui/dialogs/preferences/GeneralPreferencesPanel.java +++ b/swing/src/main/java/info/openrocket/swing/gui/dialogs/preferences/GeneralPreferencesPanel.java @@ -20,6 +20,7 @@ import javax.swing.JLabel; import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.JProgressBar; +import javax.swing.JSeparator; import javax.swing.JSpinner; import javax.swing.JTextField; import javax.swing.SwingUtilities; @@ -128,7 +129,7 @@ public class GeneralPreferencesPanel extends PreferencesPanel { //// You need to restart OpenRocket for the theme change to take effect. final JLabel lblRestartORTheme = new JLabel(); lblRestartORTheme.setForeground(GUIUtil.getUITheme().getDarkErrorColor()); - this.add(lblRestartORTheme, "spanx, wrap para*2, growx"); + this.add(lblRestartORTheme, "spanx, wrap, growx"); fontSizeSpinner.addChangeListener(new ChangeListener() { @Override @@ -156,6 +157,8 @@ public class GeneralPreferencesPanel extends PreferencesPanel { } }); + this.add(new JSeparator(JSeparator.HORIZONTAL), "spanx, growx, wrap para"); + //// User-defined thrust curves: this.add(new JLabel(trans.get("pref.dlg.lbl.User-definedthrust")), "spanx, wrap"); final JTextField field = new JTextField(); @@ -251,10 +254,94 @@ public class GeneralPreferencesPanel extends PreferencesPanel { DescriptionArea desc = new DescriptionArea(trans.get("pref.dlg.DescriptionArea.Adddirectories"), 3, -1.5f, false); desc.setBackground(GUIUtil.getUITheme().getBackgroundColor()); desc.setForeground(GUIUtil.getUITheme().getTextColor()); - this.add(desc, "spanx, growx, wrap 40lp"); - - - + this.add(desc, "spanx, growx, wrap unrel"); + + //// User-defined component presets: + this.add(new JLabel(trans.get("pref.dlg.lbl.User-definedComponentPreset")), "spanx, wrap"); + final JTextField fieldCompPres = new JTextField(); + str = preferences.getUserComponentPresetFilesAsString(); + fieldCompPres.setText(str); + fieldCompPres.getDocument().addDocumentListener(new DocumentListener() { + @Override + public void removeUpdate(DocumentEvent e) { + changed(); + } + + @Override + public void insertUpdate(DocumentEvent e) { + changed(); + } + + @Override + public void changedUpdate(DocumentEvent e) { + changed(); + } + + private void changed() { + String text = fieldCompPres.getText(); + List list = new ArrayList<>(); + for (String s : text.split(";")) { + s = s.trim(); + if (s.length() > 0) { + list.add(new File(s)); + } + } + preferences.setUserComponentPresetFiles(list); + } + }); + this.add(fieldCompPres, "w 100px, gapright unrel, spanx, growx, split"); + + //// Add button + button = new JButton(trans.get("pref.dlg.but.add")); + button.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + JFileChooser chooser = new JFileChooser(); + SimpleFileFilter filter = + new SimpleFileFilter( + trans.get("pref.dlg.AllComponentPresetfiles"), + true, "orc"); + chooser.addChoosableFileFilter(filter); + //// OpenRocket component files (*.orc) + chooser.addChoosableFileFilter(new SimpleFileFilter(trans.get("pref.dlg.ORCfiles"), + true, "orc")); + chooser.setFileFilter(filter); + chooser.setFileSelectionMode(JFileChooser.FILES_AND_DIRECTORIES); + if (defaultDirectory != null) { + chooser.setCurrentDirectory(defaultDirectory); + } + + //// Add + int returnVal = chooser.showDialog(GeneralPreferencesPanel.this, trans.get("pref.dlg.Add")); + if (returnVal == JFileChooser.APPROVE_OPTION) { + log.info(Markers.USER_MARKER, "Adding component preset file: " + chooser.getSelectedFile()); + defaultDirectory = chooser.getCurrentDirectory(); + String text = fieldCompPres.getText().trim(); + if (text.length() > 0) { + text += ";"; + } + text += chooser.getSelectedFile().getAbsolutePath(); + fieldCompPres.setText(text); + } + } + }); + this.add(button, "gapright unrel"); + + //// Reset button + button = new JButton(trans.get("pref.dlg.but.reset")); + + button.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + // First one sets to the default, but does not un-set the pref + fieldCompPres.setText(preferences.getDefaultUserComponentFile().getAbsolutePath()); + preferences.setUserComponentPresetFiles(null); + } + }); + this.add(button, "wrap"); + + this.add(new JSeparator(JSeparator.HORIZONTAL), "spanx, growx, wrap para"); + //// Check for software updates at startup final JCheckBox softwareUpdateBox = diff --git a/swing/src/main/java/info/openrocket/swing/utils/ComponentPresetEditor.java b/swing/src/main/java/info/openrocket/swing/utils/ComponentPresetEditor.java index 6ad7e71f0..2c57ca9a8 100644 --- a/swing/src/main/java/info/openrocket/swing/utils/ComponentPresetEditor.java +++ b/swing/src/main/java/info/openrocket/swing/utils/ComponentPresetEditor.java @@ -332,7 +332,7 @@ public class ComponentPresetEditor extends JPanel implements PresetResultListene chooser.setCurrentDirectory(editContext.getLastDirectory()); } else { - chooser.setCurrentDirectory(((SwingPreferences) Application.getPreferences()).getDefaultUserComponentDirectory()); + chooser.setCurrentDirectory((Application.getPreferences()).getDefaultUserComponentFile()); } int option = chooser.showOpenDialog(ComponentPresetEditor.this); @@ -414,7 +414,7 @@ public class ComponentPresetEditor extends JPanel implements PresetResultListene chooser.setSelectedFile(editContext.getOpenedFile()); } else { - chooser.setCurrentDirectory(((SwingPreferences) Application.getPreferences()).getDefaultUserComponentDirectory()); + chooser.setCurrentDirectory((Application.getPreferences()).getDefaultUserComponentFile()); } int option = chooser.showSaveDialog(ComponentPresetEditor.this);