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

jogamp.newt.WindowImpl Maven / Gradle / Ivy

/*
 * Copyright (c) 2008 Sun Microsystems, Inc. All Rights Reserved.
   Copyright (c) 2010 JogAmp Community. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 * - Redistribution of source code must retain the above copyright
 *   notice, this list of conditions and the following disclaimer.
 *
 * - Redistribution in binary form must reproduce the above copyright
 *   notice, this list of conditions and the following disclaimer in the
 *   documentation and/or other materials provided with the distribution.
 *
 * Neither the name of Sun Microsystems, Inc. or the names of
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * This software is provided "AS IS," without a warranty of any kind. ALL
 * EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES,
 * INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A
 * PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN
 * MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL NOT BE LIABLE FOR
 * ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING OR
 * DISTRIBUTING THIS SOFTWARE OR ITS DERIVATIVES. IN NO EVENT WILL SUN OR
 * ITS LICENSORS BE LIABLE FOR ANY LOST REVENUE, PROFIT OR DATA, OR FOR
 * DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL, INCIDENTAL OR PUNITIVE
 * DAMAGES, HOWEVER CAUSED AND REGARDLESS OF THE THEORY OF LIABILITY,
 * ARISING OUT OF THE USE OF OR INABILITY TO USE THIS SOFTWARE, EVEN IF
 * SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
 *
 */

package jogamp.newt;

import java.util.ArrayList;
import java.util.List;
import java.lang.ref.WeakReference;
import java.lang.reflect.Method;

import com.jogamp.common.util.ArrayHashSet;
import com.jogamp.common.util.IntBitfield;
import com.jogamp.common.util.ReflectionUtil;
import com.jogamp.newt.MonitorDevice;
import com.jogamp.newt.NewtFactory;
import com.jogamp.newt.Display;
import com.jogamp.newt.Screen;
import com.jogamp.newt.Window;
import com.jogamp.common.util.locks.LockFactory;
import com.jogamp.common.util.locks.RecursiveLock;
import com.jogamp.newt.event.DoubleTapScrollGesture;
import com.jogamp.newt.event.GestureHandler;
import com.jogamp.newt.event.InputEvent;
import com.jogamp.newt.event.KeyEvent;
import com.jogamp.newt.event.KeyListener;
import com.jogamp.newt.event.MonitorEvent;
import com.jogamp.newt.event.MouseEvent;
import com.jogamp.newt.event.MouseListener;
import com.jogamp.newt.event.NEWTEvent;
import com.jogamp.newt.event.NEWTEventConsumer;
import com.jogamp.newt.event.MonitorModeListener;
import com.jogamp.newt.event.WindowEvent;
import com.jogamp.newt.event.WindowListener;
import com.jogamp.newt.event.WindowUpdateEvent;
import com.jogamp.newt.event.MouseEvent.PointerType;

import javax.media.nativewindow.AbstractGraphicsConfiguration;
import javax.media.nativewindow.AbstractGraphicsDevice;
import javax.media.nativewindow.CapabilitiesChooser;
import javax.media.nativewindow.CapabilitiesImmutable;
import javax.media.nativewindow.NativeSurface;
import javax.media.nativewindow.NativeWindow;
import javax.media.nativewindow.NativeWindowException;
import javax.media.nativewindow.NativeWindowFactory;
import javax.media.nativewindow.SurfaceUpdatedListener;
import javax.media.nativewindow.WindowClosingProtocol;
import javax.media.nativewindow.util.DimensionImmutable;
import javax.media.nativewindow.util.Insets;
import javax.media.nativewindow.util.InsetsImmutable;
import javax.media.nativewindow.util.Point;
import javax.media.nativewindow.util.Rectangle;
import javax.media.nativewindow.util.RectangleImmutable;

import jogamp.nativewindow.SurfaceUpdatedHelper;

public abstract class WindowImpl implements Window, NEWTEventConsumer
{
    public static final boolean DEBUG_TEST_REPARENT_INCOMPATIBLE;

    static {
        Debug.initSingleton();
        DEBUG_TEST_REPARENT_INCOMPATIBLE = Debug.isPropertyDefined("newt.test.Window.reparent.incompatible", true);

        ScreenImpl.initSingleton();
    }

    protected static final ArrayList> windowList = new ArrayList>();

    /** Maybe utilized at a shutdown hook, impl. does not block. */
    public static final void shutdownAll() {
        final int wCount = windowList.size();
        if(DEBUG_IMPLEMENTATION) {
            System.err.println("Window.shutdownAll "+wCount+" instances, on thread "+getThreadName());
        }
        for(int i=0; i0; i++) { // be safe ..
            final WindowImpl w = windowList.remove(0).get();
            if(DEBUG_IMPLEMENTATION) {
                final long wh = null != w ? w.getWindowHandle() : 0;
                System.err.println("Window.shutdownAll["+(i+1)+"/"+wCount+"]: "+toHexString(wh)+", GCed "+(null==w));
            }
            if( null != w ) {
                w.shutdown();
            }
        }
    }
    private static void addWindow2List(WindowImpl window) {
        synchronized(windowList) {
            // GC before add
            int i=0, gced=0;
            while( i < windowList.size() ) {
                if( null == windowList.get(i).get() ) {
                    gced++;
                    windowList.remove(i);
                } else {
                    i++;
                }
            }
            windowList.add(new WeakReference(window));
            if(DEBUG_IMPLEMENTATION) {
                System.err.println("Window.addWindow2List: GCed "+gced+", size "+windowList.size());
            }
        }
    }

    /** Timeout of queued events (repaint and resize) */
    static final long QUEUED_EVENT_TO = 1200; // ms

    private static final PointerType[] constMousePointerTypes = new PointerType[] { PointerType.Mouse };

    //
    // Volatile: Multithread Mutable Access
    //
    private volatile long windowHandle = 0; // lifecycle critical
    private volatile boolean visible = false; // lifecycle critical
    private volatile boolean hasFocus = false;
    private volatile int width = 128, height = 128; // client-area size w/o insets, default: may be overwritten by user
    private volatile int x = 64, y = 64; // client-area pos w/o insets
    private volatile Insets insets = new Insets(); // insets of decoration (if top-level && decorated)

    private RecursiveLock windowLock = LockFactory.createRecursiveLock();  // Window instance wide lock
    private int surfaceLockCount = 0; // surface lock recursion count

    private ScreenImpl screen; // never null after create - may change reference though (reparent)
    private boolean screenReferenceAdded = false;
    private NativeWindow parentWindow = null;
    private long parentWindowHandle = 0;
    private AbstractGraphicsConfiguration config = null; // control access due to delegation
    protected CapabilitiesImmutable capsRequested = null;
    protected CapabilitiesChooser capabilitiesChooser = null; // default null -> default
    private boolean fullscreen = false, brokenFocusChange = false;
    private List fullscreenMonitors = null;
    private boolean fullscreenUseMainMonitor = true;
    private boolean autoPosition = true; // default: true (allow WM to choose top-level position, if not set by user)

    private int nfs_width, nfs_height, nfs_x, nfs_y; // non fullscreen client-area size/pos w/o insets
    private NativeWindow nfs_parent = null;          // non fullscreen parent, in case explicit reparenting is performed (offscreen)
    private String title = "Newt Window";
    private boolean undecorated = false;
    private boolean alwaysOnTop = false;
    private boolean pointerVisible = true;
    private boolean pointerConfined = false;
    private LifecycleHook lifecycleHook = null;

    private Runnable windowDestroyNotifyAction = null;

    private FocusRunnable focusAction = null;
    private KeyListener keyboardFocusHandler = null;

    private SurfaceUpdatedHelper surfaceUpdatedHelper = new SurfaceUpdatedHelper();

    private Object childWindowsLock = new Object();
    private ArrayList childWindows = new ArrayList();

    private ArrayList mouseListeners = new ArrayList();

    /** from event passing: {@link WindowImpl#consumePointerEvent(MouseEvent)}. */
    private static class PointerState0 {
        /** mouse entered window - is inside the window (may be synthetic) */
        boolean insideWindow = false;

        /** last time when a mouse button was pressed */
        long lastButtonPressTime = 0;

        void clearButton() {
            lastButtonPressTime = 0;
        }
    }
    private PointerState0 pState0 = new PointerState0();

    /** from direct input: {@link WindowImpl#doPointerEvent(boolean, boolean, int[], short, int, int, boolean, short[], int[], int[], float[], float, float[], float)}. */
    private static class PointerState1 extends PointerState0 {
        /** current pressed mouse button number */
        short buttonPressed = (short)0;
        /** current pressed mouse button modifier mask */
        int buttonPressedMask = 0;
        /** last mouse button click count */
        short lastButtonClickCount = (short)0;

        @Override
        final void clearButton() {
            super.clearButton();
            lastButtonPressTime = 0;
            lastButtonClickCount = (short)0;
            buttonPressed = 0;
            buttonPressedMask = 0;
        }

        /** last pointer-move position for 8 touch-down pointers */
        final Point[] movePositions = new Point[] {
                new Point(), new Point(), new Point(), new Point(),
                new Point(), new Point(), new Point(), new Point() };
        final Point getMovePosition(int id) {
            if( 0 <= id && id < movePositions.length ) {
                return movePositions[id];
            }
            return null;
        }
    }
    private PointerState1 pState1 = new PointerState1();

    /** pointer names -> pointer ID (consecutive index, starting w/ 0) */
    private final ArrayHashSet pName2pID = new ArrayHashSet();

    private boolean defaultGestureHandlerEnabled = true;
    private DoubleTapScrollGesture gesture2PtrTouchScroll = null;
    private ArrayList pointerGestureHandler = new ArrayList();

    private ArrayList gestureListeners = new ArrayList();

    private ArrayList keyListeners = new ArrayList();

    private ArrayList windowListeners  = new ArrayList();
    private boolean repaintQueued = false;

