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