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