2 * Copyright (c) 2003-2009 jMonkeyEngine
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are
9 * * Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
12 * * Redistributions in binary form must reproduce the above copyright
13 * notice, this list of conditions and the following disclaimer in the
14 * documentation and/or other materials provided with the distribution.
16 * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
17 * may be used to endorse or promote products derived from this software
18 * without specific prior written permission.
20 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
22 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
23 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
24 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
25 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
26 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
27 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
28 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
29 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
30 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
33 package com.jme.system;
35 import java.awt.BorderLayout;
36 import java.awt.DisplayMode;
37 import java.awt.GraphicsEnvironment;
38 import java.awt.Toolkit;
39 import java.awt.event.ActionEvent;
40 import java.awt.event.ActionListener;
41 import java.awt.event.WindowAdapter;
42 import java.awt.event.WindowEvent;
43 import java.net.MalformedURLException;
45 import java.util.ArrayList;
46 import java.util.Arrays;
47 import java.util.Comparator;
48 import java.util.logging.Logger;
49 import java.util.logging.Level;
50 import java.io.IOException;
52 import javax.swing.DefaultComboBoxModel;
53 import javax.swing.ImageIcon;
54 import javax.swing.JButton;
55 import javax.swing.JCheckBox;
56 import javax.swing.JComboBox;
57 import javax.swing.JDialog;
58 import javax.swing.JLabel;
59 import javax.swing.JOptionPane;
60 import javax.swing.JPanel;
61 import javax.swing.UIManager;
64 * <code>PropertiesDialog</code> provides an interface to make use of the
65 * <code>GameSettings</code> class. It provides a simple clean method of
66 * creating a properties file. The <code>GameSettings</code> is still created
67 * by the client application, and passed during construction.
69 * @see com.jme.system.GameSettings
71 * @author Eric Woroshow
72 * @version $Id: PropertiesDialog2.java,v 1.7 2007/08/02 22:14:06 nca Exp $
74 public final class PropertiesDialog2 extends JDialog {
75 private static final Logger logger = Logger.getLogger(PropertiesDialog2.class.getName());
77 private static final long serialVersionUID = 1L;
79 //connection to properties file.
80 private final GameSettings source;
83 private URL imageFile = null;
85 //Array of supported display modes
86 private final DisplayMode[] modes;
89 private JCheckBox fullscreenBox = null;
90 private JComboBox displayResCombo = null;
91 private JComboBox colorDepthCombo = null;
92 private JComboBox displayFreqCombo = null;
93 private JComboBox rendererCombo = null;
94 private JLabel icon = null;
97 * Constructor for the <code>PropertiesDialog</code>. Creates a
98 * properties dialog initialized for the primary display.
99 * @param source the <code>GameSettings</code> object to use for working with
100 * the properties file.
101 * @param imageFile the image file to use as the title of the dialog;
102 * <code>null</code> will result in to image being displayed
103 * @throws JmeException if the source is <code>null</code>
105 public PropertiesDialog2(GameSettings source, String imageFile) {
106 this(source, getURL(imageFile));
111 * Constructor for the <code>PropertiesDialog</code>. Creates a
112 * properties dialog initialized for the primary display.
113 * @param source the <code>GameSettings</code> object to use for working with
114 * the properties file.
115 * @param imageFile the image file to use as the title of the dialog;
116 * <code>null</code> will result in to image being displayed
117 * @throws JmeException if the source is <code>null</code>
119 public PropertiesDialog2(GameSettings source, URL imageFile) {
121 throw new JmeException("PropertyIO source cannot be null");
123 this.source = source;
124 this.imageFile = imageFile;
125 this.modes = GraphicsEnvironment.getLocalGraphicsEnvironment()
126 .getDefaultScreenDevice().getDisplayModes();
127 Arrays.sort(modes, new DisplayModeSorter());
133 * <code>setImage</code> sets the background image of the dialog.
134 * @param image <code>String</code> representing the image file.
136 public void setImage(String image) {
138 URL file = new URL("file:" + image);
140 //We can safely ignore the exception - it just means that the user gave us a bogus file
141 } catch (MalformedURLException e) {}
145 * <code>setImage</code> sets the background image of this dialog.
146 * @param image <code>URL</code> pointing to the image file.
148 public void setImage(URL image) {
149 icon.setIcon(new ImageIcon(image));
150 pack(); //Resize to accomodate the new image
155 * <code>showDialog</code> sets this dialog as visble, and brings it to the
158 private void showDialog() {
164 * <code>center</code> places this <code>PropertiesDialog</code> in the
165 * center of the screen.
167 private void center() {
169 x = (Toolkit.getDefaultToolkit().getScreenSize().width - this.getWidth()) / 2;
170 y = (Toolkit.getDefaultToolkit().getScreenSize().height - this.getHeight()) / 2;
171 this.setLocation(x, y);
175 * <code>init</code> creates the components to use the dialog.
177 private void createUI() {
179 UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
180 } catch (Exception e) {
181 logger.warning("Could not set native look and feel.");
184 addWindowListener(new WindowAdapter() {
185 public void windowClosing(WindowEvent e) {
191 setTitle("Select Display Settings");
194 JPanel mainPanel = new JPanel();
195 JPanel centerPanel = new JPanel();
196 JPanel optionsPanel = new JPanel();
197 JPanel buttonPanel = new JPanel();
199 JButton ok = new JButton("Ok");
200 JButton cancel = new JButton("Cancel");
202 icon = new JLabel(new ImageIcon(imageFile));
204 mainPanel.setLayout(new BorderLayout());
206 centerPanel.setLayout(new BorderLayout());
208 displayResCombo = setUpResolutionChooser();
209 colorDepthCombo = new JComboBox();
210 displayFreqCombo = new JComboBox();
211 fullscreenBox = new JCheckBox("Fullscreen?");
212 fullscreenBox.setSelected(source.isFullscreen());
213 rendererCombo = setUpRendererChooser();
215 updateDisplayChoices();
217 optionsPanel.add(displayResCombo);
218 optionsPanel.add(colorDepthCombo);
219 optionsPanel.add(displayFreqCombo);
220 optionsPanel.add(fullscreenBox);
221 optionsPanel.add(rendererCombo);
223 //Set the button action listeners. Cancel disposes without saving, OK saves.
224 ok.addActionListener(new ActionListener() {
225 public void actionPerformed(ActionEvent e) {
226 if (verifyAndSaveCurrentSelection())
231 cancel.addActionListener(new ActionListener() {
232 public void actionPerformed(ActionEvent e) {
239 buttonPanel.add(cancel);
241 centerPanel.add(icon, BorderLayout.NORTH);
242 centerPanel.add(optionsPanel, BorderLayout.SOUTH);
244 mainPanel.add(centerPanel, BorderLayout.CENTER);
245 mainPanel.add(buttonPanel, BorderLayout.SOUTH);
247 this.getContentPane().add(mainPanel);
255 * <code>verifyAndSaveCurrentSelection</code> first verifies that the
256 * display mode is valid for this system, and then saves the current
257 * selection as a properties.cfg file.
259 * @return if the selection is valid
261 private boolean verifyAndSaveCurrentSelection() {
262 String display = (String) displayResCombo.getSelectedItem();
264 int width = Integer.parseInt(display.substring(0, display.indexOf(" x ")));
265 display = display.substring(display.indexOf(" x ") + 3);
266 int height = Integer.parseInt(display);
268 String depthString = (String) colorDepthCombo.getSelectedItem();
269 int depth = Integer.parseInt(depthString.substring(0, depthString.indexOf(' ')));
271 String freqString = (String) displayFreqCombo.getSelectedItem();
272 int freq = Integer.parseInt(freqString.substring(0, freqString.indexOf(' ')));
274 boolean fullscreen = fullscreenBox.isSelected();
276 //query the current bit depth of the desktop
277 int curDepth = GraphicsEnvironment.getLocalGraphicsEnvironment()
278 .getDefaultScreenDevice().getDisplayMode().getBitDepth();
279 if (depth > curDepth) {
280 showError(this,"Cannot choose a higher bit depth in windowed " +
281 "mode than your current desktop bit depth");
286 String renderer = (String) rendererCombo.getSelectedItem();
288 //test valid display mode
289 DisplaySystem disp = DisplaySystem.getDisplaySystem(renderer);
290 boolean valid = (disp != null) ? disp.isValidDisplayMode(width, height, depth, freq) : false;
293 //use the GameSettings class to save it.
294 source.setWidth(width);
295 source.setHeight(height);
296 source.setDepth(depth);
297 source.setFrequency(freq);
298 source.setFullscreen(fullscreen);
299 source.setRenderer(renderer);
302 } catch (IOException ioe) {
303 logger.log(Level.WARNING,
304 "Failed to save setting changes", ioe);
307 showError(this, "Your monitor claims to not support the display mode you've selected.\n" +
308 "The combination of bit depth and refresh rate is not supported.");
314 * <code>setUpChooser</code> retrieves all available display modes and
315 * places them in a <code>JComboBox</code>. The resolution specified
316 * by GameSettings is used as the default value.
317 * @return the combo box of display modes.
319 private JComboBox setUpResolutionChooser() {
320 String[] res = getResolutions(modes);
321 JComboBox resolutionBox = new JComboBox(res);
323 resolutionBox.setSelectedItem(source.getWidth() + " x " + source.getHeight());
324 resolutionBox.addActionListener(new ActionListener() {
325 public void actionPerformed(ActionEvent e) {
326 updateDisplayChoices();
330 return resolutionBox;
334 * <code>setUpRendererChooser</code> sets the list of available renderers.
335 * Data is obtained from the <code>DisplaySystem</code> class. The renderer
336 * specified by GameSettings is used as the default value.
337 * @return the list of renderers.
339 private JComboBox setUpRendererChooser() {
340 String modes[] = DisplaySystem.getSystemProviderIdentifiers();
341 JComboBox nameBox = new JComboBox(modes);
342 nameBox.setSelectedItem(source.getRenderer());
346 private void updateDisplayChoices() {
347 String resolution = (String)displayResCombo.getSelectedItem();
348 //grab available depths
349 String[] depths = getDepths(resolution, modes);
350 colorDepthCombo.setModel(new DefaultComboBoxModel(depths));
351 colorDepthCombo.setSelectedItem(source.getDepth() + " bpp");
352 //grab available frequencies
353 String[] freqs = getFrequencies(resolution, modes);
354 displayFreqCombo.setModel(new DefaultComboBoxModel(freqs));
355 displayFreqCombo.setSelectedItem(source.getFrequency() + " Hz");
363 * Utility method for converting a String denoting a file
365 * @return a URL pointing to the file or null
367 private static URL getURL(String file) {
370 url = new URL("file:" + file);
371 } catch (MalformedURLException e) {}
375 private static void showError(java.awt.Component parent, String message) {
376 JOptionPane.showMessageDialog(
380 JOptionPane.ERROR_MESSAGE);
384 * Reutrns every unique resolution from an array of <code>DisplayMode</code>s.
386 private static String[] getResolutions(DisplayMode[] modes) {
387 ArrayList<String> resolutions = new ArrayList<String>(16);
388 for (int i = 0; i < modes.length; i++) {
389 String res = modes[i].getWidth() + " x " + modes[i].getHeight();
390 if (!resolutions.contains(res))
391 resolutions.add(res);
394 String[] res = new String[resolutions.size()];
395 resolutions.toArray(res);
400 * Returns every possible bit depth for the given resolution.
402 private static String[] getDepths(String resolution, DisplayMode[] modes) {
403 ArrayList<String> depths = new ArrayList<String>(4);
404 for (int i = 0; i < modes.length; i++) {
405 //Filter out all bit depths lower than 16 - Java incorrectly reports
406 //them as valid depths though the monitor does not support them
407 if (modes[i].getBitDepth() < 16) continue;
409 String res = modes[i].getWidth() + " x " + modes[i].getHeight();
410 String depth = modes[i].getBitDepth() + " bpp";
411 if (res.equals(resolution) && !depths.contains(depth))
415 String[] res = new String[depths.size()];
421 * Returns every possible refresh rate for the given resolution.
423 private static String[] getFrequencies(String resolution, DisplayMode[] modes) {
424 ArrayList<String> freqs = new ArrayList<String>(4);
425 for (int i = 0; i < modes.length; i++) {
426 String res = modes[i].getWidth() + " x " + modes[i].getHeight();
427 String freq = modes[i].getRefreshRate() + " Hz";
428 if (res.equals(resolution) && !freqs.contains(freq))
432 String[] res = new String[freqs.size()];
438 * Utility class for sorting <code>DisplayMode</code>s. Sorts by resolution,
439 * then bit depth, and then finally refresh rate.
441 private class DisplayModeSorter implements Comparator<DisplayMode> {
443 * @see java.util.Comparator#compare(java.lang.Object, java.lang.Object)
445 public int compare(DisplayMode a, DisplayMode b) {
447 if (a.getWidth() != b.getWidth())
448 return (a.getWidth() > b.getWidth()) ? 1 : -1;
450 if (a.getHeight() != b.getHeight())
451 return (a.getHeight() > b.getHeight()) ? 1 : -1;
453 if (a.getBitDepth() != b.getBitDepth())
454 return (a.getBitDepth() > b.getBitDepth()) ? 1 : -1;
456 if (a.getRefreshRate() != b.getRefreshRate())
457 return (a.getRefreshRate() > b.getRefreshRate()) ? 1 : -1;
458 //All fields are equal