Spec-Zone .ru
спецификации, руководства, описания, API
|
001/* 002 * Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved. 003 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 004 * 005 * This code is free software; you can redistribute it and/or modify it 006 * under the terms of the GNU General Public License version 2 only, as 007 * published by the Free Software Foundation. Oracle designates this 008 * particular file as subject to the "Classpath" exception as provided 009 * by Oracle in the LICENSE file that accompanied this code. 010 * 011 * This code is distributed in the hope that it will be useful, but WITHOUT 012 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 013 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 014 * version 2 for more details (a copy is included in the LICENSE file that 015 * accompanied this code). 016 * 017 * You should have received a copy of the GNU General Public License version 018 * 2 along with this work; if not, write to the Free Software Foundation, 019 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 020 * 021 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 022 * or visit www.oracle.com if you need additional information or have any 023 * questions. 024 */ 025 026package javafx.stage; 027 028import com.sun.javafx.Utils; 029import com.sun.javafx.event.DirectEvent; 030import java.util.ArrayList; 031import java.util.List; 032 033import javafx.beans.InvalidationListener; 034import javafx.beans.Observable; 035import javafx.beans.property.BooleanProperty; 036import javafx.beans.property.BooleanPropertyBase; 037import javafx.beans.property.ObjectProperty; 038import javafx.beans.property.SimpleBooleanProperty; 039import javafx.beans.property.SimpleObjectProperty; 040import javafx.beans.value.ChangeListener; 041import javafx.beans.value.ObservableValue; 042import javafx.collections.ObservableList; 043import javafx.event.Event; 044import javafx.event.EventHandler; 045import javafx.geometry.Bounds; 046import javafx.geometry.Rectangle2D; 047import javafx.scene.Group; 048import javafx.scene.Node; 049import javafx.scene.Parent; 050import javafx.scene.Scene; 051 052import com.sun.javafx.event.EventHandlerManager; 053import com.sun.javafx.event.EventRedirector; 054import com.sun.javafx.event.EventUtil; 055import com.sun.javafx.perf.PerformanceTracker; 056import com.sun.javafx.scene.SceneHelper; 057import com.sun.javafx.stage.FocusUngrabEvent; 058import com.sun.javafx.stage.PopupWindowPeerListener; 059import com.sun.javafx.stage.WindowCloseRequestHandler; 060import com.sun.javafx.stage.WindowEventDispatcher; 061import com.sun.javafx.tk.Toolkit; 062import javafx.beans.property.ReadOnlyObjectProperty; 063import javafx.beans.property.ReadOnlyObjectWrapper; 064import javafx.event.EventTarget; 065import javafx.event.EventType; 066import javafx.scene.input.KeyCombination; 067import javafx.scene.input.KeyEvent; 068import javafx.scene.input.MouseEvent; 069 070/** 071 * PopupWindow is the parent for a variety of different types of popup 072 * based windows including {@link Popup} and {@link javafx.scene.control.Tooltip} 073 * and {@link javafx.scene.control.ContextMenu}. 074 * <p> 075 * A PopupWindow is a secondary window which has no window decorations or title bar. 076 * It doesn't show up in the OS as a top-level window. It is typically 077 * used for tool tip like notification, drop down boxes, menus, and so forth. 078 * <p> 079 * The PopupWindow <strong>cannot be shown without an owner</strong>. 080 * PopupWindows require that an owner window exist in order to be shown. However, 081 * it is possible to create a PopupWindow ahead of time and simply set the owner 082 * (or change the owner) before first being made visible. Attempting to change 083 * the owner while the PopupWindow is visible will result in an IllegalStateException. 084 * <p> 085 * The PopupWindow encapsulates much of the behavior and functionality common to popups, 086 * such as the ability to close when the "esc" key is pressed, or the ability to 087 * hide all child popup windows whenever this window is hidden. These abilities can 088 * be enabled or disabled via properties. 089 */ 090public abstract class PopupWindow extends Window { 091 /** 092 * A private list of all child popups. 093 */ 094 private final List<PopupWindow> children = new ArrayList<PopupWindow>(); 095 096 /** 097 * Keeps track of the bounds of the group, and adjust the size of the 098 * popup window accordingly. This way as the popup content changes, the 099 * window will be changed to match. 100 */ 101 private final InvalidationListener rootBoundsListener = 102 new InvalidationListener() { 103 @Override 104 public void invalidated(final Observable observable) { 105 syncWithRootBounds(); 106 } 107 }; 108 109 /** 110 * RT-28454: When a parent node or parent window we are associated with is not 111 * visible anymore, possibly because the scene was not valid anymore, we should hide. 112 */ 113 private final ChangeListener<Boolean> ownerNodeListener = new ChangeListener<Boolean>() { 114 @Override public void changed( 115 ObservableValue<? extends Boolean> observable, 116 Boolean oldValue, Boolean newValue) { 117 if (oldValue && !newValue) { 118 hide(); 119 } 120 } 121 }; 122 123 public PopupWindow() { 124 final Scene scene = new Scene(new Group()); 125 scene.setFill(null); 126 super.setScene(scene); 127 128 scene.getRoot().layoutBoundsProperty().addListener(rootBoundsListener); 129 scene.rootProperty().addListener( 130 new InvalidationListener() { 131 private Node oldRoot = scene.getRoot(); 132 133 @Override 134 public void invalidated(final Observable observable) { 135 final Node newRoot = scene.getRoot(); 136 if (oldRoot != newRoot) { 137 if (oldRoot != null) { 138 oldRoot.layoutBoundsProperty() 139 .removeListener(rootBoundsListener); 140 } 141 142 if (newRoot != null) { 143 newRoot.layoutBoundsProperty() 144 .addListener(rootBoundsListener); 145 } 146 147 oldRoot = newRoot; 148 syncWithRootBounds(); 149 } 150 } 151 }); 152 syncWithRootBounds(); 153 } 154 155 /** 156 * Gets the observable, modifiable list of children which are placed in this 157 * PopupWindow. 158 * 159 * @return the PopupWindow content 160 * @treatAsPrivate implementation detail 161 * @deprecated This is an internal API that is not intended for use and will be removed in the next version 162 */ 163 @Deprecated 164 protected ObservableList<Node> getContent() { 165 final Parent rootNode = getScene().getRoot(); 166 if (!(rootNode instanceof Group)) { 167 throw new IllegalStateException( 168 "The content of the Popup can't be accessed"); 169 } 170 171 return ((Group) rootNode).getChildren(); 172 } 173 174 /** 175 * The window which is the parent of this popup. All popups must have an 176 * owner window. 177 */ 178 private ReadOnlyObjectWrapper<Window> ownerWindow = 179 new ReadOnlyObjectWrapper<Window>(this, "ownerWindow"); 180 public final Window getOwnerWindow() { 181 return ownerWindow.get(); 182 } 183 public final ReadOnlyObjectProperty<Window> ownerWindowProperty() { 184 return ownerWindow.getReadOnlyProperty(); 185 } 186 187 /** 188 * The node which is the owner of this popup. All popups must have an 189 * owner window but are not required to be associated with an owner node. 190 * If an autohide Popup has an owner node, mouse press inside the owner node 191 * doesn't cause the Popup to hide. 192 */ 193 private ReadOnlyObjectWrapper<Node> ownerNode = 194 new ReadOnlyObjectWrapper<Node>(this, "ownerNode"); 195 public final Node getOwnerNode() { 196 return ownerNode.get(); 197 } 198 public final ReadOnlyObjectProperty<Node> ownerNodeProperty() { 199 return ownerNode.getReadOnlyProperty(); 200 } 201 202 /** 203 * Note to subclasses: the scene used by PopupWindow is very specifically 204 * managed by PopupWindow. This method is overridden to throw 205 * UnsupportedOperationException. You cannot specify your own scene. 206 * 207 * @param scene 208 */ 209 @Override protected final void setScene(Scene scene) { 210 throw new UnsupportedOperationException(); 211 } 212 213 /** 214 * This convenience variable indicates whether, when the popup is shown, 215 * it should automatically correct its position such that it doesn't end 216 * up positioned off the screen. 217 * @defaultValue true 218 */ 219 private BooleanProperty autoFix = 220 new BooleanPropertyBase(true) { 221 @Override 222 protected void invalidated() { 223 handleAutofixActivation(isShowing(), get()); 224 } 225 226 @Override 227 public Object getBean() { 228 return PopupWindow.this; 229 } 230 231 @Override 232 public String getName() { 233 return "autoFix"; 234 } 235 }; 236 public final void setAutoFix(boolean value) { autoFix.set(value); } 237 public final boolean isAutoFix() { return autoFix.get(); } 238 public final BooleanProperty autoFixProperty() { return autoFix; } 239 240 /** 241 * Specifies whether Popups should auto hide. If a popup loses focus and 242 * autoHide is true, then the popup will be hidden automatically. 243 * @defaultValue false 244 */ 245 private BooleanProperty autoHide = 246 new SimpleBooleanProperty(this, "autoHide"); 247 public final void setAutoHide(boolean value) { autoHide.set(value); } 248 public final boolean isAutoHide() { return autoHide.get(); } 249 public final BooleanProperty autoHideProperty() { return autoHide; } 250 251 /** 252 * Called after autoHide is run. 253 */ 254 private ObjectProperty<EventHandler<Event>> onAutoHide = 255 new SimpleObjectProperty<EventHandler<Event>>(this, "onAutoHide"); 256 public final void setOnAutoHide(EventHandler<Event> value) { onAutoHide.set(value); } 257 public final EventHandler<Event> getOnAutoHide() { return onAutoHide.get(); } 258 public final ObjectProperty<EventHandler<Event>> onAutoHideProperty() { return onAutoHide; } 259 260 /** 261 * Specifies whether the PopupWindow should be hidden when an unhandled escape key 262 * is pressed while the popup has focus. 263 * @defaultValue true 264 */ 265 private BooleanProperty hideOnEscape = 266 new SimpleBooleanProperty(this, "hideOnEscape", true); 267 public final void setHideOnEscape(boolean value) { hideOnEscape.set(value); } 268 public final boolean isHideOnEscape() { return hideOnEscape.get(); } 269 public final BooleanProperty hideOnEscapeProperty() { return hideOnEscape; } 270 271 /** 272 * Specifies whether the event, which caused the Popup to hide, should be 273 * consumed. Having the event consumed prevents it from triggering some 274 * additional UI response in the Popup's owner window. 275 * @defaultValue true 276 * @since 2.2 277 */ 278 private BooleanProperty consumeAutoHidingEvents = 279 new SimpleBooleanProperty(this, "consumeAutoHidingEvents", 280 true); 281 282 public final void setConsumeAutoHidingEvents(boolean value) { 283 consumeAutoHidingEvents.set(value); 284 } 285 286 public final boolean getConsumeAutoHidingEvents() { 287 return consumeAutoHidingEvents.get(); 288 } 289 290 public final BooleanProperty consumeAutoHidingEventsProperty() { 291 return consumeAutoHidingEvents; 292 } 293 294 /** 295 * Show the popup. 296 * @param owner The owner of the popup. This must not be null. 297 * @throws NullPointerException if owner is null 298 * @throws IllegalArgumentException if the specified owner window would 299 * create cycle in the window hierarchy 300 */ 301 public void show(Window owner) { 302 validateOwnerWindow(owner); 303 showImpl(owner); 304 } 305 306 /** 307 * Shows the popup at the specified x,y location relative to the screen. 308 * The popup is associated with the specified owner node. The {@code Window} 309 * which contains the owner node at the time of the call becomes an owner 310 * window of the displayed popup. 311 * 312 * @param ownerNode The owner Node of the popup. It must not be null 313 * and must be associated with a Window. 314 * @param screenX the x location in screen coordinates at which to 315 * show this PopupWindow. 316 * @param screenY the y location in screen coordiates at which to 317 * show this PopupWindow. 318 * @throws NullPointerException if ownerNode is null 319 * @throws IllegalArgumentException if the specified owner node is not 320 * associated with a Window or when the window would create cycle 321 * in the window hierarchy 322 */ 323 public void show(Node ownerNode, double screenX, double screenY) { 324 if (ownerNode == null) { 325 throw new NullPointerException("The owner node must not be null"); 326 } 327 328 final Scene ownerNodeScene = ownerNode.getScene(); 329 if ((ownerNodeScene == null) 330 || (ownerNodeScene.getWindow() == null)) { 331 throw new IllegalArgumentException( 332 "The owner node needs to be associated with a window"); 333 } 334 335 final Window newOwnerWindow = ownerNodeScene.getWindow(); 336 validateOwnerWindow(newOwnerWindow); 337 338 this.ownerNode.set(ownerNode); 339 340 // RT-28454 PopupWindow should disappear when owner node is not visible 341 if (ownerNode != null) { 342 ownerNode.visibleProperty().addListener(ownerNodeListener); 343 } 344 345 setX(screenX); 346 setY(screenY); 347 showImpl(newOwnerWindow); 348 } 349 350 /** 351 * Show the Popup at the specified x,y location relative to the screen 352 * @param ownerWindow The owner of the popup. This must not be null. 353 * @param screenX the x location in screen coordinates at which to 354 * show this PopupWindow. 355 * @param screenY the y location in screen coordiates at which to 356 * show this PopupWindow. 357 * @throws NullPointerException if ownerWindow is null 358 * @throws IllegalArgumentException if the specified owner window would 359 * create cycle in the window hierarchy 360 */ 361 public void show(Window ownerWindow, double screenX, double screenY) { 362 validateOwnerWindow(ownerWindow); 363 364 setX(screenX); 365 setY(screenY); 366 showImpl(ownerWindow); 367 } 368 369 private void showImpl(final Window owner) { 370 if (isShowing()) { 371 if (autofixHandler != null) { 372 autofixHandler.adjustPosition(); 373 } 374 return; 375 } 376 377 // Update the owner field 378 this.ownerWindow.set(owner); 379 if (owner instanceof PopupWindow) { 380 ((PopupWindow)owner).children.add(this); 381 } 382 // RT-28454 PopupWindow should disappear when owner node is not visible 383 if (owner != null) { 384 owner.showingProperty().addListener(ownerNodeListener); 385 } 386 387 final Scene sceneValue = getScene(); 388 if (sceneValue != null) { 389 SceneHelper.parentEffectiveOrientationInvalidated(sceneValue); 390 } 391 392 // RT-28447 393 final Scene ownerScene = getRootWindow(owner).getScene(); 394 sceneValue.getStylesheets().setAll(ownerScene.getStylesheets()); 395 396 // It is required that the root window exist and be visible to show the popup. 397 if (getRootWindow(owner).isShowing()) { 398 // We do show() first so that the width and height of the 399 // popup window are initialized. This way the x,y location of the 400 // popup calculated below uses the right width and height values for 401 // its calculation. (fix for part of RT-10675). 402 show(); 403 } 404 } 405 406 /** 407 * Hide this Popup and all its children 408 */ 409 @Override public void hide() { 410 for (PopupWindow c : children) { 411 if (c.isShowing()) { 412 c.hide(); 413 } 414 } 415 children.clear(); 416 super.hide(); 417 // RT-28454 when popup hides, remove listeners; these are added when the popup shows. 418 if (getOwnerWindow() != null) getOwnerWindow().showingProperty().removeListener(ownerNodeListener); 419 if (getOwnerNode() != null) getOwnerNode().visibleProperty().removeListener(ownerNodeListener); 420 } 421 422 /** 423 * @treatAsPrivate implementation detail 424 * @deprecated This is an internal API that is not intended for use and will be removed in the next version 425 */ 426 @Deprecated 427 @Override protected void impl_visibleChanging(boolean visible) { 428 super.impl_visibleChanging(visible); 429 PerformanceTracker.logEvent("PopupWindow.storeVisible for [PopupWindow]"); 430 431 Toolkit toolkit = Toolkit.getToolkit(); 432 if (visible && (impl_peer == null)) { 433 // Setup the peer 434 impl_peer = toolkit.createTKPopupStage(StageStyle.TRANSPARENT, getOwnerWindow().impl_getPeer()); 435 peerListener = new PopupWindowPeerListener(PopupWindow.this); 436 } 437 } 438 439 private Window rootWindow; 440 441 /** 442 * @treatAsPrivate implementation detail 443 * @deprecated This is an internal API that is not intended for use and will be removed in the next version 444 */ 445 @Deprecated 446 @Override protected void impl_visibleChanged(boolean visible) { 447 super.impl_visibleChanged(visible); 448 449 final Window ownerWindowValue = getOwnerWindow(); 450 if (visible) { 451 rootWindow = getRootWindow(ownerWindowValue); 452 453 startMonitorOwnerEvents(ownerWindowValue); 454 // currently we consider popup window to be focused when it is 455 // visible and its owner window is focused (we need to track 456 // that through listener on owner window focused property) 457 // a better solution would require some focus manager, which can 458 // track focus state across multiple windows 459 bindOwnerFocusedProperty(ownerWindowValue); 460 setFocused(ownerWindowValue.isFocused()); 461 handleAutofixActivation(true, isAutoFix()); 462 rootWindow.increaseFocusGrabCounter(); 463 } else { 464 stopMonitorOwnerEvents(ownerWindowValue); 465 unbindOwnerFocusedProperty(ownerWindowValue); 466 setFocused(false); 467 handleAutofixActivation(false, isAutoFix()); 468 rootWindow.decreaseFocusGrabCounter(); 469 rootWindow = null; 470 } 471 472 PerformanceTracker.logEvent("PopupWindow.storeVisible for [PopupWindow] finished"); 473 } 474 475 private void syncWithRootBounds() { 476 final Parent rootNode = getScene().getRoot(); 477 final Bounds layoutBounds = rootNode.getLayoutBounds(); 478 479 final double layoutX = layoutBounds.getMinX(); 480 final double layoutY = layoutBounds.getMinY(); 481 482 // update popup dimensions 483 setWidth(layoutBounds.getMaxX() - layoutX); 484 setHeight(layoutBounds.getMaxY() - layoutY); 485 // update transform 486 rootNode.setTranslateX(-layoutX); 487 rootNode.setTranslateY(-layoutY); 488 489 if (isAlignWithContentOrigin()) { 490 // update window position 491 setWindowTranslate(layoutX, layoutY); 492 // compensate with scene's delta, so the manual Node.localToScene 493 // + sceenXY + windowXY calculation still works for local to screen 494 // conversions 495 SceneHelper.setSceneDelta(getScene(), layoutX, layoutY); 496 497 if (autofixActive) { 498 autofixHandler.adjustPosition(); 499 } 500 } 501 } 502 503 /** 504 * Specifies the reference point associated with the x, y location of the 505 * window on the screen. If set to {@code false} this point corresponds to 506 * the window's upper left corner. If set to {@code true} the reference 507 * point is moved to the origin of the popup content coordinate space. This 508 * simplifies placement of popup windows which content have some additional 509 * borders extending past their origins. Setting the property to {code true} 510 * for such windows makes their position independent of their borders. 511 * 512 * @defaultValue {@code true} 513 * @since JavaFX 8 514 */ 515 private BooleanProperty alignWithContentOrigin = 516 new BooleanPropertyBase(true) { 517 private boolean oldValue = true; 518 519 @Override 520 protected void invalidated() { 521 final boolean newValue = get(); 522 if (oldValue != newValue) { 523 if (newValue) { 524 final Bounds layoutBounds = 525 getScene().getRoot().getLayoutBounds(); 526 setWindowTranslate(layoutBounds.getMinX(), 527 layoutBounds.getMinY()); 528 SceneHelper.setSceneDelta(getScene(), 529 layoutBounds.getMinX(), 530 layoutBounds.getMinY()); 531 } else { 532 setWindowTranslate(0, 0); 533 SceneHelper.setSceneDelta(getScene(), 0, 0); 534 } 535 536 if (autofixActive) { 537 autofixHandler.adjustPosition(); 538 } 539 540 oldValue = newValue; 541 } 542 } 543 544 @Override 545 public Object getBean() { 546 return PopupWindow.this; 547 } 548 549 @Override 550 public String getName() { 551 return "alignWithContentOrigin"; 552 } 553 }; 554 555 public final void setAlignWithContentOrigin(boolean value) { 556 alignWithContentOrigin.set(value); 557 } 558 559 public final boolean isAlignWithContentOrigin() { 560 return alignWithContentOrigin.get(); 561 } 562 563 public final BooleanProperty alignWithContentOriginProperty() { 564 return alignWithContentOrigin; 565 } 566 567 /** 568 * 569 * Gets the root (non PopupWindow) Window for the provided window. 570 * 571 * @param win the Window for which to get the root window 572 */ 573 private static Window getRootWindow(Window win) { 574 // should be enough to traverse PopupWindow hierarchy here to get to the 575 // first non-popup focusable window 576 while (win instanceof PopupWindow) { 577 win = ((PopupWindow) win).getOwnerWindow(); 578 } 579 return win; 580 } 581 582 void doAutoHide() { 583 // There is a timing problem here. I would like to have this isVisible 584 // check, such that we don't send an onAutoHide event if it was already 585 // invisible. However, visible is already false by the time this method 586 // gets called, when done by certain code paths. 587// if (isVisible()) { 588 // hide this popup 589 hide(); 590 if (getOnAutoHide() != null) { 591 getOnAutoHide().handle(new Event(this, this, Event.ANY)); 592 } 593// } 594 } 595 596 @Override 597 WindowEventDispatcher createInternalEventDispatcher() { 598 return new WindowEventDispatcher(new PopupEventRedirector(this), 599 new WindowCloseRequestHandler(this), 600 new EventHandlerManager(this)); 601 602 } 603 604 @Override 605 Window getWindowOwner() { 606 return getOwnerWindow(); 607 } 608 609 private void startMonitorOwnerEvents(final Window ownerWindowValue) { 610 final EventRedirector parentEventRedirector = 611 ownerWindowValue.getInternalEventDispatcher() 612 .getEventRedirector(); 613 parentEventRedirector.addEventDispatcher(getEventDispatcher()); 614 } 615 616 private void stopMonitorOwnerEvents(final Window ownerWindowValue) { 617 final EventRedirector parentEventRedirector = 618 ownerWindowValue.getInternalEventDispatcher() 619 .getEventRedirector(); 620 parentEventRedirector.removeEventDispatcher(getEventDispatcher()); 621 } 622 623 private ChangeListener<Boolean> ownerFocusedListener; 624 625 private void bindOwnerFocusedProperty(final Window ownerWindowValue) { 626 ownerFocusedListener = 627 new ChangeListener<Boolean>() { 628 @Override 629 public void changed( 630 ObservableValue<? extends Boolean> observable, 631 Boolean oldValue, Boolean newValue) { 632 setFocused(newValue); 633 } 634 }; 635 ownerWindowValue.focusedProperty().addListener(ownerFocusedListener); 636 } 637 638 private void unbindOwnerFocusedProperty(final Window ownerWindowValue) { 639 ownerWindowValue.focusedProperty().removeListener(ownerFocusedListener); 640 ownerFocusedListener = null; 641 } 642 643 private boolean autofixActive; 644 private AutofixHandler autofixHandler; 645 private void handleAutofixActivation(final boolean visible, 646 final boolean autofix) { 647 final boolean newAutofixActive = visible && autofix; 648 if (autofixActive != newAutofixActive) { 649 autofixActive = newAutofixActive; 650 if (newAutofixActive) { 651 autofixHandler = new AutofixHandler(); 652 widthProperty().addListener(autofixHandler); 653 heightProperty().addListener(autofixHandler); 654 Screen.getScreens().addListener(autofixHandler); 655 autofixHandler.adjustPosition(); 656 } else { 657 widthProperty().removeListener(autofixHandler); 658 heightProperty().removeListener(autofixHandler); 659 Screen.getScreens().removeListener(autofixHandler); 660 autofixHandler = null; 661 } 662 } 663 } 664 665 private void validateOwnerWindow(final Window owner) { 666 if (owner == null) { 667 throw new NullPointerException("Owner window must not be null"); 668 } 669 670 if (wouldCreateCycle(owner, this)) { 671 throw new IllegalArgumentException( 672 "Specified owner window would create cycle" 673 + " in the window hierarchy"); 674 } 675 676 if (isShowing() && (getOwnerWindow() != owner)) { 677 throw new IllegalStateException( 678 "Popup is already shown with different owner window"); 679 } 680 } 681 682 private static boolean wouldCreateCycle(Window parent, final Window child) { 683 while (parent != null) { 684 if (parent == child) { 685 return true; 686 } 687 688 parent = parent.getWindowOwner(); 689 } 690 691 return false; 692 } 693 694 private final class AutofixHandler implements InvalidationListener { 695 @Override 696 public void invalidated(final Observable observable) { 697 adjustPosition(); 698 } 699 700 public void adjustPosition() { 701 final Screen currentScreen = 702 Utils.getScreenForPoint(getX(), getY()); 703 final Rectangle2D screenBounds = 704 Utils.hasFullScreenStage(currentScreen) 705 ? currentScreen.getBounds() 706 : currentScreen.getVisualBounds(); 707 double wtX = getWindowTranslateX(); 708 double wtY = getWindowTranslateY(); 709 double oldWindowX = getX() + wtX; 710 double oldWindowY = getY() + wtY; 711 double _x = Math.min(oldWindowX, 712 screenBounds.getMaxX() - getWidth()); 713 double _y = Math.min(oldWindowY, 714 screenBounds.getMaxY() - getHeight()); 715 _x = Math.max(_x, screenBounds.getMinX()); 716 _y = Math.max(_y, screenBounds.getMinY()); 717 if (_x != oldWindowX) { 718 setX(_x - wtX); 719 } 720 if (_y != oldWindowY) { 721 setY(_y - wtY); 722 } 723 } 724 } 725 726 static class PopupEventRedirector extends EventRedirector { 727 728 private static final KeyCombination ESCAPE_KEY_COMBINATION = 729 KeyCombination.keyCombination("Esc"); 730 private final PopupWindow popupWindow; 731 732 public PopupEventRedirector(final PopupWindow popupWindow) { 733 super(popupWindow); 734 this.popupWindow = popupWindow; 735 } 736 737 @Override 738 protected void handleRedirectedEvent(final Object eventSource, 739 final Event event) { 740 if (event instanceof KeyEvent) { 741 handleKeyEvent((KeyEvent) event); 742 return; 743 } 744 745 final EventType<?> eventType = event.getEventType(); 746 747 if (eventType == MouseEvent.MOUSE_PRESSED) { 748 handleMousePressedEvent(eventSource, event); 749 return; 750 } 751 752 if (eventType == FocusUngrabEvent.FOCUS_UNGRAB) { 753 handleFocusUngrabEvent(); 754 return; 755 } 756 } 757 758 private void handleKeyEvent(final KeyEvent event) { 759 if (event.isConsumed()) { 760 return; 761 } 762 763 final Scene scene = popupWindow.getScene(); 764 if (scene != null) { 765 final Node sceneFocusOwner = scene.getFocusOwner(); 766 final EventTarget eventTarget = 767 (sceneFocusOwner != null) ? sceneFocusOwner : scene; 768 if (EventUtil.fireEvent(eventTarget, new DirectEvent(event)) 769 == null) { 770 event.consume(); 771 return; 772 } 773 } 774 775 if ((event.getEventType() == KeyEvent.KEY_PRESSED) 776 && ESCAPE_KEY_COMBINATION.match(event)) { 777 handleEscapeKeyPressedEvent(event); 778 } 779 } 780 781 private void handleEscapeKeyPressedEvent(final Event event) { 782 if (popupWindow.isHideOnEscape()) { 783 popupWindow.doAutoHide(); 784 785 if (popupWindow.getConsumeAutoHidingEvents()) { 786 event.consume(); 787 } 788 } 789 } 790 791 private void handleMousePressedEvent(final Object eventSource, 792 final Event event) { 793 // we handle mouse pressed only for the immediate parent window, 794 // where we can check whether the mouse press is inside of the owner 795 // control or not, we will force possible child popups to close 796 // by sending the FOCUS_UNGRAB event 797 if (popupWindow.getOwnerWindow() != eventSource) { 798 return; 799 } 800 801 if (popupWindow.isAutoHide() && !isOwnerNodeEvent(event)) { 802 // the mouse press is outside of the owner control, 803 // fire FOCUS_UNGRAB to child popups 804 Event.fireEvent(popupWindow, new FocusUngrabEvent()); 805 806 popupWindow.doAutoHide(); 807 808 if (popupWindow.getConsumeAutoHidingEvents()) { 809 event.consume(); 810 } 811 } 812 } 813 814 private void handleFocusUngrabEvent() { 815 if (popupWindow.isAutoHide()) { 816 popupWindow.doAutoHide(); 817 } 818 } 819 820 private boolean isOwnerNodeEvent(final Event event) { 821 final Node ownerNode = popupWindow.getOwnerNode(); 822 if (ownerNode == null) { 823 return false; 824 } 825 826 final EventTarget eventTarget = event.getTarget(); 827 if (!(eventTarget instanceof Node)) { 828 return false; 829 } 830 831 Node node = (Node) eventTarget; 832 do { 833 if (node == ownerNode) { 834 return true; 835 } 836 node = node.getParent(); 837 } while (node != null); 838 839 return false; 840 } 841 } 842}