/* * 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(); } } }); 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; } } }