diff --git a/jme3-awt-dialogs/src/main/java/com/jme3/awt/AWTSettingsDialog.java b/jme3-awt-dialogs/src/main/java/com/jme3/awt/AWTSettingsDialog.java
index 34744b2298..efbb721054 100644
--- a/jme3-awt-dialogs/src/main/java/com/jme3/awt/AWTSettingsDialog.java
+++ b/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 javax.swing.*;
/**
- * SettingsDialog displays a Swing dialog box to interactively
- * configure the AppSettings of a desktop application before
- * start() is invoked.
- *
- * The AppSettings 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.
+ *
+ * The `AppSettings` instance to be configured is passed to the constructor.
*
* @see AppSettings
* @author Mark Powell
@@ -71,14 +70,33 @@
*/
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 static interface SelectionListener {
// 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 static interface SelectionListener {
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 static boolean showDialog(AppSettings sourceSettings, boolean loadSetting
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 void onSelection(int selection) {
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
+ * 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 int getInteger(String key) {
* @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.
+ *
+ * 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 boolean getBoolean(String key) {
* @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.
+ *
+ * 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 String getString(String key) {
* @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.
+ *
+ * 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 float getFloat(String key) {
* @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 void putInteger(String key, int value) {
* @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 void putString(String key, String value) {
* @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 void setUseJoysticks(boolean use) {
/**
* Set the graphics renderer to use, one of:
* Gamma correction requires a GPU that supports GL_ARB_framebuffer_sRGB;
* otherwise this setting will be ignored.
*
@@ -972,7 +1028,7 @@ public void setGammaCorrection(boolean gammaCorrection) {
}
/**
- * 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 String getRenderer() {
/**
* 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 int getWidth() {
/**
* 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 boolean isGammaCorrection() {
/**
* Allows the display window to be resized by dragging its edges.
- *
+ *
* Only supported for {@link JmeContext.Type#Display} contexts which
* are in windowed mode, ignored for other types.
* The default value is
* 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 boolean isOpenCLSupport() {
/**
* Sets a custom platform chooser. This chooser specifies which platform and
* which devices are used for the OpenCL context.
- *
+ *
* Default: an implementation defined one.
*
* @param chooser the class of the chooser, must have a default constructor
@@ -1509,6 +1565,30 @@ public void setDisplay(int mon) {
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.
*
SettingsDialog for the primary display.
+ * Constructs a `SettingsDialog` for the primary display.
*
- * @param source
- * the AppSettings (not null)
- * @param imageFile
- * the image file to use as the title of the dialog;
- * null will result in to image being displayed
- * @param loadSettings
- * if true, copy the settings, otherwise merge them
- * @throws IllegalArgumentException
- * if the source is null
+ * @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 SettingsDialog for the primary display.
+ * Constructs a `SettingsDialog` for the primary display.
*
- * @param source
- * the AppSettings object (not null)
- * @param imageFile
- * the image file to use as the title of the dialog;
- * null will result in to image being displayed
- * @param loadSettings
- * if true, copy the settings, otherwise merge them
- * @throws IllegalArgumentException
- * if the source is null
+ * @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 @@ protected AWTSettingsDialog(AppSettings source, URL imageFile, boolean loadSetti
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 void showDialog() {
* init 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 void windowClosing(WindowEvent e) {
}
});
- 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 void actionPerformed(ActionEvent e) {
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 void actionPerformed(ActionEvent e) {
// 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 void actionPerformed(ActionEvent e) {
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 void actionPerformed(ActionEvent e) {
});
cancel.addActionListener(new ActionListener() {
-
@Override
public void actionPerformed(ActionEvent e) {
setUserSelection(CANCEL_SELECTION);
@@ -568,7 +626,6 @@ public void run() {
colorDepthCombo.setSelectedItem(source.getBitsPerPixel() + " bpp");
}
});
-
}
/*
@@ -577,10 +634,8 @@ public void run() {
*/
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 @@ private boolean verifyAndSaveCurrentSelection() {
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 @@ private boolean verifyAndSaveCurrentSelection() {
}
// 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 @@ private boolean verifyAndSaveCurrentSelection() {
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 @@ private void updateResolutionChoices() {
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 @@ private static URL getURL(String file) {
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 @@ private String[] getWindowedResolutions(DisplayMode[] modes) {
* Returns every possible bit depth for the given resolution.
*/
private static String[] getDepths(String resolution, DisplayMode[] modes) {
- List
*
- *
false.
@@ -1241,7 +1297,7 @@ public boolean isResizable() {
/**
* When enabled the display context will swap buffers every frame.
- *
+ *