diff options
author | Sven Gothel <[email protected]> | 2012-10-26 16:43:13 +0200 |
---|---|---|
committer | Sven Gothel <[email protected]> | 2012-10-26 16:43:13 +0200 |
commit | b008de41549e38aebdfcb7b094046235a8dde72f (patch) | |
tree | 4e51584207f22bd7203d1cc64bdab25e7a171eae /src | |
parent | 4f05d5add18048c2fbd1837c0563446c11177e8c (diff) |
Fix Bug 601 - Auto-Repeat Behavior: Adding unit tests for typed key order w/ and w/o auto repeat. Incl. fix for Windows.
Auto-Repeat tests recognizes whether auto-repeat could be triggered by AWT Robot.
The latter is not possible on Windows, hence manual testing was required on this platform.
Impact: X11, Windows and OSX produce proper key sequence incl. auto-repeat modifier mask.
Diffstat (limited to 'src')
18 files changed, 969 insertions, 67 deletions
diff --git a/src/newt/classes/com/jogamp/newt/event/KeyEvent.java b/src/newt/classes/com/jogamp/newt/event/KeyEvent.java index 7daaeada6..b6f4264f5 100644 --- a/src/newt/classes/com/jogamp/newt/event/KeyEvent.java +++ b/src/newt/classes/com/jogamp/newt/event/KeyEvent.java @@ -48,6 +48,17 @@ package com.jogamp.newt.event; * Besides regular modifiers like {@link InputEvent#SHIFT_MASK} etc., * the {@link InputEvent#AUTOREPEAT_MASK} bit is added if repetition is detected. * </p> + * <p> + * Auto-Repeat shall behave as follow: + * <pre> + D = pressed, U = released, T = typed + 0 = normal, 1 = auto-repeat + + D(0), [ U(1), T(1), D(1), U(1) T(1) ..], U(0) T(0) + * </pre> + * The idea is if you mask out auto-repeat in your event listener + * you just get one long pressed key D/U/T triple. + * </p> */ @SuppressWarnings("serial") public class KeyEvent extends InputEvent diff --git a/src/newt/classes/com/jogamp/newt/event/NEWTEvent.java b/src/newt/classes/com/jogamp/newt/event/NEWTEvent.java index fd5b69ccc..9d8d92ff6 100644 --- a/src/newt/classes/com/jogamp/newt/event/NEWTEvent.java +++ b/src/newt/classes/com/jogamp/newt/event/NEWTEvent.java @@ -161,11 +161,7 @@ public class NEWTEvent extends java.util.EventObject { return sb.append("NEWTEvent[sys:").append(isSystemEvent()).append(", source:").append(getSource().getClass().getName()).append(", when:").append(getWhen()).append(" d ").append((System.currentTimeMillis()-getWhen())).append("ms]"); } - public static String toHexString(int hex) { + static String toHexString(int hex) { return "0x" + Integer.toHexString(hex); } - - public static String toHexString(long hex) { - return "0x" + Long.toHexString(hex); - } } diff --git a/src/newt/classes/jogamp/newt/driver/windows/WindowDriver.java b/src/newt/classes/jogamp/newt/driver/windows/WindowDriver.java index 71437c461..c211bac61 100644 --- a/src/newt/classes/jogamp/newt/driver/windows/WindowDriver.java +++ b/src/newt/classes/jogamp/newt/driver/windows/WindowDriver.java @@ -279,61 +279,73 @@ public class WindowDriver extends WindowImpl { return keyCode; } private int lastPressedKeyCode = 0; + private char lastTypedKeyChar = 0; private int pressedKeyBalance = 0; private int autoRepeat = 0; - @Override - public void sendKeyEvent(int eventType, int modifiers, int keyCode, char keyChar) { + private final void emitKeyEvent(boolean send, boolean wait, int eventType, int modifiers, int keyCode, char keyChar) { + if( send ) { + super.sendKeyEvent(eventType, modifiers, keyCode, keyChar); + } else { + super.enqueueKeyEvent(wait, eventType, modifiers, keyCode, keyChar); + } + } + + private final void handleKeyEvent(boolean send, boolean wait, int eventType, int modifiers, int keyCode, char keyChar) { + // System.err.println("*** handleKeyEvent: event "+KeyEvent.getEventTypeString(eventType)+", mods "+toHexString(modifiers)); + // Note that we have to regenerate the keyCode for EVENT_KEY_TYPED on this platform keyCode = validateKeyCode(eventType, modifiers, keyCode, keyChar); + + // Reorder: WINDOWS delivery order is PRESSED, TYPED and RELEASED -> NEWT order: PRESSED, RELEASED and TYPED + // Auto-Repeat: WINDOWS delivers only PRESSED and TYPED. switch(eventType) { case KeyEvent.EVENT_KEY_RELEASED: - // reorder: WINDOWS delivery order is PRESSED, TYPED and RELEASED -> NEWT order: PRESSED, RELEASED and TYPED + if( 0 != autoRepeat ) { + // AR out - send out missing PRESSED + emitKeyEvent(send, wait, KeyEvent.EVENT_KEY_PRESSED, modifiers | autoRepeat, keyCode, lastTypedKeyChar); + } + autoRepeat = 0; + emitKeyEvent(send, wait, eventType, modifiers, keyCode, keyChar); + if( 0 != lastTypedKeyChar ) { + emitKeyEvent(send, wait, KeyEvent.EVENT_KEY_TYPED, modifiers, keyCode, lastTypedKeyChar); + lastTypedKeyChar = 0; + } break; case KeyEvent.EVENT_KEY_PRESSED: - if(pressedKeyBalance > 1) { - // Auto-Repeat: WINDOWS delivers only PRESSED and TYPED. - // Since reordering already injects RELEASE, we only need to set the AUTOREPEAT_MASK. + if( pressedKeyBalance > 1 ) { pressedKeyBalance--; - autoRepeat |= InputEvent.AUTOREPEAT_MASK; + if ( 0 == autoRepeat ) { + // AR in - skip already send PRESSED + autoRepeat = InputEvent.AUTOREPEAT_MASK; + } else { + emitKeyEvent(send, wait, eventType, modifiers | autoRepeat, keyCode, (char)-1); + } } else { - autoRepeat &= ~InputEvent.AUTOREPEAT_MASK; + autoRepeat = 0; + emitKeyEvent(send, wait, eventType, modifiers, keyCode, (char)-1); } - super.sendKeyEvent(eventType, modifiers | autoRepeat, keyCode, (char)-1); break; case KeyEvent.EVENT_KEY_TYPED: - modifiers |= autoRepeat; - super.sendKeyEvent(KeyEvent.EVENT_KEY_RELEASED, modifiers, keyCode, (char)-1); - super.sendKeyEvent(eventType, modifiers, keyCode, keyChar); + if( 0 == autoRepeat ) { + lastTypedKeyChar = keyChar; + } else { + modifiers |= autoRepeat; + emitKeyEvent(send, wait, KeyEvent.EVENT_KEY_RELEASED, modifiers, keyCode, (char)-1); + emitKeyEvent(send, wait, eventType, modifiers, keyCode, keyChar); + } break; } } @Override + public void sendKeyEvent(int eventType, int modifiers, int keyCode, char keyChar) { + handleKeyEvent(true, false, eventType, modifiers, keyCode, keyChar); + } + + @Override public void enqueueKeyEvent(boolean wait, int eventType, int modifiers, int keyCode, char keyChar) { - // Note that we have to regenerate the keyCode for EVENT_KEY_TYPED on this platform - keyCode = validateKeyCode(eventType, modifiers, keyCode, keyChar); - switch(eventType) { - case KeyEvent.EVENT_KEY_RELEASED: - // reorder: WINDOWS delivery order is PRESSED, TYPED and RELEASED -> NEWT order: PRESSED, RELEASED and TYPED - break; - case KeyEvent.EVENT_KEY_PRESSED: - if(pressedKeyBalance > 1) { - // Auto-Repeat: WINDOWS delivers only PRESSED and TYPED. - // Since reordering already injects RELEASE, we only need to set the AUTOREPEAT_MASK. - pressedKeyBalance--; - autoRepeat |= InputEvent.AUTOREPEAT_MASK; - } else { - autoRepeat &= ~InputEvent.AUTOREPEAT_MASK; - } - super.enqueueKeyEvent(wait, eventType, modifiers | autoRepeat, keyCode, (char)-1); - break; - case KeyEvent.EVENT_KEY_TYPED: - modifiers |= autoRepeat; - super.enqueueKeyEvent(wait, KeyEvent.EVENT_KEY_RELEASED, modifiers, keyCode, (char)-1); - super.enqueueKeyEvent(wait, eventType, modifiers, keyCode, keyChar); - break; - } + handleKeyEvent(false, wait, eventType, modifiers, keyCode, keyChar); } //---------------------------------------------------------------------- diff --git a/src/newt/native/X11Display.c b/src/newt/native/X11Display.c index d8202fcde..9f29acc0c 100644 --- a/src/newt/native/X11Display.c +++ b/src/newt/native/X11Display.c @@ -381,6 +381,8 @@ JNIEXPORT void JNICALL Java_jogamp_newt_driver_x11_DisplayDriver_DispatchMessage } else { autoRepeatModifiers &= ~EVENT_AUTOREPEAT_MASK; } + } else { + autoRepeatModifiers &= ~EVENT_AUTOREPEAT_MASK; } // fall through intended case KeyPress: diff --git a/src/test/com/jogamp/opengl/test/junit/newt/TestKeyEventAutoRepeatNEWT.java b/src/test/com/jogamp/opengl/test/junit/newt/TestKeyEventAutoRepeatNEWT.java new file mode 100644 index 000000000..22c362dd8 --- /dev/null +++ b/src/test/com/jogamp/opengl/test/junit/newt/TestKeyEventAutoRepeatNEWT.java @@ -0,0 +1,309 @@ +/** + * Copyright 2012 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: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions 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. + * + * THIS SOFTWARE IS PROVIDED BY JogAmp Community ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JogAmp Community OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of JogAmp Community. + */ + +package com.jogamp.opengl.test.junit.newt; + +import org.junit.After; +import org.junit.Assert; +import org.junit.AfterClass; +import org.junit.Assume; +import org.junit.Before; + +import java.awt.AWTException; +import java.awt.BorderLayout; +import java.awt.Robot; +import java.lang.reflect.InvocationTargetException; +import java.util.Arrays; +import java.util.EventObject; +import java.util.List; + +import javax.media.opengl.GLCapabilities; +import javax.media.opengl.GLEventListener; +import javax.swing.JFrame; + +import java.io.IOException; + +import org.junit.BeforeClass; +import org.junit.Test; + +import com.jogamp.newt.awt.NewtCanvasAWT; +import com.jogamp.newt.event.InputEvent; +import com.jogamp.newt.event.KeyEvent; +import com.jogamp.newt.opengl.GLWindow; +import com.jogamp.opengl.util.Animator; +import com.jogamp.opengl.test.junit.jogl.demos.es2.RedSquareES2; + +import com.jogamp.opengl.test.junit.util.*; + +/** + * Testing key event order incl. auto-repeat (Bug 601) + * + * <p> + * Note Event order: + * <ol> + * <li>{@link #EVENT_KEY_PRESSED}</li> + * <li>{@link #EVENT_KEY_RELEASED}</li> + * <li>{@link #EVENT_KEY_TYPED}</li> + * </ol> + * </p> + * <p> + * Auto-Repeat shall behave as follow: + * <pre> + D = pressed, U = released, T = typed + 0 = normal, 1 = auto-repeat + + D(0), [ U(1), T(1), D(1), U(1) T(1) ..], U(0) T(0) + * </pre> + * + * The idea is if you mask out auto-repeat in your event listener + * you just get one long pressed key D/U/T triple. + */ +public class TestKeyEventAutoRepeatNEWT extends UITestCase { + static int width, height; + static long durationPerTest = 100; + static long awtWaitTimeout = 1000; + + static GLCapabilities glCaps; + + @BeforeClass + public static void initClass() { + width = 640; + height = 480; + glCaps = new GLCapabilities(null); + } + + @AfterClass + public static void release() { + } + + @Before + public void initTest() { + } + + @After + public void releaseTest() { + } + + @Test + public void test01NEWT() throws AWTException, InterruptedException, InvocationTargetException { + GLWindow glWindow = GLWindow.create(glCaps); + glWindow.setSize(width, height); + glWindow.setVisible(true); + + testImpl(glWindow); + + glWindow.destroy(); + } + + @Test + public void test02NewtCanvasAWT() throws AWTException, InterruptedException, InvocationTargetException { + GLWindow glWindow = GLWindow.create(glCaps); + + // Wrap the window in a canvas. + final NewtCanvasAWT newtCanvasAWT = new NewtCanvasAWT(glWindow); + + // Add the canvas to a frame, and make it all visible. + final JFrame frame1 = new JFrame("Swing AWT Parent Frame: "+ glWindow.getTitle()); + frame1.getContentPane().add(newtCanvasAWT, BorderLayout.CENTER); + frame1.setSize(width, height); + javax.swing.SwingUtilities.invokeAndWait(new Runnable() { + public void run() { + frame1.setVisible(true); + } } ); + + Assert.assertEquals(true, AWTRobotUtil.waitForVisible(frame1, true)); + + testImpl(glWindow); + + try { + javax.swing.SwingUtilities.invokeAndWait(new Runnable() { + public void run() { + frame1.setVisible(false); + frame1.dispose(); + }}); + } catch( Throwable throwable ) { + throwable.printStackTrace(); + Assume.assumeNoException( throwable ); + } + glWindow.destroy(); + } + + static void testKeyEventAutoRepeat(Robot robot, NEWTKeyAdapter keyAdapter, int loops, int pressDurationMS) { + System.err.println("KEY Event Auto-Repeat Test: "+loops); + EventObject[][] first = new EventObject[loops][3]; + EventObject[][] last = new EventObject[loops][3]; + + keyAdapter.reset(); + final List<EventObject> keyEvents = keyAdapter.getQueued(); + int firstIdx = 0; + for(int i=0; i<loops; i++) { + System.err.println("+++ KEY Event Auto-Repeat START Input Loop: "+i); + AWTRobotUtil.keyPress(0, robot, true, java.awt.event.KeyEvent.VK_A, pressDurationMS); + robot.waitForIdle(); + AWTRobotUtil.keyPress(0, robot, false, java.awt.event.KeyEvent.VK_A, 1000); // 1s .. no AR anymore + robot.waitForIdle(); + first[i][0] = (KeyEvent) keyEvents.get(firstIdx+0); + first[i][1] = (KeyEvent) keyEvents.get(firstIdx+1); + first[i][2] = (KeyEvent) keyEvents.get(firstIdx+2); + firstIdx = keyEvents.size() - 3; + last[i][0] = (KeyEvent) keyEvents.get(firstIdx+0); + last[i][1] = (KeyEvent) keyEvents.get(firstIdx+1); + last[i][2] = (KeyEvent) keyEvents.get(firstIdx+2); + System.err.println("+++ KEY Event Auto-Repeat END Input Loop: "+i); + + // add a pair of normal press/release in between auto-repeat! + AWTRobotUtil.keyPress(0, robot, true, java.awt.event.KeyEvent.VK_B, 10); + robot.waitForIdle(); + AWTRobotUtil.keyPress(0, robot, false, java.awt.event.KeyEvent.VK_B, 250); + robot.waitForIdle(); + firstIdx = keyEvents.size(); + } + // dumpKeyEvents(keyEvents); + + NEWTKeyUtil.validateKeyEventOrder(keyEvents); + + final boolean hasAR = 0 < keyAdapter.getKeyPressedCount(true) ; + + Assert.assertEquals("Key event count not multiple of 3", 0, keyEvents.size()%3); + final int expTotal = keyEvents.size()/3; + final int expAR = hasAR ? expTotal - loops - loops : 0; + NEWTKeyUtil.validateKeyAdapterStats(keyAdapter, expTotal, expAR); + + if( !hasAR ) { + System.err.println("No AUTO-REPEAT triggered by AWT Robot .. aborting test analysis"); + return; + } + + for(int i=0; i<loops; i++) { + System.err.println("Auto-Repeat Loop "+i+" - Head:"); + NEWTKeyUtil.dumpKeyEvents(Arrays.asList(first[i])); + System.err.println("Auto-Repeat Loop "+i+" - Tail:"); + NEWTKeyUtil.dumpKeyEvents(Arrays.asList(last[i])); + } + for(int i=0; i<loops; i++) { + KeyEvent e = (KeyEvent) first[i][0]; + Assert.assertTrue("1st Shall be A, but is "+e, KeyEvent.VK_A == e.getKeyCode() ); + Assert.assertTrue("1st Shall be PRESSED, but is "+e, KeyEvent.EVENT_KEY_PRESSED == e.getEventType() ); + Assert.assertTrue("1st Shall not be AR, but is "+e, 0 == ( InputEvent.AUTOREPEAT_MASK & e.getModifiers() ) ); + + e = (KeyEvent) first[i][1]; + Assert.assertTrue("2nd Shall be A, but is "+e, KeyEvent.VK_A == e.getKeyCode() ); + Assert.assertTrue("2nd Shall be RELEASED, but is "+e, KeyEvent.EVENT_KEY_RELEASED == e.getEventType() ); + Assert.assertTrue("2nd Shall be AR, but is "+e, 0 != ( InputEvent.AUTOREPEAT_MASK & e.getModifiers() ) ); + + e = (KeyEvent) first[i][2]; + Assert.assertTrue("3rd Shall be A, but is "+e, KeyEvent.VK_A == e.getKeyCode() ); + Assert.assertTrue("3rd Shall be TYPED, but is "+e, KeyEvent.EVENT_KEY_TYPED == e.getEventType() ); + Assert.assertTrue("3rd Shall be AR, but is "+e, 0 != ( InputEvent.AUTOREPEAT_MASK & e.getModifiers() ) ); + + e = (KeyEvent) last[i][0]; + Assert.assertTrue("last-2 Shall be A, but is "+e, KeyEvent.VK_A == e.getKeyCode() ); + Assert.assertTrue("last-2 Shall be PRESSED, but is "+e, KeyEvent.EVENT_KEY_PRESSED == e.getEventType() ); + Assert.assertTrue("last-2 Shall be AR, but is "+e, 0 != ( InputEvent.AUTOREPEAT_MASK & e.getModifiers() ) ); + + e = (KeyEvent) last[i][1]; + Assert.assertTrue("last-1 Shall be A, but is "+e, KeyEvent.VK_A == e.getKeyCode() ); + Assert.assertTrue("last-1 Shall be RELEASED, but is "+e, KeyEvent.EVENT_KEY_RELEASED == e.getEventType() ); + Assert.assertTrue("last-1 Shall not be AR, but is "+e, 0 == ( InputEvent.AUTOREPEAT_MASK & e.getModifiers() ) ); + + e = (KeyEvent) last[i][2]; + Assert.assertTrue("last-0 Shall be A, but is "+e, KeyEvent.VK_A == e.getKeyCode() ); + Assert.assertTrue("last-0 Shall be TYPED, but is "+e, KeyEvent.EVENT_KEY_TYPED == e.getEventType() ); + Assert.assertTrue("last-0 Shall not be AR, but is "+e, 0 == ( InputEvent.AUTOREPEAT_MASK & e.getModifiers() ) ); + } + } + + void testImpl(GLWindow glWindow) throws AWTException, InterruptedException, InvocationTargetException { + final Robot robot = new Robot(); + robot.setAutoWaitForIdle(true); + + GLEventListener demo1 = new RedSquareES2(); + TestListenerCom01AWT.setDemoFields(demo1, glWindow, false); + glWindow.addGLEventListener(demo1); + + NEWTKeyAdapter glWindow1KA = new NEWTKeyAdapter("GLWindow1"); + glWindow1KA.setVerbose(false); + glWindow.addKeyListener(glWindow1KA); + + Assert.assertEquals(true, AWTRobotUtil.waitForRealized(glWindow, true)); + AWTRobotUtil.clearAWTFocus(robot); + + // Continuous animation .. + Animator animator = new Animator(glWindow); + animator.start(); + + Thread.sleep(durationPerTest); // manual testing + + glWindow1KA.reset(); + AWTRobotUtil.assertRequestFocusAndWait(null, glWindow, glWindow, null, null); // programmatic + // AWTRobotUtil.assertRequestFocusAndWait(robot, glWindow, glWindow, null, null); // by mouse click + + // + // Test the key event order w/ auto-repeat + // + final int origAutoDelay = robot.getAutoDelay(); + robot.setAutoDelay(10); + try { + testKeyEventAutoRepeat(robot, glWindow1KA, 3, 1000); + } finally { + robot.setAutoDelay(origAutoDelay); + } + + // Remove listeners to avoid logging during dispose/destroy. + glWindow.removeKeyListener(glWindow1KA); + + // Shutdown the test. + animator.stop(); + } + + static int atoi(String a) { + int i=0; + try { + i = Integer.parseInt(a); + } catch (Exception ex) { ex.printStackTrace(); } + return i; + } + + public static void main(String args[]) throws IOException { + for(int i=0; i<args.length; i++) { + if(args[i].equals("-time")) { + durationPerTest = atoi(args[++i]); + } + } + /** + BufferedReader stdin = new BufferedReader(new InputStreamReader(System.in)); + System.err.println("Press enter to continue"); + System.err.println(stdin.readLine()); + */ + System.out.println("durationPerTest: "+durationPerTest); + String tstname = TestKeyEventAutoRepeatNEWT.class.getName(); + org.junit.runner.JUnitCore.main(tstname); + } + + +} diff --git a/src/test/com/jogamp/opengl/test/junit/newt/TestKeyEventOrderNEWT.java b/src/test/com/jogamp/opengl/test/junit/newt/TestKeyEventOrderNEWT.java new file mode 100644 index 000000000..e0c3005a2 --- /dev/null +++ b/src/test/com/jogamp/opengl/test/junit/newt/TestKeyEventOrderNEWT.java @@ -0,0 +1,219 @@ +/** + * Copyright 2012 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: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions 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. + * + * THIS SOFTWARE IS PROVIDED BY JogAmp Community ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JogAmp Community OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of JogAmp Community. + */ + +package com.jogamp.opengl.test.junit.newt; + +import org.junit.After; +import org.junit.Assert; +import org.junit.AfterClass; +import org.junit.Assume; +import org.junit.Before; + +import java.awt.AWTException; +import java.awt.BorderLayout; +import java.awt.Robot; +import java.lang.reflect.InvocationTargetException; + +import javax.media.opengl.GLCapabilities; +import javax.media.opengl.GLEventListener; +import javax.swing.JFrame; + +import java.io.IOException; + +import org.junit.BeforeClass; +import org.junit.Test; + +import com.jogamp.newt.awt.NewtCanvasAWT; +import com.jogamp.newt.opengl.GLWindow; +import com.jogamp.opengl.util.Animator; +import com.jogamp.opengl.test.junit.jogl.demos.es2.RedSquareES2; + +import com.jogamp.opengl.test.junit.util.*; + +/** + * Testing key event order incl. auto-repeat (Bug 601) + * + * <p> + * Note Event order: + * <ol> + * <li>{@link #EVENT_KEY_PRESSED}</li> + * <li>{@link #EVENT_KEY_RELEASED}</li> + * <li>{@link #EVENT_KEY_TYPED}</li> + * </ol> + * </p> + */ +public class TestKeyEventOrderNEWT extends UITestCase { + static int width, height; + static long durationPerTest = 100; + static long awtWaitTimeout = 1000; + + static GLCapabilities glCaps; + + @BeforeClass + public static void initClass() { + width = 640; + height = 480; + glCaps = new GLCapabilities(null); + } + + @AfterClass + public static void release() { + } + + @Before + public void initTest() { + } + + @After + public void releaseTest() { + } + + @Test + public void test01NEWT() throws AWTException, InterruptedException, InvocationTargetException { + GLWindow glWindow = GLWindow.create(glCaps); + glWindow.setSize(width, height); + glWindow.setVisible(true); + + testImpl(glWindow); + + glWindow.destroy(); + } + + @Test + public void test02NewtCanvasAWT() throws AWTException, InterruptedException, InvocationTargetException { + GLWindow glWindow = GLWindow.create(glCaps); + + // Wrap the window in a canvas. + final NewtCanvasAWT newtCanvasAWT = new NewtCanvasAWT(glWindow); + + // Add the canvas to a frame, and make it all visible. + final JFrame frame1 = new JFrame("Swing AWT Parent Frame: "+ glWindow.getTitle()); + frame1.getContentPane().add(newtCanvasAWT, BorderLayout.CENTER); + frame1.setSize(width, height); + javax.swing.SwingUtilities.invokeAndWait(new Runnable() { + public void run() { + frame1.setVisible(true); + } } ); + + Assert.assertEquals(true, AWTRobotUtil.waitForVisible(frame1, true)); + + testImpl(glWindow); + + try { + javax.swing.SwingUtilities.invokeAndWait(new Runnable() { + public void run() { + frame1.setVisible(false); + frame1.dispose(); + }}); + } catch( Throwable throwable ) { + throwable.printStackTrace(); + Assume.assumeNoException( throwable ); + } + glWindow.destroy(); + } + + static void testKeyEventOrder(Robot robot, NEWTKeyAdapter keyAdapter, int loops) { + System.err.println("KEY Event Order Test: "+loops); + keyAdapter.reset(); + for(int i=0; i<loops; i++) { + AWTRobotUtil.keyPress(0, robot, true, java.awt.event.KeyEvent.VK_A, 10); + robot.waitForIdle(); + AWTRobotUtil.keyPress(0, robot, false, java.awt.event.KeyEvent.VK_A, 100); + robot.waitForIdle(); + } + robot.delay(250); + // dumpKeyEvents(keyAdapter.getQueued()); + + NEWTKeyUtil.validateKeyEventOrder(keyAdapter.getQueued()); + + NEWTKeyUtil.validateKeyAdapterStats(keyAdapter, loops, 0); + } + + void testImpl(GLWindow glWindow) throws AWTException, InterruptedException, InvocationTargetException { + final Robot robot = new Robot(); + robot.setAutoWaitForIdle(true); + + GLEventListener demo1 = new RedSquareES2(); + TestListenerCom01AWT.setDemoFields(demo1, glWindow, false); + glWindow.addGLEventListener(demo1); + + NEWTKeyAdapter glWindow1KA = new NEWTKeyAdapter("GLWindow1"); + glWindow1KA.setVerbose(false); + glWindow.addKeyListener(glWindow1KA); + + Assert.assertEquals(true, AWTRobotUtil.waitForRealized(glWindow, true)); + AWTRobotUtil.clearAWTFocus(robot); + + // Continuous animation .. + Animator animator = new Animator(glWindow); + animator.start(); + + Thread.sleep(durationPerTest); // manual testing + + glWindow1KA.reset(); + AWTRobotUtil.assertRequestFocusAndWait(null, glWindow, glWindow, null, null); // programmatic + // AWTRobotUtil.assertRequestFocusAndWait(robot, glWindow, glWindow, null, null); // by mouse click + + // + // Test the key event order w/o auto-repeat + // + testKeyEventOrder(robot, glWindow1KA, 12); + + // Remove listeners to avoid logging during dispose/destroy. + glWindow.removeKeyListener(glWindow1KA); + + // Shutdown the test. + animator.stop(); + } + + static int atoi(String a) { + int i=0; + try { + i = Integer.parseInt(a); + } catch (Exception ex) { ex.printStackTrace(); } + return i; + } + + public static void main(String args[]) throws IOException { + for(int i=0; i<args.length; i++) { + if(args[i].equals("-time")) { + durationPerTest = atoi(args[++i]); + } + } + /** + BufferedReader stdin = new BufferedReader(new InputStreamReader(System.in)); + System.err.println("Press enter to continue"); + System.err.println(stdin.readLine()); + */ + System.out.println("durationPerTest: "+durationPerTest); + String tstname = TestKeyEventOrderNEWT.class.getName(); + org.junit.runner.JUnitCore.main(tstname); + } + + +} diff --git a/src/test/com/jogamp/opengl/test/junit/util/AWTFocusAdapter.java b/src/test/com/jogamp/opengl/test/junit/util/AWTFocusAdapter.java index fe0f2acc0..32ff6022c 100644 --- a/src/test/com/jogamp/opengl/test/junit/util/AWTFocusAdapter.java +++ b/src/test/com/jogamp/opengl/test/junit/util/AWTFocusAdapter.java @@ -36,12 +36,15 @@ public class AWTFocusAdapter implements FocusEventCountAdapter, FocusListener { String prefix; int focusCount; boolean wasTemporary; + boolean verbose = true; public AWTFocusAdapter(String prefix) { this.prefix = prefix; reset(); } + public void setVerbose(boolean v) { verbose = false; } + public boolean focusLost() { return focusCount<0; } @@ -65,7 +68,9 @@ public class AWTFocusAdapter implements FocusEventCountAdapter, FocusListener { if(focusCount<0) { focusCount=0; } focusCount++; wasTemporary = e.isTemporary(); - System.err.println("FOCUS AWT GAINED "+(wasTemporary?"TEMP":"PERM")+" [fc "+focusCount+"]: "+prefix+", "+e); + if( verbose ) { + System.err.println("FOCUS AWT GAINED "+(wasTemporary?"TEMP":"PERM")+" [fc "+focusCount+"]: "+prefix+", "+e); + } } /* @Override */ @@ -73,7 +78,9 @@ public class AWTFocusAdapter implements FocusEventCountAdapter, FocusListener { if(focusCount>0) { focusCount=0; } focusCount--; wasTemporary = e.isTemporary(); - System.err.println("FOCUS AWT LOST "+(wasTemporary?"TEMP":"PERM")+" [fc "+focusCount+"]: "+prefix+", "+e); + if( verbose ) { + System.err.println("FOCUS AWT LOST "+(wasTemporary?"TEMP":"PERM")+" [fc "+focusCount+"]: "+prefix+", "+e); + } } public String toString() { return prefix+"[focusCount "+focusCount +", temp "+wasTemporary+"]"; } diff --git a/src/test/com/jogamp/opengl/test/junit/util/AWTKeyAdapter.java b/src/test/com/jogamp/opengl/test/junit/util/AWTKeyAdapter.java index 6c0156170..a9fa373b5 100644 --- a/src/test/com/jogamp/opengl/test/junit/util/AWTKeyAdapter.java +++ b/src/test/com/jogamp/opengl/test/junit/util/AWTKeyAdapter.java @@ -29,18 +29,25 @@ package com.jogamp.opengl.test.junit.util; import java.awt.event.KeyEvent; +import java.util.ArrayList; +import java.util.EventObject; +import java.util.List; -public class AWTKeyAdapter extends java.awt.event.KeyAdapter implements InputEventCountAdapter { +public class AWTKeyAdapter extends java.awt.event.KeyAdapter implements KeyEventCountAdapter { String prefix; - int keyTyped; + int keyPressed, keyReleased, keyTyped; boolean pressed; + List<EventObject> queue = new ArrayList<EventObject>(); + boolean verbose = true; public AWTKeyAdapter(String prefix) { this.prefix = prefix; reset(); } + public void setVerbose(boolean v) { verbose = false; } + public boolean isPressed() { return pressed; } @@ -49,24 +56,54 @@ public class AWTKeyAdapter extends java.awt.event.KeyAdapter implements InputEve return keyTyped; } + public int getKeyPressedCount(boolean autoRepeatOnly) { + return keyPressed; + } + + public int getKeyReleasedCount(boolean autoRepeatOnly) { + return keyReleased; + } + + public int getKeyTypedCount(boolean autoRepeatOnly) { + return keyTyped; + } + + public List<EventObject> getQueued() { + return queue; + } + public void reset() { keyTyped = 0; + keyPressed = 0; + keyReleased = 0; pressed = false; + queue.clear(); } public void keyPressed(KeyEvent e) { pressed = true; - System.err.println("KEY AWT PRESSED ["+pressed+"]: "+prefix+", "+e); + keyPressed++; + queue.add(e); + if( verbose ) { + System.err.println("KEY AWT PRESSED ["+pressed+"]: "+prefix+", "+e); + } } public void keyReleased(KeyEvent e) { pressed = false; - System.err.println("KEY AWT RELEASED ["+pressed+"]: "+prefix+", "+e); + keyReleased++; + queue.add(e); + if( verbose ) { + System.err.println("KEY AWT RELEASED ["+pressed+"]: "+prefix+", "+e); + } } public void keyTyped(java.awt.event.KeyEvent e) { - ++keyTyped; - System.err.println("KEY AWT TYPED ["+keyTyped+"]: "+prefix+", "+e); + keyTyped++; + queue.add(e); + if( verbose ) { + System.err.println("KEY AWT TYPED ["+keyTyped+"]: "+prefix+", "+e); + } } public String toString() { return prefix+"[pressed "+pressed+", typed "+keyTyped+"]"; } diff --git a/src/test/com/jogamp/opengl/test/junit/util/AWTMouseAdapter.java b/src/test/com/jogamp/opengl/test/junit/util/AWTMouseAdapter.java index b94802348..3334f18ea 100644 --- a/src/test/com/jogamp/opengl/test/junit/util/AWTMouseAdapter.java +++ b/src/test/com/jogamp/opengl/test/junit/util/AWTMouseAdapter.java @@ -29,17 +29,24 @@ package com.jogamp.opengl.test.junit.util; import java.awt.event.MouseEvent; +import java.util.ArrayList; +import java.util.EventObject; +import java.util.List; public class AWTMouseAdapter extends java.awt.event.MouseAdapter implements InputEventCountAdapter { String prefix; int mouseClicked; boolean pressed; + List<EventObject> queue = new ArrayList<EventObject>(); + boolean verbose = true; public AWTMouseAdapter(String prefix) { this.prefix = prefix; reset(); } + public void setVerbose(boolean v) { verbose = false; } + public boolean isPressed() { return pressed; } @@ -48,24 +55,38 @@ public class AWTMouseAdapter extends java.awt.event.MouseAdapter implements Inpu return mouseClicked; } + public List<EventObject> getQueued() { + return queue; + } + public void reset() { mouseClicked = 0; pressed = false; + queue.clear(); } public void mousePressed(MouseEvent e) { pressed = true; - System.err.println("MOUSE AWT PRESSED ["+pressed+"]: "+prefix+", "+e); + queue.add(e); + if( verbose ) { + System.err.println("MOUSE AWT PRESSED ["+pressed+"]: "+prefix+", "+e); + } } public void mouseReleased(MouseEvent e) { pressed = false; - System.err.println("MOUSE AWT RELEASED ["+pressed+"]: "+prefix+", "+e); + queue.add(e); + if( verbose ) { + System.err.println("MOUSE AWT RELEASED ["+pressed+"]: "+prefix+", "+e); + } } public void mouseClicked(java.awt.event.MouseEvent e) { mouseClicked+=e.getClickCount(); - System.err.println("MOUSE AWT CLICKED ["+mouseClicked+"]: "+prefix+", "+e); + queue.add(e); + if( verbose ) { + System.err.println("MOUSE AWT CLICKED ["+mouseClicked+"]: "+prefix+", "+e); + } } public String toString() { return prefix+"[pressed "+pressed+", clicked "+mouseClicked+"]"; } diff --git a/src/test/com/jogamp/opengl/test/junit/util/AWTRobotUtil.java b/src/test/com/jogamp/opengl/test/junit/util/AWTRobotUtil.java index 160653cd5..06e172a5d 100644 --- a/src/test/com/jogamp/opengl/test/junit/util/AWTRobotUtil.java +++ b/src/test/com/jogamp/opengl/test/junit/util/AWTRobotUtil.java @@ -312,7 +312,7 @@ public class AWTRobotUtil { } if(!hasFocus) { System.err.print("*** AWTRobotUtil.assertRequestFocusAndWait() "); - if(gain.focusGained() && !lost.focusLost()) { + if( ( null == gain || gain.focusGained() ) && ( null == lost || !lost.focusLost() ) ) { // be error tolerant here, some impl. may lack focus-lost events (OS X) System.err.println("minor UI failure"); hasFocus = true; @@ -337,7 +337,7 @@ public class AWTRobotUtil { } public static int keyType(int i, Robot robot, int keyCode, - Object obj, InputEventCountAdapter counter) throws InterruptedException, AWTException, InvocationTargetException + Object obj, KeyEventCountAdapter counter) throws InterruptedException, AWTException, InvocationTargetException { int tc = 0; int j; @@ -365,13 +365,26 @@ public class AWTRobotUtil { Assert.assertEquals("Key ("+i+":"+j+") not typed one time", 1, tc); return (int) ( System.currentTimeMillis() - t0 ) ; } + + /** No validation is performed .. */ + public static int keyPress(int i, Robot robot, boolean press, int keyCode, int msDelay) { + final long t0 = System.currentTimeMillis(); + if(press) { + robot.keyPress(keyCode); + } else { + robot.keyRelease(keyCode); + } + robot.delay(msDelay); + + return (int) ( System.currentTimeMillis() - t0 ) ; + } /** * @param keyCode TODO * @param counter shall return the number of keys typed (press + release) */ public static void assertKeyType(Robot robot, int keyCode, int typeCount, - Object obj, InputEventCountAdapter counter) + Object obj, KeyEventCountAdapter counter) throws AWTException, InterruptedException, InvocationTargetException { if(null == robot) { @@ -398,6 +411,38 @@ public class AWTRobotUtil { Assert.assertEquals("Wrong key count", typeCount, counter.getCount()-c0); } + /** + * @param keyCode TODO + * @param counter shall return the number of keys typed (press + release) + */ + public static void assertKeyPress(Robot robot, int keyCode, int typeCount, + Object obj, KeyEventCountAdapter counter) + throws AWTException, InterruptedException, InvocationTargetException { + + if(null == robot) { + robot = new Robot(); + robot.setAutoWaitForIdle(true); + } + + centerMouse(robot, obj, false); + + Assert.assertEquals("Key already pressed", false, counter.isPressed()); + + if(DEBUG) { + System.err.println("**************************************"); + System.err.println("KC0: "+counter); + } + + final int c0 = counter.getCount(); + + for(int i=0; i<typeCount; i++) { + keyType(i, robot, keyCode, obj, counter); + } + + if(DEBUG) { System.err.println("KC3.0: "+counter); } + Assert.assertEquals("Wrong key count", typeCount, counter.getCount()-c0); + } + static int mouseClick(int i, Robot robot, int mouseButton, Object obj, InputEventCountAdapter counter) throws InterruptedException, AWTException, InvocationTargetException { diff --git a/src/test/com/jogamp/opengl/test/junit/util/AWTWindowFocusAdapter.java b/src/test/com/jogamp/opengl/test/junit/util/AWTWindowFocusAdapter.java index 16aacd2fd..2e74dfae8 100644 --- a/src/test/com/jogamp/opengl/test/junit/util/AWTWindowFocusAdapter.java +++ b/src/test/com/jogamp/opengl/test/junit/util/AWTWindowFocusAdapter.java @@ -35,12 +35,15 @@ public class AWTWindowFocusAdapter implements FocusEventCountAdapter, WindowFocu String prefix; int focusCount; + boolean verbose = true; public AWTWindowFocusAdapter(String prefix) { this.prefix = prefix; reset(); } + public void setVerbose(boolean v) { verbose = false; } + public boolean focusLost() { return focusCount<0; } @@ -57,14 +60,18 @@ public class AWTWindowFocusAdapter implements FocusEventCountAdapter, WindowFocu public void windowGainedFocus(WindowEvent e) { if(focusCount<0) { focusCount=0; } focusCount++; - System.err.println("FOCUS AWT GAINED (Window) [fc "+focusCount+"]: "+prefix+", "+e); + if( verbose ) { + System.err.println("FOCUS AWT GAINED (Window) [fc "+focusCount+"]: "+prefix+", "+e); + } } /* @Override */ public void windowLostFocus(WindowEvent e) { if(focusCount>0) { focusCount=0; } focusCount--; - System.err.println("FOCUS AWT LOST (Window) [fc "+focusCount+"]: "+prefix+", "+e); + if( verbose ) { + System.err.println("FOCUS AWT LOST (Window) [fc "+focusCount+"]: "+prefix+", "+e); + } } public String toString() { return prefix+"[focusCount "+focusCount +"]"; } diff --git a/src/test/com/jogamp/opengl/test/junit/util/EventCountAdapter.java b/src/test/com/jogamp/opengl/test/junit/util/EventCountAdapter.java index 76a1884c8..906e4a7c9 100644 --- a/src/test/com/jogamp/opengl/test/junit/util/EventCountAdapter.java +++ b/src/test/com/jogamp/opengl/test/junit/util/EventCountAdapter.java @@ -28,7 +28,16 @@ package com.jogamp.opengl.test.junit.util; +/** + * Base event count adapter. + * <p> + * Instance starts in verbose mode. + * </p> + */ public interface EventCountAdapter { void reset(); + + /** Instance starts in verbose mode, call w/ false to disable verbosity. */ + void setVerbose(boolean v); } diff --git a/src/test/com/jogamp/opengl/test/junit/util/InputEventCountAdapter.java b/src/test/com/jogamp/opengl/test/junit/util/InputEventCountAdapter.java index 27f3d7e29..ed7485951 100644 --- a/src/test/com/jogamp/opengl/test/junit/util/InputEventCountAdapter.java +++ b/src/test/com/jogamp/opengl/test/junit/util/InputEventCountAdapter.java @@ -28,8 +28,13 @@ package com.jogamp.opengl.test.junit.util; +import java.util.EventObject; +import java.util.List; + public interface InputEventCountAdapter extends EventCountAdapter { int getCount(); boolean isPressed(); + + public List<EventObject> getQueued(); } diff --git a/src/test/com/jogamp/opengl/test/junit/util/KeyEventCountAdapter.java b/src/test/com/jogamp/opengl/test/junit/util/KeyEventCountAdapter.java new file mode 100644 index 000000000..832f5ae82 --- /dev/null +++ b/src/test/com/jogamp/opengl/test/junit/util/KeyEventCountAdapter.java @@ -0,0 +1,38 @@ +/** + * Copyright 2012 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: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions 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. + * + * THIS SOFTWARE IS PROVIDED BY JogAmp Community ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JogAmp Community OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of JogAmp Community. + */ + +package com.jogamp.opengl.test.junit.util; + +public interface KeyEventCountAdapter extends InputEventCountAdapter { + public int getKeyPressedCount(boolean autoRepeatOnly); + + public int getKeyReleasedCount(boolean autoRepeatOnly); + + public int getKeyTypedCount(boolean autoRepeatOnly); +} + diff --git a/src/test/com/jogamp/opengl/test/junit/util/NEWTFocusAdapter.java b/src/test/com/jogamp/opengl/test/junit/util/NEWTFocusAdapter.java index 27d4abd9c..ccb1bde78 100644 --- a/src/test/com/jogamp/opengl/test/junit/util/NEWTFocusAdapter.java +++ b/src/test/com/jogamp/opengl/test/junit/util/NEWTFocusAdapter.java @@ -36,12 +36,15 @@ public class NEWTFocusAdapter implements WindowListener, FocusEventCountAdapter String prefix; int focusCount; + boolean verbose = true; public NEWTFocusAdapter(String prefix) { this.prefix = prefix; reset(); } + public void setVerbose(boolean v) { verbose = false; } + public boolean focusLost() { return focusCount<0; } @@ -57,13 +60,17 @@ public class NEWTFocusAdapter implements WindowListener, FocusEventCountAdapter public void windowGainedFocus(WindowEvent e) { if(focusCount<0) { focusCount=0; } focusCount++; - System.err.println("FOCUS NEWT GAINED [fc "+focusCount+"]: "+prefix+", "+e); + if( verbose ) { + System.err.println("FOCUS NEWT GAINED [fc "+focusCount+"]: "+prefix+", "+e); + } } public void windowLostFocus(WindowEvent e) { if(focusCount>0) { focusCount=0; } focusCount--; - System.err.println("FOCUS NEWT LOST [fc "+focusCount+"]: "+prefix+", "+e); + if( verbose ) { + System.err.println("FOCUS NEWT LOST [fc "+focusCount+"]: "+prefix+", "+e); + } } public void windowResized(WindowEvent e) { } diff --git a/src/test/com/jogamp/opengl/test/junit/util/NEWTKeyAdapter.java b/src/test/com/jogamp/opengl/test/junit/util/NEWTKeyAdapter.java index 32b392ca8..42235254a 100644 --- a/src/test/com/jogamp/opengl/test/junit/util/NEWTKeyAdapter.java +++ b/src/test/com/jogamp/opengl/test/junit/util/NEWTKeyAdapter.java @@ -28,19 +28,29 @@ package com.jogamp.opengl.test.junit.util; +import java.util.ArrayList; +import java.util.EventObject; +import java.util.List; + +import com.jogamp.newt.event.InputEvent; import com.jogamp.newt.event.KeyAdapter; import com.jogamp.newt.event.KeyEvent; -public class NEWTKeyAdapter extends KeyAdapter implements InputEventCountAdapter { +public class NEWTKeyAdapter extends KeyAdapter implements KeyEventCountAdapter { String prefix; - int keyTyped; + int keyPressed, keyReleased, keyTyped; + int keyPressedAR, keyReleasedAR, keyTypedAR; boolean pressed; + List<EventObject> queue = new ArrayList<EventObject>(); + boolean verbose = true; public NEWTKeyAdapter(String prefix) { this.prefix = prefix; reset(); } + + public void setVerbose(boolean v) { verbose = false; } public boolean isPressed() { return pressed; @@ -50,25 +60,67 @@ public class NEWTKeyAdapter extends KeyAdapter implements InputEventCountAdapter return keyTyped; } + public int getKeyPressedCount(boolean autoRepeatOnly) { + return autoRepeatOnly ? keyPressedAR: keyPressed; + } + + public int getKeyReleasedCount(boolean autoRepeatOnly) { + return autoRepeatOnly ? keyReleasedAR: keyReleased; + } + + public int getKeyTypedCount(boolean autoRepeatOnly) { + return autoRepeatOnly ? keyTypedAR: keyTyped; + } + + public List<EventObject> getQueued() { + return queue; + } + public void reset() { keyTyped = 0; + keyPressed = 0; + keyReleased = 0; + keyTypedAR = 0; + keyPressedAR = 0; + keyReleasedAR = 0; pressed = false; + queue.clear(); } public void keyPressed(KeyEvent e) { pressed = true; - System.err.println("NEWT AWT PRESSED ["+pressed+"]: "+prefix+", "+e); + keyPressed++; + if( 0 != ( e.getModifiers() & InputEvent.AUTOREPEAT_MASK ) ) { + keyPressedAR++; + } + queue.add(e); + if( verbose ) { + System.err.println("NEWT AWT PRESSED ["+pressed+"]: "+prefix+", "+e); + } } public void keyReleased(KeyEvent e) { pressed = false; - System.err.println("NEWT AWT RELEASED ["+pressed+"]: "+prefix+", "+e); + keyReleased++; + if( 0 != ( e.getModifiers() & InputEvent.AUTOREPEAT_MASK ) ) { + keyReleasedAR++; + } + queue.add(e); + if( verbose ) { + System.err.println("NEWT AWT RELEASED ["+pressed+"]: "+prefix+", "+e); + } } @Override public void keyTyped(KeyEvent e) { - ++keyTyped; - System.err.println("KEY NEWT TYPED ["+keyTyped+"]: "+prefix+", "+e); + keyTyped++; + if( 0 != ( e.getModifiers() & InputEvent.AUTOREPEAT_MASK ) ) { + keyTypedAR++; + } + queue.add(e); + if( verbose ) { + System.err.println("KEY NEWT TYPED ["+keyTyped+"]: "+prefix+", "+e); + } } public String toString() { return prefix+"[pressed "+pressed+", typed "+keyTyped+"]"; } diff --git a/src/test/com/jogamp/opengl/test/junit/util/NEWTKeyUtil.java b/src/test/com/jogamp/opengl/test/junit/util/NEWTKeyUtil.java new file mode 100644 index 000000000..dfc96edcb --- /dev/null +++ b/src/test/com/jogamp/opengl/test/junit/util/NEWTKeyUtil.java @@ -0,0 +1,103 @@ +/** + * Copyright 2012 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: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions 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. + * + * THIS SOFTWARE IS PROVIDED BY JogAmp Community ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JogAmp Community OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of JogAmp Community. + */ +package com.jogamp.opengl.test.junit.util; + +import java.util.EventObject; +import java.util.List; + +import org.junit.Assert; + +import com.jogamp.newt.event.KeyEvent; + +public class NEWTKeyUtil { + public static void dumpKeyEvents(List<EventObject> keyEvents) { + for(int i=0; i<keyEvents.size(); i++) { + System.err.println(i+": "+keyEvents.get(i)); + } + } + + public static int getNextKeyEventType(int et) { + switch( et ) { + case KeyEvent.EVENT_KEY_PRESSED: + return KeyEvent.EVENT_KEY_RELEASED; + case KeyEvent.EVENT_KEY_RELEASED: + return KeyEvent.EVENT_KEY_TYPED; + case KeyEvent.EVENT_KEY_TYPED: + return KeyEvent.EVENT_KEY_PRESSED; + default: + Assert.assertTrue("Invalid event type "+et, false); + return 0; + } + } + + public static void validateKeyEventOrder(List<EventObject> keyEvents) { + int eet = KeyEvent.EVENT_KEY_PRESSED; + for(int i=0; i<keyEvents.size(); i++) { + final KeyEvent e = (KeyEvent) keyEvents.get(i); + final int et = e.getEventType(); + Assert.assertEquals("Key event not in proper order", eet, et); + eet = getNextKeyEventType(et); + } + } + + /** + * + * @param keyAdapter + * @param expTotalCount number of physical key press/release, i.e. 1 shall result in 3 events (press, release and typed) + * @param expARCount as auto-release .. + */ + public static void validateKeyAdapterStats(NEWTKeyAdapter keyAdapter, int expTotalCount, int expARCount) { + final int keyPressed = keyAdapter.getKeyPressedCount(false); + final int keyPressedAR = keyAdapter.getKeyPressedCount(true); + final int keyReleased = keyAdapter.getKeyReleasedCount(false); + final int keyReleasedAR = keyAdapter.getKeyReleasedCount(true); + final int keyTyped = keyAdapter.getKeyTypedCount(false); + final int keyTypedAR = keyAdapter.getKeyTypedCount(true); + final int keyPressedNR = keyPressed-keyPressedAR; + final int keyReleasedNR = keyReleased-keyReleasedAR; + final int keyTypedNR = keyTyped-keyTypedAR; + System.err.println("Total Press "+keyPressed +", Release "+keyReleased +", Typed "+keyTyped); + System.err.println("AutoR Press "+keyPressedAR+", Release "+keyReleasedAR+", Typed "+keyTypedAR); + System.err.println("No AR Press "+keyPressedNR+", Release "+keyReleasedNR+", Typed "+keyTypedNR); + + final List<EventObject> keyEvents = keyAdapter.getQueued(); + Assert.assertEquals("Key event count not multiple of 3", 0, keyEvents.size()%3); + Assert.assertEquals("Key event count not 3 * press_release_count", 3*expTotalCount, keyEvents.size()); + Assert.assertEquals("Key press count failure", expTotalCount, keyPressed); + Assert.assertEquals("Key press count failure (AR)", expARCount, keyPressedAR); + Assert.assertEquals("Key released count failure", expTotalCount, keyReleased); + Assert.assertEquals("Key released count failure (AR)", expARCount, keyReleasedAR); + Assert.assertEquals("Key typed count failure", expTotalCount, keyTyped); + Assert.assertEquals("Key typed count failure (AR)", expARCount, keyTypedAR); + + // should be true - always, reaching this point - duh! + Assert.assertEquals(expTotalCount-expARCount, keyPressedNR); + Assert.assertEquals(expTotalCount-expARCount, keyReleasedNR); + Assert.assertEquals(expTotalCount-expARCount, keyTypedNR); + } +} diff --git a/src/test/com/jogamp/opengl/test/junit/util/NEWTMouseAdapter.java b/src/test/com/jogamp/opengl/test/junit/util/NEWTMouseAdapter.java index d98b9ca74..c77462884 100644 --- a/src/test/com/jogamp/opengl/test/junit/util/NEWTMouseAdapter.java +++ b/src/test/com/jogamp/opengl/test/junit/util/NEWTMouseAdapter.java @@ -28,6 +28,10 @@ package com.jogamp.opengl.test.junit.util; +import java.util.ArrayList; +import java.util.EventObject; +import java.util.List; + import com.jogamp.newt.event.MouseAdapter; import com.jogamp.newt.event.MouseEvent; @@ -36,12 +40,16 @@ public class NEWTMouseAdapter extends MouseAdapter implements InputEventCountAda String prefix; int mouseClicked; boolean pressed; + List<EventObject> queue = new ArrayList<EventObject>(); + boolean verbose = true; public NEWTMouseAdapter(String prefix) { this.prefix = prefix; reset(); } + public void setVerbose(boolean v) { verbose = false; } + public boolean isPressed() { return pressed; } @@ -50,24 +58,38 @@ public class NEWTMouseAdapter extends MouseAdapter implements InputEventCountAda return mouseClicked; } + public List<EventObject> getQueued() { + return queue; + } + public void reset() { mouseClicked = 0; pressed = false; + queue.clear(); } public void mousePressed(MouseEvent e) { pressed = true; - System.err.println("MOUSE NEWT PRESSED ["+pressed+"]: "+prefix+", "+e); + queue.add(e); + if( verbose ) { + System.err.println("MOUSE NEWT PRESSED ["+pressed+"]: "+prefix+", "+e); + } } public void mouseReleased(MouseEvent e) { pressed = false; - System.err.println("MOUSE NEWT RELEASED ["+pressed+"]: "+prefix+", "+e); + queue.add(e); + if( verbose ) { + System.err.println("MOUSE NEWT RELEASED ["+pressed+"]: "+prefix+", "+e); + } } public void mouseClicked(MouseEvent e) { mouseClicked+=e.getClickCount(); - System.err.println("MOUSE NEWT CLICKED ["+mouseClicked+"]: "+prefix+", "+e); + queue.add(e); + if( verbose ) { + System.err.println("MOUSE NEWT CLICKED ["+mouseClicked+"]: "+prefix+", "+e); + } } public String toString() { return prefix+"[pressed "+pressed+", clicked "+mouseClicked+"]"; } |