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