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