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.tk.ScreenConfigurationAccessor; 36 import com.sun.javafx.tk.Toolkit; 37 38 /** 39 * Describes the characteristics of a graphics destination such as monitor. 40 * In a virtual device multi-screen environment in which the desktop area 41 * could span multiple physical screen devices, the bounds of the 42 * {@code Screen} objects are relative to the {@code Screen.primary}. 43 * 44 * <p> 45 * For example: 46 * <pre><code> 47 * Rectangle2D primaryScreenBounds = Screen.getPrimary().getVisualBounds(); 48 * 49 * //set Stage boundaries to visible bounds of the main screen 50 * stage.setX(primaryScreenBounds.getMinX()); 51 * stage.setY(primaryScreenBounds.getMinY()); 52 * stage.setWidth(primaryScreenBounds.getWidth()); 53 * stage.setHeight(primaryScreenBounds.getHeight()); 54 * 55 * stage.show(); 56 * </code></pre> 57 * </p> 58 * @since JavaFX 2.0 59 */ 60 public final class Screen { 61 62 private static final AtomicBoolean configurationDirty = 63 new AtomicBoolean(true); 64 65 private static final ScreenConfigurationAccessor accessor; 66 67 private static Screen primary; 68 private static final ObservableList<Screen> screens = 69 FXCollections.<Screen>observableArrayList(); 70 private static final ObservableList<Screen> unmodifiableScreens = 71 FXCollections.unmodifiableObservableList(screens); 72 73 static { 74 accessor = Toolkit.getToolkit().setScreenConfigurationListener(() -> updateConfiguration()); 75 } 76 77 private Screen() { 78 } 79 80 private static void checkDirty() { 81 if (configurationDirty.compareAndSet(true, false)) { 82 updateConfiguration(); 83 } 84 } 85 86 private static void updateConfiguration() { 87 Object primaryScreen = Toolkit.getToolkit().getPrimaryScreen(); 88 Screen screenTmp = nativeToScreen(primaryScreen, Screen.primary); 89 if (screenTmp != null) { 90 Screen.primary = screenTmp; 91 } 92 93 List<?> screens = Toolkit.getToolkit().getScreens(); 94 // go through the list of new screens, see if they match the 95 // existing list; if they do reuse the list; if they don't 96 // at least try to reuse some of the old ones 97 ObservableList<Screen> newScreens = FXCollections.<Screen>observableArrayList(); 98 // if the size of the new and the old one are different just 99 // recreate the list 100 boolean canKeepOld = (Screen.screens.size() == screens.size()); 101 for (int i = 0; i < screens.size(); i++) { 102 Object obj = screens.get(i); 103 Screen origScreen = null; 104 if (canKeepOld) { 105 origScreen = Screen.screens.get(i); 106 } 107 Screen newScreen = nativeToScreen(obj, origScreen); 108 if (newScreen != null) { 109 if (canKeepOld) { 110 canKeepOld = false; 111 newScreens.clear(); 112 newScreens.addAll(Screen.screens.subList(0, i)); 113 } 114 newScreens.add(newScreen); 115 } 116 } 117 if (!canKeepOld) { 118 Screen.screens.clear(); 119 Screen.screens.addAll(newScreens); 120 } 121 122 configurationDirty.set(false); 123 } 124 125 // returns null if the new one is to be equal the old one 126 private static Screen nativeToScreen(Object obj, Screen screen) { 127 int minX = accessor.getMinX(obj); 128 int minY = accessor.getMinY(obj); 129 int width = accessor.getWidth(obj); 130 int height = accessor.getHeight(obj); 131 int visualMinX = accessor.getVisualMinX(obj); 132 int visualMinY = accessor.getVisualMinY(obj); 133 int visualWidth = accessor.getVisualWidth(obj); 134 int visualHeight = accessor.getVisualHeight(obj); 135 double dpi = accessor.getDPI(obj); 136 float outScaleX = accessor.getRecommendedOutputScaleX(obj); 137 float outScaleY = accessor.getRecommendedOutputScaleY(obj); 138 if ((screen == null) || 139 (screen.bounds.getMinX() != minX) || 140 (screen.bounds.getMinY() != minY) || 141 (screen.bounds.getWidth() != width) || 142 (screen.bounds.getHeight() != height) || 143 (screen.visualBounds.getMinX() != visualMinX) || 144 (screen.visualBounds.getMinY() != visualMinY) || 145 (screen.visualBounds.getWidth() != visualWidth) || 146 (screen.visualBounds.getHeight() != visualHeight) || 147 (screen.dpi != dpi) || 148 (screen.outputScaleX != outScaleX) || 149 (screen.outputScaleY != outScaleY)) 150 { 151 Screen s = new Screen(); 152 s.bounds = new Rectangle2D(minX, minY, width, height); 153 s.visualBounds = new Rectangle2D(visualMinX, visualMinY, visualWidth, visualHeight); 154 s.dpi = dpi; 155 s.outputScaleX = outScaleX; 156 s.outputScaleY = outScaleY; 157 return s; 158 } else { 159 return null; 160 } 161 } 162 163 static Screen getScreenForNative(Object obj) { 164 double x = accessor.getMinX(obj); 165 double y = accessor.getMinY(obj); 166 double w = accessor.getWidth(obj); 167 double h = accessor.getHeight(obj); 168 Screen intScr = null; 169 for (int i = 0; i < screens.size(); i++) { 170 Screen scr = screens.get(i); 171 if (scr.bounds.contains(x, y, w, h)) { 172 return scr; 173 } 174 if (intScr == null && scr.bounds.intersects(x, y, w, h)) { 175 intScr = scr; 176 } 177 } 178 return (intScr == null) ? getPrimary() : intScr; 179 } 180 181 /** 182 * The primary {@code Screen}. 183 */ 184 public static Screen getPrimary() { 185 checkDirty(); 186 return primary; 187 } 188 189 /** 190 * The observable list of currently available {@code Screens}. 191 */ 192 public static ObservableList<Screen> getScreens() { 193 checkDirty(); 194 return unmodifiableScreens; 195 } 196 197 /** 198 * Returns a ObservableList of {@code Screens} that intersects the provided rectangle. 199 * 200 * @param x the x coordinate of the upper-left corner of the specified 201 * rectangular area 202 * @param y the y coordinate of the upper-left corner of the specified 203 * rectangular area 204 * @param width the width of the specified rectangular area 205 * @param height the height of the specified rectangular area 206 * @return a ObservableList of {@code Screens} for which {@code Screen.bounds} 207 * intersects the provided rectangle 208 */ 209 public static ObservableList<Screen> getScreensForRectangle( 210 double x, double y, double width, double height) 211 { 212 checkDirty(); 213 ObservableList<Screen> results = FXCollections.<Screen>observableArrayList(); 214 for (Screen screen : screens) { 215 if (screen.bounds.intersects(x, y, width, height)) { 216 results.add(screen); 217 } 218 } 219 return results; 220 } 221 222 /** 223 * Returns a ObservableList of {@code Screens} that intersects the provided rectangle. 224 * 225 * @param r The specified {@code Rectangle2D} 226 * @return a ObservableList of {@code Screens} for which {@code Screen.bounds} 227 * intersects the provided rectangle 228 */ 229 public static ObservableList<Screen> getScreensForRectangle(Rectangle2D r) { 230 checkDirty(); 231 return getScreensForRectangle(r.getMinX(), r.getMinY(), r.getWidth(), r.getHeight()); 232 } 233 234 /** 235 * The bounds of this {@code Screen}. 236 */ 237 private Rectangle2D bounds = Rectangle2D.EMPTY; 238 /** 239 * Gets the bounds of this {@code Screen}. 240 * The bounds will be reported adjusted for the {@code outputScale} so 241 * that resizing a {@code Window} with these bounds and the same 242 * {@code outputScale} as this {@code Screen} will cover the entire 243 * screen. 244 * @return The bounds of this {@code Screen} 245 */ 246 public final Rectangle2D getBounds() { 247 return bounds; 248 } 249 250 /** 251 * The visual bounds of this {@code Screen}. 252 * 253 * These bounds account for objects in the native windowing system such as 254 * task bars and menu bars. These bounds are contained by {@code Screen.bounds}. 255 */ 256 private Rectangle2D visualBounds = Rectangle2D.EMPTY; 257 /** 258 * Gets the visual bounds of this {@code Screen}. 259 * 260 * These bounds account for objects in the native windowing system such as 261 * task bars and menu bars. These bounds are contained by {@code Screen.bounds}. 262 * @return The visual bounds of this {@code Screen} 263 */ 264 public final Rectangle2D getVisualBounds() { 265 return visualBounds; 266 } 267 268 /** 269 * The resolution (dots per inch) of this {@code Screen}. 270 */ 271 private double dpi; 272 /** 273 * Gets the resolution (dots per inch) of this {@code Screen}. 274 * @return The resolution of this @{code Screen} 275 */ 276 public final double getDpi() { 277 return dpi; 278 } 279 280 /** 281 * The recommended output scale factor of this {@code Screen} in the 282 * X direction. 283 */ 284 private float outputScaleX; 285 286 /** 287 * Gets the recommended output scale factor of this {@code Screen} in 288 * the horizontal ({@code X}) direction. 289 * This scale factor should be applied to a scene in order to compensate 290 * for the resolution and viewing distance of the output device. 291 * The visual bounds will be reported relative to this scale factor. 292 * @return the recommended output scale factor for the screen. 293 */ 294 public final double getOutputScaleX() { 295 return outputScaleX; 296 } 297 298 /** 299 * The recommended output scale factor of this {@code Screen} in the 300 * Y direction. 301 */ 302 private float outputScaleY; 303 304 /** 305 * Gets the recommended output scale factor of this {@code Screen} in 306 * the vertical ({@code Y}) direction. 307 * This scale factor will be applied to the scene in order to compensate 308 * for the resolution and viewing distance of the output device. 309 * The visual bounds will be reported relative to this scale factor. 310 * @return the recommended output scale factor for the screen. 311 */ 312 public final double getOutputScaleY() { 313 return outputScaleY; 314 } 315 316 /** 317 * Returns a hash code for this {@code Screen} object. 318 * @return a hash code for this {@code Screen} object. 319 */ 320 @Override public int hashCode() { 321 long bits = 7L; 322 bits = 37L * bits + bounds.hashCode(); 323 bits = 37L * bits + visualBounds.hashCode(); 324 bits = 37L * bits + Double.doubleToLongBits(dpi); 325 bits = 37L * bits + Float.floatToIntBits(outputScaleX); 326 bits = 37L * bits + Float.floatToIntBits(outputScaleY); 327 return (int) (bits ^ (bits >> 32)); 328 } 329 330 /** 331 * Indicates whether some other object is "equal to" this one. 332 * @param obj the reference object with which to compare. 333 * @return {@code true} if this object is equal to the {@code obj} argument; {@code false} otherwise. 334 */ 335 @Override public boolean equals(Object obj) { 336 if (obj == this) return true; 337 if (obj instanceof Screen) { 338 Screen other = (Screen) obj; 339 return (bounds == null ? other.bounds == null : bounds.equals(other.bounds)) 340 && (visualBounds == null ? other.visualBounds == null : visualBounds.equals(other.visualBounds)) 341 && other.dpi == dpi 342 && other.outputScaleX == outputScaleX && other.outputScaleY == outputScaleY; 343 } else return false; 344 } 345 346 /** 347 * Returns a string representation of this {@code Screen} object. 348 * @return a string representation of this {@code Screen} object. 349 */ 350 @Override public String toString() { 351 return super.toString() + " bounds:" + bounds + " visualBounds:" + visualBounds + " dpi:" 352 + dpi + " outputScale:(" + outputScaleX + "," + outputScaleY + ")"; 353 } 354 }