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