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