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