1 /*
   2  * Copyright (c) 2010, 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.security.AllPermission;
  29 import java.security.AccessControlContext;
  30 import java.security.AccessController;
  31 import java.util.ArrayList;
  32 import java.util.HashMap;
  33 import java.util.Iterator;
  34 import java.util.List;
  35 
  36 import com.sun.javafx.collections.annotations.ReturnsUnmodifiableCollection;
  37 import javafx.beans.property.DoubleProperty;
  38 import javafx.beans.property.DoublePropertyBase;
  39 import javafx.beans.property.ObjectProperty;
  40 import javafx.beans.property.ObjectPropertyBase;
  41 import javafx.beans.property.ReadOnlyBooleanProperty;
  42 import javafx.beans.property.ReadOnlyBooleanWrapper;
  43 import javafx.beans.property.ReadOnlyObjectProperty;
  44 import javafx.beans.property.ReadOnlyObjectWrapper;
  45 import javafx.beans.property.ReadOnlyDoubleProperty;
  46 import javafx.beans.property.ReadOnlyDoubleWrapper;
  47 import javafx.beans.property.SimpleObjectProperty;
  48 import javafx.collections.FXCollections;
  49 import javafx.collections.ObservableList;
  50 import javafx.collections.ObservableMap;
  51 import javafx.event.Event;
  52 import javafx.event.EventDispatchChain;
  53 import javafx.event.EventDispatcher;
  54 import javafx.event.EventHandler;
  55 import javafx.event.EventTarget;
  56 import javafx.event.EventType;
  57 import javafx.geometry.Rectangle2D;
  58 import javafx.scene.Scene;
  59 
  60 import com.sun.javafx.util.Utils;
  61 import com.sun.javafx.util.WeakReferenceQueue;
  62 import com.sun.javafx.css.StyleManager;
  63 import com.sun.javafx.stage.WindowEventDispatcher;
  64 import com.sun.javafx.stage.WindowHelper;
  65 import com.sun.javafx.stage.WindowPeerListener;
  66 import com.sun.javafx.tk.TKPulseListener;
  67 import com.sun.javafx.tk.TKScene;
  68 import com.sun.javafx.tk.TKStage;
  69 import com.sun.javafx.tk.Toolkit;
  70 
  71 
  72 /**
  73  * <p>
  74  *     A top level window within which a scene is hosted, and with which the user
  75  *     interacts. A Window might be a {@link Stage}, {@link PopupWindow}, or other
  76  *     such top level. A Window is used also for browser plug-in based deployments.
  77  * </p>
  78  *
  79  * @since JavaFX 2.0
  80  */
  81 public class Window implements EventTarget {
  82 
  83     /**
  84      * A list of all the currently _showing_ windows. This is publicly accessible via the unmodifiableWindows wrapper.
  85      */
  86     private static ObservableList<Window> windows = FXCollections.observableArrayList();
  87     private static ObservableList<Window> unmodifiableWindows = FXCollections.unmodifiableObservableList(windows);
  88 
  89     static {
  90         WindowHelper.setWindowAccessor(
  91                 new WindowHelper.WindowAccessor() {
  92                     /**
  93                      * Allow window peer listeners to directly change reported
  94                      * window location and size without changing the xExplicit,
  95                      * yExplicit, widthExplicit and heightExplicit values.
  96                      */
  97                     @Override
  98                     public void notifyLocationChanged(
  99                             Window window, double x, double y) {
 100                         window.notifyLocationChanged(x, y);
 101                     }
 102 
 103                     @Override
 104                     public void notifySizeChanged(Window window,
 105                                                   double width,
 106                                                   double height) {
 107                         window.notifySizeChanged(width, height);
 108                     }
 109 
 110                     @Override
 111                     public void notifyScreenChanged(Window window,
 112                                                   Object from,
 113                                                   Object to) {
 114                         window.notifyScreenChanged(from, to);
 115                     }
 116 
 117                     @Override
 118                     public float getUIScale(Window window) {
 119                         TKStage peer = window.impl_peer;
 120                         return peer == null ? 1.0f : peer.getUIScale();
 121                     }
 122 
 123                     @Override
 124                     public float getRenderScale(Window window) {
 125                         TKStage peer = window.impl_peer;
 126                         return peer == null ? 1.0f : peer.getRenderScale();
 127                     }
 128 
 129                     @Override
 130                     public ReadOnlyObjectProperty<Screen> screenProperty(Window window) {
 131                         return window.screenProperty();
 132                     }
 133 
 134                     @Override
 135                     public AccessControlContext getAccessControlContext(Window window) {
 136                         return window.acc;
 137                     }
 138                 });
 139     }
 140 
 141     /**
 142      * Returns a list containing a reference to the currently showing JavaFX windows. The list is unmodifiable -
 143      * attempting to modify this list will result in an {@link UnsupportedOperationException} being thrown at runtime.
 144      *
 145      * @return A list containing all windows that are currently showing.
 146      * @since 9
 147      */
 148     @ReturnsUnmodifiableCollection
 149     public static ObservableList<Window> getWindows() {
 150         final SecurityManager securityManager = System.getSecurityManager();
 151         if (securityManager != null) {
 152             securityManager.checkPermission(new AllPermission());
 153         }
 154 
 155         return unmodifiableWindows;
 156     }
 157 
 158     final AccessControlContext acc = AccessController.getContext();
 159 
 160     protected Window() {
 161         // necessary for WindowCloseRequestHandler
 162         initializeInternalEventDispatcher();
 163     }
 164 
 165     /**
 166      * The listener that gets called by peer. It's also responsible for
 167      * window size/location synchronization with the window peer, which
 168      * occurs on every pulse.
 169      *
 170      * @treatAsPrivate implementation detail
 171      * @deprecated This is an internal API that is not intended for use and will be removed in the next version
 172      */
 173     @Deprecated
 174     protected WindowPeerListener peerListener;
 175 
 176     /**
 177      * The peer of this Stage. All external access should be
 178      * made though getPeer(). Implementors note: Please ensure that this
 179      * variable is defined *after* style and *before* the other variables so
 180      * that style has been initialized prior to this call, and so that
 181      * impl_peer is initialized prior to subsequent initialization.
 182      *
 183      * @treatAsPrivate implementation detail
 184      * @deprecated This is an internal API that is not intended for use and will be removed in the next version
 185      */
 186     @Deprecated
 187     protected volatile TKStage impl_peer;
 188 
 189     private TKBoundsConfigurator peerBoundsConfigurator =
 190             new TKBoundsConfigurator();
 191 
 192     /**
 193      * Get Stage's peer
 194      *
 195      * @treatAsPrivate implementation detail
 196      * @deprecated This is an internal API that is not intended for use and will be removed in the next version
 197      */
 198     @Deprecated
 199     public TKStage impl_getPeer() {
 200         return impl_peer;
 201     }
 202 
 203     /**
 204      * @treatAsPrivate implementation detail
 205      * @deprecated This is an internal API that is not intended for use and will be removed in the next version
 206      */
 207     @Deprecated
 208     public String impl_getMXWindowType() {
 209         return getClass().getSimpleName();
 210     }
 211 
 212     /**
 213      * Indicates if a user requested the window to be sized to match the scene
 214      * size.
 215      */
 216     private boolean sizeToScene = false;
 217     /**
 218      * Set the width and height of this Window to match the size of the content
 219      * of this Window's Scene.
 220      */
 221     public void sizeToScene() {
 222         if (getScene() != null && impl_peer != null) {
 223             getScene().impl_preferredSize();
 224             adjustSize(false);
 225         } else {
 226             // Remember the request to reapply it later if needed
 227             sizeToScene = true;
 228         }
 229     }
 230 
 231     private void adjustSize(boolean selfSizePriority) {
 232         if (getScene() == null) {
 233             return;
 234         }
 235         if (impl_peer != null) {
 236             double sceneWidth = getScene().getWidth();
 237             double cw = (sceneWidth > 0) ? sceneWidth : -1;
 238             double w = -1;
 239             if (selfSizePriority && widthExplicit) {
 240                 w = getWidth();
 241             } else if (cw <= 0) {
 242                 w = widthExplicit ? getWidth() : -1;
 243             } else {
 244                 widthExplicit = false;
 245             }
 246             double sceneHeight = getScene().getHeight();
 247             double ch = (sceneHeight > 0) ? sceneHeight : -1;
 248             double h = -1;
 249             if (selfSizePriority && heightExplicit) {
 250                 h = getHeight();
 251             } else if (ch <= 0) {
 252                 h = heightExplicit ? getHeight() : -1;
 253             } else {
 254                 heightExplicit = false;
 255             }
 256 
 257             peerBoundsConfigurator.setSize(w, h, cw, ch);
 258             applyBounds();
 259         }
 260     }
 261 
 262     private static final float CENTER_ON_SCREEN_X_FRACTION = 1.0f / 2;
 263     private static final float CENTER_ON_SCREEN_Y_FRACTION = 1.0f / 3;
 264 
 265     /**
 266      * Sets x and y properties on this Window so that it is centered on the
 267      * curent screen.
 268      * The current screen is determined from the intersection of current window bounds and
 269      * visual bounds of all screens.
 270      */
 271     public void centerOnScreen() {
 272         xExplicit = false;
 273         yExplicit = false;
 274         if (impl_peer != null) {
 275             Rectangle2D bounds = getWindowScreen().getVisualBounds();
 276             double centerX =
 277                     bounds.getMinX() + (bounds.getWidth() - getWidth())
 278                                            * CENTER_ON_SCREEN_X_FRACTION;
 279             double centerY =
 280                     bounds.getMinY() + (bounds.getHeight() - getHeight())
 281                                            * CENTER_ON_SCREEN_Y_FRACTION;
 282 
 283             x.set(centerX);
 284             y.set(centerY);
 285             peerBoundsConfigurator.setLocation(centerX, centerY,
 286                                                CENTER_ON_SCREEN_X_FRACTION,
 287                                                CENTER_ON_SCREEN_Y_FRACTION);
 288             applyBounds();
 289         }
 290     }
 291 
 292     private boolean xExplicit = false;
 293     /**
 294      * The horizontal location of this {@code Stage} on the screen. Changing
 295      * this attribute will move the {@code Stage} horizontally. Changing this
 296      * attribute will not visually affect a {@code Stage} while
 297      * {@code fullScreen} is true, but will be honored by the {@code Stage} once
 298      * {@code fullScreen} becomes false.
 299      */
 300     private ReadOnlyDoubleWrapper x =
 301             new ReadOnlyDoubleWrapper(this, "x", Double.NaN);
 302 
 303     public final void setX(double value) {
 304         setXInternal(value);
 305     }
 306     public final double getX() { return x.get(); }
 307     public final ReadOnlyDoubleProperty xProperty() { return x.getReadOnlyProperty(); }
 308 
 309     void setXInternal(double value) {
 310         x.set(value);
 311         peerBoundsConfigurator.setX(value, 0);
 312         xExplicit = true;
 313     }
 314 
 315     private boolean yExplicit = false;
 316     /**
 317      * The vertical location of this {@code Stage} on the screen. Changing this
 318      * attribute will move the {@code Stage} vertically. Changing this
 319      * attribute will not visually affect a {@code Stage} while
 320      * {@code fullScreen} is true, but will be honored by the {@code Stage} once
 321      * {@code fullScreen} becomes false.
 322      */
 323     private ReadOnlyDoubleWrapper y =
 324             new ReadOnlyDoubleWrapper(this, "y", Double.NaN);
 325 
 326     public final void setY(double value) {
 327         setYInternal(value);
 328     }
 329     public final double getY() { return y.get(); }
 330     public final ReadOnlyDoubleProperty yProperty() { return y.getReadOnlyProperty(); }
 331 
 332     void setYInternal(double value) {
 333         y.set(value);
 334         peerBoundsConfigurator.setY(value, 0);
 335         yExplicit = true;
 336     }
 337 
 338     /**
 339      * Notification from the windowing system that the window's position has
 340      * changed.
 341      *
 342      * @param newX the new window x position
 343      * @param newY the new window y position
 344      */
 345     void notifyLocationChanged(double newX, double newY) {
 346         x.set(newX);
 347         y.set(newY);
 348     }
 349 
 350     private boolean widthExplicit = false;
 351 
 352     /**
 353      * The width of this {@code Stage}. Changing this attribute will narrow or
 354      * widen the width of the {@code Stage}. Changing this
 355      * attribute will not visually affect a {@code Stage} while
 356      * {@code fullScreen} is true, but will be honored by the {@code Stage} once
 357      * {@code fullScreen} becomes false. This value includes any and all
 358      * decorations which may be added by the Operating System such as resizable
 359      * frame handles. Typical applications will set the {@link javafx.scene.Scene} width
 360      * instead.
 361      * <p>
 362      * The property is read only because it can be changed externally
 363      * by the underlying platform and therefore must not be bindable.
 364      * </p>
 365      */
 366     private ReadOnlyDoubleWrapper width =
 367             new ReadOnlyDoubleWrapper(this, "width", Double.NaN);
 368 
 369     public final void setWidth(double value) {
 370         width.set(value);
 371         peerBoundsConfigurator.setWindowWidth(value);
 372         widthExplicit = true;
 373     }
 374     public final double getWidth() { return width.get(); }
 375     public final ReadOnlyDoubleProperty widthProperty() { return width.getReadOnlyProperty(); }
 376 
 377     private boolean heightExplicit = false;
 378     /**
 379      * The height of this {@code Stage}. Changing this attribute will shrink
 380      * or heighten the height of the {@code Stage}. Changing this
 381      * attribute will not visually affect a {@code Stage} while
 382      * {@code fullScreen} is true, but will be honored by the {@code Stage} once
 383      * {@code fullScreen} becomes false. This value includes any and all
 384      * decorations which may be added by the Operating System such as the title
 385      * bar. Typical applications will set the {@link javafx.scene.Scene} height instead.
 386      * <p>
 387      * The property is read only because it can be changed externally
 388      * by the underlying platform and therefore must not be bindable.
 389      * </p>
 390      */
 391     private ReadOnlyDoubleWrapper height =
 392             new ReadOnlyDoubleWrapper(this, "height", Double.NaN);
 393 
 394     public final void setHeight(double value) {
 395         height.set(value);
 396         peerBoundsConfigurator.setWindowHeight(value);
 397         heightExplicit = true;
 398     }
 399     public final double getHeight() { return height.get(); }
 400     public final ReadOnlyDoubleProperty heightProperty() { return height.getReadOnlyProperty(); }
 401 
 402     /**
 403      * Notification from the windowing system that the window's size has
 404      * changed.
 405      *
 406      * @param newWidth the new window width
 407      * @param newHeight the new window height
 408      */
 409     void notifySizeChanged(double newWidth, double newHeight) {
 410         width.set(newWidth);
 411         height.set(newHeight);
 412     }
 413 
 414     /**
 415      * Whether or not this {@code Window} has the keyboard or input focus.
 416      * <p>
 417      * The property is read only because it can be changed externally
 418      * by the underlying platform and therefore must not be bindable.
 419      * </p>
 420      *
 421      * @profile common
 422      */
 423     private ReadOnlyBooleanWrapper focused = new ReadOnlyBooleanWrapper() {
 424         @Override protected void invalidated() {
 425             focusChanged(get());
 426         }
 427 
 428         @Override
 429         public Object getBean() {
 430             return Window.this;
 431         }
 432 
 433         @Override
 434         public String getName() {
 435             return "focused";
 436         }
 437     };
 438 
 439     /**
 440      * @treatAsPrivate
 441      * @deprecated
 442      */
 443     @Deprecated
 444     public final void setFocused(boolean value) { focused.set(value); }
 445 
 446     /**
 447      * Requests that this {@code Window} get the input focus.
 448      */
 449     public final void requestFocus() {
 450         if (impl_peer != null) {
 451             impl_peer.requestFocus();
 452         }
 453     }
 454     public final boolean isFocused() { return focused.get(); }
 455     public final ReadOnlyBooleanProperty focusedProperty() { return focused.getReadOnlyProperty(); }
 456 
 457     /*************************************************************************
 458     *                                                                        *
 459     *                                                                        *
 460     *                                                                        *
 461     *************************************************************************/
 462 
 463     private static final Object USER_DATA_KEY = new Object();
 464     // A map containing a set of properties for this window
 465     private ObservableMap<Object, Object> properties;
 466 
 467     /**
 468       * Returns an observable map of properties on this node for use primarily
 469       * by application developers.
 470       *
 471       * @return an observable map of properties on this node for use primarily
 472       * by application developers
 473       *
 474       * @since JavaFX 8u40
 475       */
 476      public final ObservableMap<Object, Object> getProperties() {
 477         if (properties == null) {
 478             properties = FXCollections.observableMap(new HashMap<Object, Object>());
 479         }
 480         return properties;
 481     }
 482 
 483     /**
 484      * Tests if Window has properties.
 485      * @return true if node has properties.
 486      *
 487      * @since JavaFX 8u40
 488      */
 489      public boolean hasProperties() {
 490         return properties != null && !properties.isEmpty();
 491     }
 492 
 493     /**
 494      * Convenience method for setting a single Object property that can be
 495      * retrieved at a later date. This is functionally equivalent to calling
 496      * the getProperties().put(Object key, Object value) method. This can later
 497      * be retrieved by calling {@link Window#getUserData()}.
 498      *
 499      * @param value The value to be stored - this can later be retrieved by calling
 500      *          {@link Window#getUserData()}.
 501      *
 502      * @since JavaFX 8u40
 503      */
 504     public void setUserData(Object value) {
 505         getProperties().put(USER_DATA_KEY, value);
 506     }
 507 
 508     /**
 509      * Returns a previously set Object property, or null if no such property
 510      * has been set using the {@link Window#setUserData(java.lang.Object)} method.
 511      *
 512      * @return The Object that was previously set, or null if no property
 513      *          has been set or if null was set.
 514      *
 515      * @since JavaFX 8u40
 516      */
 517     public Object getUserData() {
 518         return getProperties().get(USER_DATA_KEY);
 519     }
 520 
 521     /**
 522      * The {@code Scene} to be rendered on this {@code Stage}. There can only
 523      * be one {@code Scene} on the {@code Stage} at a time, and a {@code Scene}
 524      * can only be on one {@code Stage} at a time. Setting a {@code Scene} on
 525      * a different {@code Stage} will cause the old {@code Stage} to lose the
 526      * reference before the new one gains it. You may swap {@code Scene}s on
 527      * a {@code Stage} at any time, even while in full-screen exclusive mode.
 528      *
 529      * An {@link IllegalStateException} is thrown if this property is set
 530      * on a thread other than the JavaFX Application Thread.
 531      *
 532      * @defaultValue null
 533      */
 534     private SceneModel scene = new SceneModel();
 535     protected void setScene(Scene value) { scene.set(value); }
 536     public final Scene getScene() { return scene.get(); }
 537     public final ReadOnlyObjectProperty<Scene> sceneProperty() { return scene.getReadOnlyProperty(); }
 538 
 539     private final class SceneModel extends ReadOnlyObjectWrapper<Scene> {
 540         private Scene oldScene;
 541 
 542         @Override protected void invalidated() {
 543             final Scene newScene = get();
 544             if (oldScene == newScene) {
 545                 return;
 546             }
 547             if (impl_peer != null) {
 548                 Toolkit.getToolkit().checkFxUserThread();
 549             }
 550             // First, detach scene peer from this window
 551             updatePeerScene(null);
 552             // Second, dispose scene peer
 553             if (oldScene != null) {
 554                 oldScene.impl_setWindow(null);
 555                 StyleManager.getInstance().forget(oldScene);
 556             }
 557             if (newScene != null) {
 558                 final Window oldWindow = newScene.getWindow();
 559                 if (oldWindow != null) {
 560                     // if the new scene was previously set to a window
 561                     // we need to remove it from that window
 562                     // NOTE: can this "scene" property be bound?
 563                     oldWindow.setScene(null);
 564                 }
 565 
 566                 // Set the "window" on the new scene. This will also trigger
 567                 // scene's peer creation.
 568                 newScene.impl_setWindow(Window.this);
 569                 // Set scene impl on stage impl
 570                 updatePeerScene(newScene.impl_getPeer());
 571 
 572                 // Fix for RT-15432: we should update new Scene's stylesheets, if the
 573                 // window is already showing. For not yet shown windows, the update is
 574                 // performed in Window.visibleChanging()
 575                 if (isShowing()) {
 576                     newScene.getRoot().impl_reapplyCSS();
 577 
 578                     if (!widthExplicit || !heightExplicit) {
 579                         getScene().impl_preferredSize();
 580                         adjustSize(true);
 581                     }
 582                 }
 583             }
 584 
 585             oldScene = newScene;
 586         }
 587 
 588         @Override
 589         public Object getBean() {
 590             return Window.this;
 591         }
 592 
 593         @Override
 594         public String getName() {
 595             return "scene";
 596         }
 597 
 598         private void updatePeerScene(final TKScene tkScene) {
 599             if (impl_peer != null) {
 600                 // Set scene impl on stage impl
 601                 impl_peer.setScene(tkScene);
 602             }
 603         }
 604     }
 605 
 606     /**
 607      * Defines the opacity of the {@code Stage} as a value between 0.0 and 1.0.
 608      * The opacity is reflected across the {@code Stage}, its {@code Decoration}
 609      * and its {@code Scene} content. On a JavaFX runtime platform that does not
 610      * support opacity, assigning a value to this variable will have no
 611      * visible difference. A {@code Stage} with 0% opacity is fully translucent.
 612      * Typically, a {@code Stage} with 0% opacity will not receive any mouse
 613      * events.
 614      *
 615      * @defaultValue 1.0
 616      */
 617     private DoubleProperty opacity;
 618 
 619     public final void setOpacity(double value) {
 620         opacityProperty().set(value);
 621     }
 622 
 623     public final double getOpacity() {
 624         return opacity == null ? 1.0 : opacity.get();
 625     }
 626 
 627     public final DoubleProperty opacityProperty() {
 628         if (opacity == null) {
 629             opacity = new DoublePropertyBase(1.0) {
 630 
 631                 @Override
 632                 protected void invalidated() {
 633                     if (impl_peer != null) {
 634                         impl_peer.setOpacity((float) get());
 635                     }
 636                 }
 637 
 638                 @Override
 639                 public Object getBean() {
 640                     return Window.this;
 641                 }
 642 
 643                 @Override
 644                 public String getName() {
 645                     return "opacity";
 646                 }
 647             };
 648         }
 649         return opacity;
 650     }
 651 
 652     /**
 653      * Called when there is an external request to close this {@code Window}.
 654      * The installed event handler can prevent window closing by consuming the
 655      * received event.
 656      */
 657     private ObjectProperty<EventHandler<WindowEvent>> onCloseRequest;
 658     public final void setOnCloseRequest(EventHandler<WindowEvent> value) {
 659         onCloseRequestProperty().set(value);
 660     }
 661     public final EventHandler<WindowEvent> getOnCloseRequest() {
 662         return (onCloseRequest != null) ? onCloseRequest.get() : null;
 663     }
 664     public final ObjectProperty<EventHandler<WindowEvent>>
 665             onCloseRequestProperty() {
 666         if (onCloseRequest == null) {
 667             onCloseRequest = new ObjectPropertyBase<EventHandler<WindowEvent>>() {
 668                 @Override protected void invalidated() {
 669                     setEventHandler(WindowEvent.WINDOW_CLOSE_REQUEST, get());
 670                 }
 671 
 672                 @Override
 673                 public Object getBean() {
 674                     return Window.this;
 675                 }
 676 
 677                 @Override
 678                 public String getName() {
 679                     return "onCloseRequest";
 680                 }
 681             };
 682         }
 683         return onCloseRequest;
 684     }
 685 
 686     /**
 687      * Called just prior to the Window being shown.
 688      */
 689     private ObjectProperty<EventHandler<WindowEvent>> onShowing;
 690     public final void setOnShowing(EventHandler<WindowEvent> value) { onShowingProperty().set(value); }
 691     public final EventHandler<WindowEvent> getOnShowing() {
 692         return onShowing == null ? null : onShowing.get();
 693     }
 694     public final ObjectProperty<EventHandler<WindowEvent>> onShowingProperty() {
 695         if (onShowing == null) {
 696             onShowing = new ObjectPropertyBase<EventHandler<WindowEvent>>() {
 697                 @Override protected void invalidated() {
 698                     setEventHandler(WindowEvent.WINDOW_SHOWING, get());
 699                 }
 700 
 701                 @Override
 702                 public Object getBean() {
 703                     return Window.this;
 704                 }
 705 
 706                 @Override
 707                 public String getName() {
 708                     return "onShowing";
 709                 }
 710             };
 711         }
 712         return onShowing;
 713     }
 714 
 715     /**
 716      * Called just after the Window is shown.
 717      */
 718     private ObjectProperty<EventHandler<WindowEvent>> onShown;
 719     public final void setOnShown(EventHandler<WindowEvent> value) { onShownProperty().set(value); }
 720     public final EventHandler<WindowEvent> getOnShown() {
 721         return onShown == null ? null : onShown.get();
 722     }
 723     public final ObjectProperty<EventHandler<WindowEvent>> onShownProperty() {
 724         if (onShown == null) {
 725             onShown = new ObjectPropertyBase<EventHandler<WindowEvent>>() {
 726                 @Override protected void invalidated() {
 727                     setEventHandler(WindowEvent.WINDOW_SHOWN, get());
 728                 }
 729 
 730                 @Override
 731                 public Object getBean() {
 732                     return Window.this;
 733                 }
 734 
 735                 @Override
 736                 public String getName() {
 737                     return "onShown";
 738                 }
 739             };
 740         }
 741         return onShown;
 742     }
 743 
 744     /**
 745      * Called just prior to the Window being hidden.
 746      */
 747     private ObjectProperty<EventHandler<WindowEvent>> onHiding;
 748     public final void setOnHiding(EventHandler<WindowEvent> value) { onHidingProperty().set(value); }
 749     public final EventHandler<WindowEvent> getOnHiding() {
 750         return onHiding == null ? null : onHiding.get();
 751     }
 752     public final ObjectProperty<EventHandler<WindowEvent>> onHidingProperty() {
 753         if (onHiding == null) {
 754             onHiding = new ObjectPropertyBase<EventHandler<WindowEvent>>() {
 755                 @Override protected void invalidated() {
 756                     setEventHandler(WindowEvent.WINDOW_HIDING, get());
 757                 }
 758 
 759                 @Override
 760                 public Object getBean() {
 761                     return Window.this;
 762                 }
 763 
 764                 @Override
 765                 public String getName() {
 766                     return "onHiding";
 767                 }
 768             };
 769         }
 770         return onHiding;
 771     }
 772 
 773     /**
 774      * Called just after the Window has been hidden.
 775      * When the {@code Window} is hidden, this event handler is invoked allowing
 776      * the developer to clean up resources or perform other tasks when the
 777      * {@link Window} is closed.
 778      */
 779     private ObjectProperty<EventHandler<WindowEvent>> onHidden;
 780     public final void setOnHidden(EventHandler<WindowEvent> value) { onHiddenProperty().set(value); }
 781     public final EventHandler<WindowEvent> getOnHidden() {
 782         return onHidden == null ? null : onHidden.get();
 783     }
 784     public final ObjectProperty<EventHandler<WindowEvent>> onHiddenProperty() {
 785         if (onHidden == null) {
 786             onHidden = new ObjectPropertyBase<EventHandler<WindowEvent>>() {
 787                 @Override protected void invalidated() {
 788                     setEventHandler(WindowEvent.WINDOW_HIDDEN, get());
 789                 }
 790 
 791                 @Override
 792                 public Object getBean() {
 793                     return Window.this;
 794                 }
 795 
 796                 @Override
 797                 public String getName() {
 798                     return "onHidden";
 799                 }
 800             };
 801         }
 802         return onHidden;
 803     }
 804 
 805     /**
 806      * Whether or not this {@code Stage} is showing (that is, open on the
 807      * user's system). The Stage might be "showing", yet the user might not
 808      * be able to see it due to the Stage being rendered behind another window
 809      * or due to the Stage being positioned off the monitor.
 810      *
 811      * @defaultValue false
 812      */
 813     private ReadOnlyBooleanWrapper showing = new ReadOnlyBooleanWrapper() {
 814         private boolean oldVisible;
 815 
 816         @Override protected void invalidated() {
 817             final boolean newVisible = get();
 818             if (oldVisible == newVisible) {
 819                 return;
 820             }
 821 
 822             if (!oldVisible && newVisible) {
 823                 fireEvent(new WindowEvent(Window.this, WindowEvent.WINDOW_SHOWING));
 824             } else {
 825                 fireEvent(new WindowEvent(Window.this, WindowEvent.WINDOW_HIDING));
 826             }
 827 
 828             oldVisible = newVisible;
 829             impl_visibleChanging(newVisible);
 830             if (newVisible) {
 831                 hasBeenVisible = true;
 832                 windows.add(Window.this);
 833             } else {
 834                 windows.remove(Window.this);
 835             }
 836             Toolkit tk = Toolkit.getToolkit();
 837             if (impl_peer != null) {
 838                 if (newVisible) {
 839                     if (peerListener == null) {
 840                         peerListener = new WindowPeerListener(Window.this);
 841                     }
 842 
 843                     // Setup listener for changes coming back from peer
 844                     impl_peer.setTKStageListener(peerListener);
 845                     // Register pulse listener
 846                     tk.addStageTkPulseListener(peerBoundsConfigurator);
 847 
 848                     if (getScene() != null) {
 849                         getScene().impl_initPeer();
 850                         impl_peer.setScene(getScene().impl_getPeer());
 851                         getScene().impl_preferredSize();
 852                     }
 853 
 854                     // Set peer bounds
 855                     if ((getScene() != null) && (!widthExplicit || !heightExplicit)) {
 856                         adjustSize(true);
 857                     } else {
 858                         peerBoundsConfigurator.setSize(
 859                                 getWidth(), getHeight(), -1, -1);
 860                     }
 861 
 862                     if (!xExplicit && !yExplicit) {
 863                         centerOnScreen();
 864                     } else {
 865                         peerBoundsConfigurator.setLocation(getX(), getY(),
 866                                                            0, 0);
 867                     }
 868 
 869                     // set peer bounds before the window is shown
 870                     applyBounds();
 871 
 872                     impl_peer.setOpacity((float)getOpacity());
 873 
 874                     impl_peer.setVisible(true);
 875                     fireEvent(new WindowEvent(Window.this, WindowEvent.WINDOW_SHOWN));
 876                 } else {
 877                     impl_peer.setVisible(false);
 878 
 879                     // Call listener
 880                     fireEvent(new WindowEvent(Window.this, WindowEvent.WINDOW_HIDDEN));
 881 
 882                     if (getScene() != null) {
 883                         impl_peer.setScene(null);
 884                         getScene().impl_disposePeer();
 885                         StyleManager.getInstance().forget(getScene());
 886                     }
 887 
 888                     // Remove toolkit pulse listener
 889                     tk.removeStageTkPulseListener(peerBoundsConfigurator);
 890                     // Remove listener for changes coming back from peer
 891                     impl_peer.setTKStageListener(null);
 892 
 893                     // Notify peer
 894                     impl_peer.close();
 895                 }
 896             }
 897             if (newVisible) {
 898                 tk.requestNextPulse();
 899             }
 900             impl_visibleChanged(newVisible);
 901 
 902             if (sizeToScene) {
 903                 if (newVisible) {
 904                     // Now that the visibleChanged has completed, the insets of the window
 905                     // might have changed (e.g. due to setResizable(false)). Reapply the
 906                     // sizeToScene() request if needed to account for the new insets.
 907                     sizeToScene();
 908                 }
 909 
 910                 // Reset the flag unconditionally upon visibility changes
 911                 sizeToScene = false;
 912             }
 913         }
 914 
 915         @Override
 916         public Object getBean() {
 917             return Window.this;
 918         }
 919 
 920         @Override
 921         public String getName() {
 922             return "showing";
 923         }
 924     };
 925     private void setShowing(boolean value) {
 926         Toolkit.getToolkit().checkFxUserThread();
 927         showing.set(value);
 928     }
 929     public final boolean isShowing() { return showing.get(); }
 930     public final ReadOnlyBooleanProperty showingProperty() { return showing.getReadOnlyProperty(); }
 931 
 932     // flag indicating whether this window has ever been made visible.
 933     boolean hasBeenVisible = false;
 934 
 935     /**
 936      * Attempts to show this Window by setting visibility to true
 937      *
 938      * @throws IllegalStateException if this method is called on a thread
 939      * other than the JavaFX Application Thread.
 940      */
 941     protected void show() {
 942         setShowing(true);
 943     }
 944 
 945     /**
 946      * Attempts to hide this Window by setting the visibility to false.
 947      *
 948      * @throws IllegalStateException if this method is called on a thread
 949      * other than the JavaFX Application Thread.
 950      */
 951     public void hide() {
 952         setShowing(false);
 953     }
 954 
 955     /**
 956      * This can be replaced by listening for the onShowing/onHiding events
 957      * @treatAsPrivate implementation detail
 958      * @deprecated This is an internal API that is not intended for use and will be removed in the next version
 959      */
 960     @Deprecated
 961     protected void impl_visibleChanging(boolean visible) {
 962         if (visible && (getScene() != null)) {
 963             getScene().getRoot().impl_reapplyCSS();
 964         }
 965     }
 966 
 967     /**
 968      * This can be replaced by listening for the onShown/onHidden events
 969      * @treatAsPrivate implementation detail
 970      * @deprecated This is an internal API that is not intended for use and will be removed in the next version
 971      */
 972     @Deprecated
 973     protected void impl_visibleChanged(boolean visible) {
 974         assert impl_peer != null;
 975         if (!visible) {
 976             peerListener = null;
 977             impl_peer = null;
 978         }
 979     }
 980 
 981     // PENDING_DOC_REVIEW
 982     /**
 983      * Specifies the event dispatcher for this node. The default event
 984      * dispatcher sends the received events to the registered event handlers and
 985      * filters. When replacing the value with a new {@code EventDispatcher},
 986      * the new dispatcher should forward events to the replaced dispatcher
 987      * to maintain the node's default event handling behavior.
 988      */
 989     private ObjectProperty<EventDispatcher> eventDispatcher;
 990 
 991     public final void setEventDispatcher(EventDispatcher value) {
 992         eventDispatcherProperty().set(value);
 993     }
 994 
 995     public final EventDispatcher getEventDispatcher() {
 996         return eventDispatcherProperty().get();
 997     }
 998 
 999     public final ObjectProperty<EventDispatcher> eventDispatcherProperty() {
1000         initializeInternalEventDispatcher();
1001         return eventDispatcher;
1002     }
1003 
1004     private WindowEventDispatcher internalEventDispatcher;
1005 
1006     // PENDING_DOC_REVIEW
1007     /**
1008      * Registers an event handler to this node. The handler is called when the
1009      * node receives an {@code Event} of the specified type during the bubbling
1010      * phase of event delivery.
1011      *
1012      * @param <T> the specific event class of the handler
1013      * @param eventType the type of the events to receive by the handler
1014      * @param eventHandler the handler to register
1015      * @throws NullPointerException if the event type or handler is null
1016      */
1017     public final <T extends Event> void addEventHandler(
1018             final EventType<T> eventType,
1019             final EventHandler<? super T> eventHandler) {
1020         getInternalEventDispatcher().getEventHandlerManager()
1021                                     .addEventHandler(eventType, eventHandler);
1022     }
1023 
1024     // PENDING_DOC_REVIEW
1025     /**
1026      * Unregisters a previously registered event handler from this node. One
1027      * handler might have been registered for different event types, so the
1028      * caller needs to specify the particular event type from which to
1029      * unregister the handler.
1030      *
1031      * @param <T> the specific event class of the handler
1032      * @param eventType the event type from which to unregister
1033      * @param eventHandler the handler to unregister
1034      * @throws NullPointerException if the event type or handler is null
1035      */
1036     public final <T extends Event> void removeEventHandler(
1037             final EventType<T> eventType,
1038             final EventHandler<? super T> eventHandler) {
1039         getInternalEventDispatcher().getEventHandlerManager()
1040                                     .removeEventHandler(eventType,
1041                                                         eventHandler);
1042     }
1043 
1044     // PENDING_DOC_REVIEW
1045     /**
1046      * Registers an event filter to this node. The filter is called when the
1047      * node receives an {@code Event} of the specified type during the capturing
1048      * phase of event delivery.
1049      *
1050      * @param <T> the specific event class of the filter
1051      * @param eventType the type of the events to receive by the filter
1052      * @param eventFilter the filter to register
1053      * @throws NullPointerException if the event type or filter is null
1054      */
1055     public final <T extends Event> void addEventFilter(
1056             final EventType<T> eventType,
1057             final EventHandler<? super T> eventFilter) {
1058         getInternalEventDispatcher().getEventHandlerManager()
1059                                     .addEventFilter(eventType, eventFilter);
1060     }
1061 
1062     // PENDING_DOC_REVIEW
1063     /**
1064      * Unregisters a previously registered event filter from this node. One
1065      * filter might have been registered for different event types, so the
1066      * caller needs to specify the particular event type from which to
1067      * unregister the filter.
1068      *
1069      * @param <T> the specific event class of the filter
1070      * @param eventType the event type from which to unregister
1071      * @param eventFilter the filter to unregister
1072      * @throws NullPointerException if the event type or filter is null
1073      */
1074     public final <T extends Event> void removeEventFilter(
1075             final EventType<T> eventType,
1076             final EventHandler<? super T> eventFilter) {
1077         getInternalEventDispatcher().getEventHandlerManager()
1078                                     .removeEventFilter(eventType, eventFilter);
1079     }
1080 
1081     /**
1082      * Sets the handler to use for this event type. There can only be one such handler
1083      * specified at a time. This handler is guaranteed to be called first. This is
1084      * used for registering the user-defined onFoo event handlers.
1085      *
1086      * @param <T> the specific event class of the handler
1087      * @param eventType the event type to associate with the given eventHandler
1088      * @param eventHandler the handler to register, or null to unregister
1089      * @throws NullPointerException if the event type is null
1090      */
1091     protected final <T extends Event> void setEventHandler(
1092             final EventType<T> eventType,
1093             final EventHandler<? super T> eventHandler) {
1094         getInternalEventDispatcher().getEventHandlerManager()
1095                                     .setEventHandler(eventType, eventHandler);
1096     }
1097 
1098     WindowEventDispatcher getInternalEventDispatcher() {
1099         initializeInternalEventDispatcher();
1100         return internalEventDispatcher;
1101     }
1102 
1103     private void initializeInternalEventDispatcher() {
1104         if (internalEventDispatcher == null) {
1105             internalEventDispatcher = createInternalEventDispatcher();
1106             eventDispatcher = new SimpleObjectProperty<EventDispatcher>(
1107                                           this,
1108                                           "eventDispatcher",
1109                                           internalEventDispatcher);
1110         }
1111     }
1112 
1113     WindowEventDispatcher createInternalEventDispatcher() {
1114         return new WindowEventDispatcher(this);
1115     }
1116 
1117     /**
1118      * Fires the specified event.
1119      * <p>
1120      * This method must be called on the FX user thread.
1121      *
1122      * @param event the event to fire
1123      */
1124     public final void fireEvent(Event event) {
1125         Event.fireEvent(this, event);
1126     }
1127 
1128     // PENDING_DOC_REVIEW
1129     /**
1130      * Construct an event dispatch chain for this window.
1131      *
1132      * @param tail the initial chain to build from
1133      * @return the resulting event dispatch chain for this window
1134      */
1135     @Override
1136     public EventDispatchChain buildEventDispatchChain(
1137             EventDispatchChain tail) {
1138         if (eventDispatcher != null) {
1139             final EventDispatcher eventDispatcherValue = eventDispatcher.get();
1140             if (eventDispatcherValue != null) {
1141                 tail = tail.prepend(eventDispatcherValue);
1142             }
1143         }
1144 
1145         return tail;
1146     }
1147 
1148     private int focusGrabCounter;
1149 
1150     void increaseFocusGrabCounter() {
1151         if ((++focusGrabCounter == 1) && (impl_peer != null) && isFocused()) {
1152             impl_peer.grabFocus();
1153         }
1154     }
1155 
1156     void decreaseFocusGrabCounter() {
1157         if ((--focusGrabCounter == 0) && (impl_peer != null)) {
1158             impl_peer.ungrabFocus();
1159         }
1160     }
1161 
1162     private void focusChanged(final boolean newIsFocused) {
1163         if ((focusGrabCounter > 0) && (impl_peer != null) && newIsFocused) {
1164             impl_peer.grabFocus();
1165         }
1166     }
1167 
1168     final void applyBounds() {
1169         peerBoundsConfigurator.apply();
1170     }
1171 
1172     Window getWindowOwner() {
1173         return null;
1174     }
1175 
1176     private Screen getWindowScreen() {
1177         Window window = this;
1178         do {
1179             if (!Double.isNaN(window.getX())
1180                     && !Double.isNaN(window.getY())
1181                     && !Double.isNaN(window.getWidth())
1182                     && !Double.isNaN(window.getHeight())) {
1183                 return Utils.getScreenForRectangle(
1184                                      new Rectangle2D(window.getX(),
1185                                                      window.getY(),
1186                                                      window.getWidth(),
1187                                                      window.getHeight()));
1188             }
1189 
1190             window = window.getWindowOwner();
1191         } while (window != null);
1192 
1193         return Screen.getPrimary();
1194     }
1195 
1196     private final ReadOnlyObjectWrapper<Screen> screen = new ReadOnlyObjectWrapper<>(Screen.getPrimary());
1197     private ReadOnlyObjectProperty<Screen> screenProperty() { return screen.getReadOnlyProperty(); }
1198 
1199     private void notifyScreenChanged(Object from, Object to) {
1200         screen.set(Screen.getScreenForNative(to));
1201     }
1202 
1203     /**
1204      * Caches all requested bounds settings and applies them at once during
1205      * the next pulse.
1206      */
1207     private final class TKBoundsConfigurator implements TKPulseListener {
1208         private double x;
1209         private double y;
1210         private float xGravity;
1211         private float yGravity;
1212         private double windowWidth;
1213         private double windowHeight;
1214         private double clientWidth;
1215         private double clientHeight;
1216 
1217         private boolean dirty;
1218 
1219         public TKBoundsConfigurator() {
1220             reset();
1221         }
1222 
1223         public void setX(final double x, final float xGravity) {
1224             this.x = x;
1225             this.xGravity = xGravity;
1226             setDirty();
1227         }
1228 
1229         public void setY(final double y, final float yGravity) {
1230             this.y = y;
1231             this.yGravity = yGravity;
1232             setDirty();
1233         }
1234 
1235         public void setWindowWidth(final double windowWidth) {
1236             this.windowWidth = windowWidth;
1237             setDirty();
1238         }
1239 
1240         public void setWindowHeight(final double windowHeight) {
1241             this.windowHeight = windowHeight;
1242             setDirty();
1243         }
1244 
1245         public void setClientWidth(final double clientWidth) {
1246             this.clientWidth = clientWidth;
1247             setDirty();
1248         }
1249 
1250         public void setClientHeight(final double clientHeight) {
1251             this.clientHeight = clientHeight;
1252             setDirty();
1253         }
1254 
1255         public void setLocation(final double x,
1256                                 final double y,
1257                                 final float xGravity,
1258                                 final float yGravity) {
1259             this.x = x;
1260             this.y = y;
1261             this.xGravity = xGravity;
1262             this.yGravity = yGravity;
1263             setDirty();
1264         }
1265 
1266         public void setSize(final double windowWidth,
1267                             final double windowHeight,
1268                             final double clientWidth,
1269                             final double clientHeight) {
1270             this.windowWidth = windowWidth;
1271             this.windowHeight = windowHeight;
1272             this.clientWidth = clientWidth;
1273             this.clientHeight = clientHeight;
1274             setDirty();
1275         }
1276 
1277         public void apply() {
1278             if (dirty) {
1279                 impl_peer.setBounds((float) (Double.isNaN(x) ? 0 : x),
1280                                     (float) (Double.isNaN(y) ? 0 : y),
1281                                     !Double.isNaN(x),
1282                                     !Double.isNaN(y),
1283                                     (float) windowWidth,
1284                                     (float) windowHeight,
1285                                     (float) clientWidth,
1286                                     (float) clientHeight,
1287                                     xGravity, yGravity);
1288 
1289                 reset();
1290             }
1291         }
1292 
1293         @Override
1294         public void pulse() {
1295             apply();
1296         }
1297 
1298         private void reset() {
1299             x = Double.NaN;
1300             y = Double.NaN;
1301             xGravity = 0;
1302             yGravity = 0;
1303             windowWidth = -1;
1304             windowHeight = -1;
1305             clientWidth = -1;
1306             clientHeight = -1;
1307             dirty = false;
1308         }
1309 
1310         private void setDirty() {
1311             if (!dirty) {
1312                 Toolkit.getToolkit().requestNextPulse();
1313                 dirty = true;
1314             }
1315         }
1316     }
1317 }