Merge pull request #2554 from SiboVG/issue-1081

[#1081] Add ability to choose user-defined component presets location
This commit is contained in:
Sibo Van Gool 2024-09-10 23:30:05 +02:00 committed by GitHub
commit fd735d85cc
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 214 additions and 28 deletions

View File

@ -1,6 +1,7 @@
package info.openrocket.core.database; package info.openrocket.core.database;
import java.io.File; import java.io.File;
import java.io.FileInputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.util.Collection; import java.util.Collection;
@ -28,7 +29,7 @@ public class ComponentPresetDatabaseLoader extends AsynchronousDatabaseLoader {
private static final String SYSTEM_PRESET_DIR = "datafiles/components"; private static final String SYSTEM_PRESET_DIR = "datafiles/components";
private int fileCount = 0; private int fileCount = 0;
private int presetCount = 0; private int presetCount = 0;
/** the database is immutable*/ /** the database is immutable*/
private final ComponentPresetDatabase componentPresetDao = new ComponentPresetDatabase(); 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 * uses the directory defined in the preferences
*/ */
private void loadUserComponents() { private void loadUserComponents() {
log.info("Starting reading user-defined component presets");
SimpleFileFilter orcFilter = new SimpleFileFilter("", false, "orc"); SimpleFileFilter orcFilter = new SimpleFileFilter("", false, "orc");
FileIterator iterator; int initialCount = presetCount;
try { for (File file : (Application.getPreferences()).getUserComponentPresetFiles()) {
iterator = new DirectoryIterator( if (file.isFile()) {
Application.getPreferences().getDefaultUserComponentDirectory(), try {
orcFilter, InputStream stream = new FileInputStream(file);
true); loadFile(file.getName(), stream);
} catch (IOException ioex) { } catch (IOException e) {
log.debug("Error opening UserComponentDirectory", ioex); log.warn("Error opening file " + file, e);
return; }
} } else if (file.isDirectory()) {
while (iterator.hasNext()) { loadDirectory(orcFilter, file);
Pair<File, InputStream> f = iterator.next(); } else {
Collection<ComponentPreset> presets = loadFile(f.getU().getName(), f.getV()); log.warn("User-defined motor file " + file + " is neither file nor directory");
componentPresetDao.addAll(presets); }
fileCount++;
presetCount += presets.size();
} }
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); log.info("Loading component presets from " + SYSTEM_PRESET_DIR);
FileIterator iterator = DirectoryIterator.findDirectory(SYSTEM_PRESET_DIR, new SimpleFileFilter("", false, "orc")); FileIterator iterator = DirectoryIterator.findDirectory(SYSTEM_PRESET_DIR, new SimpleFileFilter("", false, "orc"));
if(iterator == null) if (iterator == null)
return; return;
while (iterator.hasNext()) { while (iterator.hasNext()) {
@ -115,6 +116,28 @@ public class ComponentPresetDatabaseLoader extends AsynchronousDatabaseLoader {
OpenRocketComponentLoader loader = new OpenRocketComponentLoader(); OpenRocketComponentLoader loader = new OpenRocketComponentLoader();
Collection<ComponentPreset> presets = loader.load(stream, fileName); Collection<ComponentPreset> presets = loader.load(stream, fileName);
return presets; 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<File, InputStream> f = iterator.next();
Collection<ComponentPreset> presets = loadFile(f.getU().getName(), f.getV());
componentPresetDao.addAll(presets);
fileCount++;
presetCount += presets.size();
}
} }
} }

View File

@ -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 BODY_COMPONENT_INSERT_POSITION_KEY = "BodyComponentInsertPosition";
public static final String STAGE_INSERT_POSITION_KEY = "StageInsertPosition"; public static final String STAGE_INSERT_POSITION_KEY = "StageInsertPosition";
public static final String USER_THRUST_CURVES_KEY = "UserThrustCurves"; 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"; public static final String DEFAULT_MACH_NUMBER = "DefaultMachNumber";
@ -1018,7 +1019,57 @@ public abstract class ApplicationPreferences implements ChangeSource, ORPreferen
return null; return null;
} }
public File getDefaultUserComponentDirectory() { /**
* Return a list of files/directories to be loaded as custom component presets.
* <p>
* 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<File> getUserComponentPresetFiles() {
List<File> 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<File> 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"); File compdir = new File(SystemInfo.getUserApplicationDirectory(), "Components");
if (!compdir.isDirectory()) { if (!compdir.isDirectory()) {
@ -1034,6 +1085,28 @@ public abstract class ApplicationPreferences implements ChangeSource, ORPreferen
return compdir; return compdir;
} }
/**
* Set the list of files/directories to be loaded as custom component presets.
*
* @param files the files to load, or <code>null</code> to reset to default value.
*/
public void setUserComponentPresetFiles(List<File> 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. * Return a list of files/directories to be loaded as custom thrust curves.
* <p> * <p>

View File

@ -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 = 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.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-definedthrust = User-defined thrust curves:
pref.dlg.lbl.User-definedComponentPreset = User-defined component presets:
pref.dlg.lbl.Windspeed = Wind speed pref.dlg.lbl.Windspeed = Wind speed
pref.dlg.Allthrustcurvefiles = All thrust curve files (*.eng; *.rse; *.zip; directories) pref.dlg.Allthrustcurvefiles = All thrust curve files (*.eng; *.rse; *.zip; directories)
pref.dlg.RASPfiles = RASP motor files (*.eng) pref.dlg.RASPfiles = RASP motor files (*.eng)
pref.dlg.RockSimfiles = RockSim engine files (*.rse) pref.dlg.RockSimfiles = RockSim engine files (*.rse)
pref.dlg.ZIParchives = ZIP archives (*.zip) 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 = 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.Checkupdates.ttip = Check for software updates every time you start up OpenRocket
pref.dlg.checkbox.CheckBetaupdates = Also check for pre-releases pref.dlg.checkbox.CheckBetaupdates = Also check for pre-releases

View File

@ -20,6 +20,7 @@ import javax.swing.JLabel;
import javax.swing.JOptionPane; import javax.swing.JOptionPane;
import javax.swing.JPanel; import javax.swing.JPanel;
import javax.swing.JProgressBar; import javax.swing.JProgressBar;
import javax.swing.JSeparator;
import javax.swing.JSpinner; import javax.swing.JSpinner;
import javax.swing.JTextField; import javax.swing.JTextField;
import javax.swing.SwingUtilities; 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. //// You need to restart OpenRocket for the theme change to take effect.
final JLabel lblRestartORTheme = new JLabel(); final JLabel lblRestartORTheme = new JLabel();
lblRestartORTheme.setForeground(GUIUtil.getUITheme().getDarkErrorColor()); lblRestartORTheme.setForeground(GUIUtil.getUITheme().getDarkErrorColor());
this.add(lblRestartORTheme, "spanx, wrap para*2, growx"); this.add(lblRestartORTheme, "spanx, wrap, growx");
fontSizeSpinner.addChangeListener(new ChangeListener() { fontSizeSpinner.addChangeListener(new ChangeListener() {
@Override @Override
@ -156,6 +157,8 @@ public class GeneralPreferencesPanel extends PreferencesPanel {
} }
}); });
this.add(new JSeparator(JSeparator.HORIZONTAL), "spanx, growx, wrap para");
//// User-defined thrust curves: //// User-defined thrust curves:
this.add(new JLabel(trans.get("pref.dlg.lbl.User-definedthrust")), "spanx, wrap"); this.add(new JLabel(trans.get("pref.dlg.lbl.User-definedthrust")), "spanx, wrap");
final JTextField field = new JTextField(); 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); DescriptionArea desc = new DescriptionArea(trans.get("pref.dlg.DescriptionArea.Adddirectories"), 3, -1.5f, false);
desc.setBackground(GUIUtil.getUITheme().getBackgroundColor()); desc.setBackground(GUIUtil.getUITheme().getBackgroundColor());
desc.setForeground(GUIUtil.getUITheme().getTextColor()); 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<File> 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 //// Check for software updates at startup
final JCheckBox softwareUpdateBox = final JCheckBox softwareUpdateBox =

View File

@ -332,7 +332,7 @@ public class ComponentPresetEditor extends JPanel implements PresetResultListene
chooser.setCurrentDirectory(editContext.getLastDirectory()); chooser.setCurrentDirectory(editContext.getLastDirectory());
} }
else { else {
chooser.setCurrentDirectory(((SwingPreferences) Application.getPreferences()).getDefaultUserComponentDirectory()); chooser.setCurrentDirectory((Application.getPreferences()).getDefaultUserComponentFile());
} }
int option = chooser.showOpenDialog(ComponentPresetEditor.this); int option = chooser.showOpenDialog(ComponentPresetEditor.this);
@ -414,7 +414,7 @@ public class ComponentPresetEditor extends JPanel implements PresetResultListene
chooser.setSelectedFile(editContext.getOpenedFile()); chooser.setSelectedFile(editContext.getOpenedFile());
} }
else { else {
chooser.setCurrentDirectory(((SwingPreferences) Application.getPreferences()).getDefaultUserComponentDirectory()); chooser.setCurrentDirectory((Application.getPreferences()).getDefaultUserComponentFile());
} }
int option = chooser.showSaveDialog(ComponentPresetEditor.this); int option = chooser.showSaveDialog(ComponentPresetEditor.this);