From cf9a4e236891ce2f6d9469a017e880eed704dea0 Mon Sep 17 00:00:00 2001
From: Sven Gothel <sgothel@jausoft.com>
Date: Sun, 28 Oct 2012 05:44:24 +0100
Subject: Fix NEWT KeyCode: Basic KeyCode Validation on X11, Windows and OSX

- X11: Add VK_QUOTE mapping

- OSX: Add single shift, ctrl alt key press;
       Fix mapping: Command -> Windows, Option -> ALT, add BACK_QUOTE and QUOTE.
---
 .../classes/com/jogamp/newt/event/KeyEvent.java    |  16 +-
 .../jogamp/newt/driver/macosx/MacKeyUtil.java      | 145 ++++++------
 .../jogamp/newt/driver/macosx/WindowDriver.java    |   6 +-
 src/newt/native/NewtMacWindow.h                    |   4 +
 src/newt/native/NewtMacWindow.m                    |  74 +++++-
 src/newt/native/X11Display.c                       |   2 +
 .../opengl/test/junit/newt/TestKeyCodeNEWT.java    | 256 +++++++++++++++++++++
 .../jogamp/opengl/test/junit/util/NEWTKeyUtil.java |  64 ++++++
 8 files changed, 468 insertions(+), 99 deletions(-)
 create mode 100644 src/test/com/jogamp/opengl/test/junit/newt/TestKeyCodeNEWT.java

(limited to 'src')

diff --git a/src/newt/classes/com/jogamp/newt/event/KeyEvent.java b/src/newt/classes/com/jogamp/newt/event/KeyEvent.java
index a8ac70b4d..8d3d9e88f 100644
--- a/src/newt/classes/com/jogamp/newt/event/KeyEvent.java
+++ b/src/newt/classes/com/jogamp/newt/event/KeyEvent.java
@@ -156,10 +156,10 @@ public class KeyEvent extends InputEvent
 
     /* Virtual key codes. */
 
-    public static final int VK_ENTER          = '\n';
-    public static final int VK_BACK_SPACE     = '\b';
-    public static final int VK_TAB            = '\t';
     public static final int VK_CANCEL         = 0x03;
+    public static final int VK_BACK_SPACE     = 0x08; // '\b'
+    public static final int VK_TAB            = 0x09; // '\t'
+    public static final int VK_ENTER          = 0x0A; // '\n'
     public static final int VK_CLEAR          = 0x0C;
     public static final int VK_SHIFT          = 0x10;
     public static final int VK_CONTROL        = 0x11;
@@ -296,18 +296,10 @@ public class KeyEvent extends InputEvent
     public static final int VK_MULTIPLY       = 0x6A;
     public static final int VK_ADD            = 0x6B;
 
-    /** 
-     * This constant is obsolete, and is included only for backwards
-     * compatibility.
-     * @see #VK_SEPARATOR
-     */
-    public static final int VK_SEPARATER      = 0x6C;
-
     /** 
      * Constant for the Numpad Separator key. 
-     * @since 1.4
      */
-    public static final int VK_SEPARATOR      = VK_SEPARATER;
+    public static final int VK_SEPARATOR      = 0x6C;
 
     public static final int VK_SUBTRACT       = 0x6D;
     public static final int VK_DECIMAL        = 0x6E;
