javafx.stage.Window 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 java.security.AllPermission;
import java.security.AccessControlContext;
import java.security.AccessController;
import java.util.Iterator;
import javafx.beans.property.DoubleProperty;
import javafx.beans.property.DoublePropertyBase;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.ObjectPropertyBase;
import javafx.beans.property.ReadOnlyBooleanProperty;
import javafx.beans.property.ReadOnlyBooleanWrapper;
import javafx.beans.property.ReadOnlyObjectProperty;
import javafx.beans.property.ReadOnlyObjectWrapper;
import javafx.beans.property.ReadOnlyDoubleProperty;
import javafx.beans.property.ReadOnlyDoubleWrapper;
import javafx.beans.property.SimpleObjectProperty;
import javafx.event.Event;
import javafx.event.EventDispatchChain;
import javafx.event.EventDispatcher;
import javafx.event.EventHandler;
import javafx.event.EventTarget;
import javafx.event.EventType;
import javafx.geometry.Rectangle2D;
import javafx.scene.Scene;
import com.sun.javafx.Utils;
import com.sun.javafx.WeakReferenceQueue;
import com.sun.javafx.css.StyleManager;
import com.sun.javafx.stage.WindowEventDispatcher;
import com.sun.javafx.stage.WindowHelper;
import com.sun.javafx.stage.WindowPeerListener;
import com.sun.javafx.tk.TKPulseListener;
import com.sun.javafx.tk.TKScene;
import com.sun.javafx.tk.TKStage;
import com.sun.javafx.tk.Toolkit;
/**
*
* A top level window within which a scene is hosted, and with which the user
* interacts. A Window might be a {@link Stage}, {@link PopupWindow}, or other
* such top level. A Window is used also for browser plug-in based deployments.
*
*
* @since JavaFX 2.0
*/
public class Window implements EventTarget {
/**
* A list of all the currently existing windows. This is only used by SQE for testing.
*/
private static WeakReferenceQueuewindowQueue = new WeakReferenceQueue();
static {
WindowHelper.setWindowAccessor(
new WindowHelper.WindowAccessor() {
/**
* Allow window peer listeners to directly change window
* location and size without changing the xExplicit,
* yExplicit, widthExplicit and heightExplicit values.
*/
@Override
public void setLocation(Window window, double x, double y) {
window.x.set(x - window.winTranslateX);
window.y.set(y - window.winTranslateY);
}
@Override
public void setSize(Window window,
double width,
double height) {
window.width.set(width);
window.height.set(height);
}
});
}
/**
* Return all Windows
*
* @return Iterator of all Windows
* @treatAsPrivate implementation detail
* @deprecated This is an internal API that is not intended for use and will be removed in the next version
*/
@Deprecated
public static Iterator impl_getWindows() {
final SecurityManager securityManager = System.getSecurityManager();
if (securityManager != null) {
securityManager.checkPermission(new AllPermission());
}
return (Iterator) windowQueue.iterator();
}
private final AccessControlContext acc = AccessController.getContext();
protected Window() {
// necessary for WindowCloseRequestHandler
initializeInternalEventDispatcher();
}
/**
* The listener that gets called by peer. It's also responsible for
* window size/location synchronization with the window peer, which
* occurs on every pulse.
*
* @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 WindowPeerListener peerListener;
/**
* The peer of this Stage. All external access should be
* made though getPeer(). Implementors note: Please ensure that this
* variable is defined *after* style and *before* the other variables so
* that style has been initialized prior to this call, and so that
* impl_peer is initialized prior to subsequent initialization.
*
* @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 TKStage impl_peer;
private TKBoundsConfigurator peerBoundsConfigurator =
new TKBoundsConfigurator();
/**
* Get Stage's peer
*
* @treatAsPrivate implementation detail
* @deprecated This is an internal API that is not intended for use and will be removed in the next version
*/
@Deprecated
public TKStage impl_getPeer() {
return impl_peer;
}
/**
* @treatAsPrivate implementation detail
* @deprecated This is an internal API that is not intended for use and will be removed in the next version
*/
@Deprecated
public String impl_getMXWindowType() {
return getClass().getSimpleName();
}
/**
* Set the width and height of this Window to match the size of the content
* of this Window's Scene.
*/
public void sizeToScene() {
if (getScene() != null && impl_peer != null) {
getScene().impl_preferredSize();
adjustSize(false);
}
}
private void adjustSize(boolean selfSizePriority) {
if (getScene() == null) {
return;
}
if (impl_peer != null) {
double sceneWidth = getScene().getWidth();
double cw = (sceneWidth > 0) ? sceneWidth : -1;
double w = -1;
if (selfSizePriority && widthExplicit) {
w = getWidth();
} else if (cw <= 0) {
w = widthExplicit ? getWidth() : -1;
} else {
widthExplicit = false;
}
double sceneHeight = getScene().getHeight();
double ch = (sceneHeight > 0) ? sceneHeight : -1;
double h = -1;
if (selfSizePriority && heightExplicit) {
h = getHeight();
} else if (ch <= 0) {
h = heightExplicit ? getHeight() : -1;
} else {
heightExplicit = false;
}
peerBoundsConfigurator.setSize(w, h, cw, ch);
applyBounds();
}
}
private static final float CENTER_ON_SCREEN_X_FRACTION = 1.0f / 2;
private static final float CENTER_ON_SCREEN_Y_FRACTION = 1.0f / 3;
/**
* Sets x and y properties on this Window so that it is centered on the screen.
*/
public void centerOnScreen() {
xExplicit = false;
yExplicit = false;
if (impl_peer != null) {
Rectangle2D bounds = getWindowScreen().getVisualBounds();
double centerX =
bounds.getMinX() + (bounds.getWidth() - getWidth())
* CENTER_ON_SCREEN_X_FRACTION;
double centerY =
bounds.getMinY() + (bounds.getHeight() - getHeight())
* CENTER_ON_SCREEN_Y_FRACTION;
x.set(centerX - winTranslateX);
y.set(centerY - winTranslateY);
peerBoundsConfigurator.setLocation(centerX, centerY,
CENTER_ON_SCREEN_X_FRACTION,
CENTER_ON_SCREEN_Y_FRACTION);
applyBounds();
}
}
private double winTranslateX;
private double winTranslateY;
final void setWindowTranslate(final double translateX,
final double translateY) {
if (translateX != winTranslateX) {
winTranslateX = translateX;
peerBoundsConfigurator.setX(getX() + translateX, 0);
}
if (translateY != winTranslateY) {
winTranslateY = translateY;
peerBoundsConfigurator.setY(getY() + translateY, 0);
}
}
final double getWindowTranslateX() {
return winTranslateX;
}
final double getWindowTranslateY() {
return winTranslateY;
}
private boolean xExplicit = false;
/**
* The horizontal location of this {@code Stage} on the screen. Changing
* this attribute will move the {@code Stage} horizontally. Changing this
* attribute will not visually affect a {@code Stage} while
* {@code fullScreen} is true, but will be honored by the {@code Stage} once
* {@code fullScreen} becomes false.
*/
private ReadOnlyDoubleWrapper x =
new ReadOnlyDoubleWrapper(this, "x", Double.NaN);
public final void setX(double value) {
x.set(value);
peerBoundsConfigurator.setX(value + winTranslateX, 0);
xExplicit = true;
}
public final double getX() { return x.get(); }
public final ReadOnlyDoubleProperty xProperty() { return x.getReadOnlyProperty(); }
private boolean yExplicit = false;
/**
* The vertical location of this {@code Stage} on the screen. Changing this
* attribute will move the {@code Stage} vertically. Changing this
* attribute will not visually affect a {@code Stage} while
* {@code fullScreen} is true, but will be honored by the {@code Stage} once
* {@code fullScreen} becomes false.
*/
private ReadOnlyDoubleWrapper y =
new ReadOnlyDoubleWrapper(this, "y", Double.NaN);
public final void setY(double value) {
y.set(value);
peerBoundsConfigurator.setY(value + winTranslateY, 0);
yExplicit = true;
}
public final double getY() { return y.get(); }
public final ReadOnlyDoubleProperty yProperty() { return y.getReadOnlyProperty(); }
private boolean widthExplicit = false;
/**
* The width of this {@code Stage}. Changing this attribute will narrow or
* widen the width of the {@code Stage}. Changing this
* attribute will not visually affect a {@code Stage} while
* {@code fullScreen} is true, but will be honored by the {@code Stage} once
* {@code fullScreen} becomes false. This value includes any and all
* decorations which may be added by the Operating System such as resizable
* frame handles. Typical applications will set the {@link javafx.scene.Scene} width
* instead.
*
* The property is read only because it can be changed externally
* by the underlying platform and therefore must not be bindable.
*
*/
private ReadOnlyDoubleWrapper width =
new ReadOnlyDoubleWrapper(this, "width", Double.NaN);
public final void setWidth(double value) {
width.set(value);
peerBoundsConfigurator.setWindowWidth(value);
widthExplicit = true;
}
public final double getWidth() { return width.get(); }
public final ReadOnlyDoubleProperty widthProperty() { return width.getReadOnlyProperty(); }
private boolean heightExplicit = false;
/**
* The height of this {@code Stage}. Changing this attribute will shrink
* or heighten the height of the {@code Stage}. Changing this
* attribute will not visually affect a {@code Stage} while
* {@code fullScreen} is true, but will be honored by the {@code Stage} once
* {@code fullScreen} becomes false. This value includes any and all
* decorations which may be added by the Operating System such as the title
* bar. Typical applications will set the {@link javafx.scene.Scene} height instead.
*
* The property is read only because it can be changed externally
* by the underlying platform and therefore must not be bindable.
*
*/
private ReadOnlyDoubleWrapper height =
new ReadOnlyDoubleWrapper(this, "height", Double.NaN);
public final void setHeight(double value) {
height.set(value);
peerBoundsConfigurator.setWindowHeight(value);
heightExplicit = true;
}
public final double getHeight() { return height.get(); }
public final ReadOnlyDoubleProperty heightProperty() { return height.getReadOnlyProperty(); }
/**
* Whether or not this {@code Window} has the keyboard or input focus.
*
* The property is read only because it can be changed externally
* by the underlying platform and therefore must not be bindable.
*
*
* @profile common
*/
private ReadOnlyBooleanWrapper focused = new ReadOnlyBooleanWrapper() {
@Override protected void invalidated() {
focusChanged(get());
}
@Override
public Object getBean() {
return Window.this;
}
@Override
public String getName() {
return "focused";
}
};
/**
* @treatAsPrivate
* @deprecated
*/
@Deprecated
public final void setFocused(boolean value) { focused.set(value); }
/**
* Requests that this {@code Window} get the input focus.
*/
public final void requestFocus() {
if (impl_peer != null) {
impl_peer.requestFocus();
}
}
public final boolean isFocused() { return focused.get(); }
public final ReadOnlyBooleanProperty focusedProperty() { return focused.getReadOnlyProperty(); }
/**
* The {@code Scene} to be rendered on this {@code Stage}. There can only
* be one {@code Scene} on the {@code Stage} at a time, and a {@code Scene}
* can only be on one {@code Stage} at a time. Setting a {@code Scene} on
* a different {@code Stage} will cause the old {@code Stage} to lose the
* reference before the new one gains it. You may swap {@code Scene}s on
* a {@code Stage} at any time, even while in full-screen exclusive mode.
*
* An {@link IllegalStateException} is thrown if this property is set
* on a thread other than the JavaFX Application Thread.
*
* @defaultValue null
*/
private SceneModel scene = new SceneModel();
protected void setScene(Scene value) { scene.set(value); }
public final Scene getScene() { return scene.get(); }
public final ReadOnlyObjectProperty sceneProperty() { return scene.getReadOnlyProperty(); }
private final class SceneModel extends ReadOnlyObjectWrapper {
private Scene oldScene;
@Override protected void invalidated() {
final Scene newScene = get();
if (oldScene == newScene) {
return;
}
Toolkit.getToolkit().checkFxUserThread();
// First, detach scene peer from this window
updatePeerScene(null);
// Second, dispose scene peer
if (oldScene != null) {
oldScene.impl_setWindow(null);
StyleManager.getInstance().forget(oldScene);
}
if (newScene != null) {
final Window oldWindow = newScene.getWindow();
if (oldWindow != null) {
// if the new scene was previously set to a window
// we need to remove it from that window
// NOTE: can this "scene" property be bound?
oldWindow.setScene(null);
}
// Set the "window" on the new scene. This will also trigger
// scene's peer creation.
newScene.impl_setWindow(Window.this);
// Set scene impl on stage impl
updatePeerScene(newScene.impl_getPeer());
// Fix for RT-15432: we should update new Scene's stylesheets, if the
// window is already showing. For not yet shown windows, the update is
// performed in Window.visibleChanging()
if (isShowing()) {
newScene.getRoot().impl_reapplyCSS();
if (!widthExplicit || !heightExplicit) {
getScene().impl_preferredSize();
adjustSize(true);
}
}
}
oldScene = newScene;
}
@Override
public Object getBean() {
return Window.this;
}
@Override
public String getName() {
return "scene";
}
private void updatePeerScene(final TKScene tkScene) {
if (impl_peer != null) {
// Set scene impl on stage impl
impl_peer.setScene(tkScene);
}
}
}
/**
* Defines the opacity of the {@code Stage} as a value between 0.0 and 1.0.
* The opacity is reflected across the {@code Stage}, its {@code Decoration}
* and its {@code Scene} content. On a JavaFX runtime platform that does not
* support opacity, assigning a value to this variable will have no
* visible difference. A {@code Stage} with 0% opacity is fully translucent.
* Typically, a {@code Stage} with 0% opacity will not receive any mouse
* events.
*
* @defaultValue 1.0
*/
private DoubleProperty opacity;
public final void setOpacity(double value) {
opacityProperty().set(value);
}
public final double getOpacity() {
return opacity == null ? 1.0 : opacity.get();
}
public final DoubleProperty opacityProperty() {
if (opacity == null) {
opacity = new DoublePropertyBase(1.0) {
@Override
protected void invalidated() {
if (impl_peer != null) {
impl_peer.setOpacity((float) get());
}
}
@Override
public Object getBean() {
return Window.this;
}
@Override
public String getName() {
return "opacity";
}
};
}
return opacity;
}
/**
* Called when there is an external request to close this {@code Window}.
* The installed event handler can prevent window closing by consuming the
* received event.
*/
private ObjectProperty> onCloseRequest;
public final void setOnCloseRequest(EventHandler value) {
onCloseRequestProperty().set(value);
}
public final EventHandler getOnCloseRequest() {
return (onCloseRequest != null) ? onCloseRequest.get() : null;
}
public final ObjectProperty>
onCloseRequestProperty() {
if (onCloseRequest == null) {
onCloseRequest = new ObjectPropertyBase>() {
@Override protected void invalidated() {
setEventHandler(WindowEvent.WINDOW_CLOSE_REQUEST, get());
}
@Override
public Object getBean() {
return Window.this;
}
@Override
public String getName() {
return "onCloseRequest";
}
};
}
return onCloseRequest;
}
/**
* Called just prior to the Window being shown.
*/
private ObjectProperty> onShowing;
public final void setOnShowing(EventHandler value) { onShowingProperty().set(value); }
public final EventHandler getOnShowing() {
return onShowing == null ? null : onShowing.get();
}
public final ObjectProperty> onShowingProperty() {
if (onShowing == null) {
onShowing = new ObjectPropertyBase>() {
@Override protected void invalidated() {
setEventHandler(WindowEvent.WINDOW_SHOWING, get());
}
@Override
public Object getBean() {
return Window.this;
}
@Override
public String getName() {
return "onShowing";
}
};
}
return onShowing;
}
/**
* Called just after the Window is shown.
*/
private ObjectProperty> onShown;
public final void setOnShown(EventHandler value) { onShownProperty().set(value); }
public final EventHandler getOnShown() {
return onShown == null ? null : onShown.get();
}
public final ObjectProperty> onShownProperty() {
if (onShown == null) {
onShown = new ObjectPropertyBase>() {
@Override protected void invalidated() {
setEventHandler(WindowEvent.WINDOW_SHOWN, get());
}
@Override
public Object getBean() {
return Window.this;
}
@Override
public String getName() {
return "onShown";
}
};
}
return onShown;
}
/**
* Called just prior to the Window being hidden.
*/
private ObjectProperty> onHiding;
public final void setOnHiding(EventHandler value) { onHidingProperty().set(value); }
public final EventHandler getOnHiding() {
return onHiding == null ? null : onHiding.get();
}
public final ObjectProperty> onHidingProperty() {
if (onHiding == null) {
onHiding = new ObjectPropertyBase>() {
@Override protected void invalidated() {
setEventHandler(WindowEvent.WINDOW_HIDING, get());
}
@Override
public Object getBean() {
return Window.this;
}
@Override
public String getName() {
return "onHiding";
}
};
}
return onHiding;
}
/**
* Called just after the Window has been hidden.
* When the {@code Window} is hidden, this event handler is invoked allowing
* the developer to clean up resources or perform other tasks when the
* {@link Window} is closed.
*/
private ObjectProperty> onHidden;
public final void setOnHidden(EventHandler value) { onHiddenProperty().set(value); }
public final EventHandler getOnHidden() {
return onHidden == null ? null : onHidden.get();
}
public final ObjectProperty> onHiddenProperty() {
if (onHidden == null) {
onHidden = new ObjectPropertyBase>() {
@Override protected void invalidated() {
setEventHandler(WindowEvent.WINDOW_HIDDEN, get());
}
@Override
public Object getBean() {
return Window.this;
}
@Override
public String getName() {
return "onHidden";
}
};
}
return onHidden;
}
/**
* Whether or not this {@code Stage} is showing (that is, open on the
* user's system). The Stage might be "showing", yet the user might not
* be able to see it due to the Stage being rendered behind another window
* or due to the Stage being positioned off the monitor.
*
* @defaultValue false
*/
private ReadOnlyBooleanWrapper showing = new ReadOnlyBooleanWrapper() {
private boolean oldVisible;
@Override protected void invalidated() {
final boolean newVisible = get();
if (oldVisible == newVisible) {
return;
}
if (!oldVisible && newVisible) {
fireEvent(new WindowEvent(Window.this, WindowEvent.WINDOW_SHOWING));
} else {
fireEvent(new WindowEvent(Window.this, WindowEvent.WINDOW_HIDING));
}
oldVisible = newVisible;
impl_visibleChanging(newVisible);
if (newVisible) {
hasBeenVisible = true;
windowQueue.add(Window.this);
} else {
windowQueue.remove(Window.this);
}
Toolkit tk = Toolkit.getToolkit();
if (impl_peer != null) {
if (newVisible) {
impl_peer.setSecurityContext(acc);
if (peerListener == null) {
peerListener = new WindowPeerListener(Window.this);
}
// Setup listener for changes coming back from peer
impl_peer.setTKStageListener(peerListener);
// Register pulse listener
tk.addStageTkPulseListener(peerBoundsConfigurator);
if (getScene() != null) {
getScene().impl_initPeer();
impl_peer.setScene(getScene().impl_getPeer());
getScene().impl_preferredSize();
}
// Set peer bounds
if ((getScene() != null) && (!widthExplicit || !heightExplicit)) {
adjustSize(true);
} else {
peerBoundsConfigurator.setSize(
getWidth(), getHeight(), -1, -1);
}
if (!xExplicit && !yExplicit) {
centerOnScreen();
} else {
peerBoundsConfigurator.setLocation(
getX() + winTranslateX,
getY() + winTranslateY,
0, 0);
}
// set peer bounds before the window is shown
applyBounds();
impl_peer.setOpacity((float)getOpacity());
impl_peer.setVisible(true);
fireEvent(new WindowEvent(Window.this, WindowEvent.WINDOW_SHOWN));
} else {
impl_peer.setVisible(false);
// Call listener
fireEvent(new WindowEvent(Window.this, WindowEvent.WINDOW_HIDDEN));
if (getScene() != null) {
impl_peer.setScene(null);
getScene().impl_disposePeer();
StyleManager.getInstance().forget(getScene());
}
// Remove toolkit pulse listener
tk.removeStageTkPulseListener(peerBoundsConfigurator);
// Remove listener for changes coming back from peer
impl_peer.setTKStageListener(null);
// Notify peer
impl_peer.close();
}
}
if (newVisible) {
tk.requestNextPulse();
}
impl_visibleChanged(newVisible);
}
@Override
public Object getBean() {
return Window.this;
}
@Override
public String getName() {
return "showing";
}
};
private void setShowing(boolean value) {
Toolkit.getToolkit().checkFxUserThread();
showing.set(value);
}
public final boolean isShowing() { return showing.get(); }
public final ReadOnlyBooleanProperty showingProperty() { return showing.getReadOnlyProperty(); }
// flag indicating whether this window has ever been made visible.
boolean hasBeenVisible = false;
/**
* Attempts to show this Window by setting visibility to true
*
* @throws IllegalStateException if this method is called on a thread
* other than the JavaFX Application Thread.
*/
protected void show() {
setShowing(true);
}
/**
* Attempts to hide this Window by setting the visibility to false.
*
* @throws IllegalStateException if this method is called on a thread
* other than the JavaFX Application Thread.
*/
public void hide() {
setShowing(false);
}
/**
* This can be replaced by listening for the onShowing/onHiding events
* @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 void impl_visibleChanging(boolean visible) {
if (visible && (getScene() != null)) {
getScene().getRoot().impl_reapplyCSS();
}
}
/**
* This can be replaced by listening for the onShown/onHidden events
* @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 void impl_visibleChanged(boolean visible) {
assert impl_peer != null;
if (!visible) {
peerListener = null;
impl_peer = null;
}
}
// PENDING_DOC_REVIEW
/**
* Specifies the event dispatcher for this node. The default event
* dispatcher sends the received events to the registered event handlers and
* filters. When replacing the value with a new {@code EventDispatcher},
* the new dispatcher should forward events to the replaced dispatcher
* to maintain the node's default event handling behavior.
*/
private ObjectProperty eventDispatcher;
public final void setEventDispatcher(EventDispatcher value) {
eventDispatcherProperty().set(value);
}
public final EventDispatcher getEventDispatcher() {
return eventDispatcherProperty().get();
}
public final ObjectProperty eventDispatcherProperty() {
initializeInternalEventDispatcher();
return eventDispatcher;
}
private WindowEventDispatcher internalEventDispatcher;
// PENDING_DOC_REVIEW
/**
* Registers an event handler to this node. The handler is called when the
* node receives an {@code Event} of the specified type during the bubbling
* phase of event delivery.
*
* @param the specific event class of the handler
* @param eventType the type of the events to receive by the handler
* @param eventHandler the handler to register
* @throws NullPointerException if the event type or handler is null
*/
public final void addEventHandler(
final EventType eventType,
final EventHandler eventHandler) {
getInternalEventDispatcher().getEventHandlerManager()
.addEventHandler(eventType, eventHandler);
}
// PENDING_DOC_REVIEW
/**
* Unregisters a previously registered event handler from this node. One
* handler might have been registered for different event types, so the
* caller needs to specify the particular event type from which to
* unregister the handler.
*
* @param the specific event class of the handler
* @param eventType the event type from which to unregister
* @param eventHandler the handler to unregister
* @throws NullPointerException if the event type or handler is null
*/
public final void removeEventHandler(
final EventType eventType,
final EventHandler eventHandler) {
getInternalEventDispatcher().getEventHandlerManager()
.removeEventHandler(eventType,
eventHandler);
}
// PENDING_DOC_REVIEW
/**
* Registers an event filter to this node. The filter is called when the
* node receives an {@code Event} of the specified type during the capturing
* phase of event delivery.
*
* @param the specific event class of the filter
* @param eventType the type of the events to receive by the filter
* @param eventFilter the filter to register
* @throws NullPointerException if the event type or filter is null
*/
public final void addEventFilter(
final EventType eventType,
final EventHandler eventFilter) {
getInternalEventDispatcher().getEventHandlerManager()
.addEventFilter(eventType, eventFilter);
}
// PENDING_DOC_REVIEW
/**
* Unregisters a previously registered event filter from this node. One
* filter might have been registered for different event types, so the
* caller needs to specify the particular event type from which to
* unregister the filter.
*
* @param the specific event class of the filter
* @param eventType the event type from which to unregister
* @param eventFilter the filter to unregister
* @throws NullPointerException if the event type or filter is null
*/
public final void removeEventFilter(
final EventType eventType,
final EventHandler eventFilter) {
getInternalEventDispatcher().getEventHandlerManager()
.removeEventFilter(eventType, eventFilter);
}
/**
* Sets the handler to use for this event type. There can only be one such handler
* specified at a time. This handler is guaranteed to be called first. This is
* used for registering the user-defined onFoo event handlers.
*
* @param the specific event class of the handler
* @param eventType the event type to associate with the given eventHandler
* @param eventHandler the handler to register, or null to unregister
* @throws NullPointerException if the event type is null
*/
protected final void setEventHandler(
final EventType eventType,
final EventHandler eventHandler) {
getInternalEventDispatcher().getEventHandlerManager()
.setEventHandler(eventType, eventHandler);
}
WindowEventDispatcher getInternalEventDispatcher() {
initializeInternalEventDispatcher();
return internalEventDispatcher;
}
private void initializeInternalEventDispatcher() {
if (internalEventDispatcher == null) {
internalEventDispatcher = createInternalEventDispatcher();
eventDispatcher = new SimpleObjectProperty(
this,
"eventDispatcher",
internalEventDispatcher);
}
}
WindowEventDispatcher createInternalEventDispatcher() {
return new WindowEventDispatcher(this);
}
/**
* Fires the specified event.
*
* This method must be called on the FX user thread.
*
* @param event the event to fire
*/
public final void fireEvent(Event event) {
Event.fireEvent(this, event);
}
// PENDING_DOC_REVIEW
/**
* Construct an event dispatch chain for this window.
*
* @param tail the initial chain to build from
* @return the resulting event dispatch chain for this window
*/
@Override
public EventDispatchChain buildEventDispatchChain(
EventDispatchChain tail) {
if (eventDispatcher != null) {
final EventDispatcher eventDispatcherValue = eventDispatcher.get();
if (eventDispatcherValue != null) {
tail = tail.prepend(eventDispatcherValue);
}
}
return tail;
}
private int focusGrabCounter;
void increaseFocusGrabCounter() {
if ((++focusGrabCounter == 1) && (impl_peer != null) && isFocused()) {
impl_peer.grabFocus();
}
}
void decreaseFocusGrabCounter() {
if ((--focusGrabCounter == 0) && (impl_peer != null)) {
impl_peer.ungrabFocus();
}
}
private void focusChanged(final boolean newIsFocused) {
if ((focusGrabCounter > 0) && (impl_peer != null) && newIsFocused) {
impl_peer.grabFocus();
}
}
final void applyBounds() {
peerBoundsConfigurator.apply();
}
Window getWindowOwner() {
return null;
}
private Screen getWindowScreen() {
Window window = this;
do {
if (!Double.isNaN(window.getX())
&& !Double.isNaN(window.getY())
&& !Double.isNaN(window.getWidth())
&& !Double.isNaN(window.getHeight())) {
return Utils.getScreenForRectangle(
new Rectangle2D(window.getX(),
window.getY(),
window.getWidth(),
window.getHeight()));
}
window = window.getWindowOwner();
} while (window != null);
return Screen.getPrimary();
}
/**
* Caches all requested bounds settings and applies them at once during
* the next pulse.
*/
private final class TKBoundsConfigurator implements TKPulseListener {
private double x;
private double y;
private float xGravity;
private float yGravity;
private double windowWidth;
private double windowHeight;
private double clientWidth;
private double clientHeight;
private boolean dirty;
public TKBoundsConfigurator() {
reset();
}
public void setX(final double x, final float xGravity) {
this.x = x;
this.xGravity = xGravity;
setDirty();
}
public void setY(final double y, final float yGravity) {
this.y = y;
this.yGravity = yGravity;
setDirty();
}
public void setWindowWidth(final double windowWidth) {
this.windowWidth = windowWidth;
setDirty();
}
public void setWindowHeight(final double windowHeight) {
this.windowHeight = windowHeight;
setDirty();
}
public void setClientWidth(final double clientWidth) {
this.clientWidth = clientWidth;
setDirty();
}
public void setClientHeight(final double clientHeight) {
this.clientHeight = clientHeight;
setDirty();
}
public void setLocation(final double x,
final double y,
final float xGravity,
final float yGravity) {
this.x = x;
this.y = y;
this.xGravity = xGravity;
this.yGravity = yGravity;
setDirty();
}
public void setSize(final double windowWidth,
final double windowHeight,
final double clientWidth,
final double clientHeight) {
this.windowWidth = windowWidth;
this.windowHeight = windowHeight;
this.clientWidth = clientWidth;
this.clientHeight = clientHeight;
setDirty();
}
public void apply() {
if (dirty) {
impl_peer.setBounds((float) (Double.isNaN(x) ? 0 : x),
(float) (Double.isNaN(y) ? 0 : y),
!Double.isNaN(x),
!Double.isNaN(y),
(float) windowWidth,
(float) windowHeight,
(float) clientWidth,
(float) clientHeight,
xGravity, yGravity);
reset();
}
}
@Override
public void pulse() {
apply();
}
private void reset() {
x = Double.NaN;
y = Double.NaN;
xGravity = 0;
yGravity = 0;
windowWidth = -1;
windowHeight = -1;
clientWidth = -1;
clientHeight = -1;
dirty = false;
}
private void setDirty() {
if (!dirty) {
Toolkit.getToolkit().requestNextPulse();
dirty = true;
}
}
}
}