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