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