* Added a (hacky) fix for a Java Swing/AWT + GLFW interaction issue… … on Linux. * Added a proper comment * Added an extra call to System.gc() as recommendation from lwjgl’s @Spasi "Run System.gc(), at least two times. Not sure how AWT handles native resources, maybe there’s a finalizable/PhantomRef-ed resource that is not released immediately, but a subsequent GC triggers a (too late) free. "
905 lines
32 KiB
Java
905 lines
32 KiB
Java
/*
|
||
* Copyright (c) 2009-2012 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.app;
|
||
|
||
import com.jme3.system.AppSettings;
|
||
import java.awt.*;
|
||
import java.awt.event.*;
|
||
import java.awt.image.BufferedImage;
|
||
import java.lang.reflect.Method;
|
||
import java.net.MalformedURLException;
|
||
import java.net.URL;
|
||
import java.text.MessageFormat;
|
||
import java.util.ArrayList;
|
||
import java.util.Arrays;
|
||
import java.util.Comparator;
|
||
import java.util.List;
|
||
import java.util.ResourceBundle;
|
||
import java.util.logging.Level;
|
||
import java.util.logging.Logger;
|
||
import java.util.prefs.BackingStoreException;
|
||
import javax.swing.*;
|
||
|
||
/**
|
||
* <code>PropertiesDialog</code> provides an interface to make use of the
|
||
* <code>GameSettings</code> class. The <code>GameSettings</code> object
|
||
* is still created by the client application, and passed during construction.
|
||
*
|
||
* @see AppSettings
|
||
* @author Mark Powell
|
||
* @author Eric Woroshow
|
||
* @author Joshua Slack - reworked for proper use of GL commands.
|
||
*/
|
||
public final class SettingsDialog extends JFrame {
|
||
|
||
public static interface SelectionListener {
|
||
|
||
public void onSelection(int selection);
|
||
}
|
||
private static final Logger logger = Logger.getLogger(SettingsDialog.class.getName());
|
||
private static final long serialVersionUID = 1L;
|
||
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");
|
||
|
||
// connection to properties file.
|
||
private final AppSettings source;
|
||
|
||
// Title Image
|
||
private URL imageFile = null;
|
||
// Array of supported display modes
|
||
private DisplayMode[] modes = null;
|
||
private static final DisplayMode[] windowDefaults = new DisplayMode[] {
|
||
new DisplayMode(1024, 768, 24, 60),
|
||
new DisplayMode(1280, 720, 24, 60),
|
||
new DisplayMode(1280, 1024, 24, 60),
|
||
new DisplayMode(1440, 900, 24, 60),
|
||
new DisplayMode(1680, 1050, 24, 60),
|
||
};
|
||
private DisplayMode[] windowModes = null;
|
||
|
||
// UI components
|
||
private JCheckBox vsyncBox = null;
|
||
private JCheckBox gammaBox = null;
|
||
private JCheckBox fullscreenBox = null;
|
||
private JComboBox displayResCombo = null;
|
||
private JComboBox colorDepthCombo = null;
|
||
private JComboBox displayFreqCombo = null;
|
||
private JComboBox antialiasCombo = null;
|
||
private JLabel icon = null;
|
||
private int selection = 0;
|
||
private SelectionListener selectionListener = null;
|
||
|
||
private int minWidth = 0;
|
||
private int minHeight = 0;
|
||
|
||
/**
|
||
* Constructor for the <code>PropertiesDialog</code>. Creates a
|
||
* properties dialog initialized for the primary display.
|
||
*
|
||
* @param source
|
||
* the <code>AppSettings</code> object to use for working with
|
||
* the properties file.
|
||
* @param imageFile
|
||
* the image file to use as the title of the dialog;
|
||
* <code>null</code> will result in to image being displayed
|
||
* @throws NullPointerException
|
||
* if the source is <code>null</code>
|
||
*/
|
||
public SettingsDialog(AppSettings source, String imageFile, boolean loadSettings) {
|
||
this(source, getURL(imageFile), loadSettings);
|
||
}
|
||
|
||
/**
|
||
* Constructor for the <code>PropertiesDialog</code>. Creates a
|
||
* properties dialog initialized for the primary display.
|
||
*
|
||
* @param source
|
||
* the <code>GameSettings</code> object to use for working with
|
||
* the properties file.
|
||
* @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
|
||
* @throws JmeException
|
||
* if the source is <code>null</code>
|
||
*/
|
||
public SettingsDialog(AppSettings source, URL imageFile, boolean loadSettings) {
|
||
if (source == null) {
|
||
throw new NullPointerException("Settings source cannot be null");
|
||
}
|
||
|
||
this.source = source;
|
||
this.imageFile = imageFile;
|
||
|
||
//setModal(true);
|
||
setAlwaysOnTop(true);
|
||
setResizable(false);
|
||
|
||
AppSettings registrySettings = new AppSettings(true);
|
||
|
||
String appTitle;
|
||
if(source.getTitle()!=null){
|
||
appTitle = source.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);
|
||
}
|
||
|
||
if (loadSettings) {
|
||
source.copyFrom(registrySettings);
|
||
} else if(!registrySettings.isEmpty()) {
|
||
source.mergeFrom(registrySettings);
|
||
}
|
||
|
||
GraphicsDevice device = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice();
|
||
|
||
modes = device.getDisplayModes();
|
||
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++) {
|
||
|
||
if (dmIndex >= modes.length) {
|
||
merged[mergedIndex] = windowDefaults[wdIndex++];
|
||
} else if (wdIndex >= windowDefaults.length) {
|
||
merged[mergedIndex] = modes[dmIndex++];
|
||
} else if (modes[dmIndex].getWidth() < windowDefaults[wdIndex].getWidth()) {
|
||
merged[mergedIndex] = modes[dmIndex++];
|
||
} else if (modes[dmIndex].getWidth() == windowDefaults[wdIndex].getWidth()) {
|
||
if (modes[dmIndex].getHeight() < windowDefaults[wdIndex].getHeight()) {
|
||
merged[mergedIndex] = modes[dmIndex++];
|
||
} else if (modes[dmIndex].getHeight() == windowDefaults[wdIndex].getHeight()) {
|
||
merged[mergedIndex] = modes[dmIndex++];
|
||
wdIndex++;
|
||
} else {
|
||
merged[mergedIndex] = windowDefaults[wdIndex++];
|
||
}
|
||
} else {
|
||
merged[mergedIndex] = windowDefaults[wdIndex++];
|
||
}
|
||
}
|
||
|
||
if (merged.length == mergedIndex) {
|
||
windowModes = merged;
|
||
} else {
|
||
windowModes = Arrays.copyOfRange(merged, 0, mergedIndex);
|
||
}
|
||
|
||
createUI();
|
||
}
|
||
|
||
public void setSelectionListener(SelectionListener sl) {
|
||
this.selectionListener = sl;
|
||
}
|
||
|
||
public int getUserSelection() {
|
||
return selection;
|
||
}
|
||
|
||
private void setUserSelection(int selection) {
|
||
this.selection = selection;
|
||
selectionListener.onSelection(selection);
|
||
}
|
||
|
||
public int getMinWidth() {
|
||
return minWidth;
|
||
}
|
||
|
||
public void setMinWidth(int minWidth) {
|
||
this.minWidth = minWidth;
|
||
}
|
||
|
||
public int getMinHeight() {
|
||
return minHeight;
|
||
}
|
||
|
||
public void setMinHeight(int minHeight) {
|
||
this.minHeight = minHeight;
|
||
}
|
||
|
||
|
||
|
||
|
||
/**
|
||
* <code>setImage</code> sets the background image of the dialog.
|
||
*
|
||
* @param image
|
||
* <code>String</code> representing the image file.
|
||
*/
|
||
public void setImage(String image) {
|
||
try {
|
||
URL file = new URL("file:" + image);
|
||
setImage(file);
|
||
} catch (MalformedURLException e) {
|
||
logger.log(Level.WARNING, "Couldn’t read from file '" + image + "'", e);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* <code>setImage</code> sets the background image of this dialog.
|
||
*
|
||
* @param image
|
||
* <code>URL</code> pointing to the image file.
|
||
*/
|
||
public void setImage(URL image) {
|
||
icon.setIcon(new ImageIcon(image));
|
||
pack(); // Resize to accomodate the new image
|
||
setLocationRelativeTo(null); // put in center
|
||
}
|
||
|
||
/**
|
||
* <code>showDialog</code> sets this dialog as visble, and brings it to
|
||
* the front.
|
||
*/
|
||
public void showDialog() {
|
||
setLocationRelativeTo(null);
|
||
setVisible(true);
|
||
toFront();
|
||
}
|
||
|
||
/**
|
||
* <code>init</code> creates the components to use the dialog.
|
||
*/
|
||
private void createUI() {
|
||
GridBagConstraints gbc;
|
||
|
||
JPanel mainPanel = new JPanel(new GridBagLayout());
|
||
|
||
try {
|
||
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
|
||
} catch (Exception e) {
|
||
logger.warning("Could not set native look and feel.");
|
||
}
|
||
|
||
addWindowListener(new WindowAdapter() {
|
||
|
||
@Override
|
||
public void windowClosing(WindowEvent e) {
|
||
setUserSelection(CANCEL_SELECTION);
|
||
dispose();
|
||
}
|
||
});
|
||
|
||
if (source.getIcons() != null) {
|
||
safeSetIconImages( (List<BufferedImage>) 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 cancel = new JButton(resourceBundle.getString("button.cancel"));
|
||
|
||
icon = new JLabel(imageFile != null ? new ImageIcon(imageFile) : null);
|
||
|
||
KeyListener aListener = new KeyAdapter() {
|
||
|
||
@Override
|
||
public void keyPressed(KeyEvent e) {
|
||
if (e.getKeyCode() == KeyEvent.VK_ENTER) {
|
||
if (verifyAndSaveCurrentSelection()) {
|
||
setUserSelection(APPROVE_SELECTION);
|
||
dispose();
|
||
}
|
||
}
|
||
else if (e.getKeyCode() == KeyEvent.VK_ESCAPE) {
|
||
setUserSelection(CANCEL_SELECTION);
|
||
dispose();
|
||
}
|
||
}
|
||
};
|
||
|
||
displayResCombo = setUpResolutionChooser();
|
||
displayResCombo.addKeyListener(aListener);
|
||
colorDepthCombo = new JComboBox();
|
||
colorDepthCombo.addKeyListener(aListener);
|
||
displayFreqCombo = new JComboBox();
|
||
displayFreqCombo.addKeyListener(aListener);
|
||
antialiasCombo = new JComboBox();
|
||
antialiasCombo.addKeyListener(aListener);
|
||
fullscreenBox = new JCheckBox(resourceBundle.getString("checkbox.fullscreen"));
|
||
fullscreenBox.setSelected(source.isFullscreen());
|
||
fullscreenBox.addActionListener(new ActionListener() {
|
||
|
||
public void actionPerformed(ActionEvent e) {
|
||
updateResolutionChoices();
|
||
}
|
||
});
|
||
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;
|
||
gbc.gridwidth = 2;
|
||
gbc.gridy = 1;
|
||
gbc.anchor = GridBagConstraints.EAST;
|
||
mainPanel.add(fullscreenBox, gbc);
|
||
gbc = new GridBagConstraints();
|
||
gbc.weightx = 0.5;
|
||
// gbc.insets = new Insets(4, 16, 0, 4);
|
||
gbc.gridx = 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.anchor = GridBagConstraints.WEST;
|
||
mainPanel.add(gammaBox, gbc);
|
||
|
||
|
||
gbc = new GridBagConstraints();
|
||
gbc.insets = new Insets(4, 4, 4, 4);
|
||
gbc.gridx = 0;
|
||
gbc.gridy = 2;
|
||
gbc.anchor = GridBagConstraints.EAST;
|
||
gbc.weightx = 0.5;
|
||
mainPanel.add(new JLabel(resourceBundle.getString("label.resolutions")), gbc);
|
||
gbc = new GridBagConstraints();
|
||
gbc.gridx = 1;
|
||
gbc.gridy = 2;
|
||
gbc.anchor = GridBagConstraints.WEST;
|
||
mainPanel.add(displayResCombo, gbc);
|
||
gbc = new GridBagConstraints();
|
||
gbc.insets = new Insets(4, 16, 4, 4);
|
||
gbc.gridx = 2;
|
||
gbc.gridy = 2;
|
||
gbc.anchor = GridBagConstraints.EAST;
|
||
mainPanel.add(new JLabel(resourceBundle.getString("label.colordepth")), gbc);
|
||
gbc = new GridBagConstraints();
|
||
gbc.weightx = 0.5;
|
||
gbc.gridx = 3;
|
||
gbc.gridy = 2;
|
||
gbc.anchor = GridBagConstraints.WEST;
|
||
mainPanel.add(colorDepthCombo, gbc);
|
||
gbc = new GridBagConstraints();
|
||
gbc.insets = new Insets(4, 4, 4, 4);
|
||
gbc.weightx = 0.5;
|
||
gbc.gridx = 0;
|
||
gbc.gridy = 3;
|
||
gbc.anchor = GridBagConstraints.EAST;
|
||
mainPanel.add(new JLabel(resourceBundle.getString("label.refresh")), gbc);
|
||
gbc = new GridBagConstraints();
|
||
gbc.gridx = 1;
|
||
gbc.gridy = 3;
|
||
gbc.anchor = GridBagConstraints.WEST;
|
||
mainPanel.add(displayFreqCombo, gbc);
|
||
gbc = new GridBagConstraints();
|
||
gbc.insets = new Insets(4, 16, 4, 4);
|
||
gbc.gridx = 2;
|
||
gbc.gridy = 3;
|
||
gbc.anchor = GridBagConstraints.EAST;
|
||
mainPanel.add(new JLabel(resourceBundle.getString("label.antialias")), gbc);
|
||
gbc = new GridBagConstraints();
|
||
gbc.weightx = 0.5;
|
||
gbc.gridx = 3;
|
||
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() {
|
||
|
||
public void actionPerformed(ActionEvent e) {
|
||
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();
|
||
System.gc();
|
||
}
|
||
}
|
||
});
|
||
|
||
cancel.addActionListener(new ActionListener() {
|
||
|
||
public void actionPerformed(ActionEvent e) {
|
||
setUserSelection(CANCEL_SELECTION);
|
||
dispose();
|
||
}
|
||
});
|
||
|
||
gbc = new GridBagConstraints();
|
||
gbc.gridx = 0;
|
||
gbc.gridwidth = 2;
|
||
gbc.gridy = 4;
|
||
gbc.anchor = GridBagConstraints.EAST;
|
||
mainPanel.add(ok, gbc);
|
||
gbc = new GridBagConstraints();
|
||
gbc.insets = new Insets(4, 16, 4, 4);
|
||
gbc.gridx = 2;
|
||
gbc.gridwidth = 2;
|
||
gbc.gridy = 4;
|
||
gbc.anchor = GridBagConstraints.WEST;
|
||
mainPanel.add(cancel, gbc);
|
||
|
||
if (icon != null) {
|
||
gbc = new GridBagConstraints();
|
||
gbc.gridwidth = 4;
|
||
mainPanel.add(icon, gbc);
|
||
}
|
||
|
||
this.getContentPane().add(mainPanel);
|
||
|
||
pack();
|
||
|
||
mainPanel.getRootPane().setDefaultButton(ok);
|
||
SwingUtilities.invokeLater(new Runnable() {
|
||
|
||
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.
|
||
updateResolutionChoices();
|
||
if (source.getWidth() != 0 && source.getHeight() != 0) {
|
||
displayResCombo.setSelectedItem(source.getWidth() + " x "
|
||
+ source.getHeight());
|
||
} else {
|
||
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 */
|
||
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.
|
||
Window owner = getOwner();
|
||
if (owner != null) {
|
||
Method setIconImages = owner.getClass().getMethod("setIconImages", List.class);
|
||
setIconImages.invoke(owner, icons);
|
||
return;
|
||
}
|
||
|
||
Method setIconImages = getClass().getMethod("setIconImages", List.class);
|
||
setIconImages.invoke(this, icons);
|
||
} catch (Exception e) {
|
||
logger.log(Level.WARNING, "Error setting icon images", e);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* <code>verifyAndSaveCurrentSelection</code> first verifies that the
|
||
* display mode is valid for this system, and then saves the current
|
||
* selection as a properties.cfg file.
|
||
*
|
||
* @return if the selection is valid
|
||
*/
|
||
private boolean verifyAndSaveCurrentSelection() {
|
||
String display = (String) displayResCombo.getSelectedItem();
|
||
boolean fullscreen = fullscreenBox.isSelected();
|
||
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 depthString = (String) colorDepthCombo.getSelectedItem();
|
||
int depth = -1;
|
||
if (depthString.equals("???")) {
|
||
depth = 0;
|
||
} else {
|
||
depth = Integer.parseInt(depthString.substring(0, depthString.indexOf(' ')));
|
||
}
|
||
|
||
String freqString = (String) displayFreqCombo.getSelectedItem();
|
||
int freq = -1;
|
||
if (fullscreen) {
|
||
if (freqString.equals("???")) {
|
||
freq = 0;
|
||
} else {
|
||
freq = Integer.parseInt(freqString.substring(0, freqString.indexOf(' ')));
|
||
}
|
||
}
|
||
|
||
String aaString = (String) antialiasCombo.getSelectedItem();
|
||
int multisample = -1;
|
||
if (aaString.equals(resourceBundle.getString("antialias.disabled"))) {
|
||
multisample = 0;
|
||
} else {
|
||
multisample = Integer.parseInt(aaString.substring(0, aaString.indexOf('x')));
|
||
}
|
||
|
||
// 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;
|
||
|
||
// test valid display mode when going full screen
|
||
if (!fullscreen) {
|
||
valid = true;
|
||
} else {
|
||
GraphicsDevice device = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice();
|
||
valid = device.isFullScreenSupported();
|
||
}
|
||
|
||
if (valid) {
|
||
//use the GameSettings class to save it.
|
||
source.setWidth(width);
|
||
source.setHeight(height);
|
||
source.setBitsPerPixel(depth);
|
||
source.setFrequency(freq);
|
||
source.setFullscreen(fullscreen);
|
||
source.setVSync(vsync);
|
||
source.setGammaCorrection(gamma);
|
||
//source.setRenderer(renderer);
|
||
source.setSamples(multisample);
|
||
|
||
String appTitle = source.getTitle();
|
||
|
||
try {
|
||
source.save(appTitle);
|
||
} catch (BackingStoreException ex) {
|
||
logger.log(Level.WARNING,
|
||
"Failed to save setting changes", ex);
|
||
}
|
||
} else {
|
||
showError(
|
||
this,
|
||
resourceBundle.getString("error.unsupportedmode"));
|
||
}
|
||
|
||
return valid;
|
||
}
|
||
|
||
/**
|
||
* <code>setUpChooser</code> retrieves all available display modes and
|
||
* places them in a <code>JComboBox</code>. The resolution specified by
|
||
* GameSettings is used as the default value.
|
||
*
|
||
* @return the combo box of display modes.
|
||
*/
|
||
private JComboBox setUpResolutionChooser() {
|
||
JComboBox resolutionBox = new JComboBox();
|
||
|
||
resolutionBox.addActionListener(new ActionListener() {
|
||
|
||
public void actionPerformed(ActionEvent e) {
|
||
updateDisplayChoices();
|
||
}
|
||
});
|
||
|
||
return resolutionBox;
|
||
}
|
||
|
||
/**
|
||
* <code>updateDisplayChoices</code> updates the available color depth and
|
||
* display frequency options to match the currently selected resolution.
|
||
*/
|
||
private void updateDisplayChoices() {
|
||
if (!fullscreenBox.isSelected()) {
|
||
// don't run this function when changing windowed settings
|
||
return;
|
||
}
|
||
String resolution = (String) displayResCombo.getSelectedItem();
|
||
String colorDepth = (String) colorDepthCombo.getSelectedItem();
|
||
if (colorDepth == null) {
|
||
colorDepth = source.getBitsPerPixel() + " bpp";
|
||
}
|
||
String displayFreq = (String) displayFreqCombo.getSelectedItem();
|
||
if (displayFreq == null) {
|
||
displayFreq = source.getFrequency() + " Hz";
|
||
}
|
||
|
||
// grab available depths
|
||
String[] depths = getDepths(resolution, modes);
|
||
colorDepthCombo.setModel(new DefaultComboBoxModel(depths));
|
||
colorDepthCombo.setSelectedItem(colorDepth);
|
||
// grab available frequencies
|
||
String[] freqs = getFrequencies(resolution, modes);
|
||
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.
|
||
displayFreqCombo.setSelectedItem(getBestFrequency(resolution, modes));
|
||
}
|
||
}
|
||
|
||
/**
|
||
* <code>updateResolutionChoices</code> updates the available resolutions
|
||
* list to match the currently selected window mode (fullscreen or
|
||
* windowed). It then sets up a list of standard options (if windowed) or
|
||
* calls <code>updateDisplayChoices</code> (if fullscreen).
|
||
*/
|
||
private void updateResolutionChoices() {
|
||
if (!fullscreenBox.isSelected()) {
|
||
displayResCombo.setModel(new DefaultComboBoxModel(
|
||
getWindowedResolutions(windowModes)));
|
||
if (displayResCombo.getItemCount() > 0) {
|
||
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")}));
|
||
displayFreqCombo.setEnabled(false);
|
||
} else {
|
||
displayResCombo.setModel(new DefaultComboBoxModel(
|
||
getResolutions(modes, Integer.MAX_VALUE, Integer.MAX_VALUE)));
|
||
if (displayResCombo.getItemCount() > 0) {
|
||
displayResCombo.setSelectedIndex(displayResCombo.getItemCount()-1);
|
||
}
|
||
displayFreqCombo.setEnabled(true);
|
||
updateDisplayChoices();
|
||
}
|
||
}
|
||
|
||
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"};
|
||
antialiasCombo.setModel(new DefaultComboBoxModel(choices));
|
||
antialiasCombo.setSelectedItem(choices[Math.min(source.getSamples()/2,5)]);
|
||
}
|
||
|
||
//
|
||
// Utility methods
|
||
//
|
||
/**
|
||
* Utility method for converting a String denoting a file into a URL.
|
||
*
|
||
* @return a URL pointing to the file or null
|
||
*/
|
||
private static URL getURL(String file) {
|
||
URL url = null;
|
||
try {
|
||
url = new URL("file:" + file);
|
||
} catch (MalformedURLException e) {
|
||
logger.log(Level.WARNING, "Invalid file name '" + file + "'", e);
|
||
}
|
||
return url;
|
||
}
|
||
|
||
private static void showError(java.awt.Component parent, String 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.
|
||
*/
|
||
private String[] getResolutions(DisplayMode[] modes, int heightLimit, int widthLimit) {
|
||
Insets insets = getInsets();
|
||
heightLimit -= insets.top + insets.bottom;
|
||
widthLimit -= insets.left + insets.right;
|
||
|
||
ArrayList<String> resolutions = new ArrayList<String>(modes.length);
|
||
for (int i = 0; i < modes.length; i++) {
|
||
int height = modes[i].getHeight();
|
||
int width = modes[i].getWidth();
|
||
if (width >= minWidth && height >= minHeight) {
|
||
if (height >= heightLimit) {
|
||
height = heightLimit;
|
||
}
|
||
if (width >= widthLimit) {
|
||
width = widthLimit;
|
||
}
|
||
|
||
String res = width + " x " + height;
|
||
if (!resolutions.contains(res)) {
|
||
resolutions.add(res);
|
||
}
|
||
}
|
||
}
|
||
|
||
String[] res = new String[resolutions.size()];
|
||
resolutions.toArray(res);
|
||
return res;
|
||
}
|
||
|
||
/**
|
||
* 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 (int i = 0; i < modes.length; i++) {
|
||
if (maxHeight < modes[i].getHeight()) {
|
||
maxHeight = modes[i].getHeight();
|
||
}
|
||
if (maxWidth < modes[i].getWidth()) {
|
||
maxWidth = modes[i].getWidth();
|
||
}
|
||
}
|
||
|
||
return getResolutions(modes, maxHeight, maxWidth);
|
||
}
|
||
|
||
/**
|
||
* Returns every possible bit depth for the given resolution.
|
||
*/
|
||
private static String[] getDepths(String resolution, DisplayMode[] modes) {
|
||
ArrayList<String> depths = new ArrayList<String>(4);
|
||
for (int i = 0; i < modes.length; i++) {
|
||
// Filter out all bit depths lower than 16 - Java incorrectly
|
||
// reports
|
||
// them as valid depths though the monitor does not support them
|
||
if (modes[i].getBitDepth() < 16 && modes[i].getBitDepth() > 0) {
|
||
continue;
|
||
}
|
||
|
||
String res = modes[i].getWidth() + " x " + modes[i].getHeight();
|
||
String depth = modes[i].getBitDepth() + " bpp";
|
||
if (res.equals(resolution) && !depths.contains(depth)) {
|
||
depths.add(depth);
|
||
}
|
||
}
|
||
|
||
if (depths.size() == 1 && depths.contains("-1 bpp")) {
|
||
// add some default depths, possible system is multi-depth supporting
|
||
depths.clear();
|
||
depths.add("24 bpp");
|
||
}
|
||
|
||
String[] res = new String[depths.size()];
|
||
depths.toArray(res);
|
||
return res;
|
||
}
|
||
|
||
/**
|
||
* Returns every possible refresh rate for the given resolution.
|
||
*/
|
||
private static String[] getFrequencies(String resolution,
|
||
DisplayMode[] modes) {
|
||
ArrayList<String> freqs = new ArrayList<String>(4);
|
||
for (int i = 0; i < modes.length; i++) {
|
||
String res = modes[i].getWidth() + " x " + modes[i].getHeight();
|
||
String freq;
|
||
if (modes[i].getRefreshRate() == DisplayMode.REFRESH_RATE_UNKNOWN) {
|
||
freq = "???";
|
||
} else {
|
||
freq = modes[i].getRefreshRate() + " Hz";
|
||
}
|
||
|
||
if (res.equals(resolution) && !freqs.contains(freq)) {
|
||
freqs.add(freq);
|
||
}
|
||
}
|
||
|
||
String[] res = new String[freqs.size()];
|
||
freqs.toArray(res);
|
||
return res;
|
||
}
|
||
|
||
/**
|
||
* Chooses the closest frequency to 60 Hz.
|
||
*
|
||
* @param resolution
|
||
* @param modes
|
||
* @return
|
||
*/
|
||
private static String getBestFrequency(String resolution, DisplayMode[] modes) {
|
||
int closest = Integer.MAX_VALUE;
|
||
int desired = 60;
|
||
for (int i = 0; i < modes.length; i++) {
|
||
String res = modes[i].getWidth() + " x " + modes[i].getHeight();
|
||
int freq = modes[i].getRefreshRate();
|
||
if (freq != DisplayMode.REFRESH_RATE_UNKNOWN && res.equals(resolution)) {
|
||
if (Math.abs(freq - desired) <
|
||
Math.abs(closest - desired)) {
|
||
closest = modes[i].getRefreshRate();
|
||
}
|
||
}
|
||
}
|
||
|
||
if (closest != Integer.MAX_VALUE) {
|
||
return closest + " Hz";
|
||
} else {
|
||
return null;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 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> {
|
||
|
||
/**
|
||
* @see java.util.Comparator#compare(java.lang.Object, java.lang.Object)
|
||
*/
|
||
public int compare(DisplayMode a, DisplayMode b) {
|
||
// Width
|
||
if (a.getWidth() != b.getWidth()) {
|
||
return (a.getWidth() > b.getWidth()) ? 1 : -1;
|
||
}
|
||
// Height
|
||
if (a.getHeight() != b.getHeight()) {
|
||
return (a.getHeight() > b.getHeight()) ? 1 : -1;
|
||
}
|
||
// Bit depth
|
||
if (a.getBitDepth() != b.getBitDepth()) {
|
||
return (a.getBitDepth() > b.getBitDepth()) ? 1 : -1;
|
||
}
|
||
// Refresh rate
|
||
if (a.getRefreshRate() != b.getRefreshRate()) {
|
||
return (a.getRefreshRate() > b.getRefreshRate()) ? 1 : -1;
|
||
}
|
||
// All fields are equal
|
||
return 0;
|
||
}
|
||
}
|
||
}
|