jogamp.newt.WindowImpl Maven / Gradle / Ivy
Show all versions of jogl Show documentation
/*
* Copyright (c) 2008 Sun Microsystems, Inc. All Rights Reserved.
Copyright (c) 2010 JogAmp Community. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* - Redistribution of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* - Redistribution in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* Neither the name of Sun Microsystems, Inc. or the names of
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* This software is provided "AS IS," without a warranty of any kind. ALL
* EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES,
* INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A
* PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN
* MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL NOT BE LIABLE FOR
* ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING OR
* DISTRIBUTING THIS SOFTWARE OR ITS DERIVATIVES. IN NO EVENT WILL SUN OR
* ITS LICENSORS BE LIABLE FOR ANY LOST REVENUE, PROFIT OR DATA, OR FOR
* DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL, INCIDENTAL OR PUNITIVE
* DAMAGES, HOWEVER CAUSED AND REGARDLESS OF THE THEORY OF LIABILITY,
* ARISING OUT OF THE USE OF OR INABILITY TO USE THIS SOFTWARE, EVEN IF
* SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
*
*/
package jogamp.newt;
import java.lang.ref.WeakReference;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import com.jogamp.nativewindow.AbstractGraphicsConfiguration;
import com.jogamp.nativewindow.AbstractGraphicsDevice;
import com.jogamp.nativewindow.CapabilitiesChooser;
import com.jogamp.nativewindow.CapabilitiesImmutable;
import com.jogamp.nativewindow.NativeSurface;
import com.jogamp.nativewindow.NativeWindow;
import com.jogamp.nativewindow.NativeWindowException;
import com.jogamp.nativewindow.NativeWindowFactory;
import com.jogamp.nativewindow.OffscreenLayerSurface;
import com.jogamp.nativewindow.ScalableSurface;
import com.jogamp.nativewindow.SurfaceUpdatedListener;
import com.jogamp.nativewindow.WindowClosingProtocol;
import com.jogamp.nativewindow.util.DimensionImmutable;
import com.jogamp.nativewindow.util.Insets;
import com.jogamp.nativewindow.util.InsetsImmutable;
import com.jogamp.nativewindow.util.PixelRectangle;
import com.jogamp.nativewindow.util.Point;
import com.jogamp.nativewindow.util.PointImmutable;
import com.jogamp.nativewindow.util.Rectangle;
import com.jogamp.nativewindow.util.RectangleImmutable;
import jogamp.nativewindow.SurfaceScaleUtils;
import jogamp.nativewindow.SurfaceUpdatedHelper;
import com.jogamp.common.ExceptionUtils;
import com.jogamp.common.util.ArrayHashSet;
import com.jogamp.common.util.IntBitfield;
import com.jogamp.common.util.PropertyAccess;
import com.jogamp.common.util.ReflectionUtil;
import com.jogamp.common.util.locks.LockFactory;
import com.jogamp.common.util.locks.RecursiveLock;
import com.jogamp.newt.Display;
import com.jogamp.newt.Display.PointerIcon;
import com.jogamp.newt.MonitorDevice;
import com.jogamp.newt.NewtFactory;
import com.jogamp.newt.Screen;
import com.jogamp.newt.Window;
import com.jogamp.newt.event.DoubleTapScrollGesture;
import com.jogamp.newt.event.GestureHandler;
import com.jogamp.newt.event.InputEvent;
import com.jogamp.newt.event.KeyEvent;
import com.jogamp.newt.event.KeyListener;
import com.jogamp.newt.event.MonitorEvent;
import com.jogamp.newt.event.MonitorModeListener;
import com.jogamp.newt.event.MouseEvent;
import com.jogamp.newt.event.MouseEvent.PointerType;
import com.jogamp.newt.event.MouseListener;
import com.jogamp.newt.event.NEWTEvent;
import com.jogamp.newt.event.NEWTEventConsumer;
import com.jogamp.newt.event.WindowEvent;
import com.jogamp.newt.event.WindowListener;
import com.jogamp.newt.event.WindowUpdateEvent;
public abstract class WindowImpl implements Window, NEWTEventConsumer
{
public static final boolean DEBUG_TEST_REPARENT_INCOMPATIBLE;
static {
Debug.initSingleton();
DEBUG_TEST_REPARENT_INCOMPATIBLE = PropertyAccess.isPropertyDefined("newt.test.Window.reparent.incompatible", true);
ScreenImpl.initSingleton();
}
protected static final ArrayList> windowList = new ArrayList>();
/** Maybe utilized at a shutdown hook, impl. does not block. */
public static final void shutdownAll() {
final int wCount = windowList.size();
if(DEBUG_IMPLEMENTATION) {
System.err.println("Window.shutdownAll "+wCount+" instances, on thread "+getThreadName());
}
for(int i=0; i0; i++) { // be safe ..
final WindowImpl w = windowList.remove(0).get();
if(DEBUG_IMPLEMENTATION) {
final long wh = null != w ? w.getWindowHandle() : 0;
System.err.println("Window.shutdownAll["+(i+1)+"/"+wCount+"]: "+toHexString(wh)+", GCed "+(null==w));
}
if( null != w ) {
w.shutdown();
}
}
}
private static void addWindow2List(final WindowImpl window) {
synchronized(windowList) {
// GC before add
int i=0, gced=0;
while( i < windowList.size() ) {
if( null == windowList.get(i).get() ) {
gced++;
windowList.remove(i);
} else {
i++;
}
}
windowList.add(new WeakReference(window));
if(DEBUG_IMPLEMENTATION) {
System.err.println("Window.addWindow2List: GCed "+gced+", size "+windowList.size());
}
}
}
/** Timeout of queued events (repaint and resize) */
static final long QUEUED_EVENT_TO = 1200; // ms
private static final PointerType[] constMousePointerTypes = new PointerType[] { PointerType.Mouse };
//
// Volatile: Multithread Mutable Access
//
private volatile long windowHandle = 0; // lifecycle critical
private volatile boolean visible = false; // lifecycle critical
private volatile boolean hasFocus = false;
private volatile int pixWidth = 128, pixHeight = 128; // client-area size w/o insets in pixel units, default: may be overwritten by user
private volatile int winWidth = 128, winHeight = 128; // client-area size w/o insets in window units, default: may be overwritten by user
protected final float[] minPixelScale = new float[] { ScalableSurface.IDENTITY_PIXELSCALE, ScalableSurface.IDENTITY_PIXELSCALE };
protected final float[] maxPixelScale = new float[] { ScalableSurface.IDENTITY_PIXELSCALE, ScalableSurface.IDENTITY_PIXELSCALE };
protected final float[] hasPixelScale = new float[] { ScalableSurface.IDENTITY_PIXELSCALE, ScalableSurface.IDENTITY_PIXELSCALE };
protected final float[] reqPixelScale = new float[] { ScalableSurface.AUTOMAX_PIXELSCALE, ScalableSurface.AUTOMAX_PIXELSCALE };
private volatile int x = 64, y = 64; // client-area pos w/o insets in window units
private volatile Insets insets = new Insets(); // insets of decoration (if top-level && decorated)
private boolean blockInsetsChange = false; // block insets change (from same thread)
private final RecursiveLock windowLock = LockFactory.createRecursiveLock(); // Window instance wide lock
private int surfaceLockCount = 0; // surface lock recursion count
private ScreenImpl screen; // never null after create - may change reference though (reparent)
private boolean screenReferenceAdded = false;
private NativeWindow parentWindow = null;
private long parentWindowHandle = 0;
private AbstractGraphicsConfiguration config = null; // control access due to delegation
protected CapabilitiesImmutable capsRequested = null;
protected CapabilitiesChooser capabilitiesChooser = null; // default null -> default
private boolean fullscreen = false, brokenFocusChange = false;
private List fullscreenMonitors = null;
private boolean fullscreenUseMainMonitor = true;
private boolean autoPosition = true; // default: true (allow WM to choose top-level position, if not set by user)
private int nfs_width, nfs_height, nfs_x, nfs_y; // non fullscreen client-area size/pos w/o insets
private boolean nfs_alwaysOnTop; // non fullscreen alwaysOnTop setting
private NativeWindow nfs_parent = null; // non fullscreen parent, in case explicit reparenting is performed (offscreen)
private String title = "Newt Window";
private boolean undecorated = false;
private boolean alwaysOnTop = false;
private PointerIconImpl pointerIcon = null;
private boolean pointerVisible = true;
private boolean pointerConfined = false;
private LifecycleHook lifecycleHook = null;
private Runnable windowDestroyNotifyAction = null;
private FocusRunnable focusAction = null;
private KeyListener keyboardFocusHandler = null;
private final SurfaceUpdatedHelper surfaceUpdatedHelper = new SurfaceUpdatedHelper();
private final Object childWindowsLock = new Object();
private final ArrayList childWindows = new ArrayList();
private ArrayList mouseListeners = new ArrayList();
/** from event passing: {@link WindowImpl#consumePointerEvent(MouseEvent)}. */
private static class PointerState0 {
/** Pointer entered window - is inside the window (may be synthetic) */
boolean insideSurface = false;
/** Mouse EXIT has been sent (only for MOUSE type enter/exit)*/
boolean exitSent = false;
/** last time when a pointer button was pressed */
long lastButtonPressTime = 0;
/** Pointer in dragging mode */
boolean dragging = false;
void clearButton() {
lastButtonPressTime = 0;
}
public String toString() { return "PState0[inside "+insideSurface+", exitSent "+exitSent+", lastPress "+lastButtonPressTime+", dragging "+dragging+"]"; }
}
private final PointerState0 pState0 = new PointerState0();
/** from direct input: {@link WindowImpl#doPointerEvent(boolean, boolean, int[], short, int, int, boolean, short[], int[], int[], float[], float, float[], float)}. */
private static class PointerState1 extends PointerState0 {
/** Current pressed mouse button number */
short buttonPressed = (short)0;
/** Current pressed mouse button modifier mask */
int buttonPressedMask = 0;
/** Last mouse button click count */
short lastButtonClickCount = (short)0;
@Override
final void clearButton() {
super.clearButton();
lastButtonClickCount = (short)0;
if( !dragging || 0 == buttonPressedMask ) {
buttonPressed = 0;
buttonPressedMask = 0;
dragging = false;
}
}
/** Last pointer-move position for 8 touch-down pointers */
final Point[] movePositions = new Point[] {
new Point(), new Point(), new Point(), new Point(),
new Point(), new Point(), new Point(), new Point() };
final Point getMovePosition(final int id) {
if( 0 <= id && id < movePositions.length ) {
return movePositions[id];
}
return null;
}
public final String toString() { return "PState1[inside "+insideSurface+", exitSent "+exitSent+", lastPress "+lastButtonPressTime+
", pressed [button "+buttonPressed+", mask "+buttonPressedMask+", dragging "+dragging+", clickCount "+lastButtonClickCount+"]"; }
}
private final PointerState1 pState1 = new PointerState1();
/** Pointer names -> pointer ID (consecutive index, starting w/ 0) */
private final ArrayHashSet pName2pID = new ArrayHashSet();
private boolean defaultGestureHandlerEnabled = true;
private DoubleTapScrollGesture gesture2PtrTouchScroll = null;
private ArrayList pointerGestureHandler = new ArrayList();
private ArrayList gestureListeners = new ArrayList();
private ArrayList keyListeners = new ArrayList();
private ArrayList windowListeners = new ArrayList();
private boolean repaintQueued = false;
//
// Construction Methods
//
private static Class> getWindowClass(final String type)
throws ClassNotFoundException
{
final Class> windowClass = NewtFactory.getCustomClass(type, "WindowDriver");
if(null==windowClass) {
throw new ClassNotFoundException("Failed to find NEWT Window Class <"+type+".WindowDriver>");
}
return windowClass;
}
public static WindowImpl create(final NativeWindow parentWindow, final long parentWindowHandle, final Screen screen, final CapabilitiesImmutable caps) {
try {
Class> windowClass;
if(caps.isOnscreen()) {
windowClass = getWindowClass(screen.getDisplay().getType());
} else {
windowClass = OffscreenWindow.class;
}
final WindowImpl window = (WindowImpl) windowClass.newInstance();
window.parentWindow = parentWindow;
window.parentWindowHandle = parentWindowHandle;
window.screen = (ScreenImpl) screen;
window.capsRequested = (CapabilitiesImmutable) caps.cloneMutable();
window.instantiationFinished();
addWindow2List(window);
return window;
} catch (final Throwable t) {
t.printStackTrace();
throw new NativeWindowException(t);
}
}
public static WindowImpl create(final Object[] cstrArguments, final Screen screen, final CapabilitiesImmutable caps) {
try {
final Class> windowClass = getWindowClass(screen.getDisplay().getType());
final Class>[] cstrArgumentTypes = getCustomConstructorArgumentTypes(windowClass);
if(null==cstrArgumentTypes) {
throw new NativeWindowException("WindowClass "+windowClass+" doesn't support custom arguments in constructor");
}
final int argsChecked = verifyConstructorArgumentTypes(cstrArgumentTypes, cstrArguments);
if ( argsChecked < cstrArguments.length ) {
throw new NativeWindowException("WindowClass "+windowClass+" constructor mismatch at argument #"+argsChecked+"; Constructor: "+getTypeStrList(cstrArgumentTypes)+", arguments: "+getArgsStrList(cstrArguments));
}
final WindowImpl window = (WindowImpl) ReflectionUtil.createInstance( windowClass, cstrArgumentTypes, cstrArguments ) ;
window.screen = (ScreenImpl) screen;
window.capsRequested = (CapabilitiesImmutable) caps.cloneMutable();
window.instantiationFinished();
addWindow2List(window);
return window;
} catch (final Throwable t) {
throw new NativeWindowException(t);
}
}
/** Fast invalidation of instance w/o any blocking function call. */
private final void shutdown() {
if(null!=lifecycleHook) {
lifecycleHook.shutdownRenderingAction();
}
setWindowHandle(0);
visible = false;
fullscreen = false;
fullscreenMonitors = null;
fullscreenUseMainMonitor = true;
hasFocus = false;
parentWindowHandle = 0;
}
protected final void setGraphicsConfiguration(final AbstractGraphicsConfiguration cfg) {
config = cfg;
}
public static interface LifecycleHook {
/**
* Reset of internal state counter, ie totalFrames, etc.
* Called from EDT while window is locked.
*/
public abstract void resetCounter();
/**
* Invoked after Window setVisible,
* allows allocating resources depending on the native Window.
* Called from EDT while window is locked.
*/
void setVisibleActionPost(boolean visible, boolean nativeWindowCreated);
/**
* Notifies the receiver to preserve resources (GL, ..)
* for the next destroy*() calls (only), if supported and if value
is true
, otherwise clears preservation flag.
* @param value true
to set the one-shot preservation if supported, otherwise clears it.
*/
void preserveGLStateAtDestroy(boolean value);
/**
* Invoked before Window destroy action,
* allows releasing of resources depending on the native Window.
* Surface not locked yet.
* Called not necessarily from EDT.
*/
void destroyActionPreLock();
/**
* Invoked before Window destroy action,
* allows releasing of resources depending on the native Window.
* Surface locked.
* Called from EDT while window is locked.
*/
void destroyActionInLock();
/**
* Invoked for expensive modifications, ie while reparenting and MonitorMode change.
* No lock is hold when invoked.
*
* @return true is paused, otherwise false. If true {@link #resumeRenderingAction()} shall be issued.
*
* @see #resumeRenderingAction()
*/
boolean pauseRenderingAction();
/**
* Invoked for expensive modifications, ie while reparenting and MonitorMode change.
* No lock is hold when invoked.
*
* @see #pauseRenderingAction()
*/
void resumeRenderingAction();
/**
* Shutdown rendering action (thread) abnormally.
*
* Should be called only at shutdown, if necessary.
*
*/
void shutdownRenderingAction();
}
private boolean createNative() {
long tStart;
if(DEBUG_IMPLEMENTATION) {
tStart = System.nanoTime();
System.err.println("Window.createNative() START ("+getThreadName()+", "+this+")");
} else {
tStart = 0;
}
if( null != parentWindow &&
NativeSurface.LOCK_SURFACE_NOT_READY >= parentWindow.lockSurface() ) {
throw new NativeWindowException("Parent surface lock: not ready: "+parentWindow);
}
final boolean hasParent = null != parentWindow || 0 != this.parentWindowHandle;
// child window: position defaults to 0/0, no auto position, no negative position
if( hasParent && ( autoPosition || 0>getX() || 0>getY() ) ) {
definePosition(0, 0);
}
boolean postParentlockFocus = false;
try {
if(validateParentWindowHandle()) {
if( !screenReferenceAdded ) {
screen.addReference();
screenReferenceAdded = true;
}
if(canCreateNativeImpl()) {
final int wX, wY;
final boolean usePosition;
if( autoPosition ) {
wX = 0;
wY = 0;
usePosition = false;
} else {
wX = getX();
wY = getY();
usePosition = true;
}
final long t0 = System.currentTimeMillis();
createNativeImpl();
screen.addMonitorModeListener(monitorModeListenerImpl);
setTitleImpl(title);
setPointerIconIntern(pointerIcon);
setPointerVisibleIntern(pointerVisible);
confinePointerImpl(pointerConfined);
setKeyboardVisible(keyboardVisible);
final long remainingV = waitForVisible(true, false);
if( 0 <= remainingV ) {
if(isFullscreen()) {
synchronized(fullScreenAction) {
fullscreen = false; // trigger a state change
fullScreenAction.init(true);
fullScreenAction.run();
}
} else if ( !hasParent ) {
// Wait until position is reached within tolerances, either auto-position or custom position.
waitForPosition(usePosition, wX, wY, Window.TIMEOUT_NATIVEWINDOW);
}
if (DEBUG_IMPLEMENTATION) {
System.err.println("Window.createNative(): elapsed "+(System.currentTimeMillis()-t0)+" ms");
}
postParentlockFocus = true;
}
}
}
} finally {
if(null!=parentWindow) {
parentWindow.unlockSurface();
}
}
if(postParentlockFocus) {
// harmonize focus behavior for all platforms: focus on creation
requestFocusInt(isFullscreen() /* skipFocusAction if fullscreen */);
((DisplayImpl) screen.getDisplay()).dispatchMessagesNative(); // status up2date
}
if(DEBUG_IMPLEMENTATION) {
System.err.println("Window.createNative() END ("+getThreadName()+", "+this+") total "+ (System.nanoTime()-tStart)/1e6 +"ms");
}
return isNativeValid() ;
}
private void removeScreenReference() {
if(screenReferenceAdded) {
// be nice, probably already called recursive via
// closeAndInvalidate() -> closeNativeIml() -> .. -> windowDestroyed() -> closeAndInvalidate() !
// or via reparentWindow .. etc
screenReferenceAdded = false;
screen.removeReference();
}
}
private boolean validateParentWindowHandle() {
if(null!=parentWindow) {
parentWindowHandle = getNativeWindowHandle(parentWindow);
return 0 != parentWindowHandle ;
}
return true;
}
private static long getNativeWindowHandle(final NativeWindow nativeWindow) {
long handle = 0;
if(null!=nativeWindow) {
boolean wasLocked = false;
if( NativeSurface.LOCK_SURFACE_NOT_READY < nativeWindow.lockSurface() ) {
wasLocked = true;
try {
handle = nativeWindow.getWindowHandle();
if(0==handle) {
throw new NativeWindowException("Parent native window handle is NULL, after succesful locking: "+nativeWindow);
}
} catch (final NativeWindowException nwe) {
if(DEBUG_IMPLEMENTATION) {
System.err.println("Window.getNativeWindowHandle: not successful yet: "+nwe);
}
} finally {
nativeWindow.unlockSurface();
}
}
if(DEBUG_IMPLEMENTATION) {
System.err.println("Window.getNativeWindowHandle: locked "+wasLocked+", "+nativeWindow);
}
}
return handle;
}
//----------------------------------------------------------------------
// NativeSurface: Native implementation
//
protected int lockSurfaceImpl() { return LOCK_SUCCESS; }
protected void unlockSurfaceImpl() { }
//----------------------------------------------------------------------
// WindowClosingProtocol implementation
//
private final Object closingListenerLock = new Object();
private WindowClosingMode defaultCloseOperation = WindowClosingMode.DISPOSE_ON_CLOSE;
@Override
public final WindowClosingMode getDefaultCloseOperation() {
synchronized (closingListenerLock) {
return defaultCloseOperation;
}
}
@Override
public final WindowClosingMode setDefaultCloseOperation(final WindowClosingMode op) {
synchronized (closingListenerLock) {
final WindowClosingMode _op = defaultCloseOperation;
defaultCloseOperation = op;
return _op;
}
}
//----------------------------------------------------------------------
// Window: Native implementation
//
/**
* Notifies the driver impl. that the instantiation is finished,
* ie. instance created and all fields set.
*/
protected void instantiationFinished() {
// nop
}
protected boolean canCreateNativeImpl() {
return true; // default: always able to be created
}
/**
* The native implementation must set the native windowHandle.
*
*
* The implementation shall respect the states {@link #isAlwaysOnTop()}/{@link #FLAG_IS_ALWAYSONTOP} and
* {@link #isUndecorated()}/{@link #FLAG_IS_UNDECORATED}, ie. the created window shall reflect those settings.
*
*
*
* The implementation should invoke the referenced java state callbacks
* to notify this Java object of state changes.
*
* @see #windowDestroyNotify(boolean)
* @see #focusChanged(boolean, boolean)
* @see #visibleChanged(boolean, boolean)
* @see #sizeChanged(int,int)
* @see #positionChanged(boolean,int, int)
* @see #windowDestroyNotify(boolean)
*/
protected abstract void createNativeImpl();
protected abstract void closeNativeImpl();
/**
* Async request which shall be performed within {@link #TIMEOUT_NATIVEWINDOW}.
*
* If if force == false
the native implementation
* may only request focus if not yet owner.
*
* {@link #focusChanged(boolean, boolean)} should be called
* to notify about the focus traversal.
*
*
* @param force if true, bypass {@link #focusChanged(boolean, boolean)} and force focus request
*/
protected abstract void requestFocusImpl(boolean force);
public static final int FLAG_CHANGE_PARENTING = 1 << 0;
public static final int FLAG_CHANGE_DECORATION = 1 << 1;
public static final int FLAG_CHANGE_FULLSCREEN = 1 << 2;
public static final int FLAG_CHANGE_ALWAYSONTOP = 1 << 3;
public static final int FLAG_CHANGE_VISIBILITY = 1 << 4;
public static final int FLAG_HAS_PARENT = 1 << 8;
public static final int FLAG_IS_UNDECORATED = 1 << 9;
public static final int FLAG_IS_FULLSCREEN = 1 << 10;
public static final int FLAG_IS_FULLSCREEN_SPAN = 1 << 11;
public static final int FLAG_IS_ALWAYSONTOP = 1 << 12;
public static final int FLAG_IS_VISIBLE = 1 << 13;
/**
* The native implementation should invoke the referenced java state callbacks
* to notify this Java object of state changes.
*
*
* Implementations shall set x/y to 0, in case it's negative. This could happen due
* to insets and positioning a decorated window to 0/0, which would place the frame
* outside of the screen.
*
* @param x client-area position in window units, or <0 if unchanged
* @param y client-area position in window units, or <0 if unchanged
* @param width client-area size in window units, or <=0 if unchanged
* @param height client-area size in window units, or <=0 if unchanged
* @param flags bitfield of change and status flags
*
* @see #sizeChanged(int,int)
* @see #positionChanged(boolean,int, int)
*/
protected abstract boolean reconfigureWindowImpl(int x, int y, int width, int height, int flags);
/**
* Tests whether a single reconfigure flag is supported by implementation.
*
* Default is all but {@link #FLAG_IS_FULLSCREEN_SPAN}
*
*/
protected boolean isReconfigureFlagSupported(final int changeFlags) {
return 0 == ( changeFlags & FLAG_IS_FULLSCREEN_SPAN );
}
protected int getReconfigureFlags(final int changeFlags, final boolean visible) {
return changeFlags | ( ( 0 != getParentWindowHandle() ) ? FLAG_HAS_PARENT : 0 ) |
( isUndecorated() ? FLAG_IS_UNDECORATED : 0 ) |
( isFullscreen() ? FLAG_IS_FULLSCREEN : 0 ) |
( isAlwaysOnTop() ? FLAG_IS_ALWAYSONTOP : 0 ) |
( visible ? FLAG_IS_VISIBLE : 0 ) ;
}
protected static String getReconfigureFlagsAsString(StringBuilder sb, final int flags) {
if(null == sb) { sb = new StringBuilder(); }
sb.append("[");
if( 0 != ( FLAG_CHANGE_PARENTING & flags) ) {
sb.append("*");
}
sb.append("PARENT ");
sb.append(0 != ( FLAG_HAS_PARENT & flags));
sb.append(", ");
if( 0 != ( FLAG_CHANGE_FULLSCREEN & flags) ) {
sb.append("*");
}
sb.append("FS ");
sb.append(0 != ( FLAG_IS_FULLSCREEN & flags));
sb.append("[span ");
sb.append(0 != ( FLAG_IS_FULLSCREEN_SPAN & flags));
sb.append("], ");
if( 0 != ( FLAG_CHANGE_DECORATION & flags) ) {
sb.append("*");
}
sb.append("UNDECOR ");
sb.append(0 != ( FLAG_IS_UNDECORATED & flags));
sb.append(", ");
if( 0 != ( FLAG_CHANGE_ALWAYSONTOP & flags) ) {
sb.append("*");
}
sb.append("ALWAYSONTOP ");
sb.append(0 != ( FLAG_IS_ALWAYSONTOP & flags));
sb.append(", ");
if( 0 != ( FLAG_CHANGE_VISIBILITY & flags) ) {
sb.append("*");
}
sb.append("VISIBLE ");
sb.append(0 != ( FLAG_IS_VISIBLE & flags));
sb.append("]");
return sb.toString();
}
protected void setTitleImpl(final String title) {}
/**
* Translates the given window client-area coordinates with top-left origin
* to screen coordinates in window units.
*
* Since the position reflects the client area, it does not include the insets.
*
*
* May return null
, in which case the caller shall traverse through the NativeWindow tree
* as demonstrated in {@link #getLocationOnScreen(com.jogamp.nativewindow.util.Point)}.
*
*
* @return if not null, the screen location of the given coordinates
*/
protected abstract Point getLocationOnScreenImpl(int x, int y);
/**
* Triggered by user via {@link #getInsets()}.
* Implementations may implement this hook to update the insets.
* However, they may prefer the event driven path via {@link #insetsChanged(boolean, int, int, int, int)}.
*
* @see #getInsets()
* @see #insetsChanged(boolean, int, int, int, int)
*/
protected abstract void updateInsetsImpl(Insets insets);
protected boolean setPointerVisibleImpl(final boolean pointerVisible) { return false; }
protected boolean confinePointerImpl(final boolean confine) { return false; }
protected void warpPointerImpl(final int x, final int y) { }
protected void setPointerIconImpl(final PointerIconImpl pi) { }
//----------------------------------------------------------------------
// NativeSurface
//
@Override
public final int lockSurface() throws NativeWindowException, RuntimeException {
final RecursiveLock _wlock = windowLock;
_wlock.lock();
surfaceLockCount++;
int res = ( 1 == surfaceLockCount ) ? LOCK_SURFACE_NOT_READY : LOCK_SUCCESS; // new lock ?
if ( LOCK_SURFACE_NOT_READY == res ) {
try {
if( isNativeValid() ) {
final AbstractGraphicsDevice adevice = getGraphicsConfiguration().getScreen().getDevice();
adevice.lock();
try {
res = lockSurfaceImpl();
} finally {
if (LOCK_SURFACE_NOT_READY >= res) {
adevice.unlock();
}
}
}
} finally {
if (LOCK_SURFACE_NOT_READY >= res) {
surfaceLockCount--;
_wlock.unlock();
}
}
}
return res;
}
@Override
public final void unlockSurface() {
final RecursiveLock _wlock = windowLock;
_wlock.validateLocked();
if ( 1 == surfaceLockCount ) {
final AbstractGraphicsDevice adevice = getGraphicsConfiguration().getScreen().getDevice();
try {
unlockSurfaceImpl();
} finally {
adevice.unlock();
}
}
surfaceLockCount--;
_wlock.unlock();
}
@Override
public final boolean isSurfaceLockedByOtherThread() {
return windowLock.isLockedByOtherThread();
}
@Override
public final Thread getSurfaceLockOwner() {
return windowLock.getOwner();
}
public final RecursiveLock getLock() {
return windowLock;
}
@Override
public long getSurfaceHandle() {
return windowHandle; // default: return window handle
}
@Override
public boolean surfaceSwap() {
return false;
}
@Override
public final void addSurfaceUpdatedListener(final SurfaceUpdatedListener l) {
surfaceUpdatedHelper.addSurfaceUpdatedListener(l);
}
@Override
public final void addSurfaceUpdatedListener(final int index, final SurfaceUpdatedListener l) throws IndexOutOfBoundsException {
surfaceUpdatedHelper.addSurfaceUpdatedListener(index, l);
}
@Override
public final void removeSurfaceUpdatedListener(final SurfaceUpdatedListener l) {
surfaceUpdatedHelper.removeSurfaceUpdatedListener(l);
}
@Override
public final void surfaceUpdated(final Object updater, final NativeSurface ns, final long when) {
surfaceUpdatedHelper.surfaceUpdated(updater, ns, when);
}
@Override
public final AbstractGraphicsConfiguration getGraphicsConfiguration() {
return config.getNativeGraphicsConfiguration();
}
@Override
public final long getDisplayHandle() {
return config.getNativeGraphicsConfiguration().getScreen().getDevice().getHandle();
}
@Override
public final int getScreenIndex() {
return screen.getIndex();
}
//----------------------------------------------------------------------
// NativeWindow
//
// public final void destroy() - see below
@Override
public final NativeSurface getNativeSurface() { return this; }
@Override
public final NativeWindow getParent() {
return parentWindow;
}
@Override
public final long getWindowHandle() {
return windowHandle;
}
@Override
public Point getLocationOnScreen(Point storage) {
if(isNativeValid()) {
Point d;
final RecursiveLock _lock = windowLock;
_lock.lock();
try {
d = getLocationOnScreenImpl(0, 0);
} finally {
_lock.unlock();
}
if(null!=d) {
if(null!=storage) {
storage.translate(d.getX(),d.getY());
return storage;
}
return d;
}
// fall through intended ..
}
if(null!=storage) {
storage.translate(getX(),getY());
} else {
storage = new Point(getX(),getY());
}
if(null!=parentWindow) {
// traverse through parent list ..
parentWindow.getLocationOnScreen(storage);
}
return storage;
}
//----------------------------------------------------------------------
// Window
//
@Override
public final boolean isNativeValid() {
return 0 != windowHandle ;
}
@Override
public final Screen getScreen() {
return screen;
}
protected void setScreen(final ScreenImpl newScreen) { // never null !
removeScreenReference();
screen = newScreen;
}
@Override
public final MonitorDevice getMainMonitor() {
return screen.getMainMonitor( getBounds() );
}
/**
* @param visible
* @param x client-area position in window units, or <0 if unchanged
* @param y client-area position in window units, or <0 if unchanged
* @param width client-area size in window units, or <=0 if unchanged
* @param height client-area size in window units, or <=0 if unchanged
*/
protected final void setVisibleImpl(final boolean visible, final int x, final int y, final int width, final int height) {
reconfigureWindowImpl(x, y, width, height, getReconfigureFlags(FLAG_CHANGE_VISIBILITY, visible));
}
final void setVisibleActionImpl(final boolean visible) {
boolean nativeWindowCreated = false;
boolean madeVisible = false;
final RecursiveLock _lock = windowLock;
_lock.lock();
try {
if(!visible && null!=childWindows && childWindows.size()>0) {
synchronized(childWindowsLock) {
for(int i = 0; i < childWindows.size(); i++ ) {
final NativeWindow nw = childWindows.get(i);
if(nw instanceof WindowImpl) {
((WindowImpl)nw).setVisible(false);
}
}
}
}
if(!isNativeValid() && visible) {
if( 00) {
synchronized(childWindowsLock) {
for(int i = 0; i < childWindows.size(); i++ ) {
final NativeWindow nw = childWindows.get(i);
if(nw instanceof WindowImpl) {
((WindowImpl)nw).setVisible(true);
}
}
}
}
if(DEBUG_IMPLEMENTATION) {
System.err.println("Window setVisible: END ("+getThreadName()+") "+getX()+"/"+getY()+" "+getWidth()+"x"+getHeight()+", fs "+fullscreen+", windowHandle "+toHexString(windowHandle)+", visible: "+WindowImpl.this.visible+", nativeWindowCreated: "+nativeWindowCreated+", madeVisible: "+madeVisible);
}
} finally {
if(null!=lifecycleHook) {
lifecycleHook.resetCounter();
}
_lock.unlock();
}
if( nativeWindowCreated || madeVisible ) {
sendWindowEvent(WindowEvent.EVENT_WINDOW_RESIZED); // trigger a resize/relayout and repaint to listener
}
}
private class VisibleAction implements Runnable {
boolean visible;
private VisibleAction(final boolean visible) {
this.visible = visible;
}
@Override
public final void run() {
setVisibleActionImpl(visible);
}
}
@Override
public final void setVisible(final boolean wait, final boolean visible) {
if(DEBUG_IMPLEMENTATION) {
System.err.println("Window setVisible: START ("+getThreadName()+") "+getX()+"/"+getY()+" "+getWidth()+"x"+getHeight()+", fs "+fullscreen+", windowHandle "+toHexString(windowHandle)+", visible: "+this.visible+" -> "+visible+", parentWindowHandle "+toHexString(parentWindowHandle)+", parentWindow "+(null!=parentWindow));
}
runOnEDTIfAvail(wait, new VisibleAction(visible));
}
@Override
public final void setVisible(final boolean visible) {
setVisible(true, visible);
}
private class SetSizeAction implements Runnable {
int width, height;
boolean force;
private SetSizeAction(final int w, final int h, final boolean disregardFS) {
this.width = w;
this.height = h;
this.force = disregardFS;
}
@Override
public final void run() {
final RecursiveLock _lock = windowLock;
_lock.lock();
try {
if ( force || ( !isFullscreen() && ( getWidth() != width || getHeight() != height ) ) ) {
if(DEBUG_IMPLEMENTATION) {
System.err.println("Window setSize: START force "+force+", "+getWidth()+"x"+getHeight()+" -> "+width+"x"+height+", fs "+fullscreen+", windowHandle "+toHexString(windowHandle)+", visible "+visible);
}
int visibleAction; // 0 nop, 1 invisible, 2 visible (create)
if ( visible && isNativeValid() && ( 0 >= width || 0 >= height ) ) {
visibleAction=1; // invisible
defineSize(0, 0);
} else if ( visible && !isNativeValid() && 0 < width && 0 < height ) {
visibleAction = 2; // visible (create)
defineSize(width, height);
} else if ( visible && isNativeValid() ) {
visibleAction = 0;
// this width/height will be set by windowChanged, called by the native implementation
reconfigureWindowImpl(getX(), getY(), width, height, getReconfigureFlags(0, isVisible()));
WindowImpl.this.waitForSize(width, height, false, TIMEOUT_NATIVEWINDOW);
} else {
// invisible or invalid w/ 0 size
visibleAction = 0;
defineSize(width, height);
}
if(DEBUG_IMPLEMENTATION) {
System.err.println("Window setSize: END "+getWidth()+"x"+getHeight()+", visibleAction "+visibleAction);
}
switch(visibleAction) {
case 1: setVisibleActionImpl(false); break;
case 2: setVisibleActionImpl(true); break;
}
}
} finally {
_lock.unlock();
}
}
}
private void setSize(final int width, final int height, final boolean force) {
runOnEDTIfAvail(true, new SetSizeAction(width, height, force));
}
@Override
public final void setSize(final int width, final int height) {
runOnEDTIfAvail(true, new SetSizeAction(width, height, false));
}
@Override
public final void setSurfaceSize(final int pixelWidth, final int pixelHeight) {
setSize( SurfaceScaleUtils.scaleInv(pixelWidth, getPixelScaleX()),
SurfaceScaleUtils.scaleInv(pixelHeight, getPixelScaleY()) );
}
@Override
public final void setTopLevelSize(final int width, final int height) {
setSize(width - getInsets().getTotalWidth(), height - getInsets().getTotalHeight());
}
private final Runnable destroyAction = new Runnable() {
@Override
public final void run() {
boolean animatorPaused = false;
if(null!=lifecycleHook) {
animatorPaused = lifecycleHook.pauseRenderingAction();
}
if(null!=lifecycleHook) {
lifecycleHook.destroyActionPreLock();
}
RuntimeException lifecycleCaughtInLock = null;
final RecursiveLock _lock = windowLock;
_lock.lock();
try {
if(DEBUG_IMPLEMENTATION) {
System.err.println("Window DestroyAction() hasScreen "+(null != screen)+", isNativeValid "+isNativeValid()+" - "+getThreadName());
}
// send synced destroy-notify notification
sendWindowEvent(WindowEvent.EVENT_WINDOW_DESTROY_NOTIFY);
// Childs first ..
synchronized(childWindowsLock) {
if(childWindows.size()>0) {
// avoid ConcurrentModificationException: parent -> child -> parent.removeChild(this)
@SuppressWarnings("unchecked")
final ArrayList clonedChildWindows = (ArrayList) childWindows.clone();
while( clonedChildWindows.size() > 0 ) {
final NativeWindow nw = clonedChildWindows.remove(0);
if(nw instanceof WindowImpl) {
((WindowImpl)nw).windowDestroyNotify(true);
} else {
nw.destroy();
}
}
}
}
if(null!=lifecycleHook) {
// send synced destroy notification for proper cleanup, eg GLWindow/OpenGL
try {
lifecycleHook.destroyActionInLock();
} catch (final RuntimeException re) {
lifecycleCaughtInLock = re;
}
}
if( isNativeValid() ) {
screen.removeMonitorModeListener(monitorModeListenerImpl);
closeNativeImpl();
final AbstractGraphicsDevice cfgADevice = config.getScreen().getDevice();
if( cfgADevice != screen.getDisplay().getGraphicsDevice() ) { // don't pull display's device
cfgADevice.close(); // ensure a cfg's device is closed
}
setGraphicsConfiguration(null);
}
removeScreenReference();
final Display dpy = screen.getDisplay();
if(null != dpy) {
dpy.validateEDTStopped();
}
// send synced destroyed notification
sendWindowEvent(WindowEvent.EVENT_WINDOW_DESTROYED);
if(DEBUG_IMPLEMENTATION) {
System.err.println("Window.destroy() END "+getThreadName()/*+", "+WindowImpl.this*/);
if( null != lifecycleCaughtInLock ) {
System.err.println("Window.destroy() caught: "+lifecycleCaughtInLock.getMessage());
lifecycleCaughtInLock.printStackTrace();
}
}
if( null != lifecycleCaughtInLock ) {
throw lifecycleCaughtInLock;
}
} finally {
// update states before release window lock
setWindowHandle(0);
visible = false;
fullscreen = false;
fullscreenMonitors = null;
fullscreenUseMainMonitor = true;
hasFocus = false;
parentWindowHandle = 0;
hasPixelScale[0] = ScalableSurface.IDENTITY_PIXELSCALE;
hasPixelScale[1] = ScalableSurface.IDENTITY_PIXELSCALE;
minPixelScale[0] = ScalableSurface.IDENTITY_PIXELSCALE;
minPixelScale[1] = ScalableSurface.IDENTITY_PIXELSCALE;
maxPixelScale[0] = ScalableSurface.IDENTITY_PIXELSCALE;
maxPixelScale[1] = ScalableSurface.IDENTITY_PIXELSCALE;
_lock.unlock();
}
if(animatorPaused) {
lifecycleHook.resumeRenderingAction();
}
// these refs shall be kept alive - resurrection via setVisible(true)
/**
if(null!=parentWindow && parentWindow instanceof Window) {
((Window)parentWindow).removeChild(WindowImpl.this);
}
childWindows = null;
surfaceUpdatedListeners = null;
mouseListeners = null;
keyListeners = null;
capsRequested = null;
lifecycleHook = null;
screen = null;
windowListeners = null;
parentWindow = null;
*/
} };
@Override
public void destroy() {
visible = false; // Immediately mark synchronized visibility flag, avoiding possible recreation
runOnEDTIfAvail(true, destroyAction);
}
protected void destroy(final boolean preserveResources) {
if( null != lifecycleHook ) {
lifecycleHook.preserveGLStateAtDestroy( preserveResources );
}
destroy();
}
/**
* @param cWin child window, must not be null
* @param pWin parent window, may be null
* @return true if at least one of both window's configurations is offscreen
*/
protected static boolean isOffscreenInstance(final NativeWindow cWin, final NativeWindow pWin) {
boolean ofs = false;
final AbstractGraphicsConfiguration cWinCfg = cWin.getGraphicsConfiguration();
if( null != cWinCfg ) {
ofs = !cWinCfg.getChosenCapabilities().isOnscreen();
}
if( !ofs && null != pWin ) {
final AbstractGraphicsConfiguration pWinCfg = pWin.getGraphicsConfiguration();
if( null != pWinCfg ) {
ofs = !pWinCfg.getChosenCapabilities().isOnscreen();
}
}
return ofs;
}
private class ReparentAction implements Runnable {
final NativeWindow newParentWindow;
final int topLevelX, topLevelY;
final int hints;
ReparentOperation operation;
private ReparentAction(final NativeWindow newParentWindow, final int topLevelX, final int topLevelY, int hints) {
this.newParentWindow = newParentWindow;
this.topLevelX = topLevelX;
this.topLevelY = topLevelY;
if( DEBUG_TEST_REPARENT_INCOMPATIBLE ) {
hints |= REPARENT_HINT_FORCE_RECREATION;
}
this.hints = hints;
this.operation = ReparentOperation.ACTION_INVALID; // ensure it's set
}
private ReparentOperation getOp() {
return operation;
}
@Override
public final void run() {
if( WindowImpl.this.isFullscreen() ) {
// Bug 924: Ignore reparent when in fullscreen - otherwise may confuse WM
if( DEBUG_IMPLEMENTATION) {
System.err.println("Window.reparent: NOP (in fullscreen, "+getThreadName()+") valid "+isNativeValid()+
", windowHandle "+toHexString(windowHandle)+" parentWindowHandle "+toHexString(parentWindowHandle));
}
return;
}
boolean animatorPaused = false;
if(null!=lifecycleHook) {
animatorPaused = lifecycleHook.pauseRenderingAction();
}
reparent();
if(animatorPaused) {
lifecycleHook.resumeRenderingAction();
}
}
private void reparent() {
// mirror pos/size so native change notification can get overwritten
final int oldX = getX();
final int oldY = getY();
final int oldWidth = getWidth();
final int oldHeight = getHeight();
final int x, y;
int width = oldWidth;
int height = oldHeight;
final boolean wasVisible;
final boolean becomesVisible;
final boolean forceDestroyCreate;
final RecursiveLock _lock = windowLock;
_lock.lock();
try {
{
boolean v = 0 != ( REPARENT_HINT_FORCE_RECREATION & hints );
if(isNativeValid()) {
// force recreation if offscreen, since it may become onscreen
v |= isOffscreenInstance(WindowImpl.this, newParentWindow);
}
forceDestroyCreate = v;
}
wasVisible = isVisible();
becomesVisible = wasVisible || 0 != ( REPARENT_HINT_BECOMES_VISIBLE & hints );
Window newParentWindowNEWT = null;
if(newParentWindow instanceof Window) {
newParentWindowNEWT = (Window) newParentWindow;
}
long newParentWindowHandle = 0 ;
if( DEBUG_IMPLEMENTATION) {
System.err.println("Window.reparent: START ("+getThreadName()+") valid "+isNativeValid()+
", windowHandle "+toHexString(windowHandle)+" parentWindowHandle "+toHexString(parentWindowHandle)+
", visible "+wasVisible+", becomesVisible "+becomesVisible+
", forceDestroyCreate "+forceDestroyCreate+
", DEBUG_TEST_REPARENT_INCOMPATIBLE "+DEBUG_TEST_REPARENT_INCOMPATIBLE+
", HINT_FORCE_RECREATION "+( 0 != ( REPARENT_HINT_FORCE_RECREATION & hints ) )+
", HINT_BECOMES_VISIBLE "+( 0 != ( REPARENT_HINT_BECOMES_VISIBLE & hints ) ) +
", old parentWindow: "+Display.hashCodeNullSafe(parentWindow)+
", new parentWindow: "+Display.hashCodeNullSafe(newParentWindow) );
}
if(null!=newParentWindow) {
// REPARENT TO CHILD WINDOW
// reset position to 0/0 within parent space
x = 0;
y = 0;
// refit if size is bigger than parent
if( width > newParentWindow.getWidth() ) {
width = newParentWindow.getWidth();
}
if( height > newParentWindow.getHeight() ) {
height = newParentWindow.getHeight();
}
// Case: Child Window
newParentWindowHandle = getNativeWindowHandle(newParentWindow);
if(0 == newParentWindowHandle) {
// Case: Parent's native window not realized yet
if(null==newParentWindowNEWT) {
throw new NativeWindowException("Reparenting with non NEWT Window type only available after it's realized: "+newParentWindow);
}
// Destroy this window and use parent's Screen.
// It may be created properly when the parent is made visible.
destroy( becomesVisible );
setScreen( (ScreenImpl) newParentWindowNEWT.getScreen() );
operation = ReparentOperation.ACTION_NATIVE_CREATION_PENDING;
} else if(newParentWindow != getParent()) {
// Case: Parent's native window realized and changed
if( !isNativeValid() ) {
// May create a new compatible Screen/Display and
// mark it for creation.
if(null!=newParentWindowNEWT) {
setScreen( (ScreenImpl) newParentWindowNEWT.getScreen() );
} else {
final Screen newScreen = NewtFactory.createCompatibleScreen(newParentWindow, screen);
if( screen != newScreen ) {
// auto destroy on-the-fly created Screen/Display
setScreen( (ScreenImpl) newScreen );
}
}
if( 0 < width && 0 < height ) {
operation = ReparentOperation.ACTION_NATIVE_CREATION;
} else {
operation = ReparentOperation.ACTION_NATIVE_CREATION_PENDING;
}
} else if ( forceDestroyCreate || !NewtFactory.isScreenCompatible(newParentWindow, screen) ) {
// Destroy this window, may create a new compatible Screen/Display, while trying to preserve resources if becoming visible again.
destroy( becomesVisible );
if(null!=newParentWindowNEWT) {
setScreen( (ScreenImpl) newParentWindowNEWT.getScreen() );
} else {
setScreen( (ScreenImpl) NewtFactory.createCompatibleScreen(newParentWindow, screen) );
}
operation = ReparentOperation.ACTION_NATIVE_CREATION;
} else {
// Mark it for native reparenting
operation = ReparentOperation.ACTION_NATIVE_REPARENTING;
}
} else {
// Case: Parent's native window realized and not changed
operation = ReparentOperation.ACTION_NOP;
}
} else {
// REPARENT TO TOP-LEVEL WINDOW
if( 0 <= topLevelX && 0 <= topLevelY ) {
x = topLevelX;
y = topLevelY;
} else if( null != parentWindow ) {
// child -> top
// put client to current parent+child position
final Point p = getLocationOnScreen(null);
x = p.getX();
y = p.getY();
} else {
x = oldX;
y = oldY;
}
// Case: Top Window
if( 0 == parentWindowHandle ) {
// Already Top Window
operation = ReparentOperation.ACTION_NOP;
} else if( !isNativeValid() || forceDestroyCreate ) {
// Destroy this window and mark it for [pending] creation.
// If isNativeValid() and becoming visible again - try to preserve resources, i.e. b/c on-/offscreen switch.
destroy( becomesVisible );
if( 0 < width && 0 < height ) {
operation = ReparentOperation.ACTION_NATIVE_CREATION;
} else {
operation = ReparentOperation.ACTION_NATIVE_CREATION_PENDING;
}
} else {
// Mark it for native reparenting
operation = ReparentOperation.ACTION_NATIVE_REPARENTING;
}
}
parentWindowHandle = newParentWindowHandle;
if ( ReparentOperation.ACTION_INVALID == operation ) {
throw new NativeWindowException("Internal Error: reparentAction not set");
}
if(DEBUG_IMPLEMENTATION) {
System.err.println("Window.reparent: ACTION ("+getThreadName()+") windowHandle "+toHexString(windowHandle)+" new parentWindowHandle "+toHexString(newParentWindowHandle)+", reparentAction "+operation+", pos/size "+x+"/"+y+" "+width+"x"+height+", visible "+wasVisible);
}
if( ReparentOperation.ACTION_NOP == operation ) {
return;
}
if( null == newParentWindow ) {
// CLIENT -> TOP: Reset Parent's Pointer State
setOffscreenPointerIcon(null);
setOffscreenPointerVisible(true, null);
}
// rearrange window tree
if(null!=parentWindow && parentWindow instanceof Window) {
((Window)parentWindow).removeChild(WindowImpl.this);
}
parentWindow = newParentWindow;
if(parentWindow instanceof Window) {
((Window)parentWindow).addChild(WindowImpl.this);
}
if( ReparentOperation.ACTION_NATIVE_REPARENTING == operation ) {
final DisplayImpl display = (DisplayImpl) screen.getDisplay();
display.dispatchMessagesNative(); // status up2date
// TOP -> CLIENT: !visible first (fixes X11 unsuccessful return to parent window)
if( null != parentWindow && wasVisible && NativeWindowFactory.TYPE_X11 == NativeWindowFactory.getNativeWindowType(true) ) {
setVisibleImpl(false, oldX, oldY, oldWidth, oldHeight);
WindowImpl.this.waitForVisible(false, false);
// FIXME: Some composite WM behave slacky .. give 'em chance to change state -> invisible,
// even though we do exactly that (KDE+Composite)
try { Thread.sleep(100); } catch (final InterruptedException e) { }
display.dispatchMessagesNative(); // status up2date
}
// Lock parentWindow only during reparenting (attempt)
final NativeWindow parentWindowLocked;
if( null != parentWindow ) {
parentWindowLocked = parentWindow;
if( NativeSurface.LOCK_SURFACE_NOT_READY >= parentWindowLocked.lockSurface() ) {
throw new NativeWindowException("Parent surface lock: not ready: "+parentWindowLocked);
}
// update native handle, locked state
parentWindowHandle = parentWindowLocked.getWindowHandle();
} else {
parentWindowLocked = null;
}
boolean ok = false;
try {
ok = reconfigureWindowImpl(x, y, width, height, getReconfigureFlags(FLAG_CHANGE_PARENTING | FLAG_CHANGE_DECORATION, isVisible()));
} finally {
if(null!=parentWindowLocked) {
parentWindowLocked.unlockSurface();
}
}
definePosition(x, y); // position might not get updated by WM events (SWT parent apparently)
// set visible again
if(ok) {
display.dispatchMessagesNative(); // status up2date
if(wasVisible) {
setVisibleImpl(true, x, y, width, height);
ok = 0 <= WindowImpl.this.waitForVisible(true, false);
if(ok) {
if( isAlwaysOnTop() && 0 == parentWindowHandle && NativeWindowFactory.TYPE_X11 == NativeWindowFactory.getNativeWindowType(true) ) {
// Reinforce ALWAYSONTOP when CHILD -> TOP reparenting, since reparenting itself cause X11 WM to loose it's state.
reconfigureWindowImpl(x, y, width, height, getReconfigureFlags(FLAG_CHANGE_ALWAYSONTOP, isVisible()));
}
ok = WindowImpl.this.waitForSize(width, height, false, TIMEOUT_NATIVEWINDOW);
}
if(ok) {
if( 0 == parentWindowHandle ) {
// Position mismatch shall not lead to reparent failure
WindowImpl.this.waitForPosition(true, x, y, TIMEOUT_NATIVEWINDOW);
}
requestFocusInt( 0 == parentWindowHandle /* skipFocusAction if top-level */);
display.dispatchMessagesNative(); // status up2date
}
}
}
if(!ok || !wasVisible) {
// make size and position persistent manual,
// since we don't have a WM feedback (invisible or recreation)
definePosition(x, y);
defineSize(width, height);
}
if(!ok) {
// native reparent failed -> try creation, while trying to preserve resources if becoming visible again.
if(DEBUG_IMPLEMENTATION) {
System.err.println("Window.reparent: native reparenting failed ("+getThreadName()+") windowHandle "+toHexString(windowHandle)+" parentWindowHandle "+toHexString(parentWindowHandle)+" -> "+toHexString(newParentWindowHandle)+" - Trying recreation");
}
destroy( becomesVisible );
operation = ReparentOperation.ACTION_NATIVE_CREATION ;
} else {
if( null != parentWindow ) {
// TOP -> CLIENT: Setup Parent's Pointer State
setOffscreenPointerIcon(pointerIcon);
setOffscreenPointerVisible(pointerVisible, pointerIcon);
}
}
} else {
// Case
// ACTION_NATIVE_CREATION
// ACTION_NATIVE_CREATION_PENDING;
// make size and position persistent for proper [re]creation
definePosition(x, y);
defineSize(width, height);
}
if(DEBUG_IMPLEMENTATION) {
System.err.println("Window.reparent: END-1 ("+getThreadName()+") windowHandle "+toHexString(windowHandle)+
", visible: "+visible+", parentWindowHandle "+toHexString(parentWindowHandle)+
", parentWindow "+ Display.hashCodeNullSafe(parentWindow)+" "+
getX()+"/"+getY()+" "+getWidth()+"x"+getHeight());
}
} finally {
if(null!=lifecycleHook) {
lifecycleHook.resetCounter();
}
_lock.unlock();
}
if(wasVisible) {
switch (operation) {
case ACTION_NATIVE_REPARENTING:
// trigger a resize/relayout and repaint to listener
sendWindowEvent(WindowEvent.EVENT_WINDOW_RESIZED);
break;
case ACTION_NATIVE_CREATION:
// This may run on the new Display/Screen connection, hence a new EDT task
runOnEDTIfAvail(true, reparentActionRecreate);
break;
default:
}
}
if(DEBUG_IMPLEMENTATION) {
System.err.println("Window.reparent: END-X ("+getThreadName()+") windowHandle "+toHexString(windowHandle)+
", visible: "+visible+", parentWindowHandle "+toHexString(parentWindowHandle)+
", parentWindow "+ Display.hashCodeNullSafe(parentWindow)+" "+
getX()+"/"+getY()+" "+getWidth()+"x"+getHeight());
}
}
}
private final Runnable reparentActionRecreate = new Runnable() {
@Override
public final void run() {
final RecursiveLock _lock = windowLock;
_lock.lock();
try {
if(DEBUG_IMPLEMENTATION) {
System.err.println("Window.reparent: ReparentActionRecreate ("+getThreadName()+") windowHandle "+toHexString(windowHandle)+", visible: "+visible+", parentWindowHandle "+toHexString(parentWindowHandle)+", parentWindow "+Display.hashCodeNullSafe(parentWindow));
}
setVisibleActionImpl(true); // native creation
requestFocusInt( 0 == parentWindowHandle /* skipFocusAction if top-level */);
} finally {
_lock.unlock();
}
} };
@Override
public final ReparentOperation reparentWindow(final NativeWindow newParent, final int x, final int y, final int hints) {
final ReparentAction reparentAction = new ReparentAction(newParent, x, y, hints);
runOnEDTIfAvail(true, reparentAction);
return reparentAction.getOp();
}
@Override
public final CapabilitiesChooser setCapabilitiesChooser(final CapabilitiesChooser chooser) {
final CapabilitiesChooser old = this.capabilitiesChooser;
this.capabilitiesChooser = chooser;
return old;
}
@Override
public final CapabilitiesImmutable getChosenCapabilities() {
return getGraphicsConfiguration().getChosenCapabilities();
}
@Override
public final CapabilitiesImmutable getRequestedCapabilities() {
return capsRequested;
}
private class DecorationAction implements Runnable {
boolean undecorated;
private DecorationAction(final boolean undecorated) {
this.undecorated = undecorated;
}
@Override
public final void run() {
final RecursiveLock _lock = windowLock;
_lock.lock();
try {
if(WindowImpl.this.undecorated != undecorated) {
// set current state
WindowImpl.this.undecorated = undecorated;
if( isNativeValid() && !isFullscreen() ) {
// Mirror pos/size so native change notification can get overwritten
final int x = getX();
final int y = getY();
final int width = getWidth();
final int height = getHeight();
final DisplayImpl display = (DisplayImpl) screen.getDisplay();
display.dispatchMessagesNative(); // status up2date
reconfigureWindowImpl(x, y, width, height, getReconfigureFlags(FLAG_CHANGE_DECORATION, isVisible()));
display.dispatchMessagesNative(); // status up2date
}
}
} finally {
_lock.unlock();
}
sendWindowEvent(WindowEvent.EVENT_WINDOW_RESIZED); // trigger a resize/relayout and repaint to listener
}
}
@Override
public final void setUndecorated(final boolean value) {
runOnEDTIfAvail(true, new DecorationAction(value));
}
@Override
public final boolean isUndecorated() {
return 0 != parentWindowHandle || undecorated || fullscreen ;
}
private class AlwaysOnTopAction implements Runnable {
boolean alwaysOnTop;
private AlwaysOnTopAction(final boolean alwaysOnTop) {
this.alwaysOnTop = alwaysOnTop;
}
@Override
public final void run() {
final RecursiveLock _lock = windowLock;
_lock.lock();
try {
if(WindowImpl.this.alwaysOnTop != alwaysOnTop) {
// set current state
WindowImpl.this.alwaysOnTop = alwaysOnTop;
if( isNativeValid() ) {
// Mirror pos/size so native change notification can get overwritten
final int x = getX();
final int y = getY();
final int width = getWidth();
final int height = getHeight();
final DisplayImpl display = (DisplayImpl) screen.getDisplay();
display.dispatchMessagesNative(); // status up2date
reconfigureWindowImpl(x, y, width, height, getReconfigureFlags(FLAG_CHANGE_ALWAYSONTOP, isVisible()));
display.dispatchMessagesNative(); // status up2date
}
}
} finally {
_lock.unlock();
}
sendWindowEvent(WindowEvent.EVENT_WINDOW_RESIZED); // trigger a resize/relayout and repaint to listener
}
}
@Override
public final void setAlwaysOnTop(final boolean value) {
if( isFullscreen() ) {
nfs_alwaysOnTop = value;
} else {
runOnEDTIfAvail(true, new AlwaysOnTopAction(value));
}
}
@Override
public final boolean isAlwaysOnTop() {
return alwaysOnTop;
}
@Override
public final String getTitle() {
return title;
}
@Override
public final void setTitle(String title) {
if (title == null) {
title = "";
}
this.title = title;
if(0 != getWindowHandle()) {
setTitleImpl(title);
}
}
@Override
public final boolean isPointerVisible() {
return pointerVisible;
}
@Override
public final void setPointerVisible(final boolean pointerVisible) {
if(this.pointerVisible != pointerVisible) {
boolean setVal = 0 == getWindowHandle();
if(!setVal) {
setVal = setPointerVisibleIntern(pointerVisible);
}
if(setVal) {
this.pointerVisible = pointerVisible;
}
}
}
private boolean setPointerVisibleIntern(final boolean pointerVisible) {
final boolean res = setOffscreenPointerVisible(pointerVisible, pointerIcon);
return setPointerVisibleImpl(pointerVisible) || res; // accept onscreen or offscreen positive result!
}
/**
* Helper method to delegate {@link #setPointerVisibleImpl(boolean)} to
* {@link OffscreenLayerSurface#hideCursor()} or {@link OffscreenLayerSurface#setCursor(PixelRectangle, PointImmutable)}.
*
* Note: JAWTWindow is an OffscreenLayerSurface.
*
*
* Performing OffscreenLayerSurface's setCursor(..)/hideCursor(), if available,
* gives same behavior on all platforms.
*
*
* If visible, implementation invokes {@link #setOffscreenPointerIcon(OffscreenLayerSurface, PointerIconImpl)} using the
* given defaultPointerIcon
, otherwise {@link OffscreenLayerSurface#hideCursor()} is invoked.
*
* @param pointerVisible true for visible, otherwise invisible.
* @param defaultPointerIcon default PointerIcon for visibility
* @param ols the {@link OffscreenLayerSurface} instance, if null method does nothing.
*/
private boolean setOffscreenPointerVisible(final boolean pointerVisible, final PointerIconImpl defaultPointerIcon) {
if( pointerVisible ) {
return setOffscreenPointerIcon(defaultPointerIcon);
} else {
final NativeWindow parent = getParent();
if( parent instanceof OffscreenLayerSurface ) {
final OffscreenLayerSurface ols = (OffscreenLayerSurface) parent;
try {
return ols.hideCursor();
} catch (final Exception e) {
e.printStackTrace();
}
}
}
return false;
}
@Override
public final PointerIcon getPointerIcon() { return pointerIcon; }
@Override
public final void setPointerIcon(final PointerIcon pi) {
final PointerIconImpl piImpl = (PointerIconImpl)pi;
if( this.pointerIcon != piImpl ) {
if( isNativeValid() ) {
runOnEDTIfAvail(true, new Runnable() {
public void run() {
setPointerIconIntern(piImpl);
} } );
}
this.pointerIcon = piImpl;
}
}
private void setPointerIconIntern(final PointerIconImpl pi) {
setOffscreenPointerIcon(pi);
setPointerIconImpl(pi);
}
/**
* Helper method to delegate {@link #setPointerIconIntern(PointerIconImpl)} to
* {@link OffscreenLayerSurface#setCursor(PixelRectangle, PointImmutable)}
*
* Note: JAWTWindow is an OffscreenLayerSurface.
*
*
* Performing OffscreenLayerSurface's setCursor(..), if available,
* gives same behavior on all platforms.
*
*
* Workaround for AWT/Windows bug within browser,
* where the PointerIcon gets periodically overridden
* by the AWT Component's icon.
*
* @param ols the {@link OffscreenLayerSurface} instance, if null method does nothing.
* @param pi the {@link PointerIconImpl} instance, if null PointerIcon gets reset.
*/
private boolean setOffscreenPointerIcon(final PointerIconImpl pi) {
final NativeWindow parent = getParent();
if( parent instanceof OffscreenLayerSurface ) {
final OffscreenLayerSurface ols = (OffscreenLayerSurface) parent;
try {
if( null != pi ) {
return ols.setCursor(pi, pi.getHotspot());
} else {
return ols.setCursor(null, null); // default
}
} catch (final Exception e) {
e.printStackTrace();
}
}
return false;
}
@Override
public final boolean isPointerConfined() {
return pointerConfined;
}
@Override
public final void confinePointer(final boolean confine) {
if(this.pointerConfined != confine) {
boolean setVal = 0 == getWindowHandle();
if(!setVal) {
if(confine) {
requestFocus();
warpPointer(getSurfaceWidth()/2, getSurfaceHeight()/2);
}
setVal = confinePointerImpl(confine);
if(confine) {
// give time to deliver mouse movements w/o confinement,
// this allows user listener to sync previous position value to the new centered position
try {
Thread.sleep(3 * screen.getDisplay().getEDTUtil().getPollPeriod());
} catch (final InterruptedException e) { }
}
}
if(setVal) {
this.pointerConfined = confine;
}
}
}
@Override
public final void warpPointer(final int x, final int y) {
if(0 != getWindowHandle()) {
warpPointerImpl(x, y);
}
}
@Override
public final InsetsImmutable getInsets() {
if(isUndecorated()) {
return Insets.getZero();
}
updateInsetsImpl(insets);
return insets;
}
@Override
public final int getX() {
return x;
}
@Override
public final int getY() {
return y;
}
@Override
public final int getWidth() {
return winWidth;
}
@Override
public final int getHeight() {
return winHeight;
}
@Override
public final Rectangle getBounds() {
return new Rectangle(x, y, winWidth, winHeight);
}
@Override
public final int getSurfaceWidth() {
return pixWidth;
}
@Override
public final int getSurfaceHeight() {
return pixHeight;
}
@Override
public final int[] convertToWindowUnits(final int[] pixelUnitsAndResult) {
return SurfaceScaleUtils.scaleInv(pixelUnitsAndResult, pixelUnitsAndResult, hasPixelScale);
}
@Override
public final int[] convertToPixelUnits(final int[] windowUnitsAndResult) {
return SurfaceScaleUtils.scale(windowUnitsAndResult, windowUnitsAndResult, hasPixelScale);
}
protected final Point convertToWindowUnits(final Point pixelUnitsAndResult) {
return pixelUnitsAndResult.scaleInv(getPixelScaleX(), getPixelScaleY());
}
protected final Point convertToPixelUnits(final Point windowUnitsAndResult) {
return windowUnitsAndResult.scale(getPixelScaleX(), getPixelScaleY());
}
/** HiDPI: We currently base scaling of window units to pixel units on an integer scale factor per component. */
protected final float getPixelScaleX() {
return hasPixelScale[0];
}
/** HiDPI: We currently base scaling of window units to pixel units on an integer scale factor per component. */
protected final float getPixelScaleY() {
return hasPixelScale[1];
}
@Override
public boolean setSurfaceScale(final float[] pixelScale) {
System.arraycopy(pixelScale, 0, reqPixelScale, 0, 2);
return false;
}
@Override
public final float[] getRequestedSurfaceScale(final float[] result) {
System.arraycopy(reqPixelScale, 0, result, 0, 2);
return result;
}
@Override
public final float[] getCurrentSurfaceScale(final float[] result) {
System.arraycopy(hasPixelScale, 0, result, 0, 2);
return result;
}
@Override
public final float[] getMinimumSurfaceScale(final float[] result) {
System.arraycopy(minPixelScale, 0, result, 0, 2);
return result;
}
@Override
public final float[] getMaximumSurfaceScale(final float[] result) {
System.arraycopy(maxPixelScale, 0, result, 0, 2);
return result;
}
@Override
public final float[] getPixelsPerMM(final float[] ppmmStore) {
getMainMonitor().getPixelsPerMM(ppmmStore);
ppmmStore[0] *= hasPixelScale[0] / maxPixelScale[0];
ppmmStore[1] *= hasPixelScale[1] / maxPixelScale[1];
return ppmmStore;
}
protected final boolean autoPosition() { return autoPosition; }
/** Sets the position fields {@link #x} and {@link #y} in window units to the given values and {@link #autoPosition} to false. */
protected final void definePosition(final int x, final int y) {
if(DEBUG_IMPLEMENTATION) {
System.err.println("definePosition: "+this.x+"/"+this.y+" -> "+x+"/"+y);
// ExceptionUtils.dumpStackTrace(System.err);
}
autoPosition = false;
this.x = x; this.y = y;
}
/**
* Sets the size fields {@link #winWidth} and {@link #winHeight} in window units to the given values
* and {@link #pixWidth} and {@link #pixHeight} in pixel units according to {@link #convertToPixelUnits(int[])}.
*/
protected final void defineSize(final int winWidth, final int winHeight) {
final int pixWidth = SurfaceScaleUtils.scale(winWidth, getPixelScaleX()); // FIXME HiDPI: Shortcut, may need to adjust if we change scaling methodology
final int pixHeight = SurfaceScaleUtils.scale(winHeight, getPixelScaleY());
if(DEBUG_IMPLEMENTATION) {
System.err.println("defineSize: win["+this.winWidth+"x"+this.winHeight+" -> "+winWidth+"x"+winHeight+
"], pixel["+this.pixWidth+"x"+this.pixHeight+" -> "+pixWidth+"x"+pixHeight+"]");
// ExceptionUtils.dumpStackTrace(System.err);
}
this.winWidth = winWidth; this.winHeight = winHeight;
this.pixWidth = pixWidth; this.pixHeight = pixHeight;
}
@Override
public final boolean isVisible() {
return visible;
}
@Override
public final boolean isFullscreen() {
return fullscreen;
}
//----------------------------------------------------------------------
// Window
//
@Override
public final Window getDelegatedWindow() {
return this;
}
//----------------------------------------------------------------------
// WindowImpl
//
/**
* If the implementation is capable of detecting a device change
* return true and clear the status/reason of the change.
*/
public boolean hasDeviceChanged() {
return false;
}
public final LifecycleHook getLifecycleHook() {
return lifecycleHook;
}
public final LifecycleHook setLifecycleHook(final LifecycleHook hook) {
final LifecycleHook old = lifecycleHook;
lifecycleHook = hook;
return old;
}
/**
* If this Window actually wraps a {@link NativeSurface} from another instance or toolkit,
* it will return such reference. Otherwise returns null.
*/
public NativeSurface getWrappedSurface() {
return null;
}
@Override
public final void setWindowDestroyNotifyAction(final Runnable r) {
windowDestroyNotifyAction = r;
}
protected final long getParentWindowHandle() {
return isFullscreen() ? 0 : parentWindowHandle;
}
@Override
public final String toString() {
final StringBuilder sb = new StringBuilder();
sb.append(getClass().getName()+"[Config "+config+
",\n "+screen+
",\n ParentWindow "+parentWindow+
",\n ParentWindowHandle "+toHexString(parentWindowHandle)+" ("+(0!=getParentWindowHandle())+")"+
",\n WindowHandle "+toHexString(getWindowHandle())+
",\n SurfaceHandle "+toHexString(getSurfaceHandle())+ " (lockedExt window "+windowLock.isLockedByOtherThread()+", surface "+isSurfaceLockedByOtherThread()+")"+
",\n window["+getX()+"/"+getY()+" (auto "+autoPosition()+") "+getWidth()+"x"+getHeight()+"], pixel["+getSurfaceWidth()+"x"+getSurfaceHeight()+
"],\n Visible "+isVisible()+", focus "+hasFocus()+
",\n Undecorated "+undecorated+" ("+isUndecorated()+")"+
",\n AlwaysOnTop "+alwaysOnTop+", Fullscreen "+fullscreen+
",\n WrappedSurface "+getWrappedSurface()+
",\n ChildWindows "+childWindows.size());
sb.append(", SurfaceUpdatedListeners num "+surfaceUpdatedHelper.size()+" [");
for (int i = 0; i < surfaceUpdatedHelper.size(); i++ ) {
sb.append(surfaceUpdatedHelper.get(i)+", ");
}
sb.append("], WindowListeners num "+windowListeners.size()+" [");
for (int i = 0; i < windowListeners.size(); i++ ) {
sb.append(windowListeners.get(i)+", ");
}
sb.append("], MouseListeners num "+mouseListeners.size()+" [");
for (int i = 0; i < mouseListeners.size(); i++ ) {
sb.append(mouseListeners.get(i)+", ");
}
sb.append("], PointerGestures default "+defaultGestureHandlerEnabled+", custom "+pointerGestureHandler.size()+" [");
for (int i = 0; i < pointerGestureHandler.size(); i++ ) {
sb.append(pointerGestureHandler.get(i)+", ");
}
sb.append("], KeyListeners num "+keyListeners.size()+" [");
for (int i = 0; i < keyListeners.size(); i++ ) {
sb.append(keyListeners.get(i)+", ");
}
sb.append("], windowLock "+windowLock+", surfaceLockCount "+surfaceLockCount+"]");
return sb.toString();
}
protected final void setWindowHandle(final long handle) {
windowHandle = handle;
}
@Override
public final void runOnEDTIfAvail(final boolean wait, final Runnable task) {
if( windowLock.isOwner( Thread.currentThread() ) ) {
task.run();
} else {
( (DisplayImpl) screen.getDisplay() ).runOnEDTIfAvail(wait, task);
}
}
private final Runnable requestFocusAction = new Runnable() {
@Override
public final void run() {
if(DEBUG_IMPLEMENTATION) {
System.err.println("Window.RequestFocusAction: force 0 - ("+getThreadName()+"): "+hasFocus+" -> true - windowHandle "+toHexString(windowHandle)+" parentWindowHandle "+toHexString(parentWindowHandle));
}
WindowImpl.this.requestFocusImpl(false);
}
};
private final Runnable requestFocusActionForced = new Runnable() {
@Override
public final void run() {
if(DEBUG_IMPLEMENTATION) {
System.err.println("Window.RequestFocusAction: force 1 - ("+getThreadName()+"): "+hasFocus+" -> true - windowHandle "+toHexString(windowHandle)+" parentWindowHandle "+toHexString(parentWindowHandle));
}
WindowImpl.this.requestFocusImpl(true);
}
};
@Override
public final boolean hasFocus() {
return hasFocus;
}
@Override
public final void requestFocus() {
requestFocus(true);
}
@Override
public final void requestFocus(final boolean wait) {
requestFocus(wait /* wait */, false /* skipFocusAction */, brokenFocusChange /* force */);
}
private void requestFocus(final boolean wait, final boolean skipFocusAction, final boolean force) {
if( isNativeValid() &&
( force || !hasFocus() ) &&
( skipFocusAction || !focusAction() ) ) {
runOnEDTIfAvail(wait, force ? requestFocusActionForced : requestFocusAction);
}
}
/** Internally forcing request focus on current thread */
private void requestFocusInt(final boolean skipFocusAction) {
if( skipFocusAction || !focusAction() ) {
if(DEBUG_IMPLEMENTATION) {
System.err.println("Window.RequestFocusInt: forcing - ("+getThreadName()+"): skipFocusAction "+skipFocusAction+", focus "+hasFocus+" -> true - windowHandle "+toHexString(windowHandle)+" parentWindowHandle "+toHexString(parentWindowHandle));
}
requestFocusImpl(true);
}
}
@Override
public final void setFocusAction(final FocusRunnable focusAction) {
this.focusAction = focusAction;
}
private boolean focusAction() {
if(DEBUG_IMPLEMENTATION) {
System.err.println("Window.focusAction() START - "+getThreadName()+", focusAction: "+focusAction+" - windowHandle "+toHexString(getWindowHandle()));
}
boolean res;
if(null!=focusAction) {
res = focusAction.run();
} else {
res = false;
}
if(DEBUG_IMPLEMENTATION) {
System.err.println("Window.focusAction() END - "+getThreadName()+", focusAction: "+focusAction+" - windowHandle "+toHexString(getWindowHandle())+", res: "+res);
}
return res;
}
protected final void setBrokenFocusChange(final boolean v) {
brokenFocusChange = v;
}
@Override
public final void setKeyboardFocusHandler(final KeyListener l) {
keyboardFocusHandler = l;
}
private class SetPositionAction implements Runnable {
int x, y;
private SetPositionAction(final int x, final int y) {
this.x = x;
this.y = y;
}
@Override
public final void run() {
final RecursiveLock _lock = windowLock;
_lock.lock();
try {
if(DEBUG_IMPLEMENTATION) {
System.err.println("Window setPosition: "+getX()+"/"+getY()+" -> "+x+"/"+y+", fs "+fullscreen+", windowHandle "+toHexString(windowHandle));
}
// Let the window be positioned if !fullscreen and position changed or being a child window.
if ( !isFullscreen() && ( getX() != x || getY() != y || null != getParent()) ) {
if(isNativeValid()) {
// this.x/this.y will be set by sizeChanged, triggered by windowing event system
reconfigureWindowImpl(x, y, getWidth(), getHeight(), getReconfigureFlags(0, isVisible()));
if( null == parentWindow ) {
// Wait until custom position is reached within tolerances
waitForPosition(true, x, y, Window.TIMEOUT_NATIVEWINDOW);
}
} else {
definePosition(x, y); // set pos for createNative(..)
}
}
} finally {
_lock.unlock();
}
}
}
@Override
public void setPosition(final int x, final int y) {
autoPosition = false;
runOnEDTIfAvail(true, new SetPositionAction(x, y));
}
@Override
public final void setTopLevelPosition(final int x, final int y) {
setPosition(x + getInsets().getLeftWidth(), y + getInsets().getTopHeight());
}
private class FullScreenAction implements Runnable {
boolean _fullscreen;
private boolean init(final boolean fullscreen) {
if(isNativeValid()) {
this._fullscreen = fullscreen;
return isFullscreen() != fullscreen;
} else {
WindowImpl.this.fullscreen = fullscreen; // set current state for createNative(..)
return false;
}
}
public boolean fsOn() { return _fullscreen; }
@Override
public final void run() {
final RecursiveLock _lock = windowLock;
_lock.lock();
blockInsetsChange = true;
try {
final int oldX = getX();
final int oldY = getY();
final int oldWidth = getWidth();
final int oldHeight = getHeight();
int x,y,w,h;
final RectangleImmutable sviewport = screen.getViewportInWindowUnits(); // window units
final RectangleImmutable viewport; // window units
final int fs_span_flag;
final boolean alwaysOnTopChange;
if(_fullscreen) {
if( null == fullscreenMonitors ) {
if( fullscreenUseMainMonitor ) {
fullscreenMonitors = new ArrayList();
fullscreenMonitors.add( getMainMonitor() );
} else {
fullscreenMonitors = getScreen().getMonitorDevices();
}
}
{
final Rectangle viewportInWindowUnits = new Rectangle();
MonitorDevice.unionOfViewports(null, viewportInWindowUnits, fullscreenMonitors);
viewport = viewportInWindowUnits;
}
if( isReconfigureFlagSupported(FLAG_IS_FULLSCREEN_SPAN) &&
( fullscreenMonitors.size() > 1 || sviewport.compareTo(viewport) > 0 ) ) {
fs_span_flag = FLAG_IS_FULLSCREEN_SPAN;
} else {
fs_span_flag = 0;
}
nfs_x = oldX;
nfs_y = oldY;
nfs_width = oldWidth;
nfs_height = oldHeight;
nfs_alwaysOnTop = alwaysOnTop;
x = viewport.getX();
y = viewport.getY();
w = viewport.getWidth();
h = viewport.getHeight();
alwaysOnTop = false;
alwaysOnTopChange = nfs_alwaysOnTop != alwaysOnTop;
} else {
fullscreenUseMainMonitor = true;
fullscreenMonitors = null;
fs_span_flag = 0;
viewport = null;
x = nfs_x;
y = nfs_y;
w = nfs_width;
h = nfs_height;
alwaysOnTopChange = nfs_alwaysOnTop != alwaysOnTop;
alwaysOnTop = nfs_alwaysOnTop;
if(null!=parentWindow) {
// reset position to 0/0 within parent space
x = 0;
y = 0;
// refit if size is bigger than parent
if( w > parentWindow.getWidth() ) {
w = parentWindow.getWidth();
}
if( h > parentWindow.getHeight() ) {
h = parentWindow.getHeight();
}
}
}
final DisplayImpl display = (DisplayImpl) screen.getDisplay();
display.dispatchMessagesNative(); // status up2date
final boolean wasVisible = isVisible();
final boolean tempInvisible = !_fullscreen && wasVisible && NativeWindowFactory.TYPE_X11 == NativeWindowFactory.getNativeWindowType(true);
if(DEBUG_IMPLEMENTATION) {
System.err.println("Window fs: "+_fullscreen+" "+x+"/"+y+" "+w+"x"+h+", "+isUndecorated()+
", virtl-screenSize: "+sviewport+" [wu], monitorsViewport "+viewport+" [wu]"+
", spanning "+(0!=fs_span_flag)+
", alwaysOnTop "+alwaysOnTop+(alwaysOnTopChange?"*":"")+
", wasVisible "+wasVisible+", tempInvisible "+tempInvisible+
", hasParent "+(null!=parentWindow)+
" @ "+Thread.currentThread().getName());
}
// fullscreen off: !visible first (fixes X11 unsuccessful return to parent window _and_ wrong window size propagation)
if( tempInvisible ) {
setVisibleImpl(false, oldX, oldY, oldWidth, oldHeight);
WindowImpl.this.waitForVisible(false, false);
try { Thread.sleep(100); } catch (final InterruptedException e) { }
display.dispatchMessagesNative(); // status up2date
}
// Lock parentWindow only during reparenting (attempt)
final NativeWindow parentWindowLocked;
if( null != parentWindow ) {
parentWindowLocked = parentWindow;
if( NativeSurface.LOCK_SURFACE_NOT_READY >= parentWindowLocked.lockSurface() ) {
throw new NativeWindowException("Parent surface lock: not ready: "+parentWindow);
}
} else {
parentWindowLocked = null;
}
try {
if(alwaysOnTopChange && _fullscreen) {
// Enter fullscreen - Disable alwaysOnTop
reconfigureWindowImpl(oldX, oldY, oldWidth, oldHeight, getReconfigureFlags(FLAG_CHANGE_ALWAYSONTOP, isVisible()));
}
WindowImpl.this.fullscreen = _fullscreen;
reconfigureWindowImpl(x, y, w, h,
getReconfigureFlags( ( ( null != parentWindowLocked ) ? FLAG_CHANGE_PARENTING : 0 ) |
fs_span_flag | FLAG_CHANGE_FULLSCREEN | FLAG_CHANGE_DECORATION, isVisible()) );
if(alwaysOnTopChange && !_fullscreen) {
// Leave fullscreen - Restore alwaysOnTop
reconfigureWindowImpl(x, y, w, h, getReconfigureFlags(FLAG_CHANGE_ALWAYSONTOP, isVisible()));
}
} finally {
if(null!=parentWindowLocked) {
parentWindowLocked.unlockSurface();
}
}
display.dispatchMessagesNative(); // status up2date
if(wasVisible) {
if( NativeWindowFactory.TYPE_X11 == NativeWindowFactory.getNativeWindowType(true) ) {
// Give sluggy WM's (e.g. Unity) a chance to properly restore window ..
try { Thread.sleep(100); } catch (final InterruptedException e) { }
display.dispatchMessagesNative(); // status up2date
}
setVisibleImpl(true, x, y, w, h);
boolean ok = 0 <= WindowImpl.this.waitForVisible(true, false);
if(ok) {
ok = WindowImpl.this.waitForSize(w, h, false, TIMEOUT_NATIVEWINDOW);
}
if(ok && !_fullscreen && null == parentWindow) {
// Position mismatch shall not lead to fullscreen failure
WindowImpl.this.waitForPosition(true, x, y, TIMEOUT_NATIVEWINDOW);
}
if(ok) {
requestFocusInt(_fullscreen /* skipFocusAction if fullscreen */);
display.dispatchMessagesNative(); // status up2date
}
if(DEBUG_IMPLEMENTATION) {
System.err.println("Window fs done: ok " + ok + ", " + WindowImpl.this);
}
}
} finally {
blockInsetsChange = false;
_lock.unlock();
}
sendWindowEvent(WindowEvent.EVENT_WINDOW_RESIZED); // trigger a resize/relayout and repaint to listener
}
}
private final FullScreenAction fullScreenAction = new FullScreenAction();
@Override
public boolean setFullscreen(final boolean fullscreen) {
return setFullscreenImpl(fullscreen, true, null);
}
@Override
public boolean setFullscreen(final List monitors) {
return setFullscreenImpl(true, false, monitors);
}
private boolean setFullscreenImpl(final boolean fullscreen, final boolean useMainMonitor, final List monitors) {
synchronized(fullScreenAction) {
fullscreenMonitors = monitors;
fullscreenUseMainMonitor = useMainMonitor;
if( fullScreenAction.init(fullscreen) ) {
if( fullScreenAction.fsOn() && isOffscreenInstance(WindowImpl.this, parentWindow) ) {
// enable fullscreen on offscreen instance
if(null != parentWindow) {
nfs_parent = parentWindow;
reparentWindow(null, -1, -1, REPARENT_HINT_FORCE_RECREATION | REPARENT_HINT_BECOMES_VISIBLE);
} else {
throw new InternalError("Offscreen instance w/o parent unhandled");
}
}
runOnEDTIfAvail(true, fullScreenAction);
if(!fullScreenAction.fsOn() && null != nfs_parent) {
// disable fullscreen on offscreen instance
reparentWindow(nfs_parent, -1, -1, REPARENT_HINT_FORCE_RECREATION | REPARENT_HINT_BECOMES_VISIBLE);
nfs_parent = null;
}
}
return this.fullscreen;
}
}
/** Notify WindowDriver about the finished monitor mode change. */
protected void monitorModeChanged(final MonitorEvent me, final boolean success) {
}
private class MonitorModeListenerImpl implements MonitorModeListener {
boolean animatorPaused = false;
boolean hidden = false;
boolean hadFocus = false;
boolean fullscreenPaused = false;
List _fullscreenMonitors = null;
boolean _fullscreenUseMainMonitor = true;
@Override
public void monitorModeChangeNotify(final MonitorEvent me) {
hadFocus = hasFocus();
final boolean isOSX = NativeWindowFactory.TYPE_MACOSX == NativeWindowFactory.getNativeWindowType(true);
final boolean quirkFSPause = fullscreen && isReconfigureFlagSupported(FLAG_IS_FULLSCREEN_SPAN);
final boolean quirkHide = !quirkFSPause && !fullscreen && isVisible() && isOSX;
if(DEBUG_IMPLEMENTATION) {
System.err.println("Window.monitorModeChangeNotify: hadFocus "+hadFocus+", qFSPause "+quirkFSPause+", qHide "+quirkHide+", "+me+" @ "+Thread.currentThread().getName());
}
if(null!=lifecycleHook) {
animatorPaused = lifecycleHook.pauseRenderingAction();
}
if( quirkFSPause ) {
if(DEBUG_IMPLEMENTATION) {
System.err.println("Window.monitorModeChangeNotify: FS Pause");
}
fullscreenPaused = true;
_fullscreenMonitors = fullscreenMonitors;
_fullscreenUseMainMonitor = fullscreenUseMainMonitor;
setFullscreenImpl(false, true, null);
}
if( quirkHide ) {
// hiding & showing the window around mode-change solves issues w/ OSX,
// where the content would be black until a resize.
hidden = true;
WindowImpl.this.setVisible(false);
}
}
@Override
public void monitorModeChanged(final MonitorEvent me, final boolean success) {
if(!animatorPaused && success && null!=lifecycleHook) {
// Didn't pass above notify method. probably detected screen change after it happened.
animatorPaused = lifecycleHook.pauseRenderingAction();
}
if(DEBUG_IMPLEMENTATION) {
System.err.println("Window.monitorModeChanged.0: success: "+success+", hadFocus "+hadFocus+", animPaused "+animatorPaused+
", hidden "+hidden+", FS "+fullscreen+", FS-paused "+fullscreenPaused+
" @ "+Thread.currentThread().getName());
System.err.println("Window.monitorModeChanged.0: "+getScreen());
System.err.println("Window.monitorModeChanged.0: "+me);
}
WindowImpl.this.monitorModeChanged(me, success);
if( success && !fullscreen && !fullscreenPaused ) {
// Simply move/resize window to fit in virtual screen if required
final RectangleImmutable viewport = screen.getViewportInWindowUnits();
if( viewport.getWidth() > 0 && viewport.getHeight() > 0 ) { // failsafe
final RectangleImmutable rect = new Rectangle(getX(), getY(), getWidth(), getHeight());
final RectangleImmutable isect = viewport.intersection(rect);
if ( getHeight() > isect.getHeight() ||
getWidth() > isect.getWidth() ) {
if(DEBUG_IMPLEMENTATION) {
System.err.println("Window.monitorModeChanged.1: Non-FS - Fit window "+rect+" into screen viewport "+viewport+
", due to minimal intersection "+isect);
}
definePosition(viewport.getX(), viewport.getY()); // set pos for setVisible(..) or createNative(..) - reduce EDT roundtrip
setSize(viewport.getWidth(), viewport.getHeight(), true /* force */);
}
}
} else if( fullscreenPaused ) {
if(DEBUG_IMPLEMENTATION) {
System.err.println("Window.monitorModeChanged.2: FS Restore");
}
setFullscreenImpl(true, _fullscreenUseMainMonitor, _fullscreenMonitors);
fullscreenPaused = false;
_fullscreenMonitors = null;
_fullscreenUseMainMonitor = true;
} else if( success && fullscreen && null != fullscreenMonitors ) {
// If changed monitor is part of this fullscreen mode, reset size! (Bug 771)
final MonitorDevice md = me.getMonitor();
if( fullscreenMonitors.contains(md) ) {
final Rectangle viewportInWindowUnits = new Rectangle();
MonitorDevice.unionOfViewports(null, viewportInWindowUnits, fullscreenMonitors);
if(DEBUG_IMPLEMENTATION) {
final RectangleImmutable winBounds = WindowImpl.this.getBounds();
System.err.println("Window.monitorModeChanged.3: FS Monitor Match: Fit window "+winBounds+" into new viewport union "+viewportInWindowUnits+" [window], provoked by "+md);
}
definePosition(viewportInWindowUnits.getX(), viewportInWindowUnits.getY()); // set pos for setVisible(..) or createNative(..) - reduce EDT roundtrip
setSize(viewportInWindowUnits.getWidth(), viewportInWindowUnits.getHeight(), true /* force */);
}
}
if( hidden ) {
WindowImpl.this.setVisible(true);
hidden = false;
}
sendWindowEvent(WindowEvent.EVENT_WINDOW_RESIZED); // trigger a resize/relayout and repaint to listener
if(animatorPaused) {
lifecycleHook.resumeRenderingAction();
}
if( hadFocus ) {
requestFocus(true);
}
if(DEBUG_IMPLEMENTATION) {
System.err.println("Window.monitorModeChanged.X: @ "+Thread.currentThread().getName()+", this: "+WindowImpl.this);
}
}
}
private final MonitorModeListenerImpl monitorModeListenerImpl = new MonitorModeListenerImpl();
//----------------------------------------------------------------------
// Child Window Management
//
@Override
public final boolean removeChild(final NativeWindow win) {
synchronized(childWindowsLock) {
return childWindows.remove(win);
}
}
@Override
public final boolean addChild(final NativeWindow win) {
if (win == null) {
return false;
}
synchronized(childWindowsLock) {
return childWindows.add(win);
}
}
//----------------------------------------------------------------------
// Generic Event Support
//
private void doEvent(final boolean enqueue, boolean wait, final com.jogamp.newt.event.NEWTEvent event) {
boolean done = false;
if(!enqueue) {
done = consumeEvent(event);
wait = done; // don't wait if event can't be consumed now
}
if(!done) {
enqueueEvent(wait, event);
}
}
@Override
public final void enqueueEvent(final boolean wait, final com.jogamp.newt.event.NEWTEvent event) {
if(isNativeValid()) {
((DisplayImpl)screen.getDisplay()).enqueueEvent(wait, event);
}
}
@Override
public final boolean consumeEvent(final NEWTEvent e) {
switch(e.getEventType()) {
// special repaint treatment
case WindowEvent.EVENT_WINDOW_REPAINT:
// queue repaint event in case window is locked, ie in operation
if( windowLock.isLockedByOtherThread() ) {
// make sure only one repaint event is queued
if(!repaintQueued) {
repaintQueued=true;
final boolean discardTO = QUEUED_EVENT_TO <= System.currentTimeMillis()-e.getWhen();
if(DEBUG_IMPLEMENTATION) {
System.err.println("Window.consumeEvent: REPAINT [me "+Thread.currentThread().getName()+", owner "+windowLock.getOwner()+"] - queued "+e+", discard-to "+discardTO);
// ExceptionUtils.dumpStackTrace(System.err);
}
return discardTO; // discardTO:=true -> consumed
}
return true;
}
repaintQueued=false; // no repaint event queued
break;
// common treatment
case WindowEvent.EVENT_WINDOW_RESIZED:
// queue event in case window is locked, ie in operation
if( windowLock.isLockedByOtherThread() ) {
final boolean discardTO = QUEUED_EVENT_TO <= System.currentTimeMillis()-e.getWhen();
if(DEBUG_IMPLEMENTATION) {
System.err.println("Window.consumeEvent: RESIZED [me "+Thread.currentThread().getName()+", owner "+windowLock.getOwner()+"] - queued "+e+", discard-to "+discardTO);
// ExceptionUtils.dumpStackTrace(System.err);
}
return discardTO; // discardTO:=true -> consumed
}
break;
default:
break;
}
if(e instanceof WindowEvent) {
consumeWindowEvent((WindowEvent)e);
} else if(e instanceof KeyEvent) {
consumeKeyEvent((KeyEvent)e);
} else if(e instanceof MouseEvent) {
consumePointerEvent((MouseEvent)e);
} else {
throw new NativeWindowException("Unexpected NEWTEvent type " + e);
}
return true;
}
//
// MouseListener/Event Support
//
//
// Native MouseEvents pre-processed to be enqueued or consumed directly
//
public final void sendMouseEvent(final short eventType, final int modifiers,
final int x, final int y, final short button, final float rotation) {
doMouseEvent(false, false, eventType, modifiers, x, y, button, MouseEvent.getRotationXYZ(rotation, modifiers), 1f);
}
public final void enqueueMouseEvent(final boolean wait, final short eventType, final int modifiers,
final int x, final int y, final short button, final float rotation) {
doMouseEvent(true, wait, eventType, modifiers, x, y, button, MouseEvent.getRotationXYZ(rotation, modifiers), 1f);
}
protected final void doMouseEvent(final boolean enqueue, final boolean wait, final short eventType, final int modifiers,
final int x, final int y, final short button, final float rotation) {
doMouseEvent(enqueue, wait, eventType, modifiers, x, y, button, MouseEvent.getRotationXYZ(rotation, modifiers), 1f);
}
/**
public final void sendMouseEvent(final short eventType, final int modifiers,
final int x, final int y, final short button, final float[] rotationXYZ, final float rotationScale) {
doMouseEvent(false, false, eventType, modifiers, x, y, button, rotationXYZ, rotationScale);
}
public final void enqueueMouseEvent(final boolean wait, final short eventType, final int modifiers,
final int x, final int y, final short button, final float[] rotationXYZ, final float rotationScale) {
doMouseEvent(true, wait, eventType, modifiers, x, y, button, rotationXYZ, rotationScale);
} */
/**
* Send mouse event (one-pointer) either to be directly consumed or to be enqueued
*
* @param enqueue if true, event will be {@link #enqueueEvent(boolean, NEWTEvent) enqueued},
* otherwise {@link #consumeEvent(NEWTEvent) consumed} directly.
* @param wait if true wait until {@link #consumeEvent(NEWTEvent) consumed}.
*/
protected void doMouseEvent(final boolean enqueue, final boolean wait, final short eventType, final int modifiers,
final int x, final int y, final short button, final float[] rotationXYZ, final float rotationScale) {
if( 0 > button || button > MouseEvent.BUTTON_COUNT ) {
throw new NativeWindowException("Invalid mouse button number" + button);
}
doPointerEvent(enqueue, wait, constMousePointerTypes, eventType, modifiers,
0 /*actionIdx*/, new short[] { (short)0 }, button,
new int[]{x}, new int[]{y}, new float[]{0f} /*pressure*/,
1f /*maxPressure*/, rotationXYZ, rotationScale);
}
/**
* Send multiple-pointer event either to be directly consumed or to be enqueued
*
* The index for the element of multiple-pointer arrays represents the pointer which triggered the event
* is passed via actionIdx.
*
*
* The given pointer names, pNames
, are mapped to consecutive pointer IDs starting w/ 0
* using a hash-map if normalPNames
is false
.
* Otherwise a simple int
to short
type cast is performed.
*
*
* See {@link #doPointerEvent(boolean, boolean, PointerType[], short, int, int, short[], short, int[], int[], float[], float, float[], float)}
* for details!
*
*
* @param enqueue if true, event will be {@link #enqueueEvent(boolean, NEWTEvent) enqueued},
* otherwise {@link #consumeEvent(NEWTEvent) consumed} directly.
* @param wait if true wait until {@link #consumeEvent(NEWTEvent) consumed}.
* @param pTypes {@link MouseEvent.PointerType} for each pointer (multiple pointer)
* @param eventType
* @param modifiers
* @param actionIdx index of multiple-pointer arrays representing the pointer which triggered the event
* @param normalPNames see pName below.
* @param pNames Pointer name for each pointer (multiple pointer).
* We assume consecutive pointer names starting w/ 0 if normalPIDs
is true
.
* Otherwise we hash-map the values during state pressed to retrieve the normal ID.
* @param pX X-axis for each pointer (multiple pointer)
* @param pY Y-axis for each pointer (multiple pointer)
* @param pPressure Pressure for each pointer (multiple pointer)
* @param maxPressure Maximum pointer pressure for all pointer
*/
public final void doPointerEvent(final boolean enqueue, final boolean wait,
final PointerType[] pTypes, final short eventType, final int modifiers,
final int actionIdx, final boolean normalPNames, final int[] pNames,
final int[] pX, final int[] pY, final float[] pPressure,
final float maxPressure, final float[] rotationXYZ, final float rotationScale) {
final int pCount = pNames.length;
final short[] pIDs = new short[pCount];
for(int i=0; i short idx
final int sz0 = pName2pID.size();
final Integer pNameI1 = pName2pID.getOrAdd(Integer.valueOf(pNames[i]));
final short pID = (short)pName2pID.indexOf(pNameI1);
pIDs[i] = pID;
if(DEBUG_MOUSE_EVENT) {
final int sz1 = pName2pID.size();
if( sz0 != sz1 ) {
System.err.println("PointerName2ID[sz "+sz1+"]: Map "+pNameI1+" == "+pID);
}
}
if( MouseEvent.EVENT_MOUSE_RELEASED == eventType ) {
pName2pID.remove(pNameI1);
if(DEBUG_MOUSE_EVENT) {
System.err.println("PointerName2ID[sz "+pName2pID.size()+"]: Unmap "+pNameI1+" == "+pID);
}
}
} else {
// simple type cast
pIDs[i] = (short)pNames[i];
}
}
final short button = 0 < pCount ? (short) ( pIDs[0] + 1 ) : (short)0;
doPointerEvent(enqueue, wait, pTypes, eventType, modifiers, actionIdx, pIDs, button,
pX, pY, pPressure, maxPressure, rotationXYZ, rotationScale);
}
/**
* Send multiple-pointer event either to be directly consumed or to be enqueued.
*
* Pointer/Mouse Processing Pass 1 (Pass 2 is performed in {@link #consumePointerEvent(MouseEvent)}.
*
*
* Usually directly called by event source to enqueue and process event.
*
*
* The index for the element of multiple-pointer arrays represents the pointer which triggered the event
* is passed via actionIdx.
*
*
*
* - Determine ENTERED/EXITED state
* - Remove redundant move/drag events
* - Reset states if applicable
* - Drop exterior events
* - Determine CLICK COUNT
* - Ignore sent CLICKED
* - Track buttonPressed incl. buttonPressedMask
* - Synthesize DRAGGED event (from MOVED if pointer is pressed)
*
*
*
* @param enqueue if true, event will be {@link #enqueueEvent(boolean, NEWTEvent) enqueued},
* otherwise {@link #consumeEvent(NEWTEvent) consumed} directly.
* @param wait if true wait until {@link #consumeEvent(NEWTEvent) consumed}.
* @param pTypes {@link MouseEvent.PointerType} for each pointer (multiple pointer)
* @param eventType
* @param modifiers
* @param pActionIdx index of multiple-pointer arrays representing the pointer which triggered the event
* @param pID Pointer ID for each pointer (multiple pointer). We assume consecutive pointerIDs starting w/ 0.
* @param button Corresponding mouse-button, a button of 0 denotes no activity, i.e. {@link PointerType#Mouse} move.
* @param pX X-axis for each pointer (multiple pointer)
* @param pY Y-axis for each pointer (multiple pointer)
* @param pPressure Pressure for each pointer (multiple pointer)
* @param maxPressure Maximum pointer pressure for all pointer
*/
public final void doPointerEvent(final boolean enqueue, final boolean wait,
final PointerType[] pTypes, final short eventType, int modifiers,
final int pActionIdx, final short[] pID, final short buttonIn, final int[] pX, final int[] pY,
final float[] pPressure, final float maxPressure, final float[] rotationXYZ, final float rotationScale) {
final long when = System.currentTimeMillis();
final int pCount = pTypes.length;
if( 0 > pActionIdx || pActionIdx >= pCount) {
throw new IllegalArgumentException("actionIdx out of bounds [0.."+(pCount-1)+"]");
}
if( 0 < pActionIdx ) {
// swap values to make idx 0 the triggering pointer
{
final PointerType aType = pTypes[pActionIdx];
pTypes[pActionIdx] = pTypes[0];
pTypes[0] = aType;
}
{
final short s = pID[pActionIdx];
pID[pActionIdx] = pID[0];
pID[0] = s;
}
{
int s = pX[pActionIdx];
pX[pActionIdx] = pX[0];
pX[0] = s;
s = pY[pActionIdx];
pY[pActionIdx] = pY[0];
pY[0] = s;
}
{
final float aPress = pPressure[pActionIdx];
pPressure[pActionIdx] = pPressure[0];
pPressure[0] = aPress;
}
}
final short button;
{
// validate button
if( 0 <= buttonIn && buttonIn <= com.jogamp.newt.event.MouseEvent.BUTTON_COUNT ) { // we allow button==0 for no button, i.e. mouse-ptr move
button = buttonIn;
} else {
button = com.jogamp.newt.event.MouseEvent.BUTTON1;
}
}
//
// - Determine ENTERED/EXITED state
// - Remove redundant move/drag events
// - Reset states if applicable
//
int x = pX[0];
int y = pY[0];
final boolean insideSurface = x >= 0 && y >= 0 && x < getSurfaceWidth() && y < getSurfaceHeight();
final Point movePositionP0 = pState1.getMovePosition(pID[0]);
switch( eventType ) {
case MouseEvent.EVENT_MOUSE_EXITED:
if( pState1.dragging ) {
// Drop mouse EXIT if dragging, i.e. due to exterior dragging outside of window.
// NOTE-1: X11 produces the 'premature' EXIT, however it also produces 'EXIT' after exterior dragging!
// NOTE-2: consumePointerEvent(MouseEvent) will synthesize a missing EXIT event!
if(DEBUG_MOUSE_EVENT) {
System.err.println("doPointerEvent: drop "+MouseEvent.getEventTypeString(eventType)+" due to dragging: "+pState1);
}
return;
}
if( null != movePositionP0 ) {
if( x==-1 && y==-1 ) {
x = movePositionP0.getX();
y = movePositionP0.getY();
}
movePositionP0.set(0, 0);
}
// Fall through intended!
case MouseEvent.EVENT_MOUSE_ENTERED:
if( eventType == MouseEvent.EVENT_MOUSE_ENTERED ) {
pState1.insideSurface = true;
pState1.exitSent = false;
} else {
pState1.insideSurface = false;
pState1.exitSent = true;
}
pState1.clearButton();
if( pTypes[0] != PointerType.Mouse ) {
// Drop !MOUSE ENTER/EXIT Events - Safeguard for non compliant implementations only.
if(DEBUG_MOUSE_EVENT) {
System.err.println("doPointerEvent: drop "+MouseEvent.getEventTypeString(eventType)+" due to !Mouse but "+pTypes[0]+": "+pState1);
}
return;
}
// clip coordinates to window dimension
x = Math.min(Math.max(x, 0), getSurfaceWidth()-1);
y = Math.min(Math.max(y, 0), getSurfaceHeight()-1);
break;
case MouseEvent.EVENT_MOUSE_MOVED:
case MouseEvent.EVENT_MOUSE_DRAGGED:
if( null != movePositionP0 ) {
if( movePositionP0.getX() == x && movePositionP0.getY() == y ) {
// Drop same position
if(DEBUG_MOUSE_EVENT) {
System.err.println("doPointerEvent: drop "+MouseEvent.getEventTypeString(eventType)+" w/ same position: "+movePositionP0+", "+pState1);
}
return;
}
movePositionP0.set(x, y);
}
// Fall through intended !
default:
if( pState1.insideSurface != insideSurface ) {
// ENTER/EXIT!
pState1.insideSurface = insideSurface;
if( insideSurface ) {
pState1.exitSent = false;
}
pState1.clearButton();
}
}
//
// Drop exterior events if not dragging pointer and not EXIT event
// Safeguard for non compliant implementations!
//
if( !pState1.dragging && !insideSurface && MouseEvent.EVENT_MOUSE_EXITED != eventType ) {
if(DEBUG_MOUSE_EVENT) {
System.err.println("doPointerEvent: drop: "+MouseEvent.getEventTypeString(eventType)+
", mod "+modifiers+", pos "+x+"/"+y+", button "+button+", lastMousePosition: "+movePositionP0+", insideWindow "+insideSurface+", "+pState1);
}
return; // .. invalid ..
}
if(DEBUG_MOUSE_EVENT) {
System.err.println("doPointerEvent: enqueue "+enqueue+", wait "+wait+", "+MouseEvent.getEventTypeString(eventType)+
", mod "+modifiers+", pos "+x+"/"+y+", button "+button+", lastMousePosition: "+movePositionP0+", "+pState1);
}
final int buttonMask = InputEvent.getButtonMask(button);
modifiers |= buttonMask; // Always add current button to modifier mask (Bug 571)
modifiers |= pState1.buttonPressedMask; // Always add currently pressed mouse buttons to modifier mask
if( isPointerConfined() ) {
modifiers |= InputEvent.CONFINED_MASK;
}
if( !isPointerVisible() ) {
modifiers |= InputEvent.INVISIBLE_MASK;
}
pX[0] = x;
pY[0] = y;
//
// - Determine CLICK COUNT
// - Ignore sent CLICKED
// - Track buttonPressed incl. buttonPressedMask
// - Synthesize DRAGGED event (from MOVED if pointer is pressed)
//
final MouseEvent e;
switch( eventType ) {
case MouseEvent.EVENT_MOUSE_CLICKED:
e = null;
break;
case MouseEvent.EVENT_MOUSE_PRESSED:
if( 0 >= pPressure[0] ) {
pPressure[0] = maxPressure;
}
pState1.buttonPressedMask |= buttonMask;
if( 1 == pCount ) {
if( when - pState1.lastButtonPressTime < MouseEvent.getClickTimeout() ) {
pState1.lastButtonClickCount++;
} else {
pState1.lastButtonClickCount=(short)1;
}
pState1.lastButtonPressTime = when;
pState1.buttonPressed = button;
e = new MouseEvent(eventType, this, when, modifiers, pTypes, pID,
pX, pY, pPressure, maxPressure, button, pState1.lastButtonClickCount, rotationXYZ, rotationScale);
} else {
e = new MouseEvent(eventType, this, when, modifiers, pTypes, pID,
pX, pY, pPressure, maxPressure, button, (short)1, rotationXYZ, rotationScale);
}
break;
case MouseEvent.EVENT_MOUSE_RELEASED:
pState1.buttonPressedMask &= ~buttonMask;
if( 1 == pCount ) {
e = new MouseEvent(eventType, this, when, modifiers, pTypes, pID,
pX, pY, pPressure, maxPressure, button, pState1.lastButtonClickCount, rotationXYZ, rotationScale);
if( when - pState1.lastButtonPressTime >= MouseEvent.getClickTimeout() ) {
pState1.lastButtonClickCount = (short)0;
pState1.lastButtonPressTime = 0;
}
pState1.buttonPressed = 0;
pState1.dragging = false;
} else {
e = new MouseEvent(eventType, this, when, modifiers, pTypes, pID,
pX, pY, pPressure, maxPressure, button, (short)1, rotationXYZ, rotationScale);
if( 0 == pState1.buttonPressedMask ) {
pState1.clearButton();
}
}
if( null != movePositionP0 ) {
movePositionP0.set(0, 0);
}
break;
case MouseEvent.EVENT_MOUSE_MOVED:
if ( 0 != pState1.buttonPressedMask ) { // any button or pointer move -> drag
e = new MouseEvent(MouseEvent.EVENT_MOUSE_DRAGGED, this, when, modifiers, pTypes, pID,
pX, pY, pPressure, maxPressure, pState1.buttonPressed, (short)1, rotationXYZ, rotationScale);
pState1.dragging = true;
} else {
e = new MouseEvent(eventType, this, when, modifiers, pTypes, pID,
pX, pY, pPressure, maxPressure, button, (short)0, rotationXYZ, rotationScale);
}
break;
case MouseEvent.EVENT_MOUSE_DRAGGED:
if( 0 >= pPressure[0] ) {
pPressure[0] = maxPressure;
}
pState1.dragging = true;
// Fall through intended!
default:
e = new MouseEvent(eventType, this, when, modifiers, pTypes, pID,
pX, pY, pPressure, maxPressure, button, (short)0, rotationXYZ, rotationScale);
}
doEvent(enqueue, wait, e); // actual mouse event
}
private static int step(final int lower, final int edge, final int value) {
return value < edge ? lower : value;
}
/**
* Consume the {@link MouseEvent}.
*
* Pointer/Mouse Processing Pass 2 (Pass 1 is performed in {@link #doPointerEvent(boolean, boolean, PointerType[], short, int, int, short[], short, int[], int[], float[], float, float[], float)}).
*
*
* Invoked before dispatching the dequeued event.
*
*
*
* - Validate
* - Handle gestures
* - Synthesize events [ENTERED, EXIT, CLICK] and gestures.
* - Drop exterior events
* - Dispatch event to listener
*
*
*/
protected void consumePointerEvent(MouseEvent pe) {
if(DEBUG_MOUSE_EVENT) {
System.err.println("consumePointerEvent.in: "+pe+", "+pState0+", pos "+pe.getX()+"/"+pe.getY()+", win["+getX()+"/"+getY()+" "+getWidth()+"x"+getHeight()+
"], pixel["+getSurfaceWidth()+"x"+getSurfaceHeight()+"]");
}
//
// - Determine ENTERED/EXITED state
// - Synthesize ENTERED and EXIT event
// - Reset states if applicable
//
final long when = pe.getWhen();
final int eventType = pe.getEventType();
final boolean insideSurface;
boolean eExitAllowed = false;
MouseEvent eEntered = null, eExited = null;
switch( eventType ) {
case MouseEvent.EVENT_MOUSE_EXITED:
if( pState0.exitSent || pState0.dragging ) {
if(DEBUG_MOUSE_EVENT) {
System.err.println("consumePointerEvent: drop "+(pState0.exitSent?"already sent":"due to dragging")+": "+pe+", "+pState0);
}
return;
}
// Fall through intended !
case MouseEvent.EVENT_MOUSE_ENTERED:
// clip coordinates to window dimension
// final int pe_x = Math.min(Math.max(pe.getX(), 0), getSurfaceWidth()-1);
// final int pe_y = Math.min(Math.max(pe.getY(), 0), getSurfaceHeight()-1);
pState0.clearButton();
if( eventType == MouseEvent.EVENT_MOUSE_ENTERED ) {
insideSurface = true;
pState0.insideSurface = true;
pState0.exitSent = false;
pState0.dragging = false;
} else {
insideSurface = false;
pState0.insideSurface = false;
pState0.exitSent = true;
}
break;
case MouseEvent.EVENT_MOUSE_MOVED:
case MouseEvent.EVENT_MOUSE_RELEASED:
if( 1 >= pe.getButtonDownCount() ) { // MOVE or RELEASE last button
eExitAllowed = !pState0.exitSent;
pState0.dragging = false;
}
// Fall through intended !
default:
final int pe_x = pe.getX();
final int pe_y = pe.getY();
insideSurface = pe_x >= 0 && pe_y >= 0 && pe_x < getSurfaceWidth() && pe_y < getSurfaceHeight();
if( pe.getPointerType(0) == PointerType.Mouse ) {
if( !pState0.insideSurface && insideSurface ) {
// ENTER .. use clipped coordinates
eEntered = new MouseEvent(MouseEvent.EVENT_MOUSE_ENTERED, pe.getSource(), pe.getWhen(), pe.getModifiers(),
Math.min(Math.max(pe_x, 0), getSurfaceWidth()-1),
Math.min(Math.max(pe_y, 0), getSurfaceHeight()-1),
(short)0, (short)0, pe.getRotation(), pe.getRotationScale());
pState0.exitSent = false;
} else if( !insideSurface && eExitAllowed ) {
// EXIT .. use clipped coordinates
eExited = new MouseEvent(MouseEvent.EVENT_MOUSE_EXITED, pe.getSource(), pe.getWhen(), pe.getModifiers(),
Math.min(Math.max(pe_x, 0), getSurfaceWidth()-1),
Math.min(Math.max(pe_y, 0), getSurfaceHeight()-1),
(short)0, (short)0, pe.getRotation(), pe.getRotationScale());
pState0.exitSent = true;
}
}
if( pState0.insideSurface != insideSurface || null != eEntered || null != eExited) {
pState0.clearButton();
}
pState0.insideSurface = insideSurface;
}
if( null != eEntered ) {
if(DEBUG_MOUSE_EVENT) {
System.err.println("consumePointerEvent.send.0: "+eEntered+", "+pState0);
}
dispatchMouseEvent(eEntered);
} else if( DEBUG_MOUSE_EVENT && !insideSurface ) {
System.err.println("INFO consumePointerEvent.exterior: "+pState0+", "+pe);
}
//
// Handle Default Gestures
//
if( defaultGestureHandlerEnabled &&
pe.getPointerType(0).getPointerClass() == MouseEvent.PointerClass.Onscreen )
{
if( null == gesture2PtrTouchScroll ) {
final int scaledScrollSlop;
final int scaledDoubleTapSlop;
final MonitorDevice monitor = getMainMonitor();
if ( null != monitor ) {
final DimensionImmutable mm = monitor.getSizeMM();
final float pixWPerMM = (float)monitor.getCurrentMode().getRotatedWidth() / (float)mm.getWidth();
final float pixHPerMM = (float)monitor.getCurrentMode().getRotatedHeight() / (float)mm.getHeight();
final float pixPerMM = Math.min(pixHPerMM, pixWPerMM);
scaledScrollSlop = Math.round(DoubleTapScrollGesture.SCROLL_SLOP_MM * pixPerMM);
scaledDoubleTapSlop = Math.round(DoubleTapScrollGesture.DOUBLE_TAP_SLOP_MM * pixPerMM);
if(DEBUG_MOUSE_EVENT) {
System.err.println("consumePointerEvent.gscroll: scrollSlop "+scaledScrollSlop+", doubleTapSlop "+scaledDoubleTapSlop+", pixPerMM "+pixPerMM+", "+monitor+", "+pState0);
}
} else {
scaledScrollSlop = DoubleTapScrollGesture.SCROLL_SLOP_PIXEL;
scaledDoubleTapSlop = DoubleTapScrollGesture.DOUBLE_TAP_SLOP_PIXEL;
}
gesture2PtrTouchScroll = new DoubleTapScrollGesture(step(DoubleTapScrollGesture.SCROLL_SLOP_PIXEL, DoubleTapScrollGesture.SCROLL_SLOP_PIXEL/2, scaledScrollSlop),
step(DoubleTapScrollGesture.DOUBLE_TAP_SLOP_PIXEL, DoubleTapScrollGesture.DOUBLE_TAP_SLOP_PIXEL/2, scaledDoubleTapSlop));
}
if( gesture2PtrTouchScroll.process(pe) ) {
pe = (MouseEvent) gesture2PtrTouchScroll.getGestureEvent();
gesture2PtrTouchScroll.clear(false);
if(DEBUG_MOUSE_EVENT) {
System.err.println("consumePointerEvent.gscroll: "+pe+", "+pState0);
}
dispatchMouseEvent(pe);
return;
}
if( gesture2PtrTouchScroll.isWithinGesture() ) {
return; // within gesture .. need more input ..
}
}
//
// Handle Custom Gestures
//
{
final int pointerGestureHandlerCount = pointerGestureHandler.size();
if( pointerGestureHandlerCount > 0 ) {
boolean withinGesture = false;
for(int i = 0; !pe.isConsumed() && i < pointerGestureHandlerCount; i++ ) {
final GestureHandler gh = pointerGestureHandler.get(i);
if( gh.process(pe) ) {
final InputEvent ieG = gh.getGestureEvent();
gh.clear(false);
if( ieG instanceof MouseEvent ) {
dispatchMouseEvent((MouseEvent)ieG);
} else if( ieG instanceof GestureHandler.GestureEvent) {
final GestureHandler.GestureEvent ge = (GestureHandler.GestureEvent) ieG;
for(int j = 0; !ge.isConsumed() && j < gestureListeners.size(); j++ ) {
gestureListeners.get(j).gestureDetected(ge);
}
}
return;
}
withinGesture |= gh.isWithinGesture();
}
if( withinGesture ) {
return;
}
}
}
//
// - Synthesize mouse CLICKED
// - Ignore sent CLICKED
//
MouseEvent eClicked = null;
switch( eventType ) {
case MouseEvent.EVENT_MOUSE_PRESSED:
if( 1 == pe.getPointerCount() ) {
pState0.lastButtonPressTime = when;
}
break;
case MouseEvent.EVENT_MOUSE_RELEASED:
if( 1 == pe.getPointerCount() && when - pState0.lastButtonPressTime < MouseEvent.getClickTimeout() ) {
eClicked = pe.createVariant(MouseEvent.EVENT_MOUSE_CLICKED);
} else {
pState0.lastButtonPressTime = 0;
}
break;
case MouseEvent.EVENT_MOUSE_CLICKED:
// ignore - synthesized here ..
if(DEBUG_MOUSE_EVENT) {
System.err.println("consumePointerEvent: drop recv'ed (synth here) "+pe+", "+pState0);
}
pe = null;
break;
case MouseEvent.EVENT_MOUSE_DRAGGED:
pState0.dragging = true;
break;
}
if( null != pe ) {
if(DEBUG_MOUSE_EVENT) {
System.err.println("consumePointerEvent.send.1: "+pe+", "+pState0);
}
dispatchMouseEvent(pe); // actual mouse event
}
if( null != eClicked ) {
if(DEBUG_MOUSE_EVENT) {
System.err.println("consumePointerEvent.send.2: "+eClicked+", "+pState0);
}
dispatchMouseEvent(eClicked);
}
if( null != eExited ) {
if(DEBUG_MOUSE_EVENT) {
System.err.println("consumePointerEvent.send.3: "+eExited+", "+pState0);
}
dispatchMouseEvent(eExited);
}
}
@Override
public final void addMouseListener(final MouseListener l) {
addMouseListener(-1, l);
}
@Override
public final void addMouseListener(int index, final MouseListener l) {
if(l == null) {
return;
}
@SuppressWarnings("unchecked")
final
ArrayList clonedListeners = (ArrayList) mouseListeners.clone();
if(0>index) {
index = clonedListeners.size();
}
clonedListeners.add(index, l);
mouseListeners = clonedListeners;
}
@Override
public final void removeMouseListener(final MouseListener l) {
if (l == null) {
return;
}
@SuppressWarnings("unchecked")
final
ArrayList clonedListeners = (ArrayList) mouseListeners.clone();
clonedListeners.remove(l);
mouseListeners = clonedListeners;
}
@Override
public final MouseListener getMouseListener(int index) {
@SuppressWarnings("unchecked")
final
ArrayList clonedListeners = (ArrayList) mouseListeners.clone();
if(0>index) {
index = clonedListeners.size()-1;
}
return clonedListeners.get(index);
}
@Override
public final MouseListener[] getMouseListeners() {
return mouseListeners.toArray(new MouseListener[mouseListeners.size()]);
}
@Override
public final void setDefaultGesturesEnabled(final boolean enable) {
defaultGestureHandlerEnabled = enable;
}
@Override
public final boolean areDefaultGesturesEnabled() {
return defaultGestureHandlerEnabled;
}
@Override
public final void addGestureHandler(final GestureHandler gh) {
addGestureHandler(-1, gh);
}
@Override
public final void addGestureHandler(int index, final GestureHandler gh) {
if(gh == null) {
return;
}
@SuppressWarnings("unchecked")
final
ArrayList cloned = (ArrayList) pointerGestureHandler.clone();
if(0>index) {
index = cloned.size();
}
cloned.add(index, gh);
pointerGestureHandler = cloned;
}
@Override
public final void removeGestureHandler(final GestureHandler gh) {
if (gh == null) {
return;
}
@SuppressWarnings("unchecked")
final
ArrayList cloned = (ArrayList) pointerGestureHandler.clone();
cloned.remove(gh);
pointerGestureHandler = cloned;
}
@Override
public final void addGestureListener(final GestureHandler.GestureListener gl) {
addGestureListener(-1, gl);
}
@Override
public final void addGestureListener(int index, final GestureHandler.GestureListener gl) {
if(gl == null) {
return;
}
@SuppressWarnings("unchecked")
final
ArrayList cloned = (ArrayList) gestureListeners.clone();
if(0>index) {
index = cloned.size();
}
cloned.add(index, gl);
gestureListeners = cloned;
}
@Override
public final void removeGestureListener(final GestureHandler.GestureListener gl) {
if (gl == null) {
return;
}
@SuppressWarnings("unchecked")
final
ArrayList cloned = (ArrayList) gestureListeners.clone();
cloned.remove(gl);
gestureListeners= cloned;
}
private final void dispatchMouseEvent(final MouseEvent e) {
for(int i = 0; !e.isConsumed() && i < mouseListeners.size(); i++ ) {
final MouseListener l = mouseListeners.get(i);
switch(e.getEventType()) {
case MouseEvent.EVENT_MOUSE_CLICKED:
l.mouseClicked(e);
break;
case MouseEvent.EVENT_MOUSE_ENTERED:
l.mouseEntered(e);
break;
case MouseEvent.EVENT_MOUSE_EXITED:
l.mouseExited(e);
break;
case MouseEvent.EVENT_MOUSE_PRESSED:
l.mousePressed(e);
break;
case MouseEvent.EVENT_MOUSE_RELEASED:
l.mouseReleased(e);
break;
case MouseEvent.EVENT_MOUSE_MOVED:
l.mouseMoved(e);
break;
case MouseEvent.EVENT_MOUSE_DRAGGED:
l.mouseDragged(e);
break;
case MouseEvent.EVENT_MOUSE_WHEEL_MOVED:
l.mouseWheelMoved(e);
break;
default:
throw new NativeWindowException("Unexpected mouse event type " + e.getEventType());
}
}
}
//
// KeyListener/Event Support
//
private static final int keyTrackingRange = 255;
private final IntBitfield keyPressedState = new IntBitfield( keyTrackingRange + 1 );
protected final boolean isKeyCodeTracked(final short keyCode) {
return ( 0xFFFF & keyCode ) <= keyTrackingRange;
}
/**
* @param keyCode the keyCode to set pressed state
* @param pressed true if pressed, otherwise false
* @return the previus pressed value
*/
protected final boolean setKeyPressed(final short keyCode, final boolean pressed) {
final int v = 0xFFFF & keyCode;
if( v <= keyTrackingRange ) {
return keyPressedState.put(v, pressed);
}
return false;
}
/**
* @param keyCode the keyCode to test pressed state
* @return true if pressed, otherwise false
*/
protected final boolean isKeyPressed(final short keyCode) {
final int v = 0xFFFF & keyCode;
if( v <= keyTrackingRange ) {
return keyPressedState.get(v);
}
return false;
}
public void sendKeyEvent(final short eventType, final int modifiers, final short keyCode, final short keySym, final char keyChar) {
// Always add currently pressed mouse buttons to modifier mask
consumeKeyEvent( KeyEvent.create(eventType, this, System.currentTimeMillis(), modifiers | pState1.buttonPressedMask, keyCode, keySym, keyChar) );
}
public void enqueueKeyEvent(final boolean wait, final short eventType, final int modifiers, final short keyCode, final short keySym, final char keyChar) {
// Always add currently pressed mouse buttons to modifier mask
enqueueEvent(wait, KeyEvent.create(eventType, this, System.currentTimeMillis(), modifiers | pState1.buttonPressedMask, keyCode, keySym, keyChar) );
}
@Override
public final void setKeyboardVisible(final boolean visible) {
if(isNativeValid()) {
// We don't skip the impl. if it seems that there is no state change,
// since we cannot assume the impl. reliably gives us it's current state.
final boolean ok = setKeyboardVisibleImpl(visible);
if(DEBUG_IMPLEMENTATION || DEBUG_KEY_EVENT) {
System.err.println("setKeyboardVisible(native): visible "+keyboardVisible+" -- op[visible:"+visible +", ok "+ok+"] -> "+(visible && ok));
}
keyboardVisibilityChanged( visible && ok );
} else {
keyboardVisibilityChanged( visible ); // earmark for creation
}
}
@Override
public final boolean isKeyboardVisible() {
return keyboardVisible;
}
/**
* Returns true
if operation was successful, otherwise false
.
*
* We assume that a failed invisible operation is due to an already invisible keyboard,
* hence even if an invisible operation failed, the keyboard is considered invisible!
*
*/
protected boolean setKeyboardVisibleImpl(final boolean visible) {
return false; // nop
}
/** Triggered by implementation's WM events to update the virtual on-screen keyboard's visibility state. */
protected void keyboardVisibilityChanged(final boolean visible) {
if(keyboardVisible != visible) {
if(DEBUG_IMPLEMENTATION || DEBUG_KEY_EVENT) {
System.err.println("keyboardVisibilityChanged: "+keyboardVisible+" -> "+visible);
}
keyboardVisible = visible;
}
}
protected boolean keyboardVisible = false;
@Override
public final void addKeyListener(final KeyListener l) {
addKeyListener(-1, l);
}
@Override
public final void addKeyListener(int index, final KeyListener l) {
if(l == null) {
return;
}
@SuppressWarnings("unchecked")
final
ArrayList clonedListeners = (ArrayList) keyListeners.clone();
if(0>index) {
index = clonedListeners.size();
}
clonedListeners.add(index, l);
keyListeners = clonedListeners;
}
@Override
public final void removeKeyListener(final KeyListener l) {
if (l == null) {
return;
}
@SuppressWarnings("unchecked")
final
ArrayList clonedListeners = (ArrayList) keyListeners.clone();
clonedListeners.remove(l);
keyListeners = clonedListeners;
}
@Override
public final KeyListener getKeyListener(int index) {
@SuppressWarnings("unchecked")
final
ArrayList clonedListeners = (ArrayList) keyListeners.clone();
if(0>index) {
index = clonedListeners.size()-1;
}
return clonedListeners.get(index);
}
@Override
public final KeyListener[] getKeyListeners() {
return keyListeners.toArray(new KeyListener[keyListeners.size()]);
}
private final boolean propagateKeyEvent(final KeyEvent e, final KeyListener l) {
switch(e.getEventType()) {
case KeyEvent.EVENT_KEY_PRESSED:
l.keyPressed(e);
break;
case KeyEvent.EVENT_KEY_RELEASED:
l.keyReleased(e);
break;
default:
throw new NativeWindowException("Unexpected key event type " + e.getEventType());
}
return e.isConsumed();
}
protected void consumeKeyEvent(final KeyEvent e) {
boolean consumedE = false;
if( null != keyboardFocusHandler && !e.isAutoRepeat() ) {
consumedE = propagateKeyEvent(e, keyboardFocusHandler);
if(DEBUG_KEY_EVENT) {
if( consumedE ) {
System.err.println("consumeKeyEvent(kfh): "+e+", consumed: "+consumedE);
}
}
}
if( !consumedE ) {
for(int i = 0; !consumedE && i < keyListeners.size(); i++ ) {
consumedE = propagateKeyEvent(e, keyListeners.get(i));
}
if(DEBUG_KEY_EVENT) {
System.err.println("consumeKeyEvent(usr): "+e+", consumed: "+consumedE);
}
}
}
//
// WindowListener/Event Support
//
@Override
public final void sendWindowEvent(final int eventType) {
consumeWindowEvent( new WindowEvent((short)eventType, this, System.currentTimeMillis()) );
}
public final void enqueueWindowEvent(final boolean wait, final int eventType) {
enqueueEvent( wait, new WindowEvent((short)eventType, this, System.currentTimeMillis()) );
}
@Override
public final void addWindowListener(final WindowListener l) {
addWindowListener(-1, l);
}
@Override
public final void addWindowListener(int index, final WindowListener l)
throws IndexOutOfBoundsException
{
if(l == null) {
return;
}
@SuppressWarnings("unchecked")
final
ArrayList clonedListeners = (ArrayList) windowListeners.clone();
if(0>index) {
index = clonedListeners.size();
}
clonedListeners.add(index, l);
windowListeners = clonedListeners;
}
@Override
public final void removeWindowListener(final WindowListener l) {
if (l == null) {
return;
}
@SuppressWarnings("unchecked")
final
ArrayList clonedListeners = (ArrayList) windowListeners.clone();
clonedListeners.remove(l);
windowListeners = clonedListeners;
}
@Override
public final WindowListener getWindowListener(int index) {
@SuppressWarnings("unchecked")
final
ArrayList clonedListeners = (ArrayList) windowListeners.clone();
if(0>index) {
index = clonedListeners.size()-1;
}
return clonedListeners.get(index);
}
@Override
public final WindowListener[] getWindowListeners() {
return windowListeners.toArray(new WindowListener[windowListeners.size()]);
}
protected void consumeWindowEvent(final WindowEvent e) {
if(DEBUG_IMPLEMENTATION) {
System.err.println("consumeWindowEvent: "+e+", visible "+isVisible()+" "+getX()+"/"+getY()+", win["+getX()+"/"+getY()+" "+getWidth()+"x"+getHeight()+
"], pixel["+getSurfaceWidth()+"x"+getSurfaceHeight()+"]");
}
for(int i = 0; !e.isConsumed() && i < windowListeners.size(); i++ ) {
final WindowListener l = windowListeners.get(i);
switch(e.getEventType()) {
case WindowEvent.EVENT_WINDOW_RESIZED:
l.windowResized(e);
break;
case WindowEvent.EVENT_WINDOW_MOVED:
l.windowMoved(e);
break;
case WindowEvent.EVENT_WINDOW_DESTROY_NOTIFY:
l.windowDestroyNotify(e);
break;
case WindowEvent.EVENT_WINDOW_DESTROYED:
l.windowDestroyed(e);
break;
case WindowEvent.EVENT_WINDOW_GAINED_FOCUS:
l.windowGainedFocus(e);
break;
case WindowEvent.EVENT_WINDOW_LOST_FOCUS:
l.windowLostFocus(e);
break;
case WindowEvent.EVENT_WINDOW_REPAINT:
l.windowRepaint((WindowUpdateEvent)e);
break;
default:
throw
new NativeWindowException("Unexpected window event type "
+ e.getEventType());
}
}
}
/** Triggered by implementation's WM events to update the focus state. */
protected void focusChanged(final boolean defer, final boolean focusGained) {
if(brokenFocusChange || hasFocus != focusGained) {
if(DEBUG_IMPLEMENTATION) {
System.err.println("Window.focusChanged: ("+getThreadName()+"): (defer: "+defer+") "+this.hasFocus+" -> "+focusGained+" - windowHandle "+toHexString(windowHandle)+" parentWindowHandle "+toHexString(parentWindowHandle));
}
hasFocus = focusGained;
final int evt = focusGained ? WindowEvent.EVENT_WINDOW_GAINED_FOCUS : WindowEvent.EVENT_WINDOW_LOST_FOCUS ;
if(!defer) {
sendWindowEvent(evt);
} else {
enqueueWindowEvent(false, evt);
}
}
}
/** Triggered by implementation's WM events to update the visibility state. */
protected final void visibleChanged(final boolean defer, final boolean visible) {
if(this.visible != visible) {
if(DEBUG_IMPLEMENTATION) {
System.err.println("Window.visibleChanged ("+getThreadName()+"): (defer: "+defer+") "+this.visible+" -> "+visible+" - windowHandle "+toHexString(windowHandle)+" parentWindowHandle "+toHexString(parentWindowHandle));
}
this.visible = visible ;
}
}
/** Returns -1 if failed, otherwise remaining time until {@link #TIMEOUT_NATIVEWINDOW}, maybe zero. */
private long waitForVisible(final boolean visible, final boolean failFast) {
return waitForVisible(visible, failFast, TIMEOUT_NATIVEWINDOW);
}
/** Returns -1 if failed, otherwise remaining time until timeOut
, maybe zero. */
private long waitForVisible(final boolean visible, final boolean failFast, final long timeOut) {
final DisplayImpl display = (DisplayImpl) screen.getDisplay();
display.dispatchMessagesNative(); // status up2date
long remaining;
for(remaining = timeOut; 0 "+newWidth+"x"+newHeight+
" - windowHandle "+toHexString(windowHandle)+" parentWindowHandle "+toHexString(parentWindowHandle));
}
if(0>newWidth || 0>newHeight) {
throw new NativeWindowException("Illegal width or height "+newWidth+"x"+newHeight+" (must be >= 0)");
}
defineSize(newWidth, newHeight);
if(isNativeValid()) {
if(!defer) {
sendWindowEvent(WindowEvent.EVENT_WINDOW_RESIZED);
} else {
enqueueWindowEvent(false, WindowEvent.EVENT_WINDOW_RESIZED);
}
}
}
}
private boolean waitForSize(final int w, final int h, final boolean failFast, final long timeOut) {
final DisplayImpl display = (DisplayImpl) screen.getDisplay();
display.dispatchMessagesNative(); // status up2date
long sleep;
for(sleep = timeOut; 0= sleep) {
final String msg = "Size/Pos not reached as requested within "+timeOut+"ms : requested "+w+"x"+h+", is "+getWidth()+"x"+getHeight();
if(failFast) {
throw new NativeWindowException(msg);
} else if (DEBUG_IMPLEMENTATION) {
System.err.println(msg);
ExceptionUtils.dumpStack(System.err);
}
return false;
} else {
return true;
}
}
/** Triggered by implementation's WM events to update the position. */
protected final void positionChanged(final boolean defer, final int newX, final int newY) {
if ( getX() != newX || getY() != newY ) {
if(DEBUG_IMPLEMENTATION) {
System.err.println("Window.positionChanged: ("+getThreadName()+"): (defer: "+defer+") "+getX()+"/"+getY()+" -> "+newX+"/"+newY+" - windowHandle "+toHexString(windowHandle)+" parentWindowHandle "+toHexString(parentWindowHandle));
}
definePosition(newX, newY);
if(!defer) {
sendWindowEvent(WindowEvent.EVENT_WINDOW_MOVED);
} else {
enqueueWindowEvent(false, WindowEvent.EVENT_WINDOW_MOVED);
}
} else {
autoPosition = false; // ensure it's off even w/ same position
}
}
/**
* Wait until position is reached within tolerances, either auto-position or custom position.
*
* Since WM may not obey our positional request exactly, we allow a tolerance of 2 times insets[left/top], or 64 pixels, whatever is greater.
*
*/
private boolean waitForPosition(final boolean useCustomPosition, final int x, final int y, final long timeOut) {
final DisplayImpl display = (DisplayImpl) screen.getDisplay();
final int maxDX, maxDY;
{
final InsetsImmutable insets = getInsets();
maxDX = Math.max(64, insets.getLeftWidth() * 2);
maxDY = Math.max(64, insets.getTopHeight() * 2);
}
long remaining = timeOut;
boolean ok;
do {
if( useCustomPosition ) {
ok = Math.abs(x - getX()) <= maxDX && Math.abs(y - getY()) <= maxDY ;
} else {
ok = !autoPosition;
}
if( !ok ) {
try { Thread.sleep(10); } catch (final InterruptedException ie) {}
display.dispatchMessagesNative(); // status up2date
remaining-=10;
}
} while ( 0= 0 && right >= 0 && top >= 0 && bottom >= 0 ) {
if( blockInsetsChange || isUndecorated() ) {
if(DEBUG_IMPLEMENTATION) {
System.err.println("Window.insetsChanged (defer: "+defer+"): Skip insets change "+insets+" -> "+new Insets(left, right, top, bottom)+" (blocked "+blockInsetsChange+", undecoration "+isUndecorated()+")");
}
} else if ( (left != insets.getLeftWidth() || right != insets.getRightWidth() ||
top != insets.getTopHeight() || bottom != insets.getBottomHeight() )
) {
if(DEBUG_IMPLEMENTATION) {
System.err.println("Window.insetsChanged (defer: "+defer+"): Changed "+insets+" -> "+new Insets(left, right, top, bottom));
}
insets.set(left, right, top, bottom);
}
}
}
/**
* Triggered by implementation's WM events or programmatic while respecting {@link #getDefaultCloseOperation()}.
*
* @param force if true, overrides {@link #setDefaultCloseOperation(WindowClosingMode)} with {@link WindowClosingProtocol#DISPOSE_ON_CLOSE}
* and hence force destruction. Otherwise is follows the user settings.
* @return true if this window is no more valid and hence has been destroyed, otherwise false.
*/
public final boolean windowDestroyNotify(final boolean force) {
final WindowClosingMode defMode = getDefaultCloseOperation();
final WindowClosingMode mode = force ? WindowClosingMode.DISPOSE_ON_CLOSE : defMode;
if(DEBUG_IMPLEMENTATION) {
System.err.println("Window.windowDestroyNotify(isNativeValid: "+isNativeValid()+", force: "+force+", mode "+defMode+" -> "+mode+") "+getThreadName()+": "+this);
// ExceptionUtils.dumpStackTrace(System.err);
}
final boolean destroyed;
if( isNativeValid() ) {
if( WindowClosingMode.DISPOSE_ON_CLOSE == mode ) {
if(force) {
setDefaultCloseOperation(mode);
}
try {
if( null == windowDestroyNotifyAction ) {
destroy();
} else {
windowDestroyNotifyAction.run();
}
} finally {
if(force) {
setDefaultCloseOperation(defMode);
}
}
} else {
// send synced destroy notifications
sendWindowEvent(WindowEvent.EVENT_WINDOW_DESTROY_NOTIFY);
}
destroyed = !isNativeValid();
} else {
destroyed = true;
}
if(DEBUG_IMPLEMENTATION) {
System.err.println("Window.windowDestroyNotify(isNativeValid: "+isNativeValid()+", force: "+force+", mode "+mode+") END "+getThreadName()+": destroyed "+destroyed+", "+this);
}
return destroyed;
}
@Override
public final void windowRepaint(final int x, final int y, final int width, final int height) {
windowRepaint(false, x, y, width, height);
}
/**
* Triggered by implementation's WM events to update the content
* @param defer if true sent event later, otherwise wait until processed.
* @param x dirty-region y-pos in pixel units
* @param y dirty-region x-pos in pixel units
* @param width dirty-region width in pixel units
* @param height dirty-region height in pixel units
*/
protected final void windowRepaint(final boolean defer, final int x, final int y, int width, int height) {
width = ( 0 >= width ) ? getSurfaceWidth() : width;
height = ( 0 >= height ) ? getSurfaceHeight() : height;
if(DEBUG_IMPLEMENTATION) {
System.err.println("Window.windowRepaint "+getThreadName()+" (defer: "+defer+") "+x+"/"+y+" "+width+"x"+height);
}
if(isNativeValid()) {
final NEWTEvent e = new WindowUpdateEvent(WindowEvent.EVENT_WINDOW_REPAINT, this, System.currentTimeMillis(),
new Rectangle(x, y, width, height));
doEvent(defer, false, e);
}
}
//
// Reflection helper ..
//
private static Class>[] getCustomConstructorArgumentTypes(final Class> windowClass) {
Class>[] argTypes = null;
try {
final Method m = windowClass.getDeclaredMethod("getCustomConstructorArgumentTypes");
argTypes = (Class[]) m.invoke(null, (Object[])null);
} catch (final Throwable t) {}
return argTypes;
}
private static int verifyConstructorArgumentTypes(final Class>[] types, final Object[] args) {
if(types.length != args.length) {
return -1;
}
for(int i=0; i[] types) {
final StringBuilder sb = new StringBuilder();
for(int i=0; i