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