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