/*
* Copyright (c) 2010, 2015, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package javafx.stage;
import java.security.AllPermission;
import java.security.AccessControlContext;
import java.security.AccessController;
import java.util.HashMap;
import com.sun.javafx.collections.annotations.ReturnsUnmodifiableCollection;
import javafx.beans.property.DoubleProperty;
import javafx.beans.property.DoublePropertyBase;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.ObjectPropertyBase;
import javafx.beans.property.ReadOnlyBooleanProperty;
import javafx.beans.property.ReadOnlyBooleanWrapper;
import javafx.beans.property.ReadOnlyObjectProperty;
import javafx.beans.property.ReadOnlyObjectWrapper;
import javafx.beans.property.ReadOnlyDoubleProperty;
import javafx.beans.property.ReadOnlyDoubleWrapper;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.property.SimpleDoubleProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.collections.ObservableMap;
import javafx.event.Event;
import javafx.event.EventDispatchChain;
import javafx.event.EventDispatcher;
import javafx.event.EventHandler;
import javafx.event.EventTarget;
import javafx.event.EventType;
import javafx.geometry.Rectangle2D;
import javafx.scene.Scene;
import com.sun.javafx.util.Utils;
import com.sun.javafx.css.StyleManager;
import com.sun.javafx.stage.WindowEventDispatcher;
import com.sun.javafx.stage.WindowHelper;
import com.sun.javafx.stage.WindowPeerListener;
import com.sun.javafx.tk.TKPulseListener;
import com.sun.javafx.tk.TKScene;
import com.sun.javafx.tk.TKStage;
import com.sun.javafx.tk.Toolkit;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
/**
*
* A top level window within which a scene is hosted, and with which the user
* interacts. A Window might be a {@link Stage}, {@link PopupWindow}, or other
* such top level. A Window is used also for browser plug-in based deployments.
*
*
* @since JavaFX 2.0
*/
public class Window implements EventTarget {
/**
* A list of all the currently _showing_ windows. This is publicly accessible via the unmodifiableWindows wrapper.
*/
private static ObservableList windows = FXCollections.observableArrayList();
private static ObservableList unmodifiableWindows = FXCollections.unmodifiableObservableList(windows);
static {
WindowHelper.setWindowAccessor(
new WindowHelper.WindowAccessor() {
/**
* Allow window peer listeners to directly change reported
* window location and size without changing the xExplicit,
* yExplicit, widthExplicit and heightExplicit values.
*/
@Override
public void notifyLocationChanged(
Window window, double x, double y) {
window.notifyLocationChanged(x, y);
}
@Override
public void notifySizeChanged(Window window,
double width,
double height) {
window.notifySizeChanged(width, height);
}
@Override
public void notifyScaleChanged(Window window,
double newOutputScaleX,
double newOutputScaleY) {
window.updateOutputScales(newOutputScaleX, newOutputScaleY);
}
@Override
public void notifyScreenChanged(Window window,
Object from,
Object to) {
window.notifyScreenChanged(from, to);
}
@Override
public float getPlatformScaleX(Window window) {
TKStage peer = window.impl_peer;
return peer == null ? 1.0f : peer.getPlatformScaleX();
}
@Override
public float getPlatformScaleY(Window window) {
TKStage peer = window.impl_peer;
return peer == null ? 1.0f : peer.getPlatformScaleY();
}
@Override
public ReadOnlyObjectProperty screenProperty(Window window) {
return window.screenProperty();
}
@Override
public AccessControlContext getAccessControlContext(Window window) {
return window.acc;
}
});
}
/**
* Returns a list containing a reference to the currently showing JavaFX windows. The list is unmodifiable -
* attempting to modify this list will result in an {@link UnsupportedOperationException} being thrown at runtime.
*
* @return A list containing all windows that are currently showing.
* @since 9
*/
@ReturnsUnmodifiableCollection
public static ObservableList getWindows() {
final SecurityManager securityManager = System.getSecurityManager();
if (securityManager != null) {
securityManager.checkPermission(new AllPermission());
}
return unmodifiableWindows;
}
final AccessControlContext acc = AccessController.getContext();
protected Window() {
// necessary for WindowCloseRequestHandler
initializeInternalEventDispatcher();
}
/**
* The listener that gets called by peer. It's also responsible for
* window size/location synchronization with the window peer, which
* occurs on every pulse.
*
* @treatAsPrivate implementation detail
* @deprecated This is an internal API that is not intended for use and will be removed in the next version
*/
@Deprecated
protected WindowPeerListener peerListener;
/**
* The peer of this Stage. All external access should be
* made though getPeer(). Implementors note: Please ensure that this
* variable is defined *after* style and *before* the other variables so
* that style has been initialized prior to this call, and so that
* impl_peer is initialized prior to subsequent initialization.
*
* @treatAsPrivate implementation detail
* @deprecated This is an internal API that is not intended for use and will be removed in the next version
*/
@Deprecated
protected volatile TKStage impl_peer;
private TKBoundsConfigurator peerBoundsConfigurator =
new TKBoundsConfigurator();
/**
* Get Stage's peer
*
* @treatAsPrivate implementation detail
* @deprecated This is an internal API that is not intended for use and will be removed in the next version
*/
@Deprecated
public TKStage impl_getPeer() {
return impl_peer;
}
/**
* @treatAsPrivate implementation detail
* @deprecated This is an internal API that is not intended for use and will be removed in the next version
*/
@Deprecated
public String impl_getMXWindowType() {
return getClass().getSimpleName();
}
/**
* Indicates if a user requested the window to be sized to match the scene
* size.
*/
private boolean sizeToScene = false;
/**
* Set the width and height of this Window to match the size of the content
* of this Window's Scene.
*/
public void sizeToScene() {
if (getScene() != null && impl_peer != null) {
getScene().impl_preferredSize();
adjustSize(false);
} else {
// Remember the request to reapply it later if needed
sizeToScene = true;
}
}
private void adjustSize(boolean selfSizePriority) {
if (getScene() == null) {
return;
}
if (impl_peer != null) {
double sceneWidth = getScene().getWidth();
double cw = (sceneWidth > 0) ? sceneWidth : -1;
double w = -1;
if (selfSizePriority && widthExplicit) {
w = getWidth();
} else if (cw <= 0) {
w = widthExplicit ? getWidth() : -1;
} else {
widthExplicit = false;
}
double sceneHeight = getScene().getHeight();
double ch = (sceneHeight > 0) ? sceneHeight : -1;
double h = -1;
if (selfSizePriority && heightExplicit) {
h = getHeight();
} else if (ch <= 0) {
h = heightExplicit ? getHeight() : -1;
} else {
heightExplicit = false;
}
peerBoundsConfigurator.setSize(w, h, cw, ch);
applyBounds();
}
}
private static final float CENTER_ON_SCREEN_X_FRACTION = 1.0f / 2;
private static final float CENTER_ON_SCREEN_Y_FRACTION = 1.0f / 3;
/**
* Sets x and y properties on this Window so that it is centered on the
* curent screen.
* The current screen is determined from the intersection of current window bounds and
* visual bounds of all screens.
*/
public void centerOnScreen() {
xExplicit = false;
yExplicit = false;
if (impl_peer != null) {
Rectangle2D bounds = getWindowScreen().getVisualBounds();
double centerX =
bounds.getMinX() + (bounds.getWidth() - getWidth())
* CENTER_ON_SCREEN_X_FRACTION;
double centerY =
bounds.getMinY() + (bounds.getHeight() - getHeight())
* CENTER_ON_SCREEN_Y_FRACTION;
x.set(centerX);
y.set(centerY);
peerBoundsConfigurator.setLocation(centerX, centerY,
CENTER_ON_SCREEN_X_FRACTION,
CENTER_ON_SCREEN_Y_FRACTION);
applyBounds();
}
}
private void updateOutputScales(double sx, double sy) {
// We call updateRenderScales() before updating the property
// values so that an application can listen to the properties
// and set their own values overriding the default values we set.
updateRenderScales(sx, sy);
// Now set the properties and trigger any potential listeners.
outputScaleX.set(sx);
outputScaleY.set(sy);
}
void updateRenderScales(double sx, double sy) {
boolean forceInt = forceIntegerRenderScale.get();
if (!renderScaleX.isBound()) {
renderScaleX.set(forceInt ? Math.ceil(sx) : sx);
}
if (!renderScaleY.isBound()) {
renderScaleY.set(forceInt ? Math.ceil(sy) : sy);
}
}
/**
* The scale that the {@code Window} will apply to horizontal scene
* coordinates in all stages of rendering and compositing the output
* to the screen or other destination device.
* This property is updated asynchronously by the system at various
* times including:
*
*
Window creation
*
At some point during moving a window to a new {@code Screen}
* which may be before or after the {@link Screen} property is updated.
*
In response to a change in user preferences for output scaling.
*
* @see outputScaleThreshold
*/
private ReadOnlyDoubleWrapper outputScaleX =
new ReadOnlyDoubleWrapper(this, "outputScaleX", 1.0);
public final double getOutputScaleX() {
return outputScaleX.get();
}
public final ReadOnlyDoubleProperty outputScaleXProperty() {
return outputScaleX.getReadOnlyProperty();
}
/**
* The scale that the {@code Window} will apply to vertical scene
* coordinates in all stages of rendering and compositing the output
* to the screen or other destination device.
* This property is updated asynchronously by the system at various
* times including:
*
*
Window creation
*
At some point during moving a window to a new {@code Screen}
* which may be before or after the {@link Screen} property is updated.
*
In response to a change in user preferences for output scaling.
*
* @see outputScaleThreshold
*/
private ReadOnlyDoubleWrapper outputScaleY =
new ReadOnlyDoubleWrapper(this, "outputScaleY", 1.0);
public final double getOutputScaleY() {
return outputScaleY.get();
}
public final ReadOnlyDoubleProperty outputScaleYProperty() {
return outputScaleY.getReadOnlyProperty();
}
/**
* Boolean property that controls whether only integer render scales
* are set by default by the system when there is a change in the
* associated output scale.
* The {@code renderScale} properties will be updated directly and
* simultaneously with any changes in the associated {@code outputScale}
* properties, but the values can be overridden by subsequent calls to
* the {@code setRenderScale} setters or through appropriate use of
* binding.
* This property will not prevent setting non-integer scales
* directly using the {@code renderScale} property object or the
* convenience setter method.
*/
private BooleanProperty forceIntegerRenderScale =
new SimpleBooleanProperty(this, "forceIntegerRenderScale", false) {
@Override
protected void invalidated() {
updateRenderScales(getOutputScaleX(),
getOutputScaleY());
}
};
public final void setForceIntegerRenderScale(boolean forced) {
forceIntegerRenderScale.set(forced);
}
public final boolean isForceIntegerRenderScale() {
return forceIntegerRenderScale.get();
}
public final BooleanProperty forceIntegerRenderScaleProperty() {
return forceIntegerRenderScale;
}
/**
* The horizontal scale that the {@code Window} will use when rendering
* its {@code Scene} to the rendering buffer.
* This property is automatically updated whenever there is a change in
* the {@link outputScaleX} property and can be overridden either by
* calling {@code setRenderScaleX()} in response to a listener on the
* {@code outputScaleX} property or by binding it appropriately.
*/
private DoubleProperty renderScaleX =
new SimpleDoubleProperty(this, "renderScaleX", 1.0) {
@Override
protected void invalidated() {
peerBoundsConfigurator.setRenderScaleX(get());
}
};
public final void setRenderScaleX(double scale) {
renderScaleX.set(scale);
}
public final double getRenderScaleX() {
return renderScaleX.get();
}
public final DoubleProperty renderScaleXProperty() {
return renderScaleX;
}
/**
* The vertical scale that the {@code Window} will use when rendering
* its {@code Scene} to the rendering buffer.
* This property is automatically updated whenever there is a change in
* the {@link outputScaleY} property and can be overridden either by
* calling {@code setRenderScaleY()} in response to a listener on the
* {@code outputScaleY} property or by binding it appropriately.
*/
private DoubleProperty renderScaleY =
new SimpleDoubleProperty(this, "renderScaleY", 1.0) {
@Override
protected void invalidated() {
peerBoundsConfigurator.setRenderScaleY(get());
}
};
public final void setRenderScaleY(double scale) {
renderScaleY.set(scale);
}
public final double getRenderScaleY() {
return renderScaleY.get();
}
public final DoubleProperty renderScaleYProperty() {
return renderScaleY;
}
private boolean xExplicit = false;
/**
* The horizontal location of this {@code Stage} on the screen. Changing
* this attribute will move the {@code Stage} horizontally. Changing this
* attribute will not visually affect a {@code Stage} while
* {@code fullScreen} is true, but will be honored by the {@code Stage} once
* {@code fullScreen} becomes false.
*/
private ReadOnlyDoubleWrapper x =
new ReadOnlyDoubleWrapper(this, "x", Double.NaN);
public final void setX(double value) {
setXInternal(value);
}
public final double getX() { return x.get(); }
public final ReadOnlyDoubleProperty xProperty() { return x.getReadOnlyProperty(); }
void setXInternal(double value) {
x.set(value);
peerBoundsConfigurator.setX(value, 0);
xExplicit = true;
}
private boolean yExplicit = false;
/**
* The vertical location of this {@code Stage} on the screen. Changing this
* attribute will move the {@code Stage} vertically. Changing this
* attribute will not visually affect a {@code Stage} while
* {@code fullScreen} is true, but will be honored by the {@code Stage} once
* {@code fullScreen} becomes false.
*/
private ReadOnlyDoubleWrapper y =
new ReadOnlyDoubleWrapper(this, "y", Double.NaN);
public final void setY(double value) {
setYInternal(value);
}
public final double getY() { return y.get(); }
public final ReadOnlyDoubleProperty yProperty() { return y.getReadOnlyProperty(); }
void setYInternal(double value) {
y.set(value);
peerBoundsConfigurator.setY(value, 0);
yExplicit = true;
}
/**
* Notification from the windowing system that the window's position has
* changed.
*
* @param newX the new window x position
* @param newY the new window y position
*/
void notifyLocationChanged(double newX, double newY) {
x.set(newX);
y.set(newY);
}
private boolean widthExplicit = false;
/**
* The width of this {@code Stage}. Changing this attribute will narrow or
* widen the width of the {@code Stage}. Changing this
* attribute will not visually affect a {@code Stage} while
* {@code fullScreen} is true, but will be honored by the {@code Stage} once
* {@code fullScreen} becomes false. This value includes any and all
* decorations which may be added by the Operating System such as resizable
* frame handles. Typical applications will set the {@link javafx.scene.Scene} width
* instead.
*
* The property is read only because it can be changed externally
* by the underlying platform and therefore must not be bindable.
*
*/
private ReadOnlyDoubleWrapper width =
new ReadOnlyDoubleWrapper(this, "width", Double.NaN);
public final void setWidth(double value) {
width.set(value);
peerBoundsConfigurator.setWindowWidth(value);
widthExplicit = true;
}
public final double getWidth() { return width.get(); }
public final ReadOnlyDoubleProperty widthProperty() { return width.getReadOnlyProperty(); }
private boolean heightExplicit = false;
/**
* The height of this {@code Stage}. Changing this attribute will shrink
* or heighten the height of the {@code Stage}. Changing this
* attribute will not visually affect a {@code Stage} while
* {@code fullScreen} is true, but will be honored by the {@code Stage} once
* {@code fullScreen} becomes false. This value includes any and all
* decorations which may be added by the Operating System such as the title
* bar. Typical applications will set the {@link javafx.scene.Scene} height instead.
*
* The property is read only because it can be changed externally
* by the underlying platform and therefore must not be bindable.
*
*/
private ReadOnlyDoubleWrapper height =
new ReadOnlyDoubleWrapper(this, "height", Double.NaN);
public final void setHeight(double value) {
height.set(value);
peerBoundsConfigurator.setWindowHeight(value);
heightExplicit = true;
}
public final double getHeight() { return height.get(); }
public final ReadOnlyDoubleProperty heightProperty() { return height.getReadOnlyProperty(); }
/**
* Notification from the windowing system that the window's size has
* changed.
*
* @param newWidth the new window width
* @param newHeight the new window height
*/
void notifySizeChanged(double newWidth, double newHeight) {
width.set(newWidth);
height.set(newHeight);
}
/**
* Whether or not this {@code Window} has the keyboard or input focus.
*
* The property is read only because it can be changed externally
* by the underlying platform and therefore must not be bindable.
*
*
* @profile common
*/
private ReadOnlyBooleanWrapper focused = new ReadOnlyBooleanWrapper() {
@Override protected void invalidated() {
focusChanged(get());
}
@Override
public Object getBean() {
return Window.this;
}
@Override
public String getName() {
return "focused";
}
};
/**
* @treatAsPrivate
* @deprecated
*/
@Deprecated
public final void setFocused(boolean value) { focused.set(value); }
/**
* Requests that this {@code Window} get the input focus.
*/
public final void requestFocus() {
if (impl_peer != null) {
impl_peer.requestFocus();
}
}
public final boolean isFocused() { return focused.get(); }
public final ReadOnlyBooleanProperty focusedProperty() { return focused.getReadOnlyProperty(); }
/*************************************************************************
* *
* *
* *
*************************************************************************/
private static final Object USER_DATA_KEY = new Object();
// A map containing a set of properties for this window
private ObservableMap