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