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