Jogamp
5288bfcc5d34d398d2174ccc7d16bbeb2f66a564
[jogl.git] / src / newt / classes / jogamp / newt / WindowImpl.java
1 /*
2  * Copyright (c) 2008 Sun Microsystems, Inc. All Rights Reserved.
3    Copyright (c) 2010 JogAmp Community. All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions are
7  * met:
8  *
9  * - Redistribution of source code must retain the above copyright
10  *   notice, this list of conditions and the following disclaimer.
11  *
12  * - Redistribution in binary form must reproduce the above copyright
13  *   notice, this list of conditions and the following disclaimer in the
14  *   documentation and/or other materials provided with the distribution.
15  *
16  * Neither the name of Sun Microsystems, Inc. or the names of
17  * contributors may be used to endorse or promote products derived from
18  * this software without specific prior written permission.
19  *
20  * This software is provided "AS IS," without a warranty of any kind. ALL
21  * EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES,
22  * INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A
23  * PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN
24  * MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL NOT BE LIABLE FOR
25  * ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING OR
26  * DISTRIBUTING THIS SOFTWARE OR ITS DERIVATIVES. IN NO EVENT WILL SUN OR
27  * ITS LICENSORS BE LIABLE FOR ANY LOST REVENUE, PROFIT OR DATA, OR FOR
28  * DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL, INCIDENTAL OR PUNITIVE
29  * DAMAGES, HOWEVER CAUSED AND REGARDLESS OF THE THEORY OF LIABILITY,
30  * ARISING OUT OF THE USE OF OR INABILITY TO USE THIS SOFTWARE, EVEN IF
31  * SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
32  *
33  */
34
35 package jogamp.newt;
36
37 import java.lang.ref.WeakReference;
38 import java.lang.reflect.Method;
39 import java.util.ArrayList;
40 import java.util.List;
41
42 import javax.media.nativewindow.AbstractGraphicsConfiguration;
43 import javax.media.nativewindow.AbstractGraphicsDevice;
44 import javax.media.nativewindow.CapabilitiesChooser;
45 import javax.media.nativewindow.CapabilitiesImmutable;
46 import javax.media.nativewindow.NativeSurface;
47 import javax.media.nativewindow.NativeWindow;
48 import javax.media.nativewindow.NativeWindowException;
49 import javax.media.nativewindow.NativeWindowFactory;
50 import javax.media.nativewindow.OffscreenLayerSurface;
51 import javax.media.nativewindow.SurfaceUpdatedListener;
52 import javax.media.nativewindow.WindowClosingProtocol;
53 import javax.media.nativewindow.util.DimensionImmutable;
54 import javax.media.nativewindow.util.Insets;
55 import javax.media.nativewindow.util.InsetsImmutable;
56 import javax.media.nativewindow.util.PixelRectangle;
57 import javax.media.nativewindow.util.Point;
58 import javax.media.nativewindow.util.PointImmutable;
59 import javax.media.nativewindow.util.Rectangle;
60 import javax.media.nativewindow.util.RectangleImmutable;
61
62 import jogamp.nativewindow.SurfaceUpdatedHelper;
63
64 import com.jogamp.common.util.ArrayHashSet;
65 import com.jogamp.common.util.IntBitfield;
66 import com.jogamp.common.util.ReflectionUtil;
67 import com.jogamp.common.util.locks.LockFactory;
68 import com.jogamp.common.util.locks.RecursiveLock;
69 import com.jogamp.newt.Display;
70 import com.jogamp.newt.Display.PointerIcon;
71 import com.jogamp.newt.MonitorDevice;
72 import com.jogamp.newt.NewtFactory;
73 import com.jogamp.newt.Screen;
74 import com.jogamp.newt.Window;
75 import com.jogamp.newt.event.DoubleTapScrollGesture;
76 import com.jogamp.newt.event.GestureHandler;
77 import com.jogamp.newt.event.InputEvent;
78 import com.jogamp.newt.event.KeyEvent;
79 import com.jogamp.newt.event.KeyListener;
80 import com.jogamp.newt.event.MonitorEvent;
81 import com.jogamp.newt.event.MonitorModeListener;
82 import com.jogamp.newt.event.MouseEvent;
83 import com.jogamp.newt.event.MouseEvent.PointerType;
84 import com.jogamp.newt.event.MouseListener;
85 import com.jogamp.newt.event.NEWTEvent;
86 import com.jogamp.newt.event.NEWTEventConsumer;
87 import com.jogamp.newt.event.WindowEvent;
88 import com.jogamp.newt.event.WindowListener;
89 import com.jogamp.newt.event.WindowUpdateEvent;
90
91 public abstract class WindowImpl implements Window, NEWTEventConsumer
92 {
93     public static final boolean DEBUG_TEST_REPARENT_INCOMPATIBLE;
94
95     static {
96         Debug.initSingleton();
97         DEBUG_TEST_REPARENT_INCOMPATIBLE = Debug.isPropertyDefined("newt.test.Window.reparent.incompatible", true);
98
99         ScreenImpl.initSingleton();
100     }
101
102     protected static final ArrayList<WeakReference<WindowImpl>> windowList = new ArrayList<WeakReference<WindowImpl>>();
103
104     /** Maybe utilized at a shutdown hook, impl. does not block. */
105     public static final void shutdownAll() {
106         final int wCount = windowList.size();
107         if(DEBUG_IMPLEMENTATION) {
108             System.err.println("Window.shutdownAll "+wCount+" instances, on thread "+getThreadName());
109         }
110         for(int i=0; i<wCount && windowList.size()>0; i++) { // be safe ..
111             final WindowImpl w = windowList.remove(0).get();
112             if(DEBUG_IMPLEMENTATION) {
113                 final long wh = null != w ? w.getWindowHandle() : 0;
114                 System.err.println("Window.shutdownAll["+(i+1)+"/"+wCount+"]: "+toHexString(wh)+", GCed "+(null==w));
115             }
116             if( null != w ) {
117                 w.shutdown();
118             }
119         }
120     }
121     private static void addWindow2List(WindowImpl window) {
122         synchronized(windowList) {
123             // GC before add
124             int i=0, gced=0;
125             while( i < windowList.size() ) {
126                 if( null == windowList.get(i).get() ) {
127                     gced++;
128                     windowList.remove(i);
129                 } else {
130                     i++;
131                 }
132             }
133             windowList.add(new WeakReference<WindowImpl>(window));
134             if(DEBUG_IMPLEMENTATION) {
135                 System.err.println("Window.addWindow2List: GCed "+gced+", size "+windowList.size());
136             }
137         }
138     }
139
140     /** Timeout of queued events (repaint and resize) */
141     static final long QUEUED_EVENT_TO = 1200; // ms
142
143     private static final PointerType[] constMousePointerTypes = new PointerType[] { PointerType.Mouse };
144
145     //
146     // Volatile: Multithread Mutable Access
147     //
148     private volatile long windowHandle = 0; // lifecycle critical
149     private volatile boolean visible = false; // lifecycle critical
150     private volatile boolean hasFocus = false;
151     private volatile int pixWidth = 128, pixHeight = 128; // client-area size w/o insets in pixel units, default: may be overwritten by user
152     private volatile int winWidth = 128, winHeight = 128; // client-area size w/o insets in window units, default: may be overwritten by user
153     private volatile int x = 64, y = 64; // client-area pos w/o insets in window units
154     private volatile Insets insets = new Insets(); // insets of decoration (if top-level && decorated)
155     private boolean blockInsetsChange = false; // block insets change (from same thread)
156
157     private final RecursiveLock windowLock = LockFactory.createRecursiveLock();  // Window instance wide lock
158     private int surfaceLockCount = 0; // surface lock recursion count
159
160     private ScreenImpl screen; // never null after create - may change reference though (reparent)
161     private boolean screenReferenceAdded = false;
162     private NativeWindow parentWindow = null;
163     private long parentWindowHandle = 0;
164     private AbstractGraphicsConfiguration config = null; // control access due to delegation
165     protected CapabilitiesImmutable capsRequested = null;
166     protected CapabilitiesChooser capabilitiesChooser = null; // default null -> default
167     private boolean fullscreen = false, brokenFocusChange = false;
168     private List<MonitorDevice> fullscreenMonitors = null;
169     private boolean fullscreenUseMainMonitor = true;
170     private boolean autoPosition = true; // default: true (allow WM to choose top-level position, if not set by user)
171
172     private int nfs_width, nfs_height, nfs_x, nfs_y; // non fullscreen client-area size/pos w/o insets
173     private boolean nfs_alwaysOnTop; // non fullscreen alwaysOnTop setting
174     private NativeWindow nfs_parent = null;          // non fullscreen parent, in case explicit reparenting is performed (offscreen)
175     private String title = "Newt Window";
176     private boolean undecorated = false;
177     private boolean alwaysOnTop = false;
178     private PointerIconImpl pointerIcon = null;
179     private boolean pointerVisible = true;
180     private boolean pointerConfined = false;
181     private LifecycleHook lifecycleHook = null;
182
183     private Runnable windowDestroyNotifyAction = null;
184
185     private FocusRunnable focusAction = null;
186     private KeyListener keyboardFocusHandler = null;
187
188     private final SurfaceUpdatedHelper surfaceUpdatedHelper = new SurfaceUpdatedHelper();
189
190     private final Object childWindowsLock = new Object();
191     private final ArrayList<NativeWindow> childWindows = new ArrayList<NativeWindow>();
192
193     private ArrayList<MouseListener> mouseListeners = new ArrayList<MouseListener>();
194
195     /** from event passing: {@link WindowImpl#consumePointerEvent(MouseEvent)}. */
196     private static class PointerState0 {
197         /** Pointer entered window - is inside the window (may be synthetic) */
198         boolean insideSurface = false;
199         /** Mouse EXIT has been sent (only for MOUSE type enter/exit)*/
200         boolean exitSent = false;
201
202         /** last time when a pointer button was pressed */
203         long lastButtonPressTime = 0;
204
205         /** Pointer in dragging mode */
206         boolean dragging = false;
207
208         void clearButton() {
209             lastButtonPressTime = 0;
210         }
211         public String toString() { return "PState0[inside "+insideSurface+", exitSent "+exitSent+", lastPress "+lastButtonPressTime+", dragging "+dragging+"]"; }
212     }
213     private final PointerState0 pState0 = new PointerState0();
214
215     /** from direct input: {@link WindowImpl#doPointerEvent(boolean, boolean, int[], short, int, int, boolean, short[], int[], int[], float[], float, float[], float)}. */
216     private static class PointerState1 extends PointerState0 {
217         /** Current pressed mouse button number */
218         short buttonPressed = (short)0;
219         /** Current pressed mouse button modifier mask */
220         int buttonPressedMask = 0;
221         /** Last mouse button click count */
222         short lastButtonClickCount = (short)0;
223
224         @Override
225         final void clearButton() {
226             super.clearButton();
227             lastButtonClickCount = (short)0;
228             if( !dragging || 0 == buttonPressedMask ) {
229                 buttonPressed = 0;
230                 buttonPressedMask = 0;
231                 dragging = false;
232             }
233         }
234
235         /** Last pointer-move position for 8 touch-down pointers */
236         final Point[] movePositions = new Point[] {
237                 new Point(), new Point(), new Point(), new Point(),
238                 new Point(), new Point(), new Point(), new Point() };
239         final Point getMovePosition(int id) {
240             if( 0 <= id && id < movePositions.length ) {
241                 return movePositions[id];
242             }
243             return null;
244         }
245         public final String toString() { return "PState1[inside "+insideSurface+", exitSent "+exitSent+", lastPress "+lastButtonPressTime+
246                             ", pressed [button "+buttonPressed+", mask "+buttonPressedMask+", dragging "+dragging+", clickCount "+lastButtonClickCount+"]"; }
247     }
248     private final PointerState1 pState1 = new PointerState1();
249
250     /** Pointer names -> pointer ID (consecutive index, starting w/ 0) */
251     private final ArrayHashSet<Integer> pName2pID = new ArrayHashSet<Integer>();
252
253     private boolean defaultGestureHandlerEnabled = true;
254     private DoubleTapScrollGesture gesture2PtrTouchScroll = null;
255     private ArrayList<GestureHandler> pointerGestureHandler = new ArrayList<GestureHandler>();
256
257     private ArrayList<GestureHandler.GestureListener> gestureListeners = new ArrayList<GestureHandler.GestureListener>();
258
259     private ArrayList<KeyListener> keyListeners = new ArrayList<KeyListener>();
260
261     private ArrayList<WindowListener> windowListeners  = new ArrayList<WindowListener>();
262     private boolean repaintQueued = false;
263
264     //
265     // Construction Methods
266     //
267
268     private static Class<?> getWindowClass(String type)
269         throws ClassNotFoundException
270     {
271         final Class<?> windowClass = NewtFactory.getCustomClass(type, "WindowDriver");
272         if(null==windowClass) {
273             throw new ClassNotFoundException("Failed to find NEWT Window Class <"+type+".WindowDriver>");
274         }
275         return windowClass;
276     }
277
278     public static WindowImpl create(NativeWindow parentWindow, long parentWindowHandle, Screen screen, CapabilitiesImmutable caps) {
279         try {
280             Class<?> windowClass;
281             if(caps.isOnscreen()) {
282                 windowClass = getWindowClass(screen.getDisplay().getType());
283             } else {
284                 windowClass = OffscreenWindow.class;
285             }
286             WindowImpl window = (WindowImpl) windowClass.newInstance();
287             window.parentWindow = parentWindow;
288             window.parentWindowHandle = parentWindowHandle;
289             window.screen = (ScreenImpl) screen;
290             window.capsRequested = (CapabilitiesImmutable) caps.cloneMutable();
291             window.instantiationFinished();
292             addWindow2List(window);
293             return window;
294         } catch (Throwable t) {
295             t.printStackTrace();
296             throw new NativeWindowException(t);
297         }
298     }
299
300     public static WindowImpl create(Object[] cstrArguments, Screen screen, CapabilitiesImmutable caps) {
301         try {
302             Class<?> windowClass = getWindowClass(screen.getDisplay().getType());
303             Class<?>[] cstrArgumentTypes = getCustomConstructorArgumentTypes(windowClass);
304             if(null==cstrArgumentTypes) {
305                 throw new NativeWindowException("WindowClass "+windowClass+" doesn't support custom arguments in constructor");
306             }
307             int argsChecked = verifyConstructorArgumentTypes(cstrArgumentTypes, cstrArguments);
308             if ( argsChecked < cstrArguments.length ) {
309                 throw new NativeWindowException("WindowClass "+windowClass+" constructor mismatch at argument #"+argsChecked+"; Constructor: "+getTypeStrList(cstrArgumentTypes)+", arguments: "+getArgsStrList(cstrArguments));
310             }
311             WindowImpl window = (WindowImpl) ReflectionUtil.createInstance( windowClass, cstrArgumentTypes, cstrArguments ) ;
312             window.screen = (ScreenImpl) screen;
313             window.capsRequested = (CapabilitiesImmutable) caps.cloneMutable();
314             window.instantiationFinished();
315             addWindow2List(window);
316             return window;
317         } catch (Throwable t) {
318             throw new NativeWindowException(t);
319         }
320     }
321
322     /** Fast invalidation of instance w/o any blocking function call. */
323     private final void shutdown() {
324         if(null!=lifecycleHook) {
325             lifecycleHook.shutdownRenderingAction();
326         }
327         setWindowHandle(0);
328         visible = false;
329         fullscreen = false;
330         fullscreenMonitors = null;
331         fullscreenUseMainMonitor = true;
332         hasFocus = false;
333         parentWindowHandle = 0;
334     }
335
336     protected final void setGraphicsConfiguration(AbstractGraphicsConfiguration cfg) {
337         config = cfg;
338     }
339
340     public static interface LifecycleHook {
341         /**
342          * Reset of internal state counter, ie totalFrames, etc.
343          * Called from EDT while window is locked.
344          */
345         public abstract void resetCounter();
346
347         /**
348          * Invoked after Window setVisible,
349          * allows allocating resources depending on the native Window.
350          * Called from EDT while window is locked.
351          */
352         void setVisibleActionPost(boolean visible, boolean nativeWindowCreated);
353
354         /**
355          * Notifies the receiver to preserve resources (GL, ..)
356          * for the next destroy*() calls (only), if supported and if <code>value</code> is <code>true</code>, otherwise clears preservation flag.
357          * @param value <code>true</code> to set the one-shot preservation if supported, otherwise clears it.
358          */
359         void preserveGLStateAtDestroy(boolean value);
360
361         /**
362          * Invoked before Window destroy action,
363          * allows releasing of resources depending on the native Window.<br>
364          * Surface not locked yet.<br>
365          * Called not necessarily from EDT.
366          */
367         void destroyActionPreLock();
368
369         /**
370          * Invoked before Window destroy action,
371          * allows releasing of resources depending on the native Window.<br>
372          * Surface locked.<br>
373          * Called from EDT while window is locked.
374          */
375         void destroyActionInLock();
376
377         /**
378          * Invoked for expensive modifications, ie while reparenting and MonitorMode change.<br>
379          * No lock is hold when invoked.<br>
380          *
381          * @return true is paused, otherwise false. If true {@link #resumeRenderingAction()} shall be issued.
382          *
383          * @see #resumeRenderingAction()
384          */
385         boolean pauseRenderingAction();
386
387         /**
388          * Invoked for expensive modifications, ie while reparenting and MonitorMode change.
389          * No lock is hold when invoked.<br>
390          *
391          * @see #pauseRenderingAction()
392          */
393         void resumeRenderingAction();
394
395         /**
396          * Shutdown rendering action (thread) abnormally.
397          * <p>
398          * Should be called only at shutdown, if necessary.
399          * </p>
400          */
401         void shutdownRenderingAction();
402     }
403
404     private boolean createNative() {
405         long tStart;
406         if(DEBUG_IMPLEMENTATION) {
407             tStart = System.nanoTime();
408             System.err.println("Window.createNative() START ("+getThreadName()+", "+this+")");
409         } else {
410             tStart = 0;
411         }
412
413         if( null != parentWindow &&
414             NativeSurface.LOCK_SURFACE_NOT_READY >= parentWindow.lockSurface() ) {
415             throw new NativeWindowException("Parent surface lock: not ready: "+parentWindow);
416         }
417
418         final boolean hasParent = null != parentWindow || 0 != this.parentWindowHandle;
419
420         // child window: position defaults to 0/0, no auto position, no negative position
421         if( hasParent && ( autoPosition || 0>getX() || 0>getY() ) ) {
422             definePosition(0, 0);
423         }
424         boolean postParentlockFocus = false;
425         try {
426             if(validateParentWindowHandle()) {
427                 if( !screenReferenceAdded ) {
428                     screen.addReference();
429                     screenReferenceAdded = true;
430                 }
431                 if(canCreateNativeImpl()) {
432                     final int wX, wY;
433                     final boolean usePosition;
434                     if( autoPosition  ) {
435                         wX = 0;
436                         wY = 0;
437                         usePosition = false;
438                     } else {
439                         wX = getX();
440                         wY = getY();
441                         usePosition = true;
442                     }
443                     final long t0 = System.currentTimeMillis();
444                     createNativeImpl();
445                     screen.addMonitorModeListener(monitorModeListenerImpl);
446                     setTitleImpl(title);
447                     setPointerIconIntern(pointerIcon);
448                     setPointerVisibleIntern(pointerVisible);
449                     confinePointerImpl(pointerConfined);
450                     setKeyboardVisible(keyboardVisible);
451                     final long remainingV = waitForVisible(true, false);
452                     if( 0 <= remainingV ) {
453                         if(isFullscreen()) {
454                             synchronized(fullScreenAction) {
455                                 fullscreen = false; // trigger a state change
456                                 fullScreenAction.init(true);
457                                 fullScreenAction.run();
458                             }
459                         } else if ( !hasParent ) {
460                             // Wait until position is reached within tolerances, either auto-position or custom position.
461                             waitForPosition(usePosition, wX, wY, Window.TIMEOUT_NATIVEWINDOW);
462                         }
463                         if (DEBUG_IMPLEMENTATION) {
464                             System.err.println("Window.createNative(): elapsed "+(System.currentTimeMillis()-t0)+" ms");
465                         }
466                         postParentlockFocus = true;
467                     }
468                 }
469             }
470         } finally {
471             if(null!=parentWindow) {
472                 parentWindow.unlockSurface();
473             }
474         }
475         if(postParentlockFocus) {
476             // harmonize focus behavior for all platforms: focus on creation
477             requestFocusInt(isFullscreen() /* skipFocusAction if fullscreen */);
478             ((DisplayImpl) screen.getDisplay()).dispatchMessagesNative(); // status up2date
479         }
480         if(DEBUG_IMPLEMENTATION) {
481             System.err.println("Window.createNative() END ("+getThreadName()+", "+this+") total "+ (System.nanoTime()-tStart)/1e6 +"ms");
482         }
483         return isNativeValid() ;
484     }
485
486     private void removeScreenReference() {
487         if(screenReferenceAdded) {
488             // be nice, probably already called recursive via
489             //   closeAndInvalidate() -> closeNativeIml() -> .. -> windowDestroyed() -> closeAndInvalidate() !
490             // or via reparentWindow .. etc
491             screenReferenceAdded = false;
492             screen.removeReference();
493         }
494     }
495
496     private boolean validateParentWindowHandle() {
497         if(null!=parentWindow) {
498             parentWindowHandle = getNativeWindowHandle(parentWindow);
499             return 0 != parentWindowHandle ;
500         }
501         return true;
502     }
503
504     private static long getNativeWindowHandle(NativeWindow nativeWindow) {
505         long handle = 0;
506         if(null!=nativeWindow) {
507             boolean wasLocked = false;
508             if( NativeSurface.LOCK_SURFACE_NOT_READY < nativeWindow.lockSurface() ) {
509                 wasLocked = true;
510                 try {
511                     handle = nativeWindow.getWindowHandle();
512                     if(0==handle) {
513                         throw new NativeWindowException("Parent native window handle is NULL, after succesful locking: "+nativeWindow);
514                     }
515                 } catch (NativeWindowException nwe) {
516                     if(DEBUG_IMPLEMENTATION) {
517                         System.err.println("Window.getNativeWindowHandle: not successful yet: "+nwe);
518                     }
519                 } finally {
520                     nativeWindow.unlockSurface();
521                 }
522             }
523             if(DEBUG_IMPLEMENTATION) {
524                 System.err.println("Window.getNativeWindowHandle: locked "+wasLocked+", "+nativeWindow);
525             }
526         }
527         return handle;
528     }
529
530
531     //----------------------------------------------------------------------
532     // NativeSurface: Native implementation
533     //
534
535     protected int lockSurfaceImpl() { return LOCK_SUCCESS; }
536
537     protected void unlockSurfaceImpl() { }
538
539     //----------------------------------------------------------------------
540     // WindowClosingProtocol implementation
541     //
542     private final Object closingListenerLock = new Object();
543     private WindowClosingMode defaultCloseOperation = WindowClosingMode.DISPOSE_ON_CLOSE;
544
545     @Override
546     public final WindowClosingMode getDefaultCloseOperation() {
547         synchronized (closingListenerLock) {
548             return defaultCloseOperation;
549         }
550     }
551
552     @Override
553     public final WindowClosingMode setDefaultCloseOperation(WindowClosingMode op) {
554         synchronized (closingListenerLock) {
555             WindowClosingMode _op = defaultCloseOperation;
556             defaultCloseOperation = op;
557             return _op;
558         }
559     }
560
561     //----------------------------------------------------------------------
562     // Window: Native implementation
563     //
564
565     /**
566      * Notifies the driver impl. that the instantiation is finished,
567      * ie. instance created and all fields set.
568      */
569     protected void instantiationFinished() {
570         // nop
571     }
572
573     protected boolean canCreateNativeImpl() {
574         return true; // default: always able to be created
575     }
576
577     /**
578      * The native implementation must set the native windowHandle.<br>
579      *
580      * <p>
581      * The implementation shall respect the states {@link #isAlwaysOnTop()}/{@link #FLAG_IS_ALWAYSONTOP} and
582      * {@link #isUndecorated()}/{@link #FLAG_IS_UNDECORATED}, ie. the created window shall reflect those settings.
583      * </p>
584      *
585      * <p>
586      * The implementation should invoke the referenced java state callbacks
587      * to notify this Java object of state changes.</p>
588      *
589      * @see #windowDestroyNotify(boolean)
590      * @see #focusChanged(boolean, boolean)
591      * @see #visibleChanged(boolean, boolean)
592      * @see #sizeChanged(int,int)
593      * @see #positionChanged(boolean,int, int)
594      * @see #windowDestroyNotify(boolean)
595      */
596     protected abstract void createNativeImpl();
597
598     protected abstract void closeNativeImpl();
599
600     /**
601      * Async request which shall be performed within {@link #TIMEOUT_NATIVEWINDOW}.
602      * <p>
603      * If if <code>force == false</code> the native implementation
604      * may only request focus if not yet owner.</p>
605      * <p>
606      * {@link #focusChanged(boolean, boolean)} should be called
607      * to notify about the focus traversal.
608      * </p>
609      *
610      * @param force if true, bypass {@link #focusChanged(boolean, boolean)} and force focus request
611      */
612     protected abstract void requestFocusImpl(boolean force);
613
614     public static final int FLAG_CHANGE_PARENTING       = 1 <<  0;
615     public static final int FLAG_CHANGE_DECORATION      = 1 <<  1;
616     public static final int FLAG_CHANGE_FULLSCREEN      = 1 <<  2;
617     public static final int FLAG_CHANGE_ALWAYSONTOP     = 1 <<  3;
618     public static final int FLAG_CHANGE_VISIBILITY      = 1 <<  4;
619
620     public static final int FLAG_HAS_PARENT             = 1 <<  8;
621     public static final int FLAG_IS_UNDECORATED         = 1 <<  9;
622     public static final int FLAG_IS_FULLSCREEN          = 1 << 10;
623     public static final int FLAG_IS_FULLSCREEN_SPAN     = 1 << 11;
624     public static final int FLAG_IS_ALWAYSONTOP         = 1 << 12;
625     public static final int FLAG_IS_VISIBLE             = 1 << 13;
626
627     /**
628      * The native implementation should invoke the referenced java state callbacks
629      * to notify this Java object of state changes.
630      *
631      * <p>
632      * Implementations shall set x/y to 0, in case it's negative. This could happen due
633      * to insets and positioning a decorated window to 0/0, which would place the frame
634      * outside of the screen.</p>
635      *
636      * @param x client-area position in window units, or <0 if unchanged
637      * @param y client-area position in window units, or <0 if unchanged
638      * @param width client-area size in window units, or <=0 if unchanged
639      * @param height client-area size in window units, or <=0 if unchanged
640      * @param flags bitfield of change and status flags
641      *
642      * @see #sizeChanged(int,int)
643      * @see #positionChanged(boolean,int, int)
644      */
645     protected abstract boolean reconfigureWindowImpl(int x, int y, int width, int height, int flags);
646
647     /**
648      * Tests whether a single reconfigure flag is supported by implementation.
649      * <p>
650      * Default is all but {@link #FLAG_IS_FULLSCREEN_SPAN}
651      * </p>
652      */
653     protected boolean isReconfigureFlagSupported(int changeFlags) {
654         return 0 == ( changeFlags & FLAG_IS_FULLSCREEN_SPAN );
655     }
656
657     protected int getReconfigureFlags(int changeFlags, boolean visible) {
658         return changeFlags |= ( ( 0 != getParentWindowHandle() ) ? FLAG_HAS_PARENT : 0 ) |
659                               ( isUndecorated() ? FLAG_IS_UNDECORATED : 0 ) |
660                               ( isFullscreen() ? FLAG_IS_FULLSCREEN : 0 ) |
661                               ( isAlwaysOnTop() ? FLAG_IS_ALWAYSONTOP : 0 ) |
662                               ( visible ? FLAG_IS_VISIBLE : 0 ) ;
663     }
664     protected static String getReconfigureFlagsAsString(StringBuilder sb, int flags) {
665         if(null == sb) { sb = new StringBuilder(); }
666         sb.append("[");
667
668         if( 0 != ( FLAG_CHANGE_PARENTING & flags) ) {
669             sb.append("*");
670         }
671         sb.append("PARENT ");
672         sb.append(0 != ( FLAG_HAS_PARENT & flags));
673         sb.append(", ");
674
675         if( 0 != ( FLAG_CHANGE_FULLSCREEN & flags) ) {
676             sb.append("*");
677         }
678         sb.append("FS ");
679         sb.append(0 != ( FLAG_IS_FULLSCREEN & flags));
680         sb.append("[span ");
681         sb.append(0 != ( FLAG_IS_FULLSCREEN_SPAN & flags));
682         sb.append("], ");
683
684         if( 0 != ( FLAG_CHANGE_DECORATION & flags) ) {
685             sb.append("*");
686         }
687         sb.append("UNDECOR ");
688         sb.append(0 != ( FLAG_IS_UNDECORATED & flags));
689         sb.append(", ");
690
691         if( 0 != ( FLAG_CHANGE_ALWAYSONTOP & flags) ) {
692             sb.append("*");
693         }
694         sb.append("ALWAYSONTOP ");
695         sb.append(0 != ( FLAG_IS_ALWAYSONTOP & flags));
696         sb.append(", ");
697
698         if( 0 != ( FLAG_CHANGE_VISIBILITY & flags) ) {
699             sb.append("*");
700         }
701         sb.append("VISIBLE ");
702         sb.append(0 != ( FLAG_IS_VISIBLE & flags));
703
704         sb.append("]");
705         return sb.toString();
706     }
707
708     protected void setTitleImpl(String title) {}
709
710     /**
711      * Translates the given window client-area coordinates with top-left origin
712      * to screen coordinates in window units.
713      * <p>
714      * Since the position reflects the client area, it does not include the insets.
715      * </p>
716      * <p>
717      * May return <code>null</code>, in which case the caller shall traverse through the NativeWindow tree
718      * as demonstrated in {@link #getLocationOnScreen(javax.media.nativewindow.util.Point)}.
719      * </p>
720      *
721      * @return if not null, the screen location of the given coordinates
722      */
723     protected abstract Point getLocationOnScreenImpl(int x, int y);
724
725     /**
726      * Triggered by user via {@link #getInsets()}.<br>
727      * Implementations may implement this hook to update the insets.<br>
728      * However, they may prefer the event driven path via {@link #insetsChanged(boolean, int, int, int, int)}.
729      *
730      * @see #getInsets()
731      * @see #insetsChanged(boolean, int, int, int, int)
732      */
733     protected abstract void updateInsetsImpl(Insets insets);
734
735     protected boolean setPointerVisibleImpl(boolean pointerVisible) { return false; }
736     protected boolean confinePointerImpl(boolean confine) { return false; }
737     protected void warpPointerImpl(int x, int y) { }
738     protected void setPointerIconImpl(final PointerIconImpl pi) { }
739
740     //----------------------------------------------------------------------
741     // NativeSurface
742     //
743
744     @Override
745     public final int lockSurface() throws NativeWindowException, RuntimeException {
746         final RecursiveLock _wlock = windowLock;
747         _wlock.lock();
748         surfaceLockCount++;
749         int res = ( 1 == surfaceLockCount ) ? LOCK_SURFACE_NOT_READY : LOCK_SUCCESS; // new lock ?
750
751         if ( LOCK_SURFACE_NOT_READY == res ) {
752             try {
753                 if( isNativeValid() ) {
754                     final AbstractGraphicsDevice adevice = getGraphicsConfiguration().getScreen().getDevice();
755                     adevice.lock();
756                     try {
757                         res = lockSurfaceImpl();
758                     } finally {
759                         if (LOCK_SURFACE_NOT_READY >= res) {
760                             adevice.unlock();
761                         }
762                     }
763                 }
764             } finally {
765                 if (LOCK_SURFACE_NOT_READY >= res) {
766                     surfaceLockCount--;
767                     _wlock.unlock();
768                 }
769             }
770         }
771         return res;
772     }
773
774     @Override
775     public final void unlockSurface() {
776         final RecursiveLock _wlock = windowLock;
777         _wlock.validateLocked();
778
779         if ( 1 == surfaceLockCount ) {
780             final AbstractGraphicsDevice adevice = getGraphicsConfiguration().getScreen().getDevice();
781             try {
782                 unlockSurfaceImpl();
783             } finally {
784                 adevice.unlock();
785             }
786         }
787         surfaceLockCount--;
788         _wlock.unlock();
789     }
790
791     @Override
792     public final boolean isSurfaceLockedByOtherThread() {
793         return windowLock.isLockedByOtherThread();
794     }
795
796     @Override
797     public final Thread getSurfaceLockOwner() {
798         return windowLock.getOwner();
799     }
800
801     public final RecursiveLock getLock() {
802         return windowLock;
803     }
804
805     @Override
806     public long getSurfaceHandle() {
807         return windowHandle; // default: return window handle
808     }
809
810     @Override
811     public boolean surfaceSwap() {
812         return false;
813     }
814
815     @Override
816     public final void addSurfaceUpdatedListener(SurfaceUpdatedListener l) {
817         surfaceUpdatedHelper.addSurfaceUpdatedListener(l);
818     }
819
820     @Override
821     public final void addSurfaceUpdatedListener(int index, SurfaceUpdatedListener l) throws IndexOutOfBoundsException {
822         surfaceUpdatedHelper.addSurfaceUpdatedListener(index, l);
823     }
824
825     @Override
826     public final void removeSurfaceUpdatedListener(SurfaceUpdatedListener l) {
827         surfaceUpdatedHelper.removeSurfaceUpdatedListener(l);
828     }
829
830     @Override
831     public final void surfaceUpdated(Object updater, NativeSurface ns, long when) {
832         surfaceUpdatedHelper.surfaceUpdated(updater, ns, when);
833     }
834
835     @Override
836     public final AbstractGraphicsConfiguration getGraphicsConfiguration() {
837         return config.getNativeGraphicsConfiguration();
838     }
839
840     @Override
841     public final long getDisplayHandle() {
842         return config.getNativeGraphicsConfiguration().getScreen().getDevice().getHandle();
843     }
844
845     @Override
846     public final int  getScreenIndex() {
847         return screen.getIndex();
848     }
849
850     //----------------------------------------------------------------------
851     // NativeWindow
852     //
853
854     // public final void destroy() - see below
855
856     @Override
857     public final NativeSurface getNativeSurface() { return this; }
858
859     @Override
860     public final NativeWindow getParent() {
861         return parentWindow;
862     }
863
864     @Override
865     public final long getWindowHandle() {
866         return windowHandle;
867     }
868
869     @Override
870     public Point getLocationOnScreen(Point storage) {
871         if(isNativeValid()) {
872             Point d;
873             final RecursiveLock _lock = windowLock;
874             _lock.lock();
875             try {
876                 d = getLocationOnScreenImpl(0, 0);
877             } finally {
878                 _lock.unlock();
879             }
880             if(null!=d) {
881                 if(null!=storage) {
882                     storage.translate(d.getX(),d.getY());
883                     return storage;
884                 }
885                 return d;
886             }
887             // fall through intended ..
888         }
889
890         if(null!=storage) {
891             storage.translate(getX(),getY());
892         } else {
893             storage = new Point(getX(),getY());
894         }
895         if(null!=parentWindow) {
896             // traverse through parent list ..
897             parentWindow.getLocationOnScreen(storage);
898         }
899         return storage;
900     }
901
902     //----------------------------------------------------------------------
903     // Window
904     //
905
906     @Override
907     public final boolean isNativeValid() {
908         return 0 != windowHandle ;
909     }
910
911     @Override
912     public final Screen getScreen() {
913         return screen;
914     }
915
916     protected void setScreen(ScreenImpl newScreen) { // never null !
917         removeScreenReference();
918         screen = newScreen;
919     }
920
921     @Override
922     public final MonitorDevice getMainMonitor() {
923         return screen.getMainMonitor( getBounds() );
924     }
925
926     /**
927      * @param visible
928      * @param x client-area position in window units, or <0 if unchanged
929      * @param y client-area position in window units, or <0 if unchanged
930      * @param width client-area size in window units, or <=0 if unchanged
931      * @param height client-area size in window units, or <=0 if unchanged
932      */
933     protected final void setVisibleImpl(boolean visible, int x, int y, int width, int height) {
934         reconfigureWindowImpl(x, y, width, height, getReconfigureFlags(FLAG_CHANGE_VISIBILITY, visible));
935     }
936     final void setVisibleActionImpl(boolean visible) {
937         boolean nativeWindowCreated = false;
938         boolean madeVisible = false;
939
940         final RecursiveLock _lock = windowLock;
941         _lock.lock();
942         try {
943             if(!visible && null!=childWindows && childWindows.size()>0) {
944               synchronized(childWindowsLock) {
945                 for(int i = 0; i < childWindows.size(); i++ ) {
946                     NativeWindow nw = childWindows.get(i);
947                     if(nw instanceof WindowImpl) {
948                         ((WindowImpl)nw).setVisible(false);
949                     }
950                 }
951               }
952             }
953             if(!isNativeValid() && visible) {
954                 if( 0<getWidth()*getHeight() ) {
955                     nativeWindowCreated = createNative();
956                     madeVisible = nativeWindowCreated;
957                 }
958                 // always flag visible, allowing a retry ..
959                 WindowImpl.this.visible = true;
960             } else if(WindowImpl.this.visible != visible) {
961                 if(isNativeValid()) {
962                     setVisibleImpl(visible, getX(), getY(), getWidth(), getHeight());
963                     WindowImpl.this.waitForVisible(visible, false);
964                     madeVisible = visible;
965                 } else {
966                     WindowImpl.this.visible = true;
967                 }
968             }
969
970             if(null!=lifecycleHook) {
971                 lifecycleHook.setVisibleActionPost(visible, nativeWindowCreated);
972             }
973
974             if(isNativeValid() && visible && null!=childWindows && childWindows.size()>0) {
975               synchronized(childWindowsLock) {
976                 for(int i = 0; i < childWindows.size(); i++ ) {
977                     NativeWindow nw = childWindows.get(i);
978                     if(nw instanceof WindowImpl) {
979                         ((WindowImpl)nw).setVisible(true);
980                     }
981                 }
982               }
983             }
984             if(DEBUG_IMPLEMENTATION) {
985                 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);
986             }
987         } finally {
988             if(null!=lifecycleHook) {
989                 lifecycleHook.resetCounter();
990             }
991             _lock.unlock();
992         }
993         if( nativeWindowCreated || madeVisible ) {
994             sendWindowEvent(WindowEvent.EVENT_WINDOW_RESIZED); // trigger a resize/relayout and repaint to listener
995         }
996     }
997     private class VisibleAction implements Runnable {
998         boolean visible;
999
1000         private VisibleAction(boolean visible) {
1001             this.visible = visible;
1002         }
1003
1004         @Override
1005         public final void run() {
1006             setVisibleActionImpl(visible);
1007         }
1008     }
1009
1010     @Override
1011     public final void setVisible(boolean wait, boolean visible) {
1012         if(DEBUG_IMPLEMENTATION) {
1013             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));
1014         }
1015         runOnEDTIfAvail(wait, new VisibleAction(visible));
1016     }
1017
1018     @Override
1019     public final void setVisible(boolean visible) {
1020         setVisible(true, visible);
1021     }
1022
1023     private class SetSizeAction implements Runnable {
1024         int width, height;
1025         boolean force;
1026
1027         private SetSizeAction(int w, int h, boolean disregardFS) {
1028             this.width = w;
1029             this.height = h;
1030             this.force = disregardFS;
1031         }
1032
1033         @Override
1034         public final void run() {
1035             final RecursiveLock _lock = windowLock;
1036             _lock.lock();
1037             try {
1038                 if ( force || ( !isFullscreen() && ( getWidth() != width || getHeight() != height ) ) ) {
1039                     if(DEBUG_IMPLEMENTATION) {
1040                         System.err.println("Window setSize: START force "+force+", "+getWidth()+"x"+getHeight()+" -> "+width+"x"+height+", fs "+fullscreen+", windowHandle "+toHexString(windowHandle)+", visible "+visible);
1041                     }
1042                     int visibleAction; // 0 nop, 1 invisible, 2 visible (create)
1043                     if ( visible && isNativeValid() && ( 0 >= width || 0 >= height ) ) {
1044                         visibleAction=1; // invisible
1045                         defineSize(0, 0);
1046                     } else if ( visible && !isNativeValid() && 0 < width && 0 < height ) {
1047                         visibleAction = 2; // visible (create)
1048                         defineSize(width, height);
1049                     } else if ( visible && isNativeValid() ) {
1050                         visibleAction = 0;
1051                         // this width/height will be set by windowChanged, called by the native implementation
1052                         reconfigureWindowImpl(getX(), getY(), width, height, getReconfigureFlags(0, isVisible()));
1053                         WindowImpl.this.waitForSize(width, height, false, TIMEOUT_NATIVEWINDOW);
1054                     } else {
1055                         // invisible or invalid w/ 0 size
1056                         visibleAction = 0;
1057                         defineSize(width, height);
1058                     }
1059                     if(DEBUG_IMPLEMENTATION) {
1060                         System.err.println("Window setSize: END "+getWidth()+"x"+getHeight()+", visibleAction "+visibleAction);
1061                     }
1062                     switch(visibleAction) {
1063                         case 1: setVisibleActionImpl(false); break;
1064                         case 2: setVisibleActionImpl(true); break;
1065                     }
1066                 }
1067             } finally {
1068                 _lock.unlock();
1069             }
1070         }
1071     }
1072
1073     private void setSize(final int width, final int height, final boolean force) {
1074         runOnEDTIfAvail(true, new SetSizeAction(width, height, force));
1075     }
1076     @Override
1077     public final void setSize(final int width, final int height) {
1078         runOnEDTIfAvail(true, new SetSizeAction(width, height, false));
1079     }
1080     @Override
1081     public final void setSurfaceSize(final int pixelWidth, final int pixelHeight) {
1082         // FIXME HiDPI: Shortcut, may need to adjust if we change scaling methodology
1083         setSize(pixelWidth / getPixelScaleX(), pixelHeight / getPixelScaleY());
1084     }
1085     @Override
1086     public final void setTopLevelSize(final int width, final int height) {
1087         setSize(width - getInsets().getTotalWidth(), height - getInsets().getTotalHeight());
1088     }
1089
1090     private class DestroyAction implements Runnable {
1091         @Override
1092         public final void run() {
1093             boolean animatorPaused = false;
1094             if(null!=lifecycleHook) {
1095                 animatorPaused = lifecycleHook.pauseRenderingAction();
1096             }
1097             if(null!=lifecycleHook) {
1098                 lifecycleHook.destroyActionPreLock();
1099             }
1100             final RecursiveLock _lock = windowLock;
1101             _lock.lock();
1102             try {
1103                 if(DEBUG_IMPLEMENTATION) {
1104                     System.err.println("Window DestroyAction() hasScreen "+(null != screen)+", isNativeValid "+isNativeValid()+" - "+getThreadName());
1105                 }
1106
1107                 // send synced destroy-notify notification
1108                 sendWindowEvent(WindowEvent.EVENT_WINDOW_DESTROY_NOTIFY);
1109
1110                 // Childs first ..
1111                 synchronized(childWindowsLock) {
1112                   if(childWindows.size()>0) {
1113                     // avoid ConcurrentModificationException: parent -> child -> parent.removeChild(this)
1114                     @SuppressWarnings("unchecked")
1115                     ArrayList<NativeWindow> clonedChildWindows = (ArrayList<NativeWindow>) childWindows.clone();
1116                     while( clonedChildWindows.size() > 0 ) {
1117                       NativeWindow nw = clonedChildWindows.remove(0);
1118                       if(nw instanceof WindowImpl) {
1119                           ((WindowImpl)nw).windowDestroyNotify(true);
1120                       } else {
1121                           nw.destroy();
1122                       }
1123                     }
1124                   }
1125                 }
1126
1127                 if(null!=lifecycleHook) {
1128                     // send synced destroy notification for proper cleanup, eg GLWindow/OpenGL
1129                     lifecycleHook.destroyActionInLock();
1130                 }
1131
1132                 if( isNativeValid() ) {
1133                     screen.removeMonitorModeListener(monitorModeListenerImpl);
1134                     closeNativeImpl();
1135                     final AbstractGraphicsDevice cfgADevice = config.getScreen().getDevice();
1136                     if( cfgADevice != screen.getDisplay().getGraphicsDevice() ) { // don't pull display's device
1137                         cfgADevice.close(); // ensure a cfg's device is closed
1138                     }
1139                     setGraphicsConfiguration(null);
1140                 }
1141                 removeScreenReference();
1142                 Display dpy = screen.getDisplay();
1143                 if(null != dpy) {
1144                     dpy.validateEDTStopped();
1145                 }
1146
1147                 // send synced destroyed notification
1148                 sendWindowEvent(WindowEvent.EVENT_WINDOW_DESTROYED);
1149
1150                 if(DEBUG_IMPLEMENTATION) {
1151                     System.err.println("Window.destroy() END "+getThreadName()/*+", "+WindowImpl.this*/);
1152                 }
1153             } finally {
1154                 // update states before release window lock
1155                 setWindowHandle(0);
1156                 visible = false;
1157                 fullscreen = false;
1158                 fullscreenMonitors = null;
1159                 fullscreenUseMainMonitor = true;
1160                 hasFocus = false;
1161                 parentWindowHandle = 0;
1162
1163                 _lock.unlock();
1164             }
1165             if(animatorPaused) {
1166                 lifecycleHook.resumeRenderingAction();
1167             }
1168
1169             // these refs shall be kept alive - resurrection via setVisible(true)
1170             /**
1171             if(null!=parentWindow && parentWindow instanceof Window) {
1172                 ((Window)parentWindow).removeChild(WindowImpl.this);
1173             }
1174             childWindows = null;
1175             surfaceUpdatedListeners = null;
1176             mouseListeners = null;
1177             keyListeners = null;
1178             capsRequested = null;
1179             lifecycleHook = null;
1180
1181             screen = null;
1182             windowListeners = null;
1183             parentWindow = null;
1184             */
1185         }
1186     }
1187     private final DestroyAction destroyAction = new DestroyAction();
1188
1189     @Override
1190     public void destroy() {
1191         visible = false; // Immediately mark synchronized visibility flag, avoiding possible recreation
1192         runOnEDTIfAvail(true, destroyAction);
1193     }
1194
1195     protected void destroy(boolean preserveResources) {
1196         if( null != lifecycleHook ) {
1197             lifecycleHook.preserveGLStateAtDestroy( preserveResources );
1198         }
1199         destroy();
1200     }
1201
1202     /**
1203      * @param cWin child window, must not be null
1204      * @param pWin parent window, may be null
1205      * @return true if at least one of both window's configurations is offscreen
1206      */
1207     protected static boolean isOffscreenInstance(NativeWindow cWin, NativeWindow pWin) {
1208         boolean ofs = false;
1209         final AbstractGraphicsConfiguration cWinCfg = cWin.getGraphicsConfiguration();
1210         if( null != cWinCfg ) {
1211             ofs = !cWinCfg.getChosenCapabilities().isOnscreen();
1212         }
1213         if( !ofs && null != pWin ) {
1214             final AbstractGraphicsConfiguration pWinCfg = pWin.getGraphicsConfiguration();
1215             if( null != pWinCfg ) {
1216                 ofs = !pWinCfg.getChosenCapabilities().isOnscreen();
1217             }
1218         }
1219         return ofs;
1220     }
1221
1222     private class ReparentAction implements Runnable {
1223         final NativeWindow newParentWindow;
1224         final int topLevelX, topLevelY;
1225         final int hints;
1226         ReparentOperation operation;
1227
1228         private ReparentAction(NativeWindow newParentWindow, int topLevelX, int topLevelY, int hints) {
1229             this.newParentWindow = newParentWindow;
1230             this.topLevelX = topLevelX;
1231             this.topLevelY = topLevelY;
1232             if( DEBUG_TEST_REPARENT_INCOMPATIBLE ) {
1233                 hints |=  REPARENT_HINT_FORCE_RECREATION;
1234             }
1235             this.hints = hints;
1236             this.operation = ReparentOperation.ACTION_INVALID; // ensure it's set
1237         }
1238
1239         private ReparentOperation getOp() {
1240             return operation;
1241         }
1242
1243         @Override
1244         public final void run() {
1245             if( WindowImpl.this.isFullscreen() ) {
1246                 // Bug 924: Ignore reparent when in fullscreen - otherwise may confuse WM
1247                 if( DEBUG_IMPLEMENTATION) {
1248                     System.err.println("Window.reparent: NOP (in fullscreen, "+getThreadName()+") valid "+isNativeValid()+
1249                                        ", windowHandle "+toHexString(windowHandle)+" parentWindowHandle "+toHexString(parentWindowHandle));
1250                 }
1251                 return;
1252             }
1253             boolean animatorPaused = false;
1254             if(null!=lifecycleHook) {
1255                 animatorPaused = lifecycleHook.pauseRenderingAction();
1256             }
1257             reparent();
1258             if(animatorPaused) {
1259                 lifecycleHook.resumeRenderingAction();
1260             }
1261         }
1262
1263         private void reparent() {
1264             // mirror pos/size so native change notification can get overwritten
1265             final int oldX = getX();
1266             final int oldY = getY();
1267             final int oldWidth = getWidth();
1268             final int oldHeight = getHeight();
1269             final int x, y;
1270             int width = oldWidth;
1271             int height = oldHeight;
1272
1273             final boolean wasVisible;
1274             final boolean becomesVisible;
1275             final boolean forceDestroyCreate;
1276
1277             final RecursiveLock _lock = windowLock;
1278             _lock.lock();
1279             try {
1280                 {
1281                     boolean v = 0 != ( REPARENT_HINT_FORCE_RECREATION & hints );
1282                     if(isNativeValid()) {
1283                         // force recreation if offscreen, since it may become onscreen
1284                         v |= isOffscreenInstance(WindowImpl.this, newParentWindow);
1285                     }
1286                     forceDestroyCreate = v;
1287                 }
1288
1289                 wasVisible = isVisible();
1290                 becomesVisible = wasVisible || 0 != ( REPARENT_HINT_BECOMES_VISIBLE & hints );
1291
1292                 Window newParentWindowNEWT = null;
1293                 if(newParentWindow instanceof Window) {
1294                     newParentWindowNEWT = (Window) newParentWindow;
1295                 }
1296
1297                 long newParentWindowHandle = 0 ;
1298
1299                 if( DEBUG_IMPLEMENTATION) {
1300                     System.err.println("Window.reparent: START ("+getThreadName()+") valid "+isNativeValid()+
1301                                        ", windowHandle "+toHexString(windowHandle)+" parentWindowHandle "+toHexString(parentWindowHandle)+
1302                                        ", visible "+wasVisible+", becomesVisible "+becomesVisible+
1303                                        ", forceDestroyCreate "+forceDestroyCreate+
1304                                        ", DEBUG_TEST_REPARENT_INCOMPATIBLE "+DEBUG_TEST_REPARENT_INCOMPATIBLE+
1305                                        ", HINT_FORCE_RECREATION "+( 0 != ( REPARENT_HINT_FORCE_RECREATION & hints ) )+
1306                                        ", HINT_BECOMES_VISIBLE "+( 0 != ( REPARENT_HINT_BECOMES_VISIBLE & hints ) ) +
1307                                        ", old parentWindow: "+Display.hashCodeNullSafe(parentWindow)+
1308                                        ", new parentWindow: "+Display.hashCodeNullSafe(newParentWindow) );
1309                 }
1310
1311                 if(null!=newParentWindow) {
1312                     // REPARENT TO CHILD WINDOW
1313
1314                     // reset position to 0/0 within parent space
1315                     x = 0;
1316                     y = 0;
1317
1318                     // refit if size is bigger than parent
1319                     if( width > newParentWindow.getWidth() ) {
1320                         width = newParentWindow.getWidth();
1321                     }
1322                     if( height > newParentWindow.getHeight() ) {
1323                         height = newParentWindow.getHeight();
1324                     }
1325
1326                     // Case: Child Window
1327                     newParentWindowHandle = getNativeWindowHandle(newParentWindow);
1328                     if(0 == newParentWindowHandle) {
1329                         // Case: Parent's native window not realized yet
1330                         if(null==newParentWindowNEWT) {
1331                             throw new NativeWindowException("Reparenting with non NEWT Window type only available after it's realized: "+newParentWindow);
1332                         }
1333                         // Destroy this window and use parent's Screen.
1334                         // It may be created properly when the parent is made visible.
1335                         destroy( becomesVisible );
1336                         setScreen( (ScreenImpl) newParentWindowNEWT.getScreen() );
1337                         operation = ReparentOperation.ACTION_NATIVE_CREATION_PENDING;
1338                     } else if(newParentWindow != getParent()) {
1339                         // Case: Parent's native window realized and changed
1340                         if( !isNativeValid() ) {
1341                             // May create a new compatible Screen/Display and
1342                             // mark it for creation.
1343                             if(null!=newParentWindowNEWT) {
1344                                 setScreen( (ScreenImpl) newParentWindowNEWT.getScreen() );
1345                             } else {
1346                                 final Screen newScreen = NewtFactory.createCompatibleScreen(newParentWindow, screen);
1347                                 if( screen != newScreen ) {
1348                                     // auto destroy on-the-fly created Screen/Display
1349                                     setScreen( (ScreenImpl) newScreen );
1350                                 }
1351                             }
1352                             if( 0 < width && 0 < height ) {
1353                                 operation = ReparentOperation.ACTION_NATIVE_CREATION;
1354                             } else {
1355                                 operation = ReparentOperation.ACTION_NATIVE_CREATION_PENDING;
1356                             }
1357                         } else if ( forceDestroyCreate || !NewtFactory.isScreenCompatible(newParentWindow, screen) ) {
1358                             // Destroy this window, may create a new compatible Screen/Display, while trying to preserve resources if becoming visible again.
1359                             destroy( becomesVisible );
1360                             if(null!=newParentWindowNEWT) {
1361                                 setScreen( (ScreenImpl) newParentWindowNEWT.getScreen() );
1362                             } else {
1363                                 setScreen( (ScreenImpl) NewtFactory.createCompatibleScreen(newParentWindow, screen) );
1364                             }
1365                             operation = ReparentOperation.ACTION_NATIVE_CREATION;
1366                         } else {
1367                             // Mark it for native reparenting
1368                             operation = ReparentOperation.ACTION_NATIVE_REPARENTING;
1369                         }
1370                     } else {
1371                         // Case: Parent's native window realized and not changed
1372                         operation = ReparentOperation.ACTION_NOP;
1373                     }
1374                 } else {
1375                     // REPARENT TO TOP-LEVEL WINDOW
1376                     if( 0 <= topLevelX && 0 <= topLevelY ) {
1377                         x = topLevelX;
1378                         y = topLevelY;
1379                     } else if( null != parentWindow ) {
1380                         // child -> top
1381                         // put client to current parent+child position
1382                         final Point p = getLocationOnScreen(null);
1383                         x = p.getX();
1384                         y = p.getY();
1385                     } else {
1386                         x = oldX;
1387                         y = oldY;
1388                     }
1389
1390                     // Case: Top Window
1391                     if( 0 == parentWindowHandle ) {
1392                         // Already Top Window
1393                         operation = ReparentOperation.ACTION_NOP;
1394                     } else if( !isNativeValid() || forceDestroyCreate ) {
1395                         // Destroy this window and mark it for [pending] creation.
1396                         // If isNativeValid() and becoming visible again - try to preserve resources, i.e. b/c on-/offscreen switch.
1397                         destroy( becomesVisible );
1398                         if( 0 < width && 0 < height ) {
1399                             operation = ReparentOperation.ACTION_NATIVE_CREATION;
1400                         } else {
1401                             operation = ReparentOperation.ACTION_NATIVE_CREATION_PENDING;
1402                         }
1403                     } else {
1404                         // Mark it for native reparenting
1405                         operation = ReparentOperation.ACTION_NATIVE_REPARENTING;
1406                     }
1407                 }
1408                 parentWindowHandle = newParentWindowHandle;
1409
1410                 if ( ReparentOperation.ACTION_INVALID == operation ) {
1411                     throw new NativeWindowException("Internal Error: reparentAction not set");
1412                 }
1413
1414                 if(DEBUG_IMPLEMENTATION) {
1415                     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);
1416                 }
1417
1418                 if( ReparentOperation.ACTION_NOP == operation ) {
1419                     return;
1420                 }
1421
1422                 if( null == newParentWindow ) {
1423                     // CLIENT -> TOP: Reset Parent's Pointer State
1424                     setOffscreenPointerIcon(null);
1425                     setOffscreenPointerVisible(true, null);
1426                 }
1427
1428                 // rearrange window tree
1429                 if(null!=parentWindow && parentWindow instanceof Window) {
1430                     ((Window)parentWindow).removeChild(WindowImpl.this);
1431                 }
1432                 parentWindow = newParentWindow;
1433                 if(parentWindow instanceof Window) {
1434                     ((Window)parentWindow).addChild(WindowImpl.this);
1435                 }
1436
1437                 if( ReparentOperation.ACTION_NATIVE_REPARENTING == operation ) {
1438                     final DisplayImpl display = (DisplayImpl) screen.getDisplay();
1439                     display.dispatchMessagesNative(); // status up2date
1440
1441                     // TOP -> CLIENT: !visible first (fixes X11 unsuccessful return to parent window)
1442                     if( null != parentWindow && wasVisible && NativeWindowFactory.TYPE_X11 == NativeWindowFactory.getNativeWindowType(true) ) {
1443                         setVisibleImpl(false, oldX, oldY, oldWidth, oldHeight);
1444                         WindowImpl.this.waitForVisible(false, false);
1445                         // FIXME: Some composite WM behave slacky .. give 'em chance to change state -> invisible,
1446                         // even though we do exactly that (KDE+Composite)
1447                         try { Thread.sleep(100); } catch (InterruptedException e) { }
1448                         display.dispatchMessagesNative(); // status up2date
1449                     }
1450
1451                     // Lock parentWindow only during reparenting (attempt)
1452                     final NativeWindow parentWindowLocked;
1453                     if( null != parentWindow ) {
1454                         parentWindowLocked = parentWindow;
1455                         if( NativeSurface.LOCK_SURFACE_NOT_READY >= parentWindowLocked.lockSurface() ) {
1456                             throw new NativeWindowException("Parent surface lock: not ready: "+parentWindowLocked);
1457                         }
1458                         // update native handle, locked state
1459                         parentWindowHandle = parentWindowLocked.getWindowHandle();
1460                     } else {
1461                         parentWindowLocked = null;
1462                     }
1463                     boolean ok = false;
1464                     try {
1465                         ok = reconfigureWindowImpl(x, y, width, height, getReconfigureFlags(FLAG_CHANGE_PARENTING | FLAG_CHANGE_DECORATION, isVisible()));
1466                     } finally {
1467                         if(null!=parentWindowLocked) {
1468                             parentWindowLocked.unlockSurface();
1469                         }
1470                     }
1471                     definePosition(x, y); // position might not get updated by WM events (SWT parent apparently)
1472
1473                     // set visible again
1474                     if(ok) {
1475                         display.dispatchMessagesNative(); // status up2date
1476                         if(wasVisible) {
1477                             setVisibleImpl(true, x, y, width, height);
1478                             ok = 0 <= WindowImpl.this.waitForVisible(true, false);
1479                             if(ok) {
1480                                 if( isAlwaysOnTop() && 0 == parentWindowHandle && NativeWindowFactory.TYPE_X11 == NativeWindowFactory.getNativeWindowType(true) ) {
1481                                     // Reinforce ALWAYSONTOP when CHILD -> TOP reparenting, since reparenting itself cause X11 WM to loose it's state.
1482                                     reconfigureWindowImpl(x, y, width, height, getReconfigureFlags(FLAG_CHANGE_ALWAYSONTOP, isVisible()));
1483                                 }
1484                                 ok = WindowImpl.this.waitForSize(width, height, false, TIMEOUT_NATIVEWINDOW);
1485                             }
1486                             if(ok) {
1487                                 if( 0 == parentWindowHandle ) {
1488                                     // Position mismatch shall not lead to reparent failure
1489                                     WindowImpl.this.waitForPosition(true, x, y, TIMEOUT_NATIVEWINDOW);
1490                                 }
1491
1492                                 requestFocusInt( 0 == parentWindowHandle /* skipFocusAction if top-level */);
1493                                 display.dispatchMessagesNative(); // status up2date
1494                             }
1495                         }
1496                     }
1497
1498                     if(!ok || !wasVisible) {
1499                         // make size and position persistent manual,
1500                         // since we don't have a WM feedback (invisible or recreation)
1501                         definePosition(x, y);
1502                         defineSize(width, height);
1503                     }
1504
1505                     if(!ok) {
1506                         // native reparent failed -> try creation, while trying to preserve resources if becoming visible again.
1507                         if(DEBUG_IMPLEMENTATION) {
1508                             System.err.println("Window.reparent: native reparenting failed ("+getThreadName()+") windowHandle "+toHexString(windowHandle)+" parentWindowHandle "+toHexString(parentWindowHandle)+" -> "+toHexString(newParentWindowHandle)+" - Trying recreation");
1509                         }
1510                         destroy( becomesVisible );
1511                         operation = ReparentOperation.ACTION_NATIVE_CREATION ;
1512                     } else {
1513                         if( null != parentWindow ) {
1514                             // TOP -> CLIENT: Setup Parent's Pointer State
1515                             setOffscreenPointerIcon(pointerIcon);
1516                             setOffscreenPointerVisible(pointerVisible, pointerIcon);
1517                         }
1518                     }
1519                 } else {
1520                     // Case
1521                     //   ACTION_NATIVE_CREATION
1522                     //   ACTION_NATIVE_CREATION_PENDING;
1523
1524                     // make size and position persistent for proper [re]creation
1525                     definePosition(x, y);
1526                     defineSize(width, height);
1527                 }
1528
1529                 if(DEBUG_IMPLEMENTATION) {
1530                     System.err.println("Window.reparent: END-1 ("+getThreadName()+") windowHandle "+toHexString(windowHandle)+
1531                                        ", visible: "+visible+", parentWindowHandle "+toHexString(parentWindowHandle)+
1532                                        ", parentWindow "+ Display.hashCodeNullSafe(parentWindow)+" "+
1533                                        getX()+"/"+getY()+" "+getWidth()+"x"+getHeight());
1534                 }
1535             } finally {
1536                 if(null!=lifecycleHook) {
1537                     lifecycleHook.resetCounter();
1538                 }
1539                 _lock.unlock();
1540             }
1541             if(wasVisible) {
1542                 switch (operation) {
1543                     case ACTION_NATIVE_REPARENTING:
1544                         // trigger a resize/relayout and repaint to listener
1545                         sendWindowEvent(WindowEvent.EVENT_WINDOW_RESIZED);
1546                         break;
1547
1548                     case ACTION_NATIVE_CREATION:
1549                         // This may run on the new Display/Screen connection, hence a new EDT task
1550                         runOnEDTIfAvail(true, reparentActionRecreate);
1551                         break;
1552
1553                     default:
1554                 }
1555             }
1556             if(DEBUG_IMPLEMENTATION) {
1557                 System.err.println("Window.reparent: END-X ("+getThreadName()+") windowHandle "+toHexString(windowHandle)+
1558                                    ", visible: "+visible+", parentWindowHandle "+toHexString(parentWindowHandle)+
1559                                    ", parentWindow "+ Display.hashCodeNullSafe(parentWindow)+" "+
1560                                    getX()+"/"+getY()+" "+getWidth()+"x"+getHeight());
1561             }
1562         }
1563     }
1564
1565     private class ReparentActionRecreate implements Runnable {
1566         @Override
1567         public final void run() {
1568             final RecursiveLock _lock = windowLock;
1569             _lock.lock();
1570             try {
1571                 if(DEBUG_IMPLEMENTATION) {
1572                     System.err.println("Window.reparent: ReparentActionRecreate ("+getThreadName()+") windowHandle "+toHexString(windowHandle)+", visible: "+visible+", parentWindowHandle "+toHexString(parentWindowHandle)+", parentWindow "+Display.hashCodeNullSafe(parentWindow));
1573                 }
1574                 setVisibleActionImpl(true); // native creation
1575                 requestFocusInt( 0 == parentWindowHandle /* skipFocusAction if top-level */);
1576             } finally {
1577                 _lock.unlock();
1578             }
1579         }
1580     }
1581     private final ReparentActionRecreate reparentActionRecreate = new ReparentActionRecreate();
1582
1583     @Override
1584     public final ReparentOperation reparentWindow(NativeWindow newParent) {
1585         return reparentWindow(newParent, -1, -1, 0);
1586     }
1587
1588     @Override
1589     public final ReparentOperation reparentWindow(NativeWindow newParent, int x, int y, boolean forceDestroyCreate) {
1590         return reparentWindow(newParent, x, y, forceDestroyCreate ? REPARENT_HINT_FORCE_RECREATION : 0);
1591     }
1592
1593     @Override
1594     public final ReparentOperation reparentWindow(NativeWindow newParent, int x, int y, int hints) {
1595         final ReparentAction reparentAction = new ReparentAction(newParent, x, y, hints);
1596         runOnEDTIfAvail(true, reparentAction);
1597         return reparentAction.getOp();
1598     }
1599
1600     @Override
1601     public final CapabilitiesChooser setCapabilitiesChooser(CapabilitiesChooser chooser) {
1602         CapabilitiesChooser old = this.capabilitiesChooser;
1603         this.capabilitiesChooser = chooser;
1604         return old;
1605     }
1606
1607     @Override
1608     public final CapabilitiesImmutable getChosenCapabilities() {
1609         return getGraphicsConfiguration().getChosenCapabilities();
1610     }
1611
1612     @Override
1613     public final CapabilitiesImmutable getRequestedCapabilities() {
1614         return capsRequested;
1615     }
1616
1617     private class DecorationAction implements Runnable {
1618         boolean undecorated;
1619
1620         private DecorationAction(boolean undecorated) {
1621             this.undecorated = undecorated;
1622         }
1623
1624         @Override
1625         public final void run() {
1626             final RecursiveLock _lock = windowLock;
1627             _lock.lock();
1628             try {
1629                 if(WindowImpl.this.undecorated != undecorated) {
1630                     // set current state
1631                     WindowImpl.this.undecorated = undecorated;
1632
1633                     if( isNativeValid() && !isFullscreen() ) {
1634                         // Mirror pos/size so native change notification can get overwritten
1635                         final int x = getX();
1636                         final int y = getY();
1637                         final int width = getWidth();
1638                         final int height = getHeight();
1639
1640                         DisplayImpl display = (DisplayImpl) screen.getDisplay();
1641                         display.dispatchMessagesNative(); // status up2date
1642                         reconfigureWindowImpl(x, y, width, height, getReconfigureFlags(FLAG_CHANGE_DECORATION, isVisible()));
1643                         display.dispatchMessagesNative(); // status up2date
1644                     }
1645                 }
1646             } finally {
1647                 _lock.unlock();
1648             }
1649             sendWindowEvent(WindowEvent.EVENT_WINDOW_RESIZED); // trigger a resize/relayout and repaint to listener
1650         }
1651     }
1652
1653     @Override
1654     public final void setUndecorated(boolean value) {
1655         runOnEDTIfAvail(true, new DecorationAction(value));
1656     }
1657
1658     @Override
1659     public final boolean isUndecorated() {
1660         return 0 != parentWindowHandle || undecorated || fullscreen ;
1661     }
1662
1663     private class AlwaysOnTopAction implements Runnable {
1664         boolean alwaysOnTop;
1665
1666         private AlwaysOnTopAction(boolean alwaysOnTop) {
1667             this.alwaysOnTop = alwaysOnTop;
1668         }
1669
1670         @Override
1671         public final void run() {
1672             final RecursiveLock _lock = windowLock;
1673             _lock.lock();
1674             try {
1675                 if(WindowImpl.this.alwaysOnTop != alwaysOnTop) {
1676                     // set current state
1677                     WindowImpl.this.alwaysOnTop = alwaysOnTop;
1678
1679                     if( isNativeValid() ) {
1680                         // Mirror pos/size so native change notification can get overwritten
1681                         final int x = getX();
1682                         final int y = getY();
1683                         final int width = getWidth();
1684                         final int height = getHeight();
1685
1686                         DisplayImpl display = (DisplayImpl) screen.getDisplay();
1687                         display.dispatchMessagesNative(); // status up2date
1688                         reconfigureWindowImpl(x, y, width, height, getReconfigureFlags(FLAG_CHANGE_ALWAYSONTOP, isVisible()));
1689                         display.dispatchMessagesNative(); // status up2date
1690                     }
1691                 }
1692             } finally {
1693                 _lock.unlock();
1694             }
1695             sendWindowEvent(WindowEvent.EVENT_WINDOW_RESIZED); // trigger a resize/relayout and repaint to listener
1696         }
1697     }
1698
1699     @Override
1700     public final void setAlwaysOnTop(boolean value) {
1701         if( isFullscreen() ) {
1702             nfs_alwaysOnTop = value;
1703         } else {
1704             runOnEDTIfAvail(true, new AlwaysOnTopAction(value));
1705         }
1706     }
1707
1708     @Override
1709     public final boolean isAlwaysOnTop() {
1710         return alwaysOnTop;
1711     }
1712
1713     @Override
1714     public final String getTitle() {
1715         return title;
1716     }
1717     @Override
1718     public final void setTitle(String title) {
1719         if (title == null) {
1720             title = "";
1721         }
1722         this.title = title;
1723         if(0 != getWindowHandle()) {
1724             setTitleImpl(title);
1725         }
1726     }
1727
1728     @Override
1729     public final boolean isPointerVisible() {
1730         return pointerVisible;
1731     }
1732     @Override
1733     public final void setPointerVisible(final boolean pointerVisible) {
1734         if(this.pointerVisible != pointerVisible) {
1735             boolean setVal = 0 == getWindowHandle();
1736             if(!setVal) {
1737                 setVal = setPointerVisibleIntern(pointerVisible);
1738             }
1739             if(setVal) {
1740                 this.pointerVisible = pointerVisible;
1741             }
1742         }
1743     }
1744     private boolean setPointerVisibleIntern(final boolean pointerVisible) {
1745         boolean res = setOffscreenPointerVisible(pointerVisible, pointerIcon);
1746         return setPointerVisibleImpl(pointerVisible) || res; // accept onscreen or offscreen positive result!
1747     }
1748     /**
1749      * Helper method to delegate {@link #setPointerVisibleImpl(boolean)} to
1750      * {@link OffscreenLayerSurface#hideCursor()} or {@link OffscreenLayerSurface#setCursor(PixelRectangle, PointImmutable)}.
1751      * <p>
1752      * Note: JAWTWindow is an OffscreenLayerSurface.
1753      * </p>
1754      * <p>
1755      * Performing OffscreenLayerSurface's setCursor(..)/hideCursor(), if available,
1756      * gives same behavior on all platforms.
1757      * </p>
1758      * <p>
1759      * If visible, implementation invokes {@link #setOffscreenPointerIcon(OffscreenLayerSurface, PointerIconImpl)} using the
1760      * given <code>defaultPointerIcon</code>, otherwise {@link OffscreenLayerSurface#hideCursor()} is invoked.
1761      * </p>
1762      * @param pointerVisible true for visible, otherwise invisible.
1763      * @param defaultPointerIcon default PointerIcon for visibility
1764      * @param ols the {@link OffscreenLayerSurface} instance, if null method does nothing.
1765      */
1766     private boolean setOffscreenPointerVisible(final boolean pointerVisible, final PointerIconImpl defaultPointerIcon) {
1767         if( pointerVisible ) {
1768             return setOffscreenPointerIcon(defaultPointerIcon);
1769         } else {
1770             final NativeWindow parent = getParent();
1771             if( parent instanceof OffscreenLayerSurface ) {
1772                 final OffscreenLayerSurface ols = (OffscreenLayerSurface) parent;
1773                 try {
1774                     return ols.hideCursor();
1775                 } catch (Exception e) {
1776                     e.printStackTrace();
1777                 }
1778             }
1779         }
1780         return false;
1781     }
1782
1783     @Override
1784     public final PointerIcon getPointerIcon() { return pointerIcon; }
1785
1786     @Override
1787     public final void setPointerIcon(final PointerIcon pi) {
1788         final PointerIconImpl piImpl = (PointerIconImpl)pi;
1789         if( this.pointerIcon != piImpl ) {
1790             if( isNativeValid() ) {
1791                 runOnEDTIfAvail(true, new Runnable() {
1792                     public void run() {
1793                         setPointerIconIntern(piImpl);
1794                     } } );
1795             }
1796             this.pointerIcon = piImpl;
1797         }
1798     }
1799     private void setPointerIconIntern(final PointerIconImpl pi) {
1800         setOffscreenPointerIcon(pi);
1801         setPointerIconImpl(pi);
1802     }
1803     /**
1804      * Helper method to delegate {@link #setPointerIconIntern(PointerIconImpl)} to
1805      * {@link OffscreenLayerSurface#setCursor(PixelRectangle, PointImmutable)}
1806      * <p>
1807      * Note: JAWTWindow is an OffscreenLayerSurface.
1808      * </p>
1809      * <p>
1810      * Performing OffscreenLayerSurface's setCursor(..), if available,
1811      * gives same behavior on all platforms.
1812      * </p>
1813      * <p>
1814      * Workaround for AWT/Windows bug within browser,
1815      * where the PointerIcon gets periodically overridden
1816      * by the AWT Component's icon.
1817      * </p>
1818      * @param ols the {@link OffscreenLayerSurface} instance, if null method does nothing.
1819      * @param pi the {@link PointerIconImpl} instance, if null PointerIcon gets reset.
1820      */
1821     private boolean setOffscreenPointerIcon(final PointerIconImpl pi) {
1822         final NativeWindow parent = getParent();
1823         if( parent instanceof OffscreenLayerSurface ) {
1824             final OffscreenLayerSurface ols = (OffscreenLayerSurface) parent;
1825             try {
1826                 if( null != pi ) {
1827                     return ols.setCursor(pi, pi.getHotspot());
1828                 } else {
1829                     return ols.setCursor(null, null); // default
1830                 }
1831             } catch (Exception e) {
1832                 e.printStackTrace();
1833             }
1834         }
1835         return false;
1836     }
1837
1838     @Override
1839     public final boolean isPointerConfined() {
1840         return pointerConfined;
1841     }
1842     @Override
1843     public final void confinePointer(boolean confine) {
1844         if(this.pointerConfined != confine) {
1845             boolean setVal = 0 == getWindowHandle();
1846             if(!setVal) {
1847                 if(confine) {
1848                     requestFocus();
1849                     warpPointer(getSurfaceWidth()/2, getSurfaceHeight()/2);
1850                 }
1851                 setVal = confinePointerImpl(confine);
1852                 if(confine) {
1853                     // give time to deliver mouse movements w/o confinement,
1854                     // this allows user listener to sync previous position value to the new centered position
1855                     try {
1856                         Thread.sleep(3 * screen.getDisplay().getEDTUtil().getPollPeriod());
1857                     } catch (InterruptedException e) { }
1858                 }
1859             }
1860             if(setVal) {
1861                 this.pointerConfined = confine;
1862             }
1863         }
1864     }
1865
1866     @Override
1867     public final void warpPointer(int x, int y) {
1868         if(0 != getWindowHandle()) {
1869             warpPointerImpl(x, y);
1870         }
1871     }
1872
1873     @Override
1874     public final InsetsImmutable getInsets() {
1875         if(isUndecorated()) {
1876             return Insets.getZero();
1877         }
1878         updateInsetsImpl(insets);
1879         return insets;
1880     }
1881
1882     @Override
1883     public final int getX() {
1884         return x;
1885     }
1886
1887     @Override
1888     public final int getY() {
1889         return y;
1890     }
1891
1892     @Override
1893     public final int getWidth() {
1894         return winWidth;
1895     }
1896
1897     @Override
1898     public final int getHeight() {
1899         return winHeight;
1900     }
1901
1902     @Override
1903     public final Rectangle getBounds() {
1904         return new Rectangle(x, y, winWidth, winHeight);
1905     }
1906
1907     @Override
1908     public final int getSurfaceWidth() {
1909         return pixWidth;
1910     }
1911
1912     @Override
1913     public final int getSurfaceHeight() {
1914         return pixHeight;
1915     }
1916
1917     @Override
1918     public final Rectangle getSurfaceBounds() {
1919         // FIXME HiDPI: Shortcut, may need to adjust if we change scaling methodology
1920         return new Rectangle(x * getPixelScaleX(), y * getPixelScaleY(), pixWidth, pixHeight);
1921     }
1922
1923     @Override
1924     public final int[] convertToWindowUnits(final int[] pixelUnitsAndResult) {
1925         pixelUnitsAndResult[0] /= getPixelScaleX();
1926         pixelUnitsAndResult[1] /= getPixelScaleY();
1927         return pixelUnitsAndResult;
1928     }
1929
1930     @Override
1931     public final int[] convertToPixelUnits(final int[] windowUnitsAndResult) {
1932         windowUnitsAndResult[0] *= getPixelScaleX();
1933         windowUnitsAndResult[1] *= getPixelScaleY();
1934         return windowUnitsAndResult;
1935     }
1936
1937     protected final Point convertToWindowUnits(final Point pixelUnitsAndResult) {
1938         return pixelUnitsAndResult.scaleInv(getPixelScaleX(), getPixelScaleY());
1939     }
1940
1941     protected final Point convertToPixelUnits(final Point windowUnitsAndResult) {
1942         return windowUnitsAndResult.scale(getPixelScaleX(), getPixelScaleY());
1943     }
1944
1945     @Override
1946     public final Rectangle convertToWindowUnits(final Rectangle pixelUnitsAndResult) {
1947         return pixelUnitsAndResult.scaleInv(getPixelScaleX(), getPixelScaleY());
1948     }
1949
1950     @Override
1951     public final Rectangle convertToPixelUnits(final Rectangle windowUnitsAndResult) {
1952         return windowUnitsAndResult.scale(getPixelScaleX(), getPixelScaleY());
1953     }
1954
1955     /** HiDPI: We currently base scaling of window units to pixel units on an integer scale factor per component. */
1956     protected int getPixelScaleX() { return 1; }
1957     /** HiDPI: We currently base scaling of window units to pixel units on an integer scale factor per component. */
1958     protected int getPixelScaleY() { return 1; }
1959
1960     protected final boolean autoPosition() { return autoPosition; }
1961
1962     /** Sets the position fields {@link #x} and {@link #y} in window units to the given values and {@link #autoPosition} to false. */
1963     protected final void definePosition(int x, int y) {
1964         if(DEBUG_IMPLEMENTATION) {
1965             System.err.println("definePosition: "+this.x+"/"+this.y+" -> "+x+"/"+y);
1966             // Thread.dumpStack();
1967         }
1968         autoPosition = false;
1969         this.x = x; this.y = y;
1970     }
1971
1972     /**
1973      * Sets the size fields {@link #winWidth} and {@link #winHeight} in window units to the given values
1974      * and {@link #pixWidth} and {@link #pixHeight} in pixel units according to {@link #convertToPixelUnits(int[])}.
1975      */
1976     protected final void defineSize(int winWidth, int winHeight) {
1977         final int pixWidth = winWidth * getPixelScaleX();   // FIXME HiDPI: Shortcut, may need to adjust if we change scaling methodology
1978         final int pixHeight = winHeight * getPixelScaleY();
1979         if(DEBUG_IMPLEMENTATION) {
1980             System.err.println("defineSize: win["+this.winWidth+"x"+this.winHeight+" -> "+winWidth+"x"+winHeight+
1981                                "], pixel["+this.pixWidth+"x"+this.pixHeight+" -> "+pixWidth+"x"+pixHeight+"]");
1982             // Thread.dumpStack();
1983         }
1984         this.winWidth = winWidth; this.winHeight = winHeight;
1985         this.pixWidth = pixWidth; this.pixHeight = pixHeight;
1986     }
1987
1988     @Override
1989     public final boolean isVisible() {
1990         return visible;
1991     }
1992
1993     @Override
1994     public final boolean isFullscreen() {
1995         return fullscreen;
1996     }
1997
1998     //----------------------------------------------------------------------
1999     // Window
2000     //
2001
2002     @Override
2003     public final Window getDelegatedWindow() {
2004         return this;
2005     }
2006
2007     //----------------------------------------------------------------------
2008     // WindowImpl
2009     //
2010
2011     /**
2012      * If the implementation is capable of detecting a device change
2013      * return true and clear the status/reason of the change.
2014      */
2015     public boolean hasDeviceChanged() {
2016         return false;
2017     }
2018
2019     public final LifecycleHook getLifecycleHook() {
2020         return lifecycleHook;
2021     }
2022
2023     public final LifecycleHook setLifecycleHook(LifecycleHook hook) {
2024         LifecycleHook old = lifecycleHook;
2025         lifecycleHook = hook;
2026         return old;
2027     }
2028
2029     /**
2030      * If this Window actually wraps a {@link NativeSurface} from another instance or toolkit,
2031      * it will return such reference. Otherwise returns null.
2032      */
2033     public NativeSurface getWrappedSurface() {
2034         return null;
2035     }
2036
2037     @Override
2038     public final void setWindowDestroyNotifyAction(Runnable r) {
2039         windowDestroyNotifyAction = r;
2040     }
2041
2042     protected final long getParentWindowHandle() {
2043         return isFullscreen() ? 0 : parentWindowHandle;
2044     }
2045
2046     @Override
2047     public final String toString() {
2048         StringBuilder sb = new StringBuilder();
2049
2050         sb.append(getClass().getName()+"[Config "+config+
2051                     ",\n "+screen+
2052                     ",\n ParentWindow "+parentWindow+
2053                     ",\n ParentWindowHandle "+toHexString(parentWindowHandle)+" ("+(0!=getParentWindowHandle())+")"+
2054                     ",\n WindowHandle "+toHexString(getWindowHandle())+
2055                     ",\n SurfaceHandle "+toHexString(getSurfaceHandle())+ " (lockedExt window "+windowLock.isLockedByOtherThread()+", surface "+isSurfaceLockedByOtherThread()+")"+
2056                     ",\n window["+getX()+"/"+getY()+" (auto "+autoPosition()+") "+getWidth()+"x"+getHeight()+"], pixel["+getSurfaceWidth()+"x"+getSurfaceHeight()+
2057                     "],\n Visible "+isVisible()+", focus "+hasFocus()+
2058                     ",\n Undecorated "+undecorated+" ("+isUndecorated()+")"+
2059                     ",\n AlwaysOnTop "+alwaysOnTop+", Fullscreen "+fullscreen+
2060                     ",\n WrappedSurface "+getWrappedSurface()+
2061                     ",\n ChildWindows "+childWindows.size());
2062
2063         sb.append(", SurfaceUpdatedListeners num "+surfaceUpdatedHelper.size()+" [");
2064         for (int i = 0; i < surfaceUpdatedHelper.size(); i++ ) {
2065           sb.append(surfaceUpdatedHelper.get(i)+", ");
2066         }
2067         sb.append("], WindowListeners num "+windowListeners.size()+" [");
2068         for (int i = 0; i < windowListeners.size(); i++ ) {
2069           sb.append(windowListeners.get(i)+", ");
2070         }
2071         sb.append("], MouseListeners num "+mouseListeners.size()+" [");
2072         for (int i = 0; i < mouseListeners.size(); i++ ) {
2073           sb.append(mouseListeners.get(i)+", ");
2074         }
2075         sb.append("], PointerGestures default "+defaultGestureHandlerEnabled+", custom "+pointerGestureHandler.size()+" [");
2076         for (int i = 0; i < pointerGestureHandler.size(); i++ ) {
2077           sb.append(pointerGestureHandler.get(i)+", ");
2078         }
2079         sb.append("], KeyListeners num "+keyListeners.size()+" [");
2080         for (int i = 0; i < keyListeners.size(); i++ ) {
2081           sb.append(keyListeners.get(i)+", ");
2082         }
2083         sb.append("], windowLock "+windowLock+", surfaceLockCount "+surfaceLockCount+"]");
2084         return sb.toString();
2085     }
2086
2087     protected final void setWindowHandle(long handle) {
2088         windowHandle = handle;
2089     }
2090
2091     @Override
2092     public final void runOnEDTIfAvail(boolean wait, final Runnable task) {
2093         if( windowLock.isOwner( Thread.currentThread() ) ) {
2094             task.run();
2095         } else {
2096             ( (DisplayImpl) screen.getDisplay() ).runOnEDTIfAvail(wait, task);
2097         }
2098     }
2099
2100     private final Runnable requestFocusAction = new Runnable() {
2101         @Override
2102         public final void run() {
2103             if(DEBUG_IMPLEMENTATION) {
2104                 System.err.println("Window.RequestFocusAction: force 0 - ("+getThreadName()+"): "+hasFocus+" -> true - windowHandle "+toHexString(windowHandle)+" parentWindowHandle "+toHexString(parentWindowHandle));
2105             }
2106             WindowImpl.this.requestFocusImpl(false);
2107         }
2108     };
2109     private final Runnable requestFocusActionForced = new Runnable() {
2110         @Override
2111         public final void run() {
2112             if(DEBUG_IMPLEMENTATION) {
2113                 System.err.println("Window.RequestFocusAction: force 1 - ("+getThreadName()+"): "+hasFocus+" -> true - windowHandle "+toHexString(windowHandle)+" parentWindowHandle "+toHexString(parentWindowHandle));
2114             }
2115             WindowImpl.this.requestFocusImpl(true);
2116         }
2117     };
2118
2119     @Override
2120     public final boolean hasFocus() {
2121         return hasFocus;
2122     }
2123
2124     @Override
2125     public final void requestFocus() {
2126         requestFocus(true);
2127     }
2128
2129     @Override
2130     public final void requestFocus(boolean wait) {
2131         requestFocus(wait /* wait */, false /* skipFocusAction */, brokenFocusChange /* force */);
2132     }
2133
2134     private void requestFocus(boolean wait, boolean skipFocusAction, boolean force) {
2135         if( isNativeValid() &&
2136             ( force || !hasFocus() ) &&
2137             ( skipFocusAction || !focusAction() ) ) {
2138             runOnEDTIfAvail(wait, force ? requestFocusActionForced : requestFocusAction);
2139         }
2140     }
2141
2142     /** Internally forcing request focus on current thread */
2143     private void requestFocusInt(boolean skipFocusAction) {
2144         if( skipFocusAction || !focusAction() ) {
2145             if(DEBUG_IMPLEMENTATION) {
2146                 System.err.println("Window.RequestFocusInt: forcing - ("+getThreadName()+"): skipFocusAction "+skipFocusAction+", focus "+hasFocus+" -> true - windowHandle "+toHexString(windowHandle)+" parentWindowHandle "+toHexString(parentWindowHandle));
2147             }
2148             requestFocusImpl(true);
2149         }
2150     }
2151
2152     @Override
2153     public final void setFocusAction(FocusRunnable focusAction) {
2154         this.focusAction = focusAction;
2155     }
2156
2157     private boolean focusAction() {
2158         if(DEBUG_IMPLEMENTATION) {
2159             System.err.println("Window.focusAction() START - "+getThreadName()+", focusAction: "+focusAction+" - windowHandle "+toHexString(getWindowHandle()));
2160         }
2161         boolean res;
2162         if(null!=focusAction) {
2163             res = focusAction.run();
2164         } else {
2165             res = false;
2166         }
2167         if(DEBUG_IMPLEMENTATION) {
2168             System.err.println("Window.focusAction() END - "+getThreadName()+", focusAction: "+focusAction+" - windowHandle "+toHexString(getWindowHandle())+", res: "+res);
2169         }
2170         return res;
2171     }
2172
2173     protected final void setBrokenFocusChange(boolean v) {
2174         brokenFocusChange = v;
2175     }
2176
2177     @Override
2178     public final void setKeyboardFocusHandler(KeyListener l) {
2179         keyboardFocusHandler = l;
2180     }
2181
2182     private class SetPositionAction implements Runnable {
2183         int x, y;
2184
2185         private SetPositionAction(int x, int y) {
2186             this.x = x;
2187             this.y = y;
2188         }
2189
2190         @Override
2191         public final void run() {
2192             final RecursiveLock _lock = windowLock;
2193             _lock.lock();
2194             try {
2195                 if(DEBUG_IMPLEMENTATION) {
2196                     System.err.println("Window setPosition: "+getX()+"/"+getY()+" -> "+x+"/"+y+", fs "+fullscreen+", windowHandle "+toHexString(windowHandle));
2197                 }
2198                 // Let the window be positioned if !fullscreen and position changed or being a child window.
2199                 if ( !isFullscreen() && ( getX() != x || getY() != y || null != getParent()) ) {
2200                     if(isNativeValid()) {
2201                         // this.x/this.y will be set by sizeChanged, triggered by windowing event system
2202                         reconfigureWindowImpl(x, y, getWidth(), getHeight(), getReconfigureFlags(0, isVisible()));
2203                         if( null == parentWindow ) {
2204                             // Wait until custom position is reached within tolerances
2205                             waitForPosition(true, x, y, Window.TIMEOUT_NATIVEWINDOW);
2206                         }
2207                     } else {
2208                         definePosition(x, y); // set pos for createNative(..)
2209                     }
2210                 }
2211             } finally {
2212                 _lock.unlock();
2213             }
2214         }
2215     }
2216
2217     @Override
2218     public void setPosition(int x, int y) {
2219         autoPosition = false;
2220         runOnEDTIfAvail(true, new SetPositionAction(x, y));
2221     }
2222
2223     @Override
2224     public final void setTopLevelPosition(int x, int y) {
2225         setPosition(x + getInsets().getLeftWidth(), y + getInsets().getTopHeight());
2226     }
2227
2228     private class FullScreenAction implements Runnable {
2229         boolean _fullscreen;
2230
2231         private boolean init(boolean fullscreen) {
2232             if(isNativeValid()) {
2233                 this._fullscreen = fullscreen;
2234                 return isFullscreen() != fullscreen;
2235             } else {
2236                 WindowImpl.this.fullscreen = fullscreen; // set current state for createNative(..)
2237                 return false;
2238             }
2239         }
2240         public boolean fsOn() { return _fullscreen; }
2241
2242         @Override
2243         public final void run() {
2244             final RecursiveLock _lock = windowLock;
2245             _lock.lock();
2246             blockInsetsChange = true;
2247             try {
2248                 final int oldX = getX();
2249                 final int oldY = getY();
2250                 final int oldWidth = getWidth();
2251                 final int oldHeight = getHeight();
2252
2253                 int x,y,w,h;
2254
2255                 final RectangleImmutable sviewport = screen.getViewportInWindowUnits(); // window units
2256                 final RectangleImmutable viewport; // window units
2257                 final int fs_span_flag;
2258                 final boolean alwaysOnTopChange;
2259                 if(_fullscreen) {
2260                     if( null == fullscreenMonitors ) {
2261                         if( fullscreenUseMainMonitor ) {
2262                             fullscreenMonitors = new ArrayList<MonitorDevice>();
2263                             fullscreenMonitors.add( getMainMonitor() );
2264                         } else {
2265                             fullscreenMonitors = getScreen().getMonitorDevices();
2266                         }
2267                     }
2268                     {
2269                         final Rectangle viewportInWindowUnits = new Rectangle();
2270                         MonitorDevice.unionOfViewports(null, viewportInWindowUnits, fullscreenMonitors);
2271                         viewport = viewportInWindowUnits;
2272                     }
2273                     if( isReconfigureFlagSupported(FLAG_IS_FULLSCREEN_SPAN) &&
2274                         ( fullscreenMonitors.size() > 1 || sviewport.compareTo(viewport) > 0 ) ) {
2275                         fs_span_flag = FLAG_IS_FULLSCREEN_SPAN;
2276                     } else {
2277                         fs_span_flag = 0;
2278                     }
2279                     nfs_x = oldX;
2280                     nfs_y = oldY;
2281                     nfs_width = oldWidth;
2282                     nfs_height = oldHeight;
2283                     nfs_alwaysOnTop = alwaysOnTop;
2284                     x = viewport.getX();
2285                     y = viewport.getY();
2286                     w = viewport.getWidth();
2287                     h = viewport.getHeight();
2288                     alwaysOnTop = false;
2289                     alwaysOnTopChange = nfs_alwaysOnTop != alwaysOnTop;
2290                 } else {
2291                     fullscreenUseMainMonitor = true;
2292                     fullscreenMonitors = null;
2293                     fs_span_flag = 0;
2294                     viewport = null;
2295                     x = nfs_x;
2296                     y = nfs_y;
2297                     w = nfs_width;
2298                     h = nfs_height;
2299                     alwaysOnTopChange = nfs_alwaysOnTop != alwaysOnTop;
2300                     alwaysOnTop = nfs_alwaysOnTop;
2301
2302                     if(null!=parentWindow) {
2303                         // reset position to 0/0 within parent space
2304                         x = 0;
2305                         y = 0;
2306
2307                         // refit if size is bigger than parent
2308                         if( w > parentWindow.getWidth() ) {
2309                             w = parentWindow.getWidth();
2310                         }
2311                         if( h > parentWindow.getHeight() ) {
2312                             h = parentWindow.getHeight();
2313                         }
2314                     }
2315                 }
2316
2317                 final DisplayImpl display = (DisplayImpl) screen.getDisplay();
2318                 display.dispatchMessagesNative(); // status up2date
2319                 final boolean wasVisible = isVisible();
2320                 final boolean tempInvisible = !_fullscreen && wasVisible && NativeWindowFactory.TYPE_X11 == NativeWindowFactory.getNativeWindowType(true);
2321
2322                 if(DEBUG_IMPLEMENTATION) {
2323                     System.err.println("Window fs: "+_fullscreen+" "+x+"/"+y+" "+w+"x"+h+", "+isUndecorated()+
2324                                        ", virtl-screenSize: "+sviewport+" [wu], monitorsViewport "+viewport+" [wu]"+
2325                                        ", spanning "+(0!=fs_span_flag)+
2326                                        ", alwaysOnTop "+alwaysOnTop+(alwaysOnTopChange?"*":"")+
2327                                        ", wasVisible "+wasVisible+", tempInvisible "+tempInvisible+
2328                                        ", hasParent "+(null!=parentWindow)+
2329                                        " @ "+Thread.currentThread().getName());
2330                 }
2331
2332                 // fullscreen off: !visible first (fixes X11 unsuccessful return to parent window _and_ wrong window size propagation)
2333                 if( tempInvisible ) {
2334                     setVisibleImpl(false, oldX, oldY, oldWidth, oldHeight);
2335                     WindowImpl.this.waitForVisible(false, false);
2336                     try { Thread.sleep(100); } catch (InterruptedException e) { }
2337                     display.dispatchMessagesNative(); // status up2date
2338                 }
2339
2340                 // Lock parentWindow only during reparenting (attempt)
2341                 final NativeWindow parentWindowLocked;
2342                 if( null != parentWindow ) {
2343                     parentWindowLocked = parentWindow;
2344                     if( NativeSurface.LOCK_SURFACE_NOT_READY >= parentWindowLocked.lockSurface() ) {
2345                         throw new NativeWindowException("Parent surface lock: not ready: "+parentWindow);
2346                     }
2347                 } else {
2348                     parentWindowLocked = null;
2349                 }
2350                 try {
2351                     if(alwaysOnTopChange && _fullscreen) {
2352                         // Enter fullscreen - Disable alwaysOnTop
2353                         reconfigureWindowImpl(oldX, oldY, oldWidth, oldHeight, getReconfigureFlags(FLAG_CHANGE_ALWAYSONTOP, isVisible()));
2354                     }
2355
2356                     WindowImpl.this.fullscreen = _fullscreen;
2357                     reconfigureWindowImpl(x, y, w, h,
2358                                           getReconfigureFlags( ( ( null != parentWindowLocked ) ? FLAG_CHANGE_PARENTING : 0 ) |
2359                                                                fs_span_flag | FLAG_CHANGE_FULLSCREEN | FLAG_CHANGE_DECORATION, isVisible()) );
2360                     if(alwaysOnTopChange && !_fullscreen) {
2361                         // Leave fullscreen - Restore alwaysOnTop
2362                         reconfigureWindowImpl(x, y, w, h, getReconfigureFlags(FLAG_CHANGE_ALWAYSONTOP, isVisible()));
2363                     }
2364                 } finally {
2365                     if(null!=parentWindowLocked) {
2366                         parentWindowLocked.unlockSurface();
2367                     }
2368                 }
2369                 display.dispatchMessagesNative(); // status up2date
2370
2371                 if(wasVisible) {
2372                     if( NativeWindowFactory.TYPE_X11 == NativeWindowFactory.getNativeWindowType(true) ) {
2373                         // Give sluggy WM's (e.g. Unity) a chance to properly restore window ..
2374                         try { Thread.sleep(100); } catch (InterruptedException e) { }
2375                         display.dispatchMessagesNative(); // status up2date
2376                     }
2377                     setVisibleImpl(true, x, y, w, h);
2378                     boolean ok = 0 <= WindowImpl.this.waitForVisible(true, false);
2379                     if(ok) {
2380                         ok = WindowImpl.this.waitForSize(w, h, false, TIMEOUT_NATIVEWINDOW);
2381                     }
2382                     if(ok && !_fullscreen && null == parentWindow) {
2383                         // Position mismatch shall not lead to fullscreen failure
2384                         WindowImpl.this.waitForPosition(true, x, y, TIMEOUT_NATIVEWINDOW);
2385                     }
2386                     if(ok) {
2387                         requestFocusInt(_fullscreen /* skipFocusAction if fullscreen */);
2388                         display.dispatchMessagesNative(); // status up2date
2389                     }
2390                     if(DEBUG_IMPLEMENTATION) {
2391                         System.err.println("Window fs done: ok " + ok + ", " + WindowImpl.this);
2392                     }
2393                 }
2394             } finally {
2395                 blockInsetsChange = false;
2396                 _lock.unlock();
2397             }
2398             sendWindowEvent(WindowEvent.EVENT_WINDOW_RESIZED); // trigger a resize/relayout and repaint to listener
2399         }
2400     }
2401     private final FullScreenAction fullScreenAction = new FullScreenAction();
2402
2403     @Override
2404     public boolean setFullscreen(boolean fullscreen) {
2405         return setFullscreenImpl(fullscreen, true, null);
2406     }
2407
2408     @Override
2409     public boolean setFullscreen(List<MonitorDevice> monitors) {
2410         return setFullscreenImpl(true, false, monitors);
2411     }
2412
2413     private boolean setFullscreenImpl(boolean fullscreen, boolean useMainMonitor, List<MonitorDevice> monitors) {
2414         synchronized(fullScreenAction) {
2415             fullscreenMonitors = monitors;
2416             fullscreenUseMainMonitor = useMainMonitor;
2417             if( fullScreenAction.init(fullscreen) ) {
2418                 if( fullScreenAction.fsOn() && isOffscreenInstance(WindowImpl.this, parentWindow) ) {
2419                     // enable fullscreen on offscreen instance
2420                     if(null != parentWindow) {
2421                         nfs_parent = parentWindow;
2422                         reparentWindow(null, -1, -1, REPARENT_HINT_FORCE_RECREATION | REPARENT_HINT_BECOMES_VISIBLE);
2423                     } else {
2424                         throw new InternalError("Offscreen instance w/o parent unhandled");
2425                     }
2426                 }
2427
2428                 runOnEDTIfAvail(true, fullScreenAction);
2429
2430                 if(!fullScreenAction.fsOn() && null != nfs_parent) {
2431                     // disable fullscreen on offscreen instance
2432                     reparentWindow(nfs_parent, -1, -1, REPARENT_HINT_FORCE_RECREATION | REPARENT_HINT_BECOMES_VISIBLE);
2433                     nfs_parent = null;
2434                 }
2435             }
2436             return this.fullscreen;
2437         }
2438     }
2439
2440     /** Notify WindowDriver about the finished monitor mode change. */
2441     protected void monitorModeChanged(MonitorEvent me, boolean success) {
2442     }
2443
2444     private class MonitorModeListenerImpl implements MonitorModeListener {
2445         boolean animatorPaused = false;
2446         boolean hidden = false;
2447         boolean hadFocus = false;
2448         boolean fullscreenPaused = false;
2449         List<MonitorDevice> _fullscreenMonitors = null;
2450         boolean _fullscreenUseMainMonitor = true;
2451
2452         @Override
2453         public void monitorModeChangeNotify(MonitorEvent me) {
2454             hadFocus = hasFocus();
2455             final boolean isOSX = NativeWindowFactory.TYPE_MACOSX == NativeWindowFactory.getNativeWindowType(true);
2456             final boolean quirkFSPause = fullscreen && isReconfigureFlagSupported(FLAG_IS_FULLSCREEN_SPAN);
2457             final boolean quirkHide = !quirkFSPause && !fullscreen && isVisible() && isOSX;
2458             if(DEBUG_IMPLEMENTATION) {
2459                 System.err.println("Window.monitorModeChangeNotify: hadFocus "+hadFocus+", qFSPause "+quirkFSPause+", qHide "+quirkHide+", "+me+" @ "+Thread.currentThread().getName());
2460             }
2461
2462             if(null!=lifecycleHook) {
2463                 animatorPaused = lifecycleHook.pauseRenderingAction();
2464             }
2465             if( quirkFSPause ) {
2466                 if(DEBUG_IMPLEMENTATION) {
2467                     System.err.println("Window.monitorModeChangeNotify: FS Pause");
2468                 }
2469                 fullscreenPaused = true;
2470                 _fullscreenMonitors = fullscreenMonitors;
2471                 _fullscreenUseMainMonitor = fullscreenUseMainMonitor;
2472                 setFullscreenImpl(false, true, null);
2473             }
2474             if( quirkHide ) {
2475                 // hiding & showing the window around mode-change solves issues w/ OSX,
2476                 // where the content would be black until a resize.
2477                 hidden = true;
2478                 WindowImpl.this.setVisible(false);
2479             }
2480         }
2481
2482         @Override
2483         public void monitorModeChanged(MonitorEvent me, boolean success) {
2484             if(!animatorPaused && success && null!=lifecycleHook) {
2485                 // Didn't pass above notify method. probably detected screen change after it happened.
2486                 animatorPaused = lifecycleHook.pauseRenderingAction();
2487             }
2488             if(DEBUG_IMPLEMENTATION) {
2489                 System.err.println("Window.monitorModeChanged.0: success: "+success+", hadFocus "+hadFocus+", animPaused "+animatorPaused+
2490                                    ", hidden "+hidden+", FS "+fullscreen+", FS-paused "+fullscreenPaused+
2491                                    " @ "+Thread.currentThread().getName());
2492                 System.err.println("Window.monitorModeChanged.0: "+getScreen());
2493                 System.err.println("Window.monitorModeChanged.0: "+me);
2494             }
2495             WindowImpl.this.monitorModeChanged(me, success);
2496
2497             if( success && !fullscreen && !fullscreenPaused ) {
2498                 // Simply move/resize window to fit in virtual screen if required
2499                 final RectangleImmutable viewport = screen.getViewportInWindowUnits();
2500                 if( viewport.getWidth() > 0 && viewport.getHeight() > 0 ) { // failsafe
2501                     final RectangleImmutable rect = new Rectangle(getX(), getY(), getWidth(), getHeight());
2502                     final RectangleImmutable isect = viewport.intersection(rect);
2503                     if ( getHeight() > isect.getHeight()  ||
2504                          getWidth() > isect.getWidth() ) {
2505                         if(DEBUG_IMPLEMENTATION) {
2506                             System.err.println("Window.monitorModeChanged.1: Non-FS - Fit window "+rect+" into screen viewport "+viewport+
2507                                                ", due to minimal intersection "+isect);
2508                         }
2509                         definePosition(viewport.getX(), viewport.getY()); // set pos for setVisible(..) or createNative(..) - reduce EDT roundtrip
2510                         setSize(viewport.getWidth(), viewport.getHeight(), true /* force */);
2511                     }
2512                 }
2513             } else if( fullscreenPaused ) {
2514                 if(DEBUG_IMPLEMENTATION) {
2515                     System.err.println("Window.monitorModeChanged.2: FS Restore");
2516                 }
2517                 setFullscreenImpl(true, _fullscreenUseMainMonitor, _fullscreenMonitors);
2518                 fullscreenPaused = false;
2519                 _fullscreenMonitors = null;
2520                 _fullscreenUseMainMonitor = true;
2521             } else if( success && fullscreen && null != fullscreenMonitors ) {
2522                 // If changed monitor is part of this fullscreen mode, reset size! (Bug 771)
2523                 final MonitorDevice md = me.getMonitor();
2524                 if( fullscreenMonitors.contains(md) ) {
2525                     final Rectangle viewportInWindowUnits = new Rectangle();
2526                     MonitorDevice.unionOfViewports(null, viewportInWindowUnits, fullscreenMonitors);
2527                     if(DEBUG_IMPLEMENTATION) {
2528                         final RectangleImmutable winBounds = WindowImpl.this.getBounds();
2529                         System.err.println("Window.monitorModeChanged.3: FS Monitor Match: Fit window "+winBounds+" into new viewport union "+viewportInWindowUnits+" [window], provoked by "+md);
2530                     }
2531                     definePosition(viewportInWindowUnits.getX(), viewportInWindowUnits.getY()); // set pos for setVisible(..) or createNative(..) - reduce EDT roundtrip
2532                     setSize(viewportInWindowUnits.getWidth(), viewportInWindowUnits.getHeight(), true /* force */);
2533                 }
2534             }
2535             if( hidden ) {
2536                 WindowImpl.this.setVisible(true);
2537                 hidden = false;
2538             }
2539             sendWindowEvent(WindowEvent.EVENT_WINDOW_RESIZED); // trigger a resize/relayout and repaint to listener
2540             if(animatorPaused) {
2541                 lifecycleHook.resumeRenderingAction();
2542             }
2543             if( hadFocus ) {
2544                 requestFocus(true);
2545             }
2546             if(DEBUG_IMPLEMENTATION) {
2547                 System.err.println("Window.monitorModeChanged.X: @ "+Thread.currentThread().getName()+", this: "+WindowImpl.this);
2548             }
2549         }
2550     }
2551     private final MonitorModeListenerImpl monitorModeListenerImpl = new MonitorModeListenerImpl();
2552
2553
2554     //----------------------------------------------------------------------
2555     // Child Window Management
2556     //
2557
2558     @Override
2559     public final boolean removeChild(NativeWindow win) {
2560         synchronized(childWindowsLock) {
2561             return childWindows.remove(win);
2562         }
2563     }
2564
2565     @Override
2566     public final boolean addChild(NativeWindow win) {
2567         if (win == null) {
2568             return false;
2569         }
2570         synchronized(childWindowsLock) {
2571             return childWindows.add(win);
2572         }
2573     }
2574
2575     //----------------------------------------------------------------------
2576     // Generic Event Support
2577     //
2578     private void doEvent(boolean enqueue, boolean wait, com.jogamp.newt.event.NEWTEvent event) {
2579         boolean done = false;
2580
2581         if(!enqueue) {
2582             done = consumeEvent(event);
2583             wait = done; // don't wait if event can't be consumed now
2584         }
2585
2586         if(!done) {
2587             enqueueEvent(wait, event);
2588         }
2589     }
2590
2591     @Override
2592     public final void enqueueEvent(boolean wait, com.jogamp.newt.event.NEWTEvent event) {
2593         if(isNativeValid()) {
2594             ((DisplayImpl)screen.getDisplay()).enqueueEvent(wait, event);
2595         }
2596     }
2597
2598     @Override
2599     public final boolean consumeEvent(NEWTEvent e) {
2600         switch(e.getEventType()) {
2601             // special repaint treatment
2602             case WindowEvent.EVENT_WINDOW_REPAINT:
2603                 // queue repaint event in case window is locked, ie in operation
2604                 if( null != windowLock.getOwner() ) {
2605                     // make sure only one repaint event is queued
2606                     if(!repaintQueued) {
2607                         repaintQueued=true;
2608                         final boolean discardTO = QUEUED_EVENT_TO <= System.currentTimeMillis()-e.getWhen();
2609                         if(DEBUG_IMPLEMENTATION) {
2610                             System.err.println("Window.consumeEvent: REPAINT "+Thread.currentThread().getName()+" - queued "+e+", discard-to "+discardTO);
2611                             // Thread.dumpStack();
2612                         }
2613                         return discardTO; // discardTO:=true -> consumed
2614                     }
2615                     return true;
2616                 }
2617                 repaintQueued=false; // no repaint event queued
2618                 break;
2619
2620             // common treatment
2621             case WindowEvent.EVENT_WINDOW_RESIZED:
2622                 // queue event in case window is locked, ie in operation
2623                 if( null != windowLock.getOwner() ) {
2624                     final boolean discardTO = QUEUED_EVENT_TO <= System.currentTimeMillis()-e.getWhen();
2625                     if(DEBUG_IMPLEMENTATION) {
2626                         System.err.println("Window.consumeEvent: RESIZED "+Thread.currentThread().getName()+" - queued "+e+", discard-to "+discardTO);
2627                         // Thread.dumpStack();
2628                     }
2629                     return discardTO; // discardTO:=true -> consumed
2630                 }
2631                 break;
2632             default:
2633                 break;
2634         }
2635         if(e instanceof WindowEvent) {
2636             consumeWindowEvent((WindowEvent)e);
2637         } else if(e instanceof KeyEvent) {
2638             consumeKeyEvent((KeyEvent)e);
2639         } else if(e instanceof MouseEvent) {
2640             consumePointerEvent((MouseEvent)e);
2641         } else {
2642             throw new NativeWindowException("Unexpected NEWTEvent type " + e);
2643         }
2644         return true;
2645     }
2646
2647     //
2648     // MouseListener/Event Support
2649     //
2650
2651     //
2652     // Native MouseEvents pre-processed to be enqueued or consumed directly
2653     //
2654
2655     public final void sendMouseEvent(final short eventType, final int modifiers,
2656                                final int x, final int y, final short button, final float rotation) {
2657         doMouseEvent(false, false, eventType, modifiers, x, y, button, MouseEvent.getRotationXYZ(rotation, modifiers), 1f);
2658     }
2659     public final void enqueueMouseEvent(final boolean wait, final short eventType, final int modifiers,
2660                                         final int x, final int y, final short button, final float rotation) {
2661         doMouseEvent(true, wait, eventType, modifiers, x, y, button, MouseEvent.getRotationXYZ(rotation, modifiers), 1f);
2662     }
2663     protected final void doMouseEvent(final boolean enqueue, final boolean wait, final short eventType, final int modifiers,
2664                                       final int x, final int y, final short button, final float rotation) {
2665         doMouseEvent(enqueue, wait, eventType, modifiers, x, y, button, MouseEvent.getRotationXYZ(rotation, modifiers), 1f);
2666     }
2667     /**
2668     public final void sendMouseEvent(final short eventType, final int modifiers,
2669                                      final int x, final int y, final short button, final float[] rotationXYZ, final float rotationScale) {
2670         doMouseEvent(false, false, eventType, modifiers, x, y, button, rotationXYZ, rotationScale);
2671     }
2672     public final void enqueueMouseEvent(final boolean wait, final short eventType, final int modifiers,
2673                                         final int x, final int y, final short button, final float[] rotationXYZ, final float rotationScale) {
2674         doMouseEvent(true, wait, eventType, modifiers, x, y, button, rotationXYZ, rotationScale);
2675     } */
2676
2677     /**
2678      * Send mouse event (one-pointer) either to be directly consumed or to be enqueued
2679      *
2680      * @param enqueue if true, event will be {@link #enqueueEvent(boolean, NEWTEvent) enqueued},
2681      *                otherwise {@link #consumeEvent(NEWTEvent) consumed} directly.
2682      * @param wait if true wait until {@link #consumeEvent(NEWTEvent) consumed}.
2683      */
2684     protected void doMouseEvent(final boolean enqueue, final boolean wait, final short eventType, final int modifiers,
2685                                 final int x, final int y, final short button, final float[] rotationXYZ, final float rotationScale) {
2686         if( 0 > button || button > MouseEvent.BUTTON_COUNT ) {
2687             throw new NativeWindowException("Invalid mouse button number" + button);
2688         }
2689         doPointerEvent(enqueue, wait, constMousePointerTypes, eventType, modifiers,
2690                        0 /*actionIdx*/, new short[] { (short)0 }, button,
2691                        new int[]{x}, new int[]{y}, new float[]{0f} /*pressure*/,
2692                        1f /*maxPressure*/, rotationXYZ, rotationScale);
2693     }
2694
2695     /**
2696      * Send multiple-pointer event either to be directly consumed or to be enqueued
2697      * <p>
2698      * The index for the element of multiple-pointer arrays represents the pointer which triggered the event
2699      * is passed via <i>actionIdx</i>.
2700      * </p>
2701      * <p>
2702      * The given pointer names, <code>pNames</code>, are mapped to consecutive pointer IDs starting w/ 0
2703      * using a hash-map if <code>normalPNames</code> is <code>false</code>.
2704      * Otherwise a simple <code>int</code> to <code>short</code> type cast is performed.
2705      * </p>
2706      * <p>
2707      * See {@link #doPointerEvent(boolean, boolean, PointerType[], short, int, int, short[], short, int[], int[], float[], float, float[], float)}
2708      * for details!
2709      * </p>
2710      *
2711      * @param enqueue if true, event will be {@link #enqueueEvent(boolean, NEWTEvent) enqueued},
2712      *                otherwise {@link #consumeEvent(NEWTEvent) consumed} directly.
2713      * @param wait if true wait until {@link #consumeEvent(NEWTEvent) consumed}.
2714      * @param pTypes {@link MouseEvent.PointerType} for each pointer (multiple pointer)
2715      * @param eventType
2716      * @param modifiers
2717      * @param actionIdx index of multiple-pointer arrays representing the pointer which triggered the event
2718      * @param normalPNames see pName below.
2719      * @param pNames Pointer name for each pointer (multiple pointer).
2720      *        We assume consecutive pointer names starting w/ 0 if <code>normalPIDs</code> is <code>true</code>.
2721      *        Otherwise we hash-map the values during state pressed to retrieve the normal ID.
2722      * @param pX X-axis for each pointer (multiple pointer)
2723      * @param pY Y-axis for each pointer (multiple pointer)
2724      * @param pPressure Pressure for each pointer (multiple pointer)
2725      * @param maxPressure Maximum pointer pressure for all pointer
2726      */
2727     public final void doPointerEvent(final boolean enqueue, final boolean wait,
2728                                      final PointerType[] pTypes, final short eventType, final int modifiers,
2729                                      final int actionIdx, final boolean normalPNames, final int[] pNames,
2730                                      final int[] pX, final int[] pY, final float[] pPressure,
2731                                      float maxPressure, final float[] rotationXYZ, final float rotationScale) {
2732         final int pCount = pNames.length;
2733         final short[] pIDs = new short[pCount];
2734         for(int i=0; i<pCount; i++) {
2735             if( !normalPNames ) {
2736                 // hash map int name -> short idx
2737                 final int sz0 = pName2pID.size();
2738                 final Integer pNameI1 = pName2pID.getOrAdd(Integer.valueOf(pNames[i]));
2739                 final short pID = (short)pName2pID.indexOf(pNameI1);
2740                 pIDs[i] = pID;
2741                 if(DEBUG_MOUSE_EVENT) {
2742                     final int sz1 = pName2pID.size();
2743                     if( sz0 != sz1 ) {
2744                         System.err.println("PointerName2ID[sz "+sz1+"]: Map "+pNameI1+" == "+pID);
2745                     }
2746                 }
2747                 if( MouseEvent.EVENT_MOUSE_RELEASED == eventType ) {
2748                     pName2pID.remove(pNameI1);
2749                     if(DEBUG_MOUSE_EVENT) {
2750                         System.err.println("PointerName2ID[sz "+pName2pID.size()+"]: Unmap "+pNameI1+" == "+pID);
2751                     }
2752                 }
2753             } else {
2754                 // simple type cast
2755                 pIDs[i] = (short)pNames[i];
2756             }
2757         }
2758         final short button = 0 < pCount ? (short) ( pIDs[0] + 1 ) : (short)0;
2759         doPointerEvent(enqueue, wait, pTypes, eventType, modifiers, actionIdx, pIDs, button,
2760                        pX, pY, pPressure, maxPressure, rotationXYZ, rotationScale);
2761     }
2762
2763     /**
2764      * Send multiple-pointer event either to be directly consumed or to be enqueued.
2765      * <p>
2766      * Pointer/Mouse Processing Pass 1 (Pass 2 is performed in {@link #consumePointerEvent(MouseEvent)}.
2767      * </p>
2768      * <p>
2769      * Usually directly called by event source to enqueue and process event.
2770      * </p>
2771      * <p>
2772      * The index for the element of multiple-pointer arrays represents the pointer which triggered the event
2773      * is passed via <i>actionIdx</i>.
2774      * </p>
2775      * <p>
2776      * <ul>
2777      * <li>Determine ENTERED/EXITED state</li>
2778      * <li>Remove redundant move/drag events</li>
2779      * <li>Reset states if applicable</li>
2780      * <li>Drop exterior events</li>
2781      * <li>Determine CLICK COUNT</li>
2782      * <li>Ignore sent CLICKED</li>
2783      * <li>Track buttonPressed incl. buttonPressedMask</li>
2784      * <li>Synthesize DRAGGED event (from MOVED if pointer is pressed)</li>
2785      * </ul>
2786      * </p>
2787      *
2788      * @param enqueue if true, event will be {@link #enqueueEvent(boolean, NEWTEvent) enqueued},
2789      *                otherwise {@link #consumeEvent(NEWTEvent) consumed} directly.
2790      * @param wait if true wait until {@link #consumeEvent(NEWTEvent) consumed}.
2791      * @param pTypes {@link MouseEvent.PointerType} for each pointer (multiple pointer)
2792      * @param eventType
2793      * @param modifiers
2794      * @param pActionIdx index of multiple-pointer arrays representing the pointer which triggered the event
2795      * @param pID Pointer ID for each pointer (multiple pointer). We assume consecutive pointerIDs starting w/ 0.
2796      * @param button Corresponding mouse-button, a button of 0 denotes no activity, i.e. {@link PointerType#Mouse} move.
2797      * @param pX X-axis for each pointer (multiple pointer)
2798      * @param pY Y-axis for each pointer (multiple pointer)
2799      * @param pPressure Pressure for each pointer (multiple pointer)
2800      * @param maxPressure Maximum pointer pressure for all pointer
2801      */
2802     public final void doPointerEvent(final boolean enqueue, final boolean wait,
2803                                      final PointerType[] pTypes, final short eventType, int modifiers,
2804                                      final int pActionIdx, final short[] pID, final short buttonIn, final int[] pX, final int[] pY,
2805                                      final float[] pPressure, final float maxPressure, final float[] rotationXYZ, final float rotationScale) {
2806         final long when = System.currentTimeMillis();
2807         final int pCount = pTypes.length;
2808
2809         if( 0 > pActionIdx || pActionIdx >= pCount) {
2810             throw new IllegalArgumentException("actionIdx out of bounds [0.."+(pCount-1)+"]");
2811         }
2812         if( 0 < pActionIdx ) {
2813             // swap values to make idx 0 the triggering pointer
2814             {
2815                 final PointerType aType = pTypes[pActionIdx];
2816                 pTypes[pActionIdx] = pTypes[0];
2817                 pTypes[0] = aType;
2818             }
2819             {
2820                 final short s = pID[pActionIdx];
2821                 pID[pActionIdx] = pID[0];
2822                 pID[0] = s;
2823             }
2824             {
2825                 int s = pX[pActionIdx];
2826                 pX[pActionIdx] = pX[0];
2827                 pX[0] = s;
2828                 s = pY[pActionIdx];
2829                 pY[pActionIdx] = pY[0];
2830                 pY[0] = s;
2831             }
2832             {
2833                 final float aPress = pPressure[pActionIdx];
2834                 pPressure[pActionIdx] = pPressure[0];
2835                 pPressure[0] = aPress;
2836             }
2837         }
2838         final short button;
2839         {
2840             // validate button
2841             if( 0 <= buttonIn && buttonIn <= com.jogamp.newt.event.MouseEvent.BUTTON_COUNT ) { // we allow button==0 for no button, i.e. mouse-ptr move
2842                 button = buttonIn;
2843             } else {
2844                 button = com.jogamp.newt.event.MouseEvent.BUTTON1;
2845             }
2846         }
2847
2848         //
2849         // - Determine ENTERED/EXITED state
2850         // - Remove redundant move/drag events
2851         // - Reset states if applicable
2852         //
2853         int x = pX[0];
2854         int y = pY[0];
2855         final boolean insideSurface = x >= 0 && y >= 0 && x < getSurfaceWidth() && y < getSurfaceHeight();
2856         final Point movePositionP0 = pState1.getMovePosition(pID[0]);
2857         switch( eventType ) {
2858             case MouseEvent.EVENT_MOUSE_EXITED:
2859                 if( pState1.dragging ) {
2860                     // Drop mouse EXIT if dragging, i.e. due to exterior dragging outside of window.
2861                     // NOTE-1: X11 produces the 'premature' EXIT, however it also produces 'EXIT' after exterior dragging!
2862                     // NOTE-2: consumePointerEvent(MouseEvent) will synthesize a missing EXIT event!
2863                     if(DEBUG_MOUSE_EVENT) {
2864                         System.err.println("doPointerEvent: drop "+MouseEvent.getEventTypeString(eventType)+" due to dragging: "+pState1);
2865                     }
2866                     return;
2867                 }
2868                 if( null != movePositionP0 ) {
2869                     if( x==-1 && y==-1 ) {
2870                         x = movePositionP0.getX();
2871                         y = movePositionP0.getY();
2872                     }
2873                     movePositionP0.set(0, 0);
2874                 }
2875                 // Fall through intended!
2876
2877             case MouseEvent.EVENT_MOUSE_ENTERED:
2878                 if( eventType == MouseEvent.EVENT_MOUSE_ENTERED ) {
2879                     pState1.insideSurface = true;
2880                     pState1.exitSent = false;
2881                 } else {
2882                     pState1.insideSurface = false;
2883                     pState1.exitSent = true;
2884                 }
2885                 pState1.clearButton();
2886                 if( pTypes[0] != PointerType.Mouse ) {
2887                     // Drop !MOUSE ENTER/EXIT Events - Safeguard for non compliant implementations only.
2888                     if(DEBUG_MOUSE_EVENT) {
2889                         System.err.println("doPointerEvent: drop "+MouseEvent.getEventTypeString(eventType)+" due to !Mouse but "+pTypes[0]+": "+pState1);
2890                     }
2891                     return;
2892                 }
2893                 // clip coordinates to window dimension
2894                 x = Math.min(Math.max(x,  0), getSurfaceWidth()-1);
2895                 y = Math.min(Math.max(y,  0), getSurfaceHeight()-1);
2896                 break;
2897
2898             case MouseEvent.EVENT_MOUSE_MOVED:
2899             case MouseEvent.EVENT_MOUSE_DRAGGED:
2900                 if( null != movePositionP0 ) {
2901                     if( movePositionP0.getX() == x && movePositionP0.getY() == y ) {
2902                         // Drop same position
2903                         if(DEBUG_MOUSE_EVENT) {
2904                             System.err.println("doPointerEvent: drop "+MouseEvent.getEventTypeString(eventType)+" w/ same position: "+movePositionP0+", "+pState1);
2905                         }
2906                         return;
2907                     }
2908                     movePositionP0.set(x, y);
2909                 }
2910
2911                 // Fall through intended !
2912
2913             default:
2914                 if( pState1.insideSurface != insideSurface ) {
2915                     // ENTER/EXIT!
2916                     pState1.insideSurface = insideSurface;
2917                     if( insideSurface ) {
2918                         pState1.exitSent = false;
2919                     }
2920                     pState1.clearButton();
2921                 }
2922         }
2923
2924         //
2925         // Drop exterior events if not dragging pointer and not EXIT event
2926         // Safeguard for non compliant implementations!
2927         //
2928         if( !pState1.dragging && !insideSurface && MouseEvent.EVENT_MOUSE_EXITED != eventType ) {
2929             if(DEBUG_MOUSE_EVENT) {
2930                 System.err.println("doPointerEvent: drop: "+MouseEvent.getEventTypeString(eventType)+
2931                                    ", mod "+modifiers+", pos "+x+"/"+y+", button "+button+", lastMousePosition: "+movePositionP0+", insideWindow "+insideSurface+", "+pState1);
2932             }
2933             return; // .. invalid ..
2934         }
2935         if(DEBUG_MOUSE_EVENT) {
2936             System.err.println("doPointerEvent: enqueue "+enqueue+", wait "+wait+", "+MouseEvent.getEventTypeString(eventType)+
2937                                ", mod "+modifiers+", pos "+x+"/"+y+", button "+button+", lastMousePosition: "+movePositionP0+", "+pState1);
2938         }
2939
2940         final int buttonMask = InputEvent.getButtonMask(button);
2941         modifiers |= buttonMask; // Always add current button to modifier mask (Bug 571)
2942         modifiers |= pState1.buttonPressedMask; // Always add currently pressed mouse buttons to modifier mask
2943
2944         if( isPointerConfined() ) {
2945             modifiers |= InputEvent.CONFINED_MASK;
2946         }
2947         if( !isPointerVisible() ) {
2948             modifiers |= InputEvent.INVISIBLE_MASK;
2949         }
2950
2951         pX[0] = x;
2952         pY[0] = y;
2953
2954         //
2955         // - Determine CLICK COUNT
2956         // - Ignore sent CLICKED
2957         // - Track buttonPressed incl. buttonPressedMask
2958         // - Synthesize DRAGGED event (from MOVED if pointer is pressed)
2959         //
2960         final MouseEvent e;
2961         switch( eventType ) {
2962             case MouseEvent.EVENT_MOUSE_CLICKED:
2963                 e = null;
2964                 break;
2965
2966             case MouseEvent.EVENT_MOUSE_PRESSED:
2967                 if( 0 >= pPressure[0] ) {
2968                     pPressure[0] = maxPressure;
2969                 }
2970                 pState1.buttonPressedMask |= buttonMask;
2971                 if( 1 == pCount ) {
2972                     if( when - pState1.lastButtonPressTime < MouseEvent.getClickTimeout() ) {
2973                         pState1.lastButtonClickCount++;
2974                     } else {
2975                         pState1.lastButtonClickCount=(short)1;
2976                     }
2977                     pState1.lastButtonPressTime = when;
2978                     pState1.buttonPressed = button;
2979                     e = new MouseEvent(eventType, this, when, modifiers, pTypes, pID,
2980                                        pX, pY, pPressure, maxPressure, button, pState1.lastButtonClickCount, rotationXYZ, rotationScale);
2981                 } else {
2982                     e = new MouseEvent(eventType, this, when, modifiers, pTypes, pID,
2983                                        pX, pY, pPressure, maxPressure, button, (short)1, rotationXYZ, rotationScale);
2984                 }
2985                 break;
2986             case MouseEvent.EVENT_MOUSE_RELEASED:
2987                 pState1.buttonPressedMask &= ~buttonMask;
2988                 if( 1 == pCount ) {
2989                     e = new MouseEvent(eventType, this, when, modifiers, pTypes, pID,
2990                                        pX, pY, pPressure, maxPressure, button, pState1.lastButtonClickCount, rotationXYZ, rotationScale);
2991                     if( when - pState1.lastButtonPressTime >= MouseEvent.getClickTimeout() ) {
2992                         pState1.lastButtonClickCount = (short)0;
2993                         pState1.lastButtonPressTime = 0;
2994                     }
2995                     pState1.buttonPressed = 0;
2996                     pState1.dragging = false;
2997                 } else {
2998                     e = new MouseEvent(eventType, this, when, modifiers, pTypes, pID,
2999                                        pX, pY, pPressure, maxPressure, button, (short)1, rotationXYZ, rotationScale);
3000                     if( 0 == pState1.buttonPressedMask ) {
3001                         pState1.clearButton();
3002                     }
3003                 }
3004                 if( null != movePositionP0 ) {
3005                     movePositionP0.set(0, 0);
3006                 }
3007                 break;
3008             case MouseEvent.EVENT_MOUSE_MOVED:
3009                 if ( 0 != pState1.buttonPressedMask ) { // any button or pointer move -> drag
3010                     e = new MouseEvent(MouseEvent.EVENT_MOUSE_DRAGGED, this, when, modifiers, pTypes, pID,
3011                                        pX, pY, pPressure, maxPressure, pState1.buttonPressed, (short)1, rotationXYZ, rotationScale);
3012                     pState1.dragging = true;
3013                 } else {
3014                     e = new MouseEvent(eventType, this, when, modifiers, pTypes, pID,
3015                                        pX, pY, pPressure, maxPressure, button, (short)0, rotationXYZ, rotationScale);
3016                 }
3017                 break;
3018             case MouseEvent.EVENT_MOUSE_DRAGGED:
3019                 if( 0 >= pPressure[0] ) {
3020                     pPressure[0] = maxPressure;
3021                 }
3022                 pState1.dragging = true;
3023                 // Fall through intended!
3024             default:
3025                 e = new MouseEvent(eventType, this, when, modifiers, pTypes, pID,
3026                                    pX, pY, pPressure, maxPressure, button, (short)0, rotationXYZ, rotationScale);
3027         }
3028         doEvent(enqueue, wait, e); // actual mouse event
3029     }
3030
3031     private static int step(int lower, int edge, int value) {
3032         return value < edge ? lower : value;
3033     }
3034
3035     /**
3036      * Consume the {@link MouseEvent}.
3037      * <p>
3038      * Pointer/Mouse Processing Pass 2 (Pass 1 is performed in {@link #doPointerEvent(boolean, boolean, PointerType[], short, int, int, short[], short, int[], int[], float[], float, float[], float)}).
3039      * </p>
3040      * <p>
3041      * Invoked before dispatching the dequeued event.
3042      * </p>
3043      * <p>
3044      * <ul>
3045      * <li>Validate</li>
3046      * <li>Handle gestures</li>
3047      * <li>Synthesize events [ENTERED, EXIT, CLICK] and gestures.</li>
3048      * <li>Drop exterior events</li>
3049      * <li>Dispatch event to listener</li>
3050      * </ul>
3051      * </p>
3052      */
3053     protected void consumePointerEvent(MouseEvent pe) {
3054         int x = pe.getX();
3055         int y = pe.getY();
3056
3057         if(DEBUG_MOUSE_EVENT) {
3058             System.err.println("consumePointerEvent.in: "+pe+", "+pState0+", pos "+x+"/"+y+", win["+getX()+"/"+getY()+" "+getWidth()+"x"+getHeight()+
3059                                "], pixel["+getSurfaceWidth()+"x"+getSurfaceHeight()+"]");
3060         }
3061
3062         //
3063         // - Determine ENTERED/EXITED state
3064         // - Synthesize ENTERED and EXIT event
3065         // - Reset states if applicable
3066         //
3067         final long when = pe.getWhen();
3068         final int eventType = pe.getEventType();
3069         final boolean insideSurface;
3070         boolean eExitAllowed = false;
3071         MouseEvent eEntered = null, eExited = null;
3072         switch( eventType ) {
3073             case MouseEvent.EVENT_MOUSE_EXITED:
3074                 if( pState0.exitSent || pState0.dragging ) {
3075                     if(DEBUG_MOUSE_EVENT) {
3076                         System.err.println("consumePointerEvent: drop "+(pState0.exitSent?"already sent":"due to dragging")+": "+pe+", "+pState0);
3077                     }
3078                     return;
3079                 }
3080                 // Fall through intended !
3081             case MouseEvent.EVENT_MOUSE_ENTERED:
3082                 // clip coordinates to window dimension
3083                 x = Math.min(Math.max(x,  0), getSurfaceWidth()-1);
3084                 y = Math.min(Math.max(y,  0), getSurfaceHeight()-1);
3085                 pState0.clearButton();
3086                 if( eventType == MouseEvent.EVENT_MOUSE_ENTERED ) {
3087                     insideSurface = true;
3088                     pState0.insideSurface = true;
3089                     pState0.exitSent = false;
3090                     pState0.dragging = false;
3091                 } else {
3092                     insideSurface = false;
3093                     pState0.insideSurface = false;
3094                     pState0.exitSent = true;
3095                 }
3096                 break;
3097
3098             case MouseEvent.EVENT_MOUSE_MOVED:
3099             case MouseEvent.EVENT_MOUSE_RELEASED:
3100                 if( 1 >= pe.getButtonDownCount() ) { // MOVE or RELEASE last button
3101                     eExitAllowed = !pState0.exitSent;
3102                     pState0.dragging = false;
3103                 }
3104                 // Fall through intended !
3105
3106             default:
3107                 insideSurface = x >= 0 && y >= 0 && x < getSurfaceWidth() && y < getSurfaceHeight();
3108                 if( pe.getPointerType(0) == PointerType.Mouse ) {
3109                     if( !pState0.insideSurface && insideSurface ) {
3110                         // ENTER .. use clipped coordinates
3111                         eEntered = new MouseEvent(MouseEvent.EVENT_MOUSE_ENTERED, pe.getSource(), pe.getWhen(), pe.getModifiers(),
3112                                                  Math.min(Math.max(x,  0), getSurfaceWidth()-1),
3113                                                  Math.min(Math.max(y,  0), getSurfaceHeight()-1),
3114                                                  (short)0, (short)0, pe.getRotation(), pe.getRotationScale());
3115                         pState0.exitSent = false;
3116                     } else if( !insideSurface && eExitAllowed ) {
3117                         // EXIT .. use clipped coordinates
3118                         eExited = new MouseEvent(MouseEvent.EVENT_MOUSE_EXITED, pe.getSource(), pe.getWhen(), pe.getModifiers(),
3119                                                  Math.min(Math.max(x,  0), getSurfaceWidth()-1),
3120                                                  Math.min(Math.max(y,  0), getSurfaceHeight()-1),
3121                                                  (short)0, (short)0, pe.getRotation(), pe.getRotationScale());
3122                         pState0.exitSent = true;
3123                     }
3124                 }
3125                 if( pState0.insideSurface != insideSurface || null != eEntered || null != eExited) {
3126                     pState0.clearButton();
3127                 }
3128                 pState0.insideSurface = insideSurface;
3129         }
3130         if( null != eEntered ) {
3131             if(DEBUG_MOUSE_EVENT) {
3132                 System.err.println("consumePointerEvent.send.0: "+eEntered+", "+pState0);
3133             }
3134             dispatchMouseEvent(eEntered);
3135         } else if( DEBUG_MOUSE_EVENT && !insideSurface ) {
3136             System.err.println("INFO consumePointerEvent.exterior: "+pState0+", "+pe);
3137         }
3138
3139         //
3140         // Handle Default Gestures
3141         //
3142         if( defaultGestureHandlerEnabled &&
3143             pe.getPointerType(0).getPointerClass() == MouseEvent.PointerClass.Onscreen )
3144         {
3145             if( null == gesture2PtrTouchScroll ) {
3146                 final int scaledScrollSlop;
3147                 final int scaledDoubleTapSlop;
3148                 final MonitorDevice monitor = getMainMonitor();
3149                 if ( null != monitor ) {
3150                     final DimensionImmutable mm = monitor.getSizeMM();
3151                     final float pixWPerMM = (float)monitor.getCurrentMode().getRotatedWidth() / (float)mm.getWidth();
3152                     final float pixHPerMM = (float)monitor.getCurrentMode().getRotatedHeight() / (float)mm.getHeight();
3153                     final float pixPerMM = Math.min(pixHPerMM, pixWPerMM);
3154                     scaledScrollSlop = Math.round(DoubleTapScrollGesture.SCROLL_SLOP_MM * pixPerMM);
3155                     scaledDoubleTapSlop = Math.round(DoubleTapScrollGesture.DOUBLE_TAP_SLOP_MM * pixPerMM);
3156                     if(DEBUG_MOUSE_EVENT) {
3157                         System.err.println("consumePointerEvent.gscroll: scrollSlop "+scaledScrollSlop+", doubleTapSlop "+scaledDoubleTapSlop+", pixPerMM "+pixPerMM+", "+monitor+", "+pState0);
3158                     }
3159                 } else {
3160                     scaledScrollSlop = DoubleTapScrollGesture.SCROLL_SLOP_PIXEL;
3161                     scaledDoubleTapSlop = DoubleTapScrollGesture.DOUBLE_TAP_SLOP_PIXEL;
3162                 }
3163                 gesture2PtrTouchScroll = new DoubleTapScrollGesture(step(DoubleTapScrollGesture.SCROLL_SLOP_PIXEL, DoubleTapScrollGesture.SCROLL_SLOP_PIXEL/2, scaledScrollSlop),
3164                                                                     step(DoubleTapScrollGesture.DOUBLE_TAP_SLOP_PIXEL, DoubleTapScrollGesture.DOUBLE_TAP_SLOP_PIXEL/2, scaledDoubleTapSlop));
3165             }
3166             if( gesture2PtrTouchScroll.process(pe) ) {
3167                 pe = (MouseEvent) gesture2PtrTouchScroll.getGestureEvent();
3168                 gesture2PtrTouchScroll.clear(false);
3169                 if(DEBUG_MOUSE_EVENT) {
3170                     System.err.println("consumePointerEvent.gscroll: "+pe+", "+pState0);
3171                 }
3172                 dispatchMouseEvent(pe);
3173                 return;
3174             }
3175             if( gesture2PtrTouchScroll.isWithinGesture() ) {
3176                 return; // within gesture .. need more input ..
3177             }
3178         }
3179         //
3180         // Handle Custom Gestures
3181         //
3182         {
3183             final int pointerGestureHandlerCount = pointerGestureHandler.size();
3184             if( pointerGestureHandlerCount > 0 ) {
3185                 boolean withinGesture = false;
3186                 for(int i = 0; !pe.isConsumed() && i < pointerGestureHandlerCount; i++ ) {
3187                     final GestureHandler gh = pointerGestureHandler.get(i);
3188                     if( gh.process(pe) ) {
3189                         final InputEvent ieG = gh.getGestureEvent();
3190                         gh.clear(false);
3191                         if( ieG instanceof MouseEvent ) {
3192                             dispatchMouseEvent((MouseEvent)ieG);
3193                         } else if( ieG instanceof GestureHandler.GestureEvent) {
3194                             final GestureHandler.GestureEvent ge = (GestureHandler.GestureEvent) ieG;
3195                             for(int j = 0; !ge.isConsumed() && j < gestureListeners.size(); j++ ) {
3196                                 gestureListeners.get(j).gestureDetected(ge);
3197                             }
3198                         }
3199                         return;
3200                     }
3201                     withinGesture |= gh.isWithinGesture();
3202                 }
3203                 if( withinGesture ) {
3204                     return;
3205                 }
3206             }
3207         }
3208
3209         //
3210         // - Synthesize mouse CLICKED
3211         // - Ignore sent CLICKED
3212         //
3213         MouseEvent eClicked = null;
3214         switch( eventType ) {
3215             case MouseEvent.EVENT_MOUSE_PRESSED:
3216                 if( 1 == pe.getPointerCount() ) {
3217                     pState0.lastButtonPressTime = when;
3218                 }
3219                 break;
3220             case MouseEvent.EVENT_MOUSE_RELEASED:
3221                 if( 1 == pe.getPointerCount() && when - pState0.lastButtonPressTime < MouseEvent.getClickTimeout() ) {
3222                     eClicked = pe.createVariant(MouseEvent.EVENT_MOUSE_CLICKED);
3223                 } else {
3224                     pState0.lastButtonPressTime = 0;
3225                 }
3226                 break;
3227             case MouseEvent.EVENT_MOUSE_CLICKED:
3228                 // ignore - synthesized here ..
3229                 if(DEBUG_MOUSE_EVENT) {
3230                     System.err.println("consumePointerEvent: drop recv'ed (synth here) "+pe+", "+pState0);
3231                 }
3232                 pe = null;
3233                 break;
3234
3235             case MouseEvent.EVENT_MOUSE_DRAGGED:
3236                 pState0.dragging = true;
3237                 break;
3238         }
3239
3240         if( null != pe ) {
3241             if(DEBUG_MOUSE_EVENT) {
3242                 System.err.println("consumePointerEvent.send.1: "+pe+", "+pState0);
3243             }
3244             dispatchMouseEvent(pe); // actual mouse event
3245         }
3246         if( null != eClicked ) {
3247             if(DEBUG_MOUSE_EVENT) {
3248                 System.err.println("consumePointerEvent.send.2: "+eClicked+", "+pState0);
3249             }
3250             dispatchMouseEvent(eClicked);
3251         }
3252         if( null != eExited ) {
3253             if(DEBUG_MOUSE_EVENT) {
3254                 System.err.println("consumePointerEvent.send.3: "+eExited+", "+pState0);
3255             }
3256             dispatchMouseEvent(eExited);
3257         }
3258     }
3259
3260     @Override
3261     public final void addMouseListener(MouseListener l) {
3262         addMouseListener(-1, l);
3263     }
3264
3265     @Override
3266     public final void addMouseListener(int index, MouseListener l) {
3267         if(l == null) {
3268             return;
3269         }
3270         @SuppressWarnings("unchecked")
3271         ArrayList<MouseListener> clonedListeners = (ArrayList<MouseListener>) mouseListeners.clone();
3272         if(0>index) {
3273             index = clonedListeners.size();
3274         }
3275         clonedListeners.add(index, l);
3276         mouseListeners = clonedListeners;
3277     }
3278
3279     @Override
3280     public final void removeMouseListener(MouseListener l) {
3281         if (l == null) {
3282             return;
3283         }
3284         @SuppressWarnings("unchecked")
3285         ArrayList<MouseListener> clonedListeners = (ArrayList<MouseListener>) mouseListeners.clone();
3286         clonedListeners.remove(l);
3287         mouseListeners = clonedListeners;
3288     }
3289
3290     @Override
3291     public final MouseListener getMouseListener(int index) {
3292         @SuppressWarnings("unchecked")
3293         ArrayList<MouseListener> clonedListeners = (ArrayList<MouseListener>) mouseListeners.clone();
3294         if(0>index) {
3295             index = clonedListeners.size()-1;
3296         }
3297         return clonedListeners.get(index);
3298     }
3299
3300     @Override
3301     public final MouseListener[] getMouseListeners() {
3302         return mouseListeners.toArray(new MouseListener[mouseListeners.size()]);
3303     }
3304
3305     @Override
3306     public final void setDefaultGesturesEnabled(boolean enable) {
3307         defaultGestureHandlerEnabled = enable;
3308     }
3309     @Override
3310     public final boolean areDefaultGesturesEnabled() {
3311         return defaultGestureHandlerEnabled;
3312     }
3313
3314     @Override
3315     public final void addGestureHandler(GestureHandler gh) {
3316         addGestureHandler(-1, gh);
3317     }
3318     @Override
3319     public final void addGestureHandler(int index, GestureHandler gh) {
3320         if(gh == null) {
3321             return;
3322         }
3323         @SuppressWarnings("unchecked")
3324         ArrayList<GestureHandler> cloned = (ArrayList<GestureHandler>) pointerGestureHandler.clone();
3325         if(0>index) {
3326             index = cloned.size();
3327         }
3328         cloned.add(index, gh);
3329         pointerGestureHandler = cloned;
3330     }
3331     @Override
3332     public final void removeGestureHandler(GestureHandler gh) {
3333         if (gh == null) {
3334             return;
3335         }
3336         @SuppressWarnings("unchecked")
3337         ArrayList<GestureHandler> cloned = (ArrayList<GestureHandler>) pointerGestureHandler.clone();
3338         cloned.remove(gh);
3339         pointerGestureHandler = cloned;
3340     }
3341     @Override
3342     public final void addGestureListener(GestureHandler.GestureListener gl) {
3343         addGestureListener(-1, gl);
3344     }
3345     @Override
3346     public final void addGestureListener(int index, GestureHandler.GestureListener gl) {
3347         if(gl == null) {
3348             return;
3349         }
3350         @SuppressWarnings("unchecked")
3351         ArrayList<GestureHandler.GestureListener> cloned = (ArrayList<GestureHandler.GestureListener>) gestureListeners.clone();
3352         if(0>index) {
3353             index = cloned.size();
3354         }
3355         cloned.add(index, gl);
3356         gestureListeners = cloned;
3357     }
3358     @Override
3359     public final void removeGestureListener(GestureHandler.GestureListener gl) {
3360         if (gl == null) {
3361             return;
3362         }
3363         @SuppressWarnings("unchecked")
3364         ArrayList<GestureHandler.GestureListener> cloned = (ArrayList<GestureHandler.GestureListener>) gestureListeners.clone();
3365         cloned.remove(gl);
3366         gestureListeners= cloned;
3367     }
3368
3369     private final void dispatchMouseEvent(MouseEvent e) {
3370         for(int i = 0; !e.isConsumed() && i < mouseListeners.size(); i++ ) {
3371             final MouseListener l = mouseListeners.get(i);
3372             switch(e.getEventType()) {
3373                 case MouseEvent.EVENT_MOUSE_CLICKED:
3374                     l.mouseClicked(e);
3375                     break;
3376                 case MouseEvent.EVENT_MOUSE_ENTERED:
3377                     l.mouseEntered(e);
3378                     break;
3379                 case MouseEvent.EVENT_MOUSE_EXITED:
3380                     l.mouseExited(e);
3381                     break;
3382                 case MouseEvent.EVENT_MOUSE_PRESSED:
3383                     l.mousePressed(e);
3384                     break;
3385                 case MouseEvent.EVENT_MOUSE_RELEASED:
3386                     l.mouseReleased(e);
3387                     break;
3388                 case MouseEvent.EVENT_MOUSE_MOVED:
3389                     l.mouseMoved(e);
3390                     break;
3391                 case MouseEvent.EVENT_MOUSE_DRAGGED:
3392                     l.mouseDragged(e);
3393                     break;
3394                 case MouseEvent.EVENT_MOUSE_WHEEL_MOVED:
3395                     l.mouseWheelMoved(e);
3396                     break;
3397                 default:
3398                     throw new NativeWindowException("Unexpected mouse event type " + e.getEventType());
3399             }
3400         }
3401     }
3402
3403     //
3404     // KeyListener/Event Support
3405     //
3406     private static final int keyTrackingRange = 255;
3407     private final IntBitfield keyPressedState = new IntBitfield( keyTrackingRange + 1 );
3408
3409     protected final boolean isKeyCodeTracked(final short keyCode) {
3410         return ( 0xFFFF & keyCode ) <= keyTrackingRange;
3411     }
3412
3413     /**
3414      * @param keyCode the keyCode to set pressed state
3415      * @param pressed true if pressed, otherwise false
3416      * @return the previus pressed value
3417      */
3418     protected final boolean setKeyPressed(short keyCode, boolean pressed) {
3419         final int v = 0xFFFF & keyCode;
3420         if( v <= keyTrackingRange ) {
3421             return keyPressedState.put(v, pressed);
3422         }
3423         return false;
3424     }
3425     /**
3426      * @param keyCode the keyCode to test pressed state
3427      * @return true if pressed, otherwise false
3428      */
3429     protected final boolean isKeyPressed(short keyCode) {
3430         final int v = 0xFFFF & keyCode;
3431         if( v <= keyTrackingRange ) {
3432             return keyPressedState.get(v);
3433         }
3434         return false;
3435     }
3436
3437     public void sendKeyEvent(short eventType, int modifiers, short keyCode, short keySym, char keyChar) {
3438         // Always add currently pressed mouse buttons to modifier mask
3439         consumeKeyEvent( KeyEvent.create(eventType, this, System.currentTimeMillis(), modifiers | pState1.buttonPressedMask, keyCode, keySym, keyChar) );
3440     }
3441
3442     public void enqueueKeyEvent(boolean wait, short eventType, int modifiers, short keyCode, short keySym, char keyChar) {
3443         // Always add currently pressed mouse buttons to modifier mask
3444         enqueueEvent(wait, KeyEvent.create(eventType, this, System.currentTimeMillis(), modifiers | pState1.buttonPressedMask, keyCode, keySym, keyChar) );
3445     }
3446
3447     @Override
3448     public final void setKeyboardVisible(boolean visible) {
3449         if(isNativeValid()) {
3450             // We don't skip the impl. if it seems that there is no state change,
3451             // since we cannot assume the impl. reliably gives us it's current state.
3452             final boolean ok = setKeyboardVisibleImpl(visible);
3453             if(DEBUG_IMPLEMENTATION || DEBUG_KEY_EVENT) {
3454                 System.err.println("setKeyboardVisible(native): visible "+keyboardVisible+" -- op[visible:"+visible +", ok "+ok+"] -> "+(visible && ok));
3455             }
3456             keyboardVisibilityChanged( visible && ok );
3457         } else {
3458             keyboardVisibilityChanged( visible ); // earmark for creation
3459         }
3460     }
3461     @Override
3462     public final boolean isKeyboardVisible() {
3463         return keyboardVisible;
3464     }
3465     /**
3466      * Returns <code>true</code> if operation was successful, otherwise <code>false</code>.
3467      * <p>
3468      * We assume that a failed invisible operation is due to an already invisible keyboard,
3469      * hence even if an invisible operation failed, the keyboard is considered invisible!