1 /* 2 * Copyright (c) 2010, 2015, 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.AllPermission; 29 import java.security.AccessControlContext; 30 import java.security.AccessController; 31 import java.util.ArrayList; 32 import java.util.HashMap; 33 import java.util.Iterator; 34 import java.util.List; 35 36 import com.sun.javafx.collections.annotations.ReturnsUnmodifiableCollection; 37 import javafx.beans.property.DoubleProperty; 38 import javafx.beans.property.DoublePropertyBase; 39 import javafx.beans.property.ObjectProperty; 40 import javafx.beans.property.ObjectPropertyBase; 41 import javafx.beans.property.ReadOnlyBooleanProperty; 42 import javafx.beans.property.ReadOnlyBooleanWrapper; 43 import javafx.beans.property.ReadOnlyObjectProperty; 44 import javafx.beans.property.ReadOnlyObjectWrapper; 45 import javafx.beans.property.ReadOnlyDoubleProperty; 46 import javafx.beans.property.ReadOnlyDoubleWrapper; 47 import javafx.beans.property.SimpleObjectProperty; 48 import javafx.collections.FXCollections; 49 import javafx.collections.ObservableList; 50 import javafx.collections.ObservableMap; 51 import javafx.event.Event; 52 import javafx.event.EventDispatchChain; 53 import javafx.event.EventDispatcher; 54 import javafx.event.EventHandler; 55 import javafx.event.EventTarget; 56 import javafx.event.EventType; 57 import javafx.geometry.Rectangle2D; 58 import javafx.scene.Scene; 59 60 import com.sun.javafx.util.Utils; 61 import com.sun.javafx.util.WeakReferenceQueue; 62 import com.sun.javafx.css.StyleManager; 63 import com.sun.javafx.stage.WindowEventDispatcher; 64 import com.sun.javafx.stage.WindowHelper; 65 import com.sun.javafx.stage.WindowPeerListener; 66 import com.sun.javafx.tk.TKPulseListener; 67 import com.sun.javafx.tk.TKScene; 68 import com.sun.javafx.tk.TKStage; 69 import com.sun.javafx.tk.Toolkit; 70 71 72 /** 73 * <p> 74 * A top level window within which a scene is hosted, and with which the user 75 * interacts. A Window might be a {@link Stage}, {@link PopupWindow}, or other 76 * such top level. A Window is used also for browser plug-in based deployments. 77 * </p> 78 * 79 * @since JavaFX 2.0 80 */ 81 public class Window implements EventTarget { 82 83 /** 84 * A list of all the currently _showing_ windows. This is publicly accessible via the unmodifiableWindows wrapper. 85 */ 86 private static ObservableList<Window> windows = FXCollections.observableArrayList(); 87 private static ObservableList<Window> unmodifiableWindows = FXCollections.unmodifiableObservableList(windows); 88 89 static { 90 WindowHelper.setWindowAccessor( 91 new WindowHelper.WindowAccessor() { 92 /** 93 * Allow window peer listeners to directly change reported 94 * window location and size without changing the xExplicit, 95 * yExplicit, widthExplicit and heightExplicit values. 96 */ 97 @Override 98 public void notifyLocationChanged( 99 Window window, double x, double y) { 100 window.notifyLocationChanged(x, y); 101 } 102 103 @Override 104 public void notifySizeChanged(Window window, 105 double width, 106 double height) { 107 window.notifySizeChanged(width, height); 108 } 109 110 @Override 111 public void notifyScreenChanged(Window window, 112 Object from, 113 Object to) { 114 window.notifyScreenChanged(from, to); 115 } 116 117 @Override 118 public float getUIScale(Window window) { 119 TKStage peer = window.impl_peer; 120 return peer == null ? 1.0f : peer.getUIScale(); 121 } 122 123 @Override 124 public float getRenderScale(Window window) { 125 TKStage peer = window.impl_peer; 126 return peer == null ? 1.0f : peer.getRenderScale(); 127 } 128 129 @Override 130 public ReadOnlyObjectProperty<Screen> screenProperty(Window window) { 131 return window.screenProperty(); 132 } 133 134 @Override 135 public AccessControlContext getAccessControlContext(Window window) { 136 return window.acc; 137 } 138 }); 139 } 140 141 /** 142 * Returns a list containing a reference to the currently showing JavaFX windows. The list is unmodifiable - 143 * attempting to modify this list will result in an {@link UnsupportedOperationException} being thrown at runtime. 144 * 145 * @return A list containing all windows that are currently showing. 146 * @since 9 147 */ 148 @ReturnsUnmodifiableCollection 149 public static ObservableList<Window> getWindows() { 150 final SecurityManager securityManager = System.getSecurityManager(); 151 if (securityManager != null) { 152 securityManager.checkPermission(new AllPermission()); 153 } 154 155 return unmodifiableWindows; 156 } 157 158 final AccessControlContext acc = AccessController.getContext(); 159 160 protected Window() { 161 // necessary for WindowCloseRequestHandler 162 initializeInternalEventDispatcher(); 163 } 164 165 /** 166 * The listener that gets called by peer. It's also responsible for 167 * window size/location synchronization with the window peer, which 168 * occurs on every pulse. 169 * 170 * @treatAsPrivate implementation detail 171 * @deprecated This is an internal API that is not intended for use and will be removed in the next version 172 */ 173 @Deprecated 174 protected WindowPeerListener peerListener; 175 176 /** 177 * The peer of this Stage. All external access should be 178 * made though getPeer(). Implementors note: Please ensure that this 179 * variable is defined *after* style and *before* the other variables so 180 * that style has been initialized prior to this call, and so that 181 * impl_peer is initialized prior to subsequent initialization. 182 * 183 * @treatAsPrivate implementation detail 184 * @deprecated This is an internal API that is not intended for use and will be removed in the next version 185 */ 186 @Deprecated 187 protected volatile TKStage impl_peer; 188 189 private TKBoundsConfigurator peerBoundsConfigurator = 190 new TKBoundsConfigurator(); 191 192 /** 193 * Get Stage's peer 194 * 195 * @treatAsPrivate implementation detail 196 * @deprecated This is an internal API that is not intended for use and will be removed in the next version 197 */ 198 @Deprecated 199 public TKStage impl_getPeer() { 200 return impl_peer; 201 } 202 203 /** 204 * @treatAsPrivate implementation detail 205 * @deprecated This is an internal API that is not intended for use and will be removed in the next version 206 */ 207 @Deprecated 208 public String impl_getMXWindowType() { 209 return getClass().getSimpleName(); 210 } 211 212 /** 213 * Indicates if a user requested the window to be sized to match the scene 214 * size. 215 */ 216 private boolean sizeToScene = false; 217 /** 218 * Set the width and height of this Window to match the size of the content 219 * of this Window's Scene. 220 */ 221 public void sizeToScene() { 222 if (getScene() != null && impl_peer != null) { 223 getScene().impl_preferredSize(); 224 adjustSize(false); 225 } else { 226 // Remember the request to reapply it later if needed 227 sizeToScene = true; 228 } 229 } 230 231 private void adjustSize(boolean selfSizePriority) { 232 if (getScene() == null) { 233 return; 234 } 235 if (impl_peer != null) { 236 double sceneWidth = getScene().getWidth(); 237 double cw = (sceneWidth > 0) ? sceneWidth : -1; 238 double w = -1; 239 if (selfSizePriority && widthExplicit) { 240 w = getWidth(); 241 } else if (cw <= 0) { 242 w = widthExplicit ? getWidth() : -1; 243 } else { 244 widthExplicit = false; 245 } 246 double sceneHeight = getScene().getHeight(); 247 double ch = (sceneHeight > 0) ? sceneHeight : -1; 248 double h = -1; 249 if (selfSizePriority && heightExplicit) { 250 h = getHeight(); 251 } else if (ch <= 0) { 252 h = heightExplicit ? getHeight() : -1; 253 } else { 254 heightExplicit = false; 255 } 256 257 peerBoundsConfigurator.setSize(w, h, cw, ch); 258 applyBounds(); 259 } 260 } 261 262 private static final float CENTER_ON_SCREEN_X_FRACTION = 1.0f / 2; 263 private static final float CENTER_ON_SCREEN_Y_FRACTION = 1.0f / 3; 264 265 /** 266 * Sets x and y properties on this Window so that it is centered on the 267 * curent screen. 268 * The current screen is determined from the intersection of current window bounds and 269 * visual bounds of all screens. 270 */ 271 public void centerOnScreen() { 272 xExplicit = false; 273 yExplicit = false; 274 if (impl_peer != null) { 275 Rectangle2D bounds = getWindowScreen().getVisualBounds(); 276 double centerX = 277 bounds.getMinX() + (bounds.getWidth() - getWidth()) 278 * CENTER_ON_SCREEN_X_FRACTION; 279 double centerY = 280 bounds.getMinY() + (bounds.getHeight() - getHeight()) 281 * CENTER_ON_SCREEN_Y_FRACTION; 282 283 x.set(centerX); 284 y.set(centerY); 285 peerBoundsConfigurator.setLocation(centerX, centerY, 286 CENTER_ON_SCREEN_X_FRACTION, 287 CENTER_ON_SCREEN_Y_FRACTION); 288 applyBounds(); 289 } 290 } 291 292 private boolean xExplicit = false; 293 /** 294 * The horizontal location of this {@code Stage} on the screen. Changing 295 * this attribute will move the {@code Stage} horizontally. Changing this 296 * attribute will not visually affect a {@code Stage} while 297 * {@code fullScreen} is true, but will be honored by the {@code Stage} once 298 * {@code fullScreen} becomes false. 299 */ 300 private ReadOnlyDoubleWrapper x = 301 new ReadOnlyDoubleWrapper(this, "x", Double.NaN); 302 303 public final void setX(double value) { 304 setXInternal(value); 305 } 306 public final double getX() { return x.get(); } 307 public final ReadOnlyDoubleProperty xProperty() { return x.getReadOnlyProperty(); } 308 309 void setXInternal(double value) { 310 x.set(value); 311 peerBoundsConfigurator.setX(value, 0); 312 xExplicit = true; 313 } 314 315 private boolean yExplicit = false; 316 /** 317 * The vertical location of this {@code Stage} on the screen. Changing this 318 * attribute will move the {@code Stage} vertically. Changing this 319 * attribute will not visually affect a {@code Stage} while 320 * {@code fullScreen} is true, but will be honored by the {@code Stage} once 321 * {@code fullScreen} becomes false. 322 */ 323 private ReadOnlyDoubleWrapper y = 324 new ReadOnlyDoubleWrapper(this, "y", Double.NaN); 325 326 public final void setY(double value) { 327 setYInternal(value); 328 } 329 public final double getY() { return y.get(); } 330 public final ReadOnlyDoubleProperty yProperty() { return y.getReadOnlyProperty(); } 331 332 void setYInternal(double value) { 333 y.set(value); 334 peerBoundsConfigurator.setY(value, 0); 335 yExplicit = true; 336 } 337 338 /** 339 * Notification from the windowing system that the window's position has 340 * changed. 341 * 342 * @param newX the new window x position 343 * @param newY the new window y position 344 */ 345 void notifyLocationChanged(double newX, double newY) { 346 x.set(newX); 347 y.set(newY); 348 } 349 350 private boolean widthExplicit = false; 351 352 /** 353 * The width of this {@code Stage}. Changing this attribute will narrow or 354 * widen the width of the {@code Stage}. Changing this 355 * attribute will not visually affect a {@code Stage} while 356 * {@code fullScreen} is true, but will be honored by the {@code Stage} once 357 * {@code fullScreen} becomes false. This value includes any and all 358 * decorations which may be added by the Operating System such as resizable 359 * frame handles. Typical applications will set the {@link javafx.scene.Scene} width 360 * instead. 361 * <p> 362 * The property is read only because it can be changed externally 363 * by the underlying platform and therefore must not be bindable. 364 * </p> 365 */ 366 private ReadOnlyDoubleWrapper width = 367 new ReadOnlyDoubleWrapper(this, "width", Double.NaN); 368 369 public final void setWidth(double value) { 370 width.set(value); 371 peerBoundsConfigurator.setWindowWidth(value); 372 widthExplicit = true; 373 } 374 public final double getWidth() { return width.get(); } 375 public final ReadOnlyDoubleProperty widthProperty() { return width.getReadOnlyProperty(); } 376 377 private boolean heightExplicit = false; 378 /** 379 * The height of this {@code Stage}. Changing this attribute will shrink 380 * or heighten the height of the {@code Stage}. Changing this 381 * attribute will not visually affect a {@code Stage} while 382 * {@code fullScreen} is true, but will be honored by the {@code Stage} once 383 * {@code fullScreen} becomes false. This value includes any and all 384 * decorations which may be added by the Operating System such as the title 385 * bar. Typical applications will set the {@link javafx.scene.Scene} height instead. 386 * <p> 387 * The property is read only because it can be changed externally 388 * by the underlying platform and therefore must not be bindable. 389 * </p> 390 */ 391 private ReadOnlyDoubleWrapper height = 392 new ReadOnlyDoubleWrapper(this, "height", Double.NaN); 393 394 public final void setHeight(double value) { 395 height.set(value); 396 peerBoundsConfigurator.setWindowHeight(value); 397 heightExplicit = true; 398 } 399 public final double getHeight() { return height.get(); } 400 public final ReadOnlyDoubleProperty heightProperty() { return height.getReadOnlyProperty(); } 401 402 /** 403 * Notification from the windowing system that the window's size has 404 * changed. 405 * 406 * @param newWidth the new window width 407 * @param newHeight the new window height 408 */ 409 void notifySizeChanged(double newWidth, double newHeight) { 410 width.set(newWidth); 411 height.set(newHeight); 412 } 413 414 /** 415 * Whether or not this {@code Window} has the keyboard or input focus. 416 * <p> 417 * The property is read only because it can be changed externally 418 * by the underlying platform and therefore must not be bindable. 419 * </p> 420 * 421 * @profile common 422 */ 423 private ReadOnlyBooleanWrapper focused = new ReadOnlyBooleanWrapper() { 424 @Override protected void invalidated() { 425 focusChanged(get()); 426 } 427 428 @Override 429 public Object getBean() { 430 return Window.this; 431 } 432 433 @Override 434 public String getName() { 435 return "focused"; 436 } 437 }; 438 439 /** 440 * @treatAsPrivate 441 * @deprecated 442 */ 443 @Deprecated 444 public final void setFocused(boolean value) { focused.set(value); } 445 446 /** 447 * Requests that this {@code Window} get the input focus. 448 */ 449 public final void requestFocus() { 450 if (impl_peer != null) { 451 impl_peer.requestFocus(); 452 } 453 } 454 public final boolean isFocused() { return focused.get(); } 455 public final ReadOnlyBooleanProperty focusedProperty() { return focused.getReadOnlyProperty(); } 456 457 /************************************************************************* 458 * * 459 * * 460 * * 461 *************************************************************************/ 462 463 private static final Object USER_DATA_KEY = new Object(); 464 // A map containing a set of properties for this window 465 private ObservableMap<Object, Object> properties; 466 467 /** 468 * Returns an observable map of properties on this node for use primarily 469 * by application developers. 470 * 471 * @return an observable map of properties on this node for use primarily 472 * by application developers 473 * 474 * @since JavaFX 8u40 475 */ 476 public final ObservableMap<Object, Object> getProperties() { 477 if (properties == null) { 478 properties = FXCollections.observableMap(new HashMap<Object, Object>()); 479 } 480 return properties; 481 } 482 483 /** 484 * Tests if Window has properties. 485 * @return true if node has properties. 486 * 487 * @since JavaFX 8u40 488 */ 489 public boolean hasProperties() { 490 return properties != null && !properties.isEmpty(); 491 } 492 493 /** 494 * Convenience method for setting a single Object property that can be 495 * retrieved at a later date. This is functionally equivalent to calling 496 * the getProperties().put(Object key, Object value) method. This can later 497 * be retrieved by calling {@link Window#getUserData()}. 498 * 499 * @param value The value to be stored - this can later be retrieved by calling 500 * {@link Window#getUserData()}. 501 * 502 * @since JavaFX 8u40 503 */ 504 public void setUserData(Object value) { 505 getProperties().put(USER_DATA_KEY, value); 506 } 507 508 /** 509 * Returns a previously set Object property, or null if no such property 510 * has been set using the {@link Window#setUserData(java.lang.Object)} method. 511 * 512 * @return The Object that was previously set, or null if no property 513 * has been set or if null was set. 514 * 515 * @since JavaFX 8u40 516 */ 517 public Object getUserData() { 518 return getProperties().get(USER_DATA_KEY); 519 } 520 521 /** 522 * The {@code Scene} to be rendered on this {@code Stage}. There can only 523 * be one {@code Scene} on the {@code Stage} at a time, and a {@code Scene} 524 * can only be on one {@code Stage} at a time. Setting a {@code Scene} on 525 * a different {@code Stage} will cause the old {@code Stage} to lose the 526 * reference before the new one gains it. You may swap {@code Scene}s on 527 * a {@code Stage} at any time, even while in full-screen exclusive mode. 528 * 529 * An {@link IllegalStateException} is thrown if this property is set 530 * on a thread other than the JavaFX Application Thread. 531 * 532 * @defaultValue null 533 */ 534 private SceneModel scene = new SceneModel(); 535 protected void setScene(Scene value) { scene.set(value); } 536 public final Scene getScene() { return scene.get(); } 537 public final ReadOnlyObjectProperty<Scene> sceneProperty() { return scene.getReadOnlyProperty(); } 538 539 private final class SceneModel extends ReadOnlyObjectWrapper<Scene> { 540 private Scene oldScene; 541 542 @Override protected void invalidated() { 543 final Scene newScene = get(); 544 if (oldScene == newScene) { 545 return; 546 } 547 if (impl_peer != null) { 548 Toolkit.getToolkit().checkFxUserThread(); 549 } 550 // First, detach scene peer from this window 551 updatePeerScene(null); 552 // Second, dispose scene peer 553 if (oldScene != null) { 554 oldScene.impl_setWindow(null); 555 StyleManager.getInstance().forget(oldScene); 556 } 557 if (newScene != null) { 558 final Window oldWindow = newScene.getWindow(); 559 if (oldWindow != null) { 560 // if the new scene was previously set to a window 561 // we need to remove it from that window 562 // NOTE: can this "scene" property be bound? 563 oldWindow.setScene(null); 564 } 565 566 // Set the "window" on the new scene. This will also trigger 567 // scene's peer creation. 568 newScene.impl_setWindow(Window.this); 569 // Set scene impl on stage impl 570 updatePeerScene(newScene.impl_getPeer()); 571 572 // Fix for RT-15432: we should update new Scene's stylesheets, if the 573 // window is already showing. For not yet shown windows, the update is 574 // performed in Window.visibleChanging() 575 if (isShowing()) { 576 newScene.getRoot().impl_reapplyCSS(); 577 578 if (!widthExplicit || !heightExplicit) { 579 getScene().impl_preferredSize(); 580 adjustSize(true); 581 } 582 } 583 } 584 585 oldScene = newScene; 586 } 587 588 @Override 589 public Object getBean() { 590 return Window.this; 591 } 592 593 @Override 594 public String getName() { 595 return "scene"; 596 } 597 598 private void updatePeerScene(final TKScene tkScene) { 599 if (impl_peer != null) { 600 // Set scene impl on stage impl 601 impl_peer.setScene(tkScene); 602 } 603 } 604 } 605 606 /** 607 * Defines the opacity of the {@code Stage} as a value between 0.0 and 1.0. 608 * The opacity is reflected across the {@code Stage}, its {@code Decoration} 609 * and its {@code Scene} content. On a JavaFX runtime platform that does not 610 * support opacity, assigning a value to this variable will have no 611 * visible difference. A {@code Stage} with 0% opacity is fully translucent. 612 * Typically, a {@code Stage} with 0% opacity will not receive any mouse 613 * events. 614 * 615 * @defaultValue 1.0 616 */ 617 private DoubleProperty opacity; 618 619 public final void setOpacity(double value) { 620 opacityProperty().set(value); 621 } 622 623 public final double getOpacity() { 624 return opacity == null ? 1.0 : opacity.get(); 625 } 626 627 public final DoubleProperty opacityProperty() { 628 if (opacity == null) { 629 opacity = new DoublePropertyBase(1.0) { 630 631 @Override 632 protected void invalidated() { 633 if (impl_peer != null) { 634 impl_peer.setOpacity((float) get()); 635 } 636 } 637 638 @Override 639 public Object getBean() { 640 return Window.this; 641 } 642 643 @Override 644 public String getName() { 645 return "opacity"; 646 } 647 }; 648 } 649 return opacity; 650 } 651 652 /** 653 * Called when there is an external request to close this {@code Window}. 654 * The installed event handler can prevent window closing by consuming the 655 * received event. 656 */ 657 private ObjectProperty<EventHandler<WindowEvent>> onCloseRequest; 658 public final void setOnCloseRequest(EventHandler<WindowEvent> value) { 659 onCloseRequestProperty().set(value); 660 } 661 public final EventHandler<WindowEvent> getOnCloseRequest() { 662 return (onCloseRequest != null) ? onCloseRequest.get() : null; 663 } 664 public final ObjectProperty<EventHandler<WindowEvent>> 665 onCloseRequestProperty() { 666 if (onCloseRequest == null) { 667 onCloseRequest = new ObjectPropertyBase<EventHandler<WindowEvent>>() { 668 @Override protected void invalidated() { 669 setEventHandler(WindowEvent.WINDOW_CLOSE_REQUEST, get()); 670 } 671 672 @Override 673 public Object getBean() { 674 return Window.this; 675 } 676 677 @Override 678 public String getName() { 679 return "onCloseRequest"; 680 } 681 }; 682 } 683 return onCloseRequest; 684 } 685 686 /** 687 * Called just prior to the Window being shown. 688 */ 689 private ObjectProperty<EventHandler<WindowEvent>> onShowing; 690 public final void setOnShowing(EventHandler<WindowEvent> value) { onShowingProperty().set(value); } 691 public final EventHandler<WindowEvent> getOnShowing() { 692 return onShowing == null ? null : onShowing.get(); 693 } 694 public final ObjectProperty<EventHandler<WindowEvent>> onShowingProperty() { 695 if (onShowing == null) { 696 onShowing = new ObjectPropertyBase<EventHandler<WindowEvent>>() { 697 @Override protected void invalidated() { 698 setEventHandler(WindowEvent.WINDOW_SHOWING, get()); 699 } 700 701 @Override 702 public Object getBean() { 703 return Window.this; 704 } 705 706 @Override 707 public String getName() { 708 return "onShowing"; 709 } 710 }; 711 } 712 return onShowing; 713 } 714 715 /** 716 * Called just after the Window is shown. 717 */ 718 private ObjectProperty<EventHandler<WindowEvent>> onShown; 719 public final void setOnShown(EventHandler<WindowEvent> value) { onShownProperty().set(value); } 720 public final EventHandler<WindowEvent> getOnShown() { 721 return onShown == null ? null : onShown.get(); 722 } 723 public final ObjectProperty<EventHandler<WindowEvent>> onShownProperty() { 724 if (onShown == null) { 725 onShown = new ObjectPropertyBase<EventHandler<WindowEvent>>() { 726 @Override protected void invalidated() { 727 setEventHandler(WindowEvent.WINDOW_SHOWN, get()); 728 } 729 730 @Override 731 public Object getBean() { 732 return Window.this; 733 } 734 735 @Override 736 public String getName() { 737 return "onShown"; 738 } 739 }; 740 } 741 return onShown; 742 } 743 744 /** 745 * Called just prior to the Window being hidden. 746 */ 747 private ObjectProperty<EventHandler<WindowEvent>> onHiding; 748 public final void setOnHiding(EventHandler<WindowEvent> value) { onHidingProperty().set(value); } 749 public final EventHandler<WindowEvent> getOnHiding() { 750 return onHiding == null ? null : onHiding.get(); 751 } 752 public final ObjectProperty<EventHandler<WindowEvent>> onHidingProperty() { 753 if (onHiding == null) { 754 onHiding = new ObjectPropertyBase<EventHandler<WindowEvent>>() { 755 @Override protected void invalidated() { 756 setEventHandler(WindowEvent.WINDOW_HIDING, get()); 757 } 758 759 @Override 760 public Object getBean() { 761 return Window.this; 762 } 763 764 @Override 765 public String getName() { 766 return "onHiding"; 767 } 768 }; 769 } 770 return onHiding; 771 } 772 773 /** 774 * Called just after the Window has been hidden. 775 * When the {@code Window} is hidden, this event handler is invoked allowing 776 * the developer to clean up resources or perform other tasks when the 777 * {@link Window} is closed. 778 */ 779 private ObjectProperty<EventHandler<WindowEvent>> onHidden; 780 public final void setOnHidden(EventHandler<WindowEvent> value) { onHiddenProperty().set(value); } 781 public final EventHandler<WindowEvent> getOnHidden() { 782 return onHidden == null ? null : onHidden.get(); 783 } 784 public final ObjectProperty<EventHandler<WindowEvent>> onHiddenProperty() { 785 if (onHidden == null) { 786 onHidden = new ObjectPropertyBase<EventHandler<WindowEvent>>() { 787 @Override protected void invalidated() { 788 setEventHandler(WindowEvent.WINDOW_HIDDEN, get()); 789 } 790 791 @Override 792 public Object getBean() { 793 return Window.this; 794 } 795 796 @Override 797 public String getName() { 798 return "onHidden"; 799 } 800 }; 801 } 802 return onHidden; 803 } 804 805 /** 806 * Whether or not this {@code Stage} is showing (that is, open on the 807 * user's system). The Stage might be "showing", yet the user might not 808 * be able to see it due to the Stage being rendered behind another window 809 * or due to the Stage being positioned off the monitor. 810 * 811 * @defaultValue false 812 */ 813 private ReadOnlyBooleanWrapper showing = new ReadOnlyBooleanWrapper() { 814 private boolean oldVisible; 815 816 @Override protected void invalidated() { 817 final boolean newVisible = get(); 818 if (oldVisible == newVisible) { 819 return; 820 } 821 822 if (!oldVisible && newVisible) { 823 fireEvent(new WindowEvent(Window.this, WindowEvent.WINDOW_SHOWING)); 824 } else { 825 fireEvent(new WindowEvent(Window.this, WindowEvent.WINDOW_HIDING)); 826 } 827 828 oldVisible = newVisible; 829 impl_visibleChanging(newVisible); 830 if (newVisible) { 831 hasBeenVisible = true; 832 windows.add(Window.this); 833 } else { 834 windows.remove(Window.this); 835 } 836 Toolkit tk = Toolkit.getToolkit(); 837 if (impl_peer != null) { 838 if (newVisible) { 839 if (peerListener == null) { 840 peerListener = new WindowPeerListener(Window.this); 841 } 842 843 // Setup listener for changes coming back from peer 844 impl_peer.setTKStageListener(peerListener); 845 // Register pulse listener 846 tk.addStageTkPulseListener(peerBoundsConfigurator); 847 848 if (getScene() != null) { 849 getScene().impl_initPeer(); 850 impl_peer.setScene(getScene().impl_getPeer()); 851 getScene().impl_preferredSize(); 852 } 853 854 // Set peer bounds 855 if ((getScene() != null) && (!widthExplicit || !heightExplicit)) { 856 adjustSize(true); 857 } else { 858 peerBoundsConfigurator.setSize( 859 getWidth(), getHeight(), -1, -1); 860 } 861 862 if (!xExplicit && !yExplicit) { 863 centerOnScreen(); 864 } else { 865 peerBoundsConfigurator.setLocation(getX(), getY(), 866 0, 0); 867 } 868 869 // set peer bounds before the window is shown 870 applyBounds(); 871 872 impl_peer.setOpacity((float)getOpacity()); 873 874 impl_peer.setVisible(true); 875 fireEvent(new WindowEvent(Window.this, WindowEvent.WINDOW_SHOWN)); 876 } else { 877 impl_peer.setVisible(false); 878 879 // Call listener 880 fireEvent(new WindowEvent(Window.this, WindowEvent.WINDOW_HIDDEN)); 881 882 if (getScene() != null) { 883 impl_peer.setScene(null); 884 getScene().impl_disposePeer(); 885 StyleManager.getInstance().forget(getScene()); 886 } 887 888 // Remove toolkit pulse listener 889 tk.removeStageTkPulseListener(peerBoundsConfigurator); 890 // Remove listener for changes coming back from peer 891 impl_peer.setTKStageListener(null); 892 893 // Notify peer 894 impl_peer.close(); 895 } 896 } 897 if (newVisible) { 898 tk.requestNextPulse(); 899 } 900 impl_visibleChanged(newVisible); 901 902 if (sizeToScene) { 903 if (newVisible) { 904 // Now that the visibleChanged has completed, the insets of the window 905 // might have changed (e.g. due to setResizable(false)). Reapply the 906 // sizeToScene() request if needed to account for the new insets. 907 sizeToScene(); 908 } 909 910 // Reset the flag unconditionally upon visibility changes 911 sizeToScene = false; 912 } 913 } 914 915 @Override 916 public Object getBean() { 917 return Window.this; 918 } 919 920 @Override 921 public String getName() { 922 return "showing"; 923 } 924 }; 925 private void setShowing(boolean value) { 926 Toolkit.getToolkit().checkFxUserThread(); 927 showing.set(value); 928 } 929 public final boolean isShowing() { return showing.get(); } 930 public final ReadOnlyBooleanProperty showingProperty() { return showing.getReadOnlyProperty(); } 931 932 // flag indicating whether this window has ever been made visible. 933 boolean hasBeenVisible = false; 934 935 /** 936 * Attempts to show this Window by setting visibility to true 937 * 938 * @throws IllegalStateException if this method is called on a thread 939 * other than the JavaFX Application Thread. 940 */ 941 protected void show() { 942 setShowing(true); 943 } 944 945 /** 946 * Attempts to hide this Window by setting the visibility to false. 947 * 948 * @throws IllegalStateException if this method is called on a thread 949 * other than the JavaFX Application Thread. 950 */ 951 public void hide() { 952 setShowing(false); 953 } 954 955 /** 956 * This can be replaced by listening for the onShowing/onHiding events 957 * @treatAsPrivate implementation detail 958 * @deprecated This is an internal API that is not intended for use and will be removed in the next version 959 */ 960 @Deprecated 961 protected void impl_visibleChanging(boolean visible) { 962 if (visible && (getScene() != null)) { 963 getScene().getRoot().impl_reapplyCSS(); 964 } 965 } 966 967 /** 968 * This can be replaced by listening for the onShown/onHidden events 969 * @treatAsPrivate implementation detail 970 * @deprecated This is an internal API that is not intended for use and will be removed in the next version 971 */ 972 @Deprecated 973 protected void impl_visibleChanged(boolean visible) { 974 assert impl_peer != null; 975 if (!visible) { 976 peerListener = null; 977 impl_peer = null; 978 } 979 } 980 981 // PENDING_DOC_REVIEW 982 /** 983 * Specifies the event dispatcher for this node. The default event 984 * dispatcher sends the received events to the registered event handlers and 985 * filters. When replacing the value with a new {@code EventDispatcher}, 986 * the new dispatcher should forward events to the replaced dispatcher 987 * to maintain the node's default event handling behavior. 988 */ 989 private ObjectProperty<EventDispatcher> eventDispatcher; 990 991 public final void setEventDispatcher(EventDispatcher value) { 992 eventDispatcherProperty().set(value); 993 } 994 995 public final EventDispatcher getEventDispatcher() { 996 return eventDispatcherProperty().get(); 997 } 998 999 public final ObjectProperty<EventDispatcher> eventDispatcherProperty() { 1000 initializeInternalEventDispatcher(); 1001 return eventDispatcher; 1002 } 1003 1004 private WindowEventDispatcher internalEventDispatcher; 1005 1006 // PENDING_DOC_REVIEW 1007 /** 1008 * Registers an event handler to this node. The handler is called when the 1009 * node receives an {@code Event} of the specified type during the bubbling 1010 * phase of event delivery. 1011 * 1012 * @param <T> the specific event class of the handler 1013 * @param eventType the type of the events to receive by the handler 1014 * @param eventHandler the handler to register 1015 * @throws NullPointerException if the event type or handler is null 1016 */ 1017 public final <T extends Event> void addEventHandler( 1018 final EventType<T> eventType, 1019 final EventHandler<? super T> eventHandler) { 1020 getInternalEventDispatcher().getEventHandlerManager() 1021 .addEventHandler(eventType, eventHandler); 1022 } 1023 1024 // PENDING_DOC_REVIEW 1025 /** 1026 * Unregisters a previously registered event handler from this node. One 1027 * handler might have been registered for different event types, so the 1028 * caller needs to specify the particular event type from which to 1029 * unregister the handler. 1030 * 1031 * @param <T> the specific event class of the handler 1032 * @param eventType the event type from which to unregister 1033 * @param eventHandler the handler to unregister 1034 * @throws NullPointerException if the event type or handler is null 1035 */ 1036 public final <T extends Event> void removeEventHandler( 1037 final EventType<T> eventType, 1038 final EventHandler<? super T> eventHandler) { 1039 getInternalEventDispatcher().getEventHandlerManager() 1040 .removeEventHandler(eventType, 1041 eventHandler); 1042 } 1043 1044 // PENDING_DOC_REVIEW 1045 /** 1046 * Registers an event filter to this node. The filter is called when the 1047 * node receives an {@code Event} of the specified type during the capturing 1048 * phase of event delivery. 1049 * 1050 * @param <T> the specific event class of the filter 1051 * @param eventType the type of the events to receive by the filter 1052 * @param eventFilter the filter to register 1053 * @throws NullPointerException if the event type or filter is null 1054 */ 1055 public final <T extends Event> void addEventFilter( 1056 final EventType<T> eventType, 1057 final EventHandler<? super T> eventFilter) { 1058 getInternalEventDispatcher().getEventHandlerManager() 1059 .addEventFilter(eventType, eventFilter); 1060 } 1061 1062 // PENDING_DOC_REVIEW 1063 /** 1064 * Unregisters a previously registered event filter from this node. One 1065 * filter might have been registered for different event types, so the 1066 * caller needs to specify the particular event type from which to 1067 * unregister the filter. 1068 * 1069 * @param <T> the specific event class of the filter 1070 * @param eventType the event type from which to unregister 1071 * @param eventFilter the filter to unregister 1072 * @throws NullPointerException if the event type or filter is null 1073 */ 1074 public final <T extends Event> void removeEventFilter( 1075 final EventType<T> eventType, 1076 final EventHandler<? super T> eventFilter) { 1077 getInternalEventDispatcher().getEventHandlerManager() 1078 .removeEventFilter(eventType, eventFilter); 1079 } 1080 1081 /** 1082 * Sets the handler to use for this event type. There can only be one such handler 1083 * specified at a time. This handler is guaranteed to be called first. This is 1084 * used for registering the user-defined onFoo event handlers. 1085 * 1086 * @param <T> the specific event class of the handler 1087 * @param eventType the event type to associate with the given eventHandler 1088 * @param eventHandler the handler to register, or null to unregister 1089 * @throws NullPointerException if the event type is null 1090 */ 1091 protected final <T extends Event> void setEventHandler( 1092 final EventType<T> eventType, 1093 final EventHandler<? super T> eventHandler) { 1094 getInternalEventDispatcher().getEventHandlerManager() 1095 .setEventHandler(eventType, eventHandler); 1096 } 1097 1098 WindowEventDispatcher getInternalEventDispatcher() { 1099 initializeInternalEventDispatcher(); 1100 return internalEventDispatcher; 1101 } 1102 1103 private void initializeInternalEventDispatcher() { 1104 if (internalEventDispatcher == null) { 1105 internalEventDispatcher = createInternalEventDispatcher(); 1106 eventDispatcher = new SimpleObjectProperty<EventDispatcher>( 1107 this, 1108 "eventDispatcher", 1109 internalEventDispatcher); 1110 } 1111 } 1112 1113 WindowEventDispatcher createInternalEventDispatcher() { 1114 return new WindowEventDispatcher(this); 1115 } 1116 1117 /** 1118 * Fires the specified event. 1119 * <p> 1120 * This method must be called on the FX user thread. 1121 * 1122 * @param event the event to fire 1123 */ 1124 public final void fireEvent(Event event) { 1125 Event.fireEvent(this, event); 1126 } 1127 1128 // PENDING_DOC_REVIEW 1129 /** 1130 * Construct an event dispatch chain for this window. 1131 * 1132 * @param tail the initial chain to build from 1133 * @return the resulting event dispatch chain for this window 1134 */ 1135 @Override 1136 public EventDispatchChain buildEventDispatchChain( 1137 EventDispatchChain tail) { 1138 if (eventDispatcher != null) { 1139 final EventDispatcher eventDispatcherValue = eventDispatcher.get(); 1140 if (eventDispatcherValue != null) { 1141 tail = tail.prepend(eventDispatcherValue); 1142 } 1143 } 1144 1145 return tail; 1146 } 1147 1148 private int focusGrabCounter; 1149 1150 void increaseFocusGrabCounter() { 1151 if ((++focusGrabCounter == 1) && (impl_peer != null) && isFocused()) { 1152 impl_peer.grabFocus(); 1153 } 1154 } 1155 1156 void decreaseFocusGrabCounter() { 1157 if ((--focusGrabCounter == 0) && (impl_peer != null)) { 1158 impl_peer.ungrabFocus(); 1159 } 1160 } 1161 1162 private void focusChanged(final boolean newIsFocused) { 1163 if ((focusGrabCounter > 0) && (impl_peer != null) && newIsFocused) { 1164 impl_peer.grabFocus(); 1165 } 1166 } 1167 1168 final void applyBounds() { 1169 peerBoundsConfigurator.apply(); 1170 } 1171 1172 Window getWindowOwner() { 1173 return null; 1174 } 1175 1176 private Screen getWindowScreen() { 1177 Window window = this; 1178 do { 1179 if (!Double.isNaN(window.getX()) 1180 && !Double.isNaN(window.getY()) 1181 && !Double.isNaN(window.getWidth()) 1182 && !Double.isNaN(window.getHeight())) { 1183 return Utils.getScreenForRectangle( 1184 new Rectangle2D(window.getX(), 1185 window.getY(), 1186 window.getWidth(), 1187 window.getHeight())); 1188 } 1189 1190 window = window.getWindowOwner(); 1191 } while (window != null); 1192 1193 return Screen.getPrimary(); 1194 } 1195 1196 private final ReadOnlyObjectWrapper<Screen> screen = new ReadOnlyObjectWrapper<>(Screen.getPrimary()); 1197 private ReadOnlyObjectProperty<Screen> screenProperty() { return screen.getReadOnlyProperty(); } 1198 1199 private void notifyScreenChanged(Object from, Object to) { 1200 screen.set(Screen.getScreenForNative(to)); 1201 } 1202 1203 /** 1204 * Caches all requested bounds settings and applies them at once during 1205 * the next pulse. 1206 */ 1207 private final class TKBoundsConfigurator implements TKPulseListener { 1208 private double x; 1209 private double y; 1210 private float xGravity; 1211 private float yGravity; 1212 private double windowWidth; 1213 private double windowHeight; 1214 private double clientWidth; 1215 private double clientHeight; 1216 1217 private boolean dirty; 1218 1219 public TKBoundsConfigurator() { 1220 reset(); 1221 } 1222 1223 public void setX(final double x, final float xGravity) { 1224 this.x = x; 1225 this.xGravity = xGravity; 1226 setDirty(); 1227 } 1228 1229 public void setY(final double y, final float yGravity) { 1230 this.y = y; 1231 this.yGravity = yGravity; 1232 setDirty(); 1233 } 1234 1235 public void setWindowWidth(final double windowWidth) { 1236 this.windowWidth = windowWidth; 1237 setDirty(); 1238 } 1239 1240 public void setWindowHeight(final double windowHeight) { 1241 this.windowHeight = windowHeight; 1242 setDirty(); 1243 } 1244 1245 public void setClientWidth(final double clientWidth) { 1246 this.clientWidth = clientWidth; 1247 setDirty(); 1248 } 1249 1250 public void setClientHeight(final double clientHeight) { 1251 this.clientHeight = clientHeight; 1252 setDirty(); 1253 } 1254 1255 public void setLocation(final double x, 1256 final double y, 1257 final float xGravity, 1258 final float yGravity) { 1259 this.x = x; 1260 this.y = y; 1261 this.xGravity = xGravity; 1262 this.yGravity = yGravity; 1263 setDirty(); 1264 } 1265 1266 public void setSize(final double windowWidth, 1267 final double windowHeight, 1268 final double clientWidth, 1269 final double clientHeight) { 1270 this.windowWidth = windowWidth; 1271 this.windowHeight = windowHeight; 1272 this.clientWidth = clientWidth; 1273 this.clientHeight = clientHeight; 1274 setDirty(); 1275 } 1276 1277 public void apply() { 1278 if (dirty) { 1279 impl_peer.setBounds((float) (Double.isNaN(x) ? 0 : x), 1280 (float) (Double.isNaN(y) ? 0 : y), 1281 !Double.isNaN(x), 1282 !Double.isNaN(y), 1283 (float) windowWidth, 1284 (float) windowHeight, 1285 (float) clientWidth, 1286 (float) clientHeight, 1287 xGravity, yGravity); 1288 1289 reset(); 1290 } 1291 } 1292 1293 @Override 1294 public void pulse() { 1295 apply(); 1296 } 1297 1298 private void reset() { 1299 x = Double.NaN; 1300 y = Double.NaN; 1301 xGravity = 0; 1302 yGravity = 0; 1303 windowWidth = -1; 1304 windowHeight = -1; 1305 clientWidth = -1; 1306 clientHeight = -1; 1307 dirty = false; 1308 } 1309 1310 private void setDirty() { 1311 if (!dirty) { 1312 Toolkit.getToolkit().requestNextPulse(); 1313 dirty = true; 1314 } 1315 } 1316 } 1317 }