package net.sf.openrocket.gui.util;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Point;
import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Set;
import java.util.prefs.BackingStoreException;
import java.util.prefs.Preferences;
import net.sf.openrocket.arch.SystemInfo;
import net.sf.openrocket.document.Simulation;
import net.sf.openrocket.material.Material;
import net.sf.openrocket.motor.Manufacturer;
import net.sf.openrocket.preset.ComponentPreset;
import net.sf.openrocket.rocketcomponent.Rocket;
import net.sf.openrocket.simulation.FlightDataType;
import net.sf.openrocket.simulation.RK4SimulationStepper;
import net.sf.openrocket.simulation.SimulationOptions;
import net.sf.openrocket.startup.Application;
import net.sf.openrocket.unit.UnitGroup;
import net.sf.openrocket.util.BugException;
import net.sf.openrocket.util.BuildProperties;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class SwingPreferences extends net.sf.openrocket.startup.Preferences {
private static final Logger log = LoggerFactory.getLogger(SwingPreferences.class);
private static final String SPLIT_CHARACTER = "|";
private static final List<Locale> SUPPORTED_LOCALES;
static {
List<Locale> list = new ArrayList<Locale>();
for (String lang : new String[] { "en", "de", "es", "fr", "it", "ru", "cs", "pl", "ja", "pt", "tr" }) {
list.add(new Locale(lang));
}
list.add(new Locale("zh", "CN"));
list.add(new Locale("uk", "UA"));
SUPPORTED_LOCALES = Collections.unmodifiableList(list);
}
/**
* Whether to use the debug-node instead of the normal node.
*/
private static final boolean DEBUG;
static {
DEBUG = (System.getProperty("openrocket.debug.prefs") != null);
}
/**
* Whether to clear all preferences at application startup. This has an effect only
* if DEBUG is true.
*/
private static final boolean CLEARPREFS = true;
/**
* The node name to use in the Java preferences storage.
*/
private static final String NODENAME = (DEBUG ? "OpenRocket-debug" : "OpenRocket");
private final Preferences PREFNODE;
public SwingPreferences() {
Preferences root = Preferences.userRoot();
if (DEBUG && CLEARPREFS) {
try {
if (root.nodeExists(NODENAME)) {
root.node(NODENAME).removeNode();
}
} catch (BackingStoreException e) {
throw new BugException("Unable to clear preference node", e);
}
}
PREFNODE = root.node(NODENAME);
}
//////////////////////
/**
* Store the current OpenRocket version into the preferences to allow for preferences migration.
*/
private void storeVersion() {
PREFNODE.put("OpenRocketVersion", BuildProperties.getVersion());
}
/**
* Return a string preference.
*
* @param key the preference key.
* @param def the default if no preference is stored
* @return the preference value
*/
@Override
public String getString(String key, String def) {
return PREFNODE.get(key, def);
}
@Override
public String getString(String directory, String key, String defaultValue) {
Preferences p = PREFNODE.node(directory);
return p.get(key, defaultValue);
}
/**
* Set a string preference.
*
* @param key the preference key
* @param value the value to set, or <code>null</code> to remove the key
*/
@Override
public void putString(String key, String value) {
if (value == null) {
PREFNODE.remove(key);
} else {
PREFNODE.put(key, value);
}
storeVersion();
}
@Override
public void putString(String directory, String key, String value) {
Preferences p = PREFNODE.node(directory);
if (value == null) {
p.remove(key);
} else {
p.put(key, value);
}
storeVersion();
}
/**
* Return a boolean preference.
*
* @param key the preference key
* @param def the default if no preference is stored
* @return the preference value
*/
@Override
public boolean getBoolean(String key, boolean def) {
return PREFNODE.getBoolean(key, def);
}
/**
* Set a boolean preference.
*
* @param key the preference key
* @param value the value to set
*/
@Override
public void putBoolean(String key, boolean value) {
PREFNODE.putBoolean(key, value);
storeVersion();
}
@Override
public int getInt(String key, int defaultValue) {
return PREFNODE.getInt(key, defaultValue);
}
@Override
public void putInt(String key, int value) {
PREFNODE.putInt(key, value);
storeVersion();
}
@Override
public double getDouble(String key, double defaultValue) {
return PREFNODE.getDouble(key, defaultValue);
}
@Override
public void putDouble(String key, double value) {
PREFNODE.putDouble(key, value);
storeVersion();
}
/**
* Return a preferences object for the specified node name.
*
* @param nodeName the node name
* @return the preferences object for that node
*/
@Override
public Preferences getNode(String nodeName) {
return PREFNODE.node(nodeName);
}
//////////////////
public static List<Locale> getSupportedLocales() {
return SUPPORTED_LOCALES;
}
public File getDefaultDirectory() {
String file = getString("defaultDirectory", null);
if (file == null)
return null;
return new File(file);
}
public void setDefaultDirectory(File dir) {
String d;
if (dir == null) {
d = null;
} else {
d = dir.getAbsolutePath();
}
putString("defaultDirectory", d);
storeVersion();
}
public File getDefaultUserComponentDirectory() {
File compdir = new File(SystemInfo.getUserApplicationDirectory(), "Components");
if (!compdir.isDirectory()) {
compdir.mkdirs();
}
if (!compdir.isDirectory()) {
return null;
}
if (!compdir.canRead()) {
return null;
}
return compdir;
}
/**
* Return a list of files/directories to be loaded as custom thrust curves.
* <p>
* If this property has not been set, the directory "ThrustCurves" 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 thrust curves.
*/
public List<File> getUserThrustCurveFiles() {
List<File> list = new ArrayList<File>();
String files = getString(USER_THRUST_CURVES_KEY, null);
if (files == null) {
// Default to application directory
File tcdir = getDefaultUserThrustCurveFile();
if (!tcdir.isDirectory()) {
tcdir.mkdirs();
}
list.add(tcdir);
} else {
for (String file : files.split("\\" + SPLIT_CHARACTER)) {
file = file.trim();
if (file.length() > 0) {
list.add(new File(file));
}
}
}
return list;
}
public File getDefaultUserThrustCurveFile() {
File appdir = SystemInfo.getUserApplicationDirectory();
File tcdir = new File(appdir, "ThrustCurves");
return tcdir;
}
/**
* Set the list of files/directories to be loaded as custom thrust curves.
*
* @param files the files to load, or <code>null</code> to reset to default value.
*/
public void setUserThrustCurveFiles(List<File> files) {
if (files == null) {
putString(USER_THRUST_CURVES_KEY, null);
return;
}
String str = "";
for (File file : files) {
if (str.length() > 0) {
str += SPLIT_CHARACTER;
}
str += file.getAbsolutePath();
}
putString(USER_THRUST_CURVES_KEY, str);
}
public Color getMotorBorderColor() {
// TODO: MEDIUM: Motor color (settable?)
return new Color(0, 0, 0, 200);
}
public Color getMotorFillColor() {
// TODO: MEDIUM: Motor fill color (settable?)
return new Color(0, 0, 0, 100);
}
public static int getMaxThreadCount() {
return Runtime.getRuntime().availableProcessors();
}
public Point getWindowPosition(Class<?> c) {
int x, y;
String pref = PREFNODE.node("windows").get("position." + c.getCanonicalName(), null);
if (pref == null)
return null;
if (pref.indexOf(',') < 0)
return null;
try {
x = Integer.parseInt(pref.substring(0, pref.indexOf(',')));
y = Integer.parseInt(pref.substring(pref.indexOf(',') + 1));
} catch (NumberFormatException e) {
return null;
}
return new Point(x, y);
}
public void setWindowPosition(Class<?> c, Point p) {
PREFNODE.node("windows").put("position." + c.getCanonicalName(), "" + p.x + "," + p.y);
storeVersion();
}
public Dimension getWindowSize(Class<?> c) {
int x, y;
String pref = PREFNODE.node("windows").get("size." + c.getCanonicalName(), null);
if (pref == null)
return null;
if (pref.indexOf(',') < 0)
return null;
try {
x = Integer.parseInt(pref.substring(0, pref.indexOf(',')));
y = Integer.parseInt(pref.substring(pref.indexOf(',') + 1));
} catch (NumberFormatException e) {
return null;
}
return new Dimension(x, y);
}
public boolean isWindowMaximized(Class<?> c) {
String pref = PREFNODE.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);
storeVersion();
}
public void setWindowMaximized(Class<?> c) {
PREFNODE.node("windows").put("size." + c.getCanonicalName(), "max");
storeVersion();
}
/**
* this class returns a java.awt.Color object for the specified key.
* you can pass (java.awt.Color) null to the second argument to
* disambiguate
*/
public Color getColor(String key, Color defaultValue) {
net.sf.openrocket.util.Color c = super.getColor(key, (net.sf.openrocket.util.Color) null);
if (c == null) {
return defaultValue;
}
return ColorConversion.toAwtColor(c);
}
/**
*
*/
public void putColor(String key, Color value) {
net.sf.openrocket.util.Color c = ColorConversion.fromAwtColor(value);
super.putColor(key, c);
}
//// Printing
//// Background flight data computation
public boolean computeFlightInBackground() {
return PREFNODE.getBoolean("backgroundFlight", true);
}
public void setComputeFlightInBackground(boolean b) {
PREFNODE.putBoolean("backgroundFlight", b);
}
public Simulation getBackgroundSimulation(Rocket rocket) {
Simulation s = new Simulation(rocket);
SimulationOptions cond = s.getOptions();
cond.setTimeStep(RK4SimulationStepper.RECOMMENDED_TIME_STEP * 2);
cond.setWindSpeedAverage(1.0);
cond.setWindSpeedDeviation(0.1);
cond.setLaunchRodLength(5);
return s;
}
///////// Export variables
public boolean isExportSelected(FlightDataType type) {
Preferences prefs = PREFNODE.node("exports");
return prefs.getBoolean(type.getName(), false);
}
public void setExportSelected(FlightDataType type, boolean selected) {
Preferences prefs = PREFNODE.node("exports");
prefs.putBoolean(type.getName(), selected);
}
///////// Default unit storage
public void loadDefaultUnits() {
Preferences prefs = PREFNODE.node("units");
try {
for (String key : prefs.keys()) {
UnitGroup group = UnitGroup.UNITS.get(key);
if (group == null)
continue;
try {
group.setDefaultUnit(prefs.get(key, null));
} catch (IllegalArgumentException ignore) {
}
}
} catch (BackingStoreException e) {
Application.getExceptionHandler().handleErrorCondition(e);
}
}
public void storeDefaultUnits() {
Preferences prefs = PREFNODE.node("units");
for (String key : UnitGroup.UNITS.keySet()) {
UnitGroup group = UnitGroup.UNITS.get(key);
if (group == null || group.getUnitCount() < 2)
continue;
prefs.put(key, group.getDefaultUnit().getUnit());
}
}
//// Material storage
/**
* Add a user-defined material to the preferences. The preferences are
* first checked for an existing material matching the provided one using
* {@link Material#equals(Object)}.
*
* @param m the material to add.
*/
@Override
public void addUserMaterial(Material m) {
Preferences prefs = PREFNODE.node("userMaterials");
// Check whether material already exists
if (getUserMaterials().contains(m)) {
return;
}
// Add material using next free key (key is not used when loading)
String mat = m.toStorableString();
for (int i = 0;; i++) {
String key = "material" + i;
if (prefs.get(key, null) == null) {
prefs.put(key, mat);
return;
}
}
}
/**
* Remove a user-defined material from the preferences. The matching is performed
* using {@link Material#equals(Object)}.
*
* @param m the material to remove.
*/
@Override
public void removeUserMaterial(Material m) {
Preferences prefs = PREFNODE.node("userMaterials");
try {
// Iterate through materials and remove all keys with a matching material
for (String key : prefs.keys()) {
String value = prefs.get(key, null);
try {
Material existing = Material.fromStorableString(value, true);
if (existing.equals(m)) {
prefs.remove(key);
}
} catch (IllegalArgumentException ignore) {
}
}
} catch (BackingStoreException e) {
throw new IllegalStateException("Cannot read preferences!", e);
}
}
/**
* Return a set of all user-defined materials in the preferences. The materials
* are created marked as user-defined.
*
* @return a set of all user-defined materials.
*/
@Override
public Set<Material> getUserMaterials() {
Preferences prefs = PREFNODE.node("userMaterials");
HashSet<Material> materials = new HashSet<Material>();
try {
for (String key : prefs.keys()) {
String value = prefs.get(key, null);
try {
Material m = Material.fromStorableString(value, true);
materials.add(m);
} catch (IllegalArgumentException e) {
log.warn("Illegal material string " + value);
}
}
} catch (BackingStoreException e) {
throw new IllegalStateException("Cannot read preferences!", e);
}
return materials;
}
//// Preset Component Favorites
@Override
public void setComponentFavorite(ComponentPreset preset, ComponentPreset.Type type, boolean favorite) {
Preferences prefs = PREFNODE.node("favoritePresets").node(type.name());
if (favorite) {
prefs.putBoolean(preset.preferenceKey(), true);
} else {
prefs.remove(preset.preferenceKey());
}
}
@Override
public Set<String> getComponentFavorites(ComponentPreset.Type type) {
Preferences prefs = PREFNODE.node("favoritePresets").node(type.name());
Set<String> collection = new HashSet<String>();
try {
collection.addAll(Arrays.asList(prefs.keys()));
} catch (BackingStoreException bex) {
}
return collection;
}
//// Decal Editor Setting
private final static String DECAL_EDITOR_PREFERNCE_NODE = "decalEditorPreference";
private final static String DECAL_EDITOR_USE_SYSTEM_DEFAULT = "<SYSTEM>";
public void clearDecalEditorPreference() {
putString(DECAL_EDITOR_PREFERNCE_NODE, null);
}
public void setDecalEditorPreference(boolean useSystem, String commandLine) {
if (useSystem) {
putString(DECAL_EDITOR_PREFERNCE_NODE, DECAL_EDITOR_USE_SYSTEM_DEFAULT);
} else if (commandLine != null) {
putString(DECAL_EDITOR_PREFERNCE_NODE, commandLine);
} else {
clearDecalEditorPreference();
}
}
public boolean isDecalEditorPreferenceSet() {
String s = getString(DECAL_EDITOR_PREFERNCE_NODE, null);
return s != null;
}
public boolean isDecalEditorPreferenceSystem() {
String s = getString(DECAL_EDITOR_PREFERNCE_NODE, null);
return DECAL_EDITOR_USE_SYSTEM_DEFAULT.equals(s);
}
public String getDecalEditorCommandLine() {
return getString(DECAL_EDITOR_PREFERNCE_NODE, null);
}
public List<Manufacturer> getExcludedMotorManufacturers() {
Preferences prefs = PREFNODE.node("excludedMotorManufacturers");
List<Manufacturer> collection = new ArrayList<Manufacturer>();
try {
String[] manuShortNames = prefs.keys();
for (String s : manuShortNames) {
Manufacturer m = Manufacturer.getManufacturer(s);
if (m != null) {
collection.add(m);
}
}
} catch (BackingStoreException e) {
}
return collection;
}
public void setExcludedMotorManufacturers(Collection<Manufacturer> manus) {
Preferences prefs = PREFNODE.node("excludedMotorManufacturers");
try {
for (String s : prefs.keys()) {
prefs.remove(s);
}
} catch (BackingStoreException e) {
}
for (Manufacturer m : manus) {
prefs.putBoolean(m.getSimpleName(), true);
}
}
}