Removed usage of deprecated Prefs.NODE public member variable. This is to prepare...
[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.io.File;
7 import java.io.IOException;
8 import java.io.InputStream;
9 import java.util.ArrayList;
10 import java.util.Collections;
11 import java.util.HashMap;
12 import java.util.HashSet;
13 import java.util.List;
14 import java.util.Locale;
15 import java.util.Map;
16 import java.util.MissingResourceException;
17 import java.util.Properties;
18 import java.util.Set;
19 import java.util.prefs.BackingStoreException;
20 import java.util.prefs.Preferences;
21
22 import net.sf.openrocket.arch.SystemInfo;
23 import net.sf.openrocket.database.Databases;
24 import net.sf.openrocket.document.Simulation;
25 import net.sf.openrocket.gui.main.ExceptionHandler;
26 import net.sf.openrocket.gui.print.PrintSettings;
27 import net.sf.openrocket.l10n.L10N;
28 import net.sf.openrocket.l10n.Translator;
29 import net.sf.openrocket.logging.LogHelper;
30 import net.sf.openrocket.material.Material;
31 import net.sf.openrocket.rocketcomponent.BodyComponent;
32 import net.sf.openrocket.rocketcomponent.FinSet;
33 import net.sf.openrocket.rocketcomponent.InternalComponent;
34 import net.sf.openrocket.rocketcomponent.LaunchLug;
35 import net.sf.openrocket.rocketcomponent.MassObject;
36 import net.sf.openrocket.rocketcomponent.RecoveryDevice;
37 import net.sf.openrocket.rocketcomponent.Rocket;
38 import net.sf.openrocket.rocketcomponent.RocketComponent;
39 import net.sf.openrocket.simulation.FlightDataType;
40 import net.sf.openrocket.simulation.RK4SimulationStepper;
41 import net.sf.openrocket.simulation.SimulationOptions;
42 import net.sf.openrocket.startup.Application;
43 import net.sf.openrocket.unit.UnitGroup;
44
45
46 public class Prefs {
47         private static final LogHelper log = Application.getLogger();
48         
49         private static final String SPLIT_CHARACTER = "|";
50         
51
52         private static final List<Locale> SUPPORTED_LOCALES;
53         static {
54                 List<Locale> list = new ArrayList<Locale>();
55                 for (String lang : new String[] { "en", "de", "es", "fr" }) {
56                         list.add(new Locale(lang));
57                 }
58                 SUPPORTED_LOCALES = Collections.unmodifiableList(list);
59         }
60         
61
62         /**
63          * Whether to use the debug-node instead of the normal node.
64          */
65         private static final boolean DEBUG;
66         static {
67                 DEBUG = (System.getProperty("openrocket.debug.prefs") != null);
68         }
69         
70         /**
71          * Whether to clear all preferences at application startup.  This has an effect only
72          * if DEBUG is true.
73          */
74         private static final boolean CLEARPREFS = true;
75         
76         /**
77          * The node name to use in the Java preferences storage.
78          */
79         private static final String NODENAME = (DEBUG ? "OpenRocket-debug" : "OpenRocket");
80         
81         
82         /*
83          * Load property file only when necessary.
84          */
85         private static class BuildPropertyHolder {
86                 
87                 public static final Properties PROPERTIES;
88                 public static final String BUILD_VERSION;
89                 public static final String BUILD_SOURCE;
90                 public static final boolean DEFAULT_CHECK_UPDATES;
91                 
92                 static {
93                         try {
94                                 InputStream is = ClassLoader.getSystemResourceAsStream("build.properties");
95                                 if (is == null) {
96                                         throw new MissingResourceException(
97                                                         "build.properties not found, distribution built wrong" +
98                                                                         "   classpath:" + System.getProperty("java.class.path"),
99                                                         "build.properties", "build.version");
100                                 }
101                                 
102                                 PROPERTIES = new Properties();
103                                 PROPERTIES.load(is);
104                                 is.close();
105                                 
106                                 String version = PROPERTIES.getProperty("build.version");
107                                 if (version == null) {
108                                         throw new MissingResourceException(
109                                                         "build.version not found in property file",
110                                                         "build.properties", "build.version");
111                                 }
112                                 BUILD_VERSION = version.trim();
113                                 
114                                 BUILD_SOURCE = PROPERTIES.getProperty("build.source");
115                                 if (BUILD_SOURCE == null) {
116                                         throw new MissingResourceException(
117                                                         "build.source not found in property file",
118                                                         "build.properties", "build.source");
119                                 }
120                                 
121                                 String value = PROPERTIES.getProperty("build.checkupdates");
122                                 if (value != null)
123                                         DEFAULT_CHECK_UPDATES = Boolean.parseBoolean(value);
124                                 else
125                                         DEFAULT_CHECK_UPDATES = true;
126                                 
127                         } catch (IOException e) {
128                                 throw new MissingResourceException(
129                                                 "Error reading build.properties",
130                                                 "build.properties", "build.version");
131                         }
132                 }
133         }
134         
135         public static final String BODY_COMPONENT_INSERT_POSITION_KEY = "BodyComponentInsertPosition";
136         
137         public static final String USER_THRUST_CURVES_KEY = "UserThrustCurves";
138         
139         public static final String CONFIRM_DELETE_SIMULATION = "ConfirmDeleteSimulation";
140         
141         // Preferences related to data export
142         public static final String EXPORT_FIELD_SEPARATOR = "ExportFieldSeparator";
143         public static final String EXPORT_SIMULATION_COMMENT = "ExportSimulationComment";
144         public static final String EXPORT_FIELD_NAME_COMMENT = "ExportFieldDescriptionComment";
145         public static final String EXPORT_EVENT_COMMENTS = "ExportEventComments";
146         public static final String EXPORT_COMMENT_CHARACTER = "ExportCommentCharacter";
147         
148         public static final String PLOT_SHOW_POINTS = "ShowPlotPoints";
149         
150         private static final String CHECK_UPDATES = "CheckUpdates";
151         public static final String LAST_UPDATE = "LastUpdateVersion";
152         
153         public static final String MOTOR_DIAMETER_FILTER = "MotorDiameterMatch";
154         public static final String MOTOR_HIDE_SIMILAR = "MotorHideSimilar";
155         
156
157         // Node names
158         public static final String PREFERRED_THRUST_CURVE_MOTOR_NODE = "preferredThrustCurveMotors";
159         
160
161         private static final Preferences PREFNODE;
162         
163
164         // Clear the preferences if debug mode and clearprefs is defined
165         static {
166                 Preferences root = Preferences.userRoot();
167                 if (DEBUG && CLEARPREFS) {
168                         try {
169                                 if (root.nodeExists(NODENAME)) {
170                                         root.node(NODENAME).removeNode();
171                                 }
172                         } catch (BackingStoreException e) {
173                                 throw new BugException("Unable to clear preference node", e);
174                         }
175                 }
176                 PREFNODE = root.node(NODENAME);
177         }
178         
179
180
181
182         /////////  Default component attributes
183         
184         private static final HashMap<Class<?>, String> DEFAULT_COLORS =
185                         new HashMap<Class<?>, String>();
186         static {
187                 DEFAULT_COLORS.put(BodyComponent.class, "0,0,240");
188                 DEFAULT_COLORS.put(FinSet.class, "0,0,200");
189                 DEFAULT_COLORS.put(LaunchLug.class, "0,0,180");
190                 DEFAULT_COLORS.put(InternalComponent.class, "170,0,100");
191                 DEFAULT_COLORS.put(MassObject.class, "0,0,0");
192                 DEFAULT_COLORS.put(RecoveryDevice.class, "255,0,0");
193         }
194         
195
196         private static final HashMap<Class<?>, String> DEFAULT_LINE_STYLES =
197                         new HashMap<Class<?>, String>();
198         static {
199                 DEFAULT_LINE_STYLES.put(RocketComponent.class, LineStyle.SOLID.name());
200                 DEFAULT_LINE_STYLES.put(MassObject.class, LineStyle.DASHED.name());
201         }
202         
203         
204         /*
205          * Within a holder class so they will load only when needed.
206          */
207         private static class DefaultMaterialHolder {
208                 private static final Translator trans = Application.getTranslator();
209                 
210                 //// Elastic cord (round 2mm, 1/16 in)
211                 private static final Material DEFAULT_LINE_MATERIAL =
212                                 Databases.findMaterial(Material.Type.LINE, trans.get("Databases.materials.Elasticcordround2mm"),
213                                                 0.0018, false);
214                 //// Ripstop nylon
215                 private static final Material DEFAULT_SURFACE_MATERIAL =
216                                 Databases.findMaterial(Material.Type.SURFACE, trans.get("Databases.materials.Ripstopnylon"), 0.067, false);
217                 //// Cardboard
218                 private static final Material DEFAULT_BULK_MATERIAL =
219                                 Databases.findMaterial(Material.Type.BULK, trans.get("Databases.materials.Cardboard"), 680, false);
220         }
221         
222         //////////////////////
223         
224
225         /**
226          * Return the OpenRocket version number.
227          */
228         public static String getVersion() {
229                 return BuildPropertyHolder.BUILD_VERSION;
230         }
231         
232         
233         /**
234          * Return the OpenRocket build source (e.g. "default" or "Debian")
235          */
236         public static String getBuildSource() {
237                 return BuildPropertyHolder.BUILD_SOURCE;
238         }
239         
240         
241         /**
242          * Return the OpenRocket unique ID.
243          * 
244          * @return      a random ID string that stays constant between OpenRocket executions
245          */
246         public static String getUniqueID() {
247                 String id = PREFNODE.get("id", null);
248                 if (id == null) {
249                         id = UniqueID.uuid();
250                         PREFNODE.put("id", id);
251                 }
252                 return id;
253         }
254         
255         
256
257         /**
258          * Store the current OpenRocket version into the preferences to allow for preferences migration.
259          */
260         private static void storeVersion() {
261                 PREFNODE.put("OpenRocketVersion", getVersion());
262         }
263         
264         
265         /**
266          * Returns a limited-range integer value from the preferences.  If the value 
267          * in the preferences is negative or greater than max, then the default value 
268          * is returned.
269          * 
270          * @param key  The preference to retrieve.
271          * @param max  Maximum allowed value for the choice.
272          * @param def  Default value.
273          * @return   The preference value.
274          */
275         public static int getChoise(String key, int max, int def) {
276                 int v = PREFNODE.getInt(key, def);
277                 if ((v < 0) || (v > max))
278                         return def;
279                 return v;
280         }
281         
282         
283         /**
284          * Helper method that puts an integer choice value into the preferences.
285          * 
286          * @param key     the preference key.
287          * @param value   the value to store.
288          */
289         public static void putChoise(String key, int value) {
290                 PREFNODE.putInt(key, value);
291                 storeVersion();
292         }
293         
294         
295         /**
296          * Return a string preference.
297          * 
298          * @param key   the preference key.
299          * @param def   the default if no preference is stored
300          * @return              the preference value
301          */
302         public static String getString(String key, String def) {
303                 return PREFNODE.get(key, def);
304         }
305         
306         /**
307          * Set a string preference.
308          * 
309          * @param key           the preference key
310          * @param value         the value to set, or <code>null</code> to remove the key
311          */
312         public static void putString(String key, String value) {
313                 if (value == null) {
314                         PREFNODE.remove(key);
315                         return;
316                 }
317                 PREFNODE.put(key, value);
318                 storeVersion();
319         }
320         
321         
322         /**
323          * Retrieve an enum value from the user preferences.
324          * 
325          * @param <T>   the enum type
326          * @param key   the key
327          * @param def   the default value, cannot be null
328          * @return              the value in the preferences, or the default value
329          */
330         public static <T extends Enum<T>> T getEnum(String key, T def) {
331                 if (def == null) {
332                         throw new BugException("Default value cannot be null");
333                 }
334                 
335                 String value = getString(key, null);
336                 if (value == null) {
337                         return def;
338                 }
339                 
340                 try {
341                         return Enum.valueOf(def.getDeclaringClass(), value);
342                 } catch (IllegalArgumentException e) {
343                         return def;
344                 }
345         }
346         
347         /**
348          * Store an enum value to the user preferences.
349          * 
350          * @param key           the key
351          * @param value         the value to store, or null to remove the value
352          */
353         public static void putEnum(String key, Enum<?> value) {
354                 if (value == null) {
355                         putString(key, null);
356                 } else {
357                         putString(key, value.name());
358                 }
359         }
360         
361         
362         /**
363          * Return a boolean preference.
364          * 
365          * @param key   the preference key
366          * @param def   the default if no preference is stored
367          * @return              the preference value
368          */
369         public static boolean getBoolean(String key, boolean def) {
370                 return PREFNODE.getBoolean(key, def);
371         }
372         
373         /**
374          * Set a boolean preference.
375          * 
376          * @param key           the preference key
377          * @param value         the value to set
378          */
379         public static void putBoolean(String key, boolean value) {
380                 PREFNODE.putBoolean(key, value);
381                 storeVersion();
382         }
383         
384         public static int getInt( String key, int defaultValue ) {
385                 return PREFNODE.getInt(key, defaultValue);
386         }
387         
388         public static void putInt( String key , int value ) {
389                 PREFNODE.putInt(key, value );
390         }
391
392         /**
393          * Return a preferences object for the specified node name.
394          * 
395          * @param nodeName      the node name
396          * @return                      the preferences object for that node
397          */
398         public static Preferences getNode(String nodeName) {
399                 return PREFNODE.node(nodeName);
400         }
401         
402         
403         //////////////////
404         
405
406         public static List<Locale> getSupportedLocales() {
407                 return SUPPORTED_LOCALES;
408         }
409         
410         public static Locale getUserLocale() {
411                 String locale = getString("locale", null);
412                 return L10N.toLocale(locale);
413         }
414         
415         public static void setUserLocale(Locale l) {
416                 if (l == null) {
417                         putString("locale", null);
418                 } else {
419                         putString("locale", l.toString());
420                 }
421         }
422         
423         
424
425         public static boolean getCheckUpdates() {
426                 return PREFNODE.getBoolean(CHECK_UPDATES, BuildPropertyHolder.DEFAULT_CHECK_UPDATES);
427         }
428         
429         public static void setCheckUpdates(boolean check) {
430                 PREFNODE.putBoolean(CHECK_UPDATES, check);
431                 storeVersion();
432         }
433         
434         public static File getDefaultDirectory() {
435                 String file = PREFNODE.get("defaultDirectory", null);
436                 if (file == null)
437                         return null;
438                 return new File(file);
439         }
440         
441         public static void setDefaultDirectory(File dir) {
442                 String d;
443                 if (dir == null) {
444                         d = null;
445                 } else {
446                         d = dir.getAbsolutePath();
447                 }
448                 PREFNODE.put("defaultDirectory", d);
449                 storeVersion();
450         }
451         
452         
453         /**
454          * Return a list of files/directories to be loaded as custom thrust curves.
455          * <p>
456          * If this property has not been set, the directory "ThrustCurves" in the user
457          * application directory will be used.  The directory will be created if it does not
458          * exist.
459          * 
460          * @return      a list of files to load as thrust curves.
461          */
462         public static List<File> getUserThrustCurveFiles() {
463                 List<File> list = new ArrayList<File>();
464                 
465                 String files = getString(USER_THRUST_CURVES_KEY, null);
466                 if (files == null) {
467                         // Default to application directory
468                         File tcdir = getDefaultUserThrustCurveFile();
469                         if (!tcdir.isDirectory()) {
470                                 tcdir.mkdirs();
471                         }
472                         list.add(tcdir);
473                 } else {
474                         for (String file : files.split("\\" + SPLIT_CHARACTER)) {
475                                 file = file.trim();
476                                 if (file.length() > 0) {
477                                         list.add(new File(file));
478                                 }
479                         }
480                 }
481                 
482                 return list;
483         }
484         
485         public static File getDefaultUserThrustCurveFile() {
486                 File appdir = SystemInfo.getUserApplicationDirectory();
487                 File tcdir = new File(appdir, "ThrustCurves");
488                 return tcdir;
489         }
490         
491         
492         /**
493          * Set the list of files/directories to be loaded as custom thrust curves.
494          * 
495          * @param files         the files to load, or <code>null</code> to reset to default value.
496          */
497         public static void setUserThrustCurveFiles(List<File> files) {
498                 if (files == null) {
499                         putString(USER_THRUST_CURVES_KEY, null);
500                         return;
501                 }
502                 
503                 String str = "";
504                 
505                 for (File file : files) {
506                         if (str.length() > 0) {
507                                 str += SPLIT_CHARACTER;
508                         }
509                         str += file.getAbsolutePath();
510                 }
511                 putString(USER_THRUST_CURVES_KEY, str);
512         }
513         
514         
515
516
517
518         public static Color getDefaultColor(Class<? extends RocketComponent> c) {
519                 String color = get("componentColors", c, DEFAULT_COLORS);
520                 if (color == null)
521                         return Color.BLACK;
522                 
523                 Color clr = parseColor(color);
524                 if (clr != null) {
525                         return clr;
526                 } else {
527                         return Color.BLACK;
528                 }
529         }
530         
531         public static void setDefaultColor(Class<? extends RocketComponent> c, Color color) {
532                 if (color == null)
533                         return;
534                 set("componentColors", c, stringifyColor(color));
535         }
536         
537         
538         private static Color parseColor(String color) {
539                 if (color == null) {
540                         return null;
541                 }
542                 
543                 String[] rgb = color.split(",");
544                 if (rgb.length == 3) {
545                         try {
546                                 int red = MathUtil.clamp(Integer.parseInt(rgb[0]), 0, 255);
547                                 int green = MathUtil.clamp(Integer.parseInt(rgb[1]), 0, 255);
548                                 int blue = MathUtil.clamp(Integer.parseInt(rgb[2]), 0, 255);
549                                 return new Color(red, green, blue);
550                         } catch (NumberFormatException ignore) {
551                         }
552                 }
553                 return null;
554         }
555         
556         
557         private static String stringifyColor(Color color) {
558                 String string = color.getRed() + "," + color.getGreen() + "," + color.getBlue();
559                 return string;
560         }
561         
562         
563
564         public static Color getMotorBorderColor() {
565                 // TODO: MEDIUM:  Motor color (settable?)
566                 return new Color(0, 0, 0, 200);
567         }
568         
569         
570         public static Color getMotorFillColor() {
571                 // TODO: MEDIUM:  Motor fill color (settable?)
572                 return new Color(0, 0, 0, 100);
573         }
574         
575         
576         public static LineStyle getDefaultLineStyle(Class<? extends RocketComponent> c) {
577                 String value = get("componentStyle", c, DEFAULT_LINE_STYLES);
578                 try {
579                         return LineStyle.valueOf(value);
580                 } catch (Exception e) {
581                         return LineStyle.SOLID;
582                 }
583         }
584         
585         public static void setDefaultLineStyle(Class<? extends RocketComponent> c,
586                         LineStyle style) {
587                 if (style == null)
588                         return;
589                 set("componentStyle", c, style.name());
590         }
591         
592         
593         public static double getDefaultMach() {
594                 // TODO: HIGH: implement custom default mach number
595                 return 0.3;
596         }
597         
598         public static Material getDefaultComponentMaterial(
599                         Class<? extends RocketComponent> componentClass,
600                         Material.Type type) {
601                 
602                 String material = get("componentMaterials", componentClass, null);
603                 if (material != null) {
604                         try {
605                                 Material m = Material.fromStorableString(material, false);
606                                 if (m.getType() == type)
607                                         return m;
608                         } catch (IllegalArgumentException ignore) {
609                         }
610                 }
611                 
612                 switch (type) {
613                 case LINE:
614                         return DefaultMaterialHolder.DEFAULT_LINE_MATERIAL;
615                 case SURFACE:
616                         return DefaultMaterialHolder.DEFAULT_SURFACE_MATERIAL;
617                 case BULK:
618                         return DefaultMaterialHolder.DEFAULT_BULK_MATERIAL;
619                 }
620                 throw new IllegalArgumentException("Unknown material type: " + type);
621         }
622         
623         public static void setDefaultComponentMaterial(
624                         Class<? extends RocketComponent> componentClass, Material material) {
625                 
626                 set("componentMaterials", componentClass,
627                                 material == null ? null : material.toStorableString());
628         }
629         
630         
631         public static int getMaxThreadCount() {
632                 return Runtime.getRuntime().availableProcessors();
633         }
634         
635         
636         /**
637          * Return whether to use additional safety code checks.
638          */
639         public static boolean useSafetyChecks() {
640                 // Currently default to false unless openrocket.debug.safetycheck is defined
641                 String s = System.getProperty("openrocket.debug.safetycheck");
642                 if (s != null && !(s.equalsIgnoreCase("false") || s.equalsIgnoreCase("off"))) {
643                         return true;
644                 }
645                 return false;
646         }
647         
648         
649         public static Point getWindowPosition(Class<?> c) {
650                 int x, y;
651                 String pref = PREFNODE.node("windows").get("position." + c.getCanonicalName(), null);
652                 
653                 if (pref == null)
654                         return null;
655                 
656                 if (pref.indexOf(',') < 0)
657                         return null;
658                 
659                 try {
660                         x = Integer.parseInt(pref.substring(0, pref.indexOf(',')));
661                         y = Integer.parseInt(pref.substring(pref.indexOf(',') + 1));
662                 } catch (NumberFormatException e) {
663                         return null;
664                 }
665                 return new Point(x, y);
666         }
667         
668         public static void setWindowPosition(Class<?> c, Point p) {
669                 PREFNODE.node("windows").put("position." + c.getCanonicalName(), "" + p.x + "," + p.y);
670                 storeVersion();
671         }
672         
673         
674
675
676         public static Dimension getWindowSize(Class<?> c) {
677                 int x, y;
678                 String pref = PREFNODE.node("windows").get("size." + c.getCanonicalName(), null);
679                 
680                 if (pref == null)
681                         return null;
682                 
683                 if (pref.indexOf(',') < 0)
684                         return null;
685                 
686                 try {
687                         x = Integer.parseInt(pref.substring(0, pref.indexOf(',')));
688                         y = Integer.parseInt(pref.substring(pref.indexOf(',') + 1));
689                 } catch (NumberFormatException e) {
690                         return null;
691                 }
692                 return new Dimension(x, y);
693         }
694         
695         
696         public static boolean isWindowMaximized(Class<?> c) {
697                 String pref = PREFNODE.node("windows").get("size." + c.getCanonicalName(), null);
698                 return "max".equals(pref);
699         }
700         
701         public static void setWindowSize(Class<?> c, Dimension d) {
702                 PREFNODE.node("windows").put("size." + c.getCanonicalName(), "" + d.width + "," + d.height);
703                 storeVersion();
704         }
705         
706         public static void setWindowMaximized(Class<?> c) {
707                 PREFNODE.node("windows").put("size." + c.getCanonicalName(), "max");
708                 storeVersion();
709         }
710         
711         
712         ////  Printing
713         
714         public static PrintSettings getPrintSettings() {
715                 PrintSettings settings = new PrintSettings();
716                 Color c;
717                 
718                 c = parseColor(getString("print.template.fillColor", null));
719                 if (c != null) {
720                         settings.setTemplateFillColor(c);
721                 }
722                 
723                 c = parseColor(getString("print.template.borderColor", null));
724                 if (c != null) {
725                         settings.setTemplateBorderColor(c);
726                 }
727                 
728                 settings.setPaperSize(getEnum("print.paper.size", settings.getPaperSize()));
729                 settings.setPaperOrientation(getEnum("print.paper.orientation", settings.getPaperOrientation()));
730                 
731                 return settings;
732         }
733         
734         public static void setPrintSettings(PrintSettings settings) {
735                 putString("print.template.fillColor", stringifyColor(settings.getTemplateFillColor()));
736                 putString("print.template.borderColor", stringifyColor(settings.getTemplateBorderColor()));
737                 putEnum("print.paper.size", settings.getPaperSize());
738                 putEnum("print.paper.orientation", settings.getPaperOrientation());
739         }
740         
741         ////  Background flight data computation
742         
743         public static boolean computeFlightInBackground() {
744                 return PREFNODE.getBoolean("backgroundFlight", true);
745         }
746         
747         public static Simulation getBackgroundSimulation(Rocket rocket) {
748                 Simulation s = new Simulation(rocket);
749                 SimulationOptions cond = s.getOptions();
750                 
751                 cond.setTimeStep(RK4SimulationStepper.RECOMMENDED_TIME_STEP * 2);
752                 cond.setWindSpeedAverage(1.0);
753                 cond.setWindSpeedDeviation(0.1);
754                 cond.setLaunchRodLength(5);
755                 return s;
756         }
757         
758         
759
760         /////////  Export variables
761         
762         public static boolean isExportSelected(FlightDataType type) {
763                 Preferences prefs = PREFNODE.node("exports");
764                 return prefs.getBoolean(type.getName(), false);
765         }
766         
767         public static void setExportSelected(FlightDataType type, boolean selected) {
768                 Preferences prefs = PREFNODE.node("exports");
769                 prefs.putBoolean(type.getName(), selected);
770         }
771         
772         
773
774         /////////  Default unit storage
775         
776         public static void loadDefaultUnits() {
777                 Preferences prefs = PREFNODE.node("units");
778                 try {
779                         
780                         for (String key : prefs.keys()) {
781                                 UnitGroup group = UnitGroup.UNITS.get(key);
782                                 if (group == null)
783                                         continue;
784                                 
785                                 try {
786                                         group.setDefaultUnit(prefs.get(key, null));
787                                 } catch (IllegalArgumentException ignore) {
788                                 }
789                         }
790                         
791                 } catch (BackingStoreException e) {
792                         ExceptionHandler.handleErrorCondition(e);
793                 }
794         }
795         
796         public static void storeDefaultUnits() {
797                 Preferences prefs = PREFNODE.node("units");
798                 
799                 for (String key : UnitGroup.UNITS.keySet()) {
800                         UnitGroup group = UnitGroup.UNITS.get(key);
801                         if (group == null || group.getUnitCount() < 2)
802                                 continue;
803                         
804                         prefs.put(key, group.getDefaultUnit().getUnit());
805                 }
806         }
807         
808         
809
810         ////  Material storage
811         
812
813         /**
814          * Add a user-defined material to the preferences.  The preferences are
815          * first checked for an existing material matching the provided one using
816          * {@link Material#equals(Object)}.
817          * 
818          * @param m             the material to add.
819          */
820         public static void addUserMaterial(Material m) {
821                 Preferences prefs = PREFNODE.node("userMaterials");
822                 
823
824                 // Check whether material already exists
825                 if (getUserMaterials().contains(m)) {
826                         return;
827                 }
828                 
829                 // Add material using next free key (key is not used when loading)
830                 String mat = m.toStorableString();
831                 for (int i = 0;; i++) {
832                         String key = "material" + i;
833                         if (prefs.get(key, null) == null) {
834                                 prefs.put(key, mat);
835                                 return;
836                         }
837                 }
838         }
839         
840         
841         /**
842          * Remove a user-defined material from the preferences.  The matching is performed
843          * using {@link Material#equals(Object)}.
844          * 
845          * @param m             the material to remove.
846          */
847         public static void removeUserMaterial(Material m) {
848                 Preferences prefs = PREFNODE.node("userMaterials");
849                 
850                 try {
851                         
852                         // Iterate through materials and remove all keys with a matching material
853                         for (String key : prefs.keys()) {
854                                 String value = prefs.get(key, null);
855                                 try {
856                                         
857                                         Material existing = Material.fromStorableString(value, true);
858                                         if (existing.equals(m)) {
859                                                 prefs.remove(key);
860                                         }
861                                         
862                                 } catch (IllegalArgumentException ignore) {
863                                 }
864                                 
865                         }
866                         
867                 } catch (BackingStoreException e) {
868                         throw new IllegalStateException("Cannot read preferences!", e);
869                 }
870         }
871         
872         
873         /**
874          * Return a set of all user-defined materials in the preferences.  The materials
875          * are created marked as user-defined.
876          * 
877          * @return      a set of all user-defined materials.
878          */
879         public static Set<Material> getUserMaterials() {
880                 Preferences prefs = PREFNODE.node("userMaterials");
881                 
882                 HashSet<Material> materials = new HashSet<Material>();
883                 try {
884                         
885                         for (String key : prefs.keys()) {
886                                 String value = prefs.get(key, null);
887                                 try {
888                                         
889                                         Material m = Material.fromStorableString(value, true);
890                                         materials.add(m);
891                                         
892                                 } catch (IllegalArgumentException e) {
893                                         log.warn("Illegal material string " + value);
894                                 }
895                                 
896                         }
897                         
898                 } catch (BackingStoreException e) {
899                         throw new IllegalStateException("Cannot read preferences!", e);
900                 }
901                 
902                 return materials;
903         }
904         
905         
906         ////  Helper methods
907         
908         private static String get(String directory,
909                         Class<? extends RocketComponent> componentClass,
910                         Map<Class<?>, String> defaultMap) {
911                 
912                 // Search preferences
913                 Class<?> c = componentClass;
914                 Preferences prefs = PREFNODE.node(directory);
915                 while (c != null && RocketComponent.class.isAssignableFrom(c)) {
916                         String value = prefs.get(c.getSimpleName(), null);
917                         if (value != null)
918                                 return value;
919                         c = c.getSuperclass();
920                 }
921                 
922                 if (defaultMap == null)
923                         return null;
924                 
925                 // Search defaults
926                 c = componentClass;
927                 while (RocketComponent.class.isAssignableFrom(c)) {
928                         String value = defaultMap.get(c);
929                         if (value != null)
930                                 return value;
931                         c = c.getSuperclass();
932                 }
933                 
934                 return null;
935         }
936         
937         
938         private static void set(String directory, Class<? extends RocketComponent> componentClass,
939                         String value) {
940                 Preferences prefs = PREFNODE.node(directory);
941                 if (value == null)
942                         prefs.remove(componentClass.getSimpleName());
943                 else
944                         prefs.put(componentClass.getSimpleName(), value);
945                 storeVersion();
946         }
947         
948 }