/*
* Copyright (c) 2009, 2015, Oracle and/or its affiliates. All rights reserved.
* ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*/
package javafx.stage;
import java.util.
List;
import java.util.concurrent.atomic.
AtomicBoolean;
import javafx.collections.
FXCollections;
import javafx.collections.
ObservableList;
import javafx.geometry.
Rectangle2D;
import com.sun.javafx.stage.
ScreenHelper;
import com.sun.javafx.tk.
ScreenConfigurationAccessor;
import com.sun.javafx.tk.
Toolkit;
/**
* Describes the characteristics of a graphics destination such as monitor.
* In a virtual device multi-screen environment in which the desktop area
* could span multiple physical screen devices, the bounds of the
* {@code Screen} objects are relative to the {@code Screen.primary}.
*
* <p>
* For example:
* <pre><code>
* Rectangle2D primaryScreenBounds = Screen.getPrimary().getVisualBounds();
*
* //set Stage boundaries to visible bounds of the main screen
* stage.setX(primaryScreenBounds.getMinX());
* stage.setY(primaryScreenBounds.getMinY());
* stage.setWidth(primaryScreenBounds.getWidth());
* stage.setHeight(primaryScreenBounds.getHeight());
*
* stage.show();
* </code></pre>
* </p>
* @since JavaFX 2.0
*/
public final class
Screen {
private static final
AtomicBoolean configurationDirty =
new
AtomicBoolean(true);
private static final
ScreenConfigurationAccessor accessor;
private static
Screen primary;
private static final
ObservableList<
Screen>
screens =
FXCollections.<
Screen>
observableArrayList();
private static final
ObservableList<
Screen>
unmodifiableScreens =
FXCollections.
unmodifiableObservableList(
screens);
static {
ScreenHelper.
setScreenAccessor(new
ScreenHelper.
ScreenAccessor() {
@
Override public float
getRenderScale(
Screen screen) { return
screen.
getRenderScale(); }
});
accessor =
Toolkit.
getToolkit().
setScreenConfigurationListener(() ->
updateConfiguration());
}
private
Screen() {
}
private static void
checkDirty() {
if (
configurationDirty.
compareAndSet(true, false)) {
updateConfiguration();
}
}
private static void
updateConfiguration() {
Object primaryScreen =
Toolkit.
getToolkit().
getPrimaryScreen();
Screen screenTmp =
nativeToScreen(
primaryScreen,
Screen.
primary);
if (
screenTmp != null) {
Screen.
primary =
screenTmp;
}
List<?>
screens =
Toolkit.
getToolkit().
getScreens();
// go through the list of new screens, see if they match the
// existing list; if they do reuse the list; if they don't
// at least try to reuse some of the old ones
ObservableList<
Screen>
newScreens =
FXCollections.<
Screen>
observableArrayList();
// if the size of the new and the old one are different just
// recreate the list
boolean
canKeepOld = (
Screen.
screens.
size() ==
screens.
size());
for (int
i = 0;
i <
screens.
size();
i++) {
Object obj =
screens.
get(
i);
Screen origScreen = null;
if (
canKeepOld) {
origScreen =
Screen.
screens.
get(
i);
}
Screen newScreen =
nativeToScreen(
obj,
origScreen);
if (
newScreen != null) {
if (
canKeepOld) {
canKeepOld = false;
newScreens.
clear();
newScreens.
addAll(
Screen.
screens.
subList(0,
i));
}
newScreens.
add(
newScreen);
}
}
if (!
canKeepOld) {
Screen.
screens.
clear();
Screen.
screens.
addAll(
newScreens);
}
configurationDirty.
set(false);
}
// returns null if the new one is to be equal the old one
private static
Screen nativeToScreen(
Object obj,
Screen screen) {
int
minX =
accessor.
getMinX(
obj);
int
minY =
accessor.
getMinY(
obj);
int
width =
accessor.
getWidth(
obj);
int
height =
accessor.
getHeight(
obj);
int
visualMinX =
accessor.
getVisualMinX(
obj);
int
visualMinY =
accessor.
getVisualMinY(
obj);
int
visualWidth =
accessor.
getVisualWidth(
obj);
int
visualHeight =
accessor.
getVisualHeight(
obj);
double
dpi =
accessor.
getDPI(
obj);
float
renderScale =
accessor.
getRenderScale(
obj);
if ((
screen == null) ||
(
screen.
bounds.
getMinX() !=
minX) ||
(
screen.
bounds.
getMinY() !=
minY) ||
(
screen.
bounds.
getWidth() !=
width) ||
(
screen.
bounds.
getHeight() !=
height) ||
(
screen.
visualBounds.
getMinX() !=
visualMinX) ||
(
screen.
visualBounds.
getMinY() !=
visualMinY) ||
(
screen.
visualBounds.
getWidth() !=
visualWidth) ||
(
screen.
visualBounds.
getHeight() !=
visualHeight) ||
(
screen.
dpi !=
dpi) ||
(
screen.
renderScale !=
renderScale))
{
Screen s = new
Screen();
s.
bounds = new
Rectangle2D(
minX,
minY,
width,
height);
s.
visualBounds = new
Rectangle2D(
visualMinX,
visualMinY,
visualWidth,
visualHeight);
s.
dpi =
dpi;
s.
renderScale =
renderScale;
return
s;
} else {
return null;
}
}
static
Screen getScreenForNative(
Object obj) {
double
x =
accessor.
getMinX(
obj);
double
y =
accessor.
getMinY(
obj);
double
w =
accessor.
getWidth(
obj);
double
h =
accessor.
getHeight(
obj);
Screen intScr = null;
for (int
i = 0;
i <
screens.
size();
i++) {
Screen scr =
screens.
get(
i);
if (
scr.
bounds.
contains(
x,
y,
w,
h)) {
return
scr;
}
if (
intScr == null &&
scr.
bounds.
intersects(
x,
y,
w,
h)) {
intScr =
scr;
}
}
return (
intScr == null) ?
getPrimary() :
intScr;
}
/**
* The primary {@code Screen}.
*/
public static
Screen getPrimary() {
checkDirty();
return
primary;
}
/**
* The observable list of currently available {@code Screens}.
*/
public static
ObservableList<
Screen>
getScreens() {
checkDirty();
return
unmodifiableScreens;
}
/**
* Returns a ObservableList of {@code Screens} that intersects the provided rectangle.
*
* @param x the x coordinate of the upper-left corner of the specified
* rectangular area
* @param y the y coordinate of the upper-left corner of the specified
* rectangular area
* @param width the width of the specified rectangular area
* @param height the height of the specified rectangular area
* @return a ObservableList of {@code Screens} for which {@code Screen.bounds}
* intersects the provided rectangle
*/
public static
ObservableList<
Screen>
getScreensForRectangle(
double
x, double
y, double
width, double
height)
{
checkDirty();
ObservableList<
Screen>
results =
FXCollections.<
Screen>
observableArrayList();
for (
Screen screen :
screens) {
if (
screen.
bounds.
intersects(
x,
y,
width,
height)) {
results.
add(
screen);
}
}
return
results;
}
/**
* Returns a ObservableList of {@code Screens} that intersects the provided rectangle.
*
* @param r The specified {@code Rectangle2D}
* @return a ObservableList of {@code Screens} for which {@code Screen.bounds}
* intersects the provided rectangle
*/
public static
ObservableList<
Screen>
getScreensForRectangle(
Rectangle2D r) {
checkDirty();
return
getScreensForRectangle(
r.
getMinX(),
r.
getMinY(),
r.
getWidth(),
r.
getHeight());
}
/**
* The bounds of this {@code Screen}.
*/
private
Rectangle2D bounds =
Rectangle2D.
EMPTY;
/**
* Gets the bounds of this {@code Screen}.
* @return The bounds of this {@code Screen}
*/
public final
Rectangle2D getBounds() {
return
bounds;
}
/**
* The visual bounds of this {@code Screen}.
*
* These bounds account for objects in the native windowing system such as
* task bars and menu bars. These bounds are contained by {@code Screen.bounds}.
*/
private
Rectangle2D visualBounds =
Rectangle2D.
EMPTY;
/**
* Gets the visual bounds of this {@code Screen}.
*
* These bounds account for objects in the native windowing system such as
* task bars and menu bars. These bounds are contained by {@code Screen.bounds}.
* @return The visual bounds of this {@code Screen}
*/
public final
Rectangle2D getVisualBounds() {
return
visualBounds;
}
/**
* The resolution (dots per inch) of this {@code Screen}.
*/
private double
dpi;
/**
* Gets the resolution (dots per inch) of this {@code Screen}.
* @return The resolution of this @{code Screen}
*/
public final double
getDpi() {
return
dpi;
}
/**
* The scale factor of this {@code Screen}.
*/
private float
renderScale;
/**
* Gets the scale factor of this {@code Screen}.
* E.g. on Retina displays on Mac the scale factor may be equal to 2.0.
* On regular displays this method returns 1.0.
*/
private float
getRenderScale() {
return
renderScale;
}
/**
* Returns a hash code for this {@code Screen} object.
* @return a hash code for this {@code Screen} object.
*/
@
Override public int
hashCode() {
long
bits = 7L;
bits = 37L *
bits +
bounds.
hashCode();
bits = 37L *
bits +
visualBounds.
hashCode();
bits = 37L *
bits +
Double.
doubleToLongBits(
dpi);
bits = 37L *
bits +
Float.
floatToIntBits(
renderScale);
return (int) (
bits ^ (
bits >> 32));
}
/**
* Indicates whether some other object is "equal to" this one.
* @param obj the reference object with which to compare.
* @return {@code true} if this object is equal to the {@code obj} argument; {@code false} otherwise.
*/
@
Override public boolean
equals(
Object obj) {
if (
obj == this) return true;
if (
obj instanceof
Screen) {
Screen other = (
Screen)
obj;
return (
bounds == null ?
other.
bounds == null :
bounds.
equals(
other.
bounds))
&& (
visualBounds == null ?
other.
visualBounds == null :
visualBounds.
equals(
other.
visualBounds))
&&
other.
dpi ==
dpi
&&
other.
renderScale ==
renderScale;
} else return false;
}
/**
* Returns a string representation of this {@code Screen} object.
* @return a string representation of this {@code Screen} object.
*/
@
Override public
String toString() {
return super.toString() + " bounds:" +
bounds + " visualBounds:" +
visualBounds + " dpi:" +
dpi + " renderScale:" +
renderScale;
}
}