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.material.Material;
23 import net.sf.openrocket.rocketcomponent.BodyComponent;
24 import net.sf.openrocket.rocketcomponent.FinSet;
25 import net.sf.openrocket.rocketcomponent.InternalComponent;
26 import net.sf.openrocket.rocketcomponent.LaunchLug;
27 import net.sf.openrocket.rocketcomponent.MassObject;
28 import net.sf.openrocket.rocketcomponent.RecoveryDevice;
29 import net.sf.openrocket.rocketcomponent.Rocket;
30 import net.sf.openrocket.rocketcomponent.RocketComponent;
31 import net.sf.openrocket.simulation.FlightDataBranch;
32 import net.sf.openrocket.simulation.RK4Simulator;
33 import net.sf.openrocket.simulation.SimulationConditions;
34 import net.sf.openrocket.unit.UnitGroup;
40 * Whether to use the debug-node instead of the normal node.
42 public static final boolean DEBUG = false;
45 * Whether to clear all preferences at application startup. This has an effect only
48 public static final boolean CLEARPREFS = true;
51 * The node name to use in the Java preferences storage.
53 public static final String NODENAME = (DEBUG?"OpenRocket-debug":"OpenRocket");
56 public static final String DEFAULT_BUILD_SOURCE = "default";
60 * Load property file only when necessary.
62 private static class BuildPropertyHolder {
64 public static final String BUILD_VERSION;
65 public static final String BUILD_SOURCE;
66 public static final boolean DEFAULT_CHECK_UPDATES;
70 InputStream is = ClassLoader.getSystemResourceAsStream("build.properties");
72 throw new MissingResourceException(
73 "build.properties not found, distribution built wrong" +
74 " classpath:"+System.getProperty("java.class.path"),
75 "build.properties", "build.version");
78 Properties props = new Properties();
82 String version = props.getProperty("build.version");
83 if (version == null) {
84 throw new MissingResourceException(
85 "build.version not found in property file",
86 "build.properties", "build.version");
88 BUILD_VERSION = version.trim();
90 BUILD_SOURCE = props.getProperty("build.source");
91 if (BUILD_SOURCE == null) {
92 throw new MissingResourceException(
93 "build.source not found in property file",
94 "build.properties", "build.source");
97 String value = props.getProperty("build.checkupdates");
99 DEFAULT_CHECK_UPDATES = Boolean.parseBoolean(value);
101 DEFAULT_CHECK_UPDATES = true;
103 } catch (IOException e) {
104 throw new MissingResourceException(
105 "Error reading build.properties",
106 "build.properties", "build.version");
111 public static final String BODY_COMPONENT_INSERT_POSITION_KEY = "BodyComponentInsertPosition";
114 public static final String CONFIRM_DELETE_SIMULATION = "ConfirmDeleteSimulation";
116 // Preferences related to data export
117 public static final String EXPORT_FIELD_SEPARATOR = "ExportFieldSeparator";
118 public static final String EXPORT_SIMULATION_COMMENT = "ExportSimulationComment";
119 public static final String EXPORT_FIELD_NAME_COMMENT = "ExportFieldDescriptionComment";
120 public static final String EXPORT_EVENT_COMMENTS = "ExportEventComments";
121 public static final String EXPORT_COMMENT_CHARACTER = "ExportCommentCharacter";
123 public static final String PLOT_SHOW_POINTS = "ShowPlotPoints";
125 private static final String CHECK_UPDATES = "CheckUpdates";
126 public static final String LAST_UPDATE = "LastUpdateVersion";
129 * Node to this application's preferences.
130 * @deprecated Use the static methods instead.
133 public static final Preferences NODE;
134 private static final Preferences PREFNODE;
138 Preferences root = Preferences.userRoot();
139 if (DEBUG && CLEARPREFS) {
141 if (root.nodeExists(NODENAME)) {
142 root.node(NODENAME).removeNode();
144 } catch (BackingStoreException e) {
145 throw new RuntimeException("Unable to clear preference node",e);
148 PREFNODE = root.node(NODENAME);
155 ///////// Default component attributes
157 private static final HashMap<Class<?>,String> DEFAULT_COLORS =
158 new HashMap<Class<?>,String>();
160 DEFAULT_COLORS.put(BodyComponent.class, "0,0,240");
161 DEFAULT_COLORS.put(FinSet.class, "0,0,200");
162 DEFAULT_COLORS.put(LaunchLug.class, "0,0,180");
163 DEFAULT_COLORS.put(InternalComponent.class, "170,0,100");
164 DEFAULT_COLORS.put(MassObject.class, "0,0,0");
165 DEFAULT_COLORS.put(RecoveryDevice.class, "255,0,0");
169 private static final HashMap<Class<?>,String> DEFAULT_LINE_STYLES =
170 new HashMap<Class<?>,String>();
172 DEFAULT_LINE_STYLES.put(RocketComponent.class, LineStyle.SOLID.name());
173 DEFAULT_LINE_STYLES.put(MassObject.class, LineStyle.DASHED.name());
178 * Within a holder class so they will load only when needed.
180 private static class DefaultMaterialHolder {
181 private static final Material DEFAULT_LINE_MATERIAL =
182 Databases.findMaterial(Material.Type.LINE, "Elastic cord (round 2mm, 1/16 in)",
184 private static final Material DEFAULT_SURFACE_MATERIAL =
185 Databases.findMaterial(Material.Type.SURFACE, "Ripstop nylon", 0.067, false);
186 private static final Material DEFAULT_BULK_MATERIAL =
187 Databases.findMaterial(Material.Type.BULK, "Cardboard", 680, false);
190 //////////////////////
193 public static String getVersion() {
194 return BuildPropertyHolder.BUILD_VERSION;
198 public static String getBuildSource() {
199 return BuildPropertyHolder.BUILD_SOURCE;
203 public static String getUniqueID() {
204 String id = PREFNODE.get("id", null);
206 id = UniqueID.uuid();
207 PREFNODE.put("id", id);
214 public static void storeVersion() {
215 PREFNODE.put("OpenRocketVersion", getVersion());
220 * Returns a limited-range integer value from the preferences. If the value
221 * in the preferences is negative or greater than max, then the default value
224 * @param key The preference to retrieve.
225 * @param max Maximum allowed value for the choice.
226 * @param def Default value.
227 * @return The preference value.
229 public static int getChoise(String key, int max, int def) {
230 int v = PREFNODE.getInt(key, def);
231 if ((v<0) || (v>max))
238 * Helper method that puts an integer choice value into the preferences.
240 * @param key the preference key.
241 * @param value the value to store.
243 public static void putChoise(String key, int value) {
244 PREFNODE.putInt(key, value);
250 public static String getString(String key, String def) {
251 return PREFNODE.get(key, def);
254 public static void putString(String key, String value) {
255 PREFNODE.put(key, value);
260 public static boolean getBoolean(String key, boolean def) {
261 return PREFNODE.getBoolean(key, def);
264 public static void putBoolean(String key, boolean value) {
265 PREFNODE.putBoolean(key, value);
271 public static boolean getCheckUpdates() {
272 return PREFNODE.getBoolean(CHECK_UPDATES, BuildPropertyHolder.DEFAULT_CHECK_UPDATES);
275 public static void setCheckUpdates(boolean check) {
276 PREFNODE.putBoolean(CHECK_UPDATES, check);
283 public static File getDefaultDirectory() {
284 String file = PREFNODE.get("defaultDirectory", null);
287 return new File(file);
290 public static void setDefaultDirectory(File dir) {
295 d = dir.getAbsolutePath();
297 PREFNODE.put("defaultDirectory", d);
303 public static Color getDefaultColor(Class<? extends RocketComponent> c) {
304 String color = get("componentColors", c, DEFAULT_COLORS);
308 String[] rgb = color.split(",");
311 int red = MathUtil.clamp(Integer.parseInt(rgb[0]),0,255);
312 int green = MathUtil.clamp(Integer.parseInt(rgb[1]),0,255);
313 int blue = MathUtil.clamp(Integer.parseInt(rgb[2]),0,255);
314 return new Color(red,green,blue);
315 } catch (NumberFormatException ignore) { }
321 public static void setDefaultColor(Class<? extends RocketComponent> c, Color color) {
324 String string = color.getRed() + "," + color.getGreen() + "," + color.getBlue();
325 set("componentColors", c, string);
328 public static Color getMotorBorderColor() {
329 // TODO: MEDIUM: Motor color (settable?)
330 return new Color(0,0,0,200);
334 public static Color getMotorFillColor() {
335 // TODO: MEDIUM: Motor fill color (settable?)
336 return new Color(0,0,0,100);
340 public static LineStyle getDefaultLineStyle(Class<? extends RocketComponent> c) {
341 String value = get("componentStyle", c, DEFAULT_LINE_STYLES);
343 return LineStyle.valueOf(value);
344 } catch (Exception e) {
345 return LineStyle.SOLID;
349 public static void setDefaultLineStyle(Class<? extends RocketComponent> c,
353 set("componentStyle", c, style.name());
358 * Return the DPI setting of the monitor. This is either the setting provided
359 * by the system or a user-specified DPI setting.
361 * @return the DPI setting to use.
363 public static double getDPI() {
364 int dpi = PREFNODE.getInt("DPI", 0); // Tenths of a dpi
367 dpi = Toolkit.getDefaultToolkit().getScreenResolution()*10;
372 return ((double)dpi)/10.0;
376 public static double getDefaultMach() {
377 // TODO: HIGH: implement custom default mach number
384 public static Material getDefaultComponentMaterial(
385 Class<? extends RocketComponent> componentClass,
386 Material.Type type) {
388 String material = get("componentMaterials", componentClass, null);
389 if (material != null) {
391 Material m = Material.fromStorableString(material, false);
392 if (m.getType() == type)
394 } catch (IllegalArgumentException ignore) { }
399 return DefaultMaterialHolder.DEFAULT_LINE_MATERIAL;
401 return DefaultMaterialHolder.DEFAULT_SURFACE_MATERIAL;
403 return DefaultMaterialHolder.DEFAULT_BULK_MATERIAL;
405 throw new IllegalArgumentException("Unknown material type: "+type);
408 public static void setDefaultComponentMaterial(
409 Class<? extends RocketComponent> componentClass, Material material) {
411 set("componentMaterials", componentClass,
412 material==null ? null : material.toStorableString());
416 public static int getMaxThreadCount() {
417 return Runtime.getRuntime().availableProcessors();
422 public static Point getWindowPosition(Class<?> c) {
424 String pref = PREFNODE.node("windows").get("position." + c.getCanonicalName(), null);
429 if (pref.indexOf(',')<0)
433 x = Integer.parseInt(pref.substring(0,pref.indexOf(',')));
434 y = Integer.parseInt(pref.substring(pref.indexOf(',')+1));
435 } catch (NumberFormatException e) {
438 return new Point(x,y);
441 public static void setWindowPosition(Class<?> c, Point p) {
442 PREFNODE.node("windows").put("position." + c.getCanonicalName(), "" + p.x + "," + p.y);
449 public static Dimension getWindowSize(Class<?> c) {
451 String pref = PREFNODE.node("windows").get("size." + c.getCanonicalName(), null);
456 if (pref.indexOf(',')<0)
460 x = Integer.parseInt(pref.substring(0,pref.indexOf(',')));
461 y = Integer.parseInt(pref.substring(pref.indexOf(',')+1));
462 } catch (NumberFormatException e) {
465 return new Dimension(x,y);
468 public static void setWindowSize(Class<?> c, Dimension d) {
469 PREFNODE.node("windows").put("size." + c.getCanonicalName(), "" + d.width + "," + d.height);
474 //// Background flight data computation
476 public static boolean computeFlightInBackground() {
477 return PREFNODE.getBoolean("backgroundFlight", true);
480 public static Simulation getBackgroundSimulation(Rocket rocket) {
481 Simulation s = new Simulation(rocket);
482 SimulationConditions cond = s.getConditions();
484 cond.setTimeStep(RK4Simulator.RECOMMENDED_TIME_STEP*2);
485 cond.setWindSpeedAverage(1.0);
486 cond.setWindSpeedDeviation(0.1);
487 cond.setLaunchRodLength(5);
493 ///////// Export variables
495 public static boolean isExportSelected(FlightDataBranch.Type type) {
496 Preferences prefs = PREFNODE.node("exports");
497 return prefs.getBoolean(type.getName(), false);
500 public static void setExportSelected(FlightDataBranch.Type type, boolean selected) {
501 Preferences prefs = PREFNODE.node("exports");
502 prefs.putBoolean(type.getName(), selected);
507 ///////// Default unit storage
509 public static void loadDefaultUnits() {
510 Preferences prefs = PREFNODE.node("units");
513 for (String key: prefs.keys()) {
514 UnitGroup group = UnitGroup.UNITS.get(key);
519 group.setDefaultUnit(prefs.get(key, null));
520 } catch (IllegalArgumentException ignore) { }
523 } catch (BackingStoreException e) {
524 ExceptionHandler.handleErrorCondition(e);
528 public static void storeDefaultUnits() {
529 Preferences prefs = PREFNODE.node("units");
531 for (String key: UnitGroup.UNITS.keySet()) {
532 UnitGroup group = UnitGroup.UNITS.get(key);
533 if (group == null || group.getUnitCount() < 2)
536 prefs.put(key, group.getDefaultUnit().getUnit());
542 //// Material storage
546 * Add a user-defined material to the preferences. The preferences are
547 * first checked for an existing material matching the provided one using
548 * {@link Material#equals(Object)}.
550 * @param m the material to add.
552 public static void addUserMaterial(Material m) {
553 Preferences prefs = PREFNODE.node("userMaterials");
556 // Check whether material already exists
557 if (getUserMaterials().contains(m)) {
561 // Add material using next free key (key is not used when loading)
562 String mat = m.toStorableString();
563 for (int i = 0; ; i++) {
564 String key = "material" + i;
565 if (prefs.get(key, null) == null) {
574 * Remove a user-defined material from the preferences. The matching is performed
575 * using {@link Material#equals(Object)}.
577 * @param m the material to remove.
579 public static void removeUserMaterial(Material m) {
580 Preferences prefs = PREFNODE.node("userMaterials");
584 // Iterate through materials and remove all keys with a matching material
585 for (String key: prefs.keys()) {
586 String value = prefs.get(key, null);
589 Material existing = Material.fromStorableString(value, true);
590 if (existing.equals(m)) {
594 } catch (IllegalArgumentException ignore) { }
598 } catch (BackingStoreException e) {
599 throw new IllegalStateException("Cannot read preferences!", e);
605 * Return a set of all user-defined materials in the preferences. The materials
606 * are created marked as user-defined.
608 * @return a set of all user-defined materials.
610 public static Set<Material> getUserMaterials() {
611 Preferences prefs = PREFNODE.node("userMaterials");
613 HashSet<Material> materials = new HashSet<Material>();
616 for (String key: prefs.keys()) {
617 String value = prefs.get(key, null);
620 Material m = Material.fromStorableString(value, true);
623 } catch (IllegalArgumentException e) {
624 System.err.println("Illegal material string " + value);
629 } catch (BackingStoreException e) {
630 throw new IllegalStateException("Cannot read preferences!", e);
639 private static String get(String directory,
640 Class<? extends RocketComponent> componentClass,
641 Map<Class<?>, String> defaultMap) {
643 // Search preferences
644 Class<?> c = componentClass;
645 Preferences prefs = PREFNODE.node(directory);
646 while (c!=null && RocketComponent.class.isAssignableFrom(c)) {
647 String value = prefs.get(c.getSimpleName(), null);
650 c = c.getSuperclass();
653 if (defaultMap == null)
658 while (RocketComponent.class.isAssignableFrom(c)) {
659 String value = defaultMap.get(c);
662 c = c.getSuperclass();
669 private static void set(String directory, Class<? extends RocketComponent> componentClass,
671 Preferences prefs = PREFNODE.node(directory);
673 prefs.remove(componentClass.getSimpleName());
675 prefs.put(componentClass.getSimpleName(), value);