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