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