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