Hi all,
This new dialog stuff uses an interface to communicate between jme and the required dialog (can be custom made). Below is the new classes and the modifications of the major ones.
Applications that directly use "properties" to create the window will have to be refactored slightly, the refactoration is obvious. I will not post those here as they are too many. The changes are from "getWidth()" to "getDisplayWidth()"…etc. Obvious
com.jme.util.LWJGLDisplayModeSorter
package com.jme.util.lwjgl;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import org.lwjgl.LWJGLException;
import org.lwjgl.opengl.Display;
import org.lwjgl.opengl.DisplayMode;
import com.jme.system.DisplayModeDesc;
public class LWJGLDisplayModeSorter {
private ArrayList displayModes;
public LWJGLDisplayModeSorter() {
DisplayMode[] modes = null;
try {
modes = Display.getAvailableDisplayModes();
} catch (LWJGLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
Arrays.sort(modes, new DisplayModeSorter());
displayModes = new ArrayList();
for (int i = 0; i < modes.length; i++) {
DisplayModeDesc desc = new DisplayModeDesc();
DisplayMode mode = modes[i];
desc.setDepth(mode.getBitsPerPixel());
desc.setFrequency(mode.getFrequency());
desc.setHeight(mode.getHeight());
desc.setWidth(mode.getWidth());
displayModes.add(desc);
}
}
public ArrayList getOrderedDisplayModes() {
return displayModes;
}
/**
* Utility class for sorting <code>DisplayMode</code>s. Sorts by
* resolution, then bit depth, and then finally refresh rate.
*/
class DisplayModeSorter implements Comparator {
/**
* @see java.util.Comparator#compare(java.lang.Object, java.lang.Object)
*/
public int compare(Object o1, Object o2) {
DisplayMode a = (DisplayMode) o1;
DisplayMode b = (DisplayMode) o2;
// 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.getBitsPerPixel() != b.getBitsPerPixel())
return (a.getBitsPerPixel() > b.getBitsPerPixel()) ? 1 : -1;
// Refresh rate
if (a.getFrequency() != b.getFrequency())
return (a.getFrequency() > b.getFrequency()) ? 1 : -1;
// All fields are equal
return 0;
}
}
}
com.jme.system.DisplayModeDesc
package com.jme.system;
public class DisplayModeDesc {
private int width, height, frequency, depth;
public DisplayModeDesc() {
width = 0;
height = 0;
frequency = 0;
depth = 0;
}
public int getDepth() {
return depth;
}
public void setDepth(int depth) {
this.depth = depth;
}
public int getFrequency() {
return frequency;
}
public void setFrequency(int frequency) {
this.frequency = frequency;
}
public int getHeight() {
return height;
}
public void setHeight(int height) {
this.height = height;
}
public int getWidth() {
return width;
}
public void setWidth(int width) {
this.width = width;
}
}
com.jme.system.RendererDesc
package com.jme.system;
public interface RendererDesc {
public int getDisplayWidth();
public int getDisplayHeight();
public int getDisplayDepth();
public int getFrequency();
public boolean isFullscreen();
public String getRenderer();
}
com.jme.system.lwjgl.LWJGLPropertiesDialog2
package com.jme.system.lwjgl;
import java.awt.BorderLayout;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.logging.Level;
import javax.swing.DefaultComboBoxModel;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JComboBox;
import javax.swing.JDialog;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.UIManager;
import com.jme.system.DisplayModeDesc;
import com.jme.system.DisplaySystem;
import com.jme.system.JmeException;
import com.jme.system.PropertiesIO;
import com.jme.system.RendererDesc;
import com.jme.util.LoggingSystem;
import com.jme.util.lwjgl.LWJGLDisplayModeSorter;
public class LWJGLPropertiesDialog2 extends JDialog implements RendererDesc {
private static final long serialVersionUID = 1L;
// the display mode
private PropertiesIO source;
// UI components
private JCheckBox fullscreenBox = null;
private JComboBox displayResCombo = null;
private JComboBox colorDepthCombo = null;
private JComboBox displayFreqCombo = null;
private JComboBox rendererCombo = null;
private JLabel icon = null;
// Title Image
private URL imageFile = null;
// the display modes
private ArrayList modes;
/**
* Constructor for the <code>PropertiesDialog</code>. Creates a
* properties dialog initialized for the primary display.
*
* @param source
* the <code>PropertiesIO</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 JmeException
* if the source is <code>null</code>
*/
public LWJGLPropertiesDialog2(String imageFile) {
this(getURL(imageFile));
}
public LWJGLPropertiesDialog2(URL image) {
this.source = new PropertiesIO("properties.cfg");
this.imageFile = image;
modes = new LWJGLDisplayModeSorter().getOrderedDisplayModes();
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (Exception e) {
LoggingSystem.getLogger().log(Level.WARNING, "Could not set native look and feel.");
}
addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent e) {
dispose();
System.exit(0);
}
});
setTitle("Select Display Settings");
// The panels...
JPanel mainPanel = new JPanel();
JPanel centerPanel = new JPanel();
JPanel optionsPanel = new JPanel();
JPanel buttonPanel = new JPanel();
// The buttons...
JButton ok = new JButton("Ok");
JButton cancel = new JButton("Cancel");
icon = new JLabel(imageFile != null ? new ImageIcon(imageFile) : null);
mainPanel.setLayout(new BorderLayout());
centerPanel.setLayout(new BorderLayout());
KeyListener aListener = new KeyAdapter() {
public void keyPressed(KeyEvent e) {
if (e.getKeyCode() == KeyEvent.VK_ENTER) {
if (verifyAndSaveCurrentSelection())
dispose();
}
}
};
displayResCombo = setUpResolutionChooser();
displayResCombo.addKeyListener(aListener);
colorDepthCombo = new JComboBox();
colorDepthCombo.addKeyListener(aListener);
displayFreqCombo = new JComboBox();
displayFreqCombo.addKeyListener(aListener);
fullscreenBox = new JCheckBox("Fullscreen?");
fullscreenBox.setSelected(source.getFullscreen());
rendererCombo = setUpRendererChooser();
rendererCombo.addKeyListener(aListener);
updateDisplayChoices();
optionsPanel.add(displayResCombo);
optionsPanel.add(colorDepthCombo);
optionsPanel.add(displayFreqCombo);
optionsPanel.add(fullscreenBox);
optionsPanel.add(rendererCombo);
// Set the button action listeners. Cancel disposes without saving, OK
// saves.
ok.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
if (verifyAndSaveCurrentSelection())
dispose();
}
});
cancel.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
dispose();
System.exit(0);
}
});
buttonPanel.add(ok);
buttonPanel.add(cancel);
if (icon != null)
centerPanel.add(icon, BorderLayout.NORTH);
centerPanel.add(optionsPanel, BorderLayout.SOUTH);
mainPanel.add(centerPanel, BorderLayout.CENTER);
mainPanel.add(buttonPanel, BorderLayout.SOUTH);
this.getContentPane().add(mainPanel);
pack();
center();
setVisible(true);
toFront();
}
/**
* <code>center</code> places this <code>PropertiesDialog</code> in the
* center of the screen.
*/
private void center() {
int x, y;
x = (Toolkit.getDefaultToolkit().getScreenSize().width - this.getWidth()) / 2;
y = (Toolkit.getDefaultToolkit().getScreenSize().height - this.getHeight()) / 2;
this.setLocation(x, y);
}
/**
* <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();
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 = Integer.parseInt(depthString.substring(0, depthString.indexOf(" ")));
String freqString = (String) displayFreqCombo.getSelectedItem();
int freq = Integer.parseInt(freqString.substring(0, freqString.indexOf(" ")));
boolean fullscreen = fullscreenBox.isSelected();
// 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; } }
*/
String renderer = (String) rendererCombo.getSelectedItem();
// test valid display mode
DisplaySystem disp = DisplaySystem.getDisplaySystem(renderer);
boolean valid = (disp != null) ? disp.isValidDisplayMode(width, height, depth, freq)
: false;
if (valid)
// use the PropertiesIO class to save it.
source.save(width, height, depth, freq, fullscreen, renderer);
else
JOptionPane.showMessageDialog(this,
"Your monitor claims to not support the display mode you've selected.n"
+ "The combination of bit depth and refresh rate is not supported.");
return valid;
}
private void updateDisplayChoices() {
String resolution = (String) displayResCombo.getSelectedItem();
// grab available depths
String[] depths = getDepths(resolution, modes);
colorDepthCombo.setModel(new DefaultComboBoxModel(depths));
colorDepthCombo.setSelectedItem(source.getDepth() + " bpp");
// grab available frequencies
String[] freqs = getFrequencies(resolution, modes);
displayFreqCombo.setModel(new DefaultComboBoxModel(freqs));
displayFreqCombo.setSelectedItem(source.getFreq() + " Hz");
}
/**
* Reutrns every unique resolution from an array of <code>DisplayMode</code>s.
*/
private String[] getResolutions(ArrayList modes) {
ArrayList resolutions = new ArrayList(modes.size());
for (int i = 0; i < modes.size(); i++) {
String res = ((DisplayModeDesc) modes.get(i)).getWidth() + " x "
+ ((DisplayModeDesc) modes.get(i)).getHeight();
if (!resolutions.contains(res))
resolutions.add(res);
}
String[] res = new String[resolutions.size()];
resolutions.toArray(res);
return res;
}
/**
* Returns every possible bit depth for the given resolution.
*/
private String[] getDepths(String resolution, ArrayList modes) {
ArrayList depths = new ArrayList(4);
for (int i = 0; i < modes.size(); i++) {
// Filter out all bit depths lower than 16 - Java incorrectly
// reports
// them as valid depths though the monitor does not support them
if (((DisplayModeDesc) modes.get(i)).getDepth() < 16)
continue;
String res = ((DisplayModeDesc) modes.get(i)).getWidth() + " x "
+ ((DisplayModeDesc) modes.get(i)).getHeight();
String depth = String.valueOf(((DisplayModeDesc) modes.get(i)).getDepth()) + " bpp";
if (res.equals(resolution) && !depths.contains(depth))
depths.add(depth);
}
String[] res = new String[depths.size()];
depths.toArray(res);
return res;
}
/**
* Returns every possible refresh rate for the given resolution.
*/
private String[] getFrequencies(String resolution, ArrayList modes) {
ArrayList freqs = new ArrayList(4);
for (int i = 0; i < modes.size(); i++) {
String res = ((DisplayModeDesc) modes.get(i)).getWidth() + " x "
+ ((DisplayModeDesc) modes.get(i)).getHeight();
String freq = String.valueOf(((DisplayModeDesc) modes.get(i)).getFrequency()) + " Hz";
if (res.equals(resolution) && !freqs.contains(freq))
freqs.add(freq);
}
String[] res = new String[freqs.size()];
freqs.toArray(res);
return res;
}
/**
* <code>setUpChooser</code> retrieves all available display modes and
* places them in a <code>JComboBox</code>. The resolution specified by
* PropertiesIO is used as the default value.
*
* @return the combo box of display modes.
*/
private JComboBox setUpResolutionChooser() {
String[] res = getResolutions(modes);
JComboBox resolutionBox = new JComboBox(res);
resolutionBox.setSelectedItem(source.getWidth() + " x " + source.getHeight());
resolutionBox.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
updateDisplayChoices();
}
});
return resolutionBox;
}
/**
* <code>setUpRendererChooser</code> sets the list of available renderers.
* Data is obtained from the <code>DisplaySystem</code> class. The
* renderer specified by PropertiesIO is used as the default value.
*
* @return the list of renderers.
*/
private JComboBox setUpRendererChooser() {
String modes[] = DisplaySystem.rendererNames;
JComboBox nameBox = new JComboBox(modes);
nameBox.setSelectedItem(source.getRenderer());
return nameBox;
}
/**
* 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) {
}
return url;
}
public int getDisplayWidth() {
return source.getWidth();
}
public int getDisplayHeight() {
return source.getHeight();
}
public int getDisplayDepth() {
return source.getDepth();
}
public int getFrequency() {
return source.getFreq();
}
public boolean isFullscreen() {
return source.getFullscreen();
}
public String getRenderer() {
return source.getRenderer();
}
}
Thats the new classes, the modification for AbstractGame is:
// from: protected PropertiesIO properties
/** Game display properties. */
protected RendererDesc properties;
// change getAttributes() to:
/**
* <code>getAttributes</code> attempts to first obtain the properties
* information from the "properties.cfg" file, then a dialog depending on
* the dialog behaviour.
*/
protected void getAttributes() {
if (properties == null) {
if (dialogBehaviour == FIRSTRUN_OR_NOCONFIGFILE_SHOW_PROPS_DIALOG
|| dialogBehaviour == ALWAYS_SHOW_PROPS_DIALOG) {
LWJGLPropertiesDialog2 dialog = new LWJGLPropertiesDialog2(dialogImage);
setRendererDesc(dialog);
while (dialog.isVisible()) {
try {
Thread.sleep(5);
} catch (InterruptedException e) {
LoggingSystem.getLogger().log(Level.WARNING,
"Error waiting for dialog system, using defaults.");
}
}
}
}
}
// add the following method:
/**
* <code>setRendererDesc</code> sets the RendererDesc used to initialise
* the window. This is useful if the user wants to implement their own
* custom dialog
*
* @param desc
*/
public void setRendererDesc(RendererDesc desc) {
this.properties = desc;
}
And done.
Now to create your own dialog, simply implement RendererDesc, before calling AbstractGame.start(), call AbstractGame.setRendererDesc(myDialog); then call AbstractGame.start();
I think this is about as modular and easy as you can get it to be... :)
/DP