All Downloads are FREE. Search and download functionalities are using the official Maven repository.

javafx.stage.PopupWindow Maven / Gradle / Ivy

/*
 * Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

package javafx.stage;

import com.sun.javafx.Utils;
import com.sun.javafx.event.DirectEvent;
import java.util.ArrayList;
import java.util.List;

import javafx.beans.InvalidationListener;
import javafx.beans.Observable;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.BooleanPropertyBase;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.collections.ObservableList;
import javafx.event.Event;
import javafx.event.EventHandler;
import javafx.geometry.Bounds;
import javafx.geometry.Rectangle2D;
import javafx.scene.Group;
import javafx.scene.Node;
import javafx.scene.Parent;
import javafx.scene.Scene;

import com.sun.javafx.event.EventHandlerManager;
import com.sun.javafx.event.EventRedirector;
import com.sun.javafx.event.EventUtil;
import com.sun.javafx.perf.PerformanceTracker;
import com.sun.javafx.scene.SceneHelper;
import com.sun.javafx.stage.FocusUngrabEvent;
import com.sun.javafx.stage.PopupWindowPeerListener;
import com.sun.javafx.stage.WindowCloseRequestHandler;
import com.sun.javafx.stage.WindowEventDispatcher;
import com.sun.javafx.tk.Toolkit;
import javafx.beans.property.ReadOnlyObjectProperty;
import javafx.beans.property.ReadOnlyObjectWrapper;
import javafx.event.EventTarget;
import javafx.event.EventType;
import javafx.scene.input.KeyCombination;
import javafx.scene.input.KeyEvent;
import javafx.scene.input.MouseEvent;

/**
 * PopupWindow is the parent for a variety of different types of popup
 * based windows including {@link Popup} and {@link javafx.scene.control.Tooltip}
 * and {@link javafx.scene.control.ContextMenu}.
 * 

* A PopupWindow is a secondary window which has no window decorations or title bar. * It doesn't show up in the OS as a top-level window. It is typically * used for tool tip like notification, drop down boxes, menus, and so forth. *

* The PopupWindow cannot be shown without an owner. * PopupWindows require that an owner window exist in order to be shown. However, * it is possible to create a PopupWindow ahead of time and simply set the owner * (or change the owner) before first being made visible. Attempting to change * the owner while the PopupWindow is visible will result in an IllegalStateException. *

* The PopupWindow encapsulates much of the behavior and functionality common to popups, * such as the ability to close when the "esc" key is pressed, or the ability to * hide all child popup windows whenever this window is hidden. These abilities can * be enabled or disabled via properties. * @since JavaFX 2.0 */ public abstract class PopupWindow extends Window { /** * A private list of all child popups. */ private final List children = new ArrayList(); /** * Keeps track of the bounds of the group, and adjust the size of the * popup window accordingly. This way as the popup content changes, the * window will be changed to match. */ private final InvalidationListener rootBoundsListener = new InvalidationListener() { @Override public void invalidated(final Observable observable) { syncWithRootBounds(); } }; /** * RT-28454: When a parent node or parent window we are associated with is not * visible anymore, possibly because the scene was not valid anymore, we should hide. */ private final ChangeListener ownerNodeListener = new ChangeListener() { @Override public void changed( ObservableValue observable, Boolean oldValue, Boolean newValue) { if (oldValue && !newValue) { hide(); } } }; public PopupWindow() { final Scene scene = new Scene(new Group()); scene.setFill(null); super.setScene(scene); scene.getRoot().layoutBoundsProperty().addListener(rootBoundsListener); scene.rootProperty().addListener( new InvalidationListener() { private Node oldRoot = scene.getRoot(); @Override public void invalidated(final Observable observable) { final Node newRoot = scene.getRoot(); if (oldRoot != newRoot) { if (oldRoot != null) { oldRoot.layoutBoundsProperty() .removeListener(rootBoundsListener); } if (newRoot != null) { newRoot.layoutBoundsProperty() .addListener(rootBoundsListener); } oldRoot = newRoot; syncWithRootBounds(); } } }); syncWithRootBounds(); } /** * Gets the observable, modifiable list of children which are placed in this * PopupWindow. * * @return the PopupWindow content * @treatAsPrivate implementation detail * @deprecated This is an internal API that is not intended for use and will be removed in the next version */ @Deprecated protected ObservableList getContent() { final Parent rootNode = getScene().getRoot(); if (!(rootNode instanceof Group)) { throw new IllegalStateException( "The content of the Popup can't be accessed"); } return ((Group) rootNode).getChildren(); } /** * The window which is the parent of this popup. All popups must have an * owner window. */ private ReadOnlyObjectWrapper ownerWindow = new ReadOnlyObjectWrapper(this, "ownerWindow"); public final Window getOwnerWindow() { return ownerWindow.get(); } public final ReadOnlyObjectProperty ownerWindowProperty() { return ownerWindow.getReadOnlyProperty(); } /** * The node which is the owner of this popup. All popups must have an * owner window but are not required to be associated with an owner node. * If an autohide Popup has an owner node, mouse press inside the owner node * doesn't cause the Popup to hide. */ private ReadOnlyObjectWrapper ownerNode = new ReadOnlyObjectWrapper(this, "ownerNode"); public final Node getOwnerNode() { return ownerNode.get(); } public final ReadOnlyObjectProperty ownerNodeProperty() { return ownerNode.getReadOnlyProperty(); } /** * Note to subclasses: the scene used by PopupWindow is very specifically * managed by PopupWindow. This method is overridden to throw * UnsupportedOperationException. You cannot specify your own scene. * * @param scene */ @Override protected final void setScene(Scene scene) { throw new UnsupportedOperationException(); } /** * This convenience variable indicates whether, when the popup is shown, * it should automatically correct its position such that it doesn't end * up positioned off the screen. * @defaultValue true */ private BooleanProperty autoFix = new BooleanPropertyBase(true) { @Override protected void invalidated() { handleAutofixActivation(isShowing(), get()); } @Override public Object getBean() { return PopupWindow.this; } @Override public String getName() { return "autoFix"; } }; public final void setAutoFix(boolean value) { autoFix.set(value); } public final boolean isAutoFix() { return autoFix.get(); } public final BooleanProperty autoFixProperty() { return autoFix; } /** * Specifies whether Popups should auto hide. If a popup loses focus and * autoHide is true, then the popup will be hidden automatically. * @defaultValue false */ private BooleanProperty autoHide = new SimpleBooleanProperty(this, "autoHide"); public final void setAutoHide(boolean value) { autoHide.set(value); } public final boolean isAutoHide() { return autoHide.get(); } public final BooleanProperty autoHideProperty() { return autoHide; } /** * Called after autoHide is run. */ private ObjectProperty> onAutoHide = new SimpleObjectProperty>(this, "onAutoHide"); public final void setOnAutoHide(EventHandler value) { onAutoHide.set(value); } public final EventHandler getOnAutoHide() { return onAutoHide.get(); } public final ObjectProperty> onAutoHideProperty() { return onAutoHide; } /** * Specifies whether the PopupWindow should be hidden when an unhandled escape key * is pressed while the popup has focus. * @defaultValue true */ private BooleanProperty hideOnEscape = new SimpleBooleanProperty(this, "hideOnEscape", true); public final void setHideOnEscape(boolean value) { hideOnEscape.set(value); } public final boolean isHideOnEscape() { return hideOnEscape.get(); } public final BooleanProperty hideOnEscapeProperty() { return hideOnEscape; } /** * Specifies whether the event, which caused the Popup to hide, should be * consumed. Having the event consumed prevents it from triggering some * additional UI response in the Popup's owner window. * @defaultValue true * @since JavaFX 2.2 */ private BooleanProperty consumeAutoHidingEvents = new SimpleBooleanProperty(this, "consumeAutoHidingEvents", true); public final void setConsumeAutoHidingEvents(boolean value) { consumeAutoHidingEvents.set(value); } public final boolean getConsumeAutoHidingEvents() { return consumeAutoHidingEvents.get(); } public final BooleanProperty consumeAutoHidingEventsProperty() { return consumeAutoHidingEvents; } /** * Show the popup. * @param owner The owner of the popup. This must not be null. * @throws NullPointerException if owner is null * @throws IllegalArgumentException if the specified owner window would * create cycle in the window hierarchy */ public void show(Window owner) { validateOwnerWindow(owner); showImpl(owner); } /** * Shows the popup at the specified x,y location relative to the screen. * The popup is associated with the specified owner node. The {@code Window} * which contains the owner node at the time of the call becomes an owner * window of the displayed popup. * * @param ownerNode The owner Node of the popup. It must not be null * and must be associated with a Window. * @param screenX the x location in screen coordinates at which to * show this PopupWindow. * @param screenY the y location in screen coordiates at which to * show this PopupWindow. * @throws NullPointerException if ownerNode is null * @throws IllegalArgumentException if the specified owner node is not * associated with a Window or when the window would create cycle * in the window hierarchy */ public void show(Node ownerNode, double screenX, double screenY) { if (ownerNode == null) { throw new NullPointerException("The owner node must not be null"); } final Scene ownerNodeScene = ownerNode.getScene(); if ((ownerNodeScene == null) || (ownerNodeScene.getWindow() == null)) { throw new IllegalArgumentException( "The owner node needs to be associated with a window"); } final Window newOwnerWindow = ownerNodeScene.getWindow(); validateOwnerWindow(newOwnerWindow); this.ownerNode.set(ownerNode); // RT-28454 PopupWindow should disappear when owner node is not visible if (ownerNode != null) { ownerNode.visibleProperty().addListener(ownerNodeListener); } setX(screenX); setY(screenY); showImpl(newOwnerWindow); } /** * Show the Popup at the specified x,y location relative to the screen * @param ownerWindow The owner of the popup. This must not be null. * @param screenX the x location in screen coordinates at which to * show this PopupWindow. * @param screenY the y location in screen coordiates at which to * show this PopupWindow. * @throws NullPointerException if ownerWindow is null * @throws IllegalArgumentException if the specified owner window would * create cycle in the window hierarchy */ public void show(Window ownerWindow, double screenX, double screenY) { validateOwnerWindow(ownerWindow); setX(screenX); setY(screenY); showImpl(ownerWindow); } private void showImpl(final Window owner) { if (isShowing()) { if (autofixHandler != null) { autofixHandler.adjustPosition(); } return; } // Update the owner field this.ownerWindow.set(owner); if (owner instanceof PopupWindow) { ((PopupWindow)owner).children.add(this); } // RT-28454 PopupWindow should disappear when owner node is not visible if (owner != null) { owner.showingProperty().addListener(ownerNodeListener); } final Scene sceneValue = getScene(); if (sceneValue != null) { SceneHelper.parentEffectiveOrientationInvalidated(sceneValue); } // RT-28447 final Scene ownerScene = getRootWindow(owner).getScene(); sceneValue.getStylesheets().setAll(ownerScene.getStylesheets()); // It is required that the root window exist and be visible to show the popup. if (getRootWindow(owner).isShowing()) { // We do show() first so that the width and height of the // popup window are initialized. This way the x,y location of the // popup calculated below uses the right width and height values for // its calculation. (fix for part of RT-10675). show(); } } /** * Hide this Popup and all its children */ @Override public void hide() { for (PopupWindow c : children) { if (c.isShowing()) { c.hide(); } } children.clear(); super.hide(); // RT-28454 when popup hides, remove listeners; these are added when the popup shows. if (getOwnerWindow() != null) getOwnerWindow().showingProperty().removeListener(ownerNodeListener); if (getOwnerNode() != null) getOwnerNode().visibleProperty().removeListener(ownerNodeListener); } /** * @treatAsPrivate implementation detail * @deprecated This is an internal API that is not intended for use and will be removed in the next version */ @Deprecated @Override protected void impl_visibleChanging(boolean visible) { super.impl_visibleChanging(visible); PerformanceTracker.logEvent("PopupWindow.storeVisible for [PopupWindow]"); Toolkit toolkit = Toolkit.getToolkit(); if (visible && (impl_peer == null)) { // Setup the peer impl_peer = toolkit.createTKPopupStage(StageStyle.TRANSPARENT, getOwnerWindow().impl_getPeer()); peerListener = new PopupWindowPeerListener(PopupWindow.this); } } private Window rootWindow; /** * @treatAsPrivate implementation detail * @deprecated This is an internal API that is not intended for use and will be removed in the next version */ @Deprecated @Override protected void impl_visibleChanged(boolean visible) { super.impl_visibleChanged(visible); final Window ownerWindowValue = getOwnerWindow(); if (visible) { rootWindow = getRootWindow(ownerWindowValue); startMonitorOwnerEvents(ownerWindowValue); // currently we consider popup window to be focused when it is // visible and its owner window is focused (we need to track // that through listener on owner window focused property) // a better solution would require some focus manager, which can // track focus state across multiple windows bindOwnerFocusedProperty(ownerWindowValue); setFocused(ownerWindowValue.isFocused()); handleAutofixActivation(true, isAutoFix()); rootWindow.increaseFocusGrabCounter(); } else { stopMonitorOwnerEvents(ownerWindowValue); unbindOwnerFocusedProperty(ownerWindowValue); setFocused(false); handleAutofixActivation(false, isAutoFix()); rootWindow.decreaseFocusGrabCounter(); rootWindow = null; } PerformanceTracker.logEvent("PopupWindow.storeVisible for [PopupWindow] finished"); } private void syncWithRootBounds() { final Parent rootNode = getScene().getRoot(); final Bounds layoutBounds = rootNode.getLayoutBounds(); final double layoutX = layoutBounds.getMinX(); final double layoutY = layoutBounds.getMinY(); // update popup dimensions setWidth(layoutBounds.getMaxX() - layoutX); setHeight(layoutBounds.getMaxY() - layoutY); // update transform rootNode.setTranslateX(-layoutX); rootNode.setTranslateY(-layoutY); if (isAlignWithContentOrigin()) { // update window position setWindowTranslate(layoutX, layoutY); // compensate with scene's delta, so the manual Node.localToScene // + sceenXY + windowXY calculation still works for local to screen // conversions SceneHelper.setSceneDelta(getScene(), layoutX, layoutY); if (autofixActive) { autofixHandler.adjustPosition(); } } } /** * Specifies the reference point associated with the x, y location of the * window on the screen. If set to {@code false} this point corresponds to * the window's upper left corner. If set to {@code true} the reference * point is moved to the origin of the popup content coordinate space. This * simplifies placement of popup windows which content have some additional * borders extending past their origins. Setting the property to {code true} * for such windows makes their position independent of their borders. * * @defaultValue {@code true} * @since JavaFX 8.0 */ private BooleanProperty alignWithContentOrigin = new BooleanPropertyBase(true) { private boolean oldValue = true; @Override protected void invalidated() { final boolean newValue = get(); if (oldValue != newValue) { if (newValue) { final Bounds layoutBounds = getScene().getRoot().getLayoutBounds(); setWindowTranslate(layoutBounds.getMinX(), layoutBounds.getMinY()); SceneHelper.setSceneDelta(getScene(), layoutBounds.getMinX(), layoutBounds.getMinY()); } else { setWindowTranslate(0, 0); SceneHelper.setSceneDelta(getScene(), 0, 0); } if (autofixActive) { autofixHandler.adjustPosition(); } oldValue = newValue; } } @Override public Object getBean() { return PopupWindow.this; } @Override public String getName() { return "alignWithContentOrigin"; } }; public final void setAlignWithContentOrigin(boolean value) { alignWithContentOrigin.set(value); } public final boolean isAlignWithContentOrigin() { return alignWithContentOrigin.get(); } public final BooleanProperty alignWithContentOriginProperty() { return alignWithContentOrigin; } /** * * Gets the root (non PopupWindow) Window for the provided window. * * @param win the Window for which to get the root window */ private static Window getRootWindow(Window win) { // should be enough to traverse PopupWindow hierarchy here to get to the // first non-popup focusable window while (win instanceof PopupWindow) { win = ((PopupWindow) win).getOwnerWindow(); } return win; } void doAutoHide() { // There is a timing problem here. I would like to have this isVisible // check, such that we don't send an onAutoHide event if it was already // invisible. However, visible is already false by the time this method // gets called, when done by certain code paths. // if (isVisible()) { // hide this popup hide(); if (getOnAutoHide() != null) { getOnAutoHide().handle(new Event(this, this, Event.ANY)); } // } } @Override WindowEventDispatcher createInternalEventDispatcher() { return new WindowEventDispatcher(new PopupEventRedirector(this), new WindowCloseRequestHandler(this), new EventHandlerManager(this)); } @Override Window getWindowOwner() { return getOwnerWindow(); } private void startMonitorOwnerEvents(final Window ownerWindowValue) { final EventRedirector parentEventRedirector = ownerWindowValue.getInternalEventDispatcher() .getEventRedirector(); parentEventRedirector.addEventDispatcher(getEventDispatcher()); } private void stopMonitorOwnerEvents(final Window ownerWindowValue) { final EventRedirector parentEventRedirector = ownerWindowValue.getInternalEventDispatcher() .getEventRedirector(); parentEventRedirector.removeEventDispatcher(getEventDispatcher()); } private ChangeListener ownerFocusedListener; private void bindOwnerFocusedProperty(final Window ownerWindowValue) { ownerFocusedListener = new ChangeListener() { @Override public void changed( ObservableValue observable, Boolean oldValue, Boolean newValue) { setFocused(newValue); } }; ownerWindowValue.focusedProperty().addListener(ownerFocusedListener); } private void unbindOwnerFocusedProperty(final Window ownerWindowValue) { ownerWindowValue.focusedProperty().removeListener(ownerFocusedListener); ownerFocusedListener = null; } private boolean autofixActive; private AutofixHandler autofixHandler; private void handleAutofixActivation(final boolean visible, final boolean autofix) { final boolean newAutofixActive = visible && autofix; if (autofixActive != newAutofixActive) { autofixActive = newAutofixActive; if (newAutofixActive) { autofixHandler = new AutofixHandler(); widthProperty().addListener(autofixHandler); heightProperty().addListener(autofixHandler); Screen.getScreens().addListener(autofixHandler); autofixHandler.adjustPosition(); } else { widthProperty().removeListener(autofixHandler); heightProperty().removeListener(autofixHandler); Screen.getScreens().removeListener(autofixHandler); autofixHandler = null; } } } private void validateOwnerWindow(final Window owner) { if (owner == null) { throw new NullPointerException("Owner window must not be null"); } if (wouldCreateCycle(owner, this)) { throw new IllegalArgumentException( "Specified owner window would create cycle" + " in the window hierarchy"); } if (isShowing() && (getOwnerWindow() != owner)) { throw new IllegalStateException( "Popup is already shown with different owner window"); } } private static boolean wouldCreateCycle(Window parent, final Window child) { while (parent != null) { if (parent == child) { return true; } parent = parent.getWindowOwner(); } return false; } private final class AutofixHandler implements InvalidationListener { @Override public void invalidated(final Observable observable) { adjustPosition(); } public void adjustPosition() { final Screen currentScreen = Utils.getScreenForPoint(getX(), getY()); final Rectangle2D screenBounds = Utils.hasFullScreenStage(currentScreen) ? currentScreen.getBounds() : currentScreen.getVisualBounds(); double wtX = getWindowTranslateX(); double wtY = getWindowTranslateY(); double oldWindowX = getX() + wtX; double oldWindowY = getY() + wtY; double _x = Math.min(oldWindowX, screenBounds.getMaxX() - getWidth()); double _y = Math.min(oldWindowY, screenBounds.getMaxY() - getHeight()); _x = Math.max(_x, screenBounds.getMinX()); _y = Math.max(_y, screenBounds.getMinY()); if (_x != oldWindowX) { setX(_x - wtX); } if (_y != oldWindowY) { setY(_y - wtY); } } } static class PopupEventRedirector extends EventRedirector { private static final KeyCombination ESCAPE_KEY_COMBINATION = KeyCombination.keyCombination("Esc"); private final PopupWindow popupWindow; public PopupEventRedirector(final PopupWindow popupWindow) { super(popupWindow); this.popupWindow = popupWindow; } @Override protected void handleRedirectedEvent(final Object eventSource, final Event event) { if (event instanceof KeyEvent) { handleKeyEvent((KeyEvent) event); return; } final EventType eventType = event.getEventType(); if (eventType == MouseEvent.MOUSE_PRESSED) { handleMousePressedEvent(eventSource, event); return; } if (eventType == FocusUngrabEvent.FOCUS_UNGRAB) { handleFocusUngrabEvent(); return; } } private void handleKeyEvent(final KeyEvent event) { if (event.isConsumed()) { return; } final Scene scene = popupWindow.getScene(); if (scene != null) { final Node sceneFocusOwner = scene.getFocusOwner(); final EventTarget eventTarget = (sceneFocusOwner != null) ? sceneFocusOwner : scene; if (EventUtil.fireEvent(eventTarget, new DirectEvent(event)) == null) { event.consume(); return; } } if ((event.getEventType() == KeyEvent.KEY_PRESSED) && ESCAPE_KEY_COMBINATION.match(event)) { handleEscapeKeyPressedEvent(event); } } private void handleEscapeKeyPressedEvent(final Event event) { if (popupWindow.isHideOnEscape()) { popupWindow.doAutoHide(); if (popupWindow.getConsumeAutoHidingEvents()) { event.consume(); } } } private void handleMousePressedEvent(final Object eventSource, final Event event) { // we handle mouse pressed only for the immediate parent window, // where we can check whether the mouse press is inside of the owner // control or not, we will force possible child popups to close // by sending the FOCUS_UNGRAB event if (popupWindow.getOwnerWindow() != eventSource) { return; } if (popupWindow.isAutoHide() && !isOwnerNodeEvent(event)) { // the mouse press is outside of the owner control, // fire FOCUS_UNGRAB to child popups Event.fireEvent(popupWindow, new FocusUngrabEvent()); popupWindow.doAutoHide(); if (popupWindow.getConsumeAutoHidingEvents()) { event.consume(); } } } private void handleFocusUngrabEvent() { if (popupWindow.isAutoHide()) { popupWindow.doAutoHide(); } } private boolean isOwnerNodeEvent(final Event event) { final Node ownerNode = popupWindow.getOwnerNode(); if (ownerNode == null) { return false; } final EventTarget eventTarget = event.getTarget(); if (!(eventTarget instanceof Node)) { return false; } Node node = (Node) eventTarget; do { if (node == ownerNode) { return true; } node = node.getParent(); } while (node != null); return false; } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy