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