1 /* 2 * Copyright (c) 2009, 2015, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package javafx.stage; 27 28 import java.util.List; 29 import java.util.concurrent.atomic.AtomicBoolean; 30 31 import javafx.collections.FXCollections; 32 import javafx.collections.ObservableList; 33 import javafx.geometry.Rectangle2D; 34 35 import com.sun.javafx.stage.ScreenHelper; 36 import com.sun.javafx.tk.ScreenConfigurationAccessor; 37 import com.sun.javafx.tk.Toolkit; 38 39 /** 40 * Describes the characteristics of a graphics destination such as monitor. 41 * In a virtual device multi-screen environment in which the desktop area 42 * could span multiple physical screen devices, the bounds of the 43 * {@code Screen} objects are relative to the {@code Screen.primary}. 44 * 45 * <p> 46 * For example: 47 * <pre><code> 48 * Rectangle2D primaryScreenBounds = Screen.getPrimary().getVisualBounds(); 49 * 50 * //set Stage boundaries to visible bounds of the main screen 51 * stage.setX(primaryScreenBounds.getMinX()); 52 * stage.setY(primaryScreenBounds.getMinY()); 53 * stage.setWidth(primaryScreenBounds.getWidth()); 54 * stage.setHeight(primaryScreenBounds.getHeight()); 55 * 56 * stage.show(); 57 * </code></pre> 58 * </p> 59 * @since JavaFX 2.0 60 */ 61 public final class Screen { 62 63 private static final AtomicBoolean configurationDirty = 64 new AtomicBoolean(true); 65 66 private static final ScreenConfigurationAccessor accessor; 67 68 private static Screen primary; 69 private static final ObservableList<Screen> screens = 70 FXCollections.<Screen>observableArrayList(); 71 private static final ObservableList<Screen> unmodifiableScreens = 72 FXCollections.unmodifiableObservableList(screens); 73 74 static { 75 ScreenHelper.setScreenAccessor(new ScreenHelper.ScreenAccessor() { 76 @Override public float getRenderScale(Screen screen) { return screen.getRenderScale(); } 77 }); 78 79 accessor = Toolkit.getToolkit().setScreenConfigurationListener(() -> updateConfiguration()); 80 } 81 82 private Screen() { 83 } 84 85 private static void checkDirty() { 86 if (configurationDirty.compareAndSet(true, false)) { 87 updateConfiguration(); 88 } 89 } 90 91 private static void updateConfiguration() { 92 Object primaryScreen = Toolkit.getToolkit().getPrimaryScreen(); 93 Screen screenTmp = nativeToScreen(primaryScreen, Screen.primary); 94 if (screenTmp != null) { 95 Screen.primary = screenTmp; 96 } 97 98 List<?> screens = Toolkit.getToolkit().getScreens(); 99 // go through the list of new screens, see if they match the 100 // existing list; if they do reuse the list; if they don't 101 // at least try to reuse some of the old ones 102 ObservableList<Screen> newScreens = FXCollections.<Screen>observableArrayList(); 103 // if the size of the new and the old one are different just 104 // recreate the list 105 boolean canKeepOld = (Screen.screens.size() == screens.size()); 106 for (int i = 0; i < screens.size(); i++) { 107 Object obj = screens.get(i); 108 Screen origScreen = null; 109 if (canKeepOld) { 110 origScreen = Screen.screens.get(i); 111 } 112 Screen newScreen = nativeToScreen(obj, origScreen); 113 if (newScreen != null) { 114 if (canKeepOld) { 115 canKeepOld = false; 116 newScreens.clear(); 117 newScreens.addAll(Screen.screens.subList(0, i)); 118 } 119 newScreens.add(newScreen); 120 } 121 } 122 if (!canKeepOld) { 123 Screen.screens.clear(); 124 Screen.screens.addAll(newScreens); 125 } 126 127 configurationDirty.set(false); 128 } 129 130 // returns null if the new one is to be equal the old one 131 private static Screen nativeToScreen(Object obj, Screen screen) { 132 int minX = accessor.getMinX(obj); 133 int minY = accessor.getMinY(obj); 134 int width = accessor.getWidth(obj); 135 int height = accessor.getHeight(obj); 136 int visualMinX = accessor.getVisualMinX(obj); 137 int visualMinY = accessor.getVisualMinY(obj); 138 int visualWidth = accessor.getVisualWidth(obj); 139 int visualHeight = accessor.getVisualHeight(obj); 140 double dpi = accessor.getDPI(obj); 141 float renderScale = accessor.getRenderScale(obj); 142 if ((screen == null) || 143 (screen.bounds.getMinX() != minX) || 144 (screen.bounds.getMinY() != minY) || 145 (screen.bounds.getWidth() != width) || 146 (screen.bounds.getHeight() != height) || 147 (screen.visualBounds.getMinX() != visualMinX) || 148 (screen.visualBounds.getMinY() != visualMinY) || 149 (screen.visualBounds.getWidth() != visualWidth) || 150 (screen.visualBounds.getHeight() != visualHeight) || 151 (screen.dpi != dpi) || 152 (screen.renderScale != renderScale)) 153 { 154 Screen s = new Screen(); 155 s.bounds = new Rectangle2D(minX, minY, width, height); 156 s.visualBounds = new Rectangle2D(visualMinX, visualMinY, visualWidth, visualHeight); 157 s.dpi = dpi; 158 s.renderScale = renderScale; 159 return s; 160 } else { 161 return null; 162 } 163 } 164 165 static Screen getScreenForNative(Object obj) { 166 double x = accessor.getMinX(obj); 167 double y = accessor.getMinY(obj); 168 double w = accessor.getWidth(obj); 169 double h = accessor.getHeight(obj); 170 Screen intScr = null; 171 for (int i = 0; i < screens.size(); i++) { 172 Screen scr = screens.get(i); 173 if (scr.bounds.contains(x, y, w, h)) { 174 return scr; 175 } 176 if (intScr == null && scr.bounds.intersects(x, y, w, h)) { 177 intScr = scr; 178 } 179 } 180 return (intScr == null) ? getPrimary() : intScr; 181 } 182 183 /** 184 * The primary {@code Screen}. 185 */ 186 public static Screen getPrimary() { 187 checkDirty(); 188 return primary; 189 } 190 191 /** 192 * The observable list of currently available {@code Screens}. 193 */ 194 public static ObservableList<Screen> getScreens() { 195 checkDirty(); 196 return unmodifiableScreens; 197 } 198 199 /** 200 * Returns a ObservableList of {@code Screens} that intersects the provided rectangle. 201 * 202 * @param x the x coordinate of the upper-left corner of the specified 203 * rectangular area 204 * @param y the y coordinate of the upper-left corner of the specified 205 * rectangular area 206 * @param width the width of the specified rectangular area 207 * @param height the height of the specified rectangular area 208 * @return a ObservableList of {@code Screens} for which {@code Screen.bounds} 209 * intersects the provided rectangle 210 */ 211 public static ObservableList<Screen> getScreensForRectangle( 212 double x, double y, double width, double height) 213 { 214 checkDirty(); 215 ObservableList<Screen> results = FXCollections.<Screen>observableArrayList(); 216 for (Screen screen : screens) { 217 if (screen.bounds.intersects(x, y, width, height)) { 218 results.add(screen); 219 } 220 } 221 return results; 222 } 223 224 /** 225 * Returns a ObservableList of {@code Screens} that intersects the provided rectangle. 226 * 227 * @param r The specified {@code Rectangle2D} 228 * @return a ObservableList of {@code Screens} for which {@code Screen.bounds} 229 * intersects the provided rectangle 230 */ 231 public static ObservableList<Screen> getScreensForRectangle(Rectangle2D r) { 232 checkDirty(); 233 return getScreensForRectangle(r.getMinX(), r.getMinY(), r.getWidth(), r.getHeight()); 234 } 235 236 /** 237 * The bounds of this {@code Screen}. 238 */ 239 private Rectangle2D bounds = Rectangle2D.EMPTY; 240 /** 241 * Gets the bounds of this {@code Screen}. 242 * @return The bounds of this {@code Screen} 243 */ 244 public final Rectangle2D getBounds() { 245 return bounds; 246 } 247 248 /** 249 * The visual bounds of this {@code Screen}. 250 * 251 * These bounds account for objects in the native windowing system such as 252 * task bars and menu bars. These bounds are contained by {@code Screen.bounds}. 253 */ 254 private Rectangle2D visualBounds = Rectangle2D.EMPTY; 255 /** 256 * Gets the visual bounds of this {@code Screen}. 257 * 258 * These bounds account for objects in the native windowing system such as 259 * task bars and menu bars. These bounds are contained by {@code Screen.bounds}. 260 * @return The visual bounds of this {@code Screen} 261 */ 262 public final Rectangle2D getVisualBounds() { 263 return visualBounds; 264 } 265 266 /** 267 * The resolution (dots per inch) of this {@code Screen}. 268 */ 269 private double dpi; 270 /** 271 * Gets the resolution (dots per inch) of this {@code Screen}. 272 * @return The resolution of this @{code Screen} 273 */ 274 public final double getDpi() { 275 return dpi; 276 } 277 278 /** 279 * The scale factor of this {@code Screen}. 280 */ 281 private float renderScale; 282 283 /** 284 * Gets the scale factor of this {@code Screen}. 285 * E.g. on Retina displays on Mac the scale factor may be equal to 2.0. 286 * On regular displays this method returns 1.0. 287 */ 288 private float getRenderScale() { 289 return renderScale; 290 } 291 292 /** 293 * Returns a hash code for this {@code Screen} object. 294 * @return a hash code for this {@code Screen} object. 295 */ 296 @Override public int hashCode() { 297 long bits = 7L; 298 bits = 37L * bits + bounds.hashCode(); 299 bits = 37L * bits + visualBounds.hashCode(); 300 bits = 37L * bits + Double.doubleToLongBits(dpi); 301 bits = 37L * bits + Float.floatToIntBits(renderScale); 302 return (int) (bits ^ (bits >> 32)); 303 } 304 305 /** 306 * Indicates whether some other object is "equal to" this one. 307 * @param obj the reference object with which to compare. 308 * @return {@code true} if this object is equal to the {@code obj} argument; {@code false} otherwise. 309 */ 310 @Override public boolean equals(Object obj) { 311 if (obj == this) return true; 312 if (obj instanceof Screen) { 313 Screen other = (Screen) obj; 314 return (bounds == null ? other.bounds == null : bounds.equals(other.bounds)) 315 && (visualBounds == null ? other.visualBounds == null : visualBounds.equals(other.visualBounds)) 316 && other.dpi == dpi 317 && other.renderScale == renderScale; 318 } else return false; 319 } 320 321 /** 322 * Returns a string representation of this {@code Screen} object. 323 * @return a string representation of this {@code Screen} object. 324 */ 325 @Override public String toString() { 326 return super.toString() + " bounds:" + bounds + " visualBounds:" + visualBounds + " dpi:" + dpi + " renderScale:" + renderScale; 327 } 328 }