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 }