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