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