浏览代码

Merge pull request #2478 from capdevon/capdevon-AppSettings

AWTSettingsDialog: javadoc + add AppSettings Preference logging
Ryan McDonough 1 月之前
父节点
当前提交
1fc62307d2

+ 148 - 83
jme3-awt-dialogs/src/main/java/com/jme3/awt/AWTSettingsDialog.java

@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2009-2022 jMonkeyEngine
+ * Copyright (c) 2009-2025 jMonkeyEngine
  * All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
@@ -57,12 +57,11 @@ import java.util.prefs.BackingStoreException;
 import javax.swing.*;
 
 /**
- * <code>SettingsDialog</code> displays a Swing dialog box to interactively
- * configure the <code>AppSettings</code> of a desktop application before
- * <code>start()</code> is invoked.
- *
- * The <code>AppSettings</code> instance to be configured is passed to the
- * constructor.
+ * `AWTSettingsDialog` displays a Swing dialog box to interactively
+ * configure the `AppSettings` of a desktop application before
+ * `start()` is invoked.
+ * <p>
+ * The `AppSettings` instance to be configured is passed to the constructor.
  *
  * @see AppSettings
  * @author Mark Powell
@@ -71,14 +70,33 @@ import javax.swing.*;
  */
 public final class AWTSettingsDialog extends JFrame {
 
-    public static interface SelectionListener {
-
-        public void onSelection(int selection);
+    /**
+     * Listener interface for handling selection events from the settings dialog.
+     */
+    public interface SelectionListener {
+        /**
+         * Called when a selection is made in the settings dialog (OK or Cancel).
+         *
+         * @param selection The type of selection made: `NO_SELECTION`, `APPROVE_SELECTION`, or `CANCEL_SELECTION`.
+         */
+        void onSelection(int selection);
     }
 
     private static final Logger logger = Logger.getLogger(AWTSettingsDialog.class.getName());
     private static final long serialVersionUID = 1L;
-    public static final int NO_SELECTION = 0, APPROVE_SELECTION = 1, CANCEL_SELECTION = 2;
+
+    /**
+     * Indicates that no selection has been made yet.
+     */
+    public static final int NO_SELECTION = 0;
+    /**
+     * Indicates that the user approved the settings.
+     */
+    public static final int APPROVE_SELECTION = 1;
+    /**
+     * Indicates that the user canceled the settings dialog.
+     */
+    public static final int CANCEL_SELECTION = 2;
 
     // Resource bundle for i18n.
     ResourceBundle resourceBundle = ResourceBundle.getBundle("com.jme3.app/SettingsDialog");
@@ -86,8 +104,12 @@ public final class AWTSettingsDialog extends JFrame {
     // the instance being configured
     private final AppSettings source;
 
-    // Title Image
+    /**
+     * The URL of the image file to be displayed as a title icon in the dialog.
+     * Can be `null` if no image is desired.
+     */
     private URL imageFile = null;
+
     // Array of supported display modes
     private DisplayMode[] modes = null;
     private static final DisplayMode[] windowDefaults = new DisplayMode[] {
@@ -114,10 +136,24 @@ public final class AWTSettingsDialog extends JFrame {
     private int minWidth = 0;
     private int minHeight = 0;
 
+    /**
+     * Displays a settings dialog using the provided `AppSettings` source.
+     * Settings will be loaded from preferences.
+     *
+     * @param sourceSettings The `AppSettings` instance to configure.
+     * @return `true` if the user approved the settings, `false` otherwise.
+     */
     public static boolean showDialog(AppSettings sourceSettings) {
         return showDialog(sourceSettings, true);
     }
 
+    /**
+     * Displays a settings dialog using the provided `AppSettings` source.
+     *
+     * @param sourceSettings The `AppSettings` instance to configure.
+     * @param loadSettings   If `true`, settings will be loaded from preferences; otherwise, they will be merged.
+     * @return `true` if the user approved the settings, `false` otherwise.
+     */
     public static boolean showDialog(AppSettings sourceSettings, boolean loadSettings) {
         String iconPath = sourceSettings.getSettingsDialogImage();
         final URL iconUrl = JmeSystem.class.getResource(iconPath.startsWith("/") ? iconPath : "/" + iconPath);
@@ -127,10 +163,30 @@ public final class AWTSettingsDialog extends JFrame {
         return showDialog(sourceSettings, iconUrl, loadSettings);
     }
 
+    /**
+     * Displays a settings dialog using the provided `AppSettings` source and an image file path.
+     *
+     * @param sourceSettings The `AppSettings` instance to configure.
+     * @param imageFile      The path to the image file to use as the title of the dialog;
+     *                       `null` will result in no image being displayed.
+     * @param loadSettings   If `true`, settings will be loaded from preferences; otherwise, they will be merged.
+     * @return `true` if the user approved the settings, `false` otherwise.
+     */
     public static boolean showDialog(AppSettings sourceSettings, String imageFile, boolean loadSettings) {
         return showDialog(sourceSettings, getURL(imageFile), loadSettings);
     }
 
+    /**
+     * Displays a settings dialog using the provided `AppSettings` source and an image URL.
+     * This method blocks until the dialog is closed.
+     *
+     * @param sourceSettings The `AppSettings` instance to configure (not null).
+     * @param imageFile      The `URL` pointing to the image file to use as the title of the dialog;
+     *                       `null` will result in no image being displayed.
+     * @param loadSettings   If `true`, the dialog will copy settings from preferences. If `false`
+     *                       and preferences exist, they will be merged with the current settings.
+     * @return `true` if the user approved the settings, `false` otherwise (`CANCEL_SELECTION` or dialog close).
+     */
     public static boolean showDialog(AppSettings sourceSettings, URL imageFile, boolean loadSettings) {
         if (SwingUtilities.isEventDispatchThread()) {
             throw new IllegalStateException("Cannot run from EDT");
@@ -166,46 +222,47 @@ public final class AWTSettingsDialog extends JFrame {
         synchronized (lock) {
             while (!done.get()) {
                 try {
+                    // Wait until notified by the selection listener
                     lock.wait();
                 } catch (InterruptedException ex) {
+                    Thread.currentThread().interrupt();
+                    logger.log(Level.WARNING, "Settings dialog thread interrupted while waiting.", ex);
+                    return false; // Treat as cancel if interrupted
                 }
             }
         }
 
-        sourceSettings.copyFrom(settings);
+        // If approved, copy the modified settings back to the original source
+        if (result.get() == APPROVE_SELECTION) {
+            sourceSettings.copyFrom(settings);
+        }
 
-        return result.get() == AWTSettingsDialog.APPROVE_SELECTION;
+        return result.get() == APPROVE_SELECTION;
     }
 
     /**
-     * Instantiate a <code>SettingsDialog</code> for the primary display.
+     * Constructs a `SettingsDialog` for the primary display.
      *
-     * @param source
-     *            the <code>AppSettings</code> (not null)
-     * @param imageFile
-     *            the image file to use as the title of the dialog;
-     *            <code>null</code> will result in to image being displayed
-     * @param loadSettings
-     *            if true, copy the settings, otherwise merge them
-     * @throws IllegalArgumentException
-     *             if the source is <code>null</code>
+     * @param source       The `AppSettings` instance to configure (not null).
+     * @param imageFile    The path to the image file to use as the title of the dialog;
+     *                     `null` will result in no image being displayed.
+     * @param loadSettings If `true`, the dialog will copy settings from preferences. If `false`
+     *                     and preferences exist, they will be merged with the current settings.
+     * @throws IllegalArgumentException if `source` is `null`.
      */
     protected AWTSettingsDialog(AppSettings source, String imageFile, boolean loadSettings) {
         this(source, getURL(imageFile), loadSettings);
     }
 
     /**
-     * /** Instantiate a <code>SettingsDialog</code> for the primary display.
+     * Constructs a `SettingsDialog` for the primary display.
      *
-     * @param source
-     *            the <code>AppSettings</code> object (not null)
-     * @param imageFile
-     *            the image file to use as the title of the dialog;
-     *            <code>null</code> will result in to image being displayed
-     * @param loadSettings
-     *            if true, copy the settings, otherwise merge them
-     * @throws IllegalArgumentException
-     *             if the source is <code>null</code>
+     * @param source       The `AppSettings` instance to configure (not null).
+     * @param imageFile    The `URL` pointing to the image file to use as the title of the dialog;
+     *                     `null` will result in no image being displayed.
+     * @param loadSettings If `true`, the dialog will copy settings from preferences. If `false`
+     *                     and preferences exist, they will be merged with the current settings.
+     * @throws IllegalArgumentException if `source` is `null`.
      */
     protected AWTSettingsDialog(AppSettings source, URL imageFile, boolean loadSettings) {
         if (source == null) {
@@ -232,7 +289,10 @@ public final class AWTSettingsDialog extends JFrame {
         minHeight = source.getMinHeight();
 
         try {
+            logger.log(Level.INFO, "Loading AppSettings from PreferenceKey: {0}", appTitle);
             registrySettings.load(appTitle);
+            AppSettings.printPreferences(appTitle);
+
         } catch (BackingStoreException ex) {
             logger.log(Level.WARNING, "Failed to load settings", ex);
         }
@@ -355,8 +415,6 @@ public final class AWTSettingsDialog extends JFrame {
      * <code>init</code> creates the components to use the dialog.
      */
     private void createUI() {
-        GridBagConstraints gbc;
-
         JPanel mainPanel = new JPanel(new GridBagLayout());
 
         addWindowListener(new WindowAdapter() {
@@ -368,8 +426,9 @@ public final class AWTSettingsDialog extends JFrame {
             }
         });
 
-        if (source.getIcons() != null) {
-            safeSetIconImages(Arrays.asList((BufferedImage[]) source.getIcons()));
+        Object[] sourceIcons = source.getIcons();
+        if (sourceIcons != null && sourceIcons.length > 0) {
+            safeSetIconImages(Arrays.asList((BufferedImage[]) sourceIcons));
         }
 
         setTitle(MessageFormat.format(resourceBundle.getString("frame.title"), source.getTitle()));
@@ -419,7 +478,7 @@ public final class AWTSettingsDialog extends JFrame {
         gammaBox = new JCheckBox(resourceBundle.getString("checkbox.gamma"));
         gammaBox.setSelected(source.isGammaCorrection());
 
-        gbc = new GridBagConstraints();
+        GridBagConstraints gbc = new GridBagConstraints();
         gbc.weightx = 0.5;
         gbc.gridx = 0;
         gbc.gridwidth = 2;
@@ -493,7 +552,6 @@ public final class AWTSettingsDialog extends JFrame {
         // Set the button action listeners. Cancel disposes without saving, OK
         // saves.
         ok.addActionListener(new ActionListener() {
-
             @Override
             public void actionPerformed(ActionEvent e) {
                 if (verifyAndSaveCurrentSelection()) {
@@ -501,12 +559,13 @@ public final class AWTSettingsDialog extends JFrame {
                     dispose();
 
                     // System.gc() should be called to prevent "X Error of
-                    // failed request: RenderBadPicture (invalid Picture
-                    // parameter)"
+                    // failed request: RenderBadPicture (invalid Picture parameter)"
                     // on Linux when using AWT/Swing + GLFW.
                     // For more info see:
                     // https://github.com/LWJGL/lwjgl3/issues/149,
-                    // https://hub.jmonkeyengine.org/t/experimenting-lwjgl3/37275
+
+                    //  intentional double call. see this discussion:
+                    //  https://hub.jmonkeyengine.org/t/experimenting-lwjgl3/37275/12
                     System.gc();
                     System.gc();
                 }
@@ -514,7 +573,6 @@ public final class AWTSettingsDialog extends JFrame {
         });
 
         cancel.addActionListener(new ActionListener() {
-
             @Override
             public void actionPerformed(ActionEvent e) {
                 setUserSelection(CANCEL_SELECTION);
@@ -568,7 +626,6 @@ public final class AWTSettingsDialog extends JFrame {
                 colorDepthCombo.setSelectedItem(source.getBitsPerPixel() + " bpp");
             }
         });
-
     }
 
     /*
@@ -577,10 +634,8 @@ public final class AWTSettingsDialog extends JFrame {
      */
     private void safeSetIconImages(List<? extends Image> icons) {
         try {
-            // Due to Java bug 6445278, we try to set icon on our shared owner
-            // frame first.
-            // Otherwise, our alt-tab icon will be the Java default under
-            // Windows.
+            // Due to Java bug 6445278, we try to set icon on our shared owner frame first.
+            // Otherwise, our alt-tab icon will be the Java default under Windows.
             Window owner = getOwner();
             if (owner != null) {
                 Method setIconImages = owner.getClass().getMethod("setIconImages", List.class);
@@ -608,9 +663,9 @@ public final class AWTSettingsDialog extends JFrame {
         boolean vsync = vsyncBox.isSelected();
         boolean gamma = gammaBox.isSelected();
 
-        int width = Integer.parseInt(display.substring(0, display.indexOf(" x ")));
-        display = display.substring(display.indexOf(" x ") + 3);
-        int height = Integer.parseInt(display);
+        String[] parts = display.split(" x ");
+        int width = Integer.parseInt(parts[0]);
+        int height = Integer.parseInt(parts[1]);
 
         String depthString = (String) colorDepthCombo.getSelectedItem();
         int depth = -1;
@@ -639,21 +694,20 @@ public final class AWTSettingsDialog extends JFrame {
         }
 
         // FIXME: Does not work in Linux
-        /*
-         * if (!fullscreen) { //query the current bit depth of the desktop int
-         * curDepth = GraphicsEnvironment.getLocalGraphicsEnvironment()
-         * .getDefaultScreenDevice().getDisplayMode().getBitDepth(); if (depth >
-         * curDepth) { showError(this,"Cannot choose a higher bit depth in
-         * windowed " + "mode than your current desktop bit depth"); return
-         * false; } }
-         */
-
-        boolean valid = false;
+//        if (!fullscreen) { //query the current bit depth of the desktop int
+//            curDepth = GraphicsEnvironment.getLocalGraphicsEnvironment()
+//                    .getDefaultScreenDevice().getDisplayMode().getBitDepth();
+//            if (depth > curDepth) {
+//                showError(this, "Cannot choose a higher bit depth in
+//                        windowed" + "mode than your current desktop bit depth");
+//                return false;
+//            }
+//        }
+
+        boolean valid = true;
 
         // test valid display mode when going full screen
-        if (!fullscreen) {
-            valid = true;
-        } else {
+        if (fullscreen) {
             GraphicsDevice device = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice();
             valid = device.isFullScreenSupported();
         }
@@ -673,7 +727,10 @@ public final class AWTSettingsDialog extends JFrame {
             String appTitle = source.getTitle();
 
             try {
+                logger.log(Level.INFO, "Saving AppSettings to PreferencesKey: {0}", appTitle);
                 source.save(appTitle);
+                AppSettings.printPreferences(appTitle);
+
             } catch (BackingStoreException ex) {
                 logger.log(Level.WARNING, "Failed to save setting changes", ex);
             }
@@ -769,7 +826,9 @@ public final class AWTSettingsDialog extends JFrame {
     private void updateAntialiasChoices() {
         // maybe in the future will add support for determining this info
         // through PBuffer
-        String[] choices = new String[] { resourceBundle.getString("antialias.disabled"), "2x", "4x", "6x", "8x", "16x" };
+        String[] choices = new String[] {
+                resourceBundle.getString("antialias.disabled"), "2x", "4x", "6x", "8x", "16x"
+        };
         antialiasCombo.setModel(new DefaultComboBoxModel<>(choices));
         antialiasCombo.setSelectedItem(choices[Math.min(source.getSamples() / 2, 5)]);
     }
@@ -792,6 +851,12 @@ public final class AWTSettingsDialog extends JFrame {
         return url;
     }
 
+    /**
+     * Displays an error message dialog to the user.
+     *
+     * @param parent  The parent `Component` for the dialog.
+     * @param message The message `String` to display.
+     */
     private static void showError(java.awt.Component parent, String message) {
         JOptionPane.showMessageDialog(parent, message, "Error", JOptionPane.ERROR_MESSAGE);
     }
@@ -852,7 +917,7 @@ public final class AWTSettingsDialog extends JFrame {
      * Returns every possible bit depth for the given resolution.
      */
     private static String[] getDepths(String resolution, DisplayMode[] modes) {
-        List<String> depths = new ArrayList<>(4);
+        Set<String> depths = new LinkedHashSet<>(4); // Use LinkedHashSet for uniqueness and order
         for (DisplayMode mode : modes) {
             int bitDepth = mode.getBitDepth();
             if (bitDepth == DisplayMode.BIT_DEPTH_MULTI) {
@@ -865,12 +930,8 @@ public final class AWTSettingsDialog extends JFrame {
                 continue;
             }
             String res = mode.getWidth() + " x " + mode.getHeight();
-            if (!res.equals(resolution)) {
-                continue;
-            }
-            String depth = bitDepth + " bpp";
-            if (!depths.contains(depth)) {
-                depths.add(depth);
+            if (res.equals(resolution)) {
+                depths.add(bitDepth + " bpp");
             }
         }
 
@@ -884,10 +945,15 @@ public final class AWTSettingsDialog extends JFrame {
     }
 
     /**
-     * Returns every possible refresh rate for the given resolution.
+     * Returns every possible unique refresh rate string ("XX Hz" or "???")
+     * for the given resolution from an array of `DisplayMode`s.
+     *
+     * @param resolution The resolution string (e.g., "1280 x 720") to filter by.
+     * @param modes      The array of `DisplayMode`s to process.
+     * @return An array of unique refresh rate strings.
      */
     private static String[] getFrequencies(String resolution, DisplayMode[] modes) {
-        List<String> freqs = new ArrayList<>(4);
+        Set<String> freqs = new LinkedHashSet<>(4); // Use LinkedHashSet for uniqueness and order
         for (DisplayMode mode : modes) {
             String res = mode.getWidth() + " x " + mode.getHeight();
             String freq;
@@ -896,20 +962,19 @@ public final class AWTSettingsDialog extends JFrame {
             } else {
                 freq = mode.getRefreshRate() + " Hz";
             }
-            if (res.equals(resolution) && !freqs.contains(freq)) {
-                freqs.add(freq);
-            }
+            freqs.add(freq);
         }
 
         return freqs.toArray(new String[0]);
     }
 
     /**
-     * Chooses the closest frequency to 60 Hz.
-     * 
-     * @param resolution
-     * @param modes
-     * @return
+     * Chooses the closest known refresh rate to 60 Hz for a given resolution.
+     * If no known refresh rates are found for the resolution, returns `null`.
+     *
+     * @param resolution The resolution string (e.g., "1280 x 720") to find the best frequency for.
+     * @param modes      The array of `DisplayMode`s to search within.
+     * @return The best frequency string (e.g., "60 Hz") or `null` if no suitable frequency is found.
      */
     private static String getBestFrequency(String resolution, DisplayMode[] modes) {
         int closest = Integer.MAX_VALUE;

+ 119 - 39
jme3-core/src/main/java/com/jme3/system/AppSettings.java

@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2009-2022 jMonkeyEngine
+ * Copyright (c) 2009-2025 jMonkeyEngine
  * All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
@@ -39,6 +39,8 @@ import java.io.OutputStream;
 import java.util.HashMap;
 import java.util.Map;
 import java.util.Properties;
+import java.util.logging.Level;
+import java.util.logging.Logger;
 import java.util.prefs.BackingStoreException;
 import java.util.prefs.Preferences;
 
@@ -58,6 +60,8 @@ public final class AppSettings extends HashMap<String, Object> {
 
     private static final long serialVersionUID = 1L;
 
+    private static final Logger logger = Logger.getLogger(AppSettings.class.getName());
+
     private static final AppSettings defaults = new AppSettings(false);
 
     /**
@@ -508,12 +512,25 @@ public final class AppSettings extends HashMap<String, Object> {
      * @return the corresponding value, or 0 if not set
      */
     public int getInteger(String key) {
-        Integer i = (Integer) get(key);
-        if (i == null) {
-            return 0;
-        }
+        return getInteger(key, 0);
+    }
 
-        return i.intValue();
+    /**
+     * Get an integer from the settings.
+     * <p>
+     * If the key is not set, or the stored value is not an Integer, then the
+     * provided default value is returned.
+     *
+     * @param key the key of an integer setting
+     * @param defaultValue the value to return if the key is not found or the
+     * value is not an integer
+     */
+    public int getInteger(String key, int defaultValue) {
+        Object val = get(key);
+        if (val == null) {
+            return defaultValue;
+        }
+        return (Integer) val;
     }
 
     /**
@@ -525,12 +542,25 @@ public final class AppSettings extends HashMap<String, Object> {
      * @return the corresponding value, or false if not set
      */
     public boolean getBoolean(String key) {
-        Boolean b = (Boolean) get(key);
-        if (b == null) {
-            return false;
-        }
+        return getBoolean(key, false);
+    }
 
-        return b.booleanValue();
+    /**
+     * Get a boolean from the settings.
+     * <p>
+     * If the key is not set, or the stored value is not a Boolean, then the
+     * provided default value is returned.
+     *
+     * @param key the key of a boolean setting
+     * @param defaultValue the value to return if the key is not found or the
+     * value is not a boolean
+     */
+    public boolean getBoolean(String key, boolean defaultValue) {
+        Object val = get(key);
+        if (val == null) {
+            return defaultValue;
+        }
+        return (Boolean) val;
     }
 
     /**
@@ -542,12 +572,25 @@ public final class AppSettings extends HashMap<String, Object> {
      * @return the corresponding value, or null if not set
      */
     public String getString(String key) {
-        String s = (String) get(key);
-        if (s == null) {
-            return null;
-        }
+        return getString(key, null);
+    }
 
-        return s;
+    /**
+     * Get a string from the settings.
+     * <p>
+     * If the key is not set, or the stored value is not a String, then the
+     * provided default value is returned.
+     *
+     * @param key the key of a string setting
+     * @param defaultValue the value to return if the key is not found or the
+     * value is not a string
+     */
+    public String getString(String key, String defaultValue) {
+        Object val = get(key);
+        if (val == null) {
+            return defaultValue;
+        }
+        return (String) val;
     }
 
     /**
@@ -559,12 +602,25 @@ public final class AppSettings extends HashMap<String, Object> {
      * @return the corresponding value, or 0 if not set
      */
     public float getFloat(String key) {
-        Float f = (Float) get(key);
-        if (f == null) {
-            return 0f;
-        }
+        return getFloat(key, 0f);
+    }
 
-        return f.floatValue();
+    /**
+     * Get a float from the settings.
+     * <p>
+     * If the key is not set, or the stored value is not a Float, then the
+     * provided default value is returned.
+     *
+     * @param key the key of a float setting
+     * @param defaultValue the value to return if the key is not found or the
+     * value is not a float
+     */
+    public float getFloat(String key, float defaultValue) {
+        Object val = get(key);
+        if (val == null) {
+            return defaultValue;
+        }
+        return (Float) val;
     }
 
     /**
@@ -574,7 +630,7 @@ public final class AppSettings extends HashMap<String, Object> {
      * @param value the desired integer value
      */
     public void putInteger(String key, int value) {
-        put(key, Integer.valueOf(value));
+        put(key, value);
     }
 
     /**
@@ -584,7 +640,7 @@ public final class AppSettings extends HashMap<String, Object> {
      * @param value the desired boolean value
      */
     public void putBoolean(String key, boolean value) {
-        put(key, Boolean.valueOf(value));
+        put(key, value);
     }
 
     /**
@@ -604,7 +660,7 @@ public final class AppSettings extends HashMap<String, Object> {
      * @param value the desired float value
      */
     public void putFloat(String key, float value) {
-        put(key, Float.valueOf(value));
+        put(key, value);
     }
 
     /**
@@ -699,9 +755,9 @@ public final class AppSettings extends HashMap<String, Object> {
     /**
      * Set the graphics renderer to use, one of:<br>
      * <ul>
-     * <li>AppSettings.LWJGL_OPENGL1 - Force OpenGL1.1 compatability</li>
-     * <li>AppSettings.LWJGL_OPENGL2 - Force OpenGL2 compatability</li>
-     * <li>AppSettings.LWJGL_OPENGL3 - Force OpenGL3.3 compatability</li>
+     * <li>AppSettings.LWJGL_OPENGL1 - Force OpenGL1.1 compatibility</li>
+     * <li>AppSettings.LWJGL_OPENGL2 - Force OpenGL2 compatibility</li>
+     * <li>AppSettings.LWJGL_OPENGL3 - Force OpenGL3.3 compatibility</li>
      * <li>AppSettings.LWJGL_OPENGL_ANY - Choose an appropriate
      * OpenGL version based on system capabilities</li>
      * <li>AppSettings.JOGL_OPENGL_BACKWARD_COMPATIBLE</li>
@@ -740,7 +796,7 @@ public final class AppSettings extends HashMap<String, Object> {
     }
 
     /**
-     * @param value the width for the default framebuffer.
+     * @param value the width for the default frame buffer.
      * (Default: 640)
      */
     public void setWidth(int value) {
@@ -748,7 +804,7 @@ public final class AppSettings extends HashMap<String, Object> {
     }
 
     /**
-     * @param value the height for the default framebuffer.
+     * @param value the height for the default frame buffer.
      * (Default: 480)
      */
     public void setHeight(int value) {
@@ -756,7 +812,7 @@ public final class AppSettings extends HashMap<String, Object> {
     }
 
     /**
-     * Set the resolution for the default framebuffer
+     * Set the resolution for the default frame buffer
      * Use {@link #setWindowSize(int, int)} instead, for HiDPI display support.
      * @param width The width
      * @param height The height
@@ -770,8 +826,8 @@ public final class AppSettings extends HashMap<String, Object> {
     /**
      * Set the size of the window
      *
-     * @param width The width in pixels (default = width of the default framebuffer)
-     * @param height The height in pixels (default = height of the default framebuffer)
+     * @param width The width in pixels (default = width of the default frame buffer)
+     * @param height The height in pixels (default = height of the default frame buffer)
      */
     public void setWindowSize(int width, int height) {
         putInteger("WindowWidth", width);
@@ -961,7 +1017,7 @@ public final class AppSettings extends HashMap<String, Object> {
     /**
      * Enable or disable gamma correction. If enabled, the main framebuffer will
      * be configured for sRGB colors, and sRGB images will be linearized.
-     *
+     * <p>
      * Gamma correction requires a GPU that supports GL_ARB_framebuffer_sRGB;
      * otherwise this setting will be ignored.
      *
@@ -972,7 +1028,7 @@ public final class AppSettings extends HashMap<String, Object> {
     }
 
     /**
-     * Get the framerate.
+     * Get the frame rate.
      *
      * @return the maximum rate (in frames per second), or -1 for unlimited
      * @see #setFrameRate(int)
@@ -1005,7 +1061,7 @@ public final class AppSettings extends HashMap<String, Object> {
     /**
      * Get the width
      *
-     * @return the width of the default framebuffer (in pixels)
+     * @return the width of the default frame buffer (in pixels)
      * @see #setWidth(int)
      */
     public int getWidth() {
@@ -1015,7 +1071,7 @@ public final class AppSettings extends HashMap<String, Object> {
     /**
      * Get the height
      *
-     * @return the height of the default framebuffer (in pixels)
+     * @return the height of the default frame buffer (in pixels)
      * @see #setHeight(int)
      */
     public int getHeight() {
@@ -1216,7 +1272,7 @@ public final class AppSettings extends HashMap<String, Object> {
 
     /**
      * Allows the display window to be resized by dragging its edges.
-     *
+     * <p>
      * Only supported for {@link JmeContext.Type#Display} contexts which
      * are in windowed mode, ignored for other types.
      * The default value is <code>false</code>.
@@ -1241,7 +1297,7 @@ public final class AppSettings extends HashMap<String, Object> {
 
     /**
      * When enabled the display context will swap buffers every frame.
-     *
+     * <p>
      * This may need to be disabled when integrating with an external
      * library that handles buffer swapping on its own, e.g. Oculus Rift.
      * When disabled, the engine will process window messages
@@ -1283,7 +1339,7 @@ public final class AppSettings extends HashMap<String, Object> {
     /**
      * Sets a custom platform chooser. This chooser specifies which platform and
      * which devices are used for the OpenCL context.
-     *
+     * <p>
      * Default: an implementation defined one.
      *
      * @param chooser the class of the chooser, must have a default constructor
@@ -1509,6 +1565,30 @@ public final class AppSettings extends HashMap<String, Object> {
         putInteger("Display", mon);
     }
 
+    /**
+     * Prints all key-value pairs stored under a given preferences key
+     * in the Java Preferences API to standard output.
+     *
+     * @param preferencesKey The preferences key (node path) to inspect.
+     * @throws BackingStoreException If an exception occurs while accessing the preferences.
+     */
+    public static void printPreferences(String preferencesKey) throws BackingStoreException {
+        Preferences prefs = Preferences.userRoot().node(preferencesKey);
+        String[] keys = prefs.keys();
+
+        if (keys == null || keys.length == 0) {
+            logger.log(Level.WARNING, "No Preferences found under key: {0}", preferencesKey);
+        } else {
+            StringBuilder sb = new StringBuilder();
+            sb.append("Preferences for key: ").append(preferencesKey);
+            for (String key : keys) {
+                // Retrieve the value as a String (default fallback for Preferences API)
+                String value = prefs.get(key, "[Value Not Found]");
+                sb.append("\n * ").append(key).append(" = ").append(value);
+            }
+            logger.log(Level.INFO, sb.toString());
+        }
+    }
     /**
      * Sets the preferred native platform for creating the GL context on Linux distributions.
      * <p>