• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (c) 2009-2012 jMonkeyEngine
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions are
7  * met:
8  *
9  * * Redistributions of source code must retain the above copyright
10  *   notice, this list of conditions and the following disclaimer.
11  *
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.
15  *
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.
19  *
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.
31  */
32 package com.jme3.app;
33 
34 import com.jme3.system.AppSettings;
35 import java.awt.*;
36 import java.awt.event.*;
37 import java.awt.image.BufferedImage;
38 import java.lang.reflect.Method;
39 import java.net.MalformedURLException;
40 import java.net.URL;
41 import java.util.ArrayList;
42 import java.util.Arrays;
43 import java.util.Comparator;
44 import java.util.List;
45 import java.util.logging.Level;
46 import java.util.logging.Logger;
47 import java.util.prefs.BackingStoreException;
48 import javax.swing.*;
49 
50 /**
51  * <code>PropertiesDialog</code> provides an interface to make use of the
52  * <code>GameSettings</code> class. The <code>GameSettings</code> object
53  * is still created by the client application, and passed during construction.
54  *
55  * @see com.jme.system.GameSettings
56  * @author Mark Powell
57  * @author Eric Woroshow
58  * @author Joshua Slack - reworked for proper use of GL commands.
59  * @version $Id: LWJGLPropertiesDialog.java 4131 2009-03-19 20:15:28Z blaine.dev $
60  */
61 public final class SettingsDialog extends JDialog {
62 
63     public static interface SelectionListener {
64 
onSelection(int selection)65         public void onSelection(int selection);
66     }
67     private static final Logger logger = Logger.getLogger(SettingsDialog.class.getName());
68     private static final long serialVersionUID = 1L;
69     public static final int NO_SELECTION = 0,
70             APPROVE_SELECTION = 1,
71             CANCEL_SELECTION = 2;
72     // connection to properties file.
73     private final AppSettings source;
74     // Title Image
75     private URL imageFile = null;
76     // Array of supported display modes
77     private DisplayMode[] modes = null;
78     // Array of windowed resolutions
79     private String[] windowedResolutions = {"320 x 240", "640 x 480", "800 x 600",
80         "1024 x 768", "1152 x 864", "1280 x 720"};
81     // UI components
82     private JCheckBox vsyncBox = null;
83     private JCheckBox fullscreenBox = null;
84     private JComboBox displayResCombo = null;
85     private JComboBox colorDepthCombo = null;
86     private JComboBox displayFreqCombo = null;
87 //    private JComboBox rendererCombo = null;
88     private JComboBox antialiasCombo = null;
89     private JLabel icon = null;
90     private int selection = 0;
91     private SelectionListener selectionListener = null;
92 
93     /**
94      * Constructor for the <code>PropertiesDialog</code>. Creates a
95      * properties dialog initialized for the primary display.
96      *
97      * @param source
98      *            the <code>AppSettings</code> object to use for working with
99      *            the properties file.
100      * @param imageFile
101      *            the image file to use as the title of the dialog;
102      *            <code>null</code> will result in to image being displayed
103      * @throws NullPointerException
104      *             if the source is <code>null</code>
105      */
SettingsDialog(AppSettings source, String imageFile, boolean loadSettings)106     public SettingsDialog(AppSettings source, String imageFile, boolean loadSettings) {
107         this(source, getURL(imageFile), loadSettings);
108     }
109 
110     /**
111      * Constructor for the <code>PropertiesDialog</code>. Creates a
112      * properties dialog initialized for the primary display.
113      *
114      * @param source
115      *            the <code>GameSettings</code> object to use for working with
116      *            the properties file.
117      * @param imageFile
118      *            the image file to use as the title of the dialog;
119      *            <code>null</code> will result in to image being displayed
120      * @param loadSettings
121      * @throws JmeException
122      *             if the source is <code>null</code>
123      */
SettingsDialog(AppSettings source, URL imageFile, boolean loadSettings)124     public SettingsDialog(AppSettings source, URL imageFile, boolean loadSettings) {
125         if (source == null) {
126             throw new NullPointerException("Settings source cannot be null");
127         }
128 
129         this.source = source;
130         this.imageFile = imageFile;
131 
132 //        setModalityType(Dialog.ModalityType.APPLICATION_MODAL);
133         setModal(true);
134 
135         AppSettings registrySettings = new AppSettings(true);
136 
137         String appTitle;
138         if(source.getTitle()!=null){
139             appTitle = source.getTitle();
140         }else{
141            appTitle = registrySettings.getTitle();
142         }
143         try {
144             registrySettings.load(appTitle);
145         } catch (BackingStoreException ex) {
146             logger.log(Level.WARNING,
147                     "Failed to load settings", ex);
148         }
149 
150         if (loadSettings) {
151             source.copyFrom(registrySettings);
152         } else if(!registrySettings.isEmpty()) {
153             source.mergeFrom(registrySettings);
154         }
155 
156         GraphicsDevice device = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice();
157 
158         modes = device.getDisplayModes();
159         Arrays.sort(modes, new DisplayModeSorter());
160 
161         createUI();
162     }
163 
setSelectionListener(SelectionListener sl)164     public void setSelectionListener(SelectionListener sl) {
165         this.selectionListener = sl;
166     }
167 
getUserSelection()168     public int getUserSelection() {
169         return selection;
170     }
171 
setUserSelection(int selection)172     private void setUserSelection(int selection) {
173         this.selection = selection;
174         selectionListener.onSelection(selection);
175     }
176 
177     /**
178      * <code>setImage</code> sets the background image of the dialog.
179      *
180      * @param image
181      *            <code>String</code> representing the image file.
182      */
setImage(String image)183     public void setImage(String image) {
184         try {
185             URL file = new URL("file:" + image);
186             setImage(file);
187             // We can safely ignore the exception - it just means that the user
188             // gave us a bogus file
189         } catch (MalformedURLException e) {
190         }
191     }
192 
193     /**
194      * <code>setImage</code> sets the background image of this dialog.
195      *
196      * @param image
197      *            <code>URL</code> pointing to the image file.
198      */
setImage(URL image)199     public void setImage(URL image) {
200         icon.setIcon(new ImageIcon(image));
201         pack(); // Resize to accomodate the new image
202         setLocationRelativeTo(null); // put in center
203     }
204 
205     /**
206      * <code>showDialog</code> sets this dialog as visble, and brings it to
207      * the front.
208      */
showDialog()209     public void showDialog() {
210         setLocationRelativeTo(null);
211         setVisible(true);
212         toFront();
213     }
214 
215     /**
216      * <code>init</code> creates the components to use the dialog.
217      */
createUI()218     private void createUI() {
219         try {
220             UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
221         } catch (Exception e) {
222             logger.warning("Could not set native look and feel.");
223         }
224 
225         addWindowListener(new WindowAdapter() {
226 
227             public void windowClosing(WindowEvent e) {
228                 setUserSelection(CANCEL_SELECTION);
229                 dispose();
230             }
231         });
232 
233         if (source.getIcons() != null) {
234             safeSetIconImages( (List<BufferedImage>) Arrays.asList((BufferedImage[]) source.getIcons()) );
235         }
236 
237         setTitle("Select Display Settings");
238 
239         // The panels...
240         JPanel mainPanel = new JPanel();
241         JPanel centerPanel = new JPanel();
242         JPanel optionsPanel = new JPanel();
243         JPanel buttonPanel = new JPanel();
244         // The buttons...
245         JButton ok = new JButton("Ok");
246         JButton cancel = new JButton("Cancel");
247 
248         icon = new JLabel(imageFile != null ? new ImageIcon(imageFile) : null);
249 
250         mainPanel.setLayout(new BorderLayout());
251 
252         centerPanel.setLayout(new BorderLayout());
253 
254         KeyListener aListener = new KeyAdapter() {
255 
256             public void keyPressed(KeyEvent e) {
257                 if (e.getKeyCode() == KeyEvent.VK_ENTER) {
258                     if (verifyAndSaveCurrentSelection()) {
259                         setUserSelection(APPROVE_SELECTION);
260                         dispose();
261                     }
262                 }
263                 else if (e.getKeyCode() == KeyEvent.VK_ESCAPE) {
264                     setUserSelection(CANCEL_SELECTION);
265                     dispose();
266                 }
267             }
268         };
269 
270         displayResCombo = setUpResolutionChooser();
271         displayResCombo.addKeyListener(aListener);
272         colorDepthCombo = new JComboBox();
273         colorDepthCombo.addKeyListener(aListener);
274         displayFreqCombo = new JComboBox();
275         displayFreqCombo.addKeyListener(aListener);
276         antialiasCombo = new JComboBox();
277         antialiasCombo.addKeyListener(aListener);
278         fullscreenBox = new JCheckBox("Fullscreen?");
279         fullscreenBox.setSelected(source.isFullscreen());
280         fullscreenBox.addActionListener(new ActionListener() {
281 
282             public void actionPerformed(ActionEvent e) {
283                 updateResolutionChoices();
284             }
285         });
286         vsyncBox = new JCheckBox("VSync?");
287         vsyncBox.setSelected(source.isVSync());
288 //        rendererCombo = setUpRendererChooser();
289 //        rendererCombo.addKeyListener(aListener);
290 
291 
292 
293         updateResolutionChoices();
294         updateAntialiasChoices();
295         displayResCombo.setSelectedItem(source.getWidth() + " x " + source.getHeight());
296         colorDepthCombo.setSelectedItem(source.getBitsPerPixel() + " bpp");
297 
298         optionsPanel.add(displayResCombo);
299         optionsPanel.add(colorDepthCombo);
300         optionsPanel.add(displayFreqCombo);
301         optionsPanel.add(antialiasCombo);
302         optionsPanel.add(fullscreenBox);
303         optionsPanel.add(vsyncBox);
304 //        optionsPanel.add(rendererCombo);
305 
306         // Set the button action listeners. Cancel disposes without saving, OK
307         // saves.
308         ok.addActionListener(new ActionListener() {
309 
310             public void actionPerformed(ActionEvent e) {
311                 if (verifyAndSaveCurrentSelection()) {
312                     setUserSelection(APPROVE_SELECTION);
313                     dispose();
314                 }
315             }
316         });
317 
318         cancel.addActionListener(new ActionListener() {
319 
320             public void actionPerformed(ActionEvent e) {
321                 setUserSelection(CANCEL_SELECTION);
322                 dispose();
323             }
324         });
325 
326         buttonPanel.add(ok);
327         buttonPanel.add(cancel);
328 
329         if (icon != null) {
330             centerPanel.add(icon, BorderLayout.NORTH);
331         }
332         centerPanel.add(optionsPanel, BorderLayout.SOUTH);
333 
334         mainPanel.add(centerPanel, BorderLayout.CENTER);
335         mainPanel.add(buttonPanel, BorderLayout.SOUTH);
336 
337         this.getContentPane().add(mainPanel);
338 
339         pack();
340     }
341 
342     /* Access JDialog.setIconImages by reflection in case we're running on JRE < 1.6 */
safeSetIconImages(List<? extends Image> icons)343     private void safeSetIconImages(List<? extends Image> icons) {
344         try {
345             // Due to Java bug 6445278, we try to set icon on our shared owner frame first.
346             // Otherwise, our alt-tab icon will be the Java default under Windows.
347             Window owner = getOwner();
348             if (owner != null) {
349                 Method setIconImages = owner.getClass().getMethod("setIconImages", List.class);
350                 setIconImages.invoke(owner, icons);
351                 return;
352             }
353 
354             Method setIconImages = getClass().getMethod("setIconImages", List.class);
355             setIconImages.invoke(this, icons);
356         } catch (Exception e) {
357             return;
358         }
359     }
360 
361     /**
362      * <code>verifyAndSaveCurrentSelection</code> first verifies that the
363      * display mode is valid for this system, and then saves the current
364      * selection as a properties.cfg file.
365      *
366      * @return if the selection is valid
367      */
verifyAndSaveCurrentSelection()368     private boolean verifyAndSaveCurrentSelection() {
369         String display = (String) displayResCombo.getSelectedItem();
370         boolean fullscreen = fullscreenBox.isSelected();
371         boolean vsync = vsyncBox.isSelected();
372 
373         int width = Integer.parseInt(display.substring(0, display.indexOf(" x ")));
374         display = display.substring(display.indexOf(" x ") + 3);
375         int height = Integer.parseInt(display);
376 
377         String depthString = (String) colorDepthCombo.getSelectedItem();
378         int depth = -1;
379         if (depthString.equals("???")) {
380             depth = 0;
381         } else {
382             depth = Integer.parseInt(depthString.substring(0, depthString.indexOf(' ')));
383         }
384 
385         String freqString = (String) displayFreqCombo.getSelectedItem();
386         int freq = -1;
387         if (fullscreen) {
388             if (freqString.equals("???")) {
389                 freq = 0;
390             } else {
391                 freq = Integer.parseInt(freqString.substring(0, freqString.indexOf(' ')));
392             }
393         }
394 
395         String aaString = (String) antialiasCombo.getSelectedItem();
396         int multisample = -1;
397         if (aaString.equals("Disabled")) {
398             multisample = 0;
399         } else {
400             multisample = Integer.parseInt(aaString.substring(0, aaString.indexOf('x')));
401         }
402 
403         // FIXME: Does not work in Linux
404         /*
405          * if (!fullscreen) { //query the current bit depth of the desktop int
406          * curDepth = GraphicsEnvironment.getLocalGraphicsEnvironment()
407          * .getDefaultScreenDevice().getDisplayMode().getBitDepth(); if (depth >
408          * curDepth) { showError(this,"Cannot choose a higher bit depth in
409          * windowed " + "mode than your current desktop bit depth"); return
410          * false; } }
411          */
412 
413         String renderer = "LWJGL-OpenGL2";//(String) rendererCombo.getSelectedItem();
414 
415         boolean valid = false;
416 
417         // test valid display mode when going full screen
418         if (!fullscreen) {
419             valid = true;
420         } else {
421             GraphicsDevice device = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice();
422             valid = device.isFullScreenSupported();
423         }
424 
425         if (valid) {
426             //use the GameSettings class to save it.
427             source.setWidth(width);
428             source.setHeight(height);
429             source.setBitsPerPixel(depth);
430             source.setFrequency(freq);
431             source.setFullscreen(fullscreen);
432             source.setVSync(vsync);
433             //source.setRenderer(renderer);
434             source.setSamples(multisample);
435 
436             String appTitle = source.getTitle();
437 
438             try {
439                 source.save(appTitle);
440             } catch (BackingStoreException ex) {
441                 logger.log(Level.WARNING,
442                         "Failed to save setting changes", ex);
443             }
444         } else {
445             showError(
446                     this,
447                     "Your monitor claims to not support the display mode you've selected.\n"
448                     + "The combination of bit depth and refresh rate is not supported.");
449         }
450 
451         return valid;
452     }
453 
454     /**
455      * <code>setUpChooser</code> retrieves all available display modes and
456      * places them in a <code>JComboBox</code>. The resolution specified by
457      * GameSettings is used as the default value.
458      *
459      * @return the combo box of display modes.
460      */
setUpResolutionChooser()461     private JComboBox setUpResolutionChooser() {
462         String[] res = getResolutions(modes);
463         JComboBox resolutionBox = new JComboBox(res);
464 
465         resolutionBox.setSelectedItem(source.getWidth() + " x "
466                 + source.getHeight());
467         resolutionBox.addActionListener(new ActionListener() {
468 
469             public void actionPerformed(ActionEvent e) {
470                 updateDisplayChoices();
471             }
472         });
473 
474         return resolutionBox;
475     }
476 
477     /**
478      * <code>setUpRendererChooser</code> sets the list of available renderers.
479      * Data is obtained from the <code>DisplaySystem</code> class. The
480      * renderer specified by GameSettings is used as the default value.
481      *
482      * @return the list of renderers.
483      */
setUpRendererChooser()484     private JComboBox setUpRendererChooser() {
485         String modes[] = {"NULL", "JOGL-OpenGL1", "LWJGL-OpenGL2", "LWJGL-OpenGL3", "LWJGL-OpenGL3.1"};
486         JComboBox nameBox = new JComboBox(modes);
487         nameBox.setSelectedItem(source.getRenderer());
488         return nameBox;
489     }
490 
491     /**
492      * <code>updateDisplayChoices</code> updates the available color depth and
493      * display frequency options to match the currently selected resolution.
494      */
updateDisplayChoices()495     private void updateDisplayChoices() {
496         if (!fullscreenBox.isSelected()) {
497             // don't run this function when changing windowed settings
498             return;
499         }
500         String resolution = (String) displayResCombo.getSelectedItem();
501         String colorDepth = (String) colorDepthCombo.getSelectedItem();
502         if (colorDepth == null) {
503             colorDepth = source.getBitsPerPixel() + " bpp";
504         }
505         String displayFreq = (String) displayFreqCombo.getSelectedItem();
506         if (displayFreq == null) {
507             displayFreq = source.getFrequency() + " Hz";
508         }
509 
510         // grab available depths
511         String[] depths = getDepths(resolution, modes);
512         colorDepthCombo.setModel(new DefaultComboBoxModel(depths));
513         colorDepthCombo.setSelectedItem(colorDepth);
514         // grab available frequencies
515         String[] freqs = getFrequencies(resolution, modes);
516         displayFreqCombo.setModel(new DefaultComboBoxModel(freqs));
517         // Try to reset freq
518         displayFreqCombo.setSelectedItem(displayFreq);
519     }
520 
521     /**
522      * <code>updateResolutionChoices</code> updates the available resolutions
523      * list to match the currently selected window mode (fullscreen or
524      * windowed). It then sets up a list of standard options (if windowed) or
525      * calls <code>updateDisplayChoices</code> (if fullscreen).
526      */
updateResolutionChoices()527     private void updateResolutionChoices() {
528         if (!fullscreenBox.isSelected()) {
529             displayResCombo.setModel(new DefaultComboBoxModel(
530                     windowedResolutions));
531             colorDepthCombo.setModel(new DefaultComboBoxModel(new String[]{
532                         "24 bpp", "16 bpp"}));
533             displayFreqCombo.setModel(new DefaultComboBoxModel(
534                     new String[]{"n/a"}));
535             displayFreqCombo.setEnabled(false);
536         } else {
537             displayResCombo.setModel(new DefaultComboBoxModel(
538                     getResolutions(modes)));
539             displayFreqCombo.setEnabled(true);
540             updateDisplayChoices();
541         }
542     }
543 
updateAntialiasChoices()544     private void updateAntialiasChoices() {
545         // maybe in the future will add support for determining this info
546         // through pbuffer
547         String[] choices = new String[]{"Disabled", "2x", "4x", "6x", "8x", "16x"};
548         antialiasCombo.setModel(new DefaultComboBoxModel(choices));
549         antialiasCombo.setSelectedItem(choices[Math.min(source.getSamples()/2,5)]);
550     }
551 
552     //
553     // Utility methods
554     //
555     /**
556      * Utility method for converting a String denoting a file into a URL.
557      *
558      * @return a URL pointing to the file or null
559      */
getURL(String file)560     private static URL getURL(String file) {
561         URL url = null;
562         try {
563             url = new URL("file:" + file);
564         } catch (MalformedURLException e) {
565         }
566         return url;
567     }
568 
showError(java.awt.Component parent, String message)569     private static void showError(java.awt.Component parent, String message) {
570         JOptionPane.showMessageDialog(parent, message, "Error",
571                 JOptionPane.ERROR_MESSAGE);
572     }
573 
574     /**
575      * Returns every unique resolution from an array of <code>DisplayMode</code>s.
576      */
getResolutions(DisplayMode[] modes)577     private static String[] getResolutions(DisplayMode[] modes) {
578         ArrayList<String> resolutions = new ArrayList<String>(modes.length);
579         for (int i = 0; i < modes.length; i++) {
580             String res = modes[i].getWidth() + " x " + modes[i].getHeight();
581             if (!resolutions.contains(res)) {
582                 resolutions.add(res);
583             }
584         }
585 
586         String[] res = new String[resolutions.size()];
587         resolutions.toArray(res);
588         return res;
589     }
590 
591     /**
592      * Returns every possible bit depth for the given resolution.
593      */
getDepths(String resolution, DisplayMode[] modes)594     private static String[] getDepths(String resolution, DisplayMode[] modes) {
595         ArrayList<String> depths = new ArrayList<String>(4);
596         for (int i = 0; i < modes.length; i++) {
597             // Filter out all bit depths lower than 16 - Java incorrectly
598             // reports
599             // them as valid depths though the monitor does not support them
600             if (modes[i].getBitDepth() < 16 && modes[i].getBitDepth() > 0) {
601                 continue;
602             }
603 
604             String res = modes[i].getWidth() + " x " + modes[i].getHeight();
605             String depth = modes[i].getBitDepth() + " bpp";
606             if (res.equals(resolution) && !depths.contains(depth)) {
607                 depths.add(depth);
608             }
609         }
610 
611         if (depths.size() == 1 && depths.contains("-1 bpp")) {
612             // add some default depths, possible system is multi-depth supporting
613             depths.clear();
614             depths.add("24 bpp");
615         }
616 
617         String[] res = new String[depths.size()];
618         depths.toArray(res);
619         return res;
620     }
621 
622     /**
623      * Returns every possible refresh rate for the given resolution.
624      */
getFrequencies(String resolution, DisplayMode[] modes)625     private static String[] getFrequencies(String resolution,
626             DisplayMode[] modes) {
627         ArrayList<String> freqs = new ArrayList<String>(4);
628         for (int i = 0; i < modes.length; i++) {
629             String res = modes[i].getWidth() + " x " + modes[i].getHeight();
630             String freq;
631             if (modes[i].getRefreshRate() == DisplayMode.REFRESH_RATE_UNKNOWN) {
632                 freq = "???";
633             } else {
634                 freq = modes[i].getRefreshRate() + " Hz";
635             }
636 
637             if (res.equals(resolution) && !freqs.contains(freq)) {
638                 freqs.add(freq);
639             }
640         }
641 
642         String[] res = new String[freqs.size()];
643         freqs.toArray(res);
644         return res;
645     }
646 
647     /**
648      * Utility class for sorting <code>DisplayMode</code>s. Sorts by
649      * resolution, then bit depth, and then finally refresh rate.
650      */
651     private class DisplayModeSorter implements Comparator<DisplayMode> {
652 
653         /**
654          * @see java.util.Comparator#compare(java.lang.Object, java.lang.Object)
655          */
compare(DisplayMode a, DisplayMode b)656         public int compare(DisplayMode a, DisplayMode b) {
657             // Width
658             if (a.getWidth() != b.getWidth()) {
659                 return (a.getWidth() > b.getWidth()) ? 1 : -1;
660             }
661             // Height
662             if (a.getHeight() != b.getHeight()) {
663                 return (a.getHeight() > b.getHeight()) ? 1 : -1;
664             }
665             // Bit depth
666             if (a.getBitDepth() != b.getBitDepth()) {
667                 return (a.getBitDepth() > b.getBitDepth()) ? 1 : -1;
668             }
669             // Refresh rate
670             if (a.getRefreshRate() != b.getRefreshRate()) {
671                 return (a.getRefreshRate() > b.getRefreshRate()) ? 1 : -1;
672             }
673             // All fields are equal
674             return 0;
675         }
676     }
677 }
678