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