Bladeren bron

move SettingsDialog and ErrorDialog to new jme3-awt-dialogs module (#1876)

* Refactory Settings/Error dialogs in JmeDialogsFactory and jme3-awt-dialogs

* add build.gradle

* Add copyright headers

* Fix formatting and documentation

Co-authored-by: riccardobl <[email protected]>
Stephen Gold 2 jaren geleden
bovenliggende
commit
9e54d443f5

+ 14 - 18
jme3-android/src/main/java/com/jme3/system/android/JmeAndroidSystem.java

@@ -17,6 +17,8 @@ import com.jme3.audio.openal.EFX;
 import com.jme3.system.*;
 import com.jme3.system.JmeContext.Type;
 import com.jme3.util.AndroidScreenshots;
+import com.jme3.util.functional.VoidFunction;
+
 import java.io.File;
 import java.io.IOException;
 import java.io.OutputStream;
@@ -35,6 +37,18 @@ public class JmeAndroidSystem extends JmeSystemDelegate {
         } catch (UnsatisfiedLinkError e) {
         }
     }
+
+    public JmeAndroidSystem(){
+        setErrorMessageHandler((message) -> {
+            String finalMsg = message;
+            String finalTitle = "Error in application";
+            Context context = JmeAndroidSystem.getView().getContext();
+            view.getHandler().post(() -> {
+                AlertDialog dialog = new AlertDialog.Builder(context).setTitle(finalTitle).setMessage(finalMsg).create();
+                dialog.show();
+            });
+        });
+    }
     
     @Override
     public URL getPlatformAssetConfigURL() {
@@ -57,26 +71,8 @@ public class JmeAndroidSystem extends JmeSystemDelegate {
         bitmapImage.recycle();
     }
 
-    @Override
-    public void showErrorDialog(String message) {
-        final String finalMsg = message;
-        final String finalTitle = "Error in application";
-        final Context context = JmeAndroidSystem.getView().getContext();
 
-        view.getHandler().post(new Runnable() {
-            @Override
-            public void run() {
-                AlertDialog dialog = new AlertDialog.Builder(context)
-                        .setTitle(finalTitle).setMessage(finalMsg).create();
-                dialog.show();
-            }
-        });
-    }
 
-    @Override
-    public boolean showSettingsDialog(AppSettings sourceSettings, boolean loadFromRegistry) {
-        return true;
-    }
 
     @Override
     public JmeContext newContext(AppSettings settings, Type contextType) {

+ 3 - 0
jme3-awt-dialogs/build.gradle

@@ -0,0 +1,3 @@
+dependencies {
+    api project(':jme3-core')
+}

+ 98 - 0
jme3-awt-dialogs/src/main/java/com/jme3/awt/AWTErrorDialog.java

@@ -0,0 +1,98 @@
+/*
+ * Copyright (c) 2009-2022 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.awt;
+
+import java.awt.BorderLayout;
+import java.awt.Container;
+import java.awt.Dimension;
+import java.awt.Insets;
+import java.awt.event.ActionEvent;
+import javax.swing.AbstractAction;
+import javax.swing.JButton;
+import javax.swing.JDialog;
+import javax.swing.JScrollPane;
+import javax.swing.JTextArea;
+
+/**
+ * Simple dialog for displaying error messages,
+ * 
+ * @author kwando
+ */
+public class AWTErrorDialog extends JDialog {
+    public static String DEFAULT_TITLE = "Error in application";
+    public static int PADDING = 8;
+    
+    /**
+     * Create a new Dialog with a title and a message.
+     *
+     * @param message the message to display
+     * @param title the title to display
+     */
+    protected AWTErrorDialog(String message, String title) {
+        setTitle(title);
+        setSize(new Dimension(600, 400));
+        setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
+        setLocationRelativeTo(null);   
+        
+        Container container = getContentPane();
+        container.setLayout(new BorderLayout());
+        
+        JTextArea textArea = new JTextArea();
+        textArea.setText(message);
+        textArea.setEditable(false);
+        textArea.setMargin(new Insets(PADDING, PADDING, PADDING, PADDING));
+        add(new JScrollPane(textArea), BorderLayout.CENTER);
+        
+        final JDialog dialog = this;
+        JButton button = new JButton(new AbstractAction("OK"){
+            @Override
+            public void actionPerformed(ActionEvent e) {
+                dialog.dispose();
+            }
+        });
+        add(button, BorderLayout.SOUTH);
+    }
+    
+    protected AWTErrorDialog(String message){
+        this(message, DEFAULT_TITLE);
+    }
+    
+    /**
+     * Show a dialog with the provided message.
+     *
+     * @param message the message to display
+     */
+    public static void showDialog(String message) {
+        AWTErrorDialog dialog = new AWTErrorDialog(message);
+        dialog.setVisible(true);
+    }
+}

+ 172 - 110
jme3-desktop/src/main/java/com/jme3/app/SettingsDialog.java → jme3-awt-dialogs/src/main/java/com/jme3/awt/AWTSettingsDialog.java

@@ -29,9 +29,12 @@
  * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
-package com.jme3.app;
+package com.jme3.awt;
 
+import com.jme3.asset.AssetNotFoundException;
 import com.jme3.system.AppSettings;
+import com.jme3.system.JmeSystem;
+
 import java.awt.*;
 import java.awt.event.*;
 import java.awt.image.BufferedImage;
@@ -46,6 +49,8 @@ import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.ResourceBundle;
 import java.util.Set;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicInteger;
 import java.util.logging.Level;
 import java.util.logging.Logger;
 import java.util.prefs.BackingStoreException;
@@ -64,24 +69,23 @@ import javax.swing.*;
  * @author Eric Woroshow
  * @author Joshua Slack - reworked for proper use of GL commands.
  */
-public final class SettingsDialog extends JFrame {
+public final class AWTSettingsDialog extends JFrame {
 
     public static interface SelectionListener {
 
         public void onSelection(int selection);
     }
-    private static final Logger logger = Logger.getLogger(SettingsDialog.class.getName());
+
+    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;
-    
+    public static final int NO_SELECTION = 0, APPROVE_SELECTION = 1, CANCEL_SELECTION = 2;
+
     // Resource bundle for i18n.
     ResourceBundle resourceBundle = ResourceBundle.getBundle("com.jme3.app/SettingsDialog");
-    
+
     // the instance being configured
     private final AppSettings source;
-    
+
     // Title Image
     private URL imageFile = null;
     // Array of supported display modes
@@ -109,7 +113,70 @@ public final class SettingsDialog extends JFrame {
 
     private int minWidth = 0;
     private int minHeight = 0;
-    
+
+    public static boolean showDialog(AppSettings sourceSettings) {
+        return showDialog(sourceSettings, true);
+    }
+
+    public static boolean showDialog(AppSettings sourceSettings, boolean loadSettings) {
+        String iconPath = sourceSettings.getSettingsDialogImage();
+        final URL iconUrl = JmeSystem.class.getResource(iconPath.startsWith("/") ? iconPath : "/" + iconPath);
+        if (iconUrl == null) {
+            throw new AssetNotFoundException(sourceSettings.getSettingsDialogImage());
+        }
+        return showDialog(sourceSettings, iconUrl, loadSettings);
+    }
+
+    public static boolean showDialog(AppSettings sourceSettings, String imageFile, boolean loadSettings) {
+        return showDialog(sourceSettings, getURL(imageFile), loadSettings);
+    }
+
+    public static boolean showDialog(AppSettings sourceSettings, URL imageFile, boolean loadSettings) {
+        if (SwingUtilities.isEventDispatchThread()) {
+            throw new IllegalStateException("Cannot run from EDT");
+        }
+        if (GraphicsEnvironment.isHeadless()) {
+            throw new IllegalStateException("Cannot show dialog in headless environment");
+        }
+
+        AppSettings settings = new AppSettings(false);
+        settings.copyFrom(sourceSettings);
+
+        Object lock = new Object();
+        AtomicBoolean done = new AtomicBoolean();
+        AtomicInteger result = new AtomicInteger();
+        SwingUtilities.invokeLater(new Runnable() {
+            @Override
+            public void run() {
+                final SelectionListener selectionListener = new SelectionListener() {
+                    @Override
+                    public void onSelection(int selection) {
+                        synchronized (lock) {
+                            done.set(true);
+                            result.set(selection);
+                            lock.notifyAll();
+                        }
+                    }
+                };
+                AWTSettingsDialog dialog = new AWTSettingsDialog(settings, imageFile, loadSettings);
+                dialog.setSelectionListener(selectionListener);
+                dialog.showDialog();
+            }
+        });
+        synchronized (lock) {
+            while (!done.get()) {
+                try {
+                    lock.wait();
+                } catch (InterruptedException ex) {
+                }
+            }
+        }
+
+        sourceSettings.copyFrom(settings);
+
+        return result.get() == AWTSettingsDialog.APPROVE_SELECTION;
+    }
+
     /**
      * Instantiate a <code>SettingsDialog</code> for the primary display.
      *
@@ -123,12 +190,12 @@ public final class SettingsDialog extends JFrame {
      * @throws IllegalArgumentException
      *             if the source is <code>null</code>
      */
-    public SettingsDialog(AppSettings source, String imageFile, boolean loadSettings) {
+    protected AWTSettingsDialog(AppSettings source, String imageFile, boolean loadSettings) {
         this(source, getURL(imageFile), loadSettings);
     }
 
     /**
-     * Instantiate a <code>SettingsDialog</code> for the primary display.
+     * /** Instantiate a <code>SettingsDialog</code> for the primary display.
      *
      * @param source
      *            the <code>AppSettings</code> object (not null)
@@ -140,7 +207,7 @@ public final class SettingsDialog extends JFrame {
      * @throws IllegalArgumentException
      *             if the source is <code>null</code>
      */
-    public SettingsDialog(AppSettings source, URL imageFile, boolean loadSettings) {
+    protected AWTSettingsDialog(AppSettings source, URL imageFile, boolean loadSettings) {
         if (source == null) {
             throw new IllegalArgumentException("Settings source cannot be null");
         }
@@ -148,32 +215,31 @@ public final class SettingsDialog extends JFrame {
         this.source = source;
         this.imageFile = imageFile;
 
-        //setModal(true);
+        // setModal(true);
         setAlwaysOnTop(true);
         setResizable(false);
 
         AppSettings registrySettings = new AppSettings(true);
 
         String appTitle;
-        if(source.getTitle()!=null){
+        if (source.getTitle() != null) {
             appTitle = source.getTitle();
-        }else{
-           appTitle = registrySettings.getTitle();
+        } else {
+            appTitle = registrySettings.getTitle();
         }
-        
+
         minWidth = source.getMinWidth();
         minHeight = source.getMinHeight();
-        
+
         try {
             registrySettings.load(appTitle);
         } catch (BackingStoreException ex) {
-            logger.log(Level.WARNING,
-                    "Failed to load settings", ex);
+            logger.log(Level.WARNING, "Failed to load settings", ex);
         }
 
         if (loadSettings) {
             source.copyFrom(registrySettings);
-        } else if(!registrySettings.isEmpty()) {
+        } else if (!registrySettings.isEmpty()) {
             source.mergeFrom(registrySettings);
         }
 
@@ -183,17 +249,13 @@ public final class SettingsDialog extends JFrame {
         Arrays.sort(modes, new DisplayModeSorter());
 
         DisplayMode[] merged = new DisplayMode[modes.length + windowDefaults.length];
-        
+
         int wdIndex = 0;
         int dmIndex = 0;
         int mergedIndex;
-        
-        for (mergedIndex = 0;
-                mergedIndex<merged.length 
-                && (wdIndex < windowDefaults.length
-                    || dmIndex < modes.length);
-                mergedIndex++) {
-            
+
+        for (mergedIndex = 0; mergedIndex < merged.length && (wdIndex < windowDefaults.length || dmIndex < modes.length); mergedIndex++) {
+
             if (dmIndex >= modes.length) {
                 merged[mergedIndex] = windowDefaults[wdIndex++];
             } else if (wdIndex >= windowDefaults.length) {
@@ -213,13 +275,13 @@ public final class SettingsDialog extends JFrame {
                 merged[mergedIndex] = windowDefaults[wdIndex++];
             }
         }
-        
+
         if (merged.length == mergedIndex) {
             windowModes = merged;
         } else {
             windowModes = Arrays.copyOfRange(merged, 0, mergedIndex);
         }
-        
+
         createUI();
     }
 
@@ -252,9 +314,6 @@ public final class SettingsDialog extends JFrame {
         this.minHeight = minHeight;
     }
 
-    
-    
-    
     /**
      * <code>setImage</code> sets the background image of the dialog.
      * 
@@ -266,7 +325,7 @@ public final class SettingsDialog extends JFrame {
             URL file = new URL("file:" + image);
             setImage(file);
         } catch (MalformedURLException e) {
-           logger.log(Level.WARNING, "Couldn’t read from file '" + image + "'", e);
+            logger.log(Level.WARNING, "Couldn’t read from file '" + image + "'", e);
         }
     }
 
@@ -283,21 +342,21 @@ public final class SettingsDialog extends JFrame {
     }
 
     /**
-     * <code>showDialog</code> sets this dialog as visible, and brings it to
-     * the front.
+     * <code>showDialog</code> sets this dialog as visible, and brings it to the
+     * front.
      */
     public void showDialog() {
         setLocationRelativeTo(null);
-        setVisible(true);       
+        setVisible(true);
         toFront();
     }
-   
+
     /**
      * <code>init</code> creates the components to use the dialog.
      */
     private void createUI() {
         GridBagConstraints gbc;
-        
+
         JPanel mainPanel = new JPanel(new GridBagLayout());
 
         addWindowListener(new WindowAdapter() {
@@ -310,13 +369,13 @@ public final class SettingsDialog extends JFrame {
         });
 
         if (source.getIcons() != null) {
-            safeSetIconImages( Arrays.asList((BufferedImage[]) source.getIcons()) );
+            safeSetIconImages(Arrays.asList((BufferedImage[]) source.getIcons()));
         }
 
         setTitle(MessageFormat.format(resourceBundle.getString("frame.title"), source.getTitle()));
-        
+
         // The buttons...
-        JButton ok = new JButton(resourceBundle.getString("button.ok"));               
+        JButton ok = new JButton(resourceBundle.getString("button.ok"));
         JButton cancel = new JButton(resourceBundle.getString("button.cancel"));
 
         icon = new JLabel(imageFile != null ? new ImageIcon(imageFile) : null);
@@ -330,8 +389,7 @@ public final class SettingsDialog extends JFrame {
                         setUserSelection(APPROVE_SELECTION);
                         dispose();
                     }
-                }
-                else if (e.getKeyCode() == KeyEvent.VK_ESCAPE) {
+                } else if (e.getKeyCode() == KeyEvent.VK_ESCAPE) {
                     setUserSelection(CANCEL_SELECTION);
                     dispose();
                 }
@@ -357,10 +415,10 @@ public final class SettingsDialog extends JFrame {
         });
         vsyncBox = new JCheckBox(resourceBundle.getString("checkbox.vsync"));
         vsyncBox.setSelected(source.isVSync());
-        
+
         gammaBox = new JCheckBox(resourceBundle.getString("checkbox.gamma"));
         gammaBox.setSelected(source.isGammaCorrection());
-        
+
         gbc = new GridBagConstraints();
         gbc.weightx = 0.5;
         gbc.gridx = 0;
@@ -370,20 +428,19 @@ public final class SettingsDialog extends JFrame {
         mainPanel.add(fullscreenBox, gbc);
         gbc = new GridBagConstraints();
         gbc.weightx = 0.5;
-      //  gbc.insets = new Insets(4, 16, 0, 4);
+        // gbc.insets = new Insets(4, 16, 0, 4);
         gbc.gridx = 2;
-      //  gbc.gridwidth = 2;
+        // gbc.gridwidth = 2;
         gbc.gridy = 1;
         gbc.anchor = GridBagConstraints.EAST;
         mainPanel.add(vsyncBox, gbc);
         gbc = new GridBagConstraints();
         gbc.weightx = 0.5;
         gbc.gridx = 3;
-        gbc.gridy = 1;       
+        gbc.gridy = 1;
         gbc.anchor = GridBagConstraints.WEST;
         mainPanel.add(gammaBox, gbc);
 
-        
         gbc = new GridBagConstraints();
         gbc.insets = new Insets(4, 4, 4, 4);
         gbc.gridx = 0;
@@ -432,7 +489,7 @@ public final class SettingsDialog extends JFrame {
         gbc.gridy = 3;
         gbc.anchor = GridBagConstraints.WEST;
         mainPanel.add(antialiasCombo, gbc);
-        
+
         // Set the button action listeners. Cancel disposes without saving, OK
         // saves.
         ok.addActionListener(new ActionListener() {
@@ -442,10 +499,14 @@ public final class SettingsDialog extends JFrame {
                 if (verifyAndSaveCurrentSelection()) {
                     setUserSelection(APPROVE_SELECTION);
                     dispose();
-                    
-                    // System.gc() should be called to prevent "X Error of 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
+
+                    // System.gc() should be called to prevent "X Error of
+                    // 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
                     System.gc();
                     System.gc();
                 }
@@ -466,7 +527,7 @@ public final class SettingsDialog extends JFrame {
         gbc.gridwidth = 2;
         gbc.gridy = 4;
         gbc.anchor = GridBagConstraints.EAST;
-        mainPanel.add(ok, gbc);        
+        mainPanel.add(ok, gbc);
         gbc = new GridBagConstraints();
         gbc.insets = new Insets(4, 16, 4, 4);
         gbc.gridx = 2;
@@ -484,35 +545,42 @@ public final class SettingsDialog extends JFrame {
         this.getContentPane().add(mainPanel);
 
         pack();
-        
+
         mainPanel.getRootPane().setDefaultButton(ok);
         SwingUtilities.invokeLater(new Runnable() {
 
             @Override
             public void run() {
-                // Fill in the combos once the window has opened so that the insets can be read.
-                // The assumption is made that the settings window and the display window will have the
-                // same insets as that is used to resize the "full screen windowed" mode appropriately.
+                // Fill in the combos once the window has opened so that the
+                // insets can be read.
+                // The assumption is made that the settings window and the
+                // display window will have the
+                // same insets as that is used to resize the "full screen
+                // windowed" mode appropriately.
                 updateResolutionChoices();
                 if (source.getWidth() != 0 && source.getHeight() != 0) {
-                    displayResCombo.setSelectedItem(source.getWidth() + " x "
-                            + source.getHeight());
+                    displayResCombo.setSelectedItem(source.getWidth() + " x " + source.getHeight());
                 } else {
-                    displayResCombo.setSelectedIndex(displayResCombo.getItemCount()-1);
+                    displayResCombo.setSelectedIndex(displayResCombo.getItemCount() - 1);
                 }
 
                 updateAntialiasChoices();
                 colorDepthCombo.setSelectedItem(source.getBitsPerPixel() + " bpp");
             }
-        });      
-        
+        });
+
     }
 
-    /* Access JDialog.setIconImages by reflection in case we're running on JRE < 1.6 */
+    /*
+     * Access JDialog.setIconImages by reflection in case we're running on JRE <
+     * 1.6
+     */
     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);
@@ -591,7 +659,7 @@ public final class SettingsDialog extends JFrame {
         }
 
         if (valid) {
-            //use the AppSettings class to save it to backing store
+            // use the AppSettings class to save it to backing store
             source.setWidth(width);
             source.setHeight(height);
             source.setBitsPerPixel(depth);
@@ -599,7 +667,7 @@ public final class SettingsDialog extends JFrame {
             source.setFullscreen(fullscreen);
             source.setVSync(vsync);
             source.setGammaCorrection(gamma);
-            //source.setRenderer(renderer);
+            // source.setRenderer(renderer);
             source.setSamples(multisample);
 
             String appTitle = source.getTitle();
@@ -607,13 +675,10 @@ public final class SettingsDialog extends JFrame {
             try {
                 source.save(appTitle);
             } catch (BackingStoreException ex) {
-                logger.log(Level.WARNING,
-                        "Failed to save setting changes", ex);
+                logger.log(Level.WARNING, "Failed to save setting changes", ex);
             }
         } else {
-            showError(
-                    this,
-                    resourceBundle.getString("error.unsupportedmode"));
+            showError(this, resourceBundle.getString("error.unsupportedmode"));
         }
 
         return valid;
@@ -623,7 +688,7 @@ public final class SettingsDialog extends JFrame {
      * <code>setUpChooser</code> retrieves all available display modes and
      * places them in a <code>JComboBox</code>. The resolution specified by
      * AppSettings is used as the default value.
-     *
+     * 
      * @return the combo box of display modes.
      */
     private JComboBox<String> setUpResolutionChooser() {
@@ -668,7 +733,7 @@ public final class SettingsDialog extends JFrame {
         displayFreqCombo.setModel(new DefaultComboBoxModel<>(freqs));
         // Try to reset freq
         displayFreqCombo.setSelectedItem(displayFreq);
-        
+
         if (!displayFreqCombo.getSelectedItem().equals(displayFreq)) {
             // Cannot find saved frequency in available frequencies.
             // Choose the closest one to 60 Hz.
@@ -684,21 +749,17 @@ public final class SettingsDialog extends JFrame {
      */
     private void updateResolutionChoices() {
         if (!fullscreenBox.isSelected()) {
-            displayResCombo.setModel(new DefaultComboBoxModel<>(
-                    getWindowedResolutions(windowModes)));
+            displayResCombo.setModel(new DefaultComboBoxModel<>(getWindowedResolutions(windowModes)));
             if (displayResCombo.getItemCount() > 0) {
-                displayResCombo.setSelectedIndex(displayResCombo.getItemCount()-1);
+                displayResCombo.setSelectedIndex(displayResCombo.getItemCount() - 1);
             }
-            colorDepthCombo.setModel(new DefaultComboBoxModel<>(new String[]{
-                        "24 bpp", "16 bpp"}));
-            displayFreqCombo.setModel(new DefaultComboBoxModel<>(
-                    new String[]{resourceBundle.getString("refresh.na")}));
+            colorDepthCombo.setModel(new DefaultComboBoxModel<>(new String[] { "24 bpp", "16 bpp" }));
+            displayFreqCombo.setModel(new DefaultComboBoxModel<>(new String[] { resourceBundle.getString("refresh.na") }));
             displayFreqCombo.setEnabled(false);
         } else {
-            displayResCombo.setModel(new DefaultComboBoxModel<>(
-                    getResolutions(modes, Integer.MAX_VALUE, Integer.MAX_VALUE)));
+            displayResCombo.setModel(new DefaultComboBoxModel<>(getResolutions(modes, Integer.MAX_VALUE, Integer.MAX_VALUE)));
             if (displayResCombo.getItemCount() > 0) {
-                displayResCombo.setSelectedIndex(displayResCombo.getItemCount()-1);
+                displayResCombo.setSelectedIndex(displayResCombo.getItemCount() - 1);
             }
             displayFreqCombo.setEnabled(true);
             updateDisplayChoices();
@@ -708,9 +769,9 @@ public final class SettingsDialog 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)]);
+        antialiasCombo.setSelectedItem(choices[Math.min(source.getSamples() / 2, 5)]);
     }
 
     //
@@ -732,19 +793,19 @@ public final class SettingsDialog extends JFrame {
     }
 
     private static void showError(java.awt.Component parent, String message) {
-        JOptionPane.showMessageDialog(parent, message, "Error",
-                JOptionPane.ERROR_MESSAGE);
+        JOptionPane.showMessageDialog(parent, message, "Error", JOptionPane.ERROR_MESSAGE);
     }
 
     /**
-     * Returns every unique resolution from an array of <code>DisplayMode</code>s
-     * where the resolution is greater than the configured minimums.
+     * Returns every unique resolution from an array of
+     * <code>DisplayMode</code>s where the resolution is greater than the
+     * configured minimums.
      */
     private String[] getResolutions(DisplayMode[] modes, int heightLimit, int widthLimit) {
         Insets insets = getInsets();
         heightLimit -= insets.top + insets.bottom;
         widthLimit -= insets.left + insets.right;
-        
+
         Set<String> resolutions = new LinkedHashSet<>(modes.length);
         for (DisplayMode mode : modes) {
             int height = mode.getHeight();
@@ -756,7 +817,7 @@ public final class SettingsDialog extends JFrame {
                 if (width >= widthLimit) {
                     width = widthLimit;
                 }
-                
+
                 String res = width + " x " + height;
                 resolutions.add(res);
             }
@@ -764,16 +825,17 @@ public final class SettingsDialog extends JFrame {
 
         return resolutions.toArray(new String[0]);
     }
-    
+
     /**
-     * Returns every unique resolution from an array of <code>DisplayMode</code>s
-     * where the resolution is greater than the configured minimums and the height
-     * is less than the current screen resolution.
+     * Returns every unique resolution from an array of
+     * <code>DisplayMode</code>s where the resolution is greater than the
+     * configured minimums and the height is less than the current screen
+     * resolution.
      */
     private String[] getWindowedResolutions(DisplayMode[] modes) {
         int maxHeight = 0;
         int maxWidth = 0;
-        
+
         for (DisplayMode mode : modes) {
             if (maxHeight < mode.getHeight()) {
                 maxHeight = mode.getHeight();
@@ -813,7 +875,8 @@ public final class SettingsDialog extends JFrame {
         }
 
         if (depths.isEmpty()) {
-            // add some default depths, possible system is multi-depth supporting
+            // add some default depths, possible system is multi-depth
+            // supporting
             depths.add("24 bpp");
         }
 
@@ -823,8 +886,7 @@ public final class SettingsDialog extends JFrame {
     /**
      * Returns every possible refresh rate for the given resolution.
      */
-    private static String[] getFrequencies(String resolution,
-            DisplayMode[] modes) {
+    private static String[] getFrequencies(String resolution, DisplayMode[] modes) {
         List<String> freqs = new ArrayList<>(4);
         for (DisplayMode mode : modes) {
             String res = mode.getWidth() + " x " + mode.getHeight();
@@ -841,13 +903,13 @@ public final class SettingsDialog extends JFrame {
 
         return freqs.toArray(new String[0]);
     }
-    
+
     /**
      * Chooses the closest frequency to 60 Hz.
      * 
      * @param resolution
      * @param modes
-     * @return 
+     * @return
      */
     private static String getBestFrequency(String resolution, DisplayMode[] modes) {
         int closest = Integer.MAX_VALUE;
@@ -861,7 +923,7 @@ public final class SettingsDialog extends JFrame {
                 }
             }
         }
-        
+
         if (closest != Integer.MAX_VALUE) {
             return closest + " Hz";
         } else {
@@ -870,8 +932,8 @@ public final class SettingsDialog extends JFrame {
     }
 
     /**
-     * Utility class for sorting <code>DisplayMode</code>s. Sorts by
-     * resolution, then bit depth, and then finally refresh rate.
+     * Utility class for sorting <code>DisplayMode</code>s. Sorts by resolution,
+     * then bit depth, and then finally refresh rate.
      */
     private class DisplayModeSorter implements Comparator<DisplayMode> {
 

+ 48 - 0
jme3-awt-dialogs/src/main/java/com/jme3/system/JmeDialogsFactoryImpl.java

@@ -0,0 +1,48 @@
+/*
+ * Copyright (c) 2009-2022 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.system;
+
+import com.jme3.awt.AWTErrorDialog;
+import com.jme3.awt.AWTSettingsDialog;
+
+public class JmeDialogsFactoryImpl implements JmeDialogsFactory {
+    
+    public boolean showSettingsDialog(AppSettings settings, boolean loadFromRegistry){
+        return AWTSettingsDialog.showDialog(settings,loadFromRegistry);
+    }
+
+    public void showErrorDialog(String message){
+        AWTErrorDialog.showDialog(message);
+    }
+    
+}

+ 2 - 2
jme3-core/src/main/java/com/jme3/app/LegacyApplication.java

@@ -667,10 +667,10 @@ public class LegacyApplication implements Application, SystemListener {
         // Display error message on screen if not in headless mode
         if (context.getType() != JmeContext.Type.Headless) {
             if (t != null) {
-                JmeSystem.showErrorDialog(errMsg + "\n" + t.getClass().getSimpleName()
+                JmeSystem.handleErrorMessage(errMsg + "\n" + t.getClass().getSimpleName()
                         + (t.getMessage() != null ? ": " + t.getMessage() : ""));
             } else {
-                JmeSystem.showErrorDialog(errMsg);
+                JmeSystem.handleErrorMessage(errMsg);
             }
         }
 

+ 51 - 0
jme3-core/src/main/java/com/jme3/system/JmeDialogsFactory.java

@@ -0,0 +1,51 @@
+/*
+ * Copyright (c) 2009-2022 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.system;
+
+public interface JmeDialogsFactory {
+    /**
+     * Set a function to handler app settings. 
+     * The default implementation shows a settings dialog if available.
+     * @param handler handler function that accepts as argument an instance of AppSettings 
+     * to transform and a boolean with the value of true if the settings are expected to be loaded from 
+     * the user registry. The handler function returns false if the configuration is interrupted (eg.the the dialog was closed)
+     * or true otherwise.
+     */
+    public boolean showSettingsDialog(AppSettings settings, boolean loadFromRegistry);    
+
+    /**
+     * Set function to handle errors. 
+     * The default implementation show a dialog if available.
+     * @param handler Consumer to which the error is passed as String
+     */
+    public void showErrorDialog(String message);  
+}

+ 35 - 5
jme3-core/src/main/java/com/jme3/system/JmeSystem.java

@@ -34,6 +34,7 @@ package com.jme3.system;
 import com.jme3.asset.AssetManager;
 import com.jme3.audio.AudioRenderer;
 import com.jme3.input.SoftTextDialogInput;
+
 import java.io.File;
 import java.io.IOException;
 import java.io.InputStream;
@@ -41,6 +42,8 @@ import java.io.OutputStream;
 import java.lang.reflect.InvocationTargetException;
 import java.net.URL;
 import java.nio.ByteBuffer;
+import java.util.function.BiFunction;
+import java.util.function.Consumer;
 import java.util.logging.Level;
 import java.util.logging.Logger;
 
@@ -154,10 +157,7 @@ public class JmeSystem {
         return systemDelegate.newAssetManager();
     }
 
-    public static boolean showSettingsDialog(AppSettings sourceSettings, final boolean loadFromRegistry) {
-        checkDelegate();
-        return systemDelegate.showSettingsDialog(sourceSettings, loadFromRegistry);
-    }
+   
 
     /**
      * Determine which Platform (operating system and architecture) the
@@ -190,14 +190,44 @@ public class JmeSystem {
      * feels is appropriate. If this is a headless or an offscreen surface
      * context, this method should do nothing.
      *
+     * @deprecated Use JmeSystem.handleErrorMessage(String) instead
      * @param message The error message to display. May contain new line
      * characters.
      */
+    @Deprecated
     public static void showErrorDialog(String message){
+        handleErrorMessage(message);
+    }
+
+    public static void handleErrorMessage(String message){
+        checkDelegate();
+        systemDelegate.handleErrorMessage(message);
+    }
+
+    public static void setErrorMessageHandler(Consumer<String> handler){
+        checkDelegate();
+        systemDelegate.setErrorMessageHandler(handler);
+    }
+
+
+    public static void handleSettings(AppSettings sourceSettings, boolean loadFromRegistry){
+        checkDelegate();
+        systemDelegate.handleSettings(sourceSettings, loadFromRegistry);
+    }
+
+    public static void setSettingsHandler(BiFunction<AppSettings,Boolean,Boolean> handler){
+        checkDelegate();
+        systemDelegate.setSettingsHandler(handler);
+    }
+
+
+    @Deprecated
+    public static boolean showSettingsDialog(AppSettings sourceSettings, final boolean loadFromRegistry) {
         checkDelegate();
-        systemDelegate.showErrorDialog(message);
+        return systemDelegate.showSettingsDialog(sourceSettings, loadFromRegistry);
     }
 
+
     public static void initialize(AppSettings settings) {
         checkDelegate();
         systemDelegate.initialize(settings);

+ 79 - 2
jme3-core/src/main/java/com/jme3/system/JmeSystemDelegate.java

@@ -35,14 +35,18 @@ import com.jme3.asset.AssetManager;
 import com.jme3.asset.DesktopAssetManager;
 import com.jme3.audio.AudioRenderer;
 import com.jme3.input.SoftTextDialogInput;
+
 import java.io.File;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
+import java.lang.reflect.InvocationTargetException;
 import java.net.URL;
 import java.nio.ByteBuffer;
 import java.util.EnumMap;
 import java.util.Map;
+import java.util.function.BiFunction;
+import java.util.function.Consumer;
 import java.util.logging.Level;
 import java.util.logging.Logger;
 
@@ -58,6 +62,32 @@ public abstract class JmeSystemDelegate {
     protected Map<JmeSystem.StorageFolderType, File> storageFolders = new EnumMap<>(JmeSystem.StorageFolderType.class);
     protected SoftTextDialogInput softTextDialogInput = null;
 
+    protected Consumer<String> errorMessageHandler = (message) -> {
+        JmeDialogsFactory dialogFactory = null;
+        try {
+             dialogFactory = (JmeDialogsFactory)Class.forName("com.jme3.system.JmeDialogsFactoryImpl").getConstructor().newInstance();
+        } catch(ClassNotFoundException e){
+            logger.warning("JmeDialogsFactory implementation not found.");    
+        } catch (InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException | NoSuchMethodException | SecurityException e) {
+            e.printStackTrace();
+        }
+        if(dialogFactory != null) dialogFactory.showErrorDialog(message);
+        else System.err.println(message);
+    };
+
+    protected BiFunction<AppSettings,Boolean,Boolean> settingsHandler = (settings,loadFromRegistry) -> {
+        JmeDialogsFactory dialogFactory = null;
+        try {
+            dialogFactory = (JmeDialogsFactory)Class.forName("com.jme3.system.JmeDialogsFactoryImpl").getConstructor().newInstance();
+        } catch(ClassNotFoundException e){
+            logger.warning("JmeDialogsFactory implementation not found.");    
+        } catch (InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException | NoSuchMethodException | SecurityException e) {
+            e.printStackTrace();
+        }
+        if(dialogFactory != null) return dialogFactory.showSettingsDialog(settings, loadFromRegistry);
+        return true;
+    };
+
     public synchronized File getStorageFolder(JmeSystem.StorageFolderType type) {
         File storageFolder = null;
 
@@ -133,9 +163,56 @@ public abstract class JmeSystemDelegate {
     
     public abstract void writeImageFile(OutputStream outStream, String format, ByteBuffer imageData, int width, int height) throws IOException;
 
-    public abstract void showErrorDialog(String message);
+    /**
+     * Set function to handle errors. 
+     * The default implementation show a dialog if available.
+     * @param handler Consumer to which the error is passed as String
+     */
+    public void setErrorMessageHandler(Consumer<String> handler){
+        errorMessageHandler = handler;
+    }
+
+    /**
+     * Internal use only: submit an error to the error message handler
+     */
+    public void handleErrorMessage(String message){
+        if(errorMessageHandler != null) errorMessageHandler.accept(message);
+    }
+
+    /**
+     * Set a function to handler app settings. 
+     * The default implementation shows a settings dialog if available.
+     * @param handler handler function that accepts as argument an instance of AppSettings 
+     * to transform and a boolean with the value of true if the settings are expected to be loaded from 
+     * the user registry. The handler function returns false if the configuration is interrupted (eg.the the dialog was closed)
+     * or true otherwise.
+     */
+    public void setSettingsHandler(BiFunction<AppSettings,Boolean, Boolean> handler){
+        settingsHandler = handler;
+    }
+
+    /**
+     * Internal use only: summon the settings handler
+     */
+    public boolean handleSettings(AppSettings settings, boolean loadFromRegistry){
+        if(settingsHandler != null) return settingsHandler.apply(settings,loadFromRegistry);
+        return true;
+    }
+
+    /**
+     * @deprecated Use JmeSystemDelegate.handleErrorMessage(String) instead
+     * @param message
+     */
+    @Deprecated
+    public void showErrorDialog(String message){
+        handleErrorMessage(message);
+    }
+
+    @Deprecated
+    public boolean showSettingsDialog(AppSettings settings, boolean loadFromRegistry){
+        return handleSettings(settings, loadFromRegistry);
+    }
 
-    public abstract boolean showSettingsDialog(AppSettings sourceSettings, boolean loadFromRegistry);
 
     private boolean is64Bit(String arch) {
         if (arch.equals("x86")) {

+ 0 - 9
jme3-core/src/test/java/com/jme3/system/MockJmeSystemDelegate.java

@@ -43,15 +43,6 @@ public class MockJmeSystemDelegate extends JmeSystemDelegate {
     public void writeImageFile(OutputStream outStream, String format, ByteBuffer imageData, int width, int height) throws IOException {
     }
 
-    @Override
-    public void showErrorDialog(String message) {
-    }
-
-    @Override
-    public boolean showSettingsDialog(AppSettings sourceSettings, boolean loadFromRegistry) {
-        return false;
-    }
-
     @Override
     public URL getPlatformAssetConfigURL() {
         return Thread.currentThread().getContextClassLoader().getResource("com/jme3/asset/General.cfg");

+ 0 - 67
jme3-desktop/src/main/java/com/jme3/system/ErrorDialog.java

@@ -1,67 +0,0 @@
-package com.jme3.system;
-
-import java.awt.BorderLayout;
-import java.awt.Container;
-import java.awt.Dimension;
-import java.awt.Insets;
-import java.awt.event.ActionEvent;
-import javax.swing.AbstractAction;
-import javax.swing.JButton;
-import javax.swing.JDialog;
-import javax.swing.JScrollPane;
-import javax.swing.JTextArea;
-
-/**
- * Simple dialog for displaying error messages,
- * 
- * @author kwando
- */
-public class ErrorDialog extends JDialog {
-    public static String DEFAULT_TITLE = "Error in application";
-    public static int PADDING = 8;
-    
-    /**
-     * Create a new Dialog with a title and a message.
-     *
-     * @param message the message to display
-     * @param title the title to display
-     */
-    public ErrorDialog(String message, String title) {
-        setTitle(title);
-        setSize(new Dimension(600, 400));
-        setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
-        setLocationRelativeTo(null);   
-        
-        Container container = getContentPane();
-        container.setLayout(new BorderLayout());
-        
-        JTextArea textArea = new JTextArea();
-        textArea.setText(message);
-        textArea.setEditable(false);
-        textArea.setMargin(new Insets(PADDING, PADDING, PADDING, PADDING));
-        add(new JScrollPane(textArea), BorderLayout.CENTER);
-        
-        final JDialog dialog = this;
-        JButton button = new JButton(new AbstractAction("OK"){
-            @Override
-            public void actionPerformed(ActionEvent e) {
-                dialog.dispose();
-            }
-        });
-        add(button, BorderLayout.SOUTH);
-    }
-    
-    public ErrorDialog(String message){
-        this(message, DEFAULT_TITLE);
-    }
-    
-    /**
-     * Show a dialog with the provided message.
-     *
-     * @param message the message to display
-     */
-    public static void showDialog(String message) {
-        ErrorDialog dialog = new ErrorDialog(message);
-        dialog.setVisible(true);
-    }
-}

+ 11 - 94
jme3-desktop/src/main/java/com/jme3/system/JmeDesktopSystem.java

@@ -31,9 +31,6 @@
  */
 package com.jme3.system;
 
-import com.jme3.app.SettingsDialog;
-import com.jme3.app.SettingsDialog.SelectionListener;
-import com.jme3.asset.AssetNotFoundException;
 import com.jme3.audio.AudioRenderer;
 import com.jme3.audio.openal.AL;
 import com.jme3.audio.openal.ALAudioRenderer;
@@ -42,13 +39,16 @@ import com.jme3.audio.openal.EFX;
 import com.jme3.system.JmeContext.Type;
 import com.jme3.texture.Image;
 import com.jme3.texture.image.ColorSpace;
-import com.jme3.util.Screenshots;
 import jme3tools.converters.ImageToAwt;
 
-import java.awt.EventQueue;
-import java.awt.Graphics2D;
-import java.awt.GraphicsEnvironment;
-import java.awt.RenderingHints;
+import javax.imageio.IIOImage;
+import javax.imageio.ImageIO;
+import javax.imageio.ImageWriteParam;
+import javax.imageio.ImageWriter;
+import javax.imageio.plugins.jpeg.JPEGImageWriteParam;
+import javax.imageio.stream.ImageOutputStream;
+import javax.imageio.stream.MemoryCacheImageOutputStream;
+import java.awt.*;
 import java.awt.geom.AffineTransform;
 import java.awt.image.AffineTransformOp;
 import java.awt.image.BufferedImage;
@@ -57,17 +57,7 @@ import java.io.OutputStream;
 import java.lang.reflect.InvocationTargetException;
 import java.net.URL;
 import java.nio.ByteBuffer;
-import java.util.concurrent.atomic.AtomicBoolean;
-import java.util.concurrent.atomic.AtomicInteger;
 import java.util.logging.Level;
-import javax.imageio.IIOImage;
-import javax.imageio.ImageIO;
-import javax.imageio.ImageWriteParam;
-import javax.imageio.ImageWriter;
-import javax.imageio.plugins.jpeg.JPEGImageWriteParam;
-import javax.imageio.stream.ImageOutputStream;
-import javax.imageio.stream.MemoryCacheImageOutputStream;
-import javax.swing.SwingUtilities;
 
 /**
  *
@@ -75,6 +65,9 @@ import javax.swing.SwingUtilities;
  */
 public class JmeDesktopSystem extends JmeSystemDelegate {
 
+    public JmeDesktopSystem() {
+    }
+
     @Override
     public URL getPlatformAssetConfigURL() {
         return Thread.currentThread().getContextClassLoader().getResource("com/jme3/asset/Desktop.cfg");
@@ -132,82 +125,6 @@ public class JmeDesktopSystem extends JmeSystemDelegate {
         }
     }
 
-    @Override
-    public void showErrorDialog(String message) {
-        if (!GraphicsEnvironment.isHeadless()) {
-            final String msg = message;
-            EventQueue.invokeLater(new Runnable() {
-                @Override
-                public void run() {
-                    ErrorDialog.showDialog(msg);
-                }
-            });
-        } else {
-            System.err.println("[JME ERROR] " + message);
-        }
-    }
-
-    @Override
-    public boolean showSettingsDialog(AppSettings sourceSettings, final boolean loadFromRegistry) {
-        if (SwingUtilities.isEventDispatchThread()) {
-            throw new IllegalStateException("Cannot run from EDT");
-        }
-        if (GraphicsEnvironment.isHeadless()) {
-            throw new IllegalStateException("Cannot show dialog in headless environment");
-        }
-
-        final AppSettings settings = new AppSettings(false);
-        settings.copyFrom(sourceSettings);
-        String iconPath = sourceSettings.getSettingsDialogImage();
-        if(iconPath == null){
-            iconPath = "";
-        }
-        final URL iconUrl = JmeSystem.class.getResource(iconPath.startsWith("/") ? iconPath : "/" + iconPath);
-        if (iconUrl == null) {
-            throw new AssetNotFoundException(sourceSettings.getSettingsDialogImage());
-        }
-
-        final AtomicBoolean done = new AtomicBoolean();
-        final AtomicInteger result = new AtomicInteger();
-        final Object lock = new Object();
-
-        final SelectionListener selectionListener = new SelectionListener() {
-
-            @Override
-            public void onSelection(int selection) {
-                synchronized (lock) {
-                    done.set(true);
-                    result.set(selection);
-                    lock.notifyAll();
-                }
-            }
-        };
-        SwingUtilities.invokeLater(new Runnable() {
-
-            @Override
-            public void run() {
-                synchronized (lock) {
-                    SettingsDialog dialog = new SettingsDialog(settings, iconUrl, loadFromRegistry);
-                    dialog.setSelectionListener(selectionListener);
-                    dialog.showDialog();
-                }
-            }
-        });
-
-        synchronized (lock) {
-            while (!done.get()) {
-                try {
-                    lock.wait();
-                } catch (InterruptedException ex) {
-                }
-            }
-        }
-
-        sourceSettings.copyFrom(settings);
-
-        return result.get() == SettingsDialog.APPROVE_SELECTION;
-    }
-
     @SuppressWarnings("unchecked")
     private JmeContext newContextLwjgl(AppSettings settings, JmeContext.Type type) {
         try {

+ 1 - 0
jme3-examples/build.gradle

@@ -25,6 +25,7 @@ dependencies {
     implementation project(':jme3-niftygui')
     implementation project(':jme3-plugins')
     implementation project(':jme3-terrain')
+    implementation project(':jme3-awt-dialogs')
     runtimeOnly project(':jme3-testdata')
 }
 

+ 10 - 9
jme3-ios/src/main/java/com/jme3/system/ios/JmeIosSystem.java

@@ -35,6 +35,7 @@ import com.jme3.system.AppSettings;
 import com.jme3.system.JmeContext;
 import com.jme3.system.JmeSystemDelegate;
 import com.jme3.system.NullContext;
+import com.jme3.util.functional.VoidFunction;
 import com.jme3.audio.AudioRenderer;
 import com.jme3.audio.ios.IosAL;
 import com.jme3.audio.ios.IosALC;
@@ -54,6 +55,13 @@ import java.util.logging.Logger;
  */
 public class JmeIosSystem extends JmeSystemDelegate {
 
+    public JmeIosSystem() {
+        setErrorMessageHandler((message) -> {
+            showDialog(message);
+            System.err.println("JME APPLICATION ERROR:" + message);
+        });       
+    }
+
     @Override
     public URL getPlatformAssetConfigURL() {
         return Thread.currentThread().getContextClassLoader().getResource("com/jme3/asset/IOS.cfg");
@@ -64,18 +72,11 @@ public class JmeIosSystem extends JmeSystemDelegate {
         throw new UnsupportedOperationException("Not supported yet.");
     }
 
-    @Override
-    public void showErrorDialog(String message) {
-        showDialog(message);
-        System.err.println("JME APPLICATION ERROR:" + message);
-    }
+   
 
     private native void showDialog(String message);
 
-    @Override
-    public boolean showSettingsDialog(AppSettings sourceSettings, boolean loadFromRegistry) {
-        return true;
-    }
+    
 
     @Override
     public JmeContext newContext(AppSettings settings, JmeContext.Type contextType) {

+ 7 - 35
jme3-vr/src/main/java/com/jme3/app/VRApplication.java

@@ -465,10 +465,10 @@ public abstract class VRApplication implements Application, SystemListener {
         // Display error message on screen if not in headless mode
         if (context.getType() != JmeContext.Type.Headless) {
             if (t != null) {
-                JmeSystem.showErrorDialog(errMsg + "\n" + t.getClass().getSimpleName() +
+                JmeSystem.handleErrorMessage(errMsg + "\n" + t.getClass().getSimpleName() +
                         (t.getMessage() != null ? ": " +  t.getMessage() : ""));
             } else {
-                JmeSystem.showErrorDialog(errMsg);
+                JmeSystem.handleErrorMessage(errMsg);
             }
         }
 
@@ -706,40 +706,12 @@ public abstract class VRApplication implements Application, SystemListener {
             logger.config("VR mode disabled.");
 
             // not in VR, show settings dialog
-            if( Platform.get() != Platform.MACOSX ) {
-                if (!JmeSystem.showSettingsDialog(settings, loadSettings)) {
-                    logger.config("Starting application [SUCCESS]");
-                    return;
-                }
-            } else {
-                // GLFW workaround on macs
-                settings.setFrequency(defDev.getDisplayMode().getRefreshRate());
-                settings.setDepthBits(24);
-                settings.setVSync(true);
-                // try and read resolution from file in local dir
-                File resFile = new File("resolution.txt");
-                if( resFile.exists() ) {
-                    try {
-                        BufferedReader br = new BufferedReader(new FileReader(resFile));
-                        settings.setWidth(Integer.parseInt(br.readLine()));
-                        settings.setHeight(Integer.parseInt(br.readLine()));
-                        try {
-                            settings.setFullscreen(br.readLine().toLowerCase(Locale.ENGLISH).contains("full"));
-                        } catch(Exception e) {
-                            settings.setFullscreen(false);
-                        }
-                        br.close();
-                    } catch(Exception e) {
-                        settings.setWidth(1280);
-                        settings.setHeight(720);
-                    }
-                } else {
-                    settings.setWidth(1280);
-                    settings.setHeight(720);
-                    settings.setFullscreen(false);
-                }
-                settings.setResizable(false);
+            if (!JmeSystem.showSettingsDialog(settings, loadSettings)) {
+                logger.config("Starting application [SUCCESS]");
+                return;
             }
+            
+
             settings.setSwapBuffers(true);
         } else {
             logger.config("VR mode enabled.");

+ 1 - 0
settings.gradle

@@ -33,6 +33,7 @@ include 'jme3-testdata'
 
 // Example projects
 include 'jme3-examples'
+include 'jme3-awt-dialogs'
 
 if(buildAndroidExamples == "true"){
     include 'jme3-android-examples'