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