Numerous bug fixes and updates
[debian/openrocket] / src / net / sf / openrocket / util / Prefs.java
1 package net.sf.openrocket.util;
2
3 import java.awt.Color;
4 import java.awt.Dimension;
5 import java.awt.Point;
6 import java.awt.Toolkit;
7 import java.io.File;
8 import java.io.IOException;
9 import java.io.InputStream;
10 import java.util.HashMap;
11 import java.util.Map;
12 import java.util.MissingResourceException;
13 import java.util.Properties;
14 import java.util.prefs.BackingStoreException;
15 import java.util.prefs.Preferences;
16
17 import net.sf.openrocket.database.Databases;
18 import net.sf.openrocket.document.Simulation;
19 import net.sf.openrocket.material.Material;
20 import net.sf.openrocket.rocketcomponent.BodyComponent;
21 import net.sf.openrocket.rocketcomponent.FinSet;
22 import net.sf.openrocket.rocketcomponent.InternalComponent;
23 import net.sf.openrocket.rocketcomponent.LaunchLug;
24 import net.sf.openrocket.rocketcomponent.MassObject;
25 import net.sf.openrocket.rocketcomponent.RecoveryDevice;
26 import net.sf.openrocket.rocketcomponent.Rocket;
27 import net.sf.openrocket.rocketcomponent.RocketComponent;
28 import net.sf.openrocket.simulation.FlightDataBranch;
29 import net.sf.openrocket.simulation.RK4Simulator;
30 import net.sf.openrocket.simulation.SimulationConditions;
31 import net.sf.openrocket.unit.UnitGroup;
32
33
34 public class Prefs {
35
36         /**
37          * Whether to use the debug-node instead of the normal node.
38          */
39         public static final boolean DEBUG = false;
40         
41         /**
42          * Whether to clear all preferences at application startup.  This has an effect only
43          * if DEBUG is true.
44          */
45         public static final boolean CLEARPREFS = true;
46         
47         /**
48          * The node name to use in the Java preferences storage.
49          */
50         public static final String NODENAME = (DEBUG?"OpenRocket-debug":"OpenRocket");
51         
52         
53         
54         private static final String BUILD_VERSION;
55         private static final String BUILD_SOURCE;
56         
57         static {
58                 try {
59                         InputStream is = ClassLoader.getSystemResourceAsStream("build.properties");
60                         if (is == null) {
61                                 throw new MissingResourceException(
62                                                 "build.properties not found, distribution built wrong",
63                                                 "build.properties", "build.version");
64                         }
65                         
66                         Properties props = new Properties();
67                         props.load(is);
68                         is.close();
69                         
70                         BUILD_VERSION = props.getProperty("build.version");
71                         if (BUILD_VERSION == null) {
72                                 throw new MissingResourceException(
73                                                 "build.version not found in property file",
74                                                 "build.properties", "build.version");
75                         }
76                         
77                         BUILD_SOURCE = props.getProperty("build.source");
78                         
79                 } catch (IOException e) {
80                         throw new MissingResourceException(
81                                         "Error reading build.properties",
82                                         "build.properties", "build.version");
83                 }
84         }
85         
86         
87         public static final String BODY_COMPONENT_INSERT_POSITION_KEY = "BodyComponentInsertPosition";
88         
89         
90         public static final String CONFIRM_DELETE_SIMULATION = "ConfirmDeleteSimulation";
91
92         // Preferences related to data export
93         public static final String EXPORT_FIELD_SEPARATOR = "ExportFieldSeparator";
94         public static final String EXPORT_SIMULATION_COMMENT = "ExportSimulationComment";
95         public static final String EXPORT_FIELD_NAME_COMMENT = "ExportFieldDescriptionComment";
96         public static final String EXPORT_EVENT_COMMENTS = "ExportEventComments";
97         public static final String EXPORT_COMMENT_CHARACTER = "ExportCommentCharacter";
98         
99         
100         /**
101          * Node to this application's preferences.
102          * @deprecated  Use the static methods instead.
103          */
104         @Deprecated
105         public static final Preferences NODE;
106         private static final Preferences PREFNODE;
107         
108         
109         static {
110                 Preferences root = Preferences.userRoot();
111                 if (DEBUG && CLEARPREFS) {
112                         try {
113                                 if (root.nodeExists(NODENAME)) {
114                                         root.node(NODENAME).removeNode();
115                                 }
116                         } catch (BackingStoreException e) {
117                                 throw new RuntimeException("Unable to clear preference node",e);
118                         }
119                 }
120                 PREFNODE = root.node(NODENAME);
121                 NODE = PREFNODE;
122         }
123         
124         
125         
126         
127         /////////  Default component attributes
128         
129         private static final HashMap<Class<?>,String> DEFAULT_COLORS = 
130                 new HashMap<Class<?>,String>();
131         static {
132                 DEFAULT_COLORS.put(BodyComponent.class, "0,0,240");
133                 DEFAULT_COLORS.put(FinSet.class, "0,0,200");
134                 DEFAULT_COLORS.put(LaunchLug.class, "0,0,180");
135                 DEFAULT_COLORS.put(InternalComponent.class, "170,0,100");
136                 DEFAULT_COLORS.put(MassObject.class, "0,0,0");
137                 DEFAULT_COLORS.put(RecoveryDevice.class, "255,0,0");
138         }
139         
140         
141         private static final HashMap<Class<?>,String> DEFAULT_LINE_STYLES = 
142                 new HashMap<Class<?>,String>();
143         static {
144                 DEFAULT_LINE_STYLES.put(RocketComponent.class, LineStyle.SOLID.name());
145                 DEFAULT_LINE_STYLES.put(MassObject.class, LineStyle.DASHED.name());
146         }
147         
148         
149         private static final Material DEFAULT_LINE_MATERIAL = 
150                 Databases.findMaterial(Material.Type.LINE, "Elastic cord (round 2mm, 1/16 in)", 0.0018);
151         private static final Material DEFAULT_SURFACE_MATERIAL = 
152                 Databases.findMaterial(Material.Type.SURFACE, "Ripstop nylon", 0.067);
153         private static final Material DEFAULT_BULK_MATERIAL = 
154                 Databases.findMaterial(Material.Type.BULK, "Cardboard", 680);
155
156         
157         //////////////////////
158         
159         
160         public static String getVersion() {
161                 return BUILD_VERSION;
162         }
163         
164         
165         public static String getBuildSource() {
166                 return BUILD_SOURCE;
167         }
168         
169         
170         
171         public static void storeVersion() {
172                 PREFNODE.put("OpenRocketVersion", getVersion());
173         }
174         
175         
176         /**
177          * Returns a limited-range integer value from the preferences.  If the value 
178          * in the preferences is negative or greater than max, then the default value 
179          * is returned.
180          * 
181          * @param key  The preference to retrieve.
182          * @param max  Maximum allowed value for the choice.
183          * @param def  Default value.
184          * @return   The preference value.
185          */
186         public static int getChoise(String key, int max, int def) {
187                 int v = PREFNODE.getInt(key, def);
188                 if ((v<0) || (v>max))
189                         return def;
190                 return v;
191         }
192         
193         
194         /**
195          * Helper method that puts an integer choice value into the preferences.
196          * 
197          * @param key     the preference key.
198          * @param value   the value to store.
199          */
200         public static void putChoise(String key, int value) {
201                 PREFNODE.putInt(key, value);
202                 storeVersion();
203         }
204         
205         
206         
207         public static String getString(String key, String def) {
208                 return PREFNODE.get(key, def);
209         }
210         
211         public static void putString(String key, String value) {
212                 PREFNODE.put(key, value);
213                 storeVersion();
214         }
215         
216         
217         public static boolean getBoolean(String key, boolean def) {
218                 return PREFNODE.getBoolean(key, def);
219         }
220         
221         public static void putBoolean(String key, boolean value) {
222                 PREFNODE.putBoolean(key, value);
223                 storeVersion();
224         }
225
226         
227         
228         //////////////////
229         
230         public static File getDefaultDirectory() {
231                 String file = PREFNODE.get("defaultDirectory", null);
232                 if (file == null)
233                         return null;
234                 return new File(file);
235         }
236         
237         public static void setDefaultDirectory(File dir) {
238                 String d;
239                 if (dir == null) {
240                         d = null;
241                 } else {
242                         d = dir.getAbsolutePath();
243                 }
244                 PREFNODE.put("defaultDirectory", d);
245                 storeVersion();
246         }
247         
248         
249         
250         public static Color getDefaultColor(Class<? extends RocketComponent> c) {
251                 String color = get("componentColors", c, DEFAULT_COLORS);
252                 if (color == null)
253                         return Color.BLACK;
254
255                 String[] rgb = color.split(",");
256                 if (rgb.length==3) {
257                         try {
258                                 int red = MathUtil.clamp(Integer.parseInt(rgb[0]),0,255);
259                                 int green = MathUtil.clamp(Integer.parseInt(rgb[1]),0,255);
260                                 int blue = MathUtil.clamp(Integer.parseInt(rgb[2]),0,255);
261                                 return new Color(red,green,blue);
262                         } catch (NumberFormatException ignore) { }
263                 }
264
265                 return Color.BLACK;
266         }
267         
268         public static void setDefaultColor(Class<? extends RocketComponent> c, Color color) {
269                 if (color==null)
270                         return;
271                 String string = color.getRed() + "," + color.getGreen() + "," + color.getBlue();
272                 set("componentColors", c, string);
273         }
274         
275         public static Color getMotorBorderColor() {
276                 // TODO: MEDIUM:  Motor color (settable?)
277                 return new Color(0,0,0,200);
278         }
279
280         
281         public static Color getMotorFillColor() {
282                 // TODO: MEDIUM:  Motor fill color (settable?)
283                 return new Color(0,0,0,100);
284         }
285         
286         
287         public static LineStyle getDefaultLineStyle(Class<? extends RocketComponent> c) {
288                 String value = get("componentStyle", c, DEFAULT_LINE_STYLES);
289                 try {
290                         return LineStyle.valueOf(value);
291                 } catch (Exception e) {
292                         return LineStyle.SOLID;
293                 }
294         }
295         
296         public static void setDefaultLineStyle(Class<? extends RocketComponent> c,
297                         LineStyle style) {
298                 if (style == null)
299                         return;
300                 set("componentStyle", c, style.name());
301         }
302         
303
304         /**
305          * Return the DPI setting of the monitor.  This is either the setting provided
306          * by the system or a user-specified DPI setting.
307          * 
308          * @return    the DPI setting to use.
309          */
310         public static double getDPI() {
311                 int dpi = PREFNODE.getInt("DPI", 0);  // Tenths of a dpi
312                 
313                 if (dpi < 10) {
314                         dpi = Toolkit.getDefaultToolkit().getScreenResolution()*10;
315                 }
316                 if (dpi < 10)
317                         dpi = 960;
318                 
319                 return ((double)dpi)/10.0;
320         }
321         
322         
323         public static double getDefaultMach() {
324                 // TODO: HIGH: implement custom default mach number
325                 return 0.3;
326         }
327         
328         
329         
330         
331         public static Material getDefaultComponentMaterial(
332                         Class<? extends RocketComponent> componentClass,
333                         Material.Type type) {
334                 
335                 String material = get("componentMaterials", componentClass, null);
336                 if (material != null) {
337                         try {
338                                 Material m = Material.fromStorableString(material);
339                                 if (m.getType() == type)
340                                         return m;
341                         } catch (IllegalArgumentException ignore) { }
342                 }
343                 
344                 switch (type) {
345                 case LINE:
346                         return DEFAULT_LINE_MATERIAL;
347                 case SURFACE:
348                         return DEFAULT_SURFACE_MATERIAL;
349                 case BULK:
350                         return DEFAULT_BULK_MATERIAL;
351                 }
352                 throw new IllegalArgumentException("Unknown material type: "+type);
353         }
354         
355         public static void setDefaultComponentMaterial(
356                         Class<? extends RocketComponent> componentClass, Material material) {
357                 
358                 set("componentMaterials", componentClass, 
359                                 material==null ? null : material.toStorableString());
360         }
361         
362         
363         public static int getMaxThreadCount() {
364                 return Runtime.getRuntime().availableProcessors();
365         }
366         
367         
368         
369         public static Point getWindowPosition(Class<?> c) {
370                 int x, y;
371                 String pref = PREFNODE.node("windows").get("position." + c.getCanonicalName(), null);
372                 
373                 if (pref == null)
374                         return null;
375                 
376                 if (pref.indexOf(',')<0)
377                         return null;
378                 
379                 try {
380                         x = Integer.parseInt(pref.substring(0,pref.indexOf(',')));
381                         y = Integer.parseInt(pref.substring(pref.indexOf(',')+1));
382                 } catch (NumberFormatException e) {
383                         return null;
384                 }
385                 return new Point(x,y);
386         }
387         
388         public static void setWindowPosition(Class<?> c, Point p) {
389                 PREFNODE.node("windows").put("position." + c.getCanonicalName(), "" + p.x + "," + p.y);
390                 storeVersion();
391         }
392         
393         
394         
395
396         public static Dimension getWindowSize(Class<?> c) {
397                 int x, y;
398                 String pref = PREFNODE.node("windows").get("size." + c.getCanonicalName(), null);
399                 
400                 if (pref == null)
401                         return null;
402                 
403                 if (pref.indexOf(',')<0)
404                         return null;
405                 
406                 try {
407                         x = Integer.parseInt(pref.substring(0,pref.indexOf(',')));
408                         y = Integer.parseInt(pref.substring(pref.indexOf(',')+1));
409                 } catch (NumberFormatException e) {
410                         return null;
411                 }
412                 return new Dimension(x,y);
413         }
414         
415         public static void setWindowSize(Class<?> c, Dimension d) {
416                 PREFNODE.node("windows").put("size." + c.getCanonicalName(), "" + d.width + "," + d.height);
417                 storeVersion();
418         }
419         
420         
421         ////  Background flight data computation
422         
423         public static boolean computeFlightInBackground() {
424                 return PREFNODE.getBoolean("backgroundFlight", true);
425         }
426         
427         public static Simulation getBackgroundSimulation(Rocket rocket) {
428                 Simulation s = new Simulation(rocket);
429                 SimulationConditions cond = s.getConditions();
430                 
431                 cond.setTimeStep(RK4Simulator.RECOMMENDED_TIME_STEP*2);
432                 cond.setWindSpeedAverage(1.0);
433                 cond.setWindSpeedDeviation(0.1);
434                 cond.setLaunchRodLength(5);
435                 return s;
436         }
437         
438         
439         
440         /////////  Export variables
441         
442         public static boolean isExportSelected(FlightDataBranch.Type type) {
443                 Preferences prefs = PREFNODE.node("exports");
444                 return prefs.getBoolean(type.getName(), false);
445         }
446         
447         public static void setExportSelected(FlightDataBranch.Type type, boolean selected) {
448                 Preferences prefs = PREFNODE.node("exports");
449                 prefs.putBoolean(type.getName(), selected);
450         }
451         
452         
453         
454         /////////  Default unit storage
455         
456         public static void loadDefaultUnits() {
457                 Preferences prefs = PREFNODE.node("units");
458                 try {
459                         
460                         for (String key: prefs.keys()) {
461                                 UnitGroup group = UnitGroup.UNITS.get(key);
462                                 if (group == null)
463                                         continue;
464                                 
465                                 group.setDefaultUnit(prefs.get(key, null));
466                         }
467                         
468                 } catch (BackingStoreException e) {
469                         System.err.println("BackingStoreException:");
470                         e.printStackTrace();
471                 }
472         }
473         
474         public static void storeDefaultUnits() {
475                 Preferences prefs = PREFNODE.node("units");
476                 
477                 for (String key: UnitGroup.UNITS.keySet()) {
478                         UnitGroup group = UnitGroup.UNITS.get(key);
479                         if (group == null || group.getUnitCount() < 2)
480                                 continue;
481                         
482                         prefs.put(key, group.getDefaultUnit().getUnit());
483                 }
484         }
485         
486         
487         
488         ////  Helper methods
489         
490         private static String get(String directory, 
491                         Class<? extends RocketComponent> componentClass,
492                         Map<Class<?>, String> defaultMap) {
493
494                 // Search preferences
495                 Class<?> c = componentClass;
496                 Preferences prefs = PREFNODE.node(directory);
497                 while (c!=null && RocketComponent.class.isAssignableFrom(c)) {
498                         String value = prefs.get(c.getSimpleName(), null);
499                         if (value != null)
500                                 return value;
501                         c = c.getSuperclass();
502                 }
503                 
504                 if (defaultMap == null)
505                         return null;
506
507                 // Search defaults
508                 c = componentClass;
509                 while (RocketComponent.class.isAssignableFrom(c)) {
510                         String value = defaultMap.get(c);
511                         if (value != null)
512                                 return value;
513                         c = c.getSuperclass();
514                 }
515                 
516                 return null;
517         }
518
519
520         private static void set(String directory, Class<? extends RocketComponent> componentClass,
521                         String value) {
522                 Preferences prefs = PREFNODE.node(directory);
523                 if (value == null)
524                         prefs.remove(componentClass.getSimpleName());
525                 else
526                         prefs.put(componentClass.getSimpleName(), value);
527                 storeVersion();
528         }
529
530 }