diff --git a/src/newt/classes/jogamp/newt/driver/macosx/MacKeyUtil.java b/src/newt/classes/jogamp/newt/driver/macosx/MacKeyUtil.java
index d39e0027b..5966bd30f 100644
--- a/src/newt/classes/jogamp/newt/driver/macosx/MacKeyUtil.java
+++ b/src/newt/classes/jogamp/newt/driver/macosx/MacKeyUtil.java
@@ -162,13 +162,13 @@ public class MacKeyUtil {
             case kVK_Space:                return KeyEvent.VK_SPACE;
             case kVK_Delete:               return KeyEvent.VK_BACK_SPACE;
             case kVK_Escape:               return KeyEvent.VK_ESCAPE;
-            case kVK_Command:              return KeyEvent.VK_ALT;
+            case kVK_Command:              return KeyEvent.VK_WINDOWS;
             case kVK_Shift:                return KeyEvent.VK_SHIFT;
             case kVK_CapsLock:             return KeyEvent.VK_CAPS_LOCK;
-            case kVK_Option:               return KeyEvent.VK_WINDOWS;
+            case kVK_Option:               return KeyEvent.VK_ALT;
             case kVK_Control:              return KeyEvent.VK_CONTROL;
             case kVK_RightShift:           return KeyEvent.VK_SHIFT;
-            case kVK_RightOption:          return KeyEvent.VK_WINDOWS;
+            case kVK_RightOption:          return KeyEvent.VK_ALT;
             case kVK_RightControl:         return KeyEvent.VK_CONTROL;
             // case kVK_Function:             return KeyEvent.VK_F;
             case kVK_F17:                  return KeyEvent.VK_F17;
@@ -206,78 +206,73 @@ public class MacKeyUtil {
             case kVK_UpArrow:              return KeyEvent.VK_UP;
         }
         
-        if (keyChar == '\r') {
-            // Turn these into \n
-            return KeyEvent.VK_ENTER;
-        }
-
-        if (keyChar >= NSUpArrowFunctionKey && keyChar <= NSModeSwitchFunctionKey) {
-            switch (keyChar) {
-                case NSUpArrowFunctionKey:     return KeyEvent.VK_UP;
-                case NSDownArrowFunctionKey:   return KeyEvent.VK_DOWN;
-                case NSLeftArrowFunctionKey:   return KeyEvent.VK_LEFT;
-                case NSRightArrowFunctionKey:  return KeyEvent.VK_RIGHT;
-                case NSF1FunctionKey:          return KeyEvent.VK_F1;
-                case NSF2FunctionKey:          return KeyEvent.VK_F2;
-                case NSF3FunctionKey:          return KeyEvent.VK_F3;
-                case NSF4FunctionKey:          return KeyEvent.VK_F4;
-                case NSF5FunctionKey:          return KeyEvent.VK_F5;
-                case NSF6FunctionKey:          return KeyEvent.VK_F6;
-                case NSF7FunctionKey:          return KeyEvent.VK_F7;
-                case NSF8FunctionKey:          return KeyEvent.VK_F8;
-                case NSF9FunctionKey:          return KeyEvent.VK_F9;
-                case NSF10FunctionKey:         return KeyEvent.VK_F10;
-                case NSF11FunctionKey:         return KeyEvent.VK_F11;
-                case NSF12FunctionKey:         return KeyEvent.VK_F12;
-                case NSF13FunctionKey:         return KeyEvent.VK_F13;
-                case NSF14FunctionKey:         return KeyEvent.VK_F14;
-                case NSF15FunctionKey:         return KeyEvent.VK_F15;
-                case NSF16FunctionKey:         return KeyEvent.VK_F16;
-                case NSF17FunctionKey:         return KeyEvent.VK_F17;
-                case NSF18FunctionKey:         return KeyEvent.VK_F18;
-                case NSF19FunctionKey:         return KeyEvent.VK_F19;
-                case NSF20FunctionKey:         return KeyEvent.VK_F20;
-                case NSF21FunctionKey:         return KeyEvent.VK_F21;
-                case NSF22FunctionKey:         return KeyEvent.VK_F22;
-                case NSF23FunctionKey:         return KeyEvent.VK_F23;
-                case NSF24FunctionKey:         return KeyEvent.VK_F24;
-                case NSInsertFunctionKey:      return KeyEvent.VK_INSERT;
-                case NSDeleteFunctionKey:      return KeyEvent.VK_DELETE;
-                case NSHomeFunctionKey:        return KeyEvent.VK_HOME;
-                case NSBeginFunctionKey:       return KeyEvent.VK_BEGIN;
-                case NSEndFunctionKey:         return KeyEvent.VK_END;
-                case NSPageUpFunctionKey:      return KeyEvent.VK_PAGE_UP;
-                case NSPageDownFunctionKey:    return KeyEvent.VK_PAGE_DOWN;
-                case NSPrintScreenFunctionKey: return KeyEvent.VK_PRINTSCREEN;
-                case NSScrollLockFunctionKey:  return KeyEvent.VK_SCROLL_LOCK;
-                case NSPauseFunctionKey:       return KeyEvent.VK_PAUSE;
-                // Not handled:
-                // NSSysReqFunctionKey
-                // NSBreakFunctionKey
-                // NSResetFunctionKey
-                case NSStopFunctionKey:        return KeyEvent.VK_STOP;
-                // Not handled:
-                // NSMenuFunctionKey
-                // NSUserFunctionKey
-                // NSSystemFunctionKey
-                // NSPrintFunctionKey
-                // NSClearLineFunctionKey
-                // NSClearDisplayFunctionKey
-                // NSInsertLineFunctionKey
-                // NSDeleteLineFunctionKey
-                // NSInsertCharFunctionKey
-                // NSDeleteCharFunctionKey
-                // NSPrevFunctionKey
-                // NSNextFunctionKey
-                // NSSelectFunctionKey
-                // NSExecuteFunctionKey
-                // NSUndoFunctionKey
-                // NSRedoFunctionKey
-                // NSFindFunctionKey
-                // NSHelpFunctionKey
-                // NSModeSwitchFunctionKey
-                default: break;
-            }
+        switch (keyChar) {
+            case NSUpArrowFunctionKey:     return KeyEvent.VK_UP;
+            case NSDownArrowFunctionKey:   return KeyEvent.VK_DOWN;
+            case NSLeftArrowFunctionKey:   return KeyEvent.VK_LEFT;
+            case NSRightArrowFunctionKey:  return KeyEvent.VK_RIGHT;
+            case NSF1FunctionKey:          return KeyEvent.VK_F1;
+            case NSF2FunctionKey:          return KeyEvent.VK_F2;
+            case NSF3FunctionKey:          return KeyEvent.VK_F3;
+            case NSF4FunctionKey:          return KeyEvent.VK_F4;
+            case NSF5FunctionKey:          return KeyEvent.VK_F5;
+            case NSF6FunctionKey:          return KeyEvent.VK_F6;
+            case NSF7FunctionKey:          return KeyEvent.VK_F7;
+            case NSF8FunctionKey:          return KeyEvent.VK_F8;
+            case NSF9FunctionKey:          return KeyEvent.VK_F9;
+            case NSF10FunctionKey:         return KeyEvent.VK_F10;
+            case NSF11FunctionKey:         return KeyEvent.VK_F11;
+            case NSF12FunctionKey:         return KeyEvent.VK_F12;
+            case NSF13FunctionKey:         return KeyEvent.VK_F13;
+            case NSF14FunctionKey:         return KeyEvent.VK_F14;
+            case NSF15FunctionKey:         return KeyEvent.VK_F15;
+            case NSF16FunctionKey:         return KeyEvent.VK_F16;
+            case NSF17FunctionKey:         return KeyEvent.VK_F17;
+            case NSF18FunctionKey:         return KeyEvent.VK_F18;
+            case NSF19FunctionKey:         return KeyEvent.VK_F19;
+            case NSF20FunctionKey:         return KeyEvent.VK_F20;
+            case NSF21FunctionKey:         return KeyEvent.VK_F21;
+            case NSF22FunctionKey:         return KeyEvent.VK_F22;
+            case NSF23FunctionKey:         return KeyEvent.VK_F23;
+            case NSF24FunctionKey:         return KeyEvent.VK_F24;
+            case NSInsertFunctionKey:      return KeyEvent.VK_INSERT;
+            case NSDeleteFunctionKey:      return KeyEvent.VK_DELETE;
+            case NSHomeFunctionKey:        return KeyEvent.VK_HOME;
+            case NSBeginFunctionKey:       return KeyEvent.VK_BEGIN;
+            case NSEndFunctionKey:         return KeyEvent.VK_END;
+            case NSPageUpFunctionKey:      return KeyEvent.VK_PAGE_UP;
+            case NSPageDownFunctionKey:    return KeyEvent.VK_PAGE_DOWN;
+            case NSPrintScreenFunctionKey: return KeyEvent.VK_PRINTSCREEN;
+            case NSScrollLockFunctionKey:  return KeyEvent.VK_SCROLL_LOCK;
+            case NSPauseFunctionKey:       return KeyEvent.VK_PAUSE;
+            // Not handled:
+            // NSSysReqFunctionKey
+            // NSBreakFunctionKey
+            // NSResetFunctionKey
+            case NSStopFunctionKey:        return KeyEvent.VK_STOP;
+            // Not handled:
+            // NSMenuFunctionKey
+            // NSUserFunctionKey
+            // NSSystemFunctionKey
+            // NSPrintFunctionKey
+            // NSClearLineFunctionKey
+            // NSClearDisplayFunctionKey
+            // NSInsertLineFunctionKey
+            // NSDeleteLineFunctionKey
+            // NSInsertCharFunctionKey
+            // NSDeleteCharFunctionKey
+            // NSPrevFunctionKey
+            // NSNextFunctionKey
+            // NSSelectFunctionKey
+            // NSExecuteFunctionKey
+            // NSUndoFunctionKey
+            // NSRedoFunctionKey
+            // NSFindFunctionKey
+            // NSHelpFunctionKey
+            // NSModeSwitchFunctionKey
+            case 0x60: return KeyEvent.VK_BACK_QUOTE; // `
+            case 0x27: return KeyEvent.VK_QUOTE;      // '          
+            case '\r': return KeyEvent.VK_ENTER;
         }
 
         if ('a' <= keyChar && keyChar <= 'z') {
diff --git a/src/newt/classes/jogamp/newt/driver/macosx/WindowDriver.java b/src/newt/classes/jogamp/newt/driver/macosx/WindowDriver.java
index e0ea8ea76..bc83f9540 100644
--- a/src/newt/classes/jogamp/newt/driver/macosx/WindowDriver.java
+++ b/src/newt/classes/jogamp/newt/driver/macosx/WindowDriver.java
@@ -314,12 +314,12 @@ public class WindowDriver extends WindowImpl implements MutableSurface, DriverCl
         }
     }
 
-    private final void handleKeyEvent(boolean send, boolean wait, int eventType, int modifiers, int keyCode, char keyChar) {
+    private final void handleKeyEvent(boolean send, boolean wait, int eventType, int modifiers, int _keyCode, char keyChar) {
         // Note that we send the key char for the key code on this
         // platform -- we do not get any useful key codes out of the system
-        keyCode = MacKeyUtil.validateKeyCode(keyCode, keyChar);
+        final int keyCode = MacKeyUtil.validateKeyCode(_keyCode, keyChar);
         if(DEBUG_IMPLEMENTATION) {
-            System.err.println("MacWindow.sendKeyEvent "+Thread.currentThread().getName()+" char: 0x"+Integer.toHexString(keyChar)+", code 0x"+Integer.toHexString(keyCode)+" -> 0x"+Integer.toHexString(keyCode));
+            System.err.println("MacWindow.sendKeyEvent "+Thread.currentThread().getName()+" char: 0x"+Integer.toHexString(keyChar)+", code 0x"+Integer.toHexString(_keyCode)+" -> 0x"+Integer.toHexString(keyCode));
         }
         // Auto-Repeat: OSX delivers only PRESSED, inject auto-repeat RELEASE and TYPED keys _before_ PRESSED
         switch(eventType) {
diff --git a/src/newt/native/NewtMacWindow.h b/src/newt/native/NewtMacWindow.h
index c0912ad3c..29b646fbf 100644
--- a/src/newt/native/NewtMacWindow.h
+++ b/src/newt/native/NewtMacWindow.h
@@ -111,6 +111,7 @@
     BOOL mouseInside;
     BOOL cursorIsHidden;
     BOOL realized;
+    BOOL modsDown[4]; // shift, ctrl, alt/option, win/command
     NSPoint lastInsideMousePosition;
 @public
     int cachedInsets[4]; // l, r, t, b
@@ -145,6 +146,7 @@
 - (void) setMousePosition:(NSPoint)p;
 
 - (void) sendKeyEvent: (NSEvent*) event eventType: (jint) evType;
+- (void) sendKeyEvent: (jint) keyCode characters: (NSString*) chars modifiers: (NSUInteger)mods eventType: (jint) evType;
 - (void) sendMouseEvent: (NSEvent*) event eventType: (jint) evType;
 - (void) focusChanged: (BOOL) gained;
 
@@ -157,6 +159,8 @@
 - (void) windowDidResignKey: (NSNotification *) notification;
 - (void) keyDown: (NSEvent*) theEvent;
 - (void) keyUp: (NSEvent*) theEvent;
+- (void) handleFlagsChanged:(int) keyMask keyIndex: (int) keyIdx keyCode: (int) keyCode modifiers: (NSUInteger) mods;
+- (void) flagsChanged: (NSEvent *) theEvent;
 - (void) mouseEntered: (NSEvent*) theEvent;
 - (void) mouseExited: (NSEvent*) theEvent;
 - (void) mouseMoved: (NSEvent*) theEvent;
diff --git a/src/newt/native/NewtMacWindow.m b/src/newt/native/NewtMacWindow.m
index b58b99e38..de5f3773c 100644
--- a/src/newt/native/NewtMacWindow.m
+++ b/src/newt/native/NewtMacWindow.m
@@ -368,6 +368,10 @@ static jmethodID windowRepaintID = NULL;
     cachedInsets[1] = 0; // r
     cachedInsets[2] = 0; // t
     cachedInsets[3] = 0; // b
+    modsDown[0] = NO; // shift
+    modsDown[1] = NO; // ctrl
+    modsDown[2] = NO; // alt
+    modsDown[3] = NO; // win
     mouseConfined = NO;
     mouseVisible = YES;
     mouseInside = NO;
@@ -596,6 +600,14 @@ static jint mods2JavaMods(NSUInteger mods)
 }
 
 - (void) sendKeyEvent: (NSEvent*) event eventType: (jint) evType
+{
+    jint keyCode = (jint) [event keyCode];
+    NSString* chars = [event charactersIgnoringModifiers];
+    NSUInteger mods = [event modifierFlags];
+    [self sendKeyEvent: keyCode characters: chars modifiers: mods eventType: evType];
+}
+
+- (void) sendKeyEvent: (jint) keyCode characters: (NSString*) chars modifiers: (NSUInteger)mods eventType: (jint) evType
 {
     NSView* nsview = [self contentView];
     if( ! [nsview isMemberOfClass:[NewtView class]] ) {
@@ -616,16 +628,30 @@ static jint mods2JavaMods(NSUInteger mods)
     }
 
     int i;
-    jint keyCode = (jint) [event keyCode];
-    NSString* chars = [event charactersIgnoringModifiers];
-    int len = [chars length];
-    jint javaMods = mods2JavaMods([event modifierFlags]);
-
-    for (i = 0; i < len; i++) {
-        // Note: the key code in the NSEvent does not map to anything we can use
-        jchar keyChar = (jchar) [chars characterAtIndex: i];
+    int len = NULL != chars ? [chars length] : 0;
+    jint javaMods = mods2JavaMods(mods);
+
+    if(len > 0) {
+        // printable chars
+        for (i = 0; i < len; i++) {
+            // Note: the key code in the NSEvent does not map to anything we can use
+            jchar keyChar = (jchar) [chars characterAtIndex: i];
+
+            DBG_PRINT("sendKeyEvent: %d/%d char 0x%X, code 0x%X\n", i, len, (int)keyChar, (int)keyCode);
+
+            #ifdef USE_SENDIO_DIRECT
+            (*env)->CallVoidMethod(env, javaWindowObject, sendKeyEventID,
+                                   evType, javaMods, keyCode, keyChar);
+            #else
+            (*env)->CallVoidMethod(env, javaWindowObject, enqueueKeyEventID, JNI_FALSE,
+                                   evType, javaMods, keyCode, keyChar);
+            #endif
+        }
+    } else {
+        // non-printable chars
+        jchar keyChar = (jchar) -1;
 
-        DBG_PRINT("sendKeyEvent: %d/%d char 0x%X, code 0x%X\n", i, len, (int)keyChar, (int)keyCode);
+        DBG_PRINT("sendKeyEvent: code 0x%X\n", (int)keyCode);
 
         #ifdef USE_SENDIO_DIRECT
         (*env)->CallVoidMethod(env, javaWindowObject, sendKeyEventID,
@@ -805,6 +831,36 @@ static jint mods2JavaMods(NSUInteger mods)
     [self sendKeyEvent: theEvent eventType: EVENT_KEY_TYPED];
 }
 
+#define kVK_Shift     0x38
+#define kVK_Option    0x3A
+#define kVK_Control   0x3B
+#define kVK_Command   0x37
+
+- (void) handleFlagsChanged:(int) keyMask keyIndex: (int) keyIdx keyCode: (int) keyCode modifiers: (NSUInteger) mods
+{
+    if ( NO == modsDown[keyIdx] && 0 != ( mods & keyMask ) )  {
+        modsDown[keyIdx] = YES;
+        mods &= ~keyMask;
+        [self sendKeyEvent: keyCode characters: NULL modifiers: mods eventType: EVENT_KEY_TYPED];
+    } else if ( YES == modsDown[keyIdx] && 0 == ( mods & keyMask ) )  {
+        modsDown[keyIdx] = NO;
+        [self sendKeyEvent: keyCode characters: NULL modifiers: mods eventType: EVENT_KEY_RELEASED];
+        [self sendKeyEvent: keyCode characters: NULL modifiers: mods eventType: EVENT_KEY_TYPED];
+    }
+}
+
+- (void) flagsChanged:(NSEvent *) theEvent
+{
+    NSUInteger mods = [theEvent modifierFlags];
+
+    // BOOL modsDown[4]; // shift, ctrl, alt/option, win/command
+
+    [self handleFlagsChanged: NSShiftKeyMask keyIndex: 0 keyCode: kVK_Shift modifiers: mods];
+    [self handleFlagsChanged: NSControlKeyMask keyIndex: 1 keyCode: kVK_Control modifiers: mods];
+    [self handleFlagsChanged: NSAlternateKeyMask keyIndex: 2 keyCode: kVK_Option modifiers: mods];
+    [self handleFlagsChanged: NSCommandKeyMask keyIndex: 3 keyCode: kVK_Command modifiers: mods];
+}
+
 - (void) mouseEntered: (NSEvent*) theEvent
 {
     DBG_PRINT( "mouseEntered: confined %d, visible %d\n", mouseConfined, mouseVisible);
diff --git a/src/newt/native/X11Display.c b/src/newt/native/X11Display.c
index 9f29acc0c..341455f0f 100644
--- a/src/newt/native/X11Display.c
+++ b/src/newt/native/X11Display.c
@@ -148,6 +148,8 @@ static jint X11KeySym2NewtVKey(KeySym keySym) {
             return J_VK_HELP;
         case XK_grave:
             return J_VK_BACK_QUOTE;
+        case XK_apostrophe:
+            return J_VK_QUOTE;
     }
     return keySym;
 }
diff --git a/src/test/com/jogamp/opengl/test/junit/newt/TestKeyCodeNEWT.java b/src/test/com/jogamp/opengl/test/junit/newt/TestKeyCodeNEWT.java
new file mode 100644
index 000000000..8af0f0246
--- /dev/null
+++ b/src/test/com/jogamp/opengl/test/junit/newt/TestKeyCodeNEWT.java
@@ -0,0 +1,256 @@
+/**
+ * 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.ArrayList;
+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.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.*;
+import com.jogamp.opengl.test.junit.util.NEWTKeyUtil.CodeSeg;
+
+/**
+ * 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 TestKeyCodeNEWT 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 CodeSeg[] codeSegments = new CodeSeg[] {
+      new CodeSeg(0x008, 0x00a, "bs, tab, cr"),
+      new CodeSeg(0x010, 0x011, "shift, ctrl"), // single alt n/a on windows
+      new CodeSeg(0x01B, 0x01B, "esc"),
+      new CodeSeg(0x020, 0x024, "space, up, down, end, home"),
+      new CodeSeg(0x025, 0x028, "cursor"),
+      new CodeSeg(0x02C, 0x02F, ", - . /"),
+      new CodeSeg(0x030, 0x039, "0 - 9"),
+      new CodeSeg(0x03B, 0x03B, ";"),
+      new CodeSeg(0x03D, 0x03D, "="),
+      new CodeSeg(0x041, 0x05A, "a - z"),
+      new CodeSeg(0x05B, 0x05D, "[ \\ ]"),
+      // new CodeSeg(0x060, 0x06B, "numpad1"), // can be mapped to normal keycodes
+      // new CodeSeg(0x06D, 0x06F, "numpad2"), // can be mapped to normal keycodes
+      new CodeSeg(0x07F, 0x07F, "del"),
+      // new CodeSeg(0x090, 0x091, "num lock, scroll lock"),
+      // new CodeSeg(0x070, 0x07B, "F1 - F12"),
+      // new CodeSeg(0x09A, 0x09D, "prt ins hlp meta"),
+      new CodeSeg(0x0C0, 0x0C0, "back quote"),
+      new CodeSeg(0x0DE, 0x0DE, "quote"),
+      // new CodeSeg(0x0E0, 0x0E3, "cursor kp"),
+      // new CodeSeg(0x080, 0x08F, "dead-1"),
+      // new CodeSeg(0x096, 0x0A2, "& ^ \" < > { }"), 
+      // new CodeSeg(0x200, 0x20D, "extra-2"), // @ ; ..
+    };
+    
+    static void testKeyCode(Robot robot, NEWTKeyAdapter keyAdapter) {
+        List<List<EventObject>> cse = new ArrayList<List<EventObject>>();
+        
+        for(int i=0; i<codeSegments.length; i++) {
+            keyAdapter.reset();
+            final CodeSeg codeSeg = codeSegments[i];
+            // System.err.println("*** Segment "+codeSeg.description);
+            for(int c=codeSeg.min; c<=codeSeg.max; c++) {
+                // System.err.println("*** KeyCode 0x"+Integer.toHexString(c));
+                AWTRobotUtil.keyPress(0, robot, true, c, 10);
+                AWTRobotUtil.keyPress(0, robot, false, c, 10);
+                robot.waitForIdle();
+            }
+            final int codeCount = codeSeg.max - codeSeg.min + 1;
+            final List<EventObject> queue = keyAdapter.getQueued();
+            for(int j=0; j < 10 && queue.size() < 3 * codeCount; j++) { // wait until events are collected
+                robot.delay(100);
+            }
+            final ArrayList<EventObject> events = new ArrayList<EventObject>(queue);
+            cse.add(events);
+        }
+        Assert.assertEquals("KeyCode impl. incomplete", true, NEWTKeyUtil.validateKeyCode(codeSegments, cse, true));        
+    }
+        
+    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
+        //
+        testKeyCode(robot, glWindow1KA);
+        
+        // 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 = TestKeyCodeNEWT.class.getName();
+        org.junit.runner.JUnitCore.main(tstname);
+    }
+
+
+}
diff --git a/src/test/com/jogamp/opengl/test/junit/util/NEWTKeyUtil.java b/src/test/com/jogamp/opengl/test/junit/util/NEWTKeyUtil.java
index 299ea3a6e..95818845e 100644
--- a/src/test/com/jogamp/opengl/test/junit/util/NEWTKeyUtil.java
+++ b/src/test/com/jogamp/opengl/test/junit/util/NEWTKeyUtil.java
@@ -27,6 +27,7 @@
  */
 package com.jogamp.opengl.test.junit.util;
 
+import java.util.ArrayList;
 import java.util.EventObject;
 import java.util.List;
 
@@ -36,12 +37,75 @@ import com.jogamp.common.util.IntIntHashMap;
 import com.jogamp.newt.event.KeyEvent;
 
 public class NEWTKeyUtil {
+    public static class CodeSeg {
+        public final int min;
+        public final int max;
+        public final String description;
+        
+        public CodeSeg(int min, int max, String description) {
+            this.min = min;
+            this.max = max;
+            this.description = description;
+        }
+    }
+    public static class CodeEvent {
+        public final int code;
+        public final String description;
+        public final KeyEvent event;
+        
+        public CodeEvent(int code, String description, KeyEvent event) {
+            this.code = code;
+            this.description = description;
+            this.event = event;
+        }
+        public String toString() {
+            return "Code 0x"+Integer.toHexString(code)+" != "+event+" // "+description;
+        }
+    }
+    
     public static void dumpKeyEvents(List<EventObject> keyEvents) {
         for(int i=0; i<keyEvents.size(); i++) {
             System.err.println(i+": "+keyEvents.get(i));
         }        
     }
     
+    public static boolean validateKeyCode(CodeSeg[] codeSegments, List<List<EventObject>> keyEventsList, boolean verbose) {
+        final List<CodeEvent> missCodes = new ArrayList<CodeEvent>();
+        int totalCodeCount = 0;
+        boolean res = true;
+        for(int i=0; i<codeSegments.length; i++) {
+            final CodeSeg codeSeg = codeSegments[i];
+            totalCodeCount += codeSeg.max - codeSeg.min + 1;
+            final List<EventObject> keyEvents = keyEventsList.get(i);
+            res &= validateKeyCode(missCodes, codeSeg, keyEvents, verbose);
+        }        
+        if(verbose) {
+            System.err.println("*** Total KeyCode Misses "+missCodes.size()+" / "+totalCodeCount+", valid "+res);
+            for(int i=0; i<missCodes.size(); i++) {
+                System.err.println("Miss["+i+"]: "+missCodes.get(i));
+            }
+        }
+        return res;
+    }
+    public static boolean validateKeyCode(List<CodeEvent> missCodes, CodeSeg codeSeg, List<EventObject> keyEvents, boolean verbose) {
+        final int codeCount = codeSeg.max - codeSeg.min + 1;
+        int misses = 0;
+        for(int i=0; i<codeCount; i++) {
+            final int c = codeSeg.min + i;
+            final int j = i*3+0; // KEY_PRESSED
+            final KeyEvent e = (KeyEvent) ( j < keyEvents.size() ? keyEvents.get(j) : null );
+            if( null == e || c != e.getKeyCode() ) {
+                missCodes.add(new CodeEvent(c, codeSeg.description, e));
+                misses++;
+            }
+        }
+        final boolean res = 3*codeCount == keyEvents.size() && 0 == missCodes.size();
+        if(verbose) {
+            System.err.println("+++ Code Segment "+codeSeg.description+", Misses: "+misses+" / "+codeCount+", events "+keyEvents.size()+", valid "+res);
+        }
+        return res;
+    }
+    
     public static int getNextKeyEventType(int et) {
         switch( et ) {
             case KeyEvent.EVENT_KEY_PRESSED:
-- 
cgit v1.2.3