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