Java tutorial
/* * Copyright (c) 2010, 2018, 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.AccessControlContext; import java.security.AccessController; import java.util.HashMap; import javafx.application.Platform; 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; import static com.sun.javafx.FXPermissions.ACCESS_WINDOW_LIST_PERMISSION; import com.sun.javafx.scene.NodeHelper; import com.sun.javafx.scene.SceneHelper; /** * 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 window. * <p> * Window objects must be constructed and modified on the * JavaFX Application Thread. * </p> * <p> * The JavaFX Application Thread is created as part of the startup process for * the JavaFX runtime. See the {@link javafx.application.Application} class and * the {@link Platform#startup(Runnable)} method for more information. * </p> * * @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<Window> windows = FXCollections.observableArrayList(); private static ObservableList<Window> unmodifiableWindows = FXCollections.unmodifiableObservableList(windows); /* * Store the singleton instance of the WindowHelper subclass corresponding * to the subclass of this instance of Window */ private WindowHelper windowHelper = null; static { WindowHelper.setWindowAccessor(new WindowHelper.WindowAccessor() { @Override public WindowHelper getHelper(Window window) { return window.windowHelper; } @Override public void setHelper(Window window, WindowHelper windowHelper) { window.windowHelper = windowHelper; } @Override public void doVisibleChanging(Window window, boolean visible) { window.doVisibleChanging(visible); } @Override public void doVisibleChanged(Window window, boolean visible) { window.doVisibleChanged(visible); } @Override public TKStage getPeer(Window window) { return window.getPeer(); } @Override public void setPeer(Window window, TKStage peer) { window.setPeer(peer); } @Override public WindowPeerListener getPeerListener(Window window) { return window.getPeerListener(); } @Override public void setPeerListener(Window window, WindowPeerListener peerListener) { window.setPeerListener(peerListener); } @Override public void setFocused(Window window, boolean value) { window.setFocused(value); } /* * 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.getPeer(); return peer == null ? 1.0f : peer.getPlatformScaleX(); } @Override public float getPlatformScaleY(Window window) { TKStage peer = window.getPeer(); return peer == null ? 1.0f : peer.getPlatformScaleY(); } @Override public ReadOnlyObjectProperty<Screen> 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 */ public static ObservableList<Window> getWindows() { final SecurityManager securityManager = System.getSecurityManager(); if (securityManager != null) { securityManager.checkPermission(ACCESS_WINDOW_LIST_PERMISSION); } return unmodifiableWindows; } final AccessControlContext acc = AccessController.getContext(); protected Window() { // necessary for WindowCloseRequestHandler initializeInternalEventDispatcher(); WindowHelper.initHelper(this); } /* * 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. */ private WindowPeerListener peerListener; WindowPeerListener getPeerListener() { return peerListener; } void setPeerListener(WindowPeerListener peerListener) { this.peerListener = 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 * peer is initialized prior to subsequent initialization. */ private TKStage peer; private TKBoundsConfigurator peerBoundsConfigurator = new TKBoundsConfigurator(); /* * Get Stage's peer */ TKStage getPeer() { return peer; } void setPeer(TKStage peer) { this.peer = peer; } /** * 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 && peer != null) { SceneHelper.preferredSize(getScene()); adjustSize(false); } else { // Remember the request to reapply it later if needed sizeToScene = true; } } private void adjustSize(boolean selfSizePriority) { if (getScene() == null) { return; } if (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 (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: * <ul> * <li>Window creation * <li>At some point during moving a window to a new {@code Screen} * which may be before or after the {@link Screen} property is updated. * <li>In response to a change in user preferences for output scaling. * </ul> * * @see #renderScaleXProperty() * @since 9 */ 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: * <ul> * <li>Window creation * <li>At some point during moving a window to a new {@code Screen} * which may be before or after the {@link Screen} property is updated. * <li>In response to a change in user preferences for output scaling. * </ul> * * @see #renderScaleYProperty() * @since 9 */ 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. * * @defaultValue false * @see #renderScaleXProperty() * @see #renderScaleYProperty() * @since 9 */ 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 #outputScaleXProperty() outpitScaleX} 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. * * @defaultValue outputScaleX * @see #outputScaleXProperty() * @see #forceIntegerRenderScaleProperty() * @since 9 */ 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 #outputScaleYProperty() outpitScaleY} 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. * * @defaultValue outputScaleY * @see #outputScaleYProperty() * @see #forceIntegerRenderScaleProperty() * @since 9 */ 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 Window} on the screen. Changing * this attribute will move the {@code Window} horizontally. If this * {@code Window} is an instance of {@code Stage}, changing this attribute * will not visually affect the {@code Window} while * {@link Stage#fullScreenProperty() fullScreen} is true, but will be honored * by the {@code Window} once {@link Stage#fullScreenProperty() 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 Window} on the screen. Changing this * attribute will move the {@code Window} vertically. If this * {@code Window} is an instance of {@code Stage}, changing this attribute * will not visually affect the {@code Window} while * {@link Stage#fullScreenProperty() fullScreen} is true, but will be honored * by the {@code Window} once {@link Stage#fullScreenProperty() 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 Window}. Changing this attribute will narrow or * widen the width of the {@code Window}. 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. This {@code Window} will take its width from the scene if * it has never been set by the application. Resizing the window by end user * does not count as a setting the width by the application. If this * {@code Window} is an instance of {@code Stage}, changing this attribute * will not visually affect the {@code Window} while * {@link Stage#fullScreenProperty() fullScreen} is true, but will be honored * by the {@code Window} once {@link Stage#fullScreenProperty() fullScreen} * becomes false. * <p> * The property is read only because it can be changed externally * by the underlying platform and therefore must not be bindable. * </p> */ 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 Window}. Changing this attribute will shrink * or heighten the height of the {@code Window}. 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. This window will take its height from the scene if it has never * been set by the application. Resizing this window by end user does not * count as a setting the height by the application. If this * {@code Window} is an instance of {@code Stage}, changing this attribute * will not visually affect the {@code Window} while * {@link Stage#fullScreenProperty() fullScreen} is true, but will be honored * by the {@code Window} once {@link Stage#fullScreenProperty() fullScreen} * becomes false. * <p> * The property is read only because it can be changed externally * by the underlying platform and therefore must not be bindable. * </p> */ 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. * <p> * The property is read only because it can be changed externally * by the underlying platform and therefore must not be bindable. * </p> */ private ReadOnlyBooleanWrapper focused = new ReadOnlyBooleanWrapper() { @Override protected void invalidated() { focusChanged(get()); } @Override public Object getBean() { return Window.this; } @Override public String getName() { return "focused"; } }; final void setFocused(boolean value) { focused.set(value); } /** * Requests that this {@code Window} get the input focus. */ public final void requestFocus() { if (peer != null) { 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<Object, Object> properties; /** * Returns an observable map of properties on this node for use primarily * by application developers. * * @return an observable map of properties on this node for use primarily * by application developers * * @since JavaFX 8u40 */ public final ObservableMap<Object, Object> getProperties() { if (properties == null) { properties = FXCollections.observableMap(new HashMap<Object, Object>()); } return properties; } /** * Tests if Window has properties. * @return true if node has properties. * * @since JavaFX 8u40 */ public boolean hasProperties() { return properties != null && !properties.isEmpty(); } /** * Convenience method for setting a single Object property that can be * retrieved at a later date. This is functionally equivalent to calling * the getProperties().put(Object key, Object value) method. This can later * be retrieved by calling {@link Window#getUserData()}. * * @param value The value to be stored - this can later be retrieved by calling * {@link Window#getUserData()}. * * @since JavaFX 8u40 */ public void setUserData(Object value) { getProperties().put(USER_DATA_KEY, value); } /** * Returns a previously set Object property, or null if no such property * has been set using the {@link Window#setUserData(java.lang.Object)} method. * * @return The Object that was previously set, or null if no property * has been set or if null was set. * * @since JavaFX 8u40 */ public Object getUserData() { return getProperties().get(USER_DATA_KEY); } /** * The {@code Scene} to be rendered on this {@code Window}. There can only * be one {@code Scene} on the {@code Window} at a time, and a {@code Scene} * can only be on one {@code Window} at a time. Setting a {@code Scene} on * a different {@code Window} will cause the old {@code Window} to lose the * reference before the new one gains it. You may swap {@code Scene}s on * a {@code Window} at any time, even if it is an instance of {@code Stage} * and with {@link Stage#fullScreenProperty() fullScreen} set to true. * If the width or height of this {@code Window} have never been set by the * application, setting the scene will cause this {@code Window} to take its * width or height from that scene. Resizing this window by end user does * not count as setting the width or height by the application. * * An {@link IllegalStateException} is thrown if this property is set * on a thread other than the JavaFX Application Thread. * * @defaultValue null */ private SceneModel scene = new SceneModel(); protected void setScene(Scene value) { scene.set(value); } public final Scene getScene() { return scene.get(); } public final ReadOnlyObjectProperty<Scene> sceneProperty() { return scene.getReadOnlyProperty(); } private final class SceneModel extends ReadOnlyObjectWrapper<Scene> { private Scene oldScene; @Override protected void invalidated() { final Scene newScene = get(); if (oldScene == newScene) { return; } if (peer != null) { Toolkit.getToolkit().checkFxUserThread(); } // First, detach scene peer from this window updatePeerScene(null); // Second, dispose scene peer if (oldScene != null) { SceneHelper.setWindow(oldScene, null); StyleManager.getInstance().forget(oldScene); } if (newScene != null) { final Window oldWindow = newScene.getWindow(); if (oldWindow != null) { // if the new scene was previously set to a window // we need to remove it from that window // NOTE: can this "scene" property be bound? oldWindow.setScene(null); } // Set the "window" on the new scene. This will also trigger // scene's peer creation. SceneHelper.setWindow(newScene, Window.this); // Set scene impl on stage impl updatePeerScene(SceneHelper.getPeer(newScene)); // Fix for RT-15432: we should update new Scene's stylesheets, if the // window is already showing. For not yet shown windows, the update is // performed in doVisibleChanging() if (isShowing()) { NodeHelper.reapplyCSS(newScene.getRoot()); if (!widthExplicit || !heightExplicit) { SceneHelper.preferredSize(getScene()); adjustSize(true); } } } oldScene = newScene; } @Override public Object getBean() { return Window.this; } @Override public String getName() { return "scene"; } private void updatePeerScene(final TKScene tkScene) { if (peer != null) { // Set scene impl on stage impl peer.setScene(tkScene); } } } /** * Defines the opacity of the {@code Window} as a value between 0.0 and 1.0. * The opacity is reflected across the {@code Window}, its {@code Decoration} * and its {@code Scene} content. On a JavaFX runtime platform that does not * support opacity, assigning a value to this variable will have no * visible difference. A {@code Window} with 0% opacity is fully translucent. * Typically, a {@code Window} with 0% opacity will not receive any mouse * events. * * @defaultValue 1.0 */ private DoubleProperty opacity; public final void setOpacity(double value) { opacityProperty().set(value); } public final double getOpacity() { return opacity == null ? 1.0 : opacity.get(); } public final DoubleProperty opacityProperty() { if (opacity == null) { opacity = new DoublePropertyBase(1.0) { @Override protected void invalidated() { if (peer != null) { peer.setOpacity((float) get()); } } @Override public Object getBean() { return Window.this; } @Override public String getName() { return "opacity"; } }; } return opacity; } /** * Called when there is an external request to close this {@code Window}. * The installed event handler can prevent window closing by consuming the * received event. */ private ObjectProperty<EventHandler<WindowEvent>> onCloseRequest; public final void setOnCloseRequest(EventHandler<WindowEvent> value) { onCloseRequestProperty().set(value); } public final EventHandler<WindowEvent> getOnCloseRequest() { return (onCloseRequest != null) ? onCloseRequest.get() : null; } public final ObjectProperty<EventHandler<WindowEvent>> onCloseRequestProperty() { if (onCloseRequest == null) { onCloseRequest = new ObjectPropertyBase<EventHandler<WindowEvent>>() { @Override protected void invalidated() { setEventHandler(WindowEvent.WINDOW_CLOSE_REQUEST, get()); } @Override public Object getBean() { return Window.this; } @Override public String getName() { return "onCloseRequest"; } }; } return onCloseRequest; } /** * Called just prior to the Window being shown. */ private ObjectProperty<EventHandler<WindowEvent>> onShowing; public final void setOnShowing(EventHandler<WindowEvent> value) { onShowingProperty().set(value); } public final EventHandler<WindowEvent> getOnShowing() { return onShowing == null ? null : onShowing.get(); } public final ObjectProperty<EventHandler<WindowEvent>> onShowingProperty() { if (onShowing == null) { onShowing = new ObjectPropertyBase<EventHandler<WindowEvent>>() { @Override protected void invalidated() { setEventHandler(WindowEvent.WINDOW_SHOWING, get()); } @Override public Object getBean() { return Window.this; } @Override public String getName() { return "onShowing"; } }; } return onShowing; } /** * Called just after the Window is shown. */ private ObjectProperty<EventHandler<WindowEvent>> onShown; public final void setOnShown(EventHandler<WindowEvent> value) { onShownProperty().set(value); } public final EventHandler<WindowEvent> getOnShown() { return onShown == null ? null : onShown.get(); } public final ObjectProperty<EventHandler<WindowEvent>> onShownProperty() { if (onShown == null) { onShown = new ObjectPropertyBase<EventHandler<WindowEvent>>() { @Override protected void invalidated() { setEventHandler(WindowEvent.WINDOW_SHOWN, get()); } @Override public Object getBean() { return Window.this; } @Override public String getName() { return "onShown"; } }; } return onShown; } /** * Called just prior to the Window being hidden. */ private ObjectProperty<EventHandler<WindowEvent>> onHiding; public final void setOnHiding(EventHandler<WindowEvent> value) { onHidingProperty().set(value); } public final EventHandler<WindowEvent> getOnHiding() { return onHiding == null ? null : onHiding.get(); } public final ObjectProperty<EventHandler<WindowEvent>> onHidingProperty() { if (onHiding == null) { onHiding = new ObjectPropertyBase<EventHandler<WindowEvent>>() { @Override protected void invalidated() { setEventHandler(WindowEvent.WINDOW_HIDING, get()); } @Override public Object getBean() { return Window.this; } @Override public String getName() { return "onHiding"; } }; } return onHiding; } /** * Called just after the Window has been hidden. * When the {@code Window} is hidden, this event handler is invoked allowing * the developer to clean up resources or perform other tasks when the * {@link Window} is closed. */ private ObjectProperty<EventHandler<WindowEvent>> onHidden; public final void setOnHidden(EventHandler<WindowEvent> value) { onHiddenProperty().set(value); } public final EventHandler<WindowEvent> getOnHidden() { return onHidden == null ? null : onHidden.get(); } public final ObjectProperty<EventHandler<WindowEvent>> onHiddenProperty() { if (onHidden == null) { onHidden = new ObjectPropertyBase<EventHandler<WindowEvent>>() { @Override protected void invalidated() { setEventHandler(WindowEvent.WINDOW_HIDDEN, get()); } @Override public Object getBean() { return Window.this; } @Override public String getName() { return "onHidden"; } }; } return onHidden; } /** * Whether or not this {@code Window} is showing (that is, open on the * user's system). The Window might be "showing", yet the user might not * be able to see it due to the Window being rendered behind another Window * or due to the Window being positioned off the monitor. * * @defaultValue false */ private ReadOnlyBooleanWrapper showing = new ReadOnlyBooleanWrapper() { private boolean oldVisible; @Override protected void invalidated() { final boolean newVisible = get(); if (oldVisible == newVisible) { return; } if (!oldVisible && newVisible) { fireEvent(new WindowEvent(Window.this, WindowEvent.WINDOW_SHOWING)); } else { fireEvent(new WindowEvent(Window.this, WindowEvent.WINDOW_HIDING)); } oldVisible = newVisible; WindowHelper.visibleChanging(Window.this, newVisible); if (newVisible) { hasBeenVisible = true; windows.add(Window.this); } else { windows.remove(Window.this); } Toolkit tk = Toolkit.getToolkit(); if (peer != null) { if (newVisible) { if (peerListener == null) { peerListener = new WindowPeerListener(Window.this); } // Setup listener for changes coming back from peer peer.setTKStageListener(peerListener); // Register pulse listener tk.addStageTkPulseListener(peerBoundsConfigurator); if (getScene() != null) { SceneHelper.initPeer(getScene()); peer.setScene(SceneHelper.getPeer(getScene())); SceneHelper.preferredSize(getScene()); } updateOutputScales(peer.getOutputScaleX(), peer.getOutputScaleY()); // updateOutputScales may cause an update to the render // scales in many cases, but if the scale has not changed // then the lazy render scale properties might think // they do not need to send down the new values. In some // cases we have been show()n with a brand new peer, so // it is better to force the render scales into the PBC. // This may usually be a NOP, but it is similar to the // forced setSize and setLocation down below. peerBoundsConfigurator.setRenderScaleX(getRenderScaleX()); peerBoundsConfigurator.setRenderScaleY(getRenderScaleY()); // Set peer bounds if ((getScene() != null) && (!widthExplicit || !heightExplicit)) { adjustSize(true); } else { peerBoundsConfigurator.setSize(getWidth(), getHeight(), -1, -1); } if (!xExplicit && !yExplicit) { centerOnScreen(); } else { peerBoundsConfigurator.setLocation(getX(), getY(), 0, 0); } // set peer bounds before the window is shown applyBounds(); peer.setOpacity((float) getOpacity()); peer.setVisible(true); fireEvent(new WindowEvent(Window.this, WindowEvent.WINDOW_SHOWN)); } else { peer.setVisible(false); // Call listener fireEvent(new WindowEvent(Window.this, WindowEvent.WINDOW_HIDDEN)); if (getScene() != null) { peer.setScene(null); SceneHelper.disposePeer(getScene()); StyleManager.getInstance().forget(getScene()); } // Remove toolkit pulse listener tk.removeStageTkPulseListener(peerBoundsConfigurator); // Remove listener for changes coming back from peer peer.setTKStageListener(null); // Notify peer peer.close(); } } if (newVisible) { tk.requestNextPulse(); } WindowHelper.visibleChanged(Window.this, newVisible); if (sizeToScene) { if (newVisible) { // Now that the visibleChanged has completed, the insets of the window // might have changed (e.g. due to setResizable(false)). Reapply the // sizeToScene() request if needed to account for the new insets. sizeToScene(); } // Reset the flag unconditionally upon visibility changes sizeToScene = false; } } @Override public Object getBean() { return Window.this; } @Override public String getName() { return "showing"; } }; private void setShowing(boolean value) { Toolkit.getToolkit().checkFxUserThread(); showing.set(value); } public final boolean isShowing() { return showing.get(); } public final ReadOnlyBooleanProperty showingProperty() { return showing.getReadOnlyProperty(); } // flag indicating whether this window has ever been made visible. boolean hasBeenVisible = false; /** * Attempts to show this Window by setting visibility to true * * @throws IllegalStateException if this method is called on a thread * other than the JavaFX Application Thread. */ protected void show() { setShowing(true); } /** * Attempts to hide this Window by setting the visibility to false. * * @throws IllegalStateException if this method is called on a thread * other than the JavaFX Application Thread. */ public void hide() { setShowing(false); } /* * This can be replaced by listening for the onShowing/onHiding events * Note: This method MUST only be called via its accessor method. */ private void doVisibleChanging(boolean visible) { if (visible && (getScene() != null)) { NodeHelper.reapplyCSS(getScene().getRoot()); } } /* * This can be replaced by listening for the onShown/onHidden events * Note: This method MUST only be called via its accessor method. */ private void doVisibleChanged(boolean visible) { assert peer != null; if (!visible) { peerListener = null; peer = null; } } // PENDING_DOC_REVIEW /** * Specifies the event dispatcher for this node. The default event * dispatcher sends the received events to the registered event handlers and * filters. When replacing the value with a new {@code EventDispatcher}, * the new dispatcher should forward events to the replaced dispatcher * to maintain the node's default event handling behavior. */ private ObjectProperty<EventDispatcher> eventDispatcher; public final void setEventDispatcher(EventDispatcher value) { eventDispatcherProperty().set(value); } public final EventDispatcher getEventDispatcher() { return eventDispatcherProperty().get(); } public final ObjectProperty<EventDispatcher> eventDispatcherProperty() { initializeInternalEventDispatcher(); return eventDispatcher; } private WindowEventDispatcher internalEventDispatcher; // PENDING_DOC_REVIEW /** * Registers an event handler to this node. The handler is called when the * node receives an {@code Event} of the specified type during the bubbling * phase of event delivery. * * @param <T> the specific event class of the handler * @param eventType the type of the events to receive by the handler * @param eventHandler the handler to register * @throws NullPointerException if the event type or handler is null */ public final <T extends Event> void addEventHandler(final EventType<T> eventType, final EventHandler<? super T> eventHandler) { getInternalEventDispatcher().getEventHandlerManager().addEventHandler(eventType, eventHandler); } // PENDING_DOC_REVIEW /** * Unregisters a previously registered event handler from this node. One * handler might have been registered for different event types, so the * caller needs to specify the particular event type from which to * unregister the handler. * * @param <T> the specific event class of the handler * @param eventType the event type from which to unregister * @param eventHandler the handler to unregister * @throws NullPointerException if the event type or handler is null */ public final <T extends Event> void removeEventHandler(final EventType<T> eventType, final EventHandler<? super T> eventHandler) { getInternalEventDispatcher().getEventHandlerManager().removeEventHandler(eventType, eventHandler); } // PENDING_DOC_REVIEW /** * Registers an event filter to this node. The filter is called when the * node receives an {@code Event} of the specified type during the capturing * phase of event delivery. * * @param <T> the specific event class of the filter * @param eventType the type of the events to receive by the filter * @param eventFilter the filter to register * @throws NullPointerException if the event type or filter is null */ public final <T extends Event> void addEventFilter(final EventType<T> eventType, final EventHandler<? super T> eventFilter) { getInternalEventDispatcher().getEventHandlerManager().addEventFilter(eventType, eventFilter); } // PENDING_DOC_REVIEW /** * Unregisters a previously registered event filter from this node. One * filter might have been registered for different event types, so the * caller needs to specify the particular event type from which to * unregister the filter. * * @param <T> the specific event class of the filter * @param eventType the event type from which to unregister * @param eventFilter the filter to unregister * @throws NullPointerException if the event type or filter is null */ public final <T extends Event> void removeEventFilter(final EventType<T> eventType, final EventHandler<? super T> eventFilter) { getInternalEventDispatcher().getEventHandlerManager().removeEventFilter(eventType, eventFilter); } /** * Sets the handler to use for this event type. There can only be one such handler * specified at a time. This handler is guaranteed to be called first. This is * used for registering the user-defined onFoo event handlers. * * @param <T> the specific event class of the handler * @param eventType the event type to associate with the given eventHandler * @param eventHandler the handler to register, or null to unregister * @throws NullPointerException if the event type is null */ protected final <T extends Event> void setEventHandler(final EventType<T> eventType, final EventHandler<? super T> eventHandler) { getInternalEventDispatcher().getEventHandlerManager().setEventHandler(eventType, eventHandler); } WindowEventDispatcher getInternalEventDispatcher() { initializeInternalEventDispatcher(); return internalEventDispatcher; } private void initializeInternalEventDispatcher() { if (internalEventDispatcher == null) { internalEventDispatcher = createInternalEventDispatcher(); eventDispatcher = new SimpleObjectProperty<EventDispatcher>(this, "eventDispatcher", internalEventDispatcher); } } WindowEventDispatcher createInternalEventDispatcher() { return new WindowEventDispatcher(this); } /** * Fires the specified event. * <p> * This method must be called on the FX user thread. * * @param event the event to fire */ public final void fireEvent(Event event) { Event.fireEvent(this, event); } // PENDING_DOC_REVIEW /** * Construct an event dispatch chain for this window. * * @param tail the initial chain to build from * @return the resulting event dispatch chain for this window */ @Override public EventDispatchChain buildEventDispatchChain(EventDispatchChain tail) { if (eventDispatcher != null) { final EventDispatcher eventDispatcherValue = eventDispatcher.get(); if (eventDispatcherValue != null) { tail = tail.prepend(eventDispatcherValue); } } return tail; } private int focusGrabCounter; void increaseFocusGrabCounter() { if ((++focusGrabCounter == 1) && (peer != null) && isFocused()) { peer.grabFocus(); } } void decreaseFocusGrabCounter() { if ((--focusGrabCounter == 0) && (peer != null)) { peer.ungrabFocus(); } } private void focusChanged(final boolean newIsFocused) { if ((focusGrabCounter > 0) && (peer != null) && newIsFocused) { peer.grabFocus(); } } final void applyBounds() { peerBoundsConfigurator.apply(); } Window getWindowOwner() { return null; } private Screen getWindowScreen() { Window window = this; do { if (!Double.isNaN(window.getX()) && !Double.isNaN(window.getY()) && !Double.isNaN(window.getWidth()) && !Double.isNaN(window.getHeight())) { return Utils.getScreenForRectangle( new Rectangle2D(window.getX(), window.getY(), window.getWidth(), window.getHeight())); } window = window.getWindowOwner(); } while (window != null); return Screen.getPrimary(); } private final ReadOnlyObjectWrapper<Screen> screen = new ReadOnlyObjectWrapper<>(Screen.getPrimary()); private ReadOnlyObjectProperty<Screen> screenProperty() { return screen.getReadOnlyProperty(); } private void notifyScreenChanged(Object from, Object to) { screen.set(Screen.getScreenForNative(to)); } /** * Caches all requested bounds settings and applies them at once during * the next pulse. */ private final class TKBoundsConfigurator implements TKPulseListener { private double renderScaleX; private double renderScaleY; private double x; private double y; private float xGravity; private float yGravity; private double windowWidth; private double windowHeight; private double clientWidth; private double clientHeight; private boolean dirty; public TKBoundsConfigurator() { reset(); } public void setRenderScaleX(final double renderScaleX) { this.renderScaleX = renderScaleX; setDirty(); } public void setRenderScaleY(final double renderScaleY) { this.renderScaleY = renderScaleY; setDirty(); } public void setX(final double x, final float xGravity) { this.x = x; this.xGravity = xGravity; setDirty(); } public void setY(final double y, final float yGravity) { this.y = y; this.yGravity = yGravity; setDirty(); } public void setWindowWidth(final double windowWidth) { this.windowWidth = windowWidth; setDirty(); } public void setWindowHeight(final double windowHeight) { this.windowHeight = windowHeight; setDirty(); } public void setClientWidth(final double clientWidth) { this.clientWidth = clientWidth; setDirty(); } public void setClientHeight(final double clientHeight) { this.clientHeight = clientHeight; setDirty(); } public void setLocation(final double x, final double y, final float xGravity, final float yGravity) { this.x = x; this.y = y; this.xGravity = xGravity; this.yGravity = yGravity; setDirty(); } public void setSize(final double windowWidth, final double windowHeight, final double clientWidth, final double clientHeight) { this.windowWidth = windowWidth; this.windowHeight = windowHeight; this.clientWidth = clientWidth; this.clientHeight = clientHeight; setDirty(); } public void apply() { if (dirty) { // Snapshot values and then reset() before we call down // as we may end up with recursive calls back up with // new values that must be recorded as dirty. boolean xSet = !Double.isNaN(x); float newX = xSet ? (float) x : 0f; boolean ySet = !Double.isNaN(y); float newY = ySet ? (float) y : 0f; float newWW = (float) windowWidth; float newWH = (float) windowHeight; float newCW = (float) clientWidth; float newCH = (float) clientHeight; float newXG = xGravity; float newYG = yGravity; float newRX = (float) renderScaleX; float newRY = (float) renderScaleY; reset(); peer.setBounds(newX, newY, xSet, ySet, newWW, newWH, newCW, newCH, newXG, newYG, newRX, newRY); } } @Override public void pulse() { apply(); } private void reset() { renderScaleX = 0.0; renderScaleY = 0.0; x = Double.NaN; y = Double.NaN; xGravity = 0; yGravity = 0; windowWidth = -1; windowHeight = -1; clientWidth = -1; clientHeight = -1; dirty = false; } private void setDirty() { if (!dirty) { Toolkit.getToolkit().requestNextPulse(); dirty = true; } } } }