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.material.Material;
22 import net.sf.openrocket.rocketcomponent.BodyComponent;
23 import net.sf.openrocket.rocketcomponent.FinSet;
24 import net.sf.openrocket.rocketcomponent.InternalComponent;
25 import net.sf.openrocket.rocketcomponent.LaunchLug;
26 import net.sf.openrocket.rocketcomponent.MassObject;
27 import net.sf.openrocket.rocketcomponent.RecoveryDevice;
28 import net.sf.openrocket.rocketcomponent.Rocket;
29 import net.sf.openrocket.rocketcomponent.RocketComponent;
30 import net.sf.openrocket.simulation.FlightDataBranch;
31 import net.sf.openrocket.simulation.RK4Simulator;
32 import net.sf.openrocket.simulation.SimulationConditions;
33 import net.sf.openrocket.unit.UnitGroup;
39 * Whether to use the debug-node instead of the normal node.
41 public static final boolean DEBUG = false;
44 * Whether to clear all preferences at application startup. This has an effect only
47 public static final boolean CLEARPREFS = true;
50 * The node name to use in the Java preferences storage.
52 public static final String NODENAME = (DEBUG?"OpenRocket-debug":"OpenRocket");
56 private static final String BUILD_VERSION;
57 private static final String BUILD_SOURCE;
58 public static final String DEFAULT_BUILD_SOURCE = "default";
62 InputStream is = ClassLoader.getSystemResourceAsStream("build.properties");
64 throw new MissingResourceException(
65 "build.properties not found, distribution built wrong",
66 "build.properties", "build.version");
69 Properties props = new Properties();
73 BUILD_VERSION = props.getProperty("build.version");
74 if (BUILD_VERSION == null) {
75 throw new MissingResourceException(
76 "build.version not found in property file",
77 "build.properties", "build.version");
80 BUILD_SOURCE = props.getProperty("build.source");
82 } catch (IOException e) {
83 throw new MissingResourceException(
84 "Error reading build.properties",
85 "build.properties", "build.version");
90 public static final String BODY_COMPONENT_INSERT_POSITION_KEY = "BodyComponentInsertPosition";
93 public static final String CONFIRM_DELETE_SIMULATION = "ConfirmDeleteSimulation";
95 // Preferences related to data export
96 public static final String EXPORT_FIELD_SEPARATOR = "ExportFieldSeparator";
97 public static final String EXPORT_SIMULATION_COMMENT = "ExportSimulationComment";
98 public static final String EXPORT_FIELD_NAME_COMMENT = "ExportFieldDescriptionComment";
99 public static final String EXPORT_EVENT_COMMENTS = "ExportEventComments";
100 public static final String EXPORT_COMMENT_CHARACTER = "ExportCommentCharacter";
102 public static final String PLOT_SHOW_POINTS = "ShowPlotPoints";
105 * Node to this application's preferences.
106 * @deprecated Use the static methods instead.
109 public static final Preferences NODE;
110 private static final Preferences PREFNODE;
114 Preferences root = Preferences.userRoot();
115 if (DEBUG && CLEARPREFS) {
117 if (root.nodeExists(NODENAME)) {
118 root.node(NODENAME).removeNode();
120 } catch (BackingStoreException e) {
121 throw new RuntimeException("Unable to clear preference node",e);
124 PREFNODE = root.node(NODENAME);
131 ///////// Default component attributes
133 private static final HashMap<Class<?>,String> DEFAULT_COLORS =
134 new HashMap<Class<?>,String>();
136 DEFAULT_COLORS.put(BodyComponent.class, "0,0,240");
137 DEFAULT_COLORS.put(FinSet.class, "0,0,200");
138 DEFAULT_COLORS.put(LaunchLug.class, "0,0,180");
139 DEFAULT_COLORS.put(InternalComponent.class, "170,0,100");
140 DEFAULT_COLORS.put(MassObject.class, "0,0,0");
141 DEFAULT_COLORS.put(RecoveryDevice.class, "255,0,0");
145 private static final HashMap<Class<?>,String> DEFAULT_LINE_STYLES =
146 new HashMap<Class<?>,String>();
148 DEFAULT_LINE_STYLES.put(RocketComponent.class, LineStyle.SOLID.name());
149 DEFAULT_LINE_STYLES.put(MassObject.class, LineStyle.DASHED.name());
153 private static final Material DEFAULT_LINE_MATERIAL =
154 Databases.findMaterial(Material.Type.LINE, "Elastic cord (round 2mm, 1/16 in)",
156 private static final Material DEFAULT_SURFACE_MATERIAL =
157 Databases.findMaterial(Material.Type.SURFACE, "Ripstop nylon", 0.067, false);
158 private static final Material DEFAULT_BULK_MATERIAL =
159 Databases.findMaterial(Material.Type.BULK, "Cardboard", 680, false);
162 //////////////////////
165 public static String getVersion() {
166 return BUILD_VERSION;
170 public static String getBuildSource() {
176 public static void storeVersion() {
177 PREFNODE.put("OpenRocketVersion", getVersion());
182 * Returns a limited-range integer value from the preferences. If the value
183 * in the preferences is negative or greater than max, then the default value
186 * @param key The preference to retrieve.
187 * @param max Maximum allowed value for the choice.
188 * @param def Default value.
189 * @return The preference value.
191 public static int getChoise(String key, int max, int def) {
192 int v = PREFNODE.getInt(key, def);
193 if ((v<0) || (v>max))
200 * Helper method that puts an integer choice value into the preferences.
202 * @param key the preference key.
203 * @param value the value to store.
205 public static void putChoise(String key, int value) {
206 PREFNODE.putInt(key, value);
212 public static String getString(String key, String def) {
213 return PREFNODE.get(key, def);
216 public static void putString(String key, String value) {
217 PREFNODE.put(key, value);
222 public static boolean getBoolean(String key, boolean def) {
223 return PREFNODE.getBoolean(key, def);
226 public static void putBoolean(String key, boolean value) {
227 PREFNODE.putBoolean(key, value);
235 public static File getDefaultDirectory() {
236 String file = PREFNODE.get("defaultDirectory", null);
239 return new File(file);
242 public static void setDefaultDirectory(File dir) {
247 d = dir.getAbsolutePath();
249 PREFNODE.put("defaultDirectory", d);
255 public static Color getDefaultColor(Class<? extends RocketComponent> c) {
256 String color = get("componentColors", c, DEFAULT_COLORS);
260 String[] rgb = color.split(",");
263 int red = MathUtil.clamp(Integer.parseInt(rgb[0]),0,255);
264 int green = MathUtil.clamp(Integer.parseInt(rgb[1]),0,255);
265 int blue = MathUtil.clamp(Integer.parseInt(rgb[2]),0,255);
266 return new Color(red,green,blue);
267 } catch (NumberFormatException ignore) { }
273 public static void setDefaultColor(Class<? extends RocketComponent> c, Color color) {
276 String string = color.getRed() + "," + color.getGreen() + "," + color.getBlue();
277 set("componentColors", c, string);
280 public static Color getMotorBorderColor() {
281 // TODO: MEDIUM: Motor color (settable?)
282 return new Color(0,0,0,200);
286 public static Color getMotorFillColor() {
287 // TODO: MEDIUM: Motor fill color (settable?)
288 return new Color(0,0,0,100);
292 public static LineStyle getDefaultLineStyle(Class<? extends RocketComponent> c) {
293 String value = get("componentStyle", c, DEFAULT_LINE_STYLES);
295 return LineStyle.valueOf(value);
296 } catch (Exception e) {
297 return LineStyle.SOLID;
301 public static void setDefaultLineStyle(Class<? extends RocketComponent> c,
305 set("componentStyle", c, style.name());
310 * Return the DPI setting of the monitor. This is either the setting provided
311 * by the system or a user-specified DPI setting.
313 * @return the DPI setting to use.
315 public static double getDPI() {
316 int dpi = PREFNODE.getInt("DPI", 0); // Tenths of a dpi
319 dpi = Toolkit.getDefaultToolkit().getScreenResolution()*10;
324 return ((double)dpi)/10.0;
328 public static double getDefaultMach() {
329 // TODO: HIGH: implement custom default mach number
336 public static Material getDefaultComponentMaterial(
337 Class<? extends RocketComponent> componentClass,
338 Material.Type type) {
340 String material = get("componentMaterials", componentClass, null);
341 if (material != null) {
343 Material m = Material.fromStorableString(material, false);
344 if (m.getType() == type)
346 } catch (IllegalArgumentException ignore) { }
351 return DEFAULT_LINE_MATERIAL;
353 return DEFAULT_SURFACE_MATERIAL;
355 return DEFAULT_BULK_MATERIAL;
357 throw new IllegalArgumentException("Unknown material type: "+type);
360 public static void setDefaultComponentMaterial(
361 Class<? extends RocketComponent> componentClass, Material material) {
363 set("componentMaterials", componentClass,
364 material==null ? null : material.toStorableString());
368 public static int getMaxThreadCount() {
369 return Runtime.getRuntime().availableProcessors();
374 public static Point getWindowPosition(Class<?> c) {
376 String pref = PREFNODE.node("windows").get("position." + c.getCanonicalName(), null);
381 if (pref.indexOf(',')<0)
385 x = Integer.parseInt(pref.substring(0,pref.indexOf(',')));
386 y = Integer.parseInt(pref.substring(pref.indexOf(',')+1));
387 } catch (NumberFormatException e) {
390 return new Point(x,y);
393 public static void setWindowPosition(Class<?> c, Point p) {
394 PREFNODE.node("windows").put("position." + c.getCanonicalName(), "" + p.x + "," + p.y);
401 public static Dimension getWindowSize(Class<?> c) {
403 String pref = PREFNODE.node("windows").get("size." + c.getCanonicalName(), null);
408 if (pref.indexOf(',')<0)
412 x = Integer.parseInt(pref.substring(0,pref.indexOf(',')));
413 y = Integer.parseInt(pref.substring(pref.indexOf(',')+1));
414 } catch (NumberFormatException e) {
417 return new Dimension(x,y);
420 public static void setWindowSize(Class<?> c, Dimension d) {
421 PREFNODE.node("windows").put("size." + c.getCanonicalName(), "" + d.width + "," + d.height);
426 //// Background flight data computation
428 public static boolean computeFlightInBackground() {
429 return PREFNODE.getBoolean("backgroundFlight", true);
432 public static Simulation getBackgroundSimulation(Rocket rocket) {
433 Simulation s = new Simulation(rocket);
434 SimulationConditions cond = s.getConditions();
436 cond.setTimeStep(RK4Simulator.RECOMMENDED_TIME_STEP*2);
437 cond.setWindSpeedAverage(1.0);
438 cond.setWindSpeedDeviation(0.1);
439 cond.setLaunchRodLength(5);
445 ///////// Export variables
447 public static boolean isExportSelected(FlightDataBranch.Type type) {
448 Preferences prefs = PREFNODE.node("exports");
449 return prefs.getBoolean(type.getName(), false);
452 public static void setExportSelected(FlightDataBranch.Type type, boolean selected) {
453 Preferences prefs = PREFNODE.node("exports");
454 prefs.putBoolean(type.getName(), selected);
459 ///////// Default unit storage
461 public static void loadDefaultUnits() {
462 Preferences prefs = PREFNODE.node("units");
465 for (String key: prefs.keys()) {
466 UnitGroup group = UnitGroup.UNITS.get(key);
470 group.setDefaultUnit(prefs.get(key, null));
473 } catch (BackingStoreException e) {
474 System.err.println("BackingStoreException:");
479 public static void storeDefaultUnits() {
480 Preferences prefs = PREFNODE.node("units");
482 for (String key: UnitGroup.UNITS.keySet()) {
483 UnitGroup group = UnitGroup.UNITS.get(key);
484 if (group == null || group.getUnitCount() < 2)
487 prefs.put(key, group.getDefaultUnit().getUnit());
493 //// Material storage
497 * Add a user-defined material to the preferences. The preferences are
498 * first checked for an existing material matching the provided one using
499 * {@link Material#equals(Object)}.
501 * @param m the material to add.
503 public static void addUserMaterial(Material m) {
504 Preferences prefs = PREFNODE.node("userMaterials");
507 // Check whether material already exists
508 if (getUserMaterials().contains(m)) {
512 // Add material using next free key (key is not used when loading)
513 String mat = m.toStorableString();
514 for (int i = 0; ; i++) {
515 String key = "material" + i;
516 if (prefs.get(key, null) == null) {
525 * Remove a user-defined material from the preferences. The matching is performed
526 * using {@link Material#equals(Object)}.
528 * @param m the material to remove.
530 public static void removeUserMaterial(Material m) {
531 Preferences prefs = PREFNODE.node("userMaterials");
535 // Iterate through materials and remove all keys with a matching material
536 for (String key: prefs.keys()) {
537 String value = prefs.get(key, null);
540 Material existing = Material.fromStorableString(value, true);
541 if (existing.equals(m)) {
545 } catch (IllegalArgumentException ignore) { }
549 } catch (BackingStoreException e) {
550 throw new IllegalStateException("Cannot read preferences!", e);
556 * Return a set of all user-defined materials in the preferences. The materials
557 * are created marked as user-defined.
559 * @return a set of all user-defined materials.
561 public static Set<Material> getUserMaterials() {
562 Preferences prefs = PREFNODE.node("userMaterials");
564 HashSet<Material> materials = new HashSet<Material>();
567 for (String key: prefs.keys()) {
568 String value = prefs.get(key, null);
571 Material m = Material.fromStorableString(value, true);
574 } catch (IllegalArgumentException e) {
575 System.err.println("Illegal material string " + value);
580 } catch (BackingStoreException e) {
581 throw new IllegalStateException("Cannot read preferences!", e);
590 private static String get(String directory,
591 Class<? extends RocketComponent> componentClass,
592 Map<Class<?>, String> defaultMap) {
594 // Search preferences
595 Class<?> c = componentClass;
596 Preferences prefs = PREFNODE.node(directory);
597 while (c!=null && RocketComponent.class.isAssignableFrom(c)) {
598 String value = prefs.get(c.getSimpleName(), null);
601 c = c.getSuperclass();
604 if (defaultMap == null)
609 while (RocketComponent.class.isAssignableFrom(c)) {
610 String value = defaultMap.get(c);
613 c = c.getSuperclass();
620 private static void set(String directory, Class<? extends RocketComponent> componentClass,
622 Preferences prefs = PREFNODE.node(directory);
624 prefs.remove(componentClass.getSimpleName());
626 prefs.put(componentClass.getSimpleName(), value);