    /**
     * Workaround for initialization order problems on Mac OS X
     * between native Newt and (apparently) Fmod -- if Fmod is
     * initialized first then the connection to the window server
     * breaks, leading to errors from deep within the AppKit
     */
    public static void init(String type) {
        if (NativeWindowFactory.TYPE_MACOSX.equals(type)) {
            try {
                getWindowClass(type);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    //
    // Construction Methods
    //

    private static Class getWindowClass(String type)
        throws ClassNotFoundException
    {
        final Class windowClass = NewtFactory.getCustomClass(type, "WindowDriver");
        if(null==windowClass) {
            throw new ClassNotFoundException("Failed to find NEWT Window Class <"+type+".WindowDriver>");
        }
        return windowClass;
    }

    public static WindowImpl create(NativeWindow parentWindow, long parentWindowHandle, Screen screen, CapabilitiesImmutable caps) {
        try {
            Class windowClass;
            if(caps.isOnscreen()) {
                windowClass = getWindowClass(screen.getDisplay().getType());
            } else {
                windowClass = OffscreenWindow.class;
            }
            WindowImpl window = (WindowImpl) windowClass.newInstance();
            window.parentWindow = parentWindow;
            window.parentWindowHandle = parentWindowHandle;
            window.screen = (ScreenImpl) screen;
            window.capsRequested = (CapabilitiesImmutable) caps.cloneMutable();
            window.instantiationFinished();
            addWindow2List(window);
            return window;
        } catch (Throwable t) {
            t.printStackTrace();
            throw new NativeWindowException(t);
        }
    }

    public static WindowImpl create(Object[] cstrArguments, Screen screen, CapabilitiesImmutable caps) {
        try {
            Class windowClass = getWindowClass(screen.getDisplay().getType());
            Class[] cstrArgumentTypes = getCustomConstructorArgumentTypes(windowClass);
            if(null==cstrArgumentTypes) {
                throw new NativeWindowException("WindowClass "+windowClass+" doesn't support custom arguments in constructor");
            }
            int argsChecked = verifyConstructorArgumentTypes(cstrArgumentTypes, cstrArguments);
            if ( argsChecked < cstrArguments.length ) {
                throw new NativeWindowException("WindowClass "+windowClass+" constructor mismatch at argument #"+argsChecked+"; Constructor: "+getTypeStrList(cstrArgumentTypes)+", arguments: "+getArgsStrList(cstrArguments));
            }
            WindowImpl window = (WindowImpl) ReflectionUtil.createInstance( windowClass, cstrArgumentTypes, cstrArguments ) ;
            window.screen = (ScreenImpl) screen;
            window.capsRequested = (CapabilitiesImmutable) caps.cloneMutable();
            window.instantiationFinished();
            addWindow2List(window);
            return window;
        } catch (Throwable t) {
            throw new NativeWindowException(t);
        }
    }

    /** Fast invalidation of instance w/o any blocking function call. */
    private final void shutdown() {
        if(null!=lifecycleHook) {
            lifecycleHook.shutdownRenderingAction();
        }
        setWindowHandle(0);
        visible = false;
        fullscreen = false;
        fullscreenMonitors = null;
        fullscreenUseMainMonitor = true;
        hasFocus = false;
        parentWindowHandle = 0;
    }

    protected final void setGraphicsConfiguration(AbstractGraphicsConfiguration cfg) {
        config = cfg;
    }

    public static interface LifecycleHook {
        /**
         * Reset of internal state counter, ie totalFrames, etc.
         * Called from EDT while window is locked.
         */
        public abstract void resetCounter();

        /**
         * Invoked after Window setVisible,
         * allows allocating resources depending on the native Window.
         * Called from EDT while window is locked.
         */
        void setVisibleActionPost(boolean visible, boolean nativeWindowCreated);

        /**
         * Notifies the receiver to preserve resources (GL, ..)
         * for the next destroy*() calls (only), if supported and if value is true, otherwise clears preservation flag.
         * @param value true to set the one-shot preservation if supported, otherwise clears it.
         */
        void preserveGLStateAtDestroy(boolean value);

        /**
         * Invoked before Window destroy action,
         * allows releasing of resources depending on the native Window.
* Surface not locked yet.
* Called not necessarily from EDT. */ void destroyActionPreLock(); /** * Invoked before Window destroy action, * allows releasing of resources depending on the native Window.
* Surface locked.
* Called from EDT while window is locked. */ void destroyActionInLock(); /** * Invoked for expensive modifications, ie while reparenting and MonitorMode change.
* No lock is hold when invoked.
* * @return true is paused, otherwise false. If true {@link #resumeRenderingAction()} shall be issued. * * @see #resumeRenderingAction() */ boolean pauseRenderingAction(); /** * Invoked for expensive modifications, ie while reparenting and MonitorMode change. * No lock is hold when invoked.
* * @see #pauseRenderingAction() */ void resumeRenderingAction(); /** * Shutdown rendering action (thread) abnormally. *

* Should be called only at shutdown, if necessary. *

*/ void shutdownRenderingAction(); } private boolean createNative() { long tStart; if(DEBUG_IMPLEMENTATION) { tStart = System.nanoTime(); System.err.println("Window.createNative() START ("+getThreadName()+", "+this+")"); } else { tStart = 0; } if( null != parentWindow && NativeSurface.LOCK_SURFACE_NOT_READY >= parentWindow.lockSurface() ) { throw new NativeWindowException("Parent surface lock: not ready: "+parentWindow); } // child window: position defaults to 0/0, no auto position, no negative position if( null != parentWindow && ( autoPosition || 0>getX() || 0>getY() ) ) { definePosition(0, 0); } boolean postParentlockFocus = false; try { if(validateParentWindowHandle()) { if( !screenReferenceAdded ) { screen.addReference(); screenReferenceAdded = true; } if(canCreateNativeImpl()) { final int wX, wY; final boolean usePosition; if( autoPosition ) { wX = 0; wY = 0; usePosition = false; } else { wX = getX(); wY = getY(); usePosition = true; } final long t0 = System.currentTimeMillis(); createNativeImpl(); screen.addMonitorModeListener(monitorModeListenerImpl); setTitleImpl(title); setPointerVisibleImpl(pointerVisible); confinePointerImpl(pointerConfined); setKeyboardVisible(keyboardVisible); final long remainingV = waitForVisible(true, false); if( 0 <= remainingV ) { if(isFullscreen()) { synchronized(fullScreenAction) { fullscreen = false; // trigger a state change fullScreenAction.init(true); fullScreenAction.run(); } } else { // Wait until position is reached within tolerances, either auto-position or custom position. waitForPosition(usePosition, wX, wY, Window.TIMEOUT_NATIVEWINDOW); } if (DEBUG_IMPLEMENTATION) { System.err.println("Window.createNative(): elapsed "+(System.currentTimeMillis()-t0)+" ms"); } postParentlockFocus = true; } } } } finally { if(null!=parentWindow) { parentWindow.unlockSurface(); } } if(postParentlockFocus) { // harmonize focus behavior for all platforms: focus on creation requestFocusInt(isFullscreen() /* skipFocusAction if fullscreen */); ((DisplayImpl) screen.getDisplay()).dispatchMessagesNative(); // status up2date } if(DEBUG_IMPLEMENTATION) { System.err.println("Window.createNative() END ("+getThreadName()+", "+this+") total "+ (System.nanoTime()-tStart)/1e6 +"ms"); } return isNativeValid() ; } private void removeScreenReference() { if(screenReferenceAdded) { // be nice, probably already called recursive via // closeAndInvalidate() -> closeNativeIml() -> .. -> windowDestroyed() -> closeAndInvalidate() ! // or via reparentWindow .. etc screenReferenceAdded = false; screen.removeReference(); } } private boolean validateParentWindowHandle() { if(null!=parentWindow) { parentWindowHandle = getNativeWindowHandle(parentWindow); return 0 != parentWindowHandle ; } return true; } private static long getNativeWindowHandle(NativeWindow nativeWindow) { long handle = 0; if(null!=nativeWindow) { boolean wasLocked = false; if( NativeSurface.LOCK_SURFACE_NOT_READY < nativeWindow.lockSurface() ) { wasLocked = true; try { handle = nativeWindow.getWindowHandle(); if(0==handle) { throw new NativeWindowException("Parent native window handle is NULL, after succesful locking: "+nativeWindow); } } catch (NativeWindowException nwe) { if(DEBUG_IMPLEMENTATION) { System.err.println("Window.getNativeWindowHandle: not successful yet: "+nwe); } } finally { nativeWindow.unlockSurface(); } } if(DEBUG_IMPLEMENTATION) { System.err.println("Window.getNativeWindowHandle: locked "+wasLocked+", "+nativeWindow); } } return handle; } //---------------------------------------------------------------------- // NativeSurface: Native implementation // protected int lockSurfaceImpl() { return LOCK_SUCCESS; } protected void unlockSurfaceImpl() { } //---------------------------------------------------------------------- // WindowClosingProtocol implementation // private Object closingListenerLock = new Object(); private WindowClosingMode defaultCloseOperation = WindowClosingMode.DISPOSE_ON_CLOSE; @Override public WindowClosingMode getDefaultCloseOperation() { synchronized (closingListenerLock) { return defaultCloseOperation; } } @Override public WindowClosingMode setDefaultCloseOperation(WindowClosingMode op) { synchronized (closingListenerLock) { WindowClosingMode _op = defaultCloseOperation; defaultCloseOperation = op; return _op; } } //---------------------------------------------------------------------- // Window: Native implementation // /** * Notifies the driver impl. that the instantiation is finished, * ie. instance created and all fields set. */ protected void instantiationFinished() { // nop } protected boolean canCreateNativeImpl() { return true; // default: always able to be created } /** * The native implementation must set the native windowHandle.
* *

* The implementation shall respect the states {@link #isAlwaysOnTop()}/{@link #FLAG_IS_ALWAYSONTOP} and * {@link #isUndecorated()}/{@link #FLAG_IS_UNDECORATED}, ie. the created window shall reflect those settings. *

* *

* The implementation should invoke the referenced java state callbacks * to notify this Java object of state changes.

* * @see #windowDestroyNotify(boolean) * @see #focusChanged(boolean, boolean) * @see #visibleChanged(boolean, boolean) * @see #sizeChanged(int,int) * @see #positionChanged(boolean,int, int) * @see #windowDestroyNotify(boolean) */ protected abstract void createNativeImpl(); protected abstract void closeNativeImpl(); /** * Async request which shall be performed within {@link #TIMEOUT_NATIVEWINDOW}. *

* If if force == false the native implementation * may only request focus if not yet owner.

*

* {@link #focusChanged(boolean, boolean)} should be called * to notify about the focus traversal. *

* * @param force if true, bypass {@link #focusChanged(boolean, boolean)} and force focus request */ protected abstract void requestFocusImpl(boolean force); public static final int FLAG_CHANGE_PARENTING = 1 << 0; public static final int FLAG_CHANGE_DECORATION = 1 << 1; public static final int FLAG_CHANGE_FULLSCREEN = 1 << 2; public static final int FLAG_CHANGE_ALWAYSONTOP = 1 << 3; public static final int FLAG_CHANGE_VISIBILITY = 1 << 4; public static final int FLAG_HAS_PARENT = 1 << 8; public static final int FLAG_IS_UNDECORATED = 1 << 9; public static final int FLAG_IS_FULLSCREEN = 1 << 10; public static final int FLAG_IS_FULLSCREEN_SPAN = 1 << 11; public static final int FLAG_IS_ALWAYSONTOP = 1 << 12; public static final int FLAG_IS_VISIBLE = 1 << 13; /** * The native implementation should invoke the referenced java state callbacks * to notify this Java object of state changes. * *

* Implementations shall set x/y to 0, in case it's negative. This could happen due * to insets and positioning a decorated window to 0/0, which would place the frame * outside of the screen.

* * @param x client-area position, or <0 if unchanged * @param y client-area position, or <0 if unchanged * @param width client-area size, or <=0 if unchanged * @param height client-area size, or <=0 if unchanged * @param flags bitfield of change and status flags * * @see #sizeChanged(int,int) * @see #positionChanged(boolean,int, int) */ protected abstract boolean reconfigureWindowImpl(int x, int y, int width, int height, int flags); /** * Tests whether a single reconfigure flag is supported by implementation. *

* Default is all but {@link #FLAG_IS_FULLSCREEN_SPAN} *

*/ protected boolean isReconfigureFlagSupported(int changeFlags) { return 0 == ( changeFlags & FLAG_IS_FULLSCREEN_SPAN ); } protected int getReconfigureFlags(int changeFlags, boolean visible) { return changeFlags |= ( ( 0 != getParentWindowHandle() ) ? FLAG_HAS_PARENT : 0 ) | ( isUndecorated() ? FLAG_IS_UNDECORATED : 0 ) | ( isFullscreen() ? FLAG_IS_FULLSCREEN : 0 ) | ( isAlwaysOnTop() ? FLAG_IS_ALWAYSONTOP : 0 ) | ( visible ? FLAG_IS_VISIBLE : 0 ) ; } protected static String getReconfigureFlagsAsString(StringBuilder sb, int flags) { if(null == sb) { sb = new StringBuilder(); } sb.append("["); if( 0 != ( FLAG_CHANGE_PARENTING & flags) ) { sb.append("*"); } sb.append("PARENT_"); sb.append(0 != ( FLAG_HAS_PARENT & flags)); sb.append(", "); if( 0 != ( FLAG_CHANGE_FULLSCREEN & flags) ) { sb.append("*"); } sb.append("FS_"); sb.append(0 != ( FLAG_IS_FULLSCREEN & flags)); sb.append("_span_"); sb.append(0 != ( FLAG_IS_FULLSCREEN_SPAN & flags)); sb.append(", "); if( 0 != ( FLAG_CHANGE_DECORATION & flags) ) { sb.append("*"); } sb.append("UNDECOR_"); sb.append(0 != ( FLAG_IS_UNDECORATED & flags)); sb.append(", "); if( 0 != ( FLAG_CHANGE_ALWAYSONTOP & flags) ) { sb.append("*"); } sb.append("ALWAYSONTOP_"); sb.append(0 != ( FLAG_IS_ALWAYSONTOP & flags)); sb.append(", "); if( 0 != ( FLAG_CHANGE_VISIBILITY & flags) ) { sb.append("*"); } sb.append("VISIBLE_"); sb.append(0 != ( FLAG_IS_VISIBLE & flags)); sb.append("]"); return sb.toString(); } protected void setTitleImpl(String title) {} /** * Translates the given window client-area coordinates with top-left origin * to screen coordinates. *

* Since the position reflects the client area, it does not include the insets. *

*

* May return null, in which case the caller shall traverse through the NativeWindow tree * as demonstrated in {@link #getLocationOnScreen(javax.media.nativewindow.util.Point)}. *

* * @return if not null, the screen location of the given coordinates */ protected abstract Point getLocationOnScreenImpl(int x, int y); /** Triggered by user via {@link #getInsets()}.
* Implementations may implement this hook to update the insets.
* However, they may prefer the event driven path via {@link #insetsChanged(boolean, int, int, int, int)}. * * @see #getInsets() * @see #insetsChanged(boolean, int, int, int, int) */ protected abstract void updateInsetsImpl(Insets insets); protected boolean setPointerVisibleImpl(boolean pointerVisible) { return false; } protected boolean confinePointerImpl(boolean confine) { return false; } protected void warpPointerImpl(int x, int y) { } //---------------------------------------------------------------------- // NativeSurface // @Override public final int lockSurface() throws NativeWindowException, RuntimeException { final RecursiveLock _wlock = windowLock; _wlock.lock(); surfaceLockCount++; int res = ( 1 == surfaceLockCount ) ? LOCK_SURFACE_NOT_READY : LOCK_SUCCESS; // new lock ? if ( LOCK_SURFACE_NOT_READY == res ) { try { if( isNativeValid() ) { final AbstractGraphicsDevice adevice = getGraphicsConfiguration().getScreen().getDevice(); adevice.lock(); try { res = lockSurfaceImpl(); } finally { if (LOCK_SURFACE_NOT_READY >= res) { adevice.unlock(); } } } } finally { if (LOCK_SURFACE_NOT_READY >= res) { surfaceLockCount--; _wlock.unlock(); } } } return res; } @Override public final void unlockSurface() { final RecursiveLock _wlock = windowLock; _wlock.validateLocked(); if ( 1 == surfaceLockCount ) { final AbstractGraphicsDevice adevice = getGraphicsConfiguration().getScreen().getDevice(); try { unlockSurfaceImpl(); } finally { adevice.unlock(); } } surfaceLockCount--; _wlock.unlock(); } @Override public final boolean isSurfaceLockedByOtherThread() { return windowLock.isLockedByOtherThread(); } @Override public final Thread getSurfaceLockOwner() { return windowLock.getOwner(); } public final RecursiveLock getLock() { return windowLock; } @Override public long getSurfaceHandle() { return windowHandle; // default: return window handle } @Override public boolean surfaceSwap() { return false; } @Override public final AbstractGraphicsConfiguration getGraphicsConfiguration() { return config.getNativeGraphicsConfiguration(); } @Override public final long getDisplayHandle() { return config.getNativeGraphicsConfiguration().getScreen().getDevice().getHandle(); } @Override public final int getScreenIndex() { return screen.getIndex(); } //---------------------------------------------------------------------- // NativeWindow // // public final void destroy() - see below @Override public final NativeWindow getParent() { return parentWindow; } @Override public final long getWindowHandle() { return windowHandle; } @Override public Point getLocationOnScreen(Point storage) { if(isNativeValid()) { Point d; final RecursiveLock _lock = windowLock; _lock.lock(); try { d = getLocationOnScreenImpl(0, 0); } finally { _lock.unlock(); } if(null!=d) { if(null!=storage) { storage.translate(d.getX(),d.getY()); return storage; } return d; } // fall through intended .. } if(null!=storage) { storage.translate(getX(),getY()); } else { storage = new Point(getX(),getY()); } if(null!=parentWindow) { // traverse through parent list .. parentWindow.getLocationOnScreen(storage); } return storage; } //---------------------------------------------------------------------- // Window // @Override public final boolean isNativeValid() { return 0 != windowHandle ; } @Override public final Screen getScreen() { return screen; } @Override public final MonitorDevice getMainMonitor() { return screen.getMainMonitor(new Rectangle(getX(), getY(), getWidth(), getHeight())); } protected final void setVisibleImpl(boolean visible, int x, int y, int width, int height) { reconfigureWindowImpl(x, y, width, height, getReconfigureFlags(FLAG_CHANGE_VISIBILITY, visible)); } final void setVisibleActionImpl(boolean visible) { boolean nativeWindowCreated = false; boolean madeVisible = false; final RecursiveLock _lock = windowLock; _lock.lock(); try { if(!visible && null!=childWindows && childWindows.size()>0) { synchronized(childWindowsLock) { for(int i = 0; i < childWindows.size(); i++ ) { NativeWindow nw = childWindows.get(i); if(nw instanceof WindowImpl) { ((WindowImpl)nw).setVisible(false); } } } } if(!isNativeValid() && visible) { if( 00) { synchronized(childWindowsLock) { for(int i = 0; i < childWindows.size(); i++ ) { NativeWindow nw = childWindows.get(i); if(nw instanceof WindowImpl) { ((WindowImpl)nw).setVisible(true); } } } } if(DEBUG_IMPLEMENTATION) { System.err.println("Window setVisible: END ("+getThreadName()+") "+getX()+"/"+getY()+" "+getWidth()+"x"+getHeight()+", fs "+fullscreen+", windowHandle "+toHexString(windowHandle)+", visible: "+WindowImpl.this.visible+", nativeWindowCreated: "+nativeWindowCreated+", madeVisible: "+madeVisible); } } finally { if(null!=lifecycleHook) { lifecycleHook.resetCounter(); } _lock.unlock(); } if( nativeWindowCreated || madeVisible ) { sendWindowEvent(WindowEvent.EVENT_WINDOW_RESIZED); // trigger a resize/relayout and repaint to listener } } private class VisibleAction implements Runnable { boolean visible; private VisibleAction(boolean visible) { this.visible = visible; } @Override public final void run() { setVisibleActionImpl(visible); } } protected void setVisible(boolean wait, boolean visible) { if(DEBUG_IMPLEMENTATION) { System.err.println("Window setVisible: START ("+getThreadName()+") "+getX()+"/"+getY()+" "+getWidth()+"x"+getHeight()+", fs "+fullscreen+", windowHandle "+toHexString(windowHandle)+", visible: "+this.visible+" -> "+visible+", parentWindowHandle "+toHexString(parentWindowHandle)+", parentWindow "+(null!=parentWindow)); } runOnEDTIfAvail(wait, new VisibleAction(visible)); } @Override public void setVisible(boolean visible) { setVisible(true, visible); } private class SetSizeAction implements Runnable { int width, height; boolean disregardFS; private SetSizeAction(int w, int h, boolean disregardFS) { this.width = w; this.height = h; this.disregardFS = disregardFS; } @Override public final void run() { final RecursiveLock _lock = windowLock; _lock.lock(); try { if ( ( disregardFS || !isFullscreen() ) && ( getWidth() != width || getHeight() != height ) ) { if(DEBUG_IMPLEMENTATION) { System.err.println("Window setSize: START "+getWidth()+"x"+getHeight()+" -> "+width+"x"+height+", fs "+fullscreen+", windowHandle "+toHexString(windowHandle)+", visible "+visible); } int visibleAction; // 0 nop, 1 invisible, 2 visible (create) if ( visible && isNativeValid() && ( 0 >= width || 0 >= height ) ) { visibleAction=1; // invisible defineSize(0, 0); } else if ( visible && !isNativeValid() && 0 < width && 0 < height ) { visibleAction = 2; // visible (create) defineSize(width, height); } else if ( visible && isNativeValid() ) { visibleAction = 0; // this width/height will be set by windowChanged, called by the native implementation reconfigureWindowImpl(getX(), getY(), width, height, getReconfigureFlags(0, isVisible())); WindowImpl.this.waitForSize(width, height, false, TIMEOUT_NATIVEWINDOW); } else { // invisible or invalid w/ 0 size visibleAction = 0; defineSize(width, height); } if(DEBUG_IMPLEMENTATION) { System.err.println("Window setSize: END "+getWidth()+"x"+getHeight()+", visibleAction "+visibleAction); } switch(visibleAction) { case 1: setVisibleActionImpl(false); break; case 2: setVisibleActionImpl(true); break; } } } finally { _lock.unlock(); } } } private void setFullscreenSize(int width, int height) { runOnEDTIfAvail(true, new SetSizeAction(width, height, true)); } @Override public void setSize(int width, int height) { runOnEDTIfAvail(true, new SetSizeAction(width, height, false)); } @Override public void setTopLevelSize(int width, int height) { setSize(width - getInsets().getTotalWidth(), height - getInsets().getTotalHeight()); } private class DestroyAction implements Runnable { @Override public final void run() { boolean animatorPaused = false; if(null!=lifecycleHook) { animatorPaused = lifecycleHook.pauseRenderingAction(); } if(null!=lifecycleHook) { lifecycleHook.destroyActionPreLock(); } final RecursiveLock _lock = windowLock; _lock.lock(); try { if(DEBUG_IMPLEMENTATION) { System.err.println("Window DestroyAction() hasScreen "+(null != screen)+", isNativeValid "+isNativeValid()+" - "+getThreadName()); } // send synced destroy-notify notification sendWindowEvent(WindowEvent.EVENT_WINDOW_DESTROY_NOTIFY); // Childs first .. synchronized(childWindowsLock) { if(childWindows.size()>0) { // avoid ConcurrentModificationException: parent -> child -> parent.removeChild(this) @SuppressWarnings("unchecked") ArrayList clonedChildWindows = (ArrayList) childWindows.clone(); while( clonedChildWindows.size() > 0 ) { NativeWindow nw = clonedChildWindows.remove(0); if(nw instanceof WindowImpl) { ((WindowImpl)nw).windowDestroyNotify(true); } else { nw.destroy(); } } } } if(null!=lifecycleHook) { // send synced destroy notification for proper cleanup, eg GLWindow/OpenGL lifecycleHook.destroyActionInLock(); } if( isNativeValid() ) { screen.removeMonitorModeListener(monitorModeListenerImpl); closeNativeImpl(); final AbstractGraphicsDevice cfgADevice = config.getScreen().getDevice(); if( cfgADevice != screen.getDisplay().getGraphicsDevice() ) { // don't pull display's device cfgADevice.close(); // ensure a cfg's device is closed } setGraphicsConfiguration(null); } removeScreenReference(); Display dpy = screen.getDisplay(); if(null != dpy) { dpy.validateEDTStopped(); } // send synced destroyed notification sendWindowEvent(WindowEvent.EVENT_WINDOW_DESTROYED); if(DEBUG_IMPLEMENTATION) { System.err.println("Window.destroy() END "+getThreadName()/*+", "+WindowImpl.this*/); } } finally { // update states before release window lock setWindowHandle(0); visible = false; fullscreen = false; fullscreenMonitors = null; fullscreenUseMainMonitor = true; hasFocus = false; parentWindowHandle = 0; _lock.unlock(); } if(animatorPaused) { lifecycleHook.resumeRenderingAction(); } // these refs shall be kept alive - resurrection via setVisible(true) /** if(null!=parentWindow && parentWindow instanceof Window) { ((Window)parentWindow).removeChild(WindowImpl.this); } childWindows = null; surfaceUpdatedListeners = null; mouseListeners = null; keyListeners = null; capsRequested = null; lifecycleHook = null; screen = null; windowListeners = null; parentWindow = null; */ } } private final DestroyAction destroyAction = new DestroyAction(); @Override public void destroy() { visible = false; // Immediately mark synchronized visibility flag, avoiding possible recreation runOnEDTIfAvail(true, destroyAction); } protected void destroy(boolean preserveResources) { if( null != lifecycleHook ) { lifecycleHook.preserveGLStateAtDestroy( preserveResources ); } destroy(); } /** * @param cWin child window, must not be null * @param pWin parent window, may be null * @return true if at least one of both window's configurations is offscreen */ protected static boolean isOffscreenInstance(NativeWindow cWin, NativeWindow pWin) { boolean ofs = false; final AbstractGraphicsConfiguration cWinCfg = cWin.getGraphicsConfiguration(); if( null != cWinCfg ) { ofs = !cWinCfg.getChosenCapabilities().isOnscreen(); } if( !ofs && null != pWin ) { final AbstractGraphicsConfiguration pWinCfg = pWin.getGraphicsConfiguration(); if( null != pWinCfg ) { ofs = !pWinCfg.getChosenCapabilities().isOnscreen(); } } return ofs; } private class ReparentAction implements Runnable { final NativeWindow newParentWindow; final int topLevelX, topLevelY; boolean forceDestroyCreate; ReparentOperation operation; private ReparentAction(NativeWindow newParentWindow, int topLevelX, int topLevelY, boolean forceDestroyCreate) { this.newParentWindow = newParentWindow; this.topLevelX = topLevelX; this.topLevelY = topLevelY; this.forceDestroyCreate = forceDestroyCreate | DEBUG_TEST_REPARENT_INCOMPATIBLE; this.operation = ReparentOperation.ACTION_INVALID; // ensure it's set } private ReparentOperation getOp() { return operation; } private void setScreen(ScreenImpl newScreen) { // never null ! removeScreenReference(); screen = newScreen; } @Override public final void run() { boolean animatorPaused = false; if(null!=lifecycleHook) { animatorPaused = lifecycleHook.pauseRenderingAction(); } reparent(); if(animatorPaused) { lifecycleHook.resumeRenderingAction(); } } private void reparent() { // mirror pos/size so native change notification can get overwritten final int oldX = getX(); final int oldY = getY(); final int oldWidth = getWidth(); final int oldHeight = getHeight(); final int x, y; int width = oldWidth; int height = oldHeight; boolean wasVisible; final RecursiveLock _lock = windowLock; _lock.lock(); try { if(isNativeValid()) { // force recreation if offscreen, since it may become onscreen forceDestroyCreate |= isOffscreenInstance(WindowImpl.this, newParentWindow); } wasVisible = isVisible(); Window newParentWindowNEWT = null; if(newParentWindow instanceof Window) { newParentWindowNEWT = (Window) newParentWindow; } long newParentWindowHandle = 0 ; if(DEBUG_IMPLEMENTATION) { System.err.println("Window.reparent: START ("+getThreadName()+") valid "+isNativeValid()+", windowHandle "+toHexString(windowHandle)+" parentWindowHandle "+toHexString(parentWindowHandle)+", visible "+wasVisible+", old parentWindow: "+Display.hashCodeNullSafe(parentWindow)+", new parentWindow: "+Display.hashCodeNullSafe(newParentWindow)+", forceDestroyCreate "+forceDestroyCreate); } if(null!=newParentWindow) { // REPARENT TO CHILD WINDOW // reset position to 0/0 within parent space x = 0; y = 0; // refit if size is bigger than parent if( width > newParentWindow.getWidth() ) { width = newParentWindow.getWidth(); } if( height > newParentWindow.getHeight() ) { height = newParentWindow.getHeight(); } // Case: Child Window newParentWindowHandle = getNativeWindowHandle(newParentWindow); if(0 == newParentWindowHandle) { // Case: Parent's native window not realized yet if(null==newParentWindowNEWT) { throw new NativeWindowException("Reparenting with non NEWT Window type only available after it's realized: "+newParentWindow); } // Destroy this window and use parent's Screen. // It may be created properly when the parent is made visible. destroy( false ); setScreen( (ScreenImpl) newParentWindowNEWT.getScreen() ); operation = ReparentOperation.ACTION_NATIVE_CREATION_PENDING; } else if(newParentWindow != getParent()) { // Case: Parent's native window realized and changed if( !isNativeValid() ) { // May create a new compatible Screen/Display and // mark it for creation. if(null!=newParentWindowNEWT) { setScreen( (ScreenImpl) newParentWindowNEWT.getScreen() ); } else { final Screen newScreen = NewtFactory.createCompatibleScreen(newParentWindow, screen); if( screen != newScreen ) { // auto destroy on-the-fly created Screen/Display setScreen( (ScreenImpl) newScreen ); } } if( 0 < width && 0 < height ) { operation = ReparentOperation.ACTION_NATIVE_CREATION; } else { operation = ReparentOperation.ACTION_NATIVE_CREATION_PENDING; } } else if ( forceDestroyCreate || !NewtFactory.isScreenCompatible(newParentWindow, screen) ) { // Destroy this window, may create a new compatible Screen/Display, while trying to preserve resources if becoming visible again. destroy( wasVisible ); if(null!=newParentWindowNEWT) { setScreen( (ScreenImpl) newParentWindowNEWT.getScreen() ); } else { setScreen( (ScreenImpl) NewtFactory.createCompatibleScreen(newParentWindow, screen) ); } operation = ReparentOperation.ACTION_NATIVE_CREATION; } else { // Mark it for native reparenting operation = ReparentOperation.ACTION_NATIVE_REPARENTING; } } else { // Case: Parent's native window realized and not changed operation = ReparentOperation.ACTION_NOP; } } else { // REPARENT TO TOP-LEVEL WINDOW if( 0 <= topLevelX && 0 <= topLevelY ) { x = topLevelX; y = topLevelY; } else if( null != parentWindow ) { // child -> top // put client to current parent+child position final Point p = getLocationOnScreen(null); x = p.getX(); y = p.getY(); } else { x = oldX; y = oldY; } // Case: Top Window if( 0 == parentWindowHandle ) { // Already Top Window operation = ReparentOperation.ACTION_NOP; } else if( !isNativeValid() || forceDestroyCreate ) { // Destroy this window and mark it for [pending] creation. // If isNativeValid() and becoming visible again - try to preserve resources, i.e. b/c on-/offscreen switch. destroy( isNativeValid() && wasVisible ); if( 0 < width && 0 < height ) { operation = ReparentOperation.ACTION_NATIVE_CREATION; } else { operation = ReparentOperation.ACTION_NATIVE_CREATION_PENDING; } } else { // Mark it for native reparenting operation = ReparentOperation.ACTION_NATIVE_REPARENTING; } } parentWindowHandle = newParentWindowHandle; if ( ReparentOperation.ACTION_INVALID == operation ) { throw new NativeWindowException("Internal Error: reparentAction not set"); } if(DEBUG_IMPLEMENTATION) { System.err.println("Window.reparent: ACTION ("+getThreadName()+") windowHandle "+toHexString(windowHandle)+" new parentWindowHandle "+toHexString(newParentWindowHandle)+", reparentAction "+operation+", pos/size "+x+"/"+y+" "+width+"x"+height+", visible "+wasVisible); } if( ReparentOperation.ACTION_NOP == operation ) { return; } // rearrange window tree if(null!=parentWindow && parentWindow instanceof Window) { ((Window)parentWindow).removeChild(WindowImpl.this); } parentWindow = newParentWindow; if(parentWindow instanceof Window) { ((Window)parentWindow).addChild(WindowImpl.this); } if( ReparentOperation.ACTION_NATIVE_REPARENTING == operation ) { final DisplayImpl display = (DisplayImpl) screen.getDisplay(); display.dispatchMessagesNative(); // status up2date // TOP -> CLIENT: !visible first (fixes X11 unsuccessful return to parent window) if( null != parentWindow && wasVisible && NativeWindowFactory.TYPE_X11 == NativeWindowFactory.getNativeWindowType(true) ) { setVisibleImpl(false, oldX, oldY, oldWidth, oldHeight); WindowImpl.this.waitForVisible(false, false); // FIXME: Some composite WM behave slacky .. give 'em chance to change state -> invisible, // even though we do exactly that (KDE+Composite) try { Thread.sleep(100); } catch (InterruptedException e) { } display.dispatchMessagesNative(); // status up2date } // Lock parentWindow only during reparenting (attempt) final NativeWindow parentWindowLocked; if( null != parentWindow ) { parentWindowLocked = parentWindow; if( NativeSurface.LOCK_SURFACE_NOT_READY >= parentWindowLocked.lockSurface() ) { throw new NativeWindowException("Parent surface lock: not ready: "+parentWindowLocked); } // update native handle, locked state parentWindowHandle = parentWindowLocked.getWindowHandle(); } else { parentWindowLocked = null; } boolean ok = false; try { ok = reconfigureWindowImpl(x, y, width, height, getReconfigureFlags(FLAG_CHANGE_PARENTING | FLAG_CHANGE_DECORATION, isVisible())); } finally { if(null!=parentWindowLocked) { parentWindowLocked.unlockSurface(); } } definePosition(x, y); // position might not get updated by WM events (SWT parent apparently) // set visible again if(ok) { display.dispatchMessagesNative(); // status up2date if(wasVisible) { setVisibleImpl(true, x, y, width, height); ok = 0 <= WindowImpl.this.waitForVisible(true, false); if(ok) { ok = WindowImpl.this.waitForSize(width, height, false, TIMEOUT_NATIVEWINDOW); } if(ok) { ok = WindowImpl.this.waitForPosition(true, x, y, TIMEOUT_NATIVEWINDOW); } if(ok) { requestFocusInt( 0 == parentWindowHandle /* skipFocusAction if top-level */); display.dispatchMessagesNative(); // status up2date } } } if(!ok || !wasVisible) { // make size and position persistent manual, // since we don't have a WM feedback (invisible or recreation) definePosition(x, y); defineSize(width, height); } if(!ok) { // native reparent failed -> try creation, while trying to preserve resources if becoming visible again. if(DEBUG_IMPLEMENTATION) { System.err.println("Window.reparent: native reparenting failed ("+getThreadName()+") windowHandle "+toHexString(windowHandle)+" parentWindowHandle "+toHexString(parentWindowHandle)+" -> "+toHexString(newParentWindowHandle)+" - Trying recreation"); } destroy( wasVisible ); operation = ReparentOperation.ACTION_NATIVE_CREATION ; } } else { // Case // ACTION_NATIVE_CREATION // ACTION_NATIVE_CREATION_PENDING; // make size and position persistent for proper [re]creation definePosition(x, y); defineSize(width, height); } if(DEBUG_IMPLEMENTATION) { System.err.println("Window.reparent: END-1 ("+getThreadName()+") windowHandle "+toHexString(windowHandle)+", visible: "+visible+", parentWindowHandle "+toHexString(parentWindowHandle)+", parentWindow "+ Display.hashCodeNullSafe(parentWindow)+" "+getX()+"/"+getY()+" "+getWidth()+"x"+getHeight()); } } finally { if(null!=lifecycleHook) { lifecycleHook.resetCounter(); } _lock.unlock(); } if(wasVisible) { switch (operation) { case ACTION_NATIVE_REPARENTING: // trigger a resize/relayout and repaint to listener sendWindowEvent(WindowEvent.EVENT_WINDOW_RESIZED); break; case ACTION_NATIVE_CREATION: // This may run on the new Display/Screen connection, hence a new EDT task runOnEDTIfAvail(true, reparentActionRecreate); break; default: } } if(DEBUG_IMPLEMENTATION) { System.err.println("Window.reparent: END-X ("+getThreadName()+") windowHandle "+toHexString(windowHandle)+", visible: "+visible+", parentWindowHandle "+toHexString(parentWindowHandle)+", parentWindow "+ Display.hashCodeNullSafe(parentWindow)+" "+getX()+"/"+getY()+" "+getWidth()+"x"+getHeight()); } } } private class ReparentActionRecreate implements Runnable { @Override public final void run() { final RecursiveLock _lock = windowLock; _lock.lock(); try { if(DEBUG_IMPLEMENTATION) { System.err.println("Window.reparent: ReparentActionRecreate ("+getThreadName()+") windowHandle "+toHexString(windowHandle)+", visible: "+visible+", parentWindowHandle "+toHexString(parentWindowHandle)+", parentWindow "+Display.hashCodeNullSafe(parentWindow)); } setVisibleActionImpl(true); // native creation requestFocusInt( 0 == parentWindowHandle /* skipFocusAction if top-level */); } finally { _lock.unlock(); } } } private final ReparentActionRecreate reparentActionRecreate = new ReparentActionRecreate(); @Override public final ReparentOperation reparentWindow(NativeWindow newParent) { return reparentWindow(newParent, -1, -1, false); } @Override public ReparentOperation reparentWindow(NativeWindow newParent, int x, int y, boolean forceDestroyCreate) { final ReparentAction reparentAction = new ReparentAction(newParent, x, y, forceDestroyCreate); runOnEDTIfAvail(true, reparentAction); return reparentAction.getOp(); } @Override public CapabilitiesChooser setCapabilitiesChooser(CapabilitiesChooser chooser) { CapabilitiesChooser old = this.capabilitiesChooser; this.capabilitiesChooser = chooser; return old; } @Override public final CapabilitiesImmutable getChosenCapabilities() { return getGraphicsConfiguration().getChosenCapabilities(); } @Override public final CapabilitiesImmutable getRequestedCapabilities() { return capsRequested; } private class DecorationAction implements Runnable { boolean undecorated; private DecorationAction(boolean undecorated) { this.undecorated = undecorated; } @Override public final void run() { final RecursiveLock _lock = windowLock; _lock.lock(); try { if(WindowImpl.this.undecorated != undecorated) { // set current state WindowImpl.this.undecorated = undecorated; if( isNativeValid() && !isFullscreen() ) { // Mirror pos/size so native change notification can get overwritten final int x = getX(); final int y = getY(); final int width = getWidth(); final int height = getHeight(); DisplayImpl display = (DisplayImpl) screen.getDisplay(); display.dispatchMessagesNative(); // status up2date reconfigureWindowImpl(x, y, width, height, getReconfigureFlags(FLAG_CHANGE_DECORATION, isVisible())); display.dispatchMessagesNative(); // status up2date } } } finally { _lock.unlock(); } sendWindowEvent(WindowEvent.EVENT_WINDOW_RESIZED); // trigger a resize/relayout and repaint to listener } } @Override public void setUndecorated(boolean value) { runOnEDTIfAvail(true, new DecorationAction(value)); } @Override public final boolean isUndecorated() { return 0 != parentWindowHandle || undecorated || fullscreen ; } private class AlwaysOnTopAction implements Runnable { boolean alwaysOnTop; private AlwaysOnTopAction(boolean alwaysOnTop) { this.alwaysOnTop = alwaysOnTop; } @Override public final void run() { final RecursiveLock _lock = windowLock; _lock.lock(); try { if(WindowImpl.this.alwaysOnTop != alwaysOnTop) { // set current state WindowImpl.this.alwaysOnTop = alwaysOnTop; if( isNativeValid() ) { // Mirror pos/size so native change notification can get overwritten final int x = getX(); final int y = getY(); final int width = getWidth(); final int height = getHeight(); DisplayImpl display = (DisplayImpl) screen.getDisplay(); display.dispatchMessagesNative(); // status up2date reconfigureWindowImpl(x, y, width, height, getReconfigureFlags(FLAG_CHANGE_ALWAYSONTOP, isVisible())); display.dispatchMessagesNative(); // status up2date } } } finally { _lock.unlock(); } sendWindowEvent(WindowEvent.EVENT_WINDOW_RESIZED); // trigger a resize/relayout and repaint to listener } } @Override public final void setAlwaysOnTop(boolean value) { runOnEDTIfAvail(true, new AlwaysOnTopAction(value)); } @Override public final boolean isAlwaysOnTop() { return alwaysOnTop; } @Override public String getTitle() { return title; } @Override public void setTitle(String title) { if (title == null) { title = ""; } this.title = title; if(0 != getWindowHandle()) { setTitleImpl(title); } } @Override public boolean isPointerVisible() { return pointerVisible; } @Override public void setPointerVisible(boolean pointerVisible) { if(this.pointerVisible != pointerVisible) { boolean setVal = 0 == getWindowHandle(); if(!setVal) { setVal = setPointerVisibleImpl(pointerVisible); } if(setVal) { this.pointerVisible = pointerVisible; } } } @Override public boolean isPointerConfined() { return pointerConfined; } @Override public void confinePointer(boolean confine) { if(this.pointerConfined != confine) { boolean setVal = 0 == getWindowHandle(); if(!setVal) { if(confine) { requestFocus(); warpPointer(getWidth()/2, getHeight()/2); } setVal = confinePointerImpl(confine); if(confine) { // give time to deliver mouse movements w/o confinement, // this allows user listener to sync previous position value to the new centered position try { Thread.sleep(3 * screen.getDisplay().getEDTUtil().getPollPeriod()); } catch (InterruptedException e) { } } } if(setVal) { this.pointerConfined = confine; } } } @Override public void warpPointer(int x, int y) { if(0 != getWindowHandle()) { warpPointerImpl(x, y); } } @Override public final InsetsImmutable getInsets() { if(isUndecorated()) { return Insets.getZero(); } updateInsetsImpl(insets); return insets; } @Override public final int getWidth() { return width; } @Override public final int getHeight() { return height; } @Override public final int getX() { return x; } @Override public final int getY() { return y; } protected final boolean autoPosition() { return autoPosition; } /** Sets the position fields {@link #x} and {@link #y} to the given values and {@link #autoPosition} to false. */ protected final void definePosition(int x, int y) { if(DEBUG_IMPLEMENTATION) { System.err.println("definePosition: "+this.x+"/"+this.y+" -> "+x+"/"+y); // Thread.dumpStack(); } autoPosition = false; this.x = x; this.y = y; } /** Sets the size fields {@link #width} and {@link #height} to the given values. */ protected final void defineSize(int width, int height) { if(DEBUG_IMPLEMENTATION) { System.err.println("defineSize: "+this.width+"x"+this.height+" -> "+width+"x"+height); // Thread.dumpStack(); } this.width = width; this.height = height; } @Override public final boolean isVisible() { return visible; } @Override public final boolean isFullscreen() { return fullscreen; } //---------------------------------------------------------------------- // Window // @Override public final Window getDelegatedWindow() { return this; } //---------------------------------------------------------------------- // WindowImpl // /** * If the implementation is capable of detecting a device change * return true and clear the status/reason of the change. */ public boolean hasDeviceChanged() { return false; } public LifecycleHook getLifecycleHook() { return lifecycleHook; } public LifecycleHook setLifecycleHook(LifecycleHook hook) { LifecycleHook old = lifecycleHook; lifecycleHook = hook; return old; } /** * If this Window actually wraps a {@link NativeSurface} from another instance or toolkit, * it will return such reference. Otherwise returns null. */ public NativeSurface getWrappedSurface() { return null; } @Override public void setWindowDestroyNotifyAction(Runnable r) { windowDestroyNotifyAction = r; } /** * Returns the non delegated {@link AbstractGraphicsConfiguration}, * see {@link #getGraphicsConfiguration()}. */ public final AbstractGraphicsConfiguration getPrivateGraphicsConfiguration() { return config; } protected final long getParentWindowHandle() { return isFullscreen() ? 0 : parentWindowHandle; } @Override public String toString() { StringBuilder sb = new StringBuilder(); sb.append(getClass().getName()+"[Config "+config+ "\n, "+screen+ "\n, ParentWindow "+parentWindow+ "\n, ParentWindowHandle "+toHexString(parentWindowHandle)+" ("+(0!=getParentWindowHandle())+")"+ "\n, WindowHandle "+toHexString(getWindowHandle())+ "\n, SurfaceHandle "+toHexString(getSurfaceHandle())+ " (lockedExt window "+windowLock.isLockedByOtherThread()+", surface "+isSurfaceLockedByOtherThread()+")"+ "\n, Pos "+getX()+"/"+getY()+" (auto "+autoPosition()+"), size "+getWidth()+"x"+getHeight()+ "\n, Visible "+isVisible()+", focus "+hasFocus()+ "\n, Undecorated "+undecorated+" ("+isUndecorated()+")"+ "\n, AlwaysOnTop "+alwaysOnTop+", Fullscreen "+fullscreen+ "\n, WrappedSurface "+getWrappedSurface()+ "\n, ChildWindows "+childWindows.size()); sb.append(", SurfaceUpdatedListeners num "+surfaceUpdatedHelper.size()+" ["); for (int i = 0; i < surfaceUpdatedHelper.size(); i++ ) { sb.append(surfaceUpdatedHelper.get(i)+", "); } sb.append("], WindowListeners num "+windowListeners.size()+" ["); for (int i = 0; i < windowListeners.size(); i++ ) { sb.append(windowListeners.get(i)+", "); } sb.append("], MouseListeners num "+mouseListeners.size()+" ["); for (int i = 0; i < mouseListeners.size(); i++ ) { sb.append(mouseListeners.get(i)+", "); } sb.append("], PointerGestures default "+defaultGestureHandlerEnabled+", custom "+pointerGestureHandler.size()+" ["); for (int i = 0; i < pointerGestureHandler.size(); i++ ) { sb.append(pointerGestureHandler.get(i)+", "); } sb.append("], KeyListeners num "+keyListeners.size()+" ["); for (int i = 0; i < keyListeners.size(); i++ ) { sb.append(keyListeners.get(i)+", "); } sb.append("], windowLock "+windowLock+", surfaceLockCount "+surfaceLockCount+"]"); return sb.toString(); } protected final void setWindowHandle(long handle) { windowHandle = handle; } @Override public void runOnEDTIfAvail(boolean wait, final Runnable task) { if( windowLock.isOwner( Thread.currentThread() ) ) { task.run(); } else { ( (DisplayImpl) screen.getDisplay() ).runOnEDTIfAvail(wait, task); } } private final Runnable requestFocusAction = new Runnable() { @Override public final void run() { if(DEBUG_IMPLEMENTATION) { System.err.println("Window.RequestFocusAction: force 0 - ("+getThreadName()+"): "+hasFocus+" -> true - windowHandle "+toHexString(windowHandle)+" parentWindowHandle "+toHexString(parentWindowHandle)); } WindowImpl.this.requestFocusImpl(false); } }; private final Runnable requestFocusActionForced = new Runnable() { @Override public final void run() { if(DEBUG_IMPLEMENTATION) { System.err.println("Window.RequestFocusAction: force 1 - ("+getThreadName()+"): "+hasFocus+" -> true - windowHandle "+toHexString(windowHandle)+" parentWindowHandle "+toHexString(parentWindowHandle)); } WindowImpl.this.requestFocusImpl(true); } }; @Override public final boolean hasFocus() { return hasFocus; } @Override public void requestFocus() { requestFocus(true); } @Override public void requestFocus(boolean wait) { requestFocus(wait /* wait */, false /* skipFocusAction */, brokenFocusChange /* force */); } private void requestFocus(boolean wait, boolean skipFocusAction, boolean force) { if( isNativeValid() && ( force || !hasFocus() ) && ( skipFocusAction || !focusAction() ) ) { runOnEDTIfAvail(wait, force ? requestFocusActionForced : requestFocusAction); } } /** Internally forcing request focus on current thread */ private void requestFocusInt(boolean skipFocusAction) { if( skipFocusAction || !focusAction() ) { if(DEBUG_IMPLEMENTATION) { System.err.println("Window.RequestFocusInt: forcing - ("+getThreadName()+"): skipFocusAction "+skipFocusAction+", focus "+hasFocus+" -> true - windowHandle "+toHexString(windowHandle)+" parentWindowHandle "+toHexString(parentWindowHandle)); } requestFocusImpl(true); } } @Override public void setFocusAction(FocusRunnable focusAction) { this.focusAction = focusAction; } private boolean focusAction() { if(DEBUG_IMPLEMENTATION) { System.err.println("Window.focusAction() START - "+getThreadName()+", focusAction: "+focusAction+" - windowHandle "+toHexString(getWindowHandle())); } boolean res; if(null!=focusAction) { res = focusAction.run(); } else { res = false; } if(DEBUG_IMPLEMENTATION) { System.err.println("Window.focusAction() END - "+getThreadName()+", focusAction: "+focusAction+" - windowHandle "+toHexString(getWindowHandle())+", res: "+res); } return res; } protected void setBrokenFocusChange(boolean v) { brokenFocusChange = v; } @Override public void setKeyboardFocusHandler(KeyListener l) { keyboardFocusHandler = l; } private class SetPositionAction implements Runnable { int x, y; private SetPositionAction(int x, int y) { this.x = x; this.y = y; } @Override public final void run() { final RecursiveLock _lock = windowLock; _lock.lock(); try { if(DEBUG_IMPLEMENTATION) { System.err.println("Window setPosition: "+getX()+"/"+getY()+" -> "+x+"/"+y+", fs "+fullscreen+", windowHandle "+toHexString(windowHandle)); } if ( !isFullscreen() && ( getX() != x || getY() != y ) ) { if(isNativeValid()) { // this.x/this.y will be set by sizeChanged, triggered by windowing event system reconfigureWindowImpl(x, y, getWidth(), getHeight(), getReconfigureFlags(0, isVisible())); // Wait until custom position is reached within tolerances waitForPosition(true, x, y, Window.TIMEOUT_NATIVEWINDOW); } else { definePosition(x, y); // set pos for createNative(..) } } } finally { _lock.unlock(); } } } @Override public void setPosition(int x, int y) { autoPosition = false; runOnEDTIfAvail(true, new SetPositionAction(x, y)); } @Override public void setTopLevelPosition(int x, int y) { setPosition(x + getInsets().getLeftWidth(), y + getInsets().getTopHeight()); } private class FullScreenAction implements Runnable { boolean fullscreen; private boolean init(boolean fullscreen) { if(isNativeValid()) { this.fullscreen = fullscreen; return isFullscreen() != fullscreen; } else { WindowImpl.this.fullscreen = fullscreen; // set current state for createNative(..) return false; } } public boolean fsOn() { return fullscreen; } @Override public final void run() { final RecursiveLock _lock = windowLock; _lock.lock(); try { // set current state WindowImpl.this.fullscreen = fullscreen; final int oldX = getX(); final int oldY = getY(); final int oldWidth = getWidth(); final int oldHeight = getHeight(); int x,y,w,h; final RectangleImmutable sviewport = screen.getViewport(); final RectangleImmutable viewport; final int fs_span_flag; if(fullscreen) { if( null == fullscreenMonitors ) { if( fullscreenUseMainMonitor ) { fullscreenMonitors = new ArrayList(); fullscreenMonitors.add( getMainMonitor() ); } else { fullscreenMonitors = getScreen().getMonitorDevices(); } } viewport = MonitorDevice.unionOfViewports(new Rectangle(), fullscreenMonitors); if( isReconfigureFlagSupported(FLAG_IS_FULLSCREEN_SPAN) && ( fullscreenMonitors.size() > 1 || sviewport.compareTo(viewport) > 0 ) ) { fs_span_flag = FLAG_IS_FULLSCREEN_SPAN; } else { fs_span_flag = 0; } nfs_x = oldX; nfs_y = oldY; nfs_width = oldWidth; nfs_height = oldHeight; x = viewport.getX(); y = viewport.getY(); w = viewport.getWidth(); h = viewport.getHeight(); } else { fullscreenUseMainMonitor = true; fullscreenMonitors = null; fs_span_flag = 0; viewport = null; x = nfs_x; y = nfs_y; w = nfs_width; h = nfs_height; if(null!=parentWindow) { // reset position to 0/0 within parent space x = 0; y = 0; // refit if size is bigger than parent if( w > parentWindow.getWidth() ) { w = parentWindow.getWidth(); } if( h > parentWindow.getHeight() ) { h = parentWindow.getHeight(); } } } if(DEBUG_IMPLEMENTATION) { System.err.println("Window fs: "+fullscreen+" "+x+"/"+y+" "+w+"x"+h+", "+isUndecorated()+ ", virtl-screenSize: "+sviewport+", monitorsViewport "+viewport+ ", spanning "+(0!=fs_span_flag)+" @ "+Thread.currentThread().getName()); } final DisplayImpl display = (DisplayImpl) screen.getDisplay(); display.dispatchMessagesNative(); // status up2date final boolean wasVisible = isVisible(); // Lock parentWindow only during reparenting (attempt) final NativeWindow parentWindowLocked; if( null != parentWindow ) { // fullscreen off: !visible first (fixes X11 unsuccessful return to parent window) if( !fullscreen && wasVisible && NativeWindowFactory.TYPE_X11 == NativeWindowFactory.getNativeWindowType(true) ) { setVisibleImpl(false, oldX, oldY, oldWidth, oldHeight); WindowImpl.this.waitForVisible(false, false); // FIXME: Some composite WM behave slacky .. give 'em chance to change state -> invisible, // even though we do exactly that (KDE+Composite) try { Thread.sleep(100); } catch (InterruptedException e) { } display.dispatchMessagesNative(); // status up2date } parentWindowLocked = parentWindow; if( NativeSurface.LOCK_SURFACE_NOT_READY >= parentWindowLocked.lockSurface() ) { throw new NativeWindowException("Parent surface lock: not ready: "+parentWindow); } } else { parentWindowLocked = null; } try { reconfigureWindowImpl(x, y, w, h, getReconfigureFlags( ( ( null != parentWindowLocked ) ? FLAG_CHANGE_PARENTING : 0 ) | fs_span_flag | FLAG_CHANGE_FULLSCREEN | FLAG_CHANGE_DECORATION, isVisible()) ); } finally { if(null!=parentWindowLocked) { parentWindowLocked.unlockSurface(); } } display.dispatchMessagesNative(); // status up2date if(wasVisible) { setVisibleImpl(true, x, y, w, h); boolean ok = 0 <= WindowImpl.this.waitForVisible(true, false); if(ok) { ok = WindowImpl.this.waitForSize(w, h, false, TIMEOUT_NATIVEWINDOW); } if(ok && !fullscreen) { ok = WindowImpl.this.waitForPosition(true, x, y, TIMEOUT_NATIVEWINDOW); } if(ok) { requestFocusInt(fullscreen /* skipFocusAction if fullscreen */); display.dispatchMessagesNative(); // status up2date } if(DEBUG_IMPLEMENTATION) { System.err.println("Window fs done: ok " + ok + ", " + WindowImpl.this); } } } finally { _lock.unlock(); } sendWindowEvent(WindowEvent.EVENT_WINDOW_RESIZED); // trigger a resize/relayout and repaint to listener } } private final FullScreenAction fullScreenAction = new FullScreenAction(); @Override public boolean setFullscreen(boolean fullscreen) { return setFullscreenImpl(fullscreen, true, null); } @Override public boolean setFullscreen(List monitors) { return setFullscreenImpl(true, false, monitors); } private boolean setFullscreenImpl(boolean fullscreen, boolean useMainMonitor, List monitors) { synchronized(fullScreenAction) { fullscreenMonitors = monitors; fullscreenUseMainMonitor = useMainMonitor; if( fullScreenAction.init(fullscreen) ) { if(fullScreenAction.fsOn() && isOffscreenInstance(WindowImpl.this, parentWindow)) { // enable fullscreen on offscreen instance if(null != parentWindow) { nfs_parent = parentWindow; reparentWindow(null, -1, -1, true /* forceDestroyCreate */); } else { throw new InternalError("Offscreen instance w/o parent unhandled"); } } runOnEDTIfAvail(true, fullScreenAction); if(!fullScreenAction.fsOn() && null != nfs_parent) { // disable fullscreen on offscreen instance reparentWindow(nfs_parent, -1, -1, true /* forceDestroyCreate */); nfs_parent = null; } } return this.fullscreen; } } private class MonitorModeListenerImpl implements MonitorModeListener { boolean animatorPaused = false; boolean hadFocus = false; boolean fullscreenPaused = false; List _fullscreenMonitors = null; boolean _fullscreenUseMainMonitor = true; @Override public void monitorModeChangeNotify(MonitorEvent me) { hadFocus = hasFocus(); if(DEBUG_IMPLEMENTATION) { System.err.println("Window.monitorModeChangeNotify: hadFocus "+hadFocus+", "+me+" @ "+Thread.currentThread().getName()); } if(null!=lifecycleHook) { animatorPaused = lifecycleHook.pauseRenderingAction(); } if( fullscreen && isReconfigureFlagSupported(FLAG_IS_FULLSCREEN_SPAN) ) { if(DEBUG_IMPLEMENTATION) { System.err.println("Window.monitorModeChangeNotify: FS Pause"); } fullscreenPaused = true; _fullscreenMonitors = fullscreenMonitors; _fullscreenUseMainMonitor = fullscreenUseMainMonitor; setFullscreenImpl(false, true, null); } } @Override public void monitorModeChanged(MonitorEvent me, boolean success) { if(DEBUG_IMPLEMENTATION) { System.err.println("Window.monitorModeChanged: hadFocus "+hadFocus+", "+me+", success: "+success+" @ "+Thread.currentThread().getName()); } if(success) { if(!animatorPaused && null!=lifecycleHook) { // Didn't pass above notify method. probably detected screen change after it happened. animatorPaused = lifecycleHook.pauseRenderingAction(); } if( !fullscreen && !fullscreenPaused ) { // Simply move/resize window to fit in virtual screen if required final RectangleImmutable viewport = screen.getViewport(); if( viewport.getWidth() > 0 && viewport.getHeight() > 0 ) { // failsafe final RectangleImmutable rect = new Rectangle(getX(), getY(), getWidth(), getHeight()); final RectangleImmutable isect = viewport.intersection(rect); if ( getHeight() > isect.getHeight() || getWidth() > isect.getWidth() ) { if(DEBUG_IMPLEMENTATION) { System.err.println("Window.monitorModeChanged: fit window "+rect+" into screen viewport "+viewport+ ", due to minimal intersection "+isect); } definePosition(viewport.getX(), viewport.getY()); // set pos for setVisible(..) or createNative(..) - reduce EDT roundtrip setSize(viewport.getWidth(), viewport.getHeight()); } } } else if( fullscreenPaused ){ if(DEBUG_IMPLEMENTATION) { System.err.println("Window.monitorModeChanged: FS Restore"); } setFullscreenImpl(true, _fullscreenUseMainMonitor, _fullscreenMonitors); fullscreenPaused = false; _fullscreenMonitors = null; _fullscreenUseMainMonitor = true; } else { // If changed monitor is part of this fullscreen mode, reset size! (Bug 771) final MonitorDevice md = me.getMonitor(); if( fullscreenMonitors.contains(md) ) { final RectangleImmutable viewport = MonitorDevice.unionOfViewports(new Rectangle(), fullscreenMonitors); if(DEBUG_IMPLEMENTATION) { final RectangleImmutable rect = new Rectangle(getX(), getY(), getWidth(), getHeight()); System.err.println("Window.monitorModeChanged: FS Monitor Match: Fit window "+rect+" into new viewport union "+viewport+", provoked by "+md); } definePosition(viewport.getX(), viewport.getY()); // set pos for setVisible(..) or createNative(..) - reduce EDT roundtrip setFullscreenSize(viewport.getWidth(), viewport.getHeight()); } } } if(animatorPaused) { lifecycleHook.resumeRenderingAction(); } sendWindowEvent(WindowEvent.EVENT_WINDOW_RESIZED); // trigger a resize/relayout and repaint to listener if( hadFocus ) { requestFocus(true); } } } private final MonitorModeListenerImpl monitorModeListenerImpl = new MonitorModeListenerImpl(); //---------------------------------------------------------------------- // Child Window Management // @Override public final boolean removeChild(NativeWindow win) { synchronized(childWindowsLock) { return childWindows.remove(win); } } @Override public final boolean addChild(NativeWindow win) { if (win == null) { return false; } synchronized(childWindowsLock) { return childWindows.add(win); } } //---------------------------------------------------------------------- // Generic Event Support // private void doEvent(boolean enqueue, boolean wait, com.jogamp.newt.event.NEWTEvent event) { boolean done = false; if(!enqueue) { done = consumeEvent(event); wait = done; // don't wait if event can't be consumed now } if(!done) { enqueueEvent(wait, event); } } @Override public void enqueueEvent(boolean wait, com.jogamp.newt.event.NEWTEvent event) { if(isNativeValid()) { ((DisplayImpl)screen.getDisplay()).enqueueEvent(wait, event); } } @Override public final boolean consumeEvent(NEWTEvent e) { switch(e.getEventType()) { // special repaint treatment case WindowEvent.EVENT_WINDOW_REPAINT: // queue repaint event in case window is locked, ie in operation if( null != windowLock.getOwner() ) { // make sure only one repaint event is queued if(!repaintQueued) { repaintQueued=true; final boolean discardTO = QUEUED_EVENT_TO <= System.currentTimeMillis()-e.getWhen(); if(DEBUG_IMPLEMENTATION) { System.err.println("Window.consumeEvent: REPAINT "+Thread.currentThread().getName()+" - queued "+e+", discard-to "+discardTO); // Thread.dumpStack(); } return discardTO; // discardTO:=true -> consumed } return true; } repaintQueued=false; // no repaint event queued break; // common treatment case WindowEvent.EVENT_WINDOW_RESIZED: // queue event in case window is locked, ie in operation if( null != windowLock.getOwner() ) { final boolean discardTO = QUEUED_EVENT_TO <= System.currentTimeMillis()-e.getWhen(); if(DEBUG_IMPLEMENTATION) { System.err.println("Window.consumeEvent: RESIZED "+Thread.currentThread().getName()+" - queued "+e+", discard-to "+discardTO); // Thread.dumpStack(); } return discardTO; // discardTO:=true -> consumed } break; default: break; } if(e instanceof WindowEvent) { consumeWindowEvent((WindowEvent)e); } else if(e instanceof KeyEvent) { consumeKeyEvent((KeyEvent)e); } else if(e instanceof MouseEvent) { consumePointerEvent((MouseEvent)e); } else { throw new NativeWindowException("Unexpected NEWTEvent type " + e); } return true; } // // SurfaceUpdatedListener Support // @Override public void addSurfaceUpdatedListener(SurfaceUpdatedListener l) { surfaceUpdatedHelper.addSurfaceUpdatedListener(l); } @Override public void addSurfaceUpdatedListener(int index, SurfaceUpdatedListener l) throws IndexOutOfBoundsException { surfaceUpdatedHelper.addSurfaceUpdatedListener(index, l); } @Override public void removeSurfaceUpdatedListener(SurfaceUpdatedListener l) { surfaceUpdatedHelper.removeSurfaceUpdatedListener(l); } @Override public void surfaceUpdated(Object updater, NativeSurface ns, long when) { surfaceUpdatedHelper.surfaceUpdated(updater, ns, when); } // // MouseListener/Event Support // // // Native MouseEvents pre-processed to be enqueued or consumed directly // public final void sendMouseEvent(short eventType, int modifiers, int x, int y, short button, float rotation) { doMouseEvent(false, false, eventType, modifiers, x, y, button, MouseEvent.getRotationXYZ(rotation, modifiers), 1f); } public final void enqueueMouseEvent(boolean wait, short eventType, int modifiers, int x, int y, short button, float rotation) { doMouseEvent(true, wait, eventType, modifiers, x, y, button, MouseEvent.getRotationXYZ(rotation, modifiers), 1f); } protected final void doMouseEvent(boolean enqueue, boolean wait, short eventType, int modifiers, int x, int y, short button, float rotation) { doMouseEvent(enqueue, wait, eventType, modifiers, x, y, button, MouseEvent.getRotationXYZ(rotation, modifiers), 1f); } /** public final void sendMouseEvent(short eventType, int modifiers, int x, int y, short button, float[] rotationXYZ, float rotationScale) { doMouseEvent(false, false, eventType, modifiers, x, y, button, rotationXYZ, rotationScale); } public final void enqueueMouseEvent(boolean wait, short eventType, int modifiers, int x, int y, short button, float[] rotationXYZ, float rotationScale) { doMouseEvent(true, wait, eventType, modifiers, x, y, button, rotationXYZ, rotationScale); } */ /** * Send mouse event (one-pointer) either to be directly consumed or to be enqueued * * @param enqueue if true, event will be {@link #enqueueEvent(boolean, NEWTEvent) enqueued}, * otherwise {@link #consumeEvent(NEWTEvent) consumed} directly. * @param wait if true wait until {@link #consumeEvent(NEWTEvent) consumed}. */ protected void doMouseEvent(boolean enqueue, boolean wait, short eventType, int modifiers, int x, int y, short button, final float[] rotationXYZ, float rotationScale) { if( 0 > button || button > MouseEvent.BUTTON_COUNT ) { throw new NativeWindowException("Invalid mouse button number" + button); } doPointerEvent(enqueue, wait, constMousePointerTypes, eventType, modifiers, 0 /*actionIdx*/, new short[] { (short)(button-1) }, new int[]{x}, new int[]{y}, new float[]{0f} /*pressure*/, 1f /*maxPressure*/, rotationXYZ, rotationScale); } /** * Send multiple-pointer event either to be directly consumed or to be enqueued *

* The index for the element of multiple-pointer arrays represents the pointer which triggered the event * is passed via actionIdx. *

*

* The given pointer names, pNames, are mapped to consecutive pointer IDs starting w/ 0 * using a hash-map if normalPNames is false. * Otherwise a simple int to short type cast is performed. *

* * @param enqueue if true, event will be {@link #enqueueEvent(boolean, NEWTEvent) enqueued}, * otherwise {@link #consumeEvent(NEWTEvent) consumed} directly. * @param wait if true wait until {@link #consumeEvent(NEWTEvent) consumed}. * @param pTypes {@link MouseEvent.PointerType} for each pointer (multiple pointer) * @param eventType * @param modifiers * @param actionIdx index of multiple-pointer arrays representing the pointer which triggered the event * @param normalPNames see pName below. * @param pNames Pointer name for each pointer (multiple pointer). * We assume consecutive pointer names starting w/ 0 if normalPIDs is true. * Otherwise we hash-map the values during state pressed to retrieve the normal ID. * @param pX X-axis for each pointer (multiple pointer) * @param pY Y-axis for each pointer (multiple pointer) * @param pPressure Pressure for each pointer (multiple pointer) * @param maxPressure Maximum pointer pressure for all pointer */ public final void doPointerEvent(boolean enqueue, boolean wait, final PointerType[] pTypes, short eventType, int modifiers, int actionIdx, boolean normalPNames, final int[] pNames, final int[] pX, final int[] pY, float[] pPressure, float maxPressure, final float[] rotationXYZ, final float rotationScale) { final int pCount = pNames.length; final short[] pIDs = new short[pCount]; for(int i=0; i short idx final int sz0 = pName2pID.size(); final Integer pNameI1 = pName2pID.getOrAdd(Integer.valueOf(pNames[i])); final short pID = (short)pName2pID.indexOf(pNameI1); pIDs[i] = pID; if(DEBUG_MOUSE_EVENT) { final int sz1 = pName2pID.size(); if( sz0 != sz1 ) { System.err.println("PointerName2ID[sz "+sz1+"]: Map "+pNameI1+" == "+pID); } } if( MouseEvent.EVENT_MOUSE_RELEASED == eventType ) { pName2pID.remove(pNameI1); if(DEBUG_MOUSE_EVENT) { System.err.println("PointerName2ID[sz "+pName2pID.size()+"]: Unmap "+pNameI1+" == "+pID); } } } else { // simple type cast pIDs[i] = (short)pNames[i]; } } doPointerEvent(enqueue, wait, pTypes, eventType, modifiers, actionIdx, pIDs, pX, pY, pPressure, maxPressure, rotationXYZ, rotationScale); } /** * Send multiple-pointer event either to be directly consumed or to be enqueued *

* The index for the element of multiple-pointer arrays represents the pointer which triggered the event * is passed via actionIdx. *

* * @param enqueue if true, event will be {@link #enqueueEvent(boolean, NEWTEvent) enqueued}, * otherwise {@link #consumeEvent(NEWTEvent) consumed} directly. * @param wait if true wait until {@link #consumeEvent(NEWTEvent) consumed}. * @param pTypes {@link MouseEvent.PointerType} for each pointer (multiple pointer) * @param eventType * @param modifiers * @param pActionIdx index of multiple-pointer arrays representing the pointer which triggered the event * @param pID Pointer ID for each pointer (multiple pointer). We assume consecutive pointerIDs starting w/ 0. * A pointer-ID of -1 may also denote no pointer/button activity, i.e. {@link PointerType#Mouse} move. * @param pX X-axis for each pointer (multiple pointer) * @param pY Y-axis for each pointer (multiple pointer) * @param pPressure Pressure for each pointer (multiple pointer) * @param maxPressure Maximum pointer pressure for all pointer */ public final void doPointerEvent(boolean enqueue, boolean wait, final PointerType[] pTypes, short eventType, int modifiers, int pActionIdx, final short[] pID, final int[] pX, final int[] pY, final float[] pPressure, float maxPressure, final float[] rotationXYZ, float rotationScale) { final long when = System.currentTimeMillis(); final int pCount = pTypes.length; if( 0 > pActionIdx || pActionIdx >= pCount) { throw new IllegalArgumentException("actionIdx out of bounds [0.."+(pCount-1)+"]"); } if( 0 < pActionIdx ) { // swap values to make idx 0 the triggering pointer { final PointerType aType = pTypes[pActionIdx]; pTypes[pActionIdx] = pTypes[0]; pTypes[0] = aType; } { final short s = pID[pActionIdx]; pID[pActionIdx] = pID[0]; pID[0] = s; } { int s = pX[pActionIdx]; pX[pActionIdx] = pX[0]; pX[0] = s; s = pY[pActionIdx]; pY[pActionIdx] = pY[0]; pY[0] = s; } { final float aPress = pPressure[pActionIdx]; pPressure[pActionIdx] = pPressure[0]; pPressure[0] = aPress; } } final short id = pID[0]; final short button; { final int b = id + 1; if( 0 <= b && b <= com.jogamp.newt.event.MouseEvent.BUTTON_COUNT ) { // we allow id==-1 -> button==0 for no button, i.e. mouse move button = (short)b; } else { button = com.jogamp.newt.event.MouseEvent.BUTTON1; } } // // - Determine ENTERED/EXITED state // - Remove redundant move/drag events // - Reset states if applicable // int x = pX[0]; int y = pY[0]; final Point movePositionP0 = pState1.getMovePosition(id); switch( eventType ) { case MouseEvent.EVENT_MOUSE_EXITED: if( null != movePositionP0 ) { if( x==-1 && y==-1 ) { x = movePositionP0.getX(); y = movePositionP0.getY(); } movePositionP0.set(0, 0); } // Fall through intended! case MouseEvent.EVENT_MOUSE_ENTERED: // clip coordinates to window dimension x = Math.min(Math.max(x, 0), getWidth()-1); y = Math.min(Math.max(y, 0), getHeight()-1); pState1.insideWindow = eventType == MouseEvent.EVENT_MOUSE_ENTERED; pState1.clearButton(); break; case MouseEvent.EVENT_MOUSE_MOVED: case MouseEvent.EVENT_MOUSE_DRAGGED: if( null != movePositionP0 ) { if( pState1.insideWindow && movePositionP0.getX() == x && movePositionP0.getY() == y ) { if(DEBUG_MOUSE_EVENT) { System.err.println("doPointerEvent: skip "+MouseEvent.getEventTypeString(eventType)+" w/ same position: "+movePositionP0); } return; // skip same position } movePositionP0.set(x, y); } // Fall through intended ! default: if(!pState1.insideWindow) { pState1.insideWindow = true; pState1.clearButton(); } } if( x < 0 || y < 0 || x >= getWidth() || y >= getHeight() ) { if(DEBUG_MOUSE_EVENT) { System.err.println("doPointerEvent: drop: "+MouseEvent.getEventTypeString(eventType)+ ", mod "+modifiers+", pos "+x+"/"+y+", button "+button+", lastMousePosition: "+movePositionP0); } return; // .. invalid .. } if(DEBUG_MOUSE_EVENT) { System.err.println("doPointerEvent: enqueue "+enqueue+", wait "+wait+", "+MouseEvent.getEventTypeString(eventType)+ ", mod "+modifiers+", pos "+x+"/"+y+", button "+button+", lastMousePosition: "+movePositionP0); } final int buttonMask = InputEvent.getButtonMask(button); modifiers |= buttonMask; // Always add current button to modifier mask (Bug 571) modifiers |= pState1.buttonPressedMask; // Always add currently pressed mouse buttons to modifier mask if( isPointerConfined() ) { modifiers |= InputEvent.CONFINED_MASK; } if( !isPointerVisible() ) { modifiers |= InputEvent.INVISIBLE_MASK; } pX[0] = x; pY[0] = y; // // - Determine CLICK COUNT // - Ignore sent CLICKED // - Track buttonPressed incl. buttonPressedMask // - Fix MOVED/DRAGGED event // final MouseEvent e; switch( eventType ) { case MouseEvent.EVENT_MOUSE_CLICKED: e = null; break; case MouseEvent.EVENT_MOUSE_PRESSED: if( 0 >= pPressure[0] ) { pPressure[0] = maxPressure; } pState1.buttonPressedMask |= buttonMask; if( 1 == pCount ) { if( when - pState1.lastButtonPressTime < MouseEvent.getClickTimeout() ) { pState1.lastButtonClickCount++; } else { pState1.lastButtonClickCount=(short)1; } pState1.lastButtonPressTime = when; pState1.buttonPressed = button; e = new MouseEvent(eventType, this, when, modifiers, pTypes, pID, pX, pY, pPressure, maxPressure, button, pState1.lastButtonClickCount, rotationXYZ, rotationScale); } else { e = new MouseEvent(eventType, this, when, modifiers, pTypes, pID, pX, pY, pPressure, maxPressure, button, (short)1, rotationXYZ, rotationScale); } break; case MouseEvent.EVENT_MOUSE_RELEASED: if( 1 == pCount ) { e = new MouseEvent(eventType, this, when, modifiers, pTypes, pID, pX, pY, pPressure, maxPressure, button, pState1.lastButtonClickCount, rotationXYZ, rotationScale); if( when - pState1.lastButtonPressTime >= MouseEvent.getClickTimeout() ) { pState1.lastButtonClickCount = (short)0; pState1.lastButtonPressTime = 0; } pState1.buttonPressed = 0; } else { e = new MouseEvent(eventType, this, when, modifiers, pTypes, pID, pX, pY, pPressure, maxPressure, button, (short)1, rotationXYZ, rotationScale); } pState1.buttonPressedMask &= ~buttonMask; if( null != movePositionP0 ) { movePositionP0.set(0, 0); } break; case MouseEvent.EVENT_MOUSE_MOVED: if ( 0 != pState1.buttonPressedMask ) { // any button or pointer move -> drag e = new MouseEvent(MouseEvent.EVENT_MOUSE_DRAGGED, this, when, modifiers, pTypes, pID, pX, pY, pPressure, maxPressure, pState1.buttonPressed, (short)1, rotationXYZ, rotationScale); } else { e = new MouseEvent(eventType, this, when, modifiers, pTypes, pID, pX, pY, pPressure, maxPressure, button, (short)0, rotationXYZ, rotationScale); } break; case MouseEvent.EVENT_MOUSE_DRAGGED: if( 0 >= pPressure[0] ) { pPressure[0] = maxPressure; } // Fall through intended! default: e = new MouseEvent(eventType, this, when, modifiers, pTypes, pID, pX, pY, pPressure, maxPressure, button, (short)0, rotationXYZ, rotationScale); } doEvent(enqueue, wait, e); // actual mouse event } @Override public final void addMouseListener(MouseListener l) { addMouseListener(-1, l); } @Override public final void addMouseListener(int index, MouseListener l) { if(l == null) { return; } @SuppressWarnings("unchecked") ArrayList clonedListeners = (ArrayList) mouseListeners.clone(); if(0>index) { index = clonedListeners.size(); } clonedListeners.add(index, l); mouseListeners = clonedListeners; } @Override public final void removeMouseListener(MouseListener l) { if (l == null) { return; } @SuppressWarnings("unchecked") ArrayList clonedListeners = (ArrayList) mouseListeners.clone(); clonedListeners.remove(l); mouseListeners = clonedListeners; } @Override public final MouseListener getMouseListener(int index) { @SuppressWarnings("unchecked") ArrayList clonedListeners = (ArrayList) mouseListeners.clone(); if(0>index) { index = clonedListeners.size()-1; } return clonedListeners.get(index); } @Override public final MouseListener[] getMouseListeners() { return mouseListeners.toArray(new MouseListener[mouseListeners.size()]); } @Override public final void setDefaultGesturesEnabled(boolean enable) { defaultGestureHandlerEnabled = enable; } @Override public final boolean areDefaultGesturesEnabled() { return defaultGestureHandlerEnabled; } @Override public final void addGestureHandler(GestureHandler gh) { addGestureHandler(-1, gh); } @Override public final void addGestureHandler(int index, GestureHandler gh) { if(gh == null) { return; } @SuppressWarnings("unchecked") ArrayList cloned = (ArrayList) pointerGestureHandler.clone(); if(0>index) { index = cloned.size(); } cloned.add(index, gh); pointerGestureHandler = cloned; } @Override public final void removeGestureHandler(GestureHandler gh) { if (gh == null) { return; } @SuppressWarnings("unchecked") ArrayList cloned = (ArrayList) pointerGestureHandler.clone(); cloned.remove(gh); pointerGestureHandler = cloned; } @Override public final void addGestureListener(GestureHandler.GestureListener gl) { addGestureListener(-1, gl); } @Override public final void addGestureListener(int index, GestureHandler.GestureListener gl) { if(gl == null) { return; } @SuppressWarnings("unchecked") ArrayList cloned = (ArrayList) gestureListeners.clone(); if(0>index) { index = cloned.size(); } cloned.add(index, gl); gestureListeners = cloned; } @Override public final void removeGestureListener(GestureHandler.GestureListener gl) { if (gl == null) { return; } @SuppressWarnings("unchecked") ArrayList cloned = (ArrayList) gestureListeners.clone(); cloned.remove(gl); gestureListeners= cloned; } private static int step(int lower, int edge, int value) { return value < edge ? lower : value; } /** * Consume the {@link MouseEvent}, i.e. *
     *   - validate
     *   - handle gestures
     *   - synthesize events if applicable (like gestures)
     *   - dispatch event to listener
     * 
*/ protected void consumePointerEvent(MouseEvent pe) { int x = pe.getX(); int y = pe.getY(); if(DEBUG_MOUSE_EVENT) { System.err.println("consumePointerEvent.in: "+pe); } // // - Determine ENTERED/EXITED state // - Synthesize ENTERED event // - Reset states if applicable // final long when = pe.getWhen(); int eventType = pe.getEventType(); final MouseEvent eEntered; switch( eventType ) { case MouseEvent.EVENT_MOUSE_EXITED: case MouseEvent.EVENT_MOUSE_ENTERED: // clip coordinates to window dimension x = Math.min(Math.max(x, 0), getWidth()-1); y = Math.min(Math.max(y, 0), getHeight()-1); pState0.insideWindow = eventType == MouseEvent.EVENT_MOUSE_ENTERED; pState0.clearButton(); eEntered = null; break; default: if(!pState0.insideWindow) { pState0.insideWindow = true; pState0.clearButton(); eEntered = pe.createVariant(MouseEvent.EVENT_MOUSE_ENTERED); } else { eEntered = null; } } if( null != eEntered ) { if(DEBUG_MOUSE_EVENT) { System.err.println("consumePointerEvent.send.0: "+eEntered); } dispatchMouseEvent(eEntered); } else if( x < 0 || y < 0 || x >= getWidth() || y >= getHeight() ) { if(DEBUG_MOUSE_EVENT) { System.err.println("consumePointerEvent.drop: "+pe); } return; // .. invalid .. } // // Handle Default Gestures // if( defaultGestureHandlerEnabled && pe.getPointerType(0).getPointerClass() == MouseEvent.PointerClass.Onscreen ) { if( null == gesture2PtrTouchScroll ) { final int scaledScrollSlop; final int scaledDoubleTapSlop; final MonitorDevice monitor = getMainMonitor(); if ( null != monitor ) { final DimensionImmutable mm = monitor.getSizeMM(); final float pixWPerMM = (float)monitor.getCurrentMode().getRotatedWidth() / (float)mm.getWidth(); final float pixHPerMM = (float)monitor.getCurrentMode().getRotatedHeight() / (float)mm.getHeight(); final float pixPerMM = Math.min(pixHPerMM, pixWPerMM); scaledScrollSlop = Math.round(DoubleTapScrollGesture.SCROLL_SLOP_MM * pixPerMM); scaledDoubleTapSlop = Math.round(DoubleTapScrollGesture.DOUBLE_TAP_SLOP_MM * pixPerMM); if(DEBUG_MOUSE_EVENT) { System.err.println("consumePointerEvent.gscroll: scrollSlop "+scaledScrollSlop+", doubleTapSlop "+scaledDoubleTapSlop+", pixPerMM "+pixPerMM+", "+monitor); } } else { scaledScrollSlop = DoubleTapScrollGesture.SCROLL_SLOP_PIXEL; scaledDoubleTapSlop = DoubleTapScrollGesture.DOUBLE_TAP_SLOP_PIXEL; } gesture2PtrTouchScroll = new DoubleTapScrollGesture(step(DoubleTapScrollGesture.SCROLL_SLOP_PIXEL, DoubleTapScrollGesture.SCROLL_SLOP_PIXEL/2, scaledScrollSlop), step(DoubleTapScrollGesture.DOUBLE_TAP_SLOP_PIXEL, DoubleTapScrollGesture.DOUBLE_TAP_SLOP_PIXEL/2, scaledDoubleTapSlop)); } if( gesture2PtrTouchScroll.process(pe) ) { pe = (MouseEvent) gesture2PtrTouchScroll.getGestureEvent(); gesture2PtrTouchScroll.clear(false); if(DEBUG_MOUSE_EVENT) { System.err.println("consumePointerEvent.gscroll: "+pe); } dispatchMouseEvent(pe); return; } if( gesture2PtrTouchScroll.isWithinGesture() ) { return; // within gesture .. need more input .. } } // // Handle Custom Gestures // { final int pointerGestureHandlerCount = pointerGestureHandler.size(); if( pointerGestureHandlerCount > 0 ) { boolean withinGesture = false; for(int i = 0; !pe.isConsumed() && i < pointerGestureHandlerCount; i++ ) { final GestureHandler gh = pointerGestureHandler.get(i); if( gh.process(pe) ) { final InputEvent ieG = gh.getGestureEvent(); gh.clear(false); if( ieG instanceof MouseEvent ) { dispatchMouseEvent((MouseEvent)ieG); } else if( ieG instanceof GestureHandler.GestureEvent) { final GestureHandler.GestureEvent ge = (GestureHandler.GestureEvent) ieG; for(int j = 0; !ge.isConsumed() && j < gestureListeners.size(); j++ ) { gestureListeners.get(j).gestureDetected(ge); } } return; } withinGesture |= gh.isWithinGesture(); } if( withinGesture ) { return; } } } // // - Synthesize mouse CLICKED // - Ignore sent CLICKED // final MouseEvent eClicked; switch( eventType ) { case MouseEvent.EVENT_MOUSE_PRESSED: if( 1 == pe.getPointerCount() ) { pState0.lastButtonPressTime = when; } eClicked = null; break; case MouseEvent.EVENT_MOUSE_RELEASED: if( 1 == pe.getPointerCount() && when - pState0.lastButtonPressTime < MouseEvent.getClickTimeout() ) { eClicked = pe.createVariant(MouseEvent.EVENT_MOUSE_CLICKED); } else { eClicked = null; pState0.lastButtonPressTime = 0; } break; case MouseEvent.EVENT_MOUSE_CLICKED: // ignore - synthesized here .. if(DEBUG_MOUSE_EVENT) { System.err.println("consumePointerEvent: drop recv'ed (synth here) "+pe); } pe = null; eClicked = null; break; default: eClicked = null; } if( null != pe ) { if(DEBUG_MOUSE_EVENT) { System.err.println("consumePointerEvent.send.1: "+pe); } dispatchMouseEvent(pe); // actual mouse event } if( null != eClicked ) { if(DEBUG_MOUSE_EVENT) { System.err.println("consumePointerEvent.send.2: "+eClicked); } dispatchMouseEvent(eClicked); } } private final void dispatchMouseEvent(MouseEvent e) { for(int i = 0; !e.isConsumed() && i < mouseListeners.size(); i++ ) { MouseListener l = mouseListeners.get(i); switch(e.getEventType()) { case MouseEvent.EVENT_MOUSE_CLICKED: l.mouseClicked(e); break; case MouseEvent.EVENT_MOUSE_ENTERED: l.mouseEntered(e); break; case MouseEvent.EVENT_MOUSE_EXITED: l.mouseExited(e); break; case MouseEvent.EVENT_MOUSE_PRESSED: l.mousePressed(e); break; case MouseEvent.EVENT_MOUSE_RELEASED: l.mouseReleased(e); break; case MouseEvent.EVENT_MOUSE_MOVED: l.mouseMoved(e); break; case MouseEvent.EVENT_MOUSE_DRAGGED: l.mouseDragged(e); break; case MouseEvent.EVENT_MOUSE_WHEEL_MOVED: l.mouseWheelMoved(e); break; default: throw new NativeWindowException("Unexpected mouse event type " + e.getEventType()); } } } // // KeyListener/Event Support // private static final int keyTrackingRange = 255; private final IntBitfield keyPressedState = new IntBitfield( keyTrackingRange + 1 ); protected final boolean isKeyCodeTracked(final short keyCode) { return ( 0xFFFF & (int)keyCode ) <= keyTrackingRange; } /** * @param keyCode the keyCode to set pressed state * @param pressed true if pressed, otherwise false * @return the previus pressed value */ protected final boolean setKeyPressed(short keyCode, boolean pressed) { final int v = 0xFFFF & (int)keyCode; if( v <= keyTrackingRange ) { return keyPressedState.put(v, pressed); } return false; } /** * @param keyCode the keyCode to test pressed state * @return true if pressed, otherwise false */ protected final boolean isKeyPressed(short keyCode) { final int v = 0xFFFF & (int)keyCode; if( v <= keyTrackingRange ) { return keyPressedState.get(v); } return false; } public void sendKeyEvent(short eventType, int modifiers, short keyCode, short keySym, char keyChar) { // Always add currently pressed mouse buttons to modifier mask consumeKeyEvent( KeyEvent.create(eventType, this, System.currentTimeMillis(), modifiers | pState1.buttonPressedMask, keyCode, keySym, keyChar) ); } public void enqueueKeyEvent(boolean wait, short eventType, int modifiers, short keyCode, short keySym, char keyChar) { // Always add currently pressed mouse buttons to modifier mask enqueueEvent(wait, KeyEvent.create(eventType, this, System.currentTimeMillis(), modifiers | pState1.buttonPressedMask, keyCode, keySym, keyChar) ); } @Override public final void setKeyboardVisible(boolean visible) { if(isNativeValid()) { // We don't skip the impl. if it seems that there is no state change, // since we cannot assume the impl. reliably gives us it's current state. final boolean ok = setKeyboardVisibleImpl(visible); if(DEBUG_IMPLEMENTATION || DEBUG_KEY_EVENT) { System.err.println("setKeyboardVisible(native): visible "+keyboardVisible+" -- op[visible:"+visible +", ok "+ok+"] -> "+(visible && ok)); } keyboardVisibilityChanged( visible && ok ); } else { keyboardVisibilityChanged( visible ); // earmark for creation } } @Override public final boolean isKeyboardVisible() { return keyboardVisible; } /** * Returns true if operation was successful, otherwise false. *

* We assume that a failed invisible operation is due to an already invisible keyboard, * hence even if an invisible operation failed, the keyboard is considered invisible! *

*/ protected boolean setKeyboardVisibleImpl(boolean visible) { return false; // nop } /** Triggered by implementation's WM events to update the virtual on-screen keyboard's visibility state. */ protected void keyboardVisibilityChanged(boolean visible) { if(keyboardVisible != visible) { if(DEBUG_IMPLEMENTATION || DEBUG_KEY_EVENT) { System.err.println("keyboardVisibilityChanged: "+keyboardVisible+" -> "+visible); } keyboardVisible = visible; } } protected boolean keyboardVisible = false; @Override public void addKeyListener(KeyListener l) { addKeyListener(-1, l); } @Override public void addKeyListener(int index, KeyListener l) { if(l == null) { return; } @SuppressWarnings("unchecked") ArrayList clonedListeners = (ArrayList) keyListeners.clone(); if(0>index) { index = clonedListeners.size(); } clonedListeners.add(index, l); keyListeners = clonedListeners; } @Override public void removeKeyListener(KeyListener l) { if (l == null) { return; } @SuppressWarnings("unchecked") ArrayList clonedListeners = (ArrayList) keyListeners.clone(); clonedListeners.remove(l); keyListeners = clonedListeners; } @Override public KeyListener getKeyListener(int index) { @SuppressWarnings("unchecked") ArrayList clonedListeners = (ArrayList) keyListeners.clone(); if(0>index) { index = clonedListeners.size()-1; } return clonedListeners.get(index); } @Override public KeyListener[] getKeyListeners() { return keyListeners.toArray(new KeyListener[keyListeners.size()]); } private final boolean propagateKeyEvent(KeyEvent e, KeyListener l) { switch(e.getEventType()) { case KeyEvent.EVENT_KEY_PRESSED: l.keyPressed(e); break; case KeyEvent.EVENT_KEY_RELEASED: l.keyReleased(e); break; default: throw new NativeWindowException("Unexpected key event type " + e.getEventType()); } return e.isConsumed(); } protected void consumeKeyEvent(KeyEvent e) { boolean consumedE = false; if( null != keyboardFocusHandler && !e.isAutoRepeat() ) { consumedE = propagateKeyEvent(e, keyboardFocusHandler); if(DEBUG_KEY_EVENT) { if( consumedE ) { System.err.println("consumeKeyEvent(kfh): "+e+", consumed: "+consumedE); } } } if( !consumedE ) { for(int i = 0; !consumedE && i < keyListeners.size(); i++ ) { consumedE = propagateKeyEvent(e, keyListeners.get(i)); } if(DEBUG_KEY_EVENT) { System.err.println("consumeKeyEvent(usr): "+e+", consumed: "+consumedE); } } } // // WindowListener/Event Support // @Override public void sendWindowEvent(int eventType) { consumeWindowEvent( new WindowEvent((short)eventType, this, System.currentTimeMillis()) ); // FIXME } public void enqueueWindowEvent(boolean wait, int eventType) { enqueueEvent( wait, new WindowEvent((short)eventType, this, System.currentTimeMillis()) ); // FIXME } @Override public void addWindowListener(WindowListener l) { addWindowListener(-1, l); } @Override public void addWindowListener(int index, WindowListener l) throws IndexOutOfBoundsException { if(l == null) { return; } @SuppressWarnings("unchecked") ArrayList clonedListeners = (ArrayList) windowListeners.clone(); if(0>index) { index = clonedListeners.size(); } clonedListeners.add(index, l); windowListeners = clonedListeners; } @Override public final void removeWindowListener(WindowListener l) { if (l == null) { return; } @SuppressWarnings("unchecked") ArrayList clonedListeners = (ArrayList) windowListeners.clone(); clonedListeners.remove(l); windowListeners = clonedListeners; } @Override public WindowListener getWindowListener(int index) { @SuppressWarnings("unchecked") ArrayList clonedListeners = (ArrayList) windowListeners.clone(); if(0>index) { index = clonedListeners.size()-1; } return clonedListeners.get(index); } @Override public WindowListener[] getWindowListeners() { return windowListeners.toArray(new WindowListener[windowListeners.size()]); } protected void consumeWindowEvent(WindowEvent e) { if(DEBUG_IMPLEMENTATION) { System.err.println("consumeWindowEvent: "+e+", visible "+isVisible()+" "+getX()+"/"+getY()+" "+getWidth()+"x"+getHeight()); } for(int i = 0; !e.isConsumed() && i < windowListeners.size(); i++ ) { WindowListener l = windowListeners.get(i); switch(e.getEventType()) { case WindowEvent.EVENT_WINDOW_RESIZED: l.windowResized(e); break; case WindowEvent.EVENT_WINDOW_MOVED: l.windowMoved(e); break; case WindowEvent.EVENT_WINDOW_DESTROY_NOTIFY: l.windowDestroyNotify(e); break; case WindowEvent.EVENT_WINDOW_DESTROYED: l.windowDestroyed(e); break; case WindowEvent.EVENT_WINDOW_GAINED_FOCUS: l.windowGainedFocus(e); break; case WindowEvent.EVENT_WINDOW_LOST_FOCUS: l.windowLostFocus(e); break; case WindowEvent.EVENT_WINDOW_REPAINT: l.windowRepaint((WindowUpdateEvent)e); break; default: throw new NativeWindowException("Unexpected window event type " + e.getEventType()); } } } /** Triggered by implementation's WM events to update the focus state. */ protected void focusChanged(boolean defer, boolean focusGained) { if(brokenFocusChange || hasFocus != focusGained) { if(DEBUG_IMPLEMENTATION) { System.err.println("Window.focusChanged: ("+getThreadName()+"): (defer: "+defer+") "+this.hasFocus+" -> "+focusGained+" - windowHandle "+toHexString(windowHandle)+" parentWindowHandle "+toHexString(parentWindowHandle)); } hasFocus = focusGained; final int evt = focusGained ? WindowEvent.EVENT_WINDOW_GAINED_FOCUS : WindowEvent.EVENT_WINDOW_LOST_FOCUS ; if(!defer) { sendWindowEvent(evt); } else { enqueueWindowEvent(false, evt); } } } /** Triggered by implementation's WM events to update the visibility state. */ protected void visibleChanged(boolean defer, boolean visible) { if(this.visible != visible) { if(DEBUG_IMPLEMENTATION) { System.err.println("Window.visibleChanged ("+getThreadName()+"): (defer: "+defer+") "+this.visible+" -> "+visible+" - windowHandle "+toHexString(windowHandle)+" parentWindowHandle "+toHexString(parentWindowHandle)); } this.visible = visible ; } } /** Returns -1 if failed, otherwise remaining time until {@link #TIMEOUT_NATIVEWINDOW}, maybe zero. */ private long waitForVisible(boolean visible, boolean failFast) { return waitForVisible(visible, failFast, TIMEOUT_NATIVEWINDOW); } /** Returns -1 if failed, otherwise remaining time until timeOut, maybe zero. */ private long waitForVisible(boolean visible, boolean failFast, long timeOut) { final DisplayImpl display = (DisplayImpl) screen.getDisplay(); display.dispatchMessagesNative(); // status up2date long remaining; for(remaining = timeOut; 0 "+newWidth+"x"+newHeight+" - windowHandle "+toHexString(windowHandle)+" parentWindowHandle "+toHexString(parentWindowHandle)); } if(0>newWidth || 0>newHeight) { throw new NativeWindowException("Illegal width or height "+newWidth+"x"+newHeight+" (must be >= 0)"); } defineSize(newWidth, newHeight); if(isNativeValid()) { if(!defer) { sendWindowEvent(WindowEvent.EVENT_WINDOW_RESIZED); } else { enqueueWindowEvent(false, WindowEvent.EVENT_WINDOW_RESIZED); } } } } private boolean waitForSize(int w, int h, boolean failFast, long timeOut) { final DisplayImpl display = (DisplayImpl) screen.getDisplay(); display.dispatchMessagesNative(); // status up2date long sleep; for(sleep = timeOut; 0= sleep) { final String msg = "Size/Pos not reached as requested within "+timeOut+"ms : requested "+w+"x"+h+", is "+getWidth()+"x"+getHeight(); if(failFast) { throw new NativeWindowException(msg); } else if (DEBUG_IMPLEMENTATION) { System.err.println(msg); Thread.dumpStack(); } return false; } else { return true; } } /** Triggered by implementation's WM events to update the position. */ protected void positionChanged(boolean defer, int newX, int newY) { if ( getX() != newX || getY() != newY ) { if(DEBUG_IMPLEMENTATION) { System.err.println("Window.positionChanged: ("+getThreadName()+"): (defer: "+defer+") "+getX()+"/"+getY()+" -> "+newX+"/"+newY+" - windowHandle "+toHexString(windowHandle)+" parentWindowHandle "+toHexString(parentWindowHandle)); } definePosition(newX, newY); if(!defer) { sendWindowEvent(WindowEvent.EVENT_WINDOW_MOVED); } else { enqueueWindowEvent(false, WindowEvent.EVENT_WINDOW_MOVED); } } else { autoPosition = false; // ensure it's off even w/ same position } } /** * Wait until position is reached within tolerances, either auto-position or custom position. *

* Since WM may not obey our positional request exactly, we allow a tolerance of 2 times insets[left/top], or 64 pixels, whatever is greater. *

*/ private boolean waitForPosition(boolean useCustomPosition, int x, int y, long timeOut) { final DisplayImpl display = (DisplayImpl) screen.getDisplay(); final int maxDX, maxDY; { final InsetsImmutable insets = getInsets(); maxDX = Math.max(64, insets.getLeftWidth() * 2); maxDY = Math.max(64, insets.getTopHeight() * 2); } long remaining = timeOut; boolean ok; do { if( useCustomPosition ) { ok = Math.abs(x - getX()) <= maxDX && Math.abs(y - getY()) <= maxDY ; } else { ok = !autoPosition; } if( !ok ) { try { Thread.sleep(10); } catch (InterruptedException ie) {} display.dispatchMessagesNative(); // status up2date remaining-=10; } } while ( 0= 0 && right >= 0 && top >= 0 && bottom >= 0 ) { if(isUndecorated()) { if(DEBUG_IMPLEMENTATION) { System.err.println("Window.insetsChanged: skip insets change for undecoration mode"); } } else if ( (left != insets.getLeftWidth() || right != insets.getRightWidth() || top != insets.getTopHeight() || bottom != insets.getBottomHeight() ) ) { insets.set(left, right, top, bottom); if(DEBUG_IMPLEMENTATION) { System.err.println("Window.insetsChanged: (defer: "+defer+") "+insets); } } } } /** * Triggered by implementation's WM events or programmatic while respecting {@link #getDefaultCloseOperation()}. * * @param force if true, overrides {@link #setDefaultCloseOperation(WindowClosingMode)} with {@link WindowClosingProtocol#DISPOSE_ON_CLOSE} * and hence force destruction. Otherwise is follows the user settings. * @return true if this window is no more valid and hence has been destroyed, otherwise false. */ public boolean windowDestroyNotify(boolean force) { final WindowClosingMode defMode = getDefaultCloseOperation(); final WindowClosingMode mode = force ? WindowClosingMode.DISPOSE_ON_CLOSE : defMode; if(DEBUG_IMPLEMENTATION) { System.err.println("Window.windowDestroyNotify(isNativeValid: "+isNativeValid()+", force: "+force+", mode "+defMode+" -> "+mode+") "+getThreadName()+": "+this); // Thread.dumpStack(); } final boolean destroyed; if( isNativeValid() ) { if( WindowClosingMode.DISPOSE_ON_CLOSE == mode ) { if(force) { setDefaultCloseOperation(mode); } try { if( null == windowDestroyNotifyAction ) { destroy(); } else { windowDestroyNotifyAction.run(); } } finally { if(force) { setDefaultCloseOperation(defMode); } } } else { // send synced destroy notifications sendWindowEvent(WindowEvent.EVENT_WINDOW_DESTROY_NOTIFY); } destroyed = !isNativeValid(); } else { destroyed = true; } if(DEBUG_IMPLEMENTATION) { System.err.println("Window.windowDestroyNotify(isNativeValid: "+isNativeValid()+", force: "+force+", mode "+mode+") END "+getThreadName()+": destroyed "+destroyed+", "+this); } return destroyed; } @Override public void windowRepaint(int x, int y, int width, int height) { windowRepaint(false, x, y, width, height); } /** * Triggered by implementation's WM events to update the content */ protected void windowRepaint(boolean defer, int x, int y, int width, int height) { width = ( 0 >= width ) ? getWidth() : width; height = ( 0 >= height ) ? getHeight() : height; if(DEBUG_IMPLEMENTATION) { System.err.println("Window.windowRepaint "+getThreadName()+" (defer: "+defer+") "+x+"/"+y+" "+width+"x"+height); } if(isNativeValid()) { NEWTEvent e = new WindowUpdateEvent(WindowEvent.EVENT_WINDOW_REPAINT, this, System.currentTimeMillis(), new Rectangle(x, y, width, height)); doEvent(defer, false, e); } } // // Reflection helper .. // private static Class[] getCustomConstructorArgumentTypes(Class windowClass) { Class[] argTypes = null; try { Method m = windowClass.getDeclaredMethod("getCustomConstructorArgumentTypes"); argTypes = (Class[]) m.invoke(null, (Object[])null); } catch (Throwable t) {} return argTypes; } private static int verifyConstructorArgumentTypes(Class[] types, Object[] args) { if(types.length != args.length) { return -1; } for(int i=0; i[] types) { StringBuilder sb = new StringBuilder(); for(int i=0; i




© 2015 - 2024 Weber Informatics LLC | Privacy Policy