1/*
2 * Copyright (c) 2009-2012 jMonkeyEngine
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are
7 * met:
8 *
9 * * Redistributions of source code must retain the above copyright
10 *   notice, this list of conditions and the following disclaimer.
11 *
12 * * Redistributions in binary form must reproduce the above copyright
13 *   notice, this list of conditions and the following disclaimer in the
14 *   documentation and/or other materials provided with the distribution.
15 *
16 * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
17 *   may be used to endorse or promote products derived from this software
18 *   without specific prior written permission.
19 *
20 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
22 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
23 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
24 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
25 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
26 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
27 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
28 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
29 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
30 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31 */
32package com.jme3.system;
33
34import java.io.IOException;
35import java.io.InputStream;
36import java.io.OutputStream;
37import java.io.UnsupportedEncodingException;
38import java.util.HashMap;
39import java.util.Map;
40import java.util.Properties;
41import java.util.prefs.BackingStoreException;
42import java.util.prefs.Preferences;
43
44/**
45 * <code>AppSettings</code> provides a store of configuration
46 * to be used by the application.
47 * <p>
48 * By default only the {@link JmeContext context} uses the configuration,
49 * however the user may set and retrieve the settings as well.
50 *
51 * @author Kirill Vainer
52 */
53public final class AppSettings extends HashMap<String, Object> {
54
55    private static final AppSettings defaults = new AppSettings(false);
56
57    /**
58     * Use LWJGL as the display system and force using the OpenGL1.1 renderer.
59     *
60     * @see AppSettings#setRenderer(java.lang.String)
61     */
62    public static final String LWJGL_OPENGL1 = "LWJGL-OPENGL1";
63
64    /**
65     * Use LWJGL as the display system and force using the OpenGL2.0 renderer.
66     * <p>
67     * If the underlying system does not support OpenGL2.0, then the context
68     * initialization will throw an exception.
69     *
70     * @see AppSettings#setRenderer(java.lang.String)
71     */
72    public static final String LWJGL_OPENGL2 = "LWJGL-OpenGL2";
73
74    /**
75     * Use LWJGL as the display system and force using the core OpenGL3.3 renderer.
76     * <p>
77     * If the underlying system does not support OpenGL3.3, then the context
78     * initialization will throw an exception. Note that currently jMonkeyEngine
79     * does not have any shaders that support OpenGL3.3 therefore this
80     * option is not useful.
81     *
82     *
83     * @see AppSettings#setRenderer(java.lang.String)
84     */
85    public static final String LWJGL_OPENGL3 = "LWJGL-OpenGL3";
86
87    /**
88     * Use LWJGL as the display system and allow the context
89     * to choose an appropriate renderer based on system capabilities.
90     * <p>
91     * If the GPU supports OpenGL2 or later, then the OpenGL2.0 renderer will
92     * be used, otherwise, the OpenGL1.1 renderer is used.
93     *
94     * @see AppSettings#setRenderer(java.lang.String)
95     */
96    public static final String LWJGL_OPENGL_ANY = "LWJGL-OpenGL-Any";
97
98    /**
99     * The JOGL renderer is no longer supported by jME.
100     *
101     * @deprecated Use the LWJGL renderer instead.
102     *
103     * @see AppSettings#setRenderer(java.lang.String)
104     */
105    @Deprecated
106    public static final String JOGL = "JOGL";
107
108    /**
109     * The "NULL" option is no longer supported
110     *
111     * @deprecated Specify the "null" value instead
112     *
113     * @see AppSettings#setRenderer(java.lang.String)
114     * @see AppSettings#setAudioRenderer(java.lang.String)
115     */
116    @Deprecated
117    public static final String NULL = "NULL";
118
119    /**
120     * Use the LWJGL OpenAL based renderer for audio capabilities.
121     *
122     * @see AppSettings#setAudioRenderer(java.lang.String)
123     */
124    public static final String LWJGL_OPENAL = "LWJGL";
125
126    static {
127        defaults.put("Width", 640);
128        defaults.put("Height", 480);
129        defaults.put("BitsPerPixel", 24);
130        defaults.put("Frequency", 60);
131        defaults.put("DepthBits", 24);
132        defaults.put("StencilBits", 0);
133        defaults.put("Samples", 0);
134        defaults.put("Fullscreen", false);
135        defaults.put("Title", "jMonkey Engine 3.0");
136        defaults.put("Renderer", LWJGL_OPENGL2);
137        defaults.put("AudioRenderer", LWJGL_OPENAL);
138        defaults.put("DisableJoysticks", true);
139        defaults.put("UseInput", true);
140        defaults.put("VSync", false);
141        defaults.put("FrameRate", -1);
142        defaults.put("SettingsDialogImage", "/com/jme3/app/Monkey.png");
143      //  defaults.put("Icons", null);
144    }
145
146    /**
147     * Create a new instance of <code>AppSettings</code>.
148     * <p>
149     * If <code>loadDefaults</code> is true, then the default settings
150     * will be set on the AppSettings.
151     * Use false if you want to change some settings but you would like the
152     * application to load settings from previous launches.
153     *
154     * @param loadDefaults If default settings are to be loaded.
155     */
156    public AppSettings(boolean loadDefaults) {
157        if (loadDefaults) {
158            putAll(defaults);
159        }
160    }
161
162    /**
163     * Copies all settings from <code>other</code> to <code>this</code>
164     * AppSettings.
165     * <p>
166     * Any settings that are specified in other will overwrite settings
167     * set on this AppSettings.
168     *
169     * @param other The AppSettings to copy the settings from
170     */
171    public void copyFrom(AppSettings other) {
172        this.putAll(other);
173    }
174
175    /**
176     * Same as {@link #copyFrom(com.jme3.system.AppSettings) }, except
177     * doesn't overwrite settings that are already set.
178     *
179     * @param other  The AppSettings to merge the settings from
180     */
181    public void mergeFrom(AppSettings other) {
182        for (String key : other.keySet()) {
183            if (get(key) == null) {
184                put(key, other.get(key));
185            }
186        }
187    }
188
189    /**
190     * Loads the settings from the given properties input stream.
191     *
192     * @param in The InputStream to load from
193     * @throws IOException If an IOException occurs
194     *
195     * @see #save(java.io.OutputStream)
196     */
197    public void load(InputStream in) throws IOException {
198        Properties props = new Properties();
199        props.load(in);
200        for (Map.Entry<Object, Object> entry : props.entrySet()) {
201            String key = (String) entry.getKey();
202            String val = (String) entry.getValue();
203            if (val != null) {
204                val = val.trim();
205            }
206            if (key.endsWith("(int)")) {
207                key = key.substring(0, key.length() - 5);
208                int iVal = Integer.parseInt(val);
209                putInteger(key, iVal);
210            } else if (key.endsWith("(string)")) {
211                putString(key.substring(0, key.length() - 8), val);
212            } else if (key.endsWith("(bool)")) {
213                boolean bVal = Boolean.parseBoolean(val);
214                putBoolean(key.substring(0, key.length() - 6), bVal);
215            } else {
216                throw new IOException("Cannot parse key: " + key);
217            }
218        }
219    }
220
221    /**
222     * Saves all settings to the given properties output stream.
223     *
224     * @param out The OutputStream to write to
225     * @throws IOException If an IOException occurs
226     *
227     * @see #load(java.io.InputStream)
228     */
229    public void save(OutputStream out) throws IOException {
230        Properties props = new Properties();
231        for (Map.Entry<String, Object> entry : entrySet()) {
232            Object val = entry.getValue();
233            String type;
234            if (val instanceof Integer) {
235                type = "(int)";
236            } else if (val instanceof String) {
237                type = "(string)";
238            } else if (val instanceof Boolean) {
239                type = "(bool)";
240            } else {
241                throw new UnsupportedEncodingException();
242            }
243            props.setProperty(entry.getKey() + type, val.toString());
244        }
245        props.store(out, "jME3 AppSettings");
246    }
247
248    /**
249     * Loads settings previously saved in the Java preferences.
250     *
251     * @param preferencesKey The preferencesKey previously used to save the settings.
252     * @throws BackingStoreException If an exception occurs with the preferences
253     *
254     * @see #save(java.lang.String)
255     */
256    public void load(String preferencesKey) throws BackingStoreException {
257        Preferences prefs = Preferences.userRoot().node(preferencesKey);
258        String[] keys = prefs.keys();
259        if (keys != null) {
260            for (String key : keys) {
261                Object defaultValue = defaults.get(key);
262                if (defaultValue instanceof Integer) {
263                    put(key, prefs.getInt(key, (Integer) defaultValue));
264                } else if (defaultValue instanceof String) {
265                    put(key, prefs.get(key, (String) defaultValue));
266                } else if (defaultValue instanceof Boolean) {
267                    put(key, prefs.getBoolean(key, (Boolean) defaultValue));
268                }
269            }
270        }
271    }
272
273    /**
274     * Saves settings into the Java preferences.
275     * <p>
276     * On the Windows operating system, the preferences are saved in the registry
277     * at the following key:<br>
278     * <code>HKEY_CURRENT_USER\Software\JavaSoft\Prefs\[preferencesKey]</code>
279     *
280     * @param preferencesKey The preferences key to save at. Generally the
281     * application's unique name.
282     *
283     * @throws BackingStoreException If an exception occurs with the preferences
284     */
285    public void save(String preferencesKey) throws BackingStoreException {
286        Preferences prefs = Preferences.userRoot().node(preferencesKey);
287        for (String key : keySet()) {
288            prefs.put(key, get(key).toString());
289        }
290    }
291
292    /**
293     * Get an integer from the settings.
294     * <p>
295     * If the key is not set, then 0 is returned.
296     */
297    public int getInteger(String key) {
298        Integer i = (Integer) get(key);
299        if (i == null) {
300            return 0;
301        }
302
303        return i.intValue();
304    }
305
306    /**
307     * Get a boolean from the settings.
308     * <p>
309     * If the key is not set, then false is returned.
310     */
311    public boolean getBoolean(String key) {
312        Boolean b = (Boolean) get(key);
313        if (b == null) {
314            return false;
315        }
316
317        return b.booleanValue();
318    }
319
320    /**
321     * Get a string from the settings.
322     * <p>
323     * If the key is not set, then null is returned.
324     */
325    public String getString(String key) {
326        String s = (String) get(key);
327        if (s == null) {
328            return null;
329        }
330
331        return s;
332    }
333
334    /**
335     * Set an integer on the settings.
336     */
337    public void putInteger(String key, int value) {
338        put(key, Integer.valueOf(value));
339    }
340
341    /**
342     * Set a boolean on the settings.
343     */
344    public void putBoolean(String key, boolean value) {
345        put(key, Boolean.valueOf(value));
346    }
347
348    /**
349     * Set a string on the settings.
350     */
351    public void putString(String key, String value) {
352        put(key, value);
353    }
354
355    /**
356     * @param frameRate The frame-rate is the upper limit on how high
357     * the application's frames-per-second can go.
358     * (Default: -1 no frame rate limit imposed)
359     */
360    public void setFrameRate(int frameRate) {
361        putInteger("FrameRate", frameRate);
362    }
363
364    /**
365     * @param use If true, the application will initialize and use input.
366     * Set to false for headless applications that do not require keyboard
367     * or mouse input.
368     * (Default: true)
369     */
370    public void setUseInput(boolean use) {
371        putBoolean("UseInput", use);
372    }
373
374    /**
375     * @param use If true, the application will initialize and use joystick
376     * input. Set to false if no joystick input is desired.
377     * (Default: false)
378     */
379    public void setUseJoysticks(boolean use) {
380        putBoolean("DisableJoysticks", !use);
381    }
382
383    /**
384     * Set the graphics renderer to use, one of:<br>
385     * <ul>
386     * <li>AppSettings.LWJGL_OPENGL1 - Force OpenGL1.1 compatability</li>
387     * <li>AppSettings.LWJGL_OPENGL2 - Force OpenGL2 compatability</li>
388     * <li>AppSettings.LWJGL_OPENGL3 - Force OpenGL3.3 compatability</li>
389     * <li>AppSettings.LWJGL_OPENGL_ANY - Choose an appropriate
390     * OpenGL version based on system capabilities</li>
391     * <li>null - Disable graphics rendering</li>
392     * </ul>
393     * @param renderer The renderer to set
394     * (Default: AppSettings.LWJGL_OPENGL2)
395     */
396    public void setRenderer(String renderer) {
397        putString("Renderer", renderer);
398    }
399
400    /**
401     * Set a custom graphics renderer to use. The class should implement
402     * the {@link JmeContext} interface.
403     * @param clazz The custom context class.
404     * (Default: not set)
405     */
406    public void setCustomRenderer(Class<? extends JmeContext> clazz){
407        put("Renderer", "CUSTOM" + clazz.getName());
408    }
409
410    /**
411     * Set the audio renderer to use. One of:<br>
412     * <ul>
413     * <li>AppSettings.LWJGL_OPENAL - Default for LWJGL</li>
414     * <li>null - Disable audio</li>
415     * </ul>
416     * @param audioRenderer
417     * (Default: LWJGL)
418     */
419    public void setAudioRenderer(String audioRenderer) {
420        putString("AudioRenderer", audioRenderer);
421    }
422
423    /**
424     * @param value the width for the rendering display.
425     * (Default: 640)
426     */
427    public void setWidth(int value) {
428        putInteger("Width", value);
429    }
430
431    /**
432     * @param value the height for the rendering display.
433     * (Default: 480)
434     */
435    public void setHeight(int value) {
436        putInteger("Height", value);
437    }
438
439    /**
440     * Set the resolution for the rendering display
441     * @param width The width
442     * @param height The height
443     * (Default: 640x480)
444     */
445    public void setResolution(int width, int height) {
446        setWidth(width);
447        setHeight(height);
448    }
449
450    /**
451     * Set the frequency, also known as refresh rate, for the
452     * rendering display.
453     * @param value The frequency
454     * (Default: 60)
455     */
456    public void setFrequency(int value) {
457        putInteger("Frequency", value);
458    }
459
460    /**
461     * Sets the number of depth bits to use.
462     * <p>
463     * The number of depth bits specifies the precision of the depth buffer.
464     * To increase precision, specify 32 bits. To decrease precision, specify
465     * 16 bits. On some platforms 24 bits might not be supported, in that case,
466     * specify 16 bits.<p>
467     * (Default: 24)
468     *
469     * @param value The depth bits
470     */
471    public void setDepthBits(int value){
472        putInteger("DepthBits", value);
473    }
474
475    /**
476     * Set the number of stencil bits.
477     * <p>
478     * This value is only relevant when the stencil buffer is being used.
479     * Specify 8 to indicate an 8-bit stencil buffer, specify 0 to disable
480     * the stencil buffer.
481     * </p>
482     * (Default: 0)
483     *
484     * @param value Number of stencil bits
485     */
486    public void setStencilBits(int value){
487        putInteger("StencilBits", value);
488    }
489
490    /**
491     * Set the bits per pixel for the display. Appropriate
492     * values are 16 for RGB565 color format, or 24 for RGB8 color format.
493     *
494     * @param value The bits per pixel to set
495     * (Default: 24)
496     */
497    public void setBitsPerPixel(int value) {
498        putInteger("BitsPerPixel", value);
499    }
500
501    /**
502     * Set the number of samples per pixel. A value of 1 indicates
503     * each pixel should be single-sampled, higher values indicate
504     * a pixel should be multi-sampled.
505     *
506     * @param value The number of samples
507     * (Default: 1)
508     */
509    public void setSamples(int value) {
510        putInteger("Samples", value);
511    }
512
513    /**
514     * @param title The title of the rendering display
515     * (Default: jMonkeyEngine 3.0)
516     */
517    public void setTitle(String title) {
518        putString("Title", title);
519    }
520
521    /**
522     * @param value true to enable full-screen rendering, false to render in a window
523     * (Default: false)
524     */
525    public void setFullscreen(boolean value) {
526        putBoolean("Fullscreen", value);
527    }
528
529    /**
530     * Set to true to enable vertical-synchronization, limiting and synchronizing
531     * every frame rendered to the monitor's refresh rate.
532     * @param value
533     * (Default: false)
534     */
535    public void setVSync(boolean value) {
536        putBoolean("VSync", value);
537    }
538
539    /**
540     * Enable 3D stereo.
541     * <p>This feature requires hardware support from the GPU driver.
542     * @see <a href="http://en.wikipedia.org/wiki/Quad_buffering">http://en.wikipedia.org/wiki/Quad_buffering</a><br />
543     * Once enabled, filters or scene processors that handle 3D stereo rendering
544     * could use this feature to render using hardware 3D stereo.</p>
545     * (Default: false)
546     */
547    public void setStereo3D(boolean value){
548        putBoolean("Stereo3D", value);
549    }
550
551    /**
552     * Sets the application icons to be used, with the most preferred first.
553     * For Windows you should supply at least one 16x16 icon and one 32x32. The former is used for the title/task bar,
554     * the latter for the alt-tab icon.
555     * Linux (and similar platforms) expect one 32x32 icon.
556     * Mac OS X should be supplied one 128x128 icon.
557     * <br/>
558     * The icon is used for the settings window, and the LWJGL render window. Not currently supported for JOGL.
559     * Note that a bug in Java 6 (bug ID 6445278, currently hidden but available in Google cache) currently prevents
560     * the icon working for alt-tab on the settings dialog in Windows.
561     *
562     * @param value An array of BufferedImages to use as icons.
563     * (Default: not set)
564     */
565    public void setIcons(Object[] value) {
566        put("Icons", value);
567    }
568
569    /**
570     * Sets the path of the settings dialog image to use.
571     * <p>
572     * The image will be displayed in the settings dialog when the
573     * application is started.
574     * </p>
575     * (Default: /com/jme3/app/Monkey.png)
576     *
577     * @param path The path to the image in the classpath.
578     */
579    public void setSettingsDialogImage(String path) {
580        putString("SettingsDialogImage", path);
581    }
582
583    /**
584     * Get the framerate.
585     * @see #setFrameRate(int)
586     */
587    public int getFrameRate() {
588        return getInteger("FrameRate");
589    }
590
591    /**
592     * Get the use input state.
593     * @see #setUseInput(boolean)
594     */
595    public boolean useInput() {
596        return getBoolean("UseInput");
597    }
598
599    /**
600     * Get the renderer
601     * @see #setRenderer(java.lang.String)
602     */
603    public String getRenderer() {
604        return getString("Renderer");
605    }
606
607    /**
608     * Get the width
609     * @see #setWidth(int)
610     */
611    public int getWidth() {
612        return getInteger("Width");
613    }
614
615    /**
616     * Get the height
617     * @see #setHeight(int)
618     */
619    public int getHeight() {
620        return getInteger("Height");
621    }
622
623    /**
624     * Get the bits per pixel
625     * @see #setBitsPerPixel(int)
626     */
627    public int getBitsPerPixel() {
628        return getInteger("BitsPerPixel");
629    }
630
631    /**
632     * Get the frequency
633     * @see #setFrequency(int)
634     */
635    public int getFrequency() {
636        return getInteger("Frequency");
637    }
638
639    /**
640     * Get the number of depth bits
641     * @see #setDepthBits(int)
642     */
643    public int getDepthBits() {
644        return getInteger("DepthBits");
645    }
646
647    /**
648     * Get the number of stencil bits
649     * @see #setStencilBits(int)
650     */
651    public int getStencilBits() {
652        return getInteger("StencilBits");
653    }
654
655    /**
656     * Get the number of samples
657     * @see #setSamples(int)
658     */
659    public int getSamples() {
660        return getInteger("Samples");
661    }
662
663    /**
664     * Get the application title
665     * @see #setTitle(java.lang.String)
666     */
667    public String getTitle() {
668        return getString("Title");
669    }
670
671    /**
672     * Get the vsync state
673     * @see #setVSync(boolean)
674     */
675    public boolean isVSync() {
676        return getBoolean("VSync");
677    }
678
679    /**
680     * Get the fullscreen state
681     * @see #setFullscreen(boolean)
682     */
683    public boolean isFullscreen() {
684        return getBoolean("Fullscreen");
685    }
686
687    /**
688     * Get the use joysticks state
689     * @see #setUseJoysticks(boolean)
690     */
691    public boolean useJoysticks() {
692        return !getBoolean("DisableJoysticks");
693    }
694
695    /**
696     * Get the audio renderer
697     * @see #setAudioRenderer(java.lang.String)
698     */
699    public String getAudioRenderer() {
700        return getString("AudioRenderer");
701    }
702
703    /**
704     * Get the stereo 3D state
705     * @see #setStereo3D(boolean)
706     */
707    public boolean useStereo3D(){
708        return getBoolean("Stereo3D");
709    }
710
711    /**
712     * Get the icon array
713     * @see #setIcons(java.lang.Object[])
714     */
715    public Object[] getIcons() {
716        return (Object[]) get("Icons");
717    }
718
719    /**
720     * Get the settings dialog image
721     * @see #setSettingsDialogImage(java.lang.String)
722     */
723    public String getSettingsDialogImage() {
724        return getString("SettingsDialogImage");
725    }
726}
727