jogamp.newt.WindowImpl Maven / Gradle / Ivy
/*
* 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.util.ArrayList;
import java.util.List;
import java.lang.ref.WeakReference;
import java.lang.reflect.Method;
import com.jogamp.common.util.ArrayHashSet;
import com.jogamp.common.util.IntBitfield;
import com.jogamp.common.util.ReflectionUtil;
import com.jogamp.newt.MonitorDevice;
import com.jogamp.newt.NewtFactory;
import com.jogamp.newt.Display;
import com.jogamp.newt.Screen;
import com.jogamp.newt.Window;
import com.jogamp.common.util.locks.LockFactory;
import com.jogamp.common.util.locks.RecursiveLock;
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.MouseEvent;
import com.jogamp.newt.event.MouseListener;
import com.jogamp.newt.event.NEWTEvent;
import com.jogamp.newt.event.NEWTEventConsumer;
import com.jogamp.newt.event.MonitorModeListener;
import com.jogamp.newt.event.WindowEvent;
import com.jogamp.newt.event.WindowListener;
import com.jogamp.newt.event.WindowUpdateEvent;
import com.jogamp.newt.event.MouseEvent.PointerType;
import javax.media.nativewindow.AbstractGraphicsConfiguration;
import javax.media.nativewindow.AbstractGraphicsDevice;
import javax.media.nativewindow.CapabilitiesChooser;
import javax.media.nativewindow.CapabilitiesImmutable;
import javax.media.nativewindow.NativeSurface;
import javax.media.nativewindow.NativeWindow;
import javax.media.nativewindow.NativeWindowException;
import javax.media.nativewindow.NativeWindowFactory;
import javax.media.nativewindow.SurfaceUpdatedListener;
import javax.media.nativewindow.WindowClosingProtocol;
import javax.media.nativewindow.util.DimensionImmutable;
import javax.media.nativewindow.util.Insets;
import javax.media.nativewindow.util.InsetsImmutable;
import javax.media.nativewindow.util.Point;
import javax.media.nativewindow.util.Rectangle;
import javax.media.nativewindow.util.RectangleImmutable;
import jogamp.nativewindow.SurfaceUpdatedHelper;
public abstract class WindowImpl implements Window, NEWTEventConsumer
{
public static final boolean DEBUG_TEST_REPARENT_INCOMPATIBLE;
static {
Debug.initSingleton();
DEBUG_TEST_REPARENT_INCOMPATIBLE = Debug.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(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 width = 128, height = 128; // client-area size w/o insets, default: may be overwritten by user
private volatile int x = 64, y = 64; // client-area pos w/o insets
private volatile Insets insets = new Insets(); // insets of decoration (if top-level && decorated)
private 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 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 boolean pointerVisible = true;
private boolean pointerConfined = false;
private LifecycleHook lifecycleHook = null;
private Runnable windowDestroyNotifyAction = null;
private FocusRunnable focusAction = null;
private KeyListener keyboardFocusHandler = null;
private SurfaceUpdatedHelper surfaceUpdatedHelper = new SurfaceUpdatedHelper();
private Object childWindowsLock = new Object();
private ArrayList childWindows = new ArrayList();
private ArrayList mouseListeners = new ArrayList();
/** from event passing: {@link WindowImpl#consumePointerEvent(MouseEvent)}. */
private static class PointerState0 {
/** mouse entered window - is inside the window (may be synthetic) */
boolean insideWindow = false;
/** last time when a mouse button was pressed */
long lastButtonPressTime = 0;
void clearButton() {
lastButtonPressTime = 0;
}
}
private 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();
lastButtonPressTime = 0;
lastButtonClickCount = (short)0;
buttonPressed = 0;
buttonPressedMask = 0;
}
/** 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(int id) {
if( 0 <= id && id < movePositions.length ) {
return movePositions[id];
}
return null;
}
}
private 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;
/**
* Workaround for initialization order problems on Mac OS X
* between native Newt and (apparently) Fmod -- if Fmod is
* initialized first then the connection to the window server
* breaks, leading to errors from deep within the AppKit
*/
public static void init(String type) {
if (NativeWindowFactory.TYPE_MACOSX.equals(type)) {
try {
getWindowClass(type);
} catch (Exception e) {
e.printStackTrace();
}
}
}
//
// Construction Methods
//
private static Class> getWindowClass(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(NativeWindow parentWindow, long parentWindowHandle, Screen screen, CapabilitiesImmutable caps) {
try {
Class> windowClass;
if(caps.isOnscreen()) {
windowClass = getWindowClass(screen.getDisplay().getType());
} else {
windowClass = OffscreenWindow.class;
}
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 (Throwable t) {
t.printStackTrace();
throw new NativeWindowException(t);
}
}
public static WindowImpl create(Object[] cstrArguments, Screen screen, CapabilitiesImmutable caps) {
try {
Class> windowClass = getWindowClass(screen.getDisplay().getType());
Class>[] cstrArgumentTypes = getCustomConstructorArgumentTypes(windowClass);
if(null==cstrArgumentTypes) {
throw new NativeWindowException("WindowClass "+windowClass+" doesn't support custom arguments in constructor");
}
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));
}
WindowImpl window = (WindowImpl) ReflectionUtil.createInstance( windowClass, cstrArgumentTypes, cstrArguments ) ;
window.screen = (ScreenImpl) screen;
window.capsRequested = (CapabilitiesImmutable) caps.cloneMutable();
window.instantiationFinished();
addWindow2List(window);
return window;
} catch (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(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);
}
// child window: position defaults to 0/0, no auto position, no negative position
if( null != parentWindow && ( 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);
setPointerVisibleImpl(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 {
// 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(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 (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 Object closingListenerLock = new Object();
private WindowClosingMode defaultCloseOperation = WindowClosingMode.DISPOSE_ON_CLOSE;
@Override
public WindowClosingMode getDefaultCloseOperation() {
synchronized (closingListenerLock) {
return defaultCloseOperation;
}
}
@Override
public WindowClosingMode setDefaultCloseOperation(WindowClosingMode op) {
synchronized (closingListenerLock) {
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, or <0 if unchanged
* @param y client-area position, or <0 if unchanged
* @param width client-area size, or <=0 if unchanged
* @param height client-area size, 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(int changeFlags) {
return 0 == ( changeFlags & FLAG_IS_FULLSCREEN_SPAN );
}
protected int getReconfigureFlags(int changeFlags, 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, 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(String title) {}
/**
* Translates the given window client-area coordinates with top-left origin
* to screen coordinates.
*
* 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(javax.media.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(boolean pointerVisible) { return false; }
protected boolean confinePointerImpl(boolean confine) { return false; }
protected void warpPointerImpl(int x, int y) { }
//----------------------------------------------------------------------
// 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 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 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;
}
@Override
public final MonitorDevice getMainMonitor() {
return screen.getMainMonitor(new Rectangle(getX(), getY(), getWidth(), getHeight()));
}
protected final void setVisibleImpl(boolean visible, int x, int y, int width, int height) {
reconfigureWindowImpl(x, y, width, height, getReconfigureFlags(FLAG_CHANGE_VISIBILITY, visible));
}
final void setVisibleActionImpl(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++ ) {
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++ ) {
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(boolean visible) {
this.visible = visible;
}
@Override
public final void run() {
setVisibleActionImpl(visible);
}
}
protected void setVisible(boolean wait, 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 void setVisible(boolean visible) {
setVisible(true, visible);
}
private class SetSizeAction implements Runnable {
int width, height;
boolean disregardFS;
private SetSizeAction(int w, int h, boolean disregardFS) {
this.width = w;
this.height = h;
this.disregardFS = disregardFS;
}
@Override
public final void run() {
final RecursiveLock _lock = windowLock;
_lock.lock();
try {
if ( ( disregardFS || !isFullscreen() ) && ( getWidth() != width || getHeight() != height ) ) {
if(DEBUG_IMPLEMENTATION) {
System.err.println("Window setSize: START "+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 setFullscreenSize(int width, int height) {
runOnEDTIfAvail(true, new SetSizeAction(width, height, true));
}
@Override
public void setSize(int width, int height) {
runOnEDTIfAvail(true, new SetSizeAction(width, height, false));
}
@Override
public void setTopLevelSize(int width, int height) {
setSize(width - getInsets().getTotalWidth(), height - getInsets().getTotalHeight());
}
private class DestroyAction implements Runnable {
@Override
public final void run() {
boolean animatorPaused = false;
if(null!=lifecycleHook) {
animatorPaused = lifecycleHook.pauseRenderingAction();
}
if(null!=lifecycleHook) {
lifecycleHook.destroyActionPreLock();
}
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")
ArrayList clonedChildWindows = (ArrayList) childWindows.clone();
while( clonedChildWindows.size() > 0 ) {
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
lifecycleHook.destroyActionInLock();
}
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();
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*/);
}
} finally {
// update states before release window lock
setWindowHandle(0);
visible = false;
fullscreen = false;
fullscreenMonitors = null;
fullscreenUseMainMonitor = true;
hasFocus = false;
parentWindowHandle = 0;
_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;
*/
}
}
private final DestroyAction destroyAction = new DestroyAction();
@Override
public void destroy() {
visible = false; // Immediately mark synchronized visibility flag, avoiding possible recreation
runOnEDTIfAvail(true, destroyAction);
}
protected void destroy(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(NativeWindow cWin, 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;
boolean forceDestroyCreate;
ReparentOperation operation;
private ReparentAction(NativeWindow newParentWindow, int topLevelX, int topLevelY, boolean forceDestroyCreate) {
this.newParentWindow = newParentWindow;
this.topLevelX = topLevelX;
this.topLevelY = topLevelY;
this.forceDestroyCreate = forceDestroyCreate | DEBUG_TEST_REPARENT_INCOMPATIBLE;
this.operation = ReparentOperation.ACTION_INVALID; // ensure it's set
}
private ReparentOperation getOp() {
return operation;
}
private void setScreen(ScreenImpl newScreen) { // never null !
removeScreenReference();
screen = newScreen;
}
@Override
public final void run() {
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;
boolean wasVisible;
final RecursiveLock _lock = windowLock;
_lock.lock();
try {
if(isNativeValid()) {
// force recreation if offscreen, since it may become onscreen
forceDestroyCreate |= isOffscreenInstance(WindowImpl.this, newParentWindow);
}
wasVisible = isVisible();
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+", old parentWindow: "+Display.hashCodeNullSafe(parentWindow)+", new parentWindow: "+Display.hashCodeNullSafe(newParentWindow)+", forceDestroyCreate "+forceDestroyCreate);
}
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( false );
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( wasVisible );
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( isNativeValid() && wasVisible );
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;
}
// 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 (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) {
ok = WindowImpl.this.waitForSize(width, height, false, TIMEOUT_NATIVEWINDOW);
}
if(ok) {
ok = WindowImpl.this.waitForPosition(true, x, y, TIMEOUT_NATIVEWINDOW);
}
if(ok) {
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( wasVisible );
operation = ReparentOperation.ACTION_NATIVE_CREATION ;
}
} 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 class ReparentActionRecreate implements 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();
}
}
}
private final ReparentActionRecreate reparentActionRecreate = new ReparentActionRecreate();
@Override
public final ReparentOperation reparentWindow(NativeWindow newParent) {
return reparentWindow(newParent, -1, -1, false);
}
@Override
public ReparentOperation reparentWindow(NativeWindow newParent, int x, int y, boolean forceDestroyCreate) {
final ReparentAction reparentAction = new ReparentAction(newParent, x, y, forceDestroyCreate);
runOnEDTIfAvail(true, reparentAction);
return reparentAction.getOp();
}
@Override
public CapabilitiesChooser setCapabilitiesChooser(CapabilitiesChooser chooser) {
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(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();
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 void setUndecorated(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(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();
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(boolean value) {
runOnEDTIfAvail(true, new AlwaysOnTopAction(value));
}
@Override
public final boolean isAlwaysOnTop() {
return alwaysOnTop;
}
@Override
public String getTitle() {
return title;
}
@Override
public void setTitle(String title) {
if (title == null) {
title = "";
}
this.title = title;
if(0 != getWindowHandle()) {
setTitleImpl(title);
}
}
@Override
public boolean isPointerVisible() {
return pointerVisible;
}
@Override
public void setPointerVisible(boolean pointerVisible) {
if(this.pointerVisible != pointerVisible) {
boolean setVal = 0 == getWindowHandle();
if(!setVal) {
setVal = setPointerVisibleImpl(pointerVisible);
}
if(setVal) {
this.pointerVisible = pointerVisible;
}
}
}
@Override
public boolean isPointerConfined() {
return pointerConfined;
}
@Override
public void confinePointer(boolean confine) {
if(this.pointerConfined != confine) {
boolean setVal = 0 == getWindowHandle();
if(!setVal) {
if(confine) {
requestFocus();
warpPointer(getWidth()/2, getHeight()/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 (InterruptedException e) { }
}
}
if(setVal) {
this.pointerConfined = confine;
}
}
}
@Override
public void warpPointer(int x, 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 getWidth() {
return width;
}
@Override
public final int getHeight() {
return height;
}
@Override
public final int getX() {
return x;
}
@Override
public final int getY() {
return y;
}
protected final boolean autoPosition() { return autoPosition; }
/** Sets the position fields {@link #x} and {@link #y} to the given values and {@link #autoPosition} to false. */
protected final void definePosition(int x, int y) {
if(DEBUG_IMPLEMENTATION) {
System.err.println("definePosition: "+this.x+"/"+this.y+" -> "+x+"/"+y);
// Thread.dumpStack();
}
autoPosition = false;
this.x = x; this.y = y;
}
/** Sets the size fields {@link #width} and {@link #height} to the given values. */
protected final void defineSize(int width, int height) {
if(DEBUG_IMPLEMENTATION) {
System.err.println("defineSize: "+this.width+"x"+this.height+" -> "+width+"x"+height);
// Thread.dumpStack();
}
this.width = width; this.height = height;
}
@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 LifecycleHook getLifecycleHook() {
return lifecycleHook;
}
public LifecycleHook setLifecycleHook(LifecycleHook hook) {
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 void setWindowDestroyNotifyAction(Runnable r) {
windowDestroyNotifyAction = r;
}
/**
* Returns the non delegated {@link AbstractGraphicsConfiguration},
* see {@link #getGraphicsConfiguration()}. */
public final AbstractGraphicsConfiguration getPrivateGraphicsConfiguration() {
return config;
}
protected final long getParentWindowHandle() {
return isFullscreen() ? 0 : parentWindowHandle;
}
@Override
public String toString() {
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, Pos "+getX()+"/"+getY()+" (auto "+autoPosition()+"), size "+getWidth()+"x"+getHeight()+
"\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(long handle) {
windowHandle = handle;
}
@Override
public void runOnEDTIfAvail(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 void requestFocus() {
requestFocus(true);
}
@Override
public void requestFocus(boolean wait) {
requestFocus(wait /* wait */, false /* skipFocusAction */, brokenFocusChange /* force */);
}
private void requestFocus(boolean wait, boolean skipFocusAction, boolean force) {
if( isNativeValid() &&
( force || !hasFocus() ) &&
( skipFocusAction || !focusAction() ) ) {
runOnEDTIfAvail(wait, force ? requestFocusActionForced : requestFocusAction);
}
}
/** Internally forcing request focus on current thread */
private void requestFocusInt(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 void setFocusAction(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 void setBrokenFocusChange(boolean v) {
brokenFocusChange = v;
}
@Override
public void setKeyboardFocusHandler(KeyListener l) {
keyboardFocusHandler = l;
}
private class SetPositionAction implements Runnable {
int x, y;
private SetPositionAction(int x, 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));
}
if ( !isFullscreen() && ( getX() != x || getY() != y ) ) {
if(isNativeValid()) {
// this.x/this.y will be set by sizeChanged, triggered by windowing event system
reconfigureWindowImpl(x, y, getWidth(), getHeight(), getReconfigureFlags(0, isVisible()));
// 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(int x, int y) {
autoPosition = false;
runOnEDTIfAvail(true, new SetPositionAction(x, y));
}
@Override
public void setTopLevelPosition(int x, int y) {
setPosition(x + getInsets().getLeftWidth(), y + getInsets().getTopHeight());
}
private class FullScreenAction implements Runnable {
boolean fullscreen;
private boolean init(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();
try {
// set current state
WindowImpl.this.fullscreen = fullscreen;
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.getViewport();
final RectangleImmutable viewport;
final int fs_span_flag;
if(fullscreen) {
if( null == fullscreenMonitors ) {
if( fullscreenUseMainMonitor ) {
fullscreenMonitors = new ArrayList();
fullscreenMonitors.add( getMainMonitor() );
} else {
fullscreenMonitors = getScreen().getMonitorDevices();
}
}
viewport = MonitorDevice.unionOfViewports(new Rectangle(), fullscreenMonitors);
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;
x = viewport.getX();
y = viewport.getY();
w = viewport.getWidth();
h = viewport.getHeight();
} else {
fullscreenUseMainMonitor = true;
fullscreenMonitors = null;
fs_span_flag = 0;
viewport = null;
x = nfs_x;
y = nfs_y;
w = nfs_width;
h = nfs_height;
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();
}
}
}
if(DEBUG_IMPLEMENTATION) {
System.err.println("Window fs: "+fullscreen+" "+x+"/"+y+" "+w+"x"+h+", "+isUndecorated()+
", virtl-screenSize: "+sviewport+", monitorsViewport "+viewport+
", spanning "+(0!=fs_span_flag)+" @ "+Thread.currentThread().getName());
}
final DisplayImpl display = (DisplayImpl) screen.getDisplay();
display.dispatchMessagesNative(); // status up2date
final boolean wasVisible = isVisible();
// Lock parentWindow only during reparenting (attempt)
final NativeWindow parentWindowLocked;
if( null != parentWindow ) {
// fullscreen off: !visible first (fixes X11 unsuccessful return to parent window)
if( !fullscreen && 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 (InterruptedException e) { }
display.dispatchMessagesNative(); // status up2date
}
parentWindowLocked = parentWindow;
if( NativeSurface.LOCK_SURFACE_NOT_READY >= parentWindowLocked.lockSurface() ) {
throw new NativeWindowException("Parent surface lock: not ready: "+parentWindow);
}
} else {
parentWindowLocked = null;
}
try {
reconfigureWindowImpl(x, y, w, h,
getReconfigureFlags( ( ( null != parentWindowLocked ) ? FLAG_CHANGE_PARENTING : 0 ) |
fs_span_flag | FLAG_CHANGE_FULLSCREEN | FLAG_CHANGE_DECORATION, isVisible()) );
} finally {
if(null!=parentWindowLocked) {
parentWindowLocked.unlockSurface();
}
}
display.dispatchMessagesNative(); // status up2date
if(wasVisible) {
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) {
ok = 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 {
_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(boolean fullscreen) {
return setFullscreenImpl(fullscreen, true, null);
}
@Override
public boolean setFullscreen(List monitors) {
return setFullscreenImpl(true, false, monitors);
}
private boolean setFullscreenImpl(boolean fullscreen, boolean useMainMonitor, 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, true /* forceDestroyCreate */);
} 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, true /* forceDestroyCreate */);
nfs_parent = null;
}
}
return this.fullscreen;
}
}
private class MonitorModeListenerImpl implements MonitorModeListener {
boolean animatorPaused = false;
boolean hadFocus = false;
boolean fullscreenPaused = false;
List _fullscreenMonitors = null;
boolean _fullscreenUseMainMonitor = true;
@Override
public void monitorModeChangeNotify(MonitorEvent me) {
hadFocus = hasFocus();
if(DEBUG_IMPLEMENTATION) {
System.err.println("Window.monitorModeChangeNotify: hadFocus "+hadFocus+", "+me+" @ "+Thread.currentThread().getName());
}
if(null!=lifecycleHook) {
animatorPaused = lifecycleHook.pauseRenderingAction();
}
if( fullscreen && isReconfigureFlagSupported(FLAG_IS_FULLSCREEN_SPAN) ) {
if(DEBUG_IMPLEMENTATION) {
System.err.println("Window.monitorModeChangeNotify: FS Pause");
}
fullscreenPaused = true;
_fullscreenMonitors = fullscreenMonitors;
_fullscreenUseMainMonitor = fullscreenUseMainMonitor;
setFullscreenImpl(false, true, null);
}
}
@Override
public void monitorModeChanged(MonitorEvent me, boolean success) {
if(DEBUG_IMPLEMENTATION) {
System.err.println("Window.monitorModeChanged: hadFocus "+hadFocus+", "+me+", success: "+success+" @ "+Thread.currentThread().getName());
}
if(success) {
if(!animatorPaused && null!=lifecycleHook) {
// Didn't pass above notify method. probably detected screen change after it happened.
animatorPaused = lifecycleHook.pauseRenderingAction();
}
if( !fullscreen && !fullscreenPaused ) {
// Simply move/resize window to fit in virtual screen if required
final RectangleImmutable viewport = screen.getViewport();
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: 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());
}
}
} else if( fullscreenPaused ){
if(DEBUG_IMPLEMENTATION) {
System.err.println("Window.monitorModeChanged: FS Restore");
}
setFullscreenImpl(true, _fullscreenUseMainMonitor, _fullscreenMonitors);
fullscreenPaused = false;
_fullscreenMonitors = null;
_fullscreenUseMainMonitor = true;
} else {
// If changed monitor is part of this fullscreen mode, reset size! (Bug 771)
final MonitorDevice md = me.getMonitor();
if( fullscreenMonitors.contains(md) ) {
final RectangleImmutable viewport = MonitorDevice.unionOfViewports(new Rectangle(), fullscreenMonitors);
if(DEBUG_IMPLEMENTATION) {
final RectangleImmutable rect = new Rectangle(getX(), getY(), getWidth(), getHeight());
System.err.println("Window.monitorModeChanged: FS Monitor Match: Fit window "+rect+" into new viewport union "+viewport+", provoked by "+md);
}
definePosition(viewport.getX(), viewport.getY()); // set pos for setVisible(..) or createNative(..) - reduce EDT roundtrip
setFullscreenSize(viewport.getWidth(), viewport.getHeight());
}
}
}
if(animatorPaused) {
lifecycleHook.resumeRenderingAction();
}
sendWindowEvent(WindowEvent.EVENT_WINDOW_RESIZED); // trigger a resize/relayout and repaint to listener
if( hadFocus ) {
requestFocus(true);
}
}
}
private final MonitorModeListenerImpl monitorModeListenerImpl = new MonitorModeListenerImpl();
//----------------------------------------------------------------------
// Child Window Management
//
@Override
public final boolean removeChild(NativeWindow win) {
synchronized(childWindowsLock) {
return childWindows.remove(win);
}
}
@Override
public final boolean addChild(NativeWindow win) {
if (win == null) {
return false;
}
synchronized(childWindowsLock) {
return childWindows.add(win);
}
}
//----------------------------------------------------------------------
// Generic Event Support
//
private void doEvent(boolean enqueue, boolean wait, 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 void enqueueEvent(boolean wait, com.jogamp.newt.event.NEWTEvent event) {
if(isNativeValid()) {
((DisplayImpl)screen.getDisplay()).enqueueEvent(wait, event);
}
}
@Override
public final boolean consumeEvent(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( null != windowLock.getOwner() ) {
// 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 "+Thread.currentThread().getName()+" - queued "+e+", discard-to "+discardTO);
// Thread.dumpStack();
}
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( null != windowLock.getOwner() ) {
final boolean discardTO = QUEUED_EVENT_TO <= System.currentTimeMillis()-e.getWhen();
if(DEBUG_IMPLEMENTATION) {
System.err.println("Window.consumeEvent: RESIZED "+Thread.currentThread().getName()+" - queued "+e+", discard-to "+discardTO);
// Thread.dumpStack();
}
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;
}
//
// SurfaceUpdatedListener Support
//
@Override
public void addSurfaceUpdatedListener(SurfaceUpdatedListener l) {
surfaceUpdatedHelper.addSurfaceUpdatedListener(l);
}
@Override
public void addSurfaceUpdatedListener(int index, SurfaceUpdatedListener l) throws IndexOutOfBoundsException {
surfaceUpdatedHelper.addSurfaceUpdatedListener(index, l);
}
@Override
public void removeSurfaceUpdatedListener(SurfaceUpdatedListener l) {
surfaceUpdatedHelper.removeSurfaceUpdatedListener(l);
}
@Override
public void surfaceUpdated(Object updater, NativeSurface ns, long when) {
surfaceUpdatedHelper.surfaceUpdated(updater, ns, when);
}
//
// MouseListener/Event Support
//
//
// Native MouseEvents pre-processed to be enqueued or consumed directly
//
public final void sendMouseEvent(short eventType, int modifiers,
int x, int y, short button, float rotation) {
doMouseEvent(false, false, eventType, modifiers, x, y, button, MouseEvent.getRotationXYZ(rotation, modifiers), 1f);
}
public final void enqueueMouseEvent(boolean wait, short eventType, int modifiers,
int x, int y, short button, float rotation) {
doMouseEvent(true, wait, eventType, modifiers, x, y, button, MouseEvent.getRotationXYZ(rotation, modifiers), 1f);
}
protected final void doMouseEvent(boolean enqueue, boolean wait, short eventType, int modifiers,
int x, int y, short button, float rotation) {
doMouseEvent(enqueue, wait, eventType, modifiers, x, y, button, MouseEvent.getRotationXYZ(rotation, modifiers), 1f);
}
/**
public final void sendMouseEvent(short eventType, int modifiers,
int x, int y, short button, float[] rotationXYZ, float rotationScale) {
doMouseEvent(false, false, eventType, modifiers, x, y, button, rotationXYZ, rotationScale);
}
public final void enqueueMouseEvent(boolean wait, short eventType, int modifiers,
int x, int y, short button, float[] rotationXYZ, 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(boolean enqueue, boolean wait, short eventType, int modifiers,
int x, int y, short button, final float[] rotationXYZ, 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)(button-1) },
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.
*
*
* @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(boolean enqueue, boolean wait,
final PointerType[] pTypes, short eventType, int modifiers,
int actionIdx, boolean normalPNames, final int[] pNames,
final int[] pX, final int[] pY, float[] pPressure,
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];
}
}
doPointerEvent(enqueue, wait, pTypes, eventType, modifiers, actionIdx, pIDs,
pX, pY, pPressure, 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.
*
*
* @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.
* A pointer-ID of -1 may also denote no pointer/button 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(boolean enqueue, boolean wait,
final PointerType[] pTypes, short eventType, int modifiers,
int pActionIdx, final short[] pID, final int[] pX, final int[] pY, final float[] pPressure,
float maxPressure, final float[] rotationXYZ, 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 id = pID[0];
final short button;
{
final int b = id + 1;
if( 0 <= b && b <= com.jogamp.newt.event.MouseEvent.BUTTON_COUNT ) { // we allow id==-1 -> button==0 for no button, i.e. mouse move
button = (short)b;
} 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 Point movePositionP0 = pState1.getMovePosition(id);
switch( eventType ) {
case MouseEvent.EVENT_MOUSE_EXITED:
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:
// clip coordinates to window dimension
x = Math.min(Math.max(x, 0), getWidth()-1);
y = Math.min(Math.max(y, 0), getHeight()-1);
pState1.insideWindow = eventType == MouseEvent.EVENT_MOUSE_ENTERED;
pState1.clearButton();
break;
case MouseEvent.EVENT_MOUSE_MOVED:
case MouseEvent.EVENT_MOUSE_DRAGGED:
if( null != movePositionP0 ) {
if( pState1.insideWindow && movePositionP0.getX() == x && movePositionP0.getY() == y ) {
if(DEBUG_MOUSE_EVENT) {
System.err.println("doPointerEvent: skip "+MouseEvent.getEventTypeString(eventType)+" w/ same position: "+movePositionP0);
}
return; // skip same position
}
movePositionP0.set(x, y);
}
// Fall through intended !
default:
if(!pState1.insideWindow) {
pState1.insideWindow = true;
pState1.clearButton();
}
}
if( x < 0 || y < 0 || x >= getWidth() || y >= getHeight() ) {
if(DEBUG_MOUSE_EVENT) {
System.err.println("doPointerEvent: drop: "+MouseEvent.getEventTypeString(eventType)+
", mod "+modifiers+", pos "+x+"/"+y+", button "+button+", lastMousePosition: "+movePositionP0);
}
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);
}
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
// - Fix MOVED/DRAGGED event
//
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:
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;
} else {
e = new MouseEvent(eventType, this, when, modifiers, pTypes, pID,
pX, pY, pPressure, maxPressure, button, (short)1, rotationXYZ, rotationScale);
}
pState1.buttonPressedMask &= ~buttonMask;
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);
} 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;
}
// 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
}
@Override
public final void addMouseListener(MouseListener l) {
addMouseListener(-1, l);
}
@Override
public final void addMouseListener(int index, MouseListener l) {
if(l == null) {
return;
}
@SuppressWarnings("unchecked")
ArrayList clonedListeners = (ArrayList) mouseListeners.clone();
if(0>index) {
index = clonedListeners.size();
}
clonedListeners.add(index, l);
mouseListeners = clonedListeners;
}
@Override
public final void removeMouseListener(MouseListener l) {
if (l == null) {
return;
}
@SuppressWarnings("unchecked")
ArrayList clonedListeners = (ArrayList) mouseListeners.clone();
clonedListeners.remove(l);
mouseListeners = clonedListeners;
}
@Override
public final MouseListener getMouseListener(int index) {
@SuppressWarnings("unchecked")
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(boolean enable) {
defaultGestureHandlerEnabled = enable;
}
@Override
public final boolean areDefaultGesturesEnabled() {
return defaultGestureHandlerEnabled;
}
@Override
public final void addGestureHandler(GestureHandler gh) {
addGestureHandler(-1, gh);
}
@Override
public final void addGestureHandler(int index, GestureHandler gh) {
if(gh == null) {
return;
}
@SuppressWarnings("unchecked")
ArrayList cloned = (ArrayList) pointerGestureHandler.clone();
if(0>index) {
index = cloned.size();
}
cloned.add(index, gh);
pointerGestureHandler = cloned;
}
@Override
public final void removeGestureHandler(GestureHandler gh) {
if (gh == null) {
return;
}
@SuppressWarnings("unchecked")
ArrayList cloned = (ArrayList) pointerGestureHandler.clone();
cloned.remove(gh);
pointerGestureHandler = cloned;
}
@Override
public final void addGestureListener(GestureHandler.GestureListener gl) {
addGestureListener(-1, gl);
}
@Override
public final void addGestureListener(int index, GestureHandler.GestureListener gl) {
if(gl == null) {
return;
}
@SuppressWarnings("unchecked")
ArrayList cloned = (ArrayList) gestureListeners.clone();
if(0>index) {
index = cloned.size();
}
cloned.add(index, gl);
gestureListeners = cloned;
}
@Override
public final void removeGestureListener(GestureHandler.GestureListener gl) {
if (gl == null) {
return;
}
@SuppressWarnings("unchecked")
ArrayList cloned = (ArrayList) gestureListeners.clone();
cloned.remove(gl);
gestureListeners= cloned;
}
private static int step(int lower, int edge, int value) {
return value < edge ? lower : value;
}
/**
* Consume the {@link MouseEvent}, i.e.
*
* - validate
* - handle gestures
* - synthesize events if applicable (like gestures)
* - dispatch event to listener
*
*/
protected void consumePointerEvent(MouseEvent pe) {
int x = pe.getX();
int y = pe.getY();
if(DEBUG_MOUSE_EVENT) {
System.err.println("consumePointerEvent.in: "+pe);
}
//
// - Determine ENTERED/EXITED state
// - Synthesize ENTERED event
// - Reset states if applicable
//
final long when = pe.getWhen();
int eventType = pe.getEventType();
final MouseEvent eEntered;
switch( eventType ) {
case MouseEvent.EVENT_MOUSE_EXITED:
case MouseEvent.EVENT_MOUSE_ENTERED:
// clip coordinates to window dimension
x = Math.min(Math.max(x, 0), getWidth()-1);
y = Math.min(Math.max(y, 0), getHeight()-1);
pState0.insideWindow = eventType == MouseEvent.EVENT_MOUSE_ENTERED;
pState0.clearButton();
eEntered = null;
break;
default:
if(!pState0.insideWindow) {
pState0.insideWindow = true;
pState0.clearButton();
eEntered = pe.createVariant(MouseEvent.EVENT_MOUSE_ENTERED);
} else {
eEntered = null;
}
}
if( null != eEntered ) {
if(DEBUG_MOUSE_EVENT) {
System.err.println("consumePointerEvent.send.0: "+eEntered);
}
dispatchMouseEvent(eEntered);
} else if( x < 0 || y < 0 || x >= getWidth() || y >= getHeight() ) {
if(DEBUG_MOUSE_EVENT) {
System.err.println("consumePointerEvent.drop: "+pe);
}
return; // .. invalid ..
}
//
// 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);
}
} 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);
}
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
//
final MouseEvent eClicked;
switch( eventType ) {
case MouseEvent.EVENT_MOUSE_PRESSED:
if( 1 == pe.getPointerCount() ) {
pState0.lastButtonPressTime = when;
}
eClicked = null;
break;
case MouseEvent.EVENT_MOUSE_RELEASED:
if( 1 == pe.getPointerCount() && when - pState0.lastButtonPressTime < MouseEvent.getClickTimeout() ) {
eClicked = pe.createVariant(MouseEvent.EVENT_MOUSE_CLICKED);
} else {
eClicked = null;
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);
}
pe = null;
eClicked = null;
break;
default:
eClicked = null;
}
if( null != pe ) {
if(DEBUG_MOUSE_EVENT) {
System.err.println("consumePointerEvent.send.1: "+pe);
}
dispatchMouseEvent(pe); // actual mouse event
}
if( null != eClicked ) {
if(DEBUG_MOUSE_EVENT) {
System.err.println("consumePointerEvent.send.2: "+eClicked);
}
dispatchMouseEvent(eClicked);
}
}
private final void dispatchMouseEvent(MouseEvent e) {
for(int i = 0; !e.isConsumed() && i < mouseListeners.size(); i++ ) {
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 & (int)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(short keyCode, boolean pressed) {
final int v = 0xFFFF & (int)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(short keyCode) {
final int v = 0xFFFF & (int)keyCode;
if( v <= keyTrackingRange ) {
return keyPressedState.get(v);
}
return false;
}
public void sendKeyEvent(short eventType, int modifiers, short keyCode, short keySym, 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(boolean wait, short eventType, int modifiers, short keyCode, short keySym, 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(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(boolean visible) {
return false; // nop
}
/** Triggered by implementation's WM events to update the virtual on-screen keyboard's visibility state. */
protected void keyboardVisibilityChanged(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 void addKeyListener(KeyListener l) {
addKeyListener(-1, l);
}
@Override
public void addKeyListener(int index, KeyListener l) {
if(l == null) {
return;
}
@SuppressWarnings("unchecked")
ArrayList clonedListeners = (ArrayList) keyListeners.clone();
if(0>index) {
index = clonedListeners.size();
}
clonedListeners.add(index, l);
keyListeners = clonedListeners;
}
@Override
public void removeKeyListener(KeyListener l) {
if (l == null) {
return;
}
@SuppressWarnings("unchecked")
ArrayList clonedListeners = (ArrayList) keyListeners.clone();
clonedListeners.remove(l);
keyListeners = clonedListeners;
}
@Override
public KeyListener getKeyListener(int index) {
@SuppressWarnings("unchecked")
ArrayList clonedListeners = (ArrayList) keyListeners.clone();
if(0>index) {
index = clonedListeners.size()-1;
}
return clonedListeners.get(index);
}
@Override
public KeyListener[] getKeyListeners() {
return keyListeners.toArray(new KeyListener[keyListeners.size()]);
}
private final boolean propagateKeyEvent(KeyEvent e, 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(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 void sendWindowEvent(int eventType) {
consumeWindowEvent( new WindowEvent((short)eventType, this, System.currentTimeMillis()) ); // FIXME
}
public void enqueueWindowEvent(boolean wait, int eventType) {
enqueueEvent( wait, new WindowEvent((short)eventType, this, System.currentTimeMillis()) ); // FIXME
}
@Override
public void addWindowListener(WindowListener l) {
addWindowListener(-1, l);
}
@Override
public void addWindowListener(int index, WindowListener l)
throws IndexOutOfBoundsException
{
if(l == null) {
return;
}
@SuppressWarnings("unchecked")
ArrayList clonedListeners = (ArrayList) windowListeners.clone();
if(0>index) {
index = clonedListeners.size();
}
clonedListeners.add(index, l);
windowListeners = clonedListeners;
}
@Override
public final void removeWindowListener(WindowListener l) {
if (l == null) {
return;
}
@SuppressWarnings("unchecked")
ArrayList clonedListeners = (ArrayList) windowListeners.clone();
clonedListeners.remove(l);
windowListeners = clonedListeners;
}
@Override
public WindowListener getWindowListener(int index) {
@SuppressWarnings("unchecked")
ArrayList clonedListeners = (ArrayList) windowListeners.clone();
if(0>index) {
index = clonedListeners.size()-1;
}
return clonedListeners.get(index);
}
@Override
public WindowListener[] getWindowListeners() {
return windowListeners.toArray(new WindowListener[windowListeners.size()]);
}
protected void consumeWindowEvent(WindowEvent e) {
if(DEBUG_IMPLEMENTATION) {
System.err.println("consumeWindowEvent: "+e+", visible "+isVisible()+" "+getX()+"/"+getY()+" "+getWidth()+"x"+getHeight());
}
for(int i = 0; !e.isConsumed() && i < windowListeners.size(); i++ ) {
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(boolean defer, 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 void visibleChanged(boolean defer, 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(boolean visible, boolean failFast) {
return waitForVisible(visible, failFast, TIMEOUT_NATIVEWINDOW);
}
/** Returns -1 if failed, otherwise remaining time until timeOut
, maybe zero. */
private long waitForVisible(boolean visible, boolean failFast, 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(int w, int h, boolean failFast, 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);
Thread.dumpStack();
}
return false;
} else {
return true;
}
}
/** Triggered by implementation's WM events to update the position. */
protected void positionChanged(boolean defer, int newX, 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(boolean useCustomPosition, int x, int y, 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 (InterruptedException ie) {}
display.dispatchMessagesNative(); // status up2date
remaining-=10;
}
} while ( 0= 0 && right >= 0 && top >= 0 && bottom >= 0 ) {
if(isUndecorated()) {
if(DEBUG_IMPLEMENTATION) {
System.err.println("Window.insetsChanged: skip insets change for undecoration mode");
}
} else if ( (left != insets.getLeftWidth() || right != insets.getRightWidth() ||
top != insets.getTopHeight() || bottom != insets.getBottomHeight() )
) {
insets.set(left, right, top, bottom);
if(DEBUG_IMPLEMENTATION) {
System.err.println("Window.insetsChanged: (defer: "+defer+") "+insets);
}
}
}
}
/**
* 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 boolean windowDestroyNotify(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);
// Thread.dumpStack();
}
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 void windowRepaint(int x, int y, int width, int height) {
windowRepaint(false, x, y, width, height);
}
/**
* Triggered by implementation's WM events to update the content
*/
protected void windowRepaint(boolean defer, int x, int y, int width, int height) {
width = ( 0 >= width ) ? getWidth() : width;
height = ( 0 >= height ) ? getHeight() : height;
if(DEBUG_IMPLEMENTATION) {
System.err.println("Window.windowRepaint "+getThreadName()+" (defer: "+defer+") "+x+"/"+y+" "+width+"x"+height);
}
if(isNativeValid()) {
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(Class> windowClass) {
Class>[] argTypes = null;
try {
Method m = windowClass.getDeclaredMethod("getCustomConstructorArgumentTypes");
argTypes = (Class[]) m.invoke(null, (Object[])null);
} catch (Throwable t) {}
return argTypes;
}
private static int verifyConstructorArgumentTypes(Class>[] types, Object[] args) {
if(types.length != args.length) {
return -1;
}
for(int i=0; i[] types) {
StringBuilder sb = new StringBuilder();
for(int i=0; i