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 }