1 package net.sf.openrocket.util;
4 import java.awt.Dimension;
6 import java.awt.Toolkit;
8 import java.io.IOException;
9 import java.io.InputStream;
10 import java.util.HashMap;
11 import java.util.HashSet;
13 import java.util.MissingResourceException;
14 import java.util.Properties;
16 import java.util.prefs.BackingStoreException;
17 import java.util.prefs.Preferences;
19 import net.sf.openrocket.database.Databases;
20 import net.sf.openrocket.document.Simulation;
21 import net.sf.openrocket.gui.main.ExceptionHandler;
22 import net.sf.openrocket.logging.LogHelper;
23 import net.sf.openrocket.material.Material;
24 import net.sf.openrocket.rocketcomponent.BodyComponent;
25 import net.sf.openrocket.rocketcomponent.FinSet;
26 import net.sf.openrocket.rocketcomponent.InternalComponent;
27 import net.sf.openrocket.rocketcomponent.LaunchLug;
28 import net.sf.openrocket.rocketcomponent.MassObject;
29 import net.sf.openrocket.rocketcomponent.RecoveryDevice;
30 import net.sf.openrocket.rocketcomponent.Rocket;
31 import net.sf.openrocket.rocketcomponent.RocketComponent;
32 import net.sf.openrocket.simulation.FlightDataType;
33 import net.sf.openrocket.simulation.GUISimulationConditions;
34 import net.sf.openrocket.simulation.RK4SimulationStepper;
35 import net.sf.openrocket.startup.Application;
36 import net.sf.openrocket.unit.UnitGroup;
40 private static final LogHelper log = Application.getLogger();
43 * Whether to use the debug-node instead of the normal node.
45 private static final boolean DEBUG;
47 DEBUG = (System.getProperty("openrocket.debug.prefs") != null);
51 * Whether to clear all preferences at application startup. This has an effect only
54 private static final boolean CLEARPREFS = true;
57 * The node name to use in the Java preferences storage.
59 private static final String NODENAME = (DEBUG ? "OpenRocket-debug" : "OpenRocket");
63 * Load property file only when necessary.
65 private static class BuildPropertyHolder {
67 public static final String BUILD_VERSION;
68 public static final String BUILD_SOURCE;
69 public static final boolean DEFAULT_CHECK_UPDATES;
73 InputStream is = ClassLoader.getSystemResourceAsStream("build.properties");
75 throw new MissingResourceException(
76 "build.properties not found, distribution built wrong" +
77 " classpath:" + System.getProperty("java.class.path"),
78 "build.properties", "build.version");
81 Properties props = new Properties();
85 String version = props.getProperty("build.version");
86 if (version == null) {
87 throw new MissingResourceException(
88 "build.version not found in property file",
89 "build.properties", "build.version");
91 BUILD_VERSION = version.trim();
93 BUILD_SOURCE = props.getProperty("build.source");
94 if (BUILD_SOURCE == null) {
95 throw new MissingResourceException(
96 "build.source not found in property file",
97 "build.properties", "build.source");
100 String value = props.getProperty("build.checkupdates");
102 DEFAULT_CHECK_UPDATES = Boolean.parseBoolean(value);
104 DEFAULT_CHECK_UPDATES = true;
106 } catch (IOException e) {
107 throw new MissingResourceException(
108 "Error reading build.properties",
109 "build.properties", "build.version");
114 public static final String BODY_COMPONENT_INSERT_POSITION_KEY = "BodyComponentInsertPosition";
117 public static final String CONFIRM_DELETE_SIMULATION = "ConfirmDeleteSimulation";
119 // Preferences related to data export
120 public static final String EXPORT_FIELD_SEPARATOR = "ExportFieldSeparator";
121 public static final String EXPORT_SIMULATION_COMMENT = "ExportSimulationComment";
122 public static final String EXPORT_FIELD_NAME_COMMENT = "ExportFieldDescriptionComment";
123 public static final String EXPORT_EVENT_COMMENTS = "ExportEventComments";
124 public static final String EXPORT_COMMENT_CHARACTER = "ExportCommentCharacter";
126 public static final String PLOT_SHOW_POINTS = "ShowPlotPoints";
128 private static final String CHECK_UPDATES = "CheckUpdates";
129 public static final String LAST_UPDATE = "LastUpdateVersion";
131 public static final String MOTOR_DIAMETER_FILTER = "MotorDiameterMatch";
132 public static final String MOTOR_HIDE_SIMILAR = "MotorHideSimilar";
136 public static final String PREFERRED_THRUST_CURVE_MOTOR_NODE = "preferredThrustCurveMotors";
140 * Node to this application's preferences.
141 * @deprecated Use the static methods instead.
144 public static final Preferences NODE;
145 private static final Preferences PREFNODE;
148 // Clear the preferences if debug mode and clearprefs is defined
150 Preferences root = Preferences.userRoot();
151 if (DEBUG && CLEARPREFS) {
153 if (root.nodeExists(NODENAME)) {
154 root.node(NODENAME).removeNode();
156 } catch (BackingStoreException e) {
157 throw new BugException("Unable to clear preference node", e);
160 PREFNODE = root.node(NODENAME);
167 ///////// Default component attributes
169 private static final HashMap<Class<?>, String> DEFAULT_COLORS =
170 new HashMap<Class<?>, String>();
172 DEFAULT_COLORS.put(BodyComponent.class, "0,0,240");
173 DEFAULT_COLORS.put(FinSet.class, "0,0,200");
174 DEFAULT_COLORS.put(LaunchLug.class, "0,0,180");
175 DEFAULT_COLORS.put(InternalComponent.class, "170,0,100");
176 DEFAULT_COLORS.put(MassObject.class, "0,0,0");
177 DEFAULT_COLORS.put(RecoveryDevice.class, "255,0,0");
181 private static final HashMap<Class<?>, String> DEFAULT_LINE_STYLES =
182 new HashMap<Class<?>, String>();
184 DEFAULT_LINE_STYLES.put(RocketComponent.class, LineStyle.SOLID.name());
185 DEFAULT_LINE_STYLES.put(MassObject.class, LineStyle.DASHED.name());
190 * Within a holder class so they will load only when needed.
192 private static class DefaultMaterialHolder {
193 private static final Material DEFAULT_LINE_MATERIAL =
194 Databases.findMaterial(Material.Type.LINE, "Elastic cord (round 2mm, 1/16 in)",
196 private static final Material DEFAULT_SURFACE_MATERIAL =
197 Databases.findMaterial(Material.Type.SURFACE, "Ripstop nylon", 0.067, false);
198 private static final Material DEFAULT_BULK_MATERIAL =
199 Databases.findMaterial(Material.Type.BULK, "Cardboard", 680, false);
202 //////////////////////
206 * Return the OpenRocket version number.
208 public static String getVersion() {
209 return BuildPropertyHolder.BUILD_VERSION;
214 * Return the OpenRocket build source (e.g. "default" or "Debian")
216 public static String getBuildSource() {
217 return BuildPropertyHolder.BUILD_SOURCE;
222 * Return the OpenRocket unique ID.
224 * @return a random ID string that stays constant between OpenRocket executions
226 public static String getUniqueID() {
227 String id = PREFNODE.get("id", null);
229 id = UniqueID.uuid();
230 PREFNODE.put("id", id);
238 * Store the current OpenRocket version into the preferences to allow for preferences migration.
240 private static void storeVersion() {
241 PREFNODE.put("OpenRocketVersion", getVersion());
246 * Returns a limited-range integer value from the preferences. If the value
247 * in the preferences is negative or greater than max, then the default value
250 * @param key The preference to retrieve.
251 * @param max Maximum allowed value for the choice.
252 * @param def Default value.
253 * @return The preference value.
255 public static int getChoise(String key, int max, int def) {
256 int v = PREFNODE.getInt(key, def);
257 if ((v < 0) || (v > max))
264 * Helper method that puts an integer choice value into the preferences.
266 * @param key the preference key.
267 * @param value the value to store.
269 public static void putChoise(String key, int value) {
270 PREFNODE.putInt(key, value);
276 * Return a string preference.
278 * @param key the preference key.
279 * @param def the default if no preference is stored
280 * @return the preference value
282 public static String getString(String key, String def) {
283 return PREFNODE.get(key, def);
287 * Set a string preference.
289 * @param key the preference key
290 * @param value the value to set
292 public static void putString(String key, String value) {
293 PREFNODE.put(key, value);
298 * Return a boolean preference.
300 * @param key the preference key
301 * @param def the default if no preference is stored
302 * @return the preference value
304 public static boolean getBoolean(String key, boolean def) {
305 return PREFNODE.getBoolean(key, def);
309 * Set a boolean preference.
311 * @param key the preference key
312 * @param value the value to set
314 public static void putBoolean(String key, boolean value) {
315 PREFNODE.putBoolean(key, value);
321 * Return a preferences object for the specified node name.
323 * @param nodeName the node name
324 * @return the preferences object for that node
326 public static Preferences getNode(String nodeName) {
327 return PREFNODE.node(nodeName);
336 public static boolean getCheckUpdates() {
337 return PREFNODE.getBoolean(CHECK_UPDATES, BuildPropertyHolder.DEFAULT_CHECK_UPDATES);
340 public static void setCheckUpdates(boolean check) {
341 PREFNODE.putBoolean(CHECK_UPDATES, check);
345 public static File getDefaultDirectory() {
346 String file = PREFNODE.get("defaultDirectory", null);
349 return new File(file);
352 public static void setDefaultDirectory(File dir) {
357 d = dir.getAbsolutePath();
359 PREFNODE.put("defaultDirectory", d);
365 public static Color getDefaultColor(Class<? extends RocketComponent> c) {
366 String color = get("componentColors", c, DEFAULT_COLORS);
370 String[] rgb = color.split(",");
371 if (rgb.length == 3) {
373 int red = MathUtil.clamp(Integer.parseInt(rgb[0]), 0, 255);
374 int green = MathUtil.clamp(Integer.parseInt(rgb[1]), 0, 255);
375 int blue = MathUtil.clamp(Integer.parseInt(rgb[2]), 0, 255);
376 return new Color(red, green, blue);
377 } catch (NumberFormatException ignore) {
384 public static void setDefaultColor(Class<? extends RocketComponent> c, Color color) {
387 String string = color.getRed() + "," + color.getGreen() + "," + color.getBlue();
388 set("componentColors", c, string);
391 public static Color getMotorBorderColor() {
392 // TODO: MEDIUM: Motor color (settable?)
393 return new Color(0, 0, 0, 200);
397 public static Color getMotorFillColor() {
398 // TODO: MEDIUM: Motor fill color (settable?)
399 return new Color(0, 0, 0, 100);
403 public static LineStyle getDefaultLineStyle(Class<? extends RocketComponent> c) {
404 String value = get("componentStyle", c, DEFAULT_LINE_STYLES);
406 return LineStyle.valueOf(value);
407 } catch (Exception e) {
408 return LineStyle.SOLID;
412 public static void setDefaultLineStyle(Class<? extends RocketComponent> c,
416 set("componentStyle", c, style.name());
421 * Return the DPI setting of the monitor. This is either the setting provided
422 * by the system or a user-specified DPI setting.
424 * @return the DPI setting to use.
426 public static double getDPI() {
427 int dpi = PREFNODE.getInt("DPI", 0); // Tenths of a dpi
430 dpi = Toolkit.getDefaultToolkit().getScreenResolution() * 10;
435 return ((double) dpi) / 10.0;
439 public static double getDefaultMach() {
440 // TODO: HIGH: implement custom default mach number
447 public static Material getDefaultComponentMaterial(
448 Class<? extends RocketComponent> componentClass,
449 Material.Type type) {
451 String material = get("componentMaterials", componentClass, null);
452 if (material != null) {
454 Material m = Material.fromStorableString(material, false);
455 if (m.getType() == type)
457 } catch (IllegalArgumentException ignore) {
463 return DefaultMaterialHolder.DEFAULT_LINE_MATERIAL;
465 return DefaultMaterialHolder.DEFAULT_SURFACE_MATERIAL;
467 return DefaultMaterialHolder.DEFAULT_BULK_MATERIAL;
469 throw new IllegalArgumentException("Unknown material type: " + type);
472 public static void setDefaultComponentMaterial(
473 Class<? extends RocketComponent> componentClass, Material material) {
475 set("componentMaterials", componentClass,
476 material == null ? null : material.toStorableString());
480 public static int getMaxThreadCount() {
481 return Runtime.getRuntime().availableProcessors();
486 public static Point getWindowPosition(Class<?> c) {
488 String pref = PREFNODE.node("windows").get("position." + c.getCanonicalName(), null);
493 if (pref.indexOf(',') < 0)
497 x = Integer.parseInt(pref.substring(0, pref.indexOf(',')));
498 y = Integer.parseInt(pref.substring(pref.indexOf(',') + 1));
499 } catch (NumberFormatException e) {
502 return new Point(x, y);
505 public static void setWindowPosition(Class<?> c, Point p) {
506 PREFNODE.node("windows").put("position." + c.getCanonicalName(), "" + p.x + "," + p.y);
513 public static Dimension getWindowSize(Class<?> c) {
515 String pref = PREFNODE.node("windows").get("size." + c.getCanonicalName(), null);
520 if (pref.indexOf(',') < 0)
524 x = Integer.parseInt(pref.substring(0, pref.indexOf(',')));
525 y = Integer.parseInt(pref.substring(pref.indexOf(',') + 1));
526 } catch (NumberFormatException e) {
529 return new Dimension(x, y);
532 public static void setWindowSize(Class<?> c, Dimension d) {
533 PREFNODE.node("windows").put("size." + c.getCanonicalName(), "" + d.width + "," + d.height);
538 //// Background flight data computation
540 public static boolean computeFlightInBackground() {
541 return PREFNODE.getBoolean("backgroundFlight", true);
544 public static Simulation getBackgroundSimulation(Rocket rocket) {
545 Simulation s = new Simulation(rocket);
546 GUISimulationConditions cond = s.getConditions();
548 cond.setTimeStep(RK4SimulationStepper.RECOMMENDED_TIME_STEP * 2);
549 cond.setWindSpeedAverage(1.0);
550 cond.setWindSpeedDeviation(0.1);
551 cond.setLaunchRodLength(5);
557 ///////// Export variables
559 public static boolean isExportSelected(FlightDataType type) {
560 Preferences prefs = PREFNODE.node("exports");
561 return prefs.getBoolean(type.getName(), false);
564 public static void setExportSelected(FlightDataType type, boolean selected) {
565 Preferences prefs = PREFNODE.node("exports");
566 prefs.putBoolean(type.getName(), selected);
571 ///////// Default unit storage
573 public static void loadDefaultUnits() {
574 Preferences prefs = PREFNODE.node("units");
577 for (String key : prefs.keys()) {
578 UnitGroup group = UnitGroup.UNITS.get(key);
583 group.setDefaultUnit(prefs.get(key, null));
584 } catch (IllegalArgumentException ignore) {
588 } catch (BackingStoreException e) {
589 ExceptionHandler.handleErrorCondition(e);
593 public static void storeDefaultUnits() {
594 Preferences prefs = PREFNODE.node("units");
596 for (String key : UnitGroup.UNITS.keySet()) {
597 UnitGroup group = UnitGroup.UNITS.get(key);
598 if (group == null || group.getUnitCount() < 2)
601 prefs.put(key, group.getDefaultUnit().getUnit());
607 //// Material storage
611 * Add a user-defined material to the preferences. The preferences are
612 * first checked for an existing material matching the provided one using
613 * {@link Material#equals(Object)}.
615 * @param m the material to add.
617 public static void addUserMaterial(Material m) {
618 Preferences prefs = PREFNODE.node("userMaterials");
621 // Check whether material already exists
622 if (getUserMaterials().contains(m)) {
626 // Add material using next free key (key is not used when loading)
627 String mat = m.toStorableString();
628 for (int i = 0;; i++) {
629 String key = "material" + i;
630 if (prefs.get(key, null) == null) {
639 * Remove a user-defined material from the preferences. The matching is performed
640 * using {@link Material#equals(Object)}.
642 * @param m the material to remove.
644 public static void removeUserMaterial(Material m) {
645 Preferences prefs = PREFNODE.node("userMaterials");
649 // Iterate through materials and remove all keys with a matching material
650 for (String key : prefs.keys()) {
651 String value = prefs.get(key, null);
654 Material existing = Material.fromStorableString(value, true);
655 if (existing.equals(m)) {
659 } catch (IllegalArgumentException ignore) {
664 } catch (BackingStoreException e) {
665 throw new IllegalStateException("Cannot read preferences!", e);
671 * Return a set of all user-defined materials in the preferences. The materials
672 * are created marked as user-defined.
674 * @return a set of all user-defined materials.
676 public static Set<Material> getUserMaterials() {
677 Preferences prefs = PREFNODE.node("userMaterials");
679 HashSet<Material> materials = new HashSet<Material>();
682 for (String key : prefs.keys()) {
683 String value = prefs.get(key, null);
686 Material m = Material.fromStorableString(value, true);
689 } catch (IllegalArgumentException e) {
690 log.warn("Illegal material string " + value);
695 } catch (BackingStoreException e) {
696 throw new IllegalStateException("Cannot read preferences!", e);
705 private static String get(String directory,
706 Class<? extends RocketComponent> componentClass,
707 Map<Class<?>, String> defaultMap) {
709 // Search preferences
710 Class<?> c = componentClass;
711 Preferences prefs = PREFNODE.node(directory);
712 while (c != null && RocketComponent.class.isAssignableFrom(c)) {
713 String value = prefs.get(c.getSimpleName(), null);
716 c = c.getSuperclass();
719 if (defaultMap == null)
724 while (RocketComponent.class.isAssignableFrom(c)) {
725 String value = defaultMap.get(c);
728 c = c.getSuperclass();
735 private static void set(String directory, Class<? extends RocketComponent> componentClass,
737 Preferences prefs = PREFNODE.node(directory);
739 prefs.remove(componentClass.getSimpleName());
741 prefs.put(componentClass.getSimpleName(), value);