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