IDEA-165950 [TEST] Added new regression test (National keyboard layouts support)

(cherry picked from commit 0900a705bc)

with fix for JBR-5300 Change source code and test files to use GPL license
This commit is contained in:
Elena Sayapina
2019-07-18 15:57:15 +07:00
committed by jbrbot
parent 25c263f742
commit c516490a5a
12 changed files with 1462 additions and 0 deletions

View File

@@ -0,0 +1,173 @@
/*
* Copyright 2000-2023 JetBrains s.r.o.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
import java.awt.event.KeyEvent;
import java.util.HashMap;
/*
* Class containing common key functionality
*/
public class Key {
// KeyEvent.VK_ constant name corresponding to the key on US keyboard layout
private final String vkName;
// KeyChars mapped to the key on the current keyboard layout
private final MappedKeyChars mappedKeyChars;
Key(String vkName, MappedKeyChars mappedKeyChars) {
this.vkName = vkName;
this.mappedKeyChars = mappedKeyChars;
}
// Returns the virtual key code for US keyboard layout.
// Robot only knows US keyboard layout.
// So to press some key on the current layout, one needs to pass corresponding US layout key code to Robot.
// So every key stores corresponding KeyEvent.VK_ constant name to provide mapping to US layout.
int getKeyCode_US() {
try {
return KeyEvent.class.getField(vkName).getInt(null);
} catch (IllegalAccessException | NoSuchFieldException e) {
throw new RuntimeException(e);
}
};
// Returns key code for the current layout.
// Key that generates VK_ code when using a US keyboard layout also generates a unique code for other layout.
// Test firstly determines char mapped to the key on the current layout
// and then uses KeyEvent.getExtendedKeyCodeForChar(c) to get the key code.
int getKeyCode() {
KeyChar keyChar = mappedKeyChars.getKeyChar();
char ch = keyChar.getChar();
if (latinKeyCodesMap.containsKey(ch)) {
// TODO Fix this in jbruntime
// KeyEvent.getExtendedKeyCodeForChar(ch) does not return corresponding VK_ constant for non-English keys
return latinKeyCodesMap.get(ch);
} else if (keyChar.isDead() && deadKeyCodesMap.containsKey(ch)) {
// KeyEvent.getExtendedKeyCodeForChar(ch) does not return corresponding VK_ constant for dead keys
return deadKeyCodesMap.get(ch);
} else {
return KeyEvent.getExtendedKeyCodeForChar(ch);
}
}
// Returns key char for the current layout
public char getChar(Modifier modifier) {
return mappedKeyChars.getKeyChar(modifier).getChar();
}
// Checks if no char is mapped to the modifier + key
public boolean isCharNull(Modifier modifier) {
return (mappedKeyChars.getKeyChar(modifier).getChar() == Character.MIN_VALUE);
}
// Checks if modifier + key is a shortcut
public boolean isCharUndefined(Modifier modifier) {
return (mappedKeyChars.getKeyChar(modifier).getChar() == Character.MAX_VALUE);
}
// Checks if key is a dead key, no modifiers
public boolean isDead() {
return mappedKeyChars.getKeyChar().isDead();
}
// Checks if modifier + key is a dead key, always false for shortcut
public boolean isDead(Modifier modifier) {
return mappedKeyChars.getKeyChar(modifier).isDead();
}
// TODO Remove this map when KeyEvent.getExtendedKeyCodeForChar(ch) is fixed for latin keys in jbruntime
// Map storing latin chars and corresponding VK_ codes
private static final HashMap<Character, Integer> latinKeyCodesMap = new HashMap<Character, Integer>() {
{
// Please see:
// jbruntime/src/java.desktop/share/classes/java/awt/event/KeyEvent.java
put((char) 0x00E0, KeyEvent.VK_A_WITH_GRAVE);
put((char) 0x00E1, KeyEvent.VK_A_WITH_ACUTE);
put((char) 0x00E2, KeyEvent.VK_A_WITH_CIRCUMFLEX);
put((char) 0x00E3, KeyEvent.VK_A_WITH_TILDE);
put((char) 0x00E4, KeyEvent.VK_A_WITH_DIAERESIS);
put((char) 0x00E5, KeyEvent.VK_A_WITH_RING_ABOVE);
put((char) 0x00E6, KeyEvent.VK_AE);
put((char) 0x00E7, KeyEvent.VK_C_WITH_CEDILLA);
put((char) 0x00E8, KeyEvent.VK_E_WITH_GRAVE);
put((char) 0x00E9, KeyEvent.VK_E_WITH_ACUTE);
put((char) 0x00EA, KeyEvent.VK_E_WITH_CIRCUMFLEX);
put((char) 0x00EB, KeyEvent.VK_E_WITH_DIAERESIS);
put((char) 0x00EC, KeyEvent.VK_I_WITH_GRAVE);
put((char) 0x00ED, KeyEvent.VK_I_WITH_GRAVE);
put((char) 0x00EE, KeyEvent.VK_I_WITH_CIRCUMFLEX);
put((char) 0x00EF, KeyEvent.VK_I_WITH_DIAERESIS);
put((char) 0x00F0, KeyEvent.VK_ETH);
put((char) 0x00F1, KeyEvent.VK_N_WITH_TILDE);
put((char) 0x00F2, KeyEvent.VK_O_WITH_GRAVE);
put((char) 0x00F3, KeyEvent.VK_O_WITH_ACUTE);
put((char) 0x00F4, KeyEvent.VK_O_WITH_CIRCUMFLEX);
put((char) 0x00F5, KeyEvent.VK_O_WITH_TILDE);
put((char) 0x00F6, KeyEvent.VK_O_WITH_DIAERESIS);
put((char) 0x00F7, KeyEvent.VK_DIVISION_SIGN);
put((char) 0x00F8, KeyEvent.VK_O_WITH_SLASH);
put((char) 0x00F9, KeyEvent.VK_U_WITH_GRAVE);
put((char) 0x00FA, KeyEvent.VK_U_WITH_ACUTE);
put((char) 0x00FB, KeyEvent.VK_U_WITH_CIRCUMFLEX);
put((char) 0x00FC, KeyEvent.VK_U_WITH_DIAERESIS);
put((char) 0x00FD, KeyEvent.VK_Y_WITH_ACUTE);
put((char) 0x00FE, KeyEvent.VK_THORN);
put((char) 0x00FF, KeyEvent.VK_Y_WITH_DIAERESIS);
}
};
// Map storing possible dead key chars and corresponding VK_ codes
private static final HashMap<Character, Integer> deadKeyCodesMap = new HashMap<Character, Integer>() {
{
// Please see:
// jbruntime/src/java.desktop/windows/native/libawt/windows/awt_Component.cpp
// jbruntime/src/java.desktop/macosx/native/libawt_lwawt/awt/AWTEvent.m
put((char) 0x02D9, KeyEvent.VK_DEAD_ABOVEDOT);
put((char) 0x02DA, KeyEvent.VK_DEAD_ABOVERING);
put((char) 0x00B4, KeyEvent.VK_DEAD_ACUTE); // ACUTE ACCENT
put((char) 0x0384, KeyEvent.VK_DEAD_ACUTE); // GREEK TONOS
// TODO Should test map ' to acute as sometimes it may add either acute or cedilla to the next key
//put((char) 0x0027 /* ' */, KeyEvent.VK_DEAD_ACUTE); // APOSTROPHE
put((char) 0x02D8, KeyEvent.VK_DEAD_BREVE);
put((char) 0x02C7, KeyEvent.VK_DEAD_CARON);
put((char) 0x00B8, KeyEvent.VK_DEAD_CEDILLA); // CEDILLA
put((char) 0x002C /* , */, KeyEvent.VK_DEAD_CEDILLA); // COMMA
put((char) 0x02C6, KeyEvent.VK_DEAD_CIRCUMFLEX); // MODIFIER LETTER CIRCUMFLEX ACCENT
put((char) 0x005E, KeyEvent.VK_DEAD_CIRCUMFLEX); // CIRCUMFLEX ACCENT
put((char) 0x00A8, KeyEvent.VK_DEAD_DIAERESIS); // DIAERESIS
put((char) 0x0022 /* " */, KeyEvent.VK_DEAD_DIAERESIS); // QUOTATION MARK
put((char) 0x02DD, KeyEvent.VK_DEAD_DOUBLEACUTE);
put((char) 0x0060, KeyEvent.VK_DEAD_GRAVE);
put((char) 0x037A, KeyEvent.VK_DEAD_IOTA);
put((char) 0x02C9, KeyEvent.VK_DEAD_MACRON); // MODIFIER LETTER MACRON
put((char) 0x00AF, KeyEvent.VK_DEAD_MACRON); // MACRON
put((char) 0x02DB, KeyEvent.VK_DEAD_OGONEK);
put((char) 0x02DC, KeyEvent.VK_DEAD_TILDE); // SMALL TILDE
put((char) 0x007E, KeyEvent.VK_DEAD_TILDE); // TILDE
put((char) 0x309B, KeyEvent.VK_DEAD_VOICED_SOUND);
put((char) 0x309C, KeyEvent.VK_DEAD_SEMIVOICED_SOUND);
}
};
}

View File

@@ -0,0 +1,65 @@
/*
* Copyright 2000-2023 JetBrains s.r.o.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
/*
* Class describing a key char
*/
public class KeyChar {
// Char value
private final char ch;
// Dead or not
private final boolean isDead;
// Private constructor for an ordinal key char
private KeyChar(char ch) {
this.ch = ch;
this.isDead = false;
}
// Private constructor for a dead key char
private KeyChar(char ch, boolean isDead) {
this.ch = ch;
this.isDead = isDead;
}
// Helper method for an ordinal key char creation
static KeyChar ch(char ch) {
return new KeyChar(ch);
}
// Helper method for a dead key char creation
static KeyChar dead(char ch) {
return new KeyChar(ch, true);
}
// Returns dead marker
boolean isDead() {
return isDead;
}
// Returns char value
char getChar() {
return ch;
}
}

View File

@@ -0,0 +1,56 @@
/*
* Copyright 2000-2023 JetBrains s.r.o.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
/*
* Enumerates macOS keyboard layouts covered by this test
*/
public enum Layout {
ABC ("ABC", Layout_ABC.values()),
US_INTERNATIONAL_PC ("USInternational-PC", Layout_US_INTERNATIONAL_PC.values()),
SPANISH_ISO ("Spanish-ISO", Layout_SPANISH_ISO.values()),
FRENCH_PC ("French-PC", Layout_FRENCH_PC.values()),
GERMAN ("German", Layout_GERMAN.values()),
;
// Real macOS keyboard layout name without "com.apple.keylayout." prefix
private String name;
// Array of test keys for the layout
private LayoutKey[] layoutKeys;
Layout(String name, LayoutKey[] layoutKeys) {
this.name = name;
this.layoutKeys = layoutKeys;
}
// Return array of test keys for the layout
LayoutKey[] getLayoutKeys() {
return layoutKeys;
}
@Override
public String toString() {
return "com.apple.keylayout." + name;
}
}

View File

@@ -0,0 +1,33 @@
/*
* Copyright 2000-2023 JetBrains s.r.o.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
/*
* Common interface for all test layouts.
* Keyboard layouts are declared as a separate enums to allow testing of different key sets for different layouts.
* As layouts are declared as enums, they cannot extend any other class, but need to have some common functionality.
*/
public interface LayoutKey {
// Return Key object containing common key functionality
Key getKey();
}

View File

@@ -0,0 +1,120 @@
/*
* Copyright 2000-2023 JetBrains s.r.o.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
/**
* @test
* @summary Regression test for IDEA-165950: National keyboard layouts support
* @requires (jdk.version.major >= 8 & os.family == "mac")
* @modules java.desktop/sun.lwawt.macosx
* @run main NationalLayoutTest ABC
*/
/*
* Enumerates keys under test for com.apple.keylayout.ABC (macOS 10.14.5)
*/
public enum Layout_ABC implements LayoutKey {
// Enum name must be the same as KeyEvent.VK_ constant name corresponding to the key on US keyboard layout
// Note that '\u0000' may be used if no char is mapped to a key + modifier or if one wants to skip its testing
// Robot cannot press section sign key (16777383),
// located on the left side of the key 1 on the Apple International English keyboard
// SECTION ('§', '§', '±', '±'),
VK_MINUS ('-', '', '_', '—'),
VK_EQUALS ('=', '≠', '+', '±'),
VK_OPEN_BRACKET ('[', '“', '{', '”'),
VK_CLOSE_BRACKET (']', '', '}', ''),
VK_SEMICOLON (';', '…', ':', 'Ú'),
VK_QUOTE ('\'', 'æ', '"', 'Æ'),
VK_BACK_SLASH ('\\', '«', '|', '»'),
VK_BACK_QUOTE (KeyChar.ch('`'), KeyChar.dead('`'), KeyChar.ch('~'), KeyChar.ch('`')),
VK_COMMA (',', '≤', '<', '¯'),
VK_PERIOD ('.', '≥', '>', '˘'),
VK_SLASH ('/', '÷', '?', '¿'),
//VK_1 ('1', '¡', '!', ''),
//VK_2 ('2', '™', '@', '€'),
//VK_3 ('3', '£', '#', ''),
//VK_4 ('4', '¢', '$', ''),
//VK_5 ('5', '∞', '%', 'fi'),
//VK_6 ('6', '§', '^', 'fl'),
//VK_7 ('7', '¶', '&', '‡'),
//VK_8 ('8', '•', '*', '°'),
//VK_9 ('9', 'ª', '(', '·'),
//VK_0 ('0', 'º', ')', ''),
// macOS system shortcuts: Shift+Cmd+Q - Log Out, Ctrl+Cmd+Q - Lock Screen
//VK_Q ('q', 'œ', 'Q', 'Œ'),
//VK_W ('w', '∑', 'W', '„'),
//VK_E (KeyChar.ch('e'), KeyChar.dead('´') ,KeyChar.ch('E'), KeyChar.ch('´')),
//VK_R ('r', '®', 'R', '‰'),
//VK_T ('t', '†', 'T', 'ˇ'),
//VK_Y ('y', '¥', 'Y', 'Á'),
//VK_U (KeyChar.ch('u'), KeyChar.dead('¨'), KeyChar.ch('U'), KeyChar.ch('¨')),
//VK_I (KeyChar.ch('i'), KeyChar.dead('ˆ'), KeyChar.ch('I'), KeyChar.ch('ˆ')),
//VK_O ('o', 'ø', 'O', 'Ø'),
//VK_P ('p', 'π', 'P', '∏'),
//VK_A ('a', 'å', 'A', 'Å'),
//VK_S ('s', 'ß', 'S', 'Í'),
//VK_D ('d', '∂', 'D', 'Î'),
//VK_F ('f', 'ƒ', 'F', 'Ï'),
//VK_G ('g', '©', 'G', '˝'),
// macOS system shortcuts: Cmd+H - Hide the windows of the front app
//VK_H ('h', '˙', 'H', 'Ó'),
//VK_J ('j', '∆', 'J', 'Ô'),
//VK_K ('k', '˚', 'K', ''),
//VK_L ('l', '¬', 'L', 'Ò'),
//VK_Z ('z', 'Ω', 'Z', '¸'),
//VK_X ('x', '≈', 'X', '˛'),
//VK_C ('c', 'ç', 'C', 'Ç'),
// macOS system shortcuts: Cmd+V - Paste the contents of the Clipboard into the current document or app
//VK_V ('v', '√', 'V', '◊'),
//VK_B ('b', '∫', 'B', 'ı'),
//VK_N (KeyChar.ch('n'), KeyChar.dead('˜'), KeyChar.ch('N'), KeyChar.ch('˜')),
//VK_M ('m', 'µ', 'M', 'Â'),
;
// Common code for any LayoutKey enum
private final Key key;
Layout_ABC(char no, char alt, char shift, char alt_shift) {
key = new Key(name(), new MappedKeyChars(no, alt, shift, alt_shift));
}
Layout_ABC(KeyChar no, KeyChar alt, KeyChar shift, KeyChar alt_shift) {
key = new Key(name(), new MappedKeyChars(no, alt, shift, alt_shift));
}
public Key getKey() {
return key;
}
}

View File

@@ -0,0 +1,72 @@
/*
* Copyright 2000-2023 JetBrains s.r.o.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
/**
* @test
* @summary Regression test for IDEA-165950: National keyboard layouts support
* @requires (jdk.version.major >= 8 & os.family == "mac")
* @modules java.desktop/sun.lwawt.macosx
* @run main NationalLayoutTest FRENCH_PC
*/
/*
* Enumerates keys under test for com.apple.keylayout.French-PC (macOS 10.14.5)
*/
public enum Layout_FRENCH_PC implements LayoutKey {
// Enum name must be the same as KeyEvent.VK_ constant name corresponding to the key on US keyboard layout
// Note that '\u0000' may be used if no char is mapped to a key + modifier or if one wants to skip its testing
VK_MINUS (')', ']', '°', ']'),
VK_EQUALS ('=', '}', '+', '≠'),
VK_OPEN_BRACKET (KeyChar.dead('^'), KeyChar.ch('ô'), KeyChar.dead('¨'), KeyChar.ch('Ô')),
VK_CLOSE_BRACKET ('$', '¤', '£', '¥'),
VK_SEMICOLON ('m', 'µ', 'M', 'Ó'),
VK_QUOTE ('ù', 'Ù', '%', '‰'),
VK_BACK_SLASH ('*', '@', 'μ', '#'),
VK_BACK_QUOTE ('<', '«', '>', '≥'),
VK_COMMA (';', '…', '.', '•'),
VK_PERIOD (':', '÷', '/', '\\'),
VK_SLASH ('!', '¡', '§', '±'),
;
// Common code for any LayoutKey enum
private final Key key;
Layout_FRENCH_PC(char no, char alt, char shift, char alt_shift) {
key = new Key(name(), new MappedKeyChars(no, alt, shift, alt_shift));
}
Layout_FRENCH_PC(KeyChar no, KeyChar alt, KeyChar shift, KeyChar alt_shift) {
key = new Key(name(), new MappedKeyChars(no, alt, shift, alt_shift));
}
public Key getKey() {
return key;
}
}

View File

@@ -0,0 +1,73 @@
/*
* Copyright 2000-2023 JetBrains s.r.o.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
/**
* @test
* @summary Regression test for IDEA-165950: National keyboard layouts support
* @requires (jdk.version.major >= 8 & os.family == "mac")
* @modules java.desktop/sun.lwawt.macosx
* @run main NationalLayoutTest GERMAN
*/
/*
* Enumerates keys under test for com.apple.keylayout.German (macOS 10.14.5)
*/
public enum Layout_GERMAN implements LayoutKey {
// Enum name must be the same as KeyEvent.VK_ constant name corresponding to the key on US keyboard layout
// Note that '\u0000' may be used if no char is mapped to a key + modifier or if one wants to skip its testing
// Eszett
VK_MINUS ('ß', '¿', '?', '˙'),
VK_EQUALS (KeyChar.dead('´'), KeyChar.ch('\''), KeyChar.dead('`'), KeyChar.ch('˚')),
VK_OPEN_BRACKET ('ü', '•', 'Ü', '°'),
VK_CLOSE_BRACKET ('+', '±', '*', ''),
VK_SEMICOLON ('ö', 'œ', 'Ö', 'Œ'),
VK_QUOTE ('ä', 'æ', 'Ä', 'Æ'),
VK_BACK_SLASH ('#', '', '\'', ''),
VK_BACK_QUOTE ('<', '≤', '>', '≥'),
VK_COMMA (',', '∞', ';', '˛'),
VK_PERIOD ('.', '…', ':', '÷'),
VK_SLASH ('-', '', '_', '—'),
;
// Common code for any LayoutKey enum
private final Key key;
Layout_GERMAN(char no, char alt, char shift, char alt_shift) {
key = new Key(name(), new MappedKeyChars(no, alt, shift, alt_shift));
}
Layout_GERMAN(KeyChar no, KeyChar alt, KeyChar shift, KeyChar alt_shift) {
key = new Key(name(), new MappedKeyChars(no, alt, shift, alt_shift));
}
public Key getKey() {
return key;
}
}

View File

@@ -0,0 +1,72 @@
/*
* Copyright 2000-2023 JetBrains s.r.o.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
/**
* @test
* @summary Regression test for IDEA-165950: National keyboard layouts support
* @requires (jdk.version.major >= 8 & os.family == "mac")
* @modules java.desktop/sun.lwawt.macosx
* @run main NationalLayoutTest SPANISH_ISO
*/
/*
* Enumerates keys under test for com.apple.keylayout.Spanish-ISO (macOS 10.14.5)
*/
public enum Layout_SPANISH_ISO implements LayoutKey {
// Enum name must be the same as KeyEvent.VK_ constant name corresponding to the key on US keyboard layout
// Note that '\u0000' may be used if no char is mapped to a key + modifier or if one wants to skip its testing
VK_MINUS ('\'', '´', '?', '¸'),
VK_EQUALS ('¡', '', '¿', '˛'),
VK_OPEN_BRACKET (KeyChar.dead('`'), KeyChar.ch('['), KeyChar.dead('^'), KeyChar.ch('ˆ')),
VK_CLOSE_BRACKET ('+', ']', '*', '±'),
VK_SEMICOLON (KeyChar.ch('ñ'), KeyChar.dead('~'), KeyChar.ch('Ñ'), KeyChar.ch('˜')),
VK_QUOTE (KeyChar.dead('´'), KeyChar.ch('{'), KeyChar.dead('¨'), KeyChar.ch('«')),
VK_BACK_SLASH ('ç', '}', 'Ç', '»'),
VK_BACK_QUOTE ('<', '≤', '>', '≥'),
VK_COMMA (',', '„', ';', '\u0000'),
VK_PERIOD ('.', '…', ':', '…'),
VK_SLASH ('-', '', '_', '—'),
;
// Common code for any LayoutKey enum
private final Key key;
Layout_SPANISH_ISO(char no, char alt, char shift, char alt_shift) {
key = new Key(name(), new MappedKeyChars(no, alt, shift, alt_shift));
}
Layout_SPANISH_ISO(KeyChar no, KeyChar alt, KeyChar shift, KeyChar alt_shift) {
key = new Key(name(), new MappedKeyChars(no, alt, shift, alt_shift));
}
public Key getKey() {
return key;
}
}

View File

@@ -0,0 +1,73 @@
/*
* Copyright 2000-2023 JetBrains s.r.o.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
/**
* @test
* @summary Regression test for IDEA-165950: National keyboard layouts support
* @requires (jdk.version.major >= 8 & os.family == "mac")
* @modules java.desktop/sun.lwawt.macosx
* @run main NationalLayoutTest US_INTERNATIONAL_PC
*/
/*
* Enumerates keys under test for com.apple.keylayout.USInternational-PC (macOS 10.14.5)
*/
public enum Layout_US_INTERNATIONAL_PC implements LayoutKey {
// Enum name must be the same as KeyEvent.VK_ constant name corresponding to the key on US keyboard layout
// Note that '\u0000' may be used if no char is mapped to a key + modifier or if one wants to skip its testing
VK_MINUS ('-', '', '_', '—'),
VK_EQUALS ('=', '≠', '+', '±'),
VK_OPEN_BRACKET ('[', '“', '{', '”'),
VK_CLOSE_BRACKET (']', '', '}', ''),
VK_SEMICOLON (';', '…', ':', 'Ú'),
// ' is a special dead symbol, which may add either acute or cedilla to the next key
VK_QUOTE (KeyChar.dead('\''), KeyChar.ch('æ'), KeyChar.dead('\"'), KeyChar.ch('Æ')),
VK_BACK_SLASH ('\\', '«', '|', '»'),
VK_BACK_QUOTE (KeyChar.dead('`'), KeyChar.dead('`'), KeyChar.dead('~'), KeyChar.ch('`')),
VK_COMMA (',', '≤', '<', '¯'),
VK_PERIOD ('.', '≥', '>', '˘'),
VK_SLASH ('/', '÷', '?', '¿'),
;
// Common code for any LayoutKey enum
private final Key key;
Layout_US_INTERNATIONAL_PC(char no, char alt, char shift, char alt_shift) {
key = new Key(name(), new MappedKeyChars(no, alt, shift, alt_shift));
}
Layout_US_INTERNATIONAL_PC(KeyChar no, KeyChar alt, KeyChar shift, KeyChar alt_shift) {
key = new Key(name(), new MappedKeyChars(no, alt, shift, alt_shift));
}
public Key getKey() {
return key;
}
}

View File

@@ -0,0 +1,76 @@
/*
* Copyright 2000-2023 JetBrains s.r.o.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
/*
* Defines chars mapped to the key on the current keyboard layout
*/
public class MappedKeyChars {
private final KeyChar no_modifier;
private final KeyChar alt;
private final KeyChar shift;
private final KeyChar alt_shift;
MappedKeyChars(char no_modifier, char alt, char shift, char alt_shift) {
this.no_modifier = KeyChar.ch(no_modifier);
this.alt = KeyChar.ch(alt);
this.shift = KeyChar.ch(shift);
this.alt_shift = KeyChar.ch(alt_shift);
}
MappedKeyChars(KeyChar no_modifier, KeyChar alt, KeyChar shift, KeyChar alt_shift) {
this.no_modifier = no_modifier;
this.alt = alt;
this.shift = shift;
this.alt_shift = alt_shift;
}
/*
* Return char mapped to key + modifier on the current layout.
* Return "undefined" char '\uffff', if modifier is a shortcut, i.e. other than Alt, Shift or Alt+Shift or empty.
* Return "null" char '\u0000', if there is no char mapped to the key + modifier or its testing is skipped.
*/
KeyChar getKeyChar(Modifier modifier) {
if(modifier.isEmpty()) {
return no_modifier;
}
if(modifier.isAlt()) {
return alt;
}
if(modifier.isShift()) {
return shift;
}
if(modifier.isAltShift()) {
return alt_shift;
}
return KeyChar.ch(Character.MAX_VALUE);
}
/*
* Return char mapped to the low registry key on the current layout, i.e. with no modifier.
*/
KeyChar getKeyChar() {
return no_modifier;
}
}

View File

@@ -0,0 +1,111 @@
/*
* Copyright 2000-2023 JetBrains s.r.o.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
import java.awt.event.KeyEvent;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
/*
* Enumerates modifier combinations covered by this test
*/
public enum Modifier {
E(),
//A(KeyEvent.VK_ALT),
//S(KeyEvent.VK_SHIFT),
//SA(KeyEvent.VK_SHIFT, KeyEvent.VK_ALT),
// Shortcuts
M(KeyEvent.VK_META),
C(KeyEvent.VK_CONTROL),
//CS(KeyEvent.VK_CONTROL, KeyEvent.VK_SHIFT),
//CA(KeyEvent.VK_CONTROL, KeyEvent.VK_ALT),
//CM(KeyEvent.VK_CONTROL, KeyEvent.VK_META),
//MS(KeyEvent.VK_META, KeyEvent.VK_SHIFT),
//MA(KeyEvent.VK_META, KeyEvent.VK_ALT),
//CMS(KeyEvent.VK_CONTROL, KeyEvent.VK_META, KeyEvent.VK_SHIFT),
//CMA(KeyEvent.VK_CONTROL, KeyEvent.VK_META, KeyEvent.VK_ALT),
//CSA(KeyEvent.VK_CONTROL, KeyEvent.VK_SHIFT, KeyEvent.VK_ALT),
//MSA(KeyEvent.VK_META, KeyEvent.VK_SHIFT, KeyEvent.VK_ALT),
//CMSA(KeyEvent.VK_CONTROL, KeyEvent.VK_META, KeyEvent.VK_SHIFT, KeyEvent.VK_ALT),
;
// Holds array of int modifier values, where every int value corresponds to modifier KeyEvent.VK_ code
private int[] modifiers;
// Creates empty Modifier
Modifier() {
this(new int[0]);
}
// Creates Modifier using given KeyEvent.VK_ codes
Modifier(int... modifiers) {
for(int key : modifiers) {
if ((key != KeyEvent.VK_CONTROL) && (key != KeyEvent.VK_META)
&& (key != KeyEvent.VK_SHIFT) && (key != KeyEvent.VK_ALT)) {
throw new IllegalArgumentException("Bad modifier: " + key);
}
}
this.modifiers = modifiers;
}
// Returns array of int modifier values, where every int value corresponds to modifier KeyEvent.VK_ code
int[] getModifiers() {
return modifiers;
}
// Checks if no modifier is set
boolean isEmpty() {
return (modifiers.length == 0);
}
// Checks if modifier is Alt
boolean isAlt() {
return ((modifiers.length == 1) && (modifiers[0] == KeyEvent.VK_ALT));
}
// Checks if modifier is Shift
boolean isShift() {
return ((modifiers.length == 1) && (modifiers[0] == KeyEvent.VK_SHIFT));
}
// Checks if modifier is Alt+Shift
boolean isAltShift() {
List<Integer> list = Arrays.stream(modifiers).boxed().collect(Collectors.toList());
return ((modifiers.length == 2) && list.contains(KeyEvent.VK_ALT) && list.contains(KeyEvent.VK_SHIFT));
}
@Override
public String toString() {
if (modifiers.length == 0) {
return "no";
}
return Arrays.stream(modifiers).boxed().map(i -> KeyEvent.getKeyText(i)).collect(Collectors.joining(" "));
}
}

View File

@@ -0,0 +1,538 @@
/*
* Copyright 2000-2023 JetBrains s.r.o.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
import sun.lwawt.macosx.LWCToolkit;
import java.awt.AWTException;
import java.awt.BorderLayout;
import java.awt.FlowLayout;
import java.awt.Frame;
import java.awt.Label;
import java.awt.Panel;
import java.awt.Robot;
import java.awt.TextArea;
import java.awt.event.FocusAdapter;
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;
import java.awt.event.InputMethodEvent;
import java.awt.event.InputMethodListener;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.text.CharacterIterator;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
/*
* Description: Tests national keyboard layouts on macOS.
*
* Test goes over existing Layout and Modifier enums.
* Each layout key will be tested with all possible modifier combinations.
* Test switches to the requested layout (or user changes it by hands).
* Then Robot keeps pressing modifier + key, while test keeps tracking of keyPressed, charsTyped and inputMethod events.
* Finally, test compares the resulting key presses and typed chars with the expected ones using the assumptions below.
*
* Test based on the following assumptions:
* TODO check if these assumptions are correct
* - Pressing "modifier + key" always generates "modifier key code + low registry key code" for any key and modifier.
* - No keyTyped event is expected as the result of pressing dead key + key.
* - Pressing "dead key + space" generates corresponding diacritic character,
* which may be obtained using inputMethodTextChanged event.
* - Cmd, Ctrl and its combinations with other modifiers are considered as a "shortcut",
* no keyTyped event is expected as the result of pressing a shortcut,
* no attempts are made to check inputMethodTextChanged event result for a "shortcut".
*
* WARNING: Test sends many key presses which may match system and IDE shortcuts.
*
* !!! DISABLE MACOS SYSTEM SHORTCUTS BEFORE RUNNING THIS TEST !!!
* !!! DO NOT RUN THIS TEST FROM IDE !!!
*
* MacOS accessibility permission should be granted on macOS >= 10.14 for the application launching this test, so
* Java Robot is able to access keyboard (use System Preferences -> Security&Privacy -> Privacy tab -> Accessibility).
*
* Test can only be compiled by JBRSDK as it uses private LWCToolkit API for switching system keyboard layout.
* Test may be run by any JRE, but in case of non-JBR, one needs to manually switch the keyboard layout during testing.
*
* Compilation:
* <javac-1.8.*> -bootclasspath <jbrsdk8>/Contents/Home/jre/lib/rt.jar:. NationalLayoutTest.java
* <javac-11.*> --add-exports java.desktop/sun.lwawt.macosx=ALL-UNNAMED NationalLayoutTest.java
*
* Usage:
* <java> NationalLayoutTest [manual] [layout1] [layout2] ... , where layoutN is one of the existing Layout enum values
*
* Examples:
* > java NationalLayoutTest
* > java NationalLayoutTest ABC SPANISH_ISO
* > java NationalLayoutTest manual US_INTERNATIONAL_PC
*
*/
public class NationalLayoutTest {
private static final String MANUAL= "manual";
private static final int PAUSE = 2000;
// Test control keys
private static final int NEXT_KEY = KeyEvent.VK_ENTER;
private static final int NEXT_MODIFIER = KeyEvent.VK_ESCAPE;
private static final int TERMINATE_KEY = KeyEvent.VK_SPACE;
private static CountDownLatch nextKey;
private static CountDownLatch nextModifierSet;
private static boolean jbr = true;
private static boolean autoTest = true;
private static boolean showKeyCodeHex = false;
private static Frame mainFrame;
private static TextArea inputArea;
private static TextArea pressArea;
private static TextArea typeArea;
private static Robot robot;
// Synchronized lists for storing results of different key events
private static CopyOnWriteArrayList<Integer> keysPressed = new CopyOnWriteArrayList();
private static CopyOnWriteArrayList<Character> charsTyped = new CopyOnWriteArrayList();
private static CopyOnWriteArrayList<Character> inputMethodChars = new CopyOnWriteArrayList();
/*
* Test entry point
*/
public static void main(String[] args) throws InterruptedException {
// Check if the testing could be performed using Java Robot
try {
robot = new Robot();
robot.setAutoDelay(50);
} catch (AWTException e) {
throw new RuntimeException("TEST ERROR: Cannot create Java Robot " + e);
}
// Check if program arguments contain known layouts to test
List<Layout> layoutList = new ArrayList();
for (String arg : args) {
if(!arg.equals(MANUAL)) {
try {
layoutList.add(Layout.valueOf(arg));
} catch (IllegalArgumentException e) {
throw new RuntimeException("ERROR: Unexpected argument: " + arg);
}
}
}
// JBR internal API from LWCToolkit is used to switch system keyboard layout.
// So running the test for other java implementations is only possible in manual mode.
// During the test run one should switch to the requested keyboard layout by hands
// and then proceed with testing by pressing NEXT_MODIFIER.
if (!System.getProperty("java.vm.vendor").toLowerCase().contains("jetbrains")) {
System.out.println("WARNING - Not JBR mode: Cannot automatically switch keyboard layout");
jbr = false;
}
// One may want to use MANUAL mode to simply improve visibility.
// Please proceed with layout testing by pressing NEXT_MODIFIER.
if(Arrays.asList(args).contains(MANUAL) || !(jbr)) {
System.out.println("WARNING - Manual mode: Press " + KeyEvent.getKeyText(NEXT_MODIFIER)
+ " to start testing keyboard layout with the modifier set");
autoTest = false;
}
String initialLayoutName = null;
try {
// Create test GUI
createGUI();
// Save initial keyboard layout
if(jbr) {
initialLayoutName = LWCToolkit.getKeyboardLayoutId();
}
boolean failed = false;
// Test layouts defined in the command line or all enumerated test layouts in case command line is empty
for(Layout layout : (layoutList.isEmpty()
? Layout.values() : layoutList.toArray(new Layout[layoutList.size()]))) {
// Test layout with all enumerated modifier combinations
for (Modifier modifier : Modifier.values()) {
if(!testLayout(layout, modifier)) {
failed = true;
};
}
}
// Provide the test result
if(failed) {
throw new RuntimeException("TEST FAILED: Some keyboard layout tests failed, check the test output");
} else {
System.out.println("TEST PASSED");
}
} finally {
// Restore initial keyboard layout
if(initialLayoutName != null && !initialLayoutName.isEmpty()) {
LWCToolkit.switchKeyboardLayout(initialLayoutName);
}
// Destroy test GUI
destroyGUI();
// Wait for EDT auto-shutdown
Thread.sleep(PAUSE);
}
}
// Helpers for checking whether type area got focus
private static final CountDownLatch typeAreaGainedFocus = new CountDownLatch(1);
private static final FocusListener typeAreaFocusListener = new FocusAdapter() {
@Override
public void focusGained(FocusEvent e) {
typeAreaGainedFocus.countDown();
}
};
/*
* Create the test GUI - main frame with three text areas:
* 1) input area shows the current test key combination pressed by Java Robot
* 2) press area shows what key pressed events was received as the result of 1
* 3) type area (which is initially focused) shows symbols typed as the result of 1
*/
private static void createGUI() throws InterruptedException {
mainFrame = new Frame("Test Frame");
inputArea = new TextArea("",50, 30);
pressArea = new TextArea("", 50, 30);
typeArea = new TextArea("",50, 30);
// Add listeners to track keyboard events
typeArea.addFocusListener(typeAreaFocusListener);
typeArea.addKeyListener(keyListener);
typeArea.addInputMethodListener(inputMethodListener);
Panel labelPanel = new Panel();
labelPanel.add(new Label("Input Keys ---> Pressed Keys ---> Typed Chars"));
Panel textPanel = new Panel();
textPanel.setLayout(new FlowLayout());
textPanel.add(inputArea);
textPanel.add(pressArea);
textPanel.add(typeArea);
mainFrame.setLocation(200, 200);
mainFrame.add(labelPanel, BorderLayout.NORTH);
mainFrame.add(textPanel, BorderLayout.SOUTH);
mainFrame.pack();
mainFrame.setVisible(true);
typeArea.requestFocusInWindow();
// Check if type area got focus.
// Corresponding latch is released in the typeAreaFocusListener.
if(!typeAreaGainedFocus.await(PAUSE, TimeUnit.MILLISECONDS)) {
throw new RuntimeException("TEST ERROR: Failed to request focus in the text area for typing");
};
}
/*
* Destroy the test GUI
*/
private static void destroyGUI() {
if(typeArea != null) {
typeArea.removeFocusListener(typeAreaFocusListener);
typeArea.removeKeyListener(keyListener);
typeArea.removeInputMethodListener(inputMethodListener);
}
if(mainFrame != null) {
mainFrame.dispose();
}
}
/*
* Listener for keyPressed and keyTyped events
*/
private static final KeyListener keyListener = new KeyAdapter() {
@Override
public void keyPressed(KeyEvent e) {
// Obtain pressed key code
int keyCode = e.getKeyCode();
if (keyCode == KeyEvent.VK_UNDEFINED) {
keyCode = e.getExtendedKeyCode();
}
if (keyCode == NEXT_MODIFIER) {
// Support for manual mode: notify main thread that testing may be continued
nextModifierSet.countDown();
} else if (keyCode == NEXT_KEY) {
// Update press area with the next line
pressArea.append("\n");
// Notify main thread that all events related to the key testing should already have been received
nextKey.countDown();
} else if (keyCode == TERMINATE_KEY) {
// Do nothing, press TERMINATE_KEY_SEQUENCE to support dead key testing
} else {
String keyText = KeyEvent.getKeyText(keyCode);
// Update press area with the pressed key text
pressArea.append(keysPressed.isEmpty() ? keyText : " " + keyText);
// Store pressed key code to the corresponding list
keysPressed.add(keyCode);
}
}
@Override
public void keyTyped(KeyEvent e) {
// Obtain typed char
char keyChar = e.getKeyChar();
int keyCode = KeyEvent.getExtendedKeyCodeForChar(keyChar);
if ((keyCode != NEXT_MODIFIER) && (keyCode != NEXT_KEY) && (keyCode != TERMINATE_KEY)) {
// Store typed char to the corresponding list
charsTyped.add(keyChar);
}
}
};
/*
* Test listener for InputMethod events.
* Such events may occur for a special case when a character is generated by pressing more than one key,
* for example, when attaching specific diacritic to a base letter, by pressing dead key + base key.
*/
static final InputMethodListener inputMethodListener = new InputMethodListener() {
@Override
public void inputMethodTextChanged(InputMethodEvent e) {
// Store generated chars to the corresponding list
if(e.getCommittedCharacterCount() > 0) {
CharacterIterator text = e.getText();
for (char ch = text.first(); ch != CharacterIterator.DONE; ch = text.next()) {
inputMethodChars.add(ch);
}
}
}
@Override
public void caretPositionChanged(InputMethodEvent event) {
// Do nothing
}
};
/*
* Main method for testing defined layout keys with the specific modifier set
*/
private static boolean testLayout(Layout layout, Modifier modifier) throws InterruptedException {
boolean result = true;
System.out.println("\nStart testing " + layout + " layout with " + modifier + " modifier(s):");
// Switch current keyboard layout to the test one
if(jbr) {
LWCToolkit.switchKeyboardLayout(layout.toString());
}
// Support for manual mode: wait while user switches the keyboard layout (if needed)
// and proceed with layout + modifier testing by pressing NEXT_MODIFIER.
// Corresponding latch is released in the keyListener when NEXT_MODIFIER key event is received.
nextModifierSet = new CountDownLatch(1);
if(autoTest) {
pressKey(NEXT_MODIFIER);
}
if(!nextModifierSet.await(PAUSE*10, TimeUnit.MILLISECONDS)) {
throw new RuntimeException("TEST ERROR: User has not proceeded with manual testing");
};
// Clean up the test text areas
inputArea.setText("");
pressArea.setText("");
// Workaround jdk8 setText() issue
typeArea.setText(" ");
// Store modifier keys to array
int[] modifiers = modifier.getModifiers();
// Go over all keys defined for the layout
for(LayoutKey layoutKey : layout.getLayoutKeys()) {
// Clean up synchronized lists which store pressed key codes and typed chars
keysPressed = new CopyOnWriteArrayList();
charsTyped = new CopyOnWriteArrayList();
inputMethodChars = new CopyOnWriteArrayList();
// Get Key object from LayoutKey enum
Key key = layoutKey.getKey();
// Obtain the key code for the current layout
int keyCode = key.getKeyCode();
// Append input area with the modifiers + key under test
if(!modifier.isEmpty()) {
inputArea.append(modifier + " ");
}
inputArea.append(KeyEvent.getKeyText(keyCode) + "\n");
// Robot only knows US keyboard layout.
// So, to press modifier + key under test on the current layout,
// one need to pass corresponding US layout key code to Robot.
pressKey(key.getKeyCode_US(), modifiers);
// The key under test may be a dead key which does not generate a character by itself.
// But it modifies the character generated by the key pressed immediately after.
// So need to press some TERMINATE_KEY key to avoid dead key influence on the next key under test.
// Corresponding diacritic character by itself can be generated by pressing dead key followed by space.
// That is why pressing TERMINATE_KEY=space after a dead key resets the dead key modifier
// and allows testing the generated character which may be useful for some layouts.
// Test does two checks: if modifier + key is a dead key and if key with no modifier is a dead key.
// Second check is needed for shortcuts as key.isDead(modifier) is always false for a shortcut.
if(key.isDead(modifier) || key.isDead()) {
pressKey(TERMINATE_KEY);
}
// Wait while NEXT_KEY is pressed, which identifies that modifier + key testing is finished.
// Corresponding latch is released in the keyListener when NEXT_KEY key event is received.
nextKey = new CountDownLatch(1);
pressKey(NEXT_KEY);
if(!nextKey.await(PAUSE, TimeUnit.MILLISECONDS)) {
throw new RuntimeException("TEST ERROR: "
+ KeyEvent.getKeyText(NEXT_KEY) + " key pressed event was not received");
};
// Define array of key codes expected to be pressed as the result of modifiers + key
int[] keysPattern = Arrays.copyOf(modifiers, modifiers.length + 1);
keysPattern[modifiers.length] = keyCode;
// Define array of key codes which were really pressed as the result of modifiers + key
int[] keysResult = listToInts(keysPressed);
// Do not check the resulting character in case it is null,
// i.e. no char is mapped to the modifier or there is no need to test it.
if (key.isCharNull(modifier)) {
// Check if the pressed key codes are equal to the expected ones
if(!checkResult(keysPattern, keysResult, null,null)) {
result = false;
};
} else {
// Define array of chars expected to be typed as the result of modifiers + key
// Do not expect any char typed as the result of shortcut (char is undefined in this case)
char[] charsPattern = key.isCharUndefined(modifier)
? new char[0] : new char[] { key.getChar(modifier) };
// Define array of chars which were really typed as the result of modifiers + key
// No keyTyped events may be generated as the result of pressing a dead key + space,
// so check if input method text was updated in this case.
char[] charsResult = (key.isDead(modifier) && charsTyped.isEmpty())
? listToChars(inputMethodChars) : listToChars(charsTyped);
// Check if pressed key codes and typed chars are equal to the expected ones
if(!checkResult(keysPattern, keysResult, charsPattern, charsResult)) {
result = false;
};
}
}
// Provide layout + modifier testing result
System.out.println("Finish testing " + layout + " layout with " + modifier + " modifier(s): "
+ (result ? "PASSED" : "FAILED"));
return result;
}
/*
* Use Java Robot to press the key with specific modifiers
*/
private static void pressKey(int keyCode, int... modifiers) {
robot.waitForIdle();
for (int modifier : modifiers) {
robot.keyPress(modifier);
}
robot.keyPress(keyCode);
robot.keyRelease(keyCode);
for (int modifier : modifiers) {
robot.keyRelease(modifier);
}
}
/*
* Check if keys pressed and char typed are equal to the expected ones
*/
private static boolean checkResult(int[] patternKeys, int[] resultKeys, char[] patternChars, char[] resultChars) {
boolean checkKeys = Arrays.equals(patternKeys, resultKeys);
boolean checkChars = Arrays.equals(patternChars, resultChars);
boolean result = (checkKeys & checkChars);
if(!result) {
String[] patternStr = (patternKeys != null) ? intsToStrings(patternKeys) : null;
String[] resultStr = (resultKeys != null) ? intsToStrings(resultKeys) : null;
String eqKeys = checkKeys ? " == " : " != ";
System.err.println("keyText : " + Arrays.toString(patternStr) + eqKeys + Arrays.toString(resultStr));
System.err.println("keyCode : " + Arrays.toString(patternKeys) + eqKeys + Arrays.toString(resultKeys));
if(showKeyCodeHex) {
String[] patternHex = intsToHexStrings(patternKeys);
String[] resultHex = intsToHexStrings(resultKeys);
System.err.println("keyCodeHex: " + Arrays.toString(patternHex) + eqKeys + Arrays.toString(resultHex));
}
}
if(!checkChars) {
String[] patternHex = (patternChars != null) ? charsToHexStrings(patternChars) : null;
String[] resultHex = (resultChars != null) ? charsToHexStrings(resultChars) : null;
String eqChars = checkChars ? " == " : " != ";
System.err.println("keyChar : " + Arrays.toString(patternChars) + eqChars + Arrays.toString(resultChars));
System.err.println("keyCharHex: " + Arrays.toString(patternHex) + eqChars + Arrays.toString(resultHex));
}
if(!result) {
System.err.println();
}
return result;
}
/*
* Transform list of Integers to int array
*/
private static int[] listToInts(List<Integer> list) {
return list.stream().mapToInt(i->i).toArray();
}
/*
* Transform list of Characters to char array
*/
private static char[] listToChars(List<Character> list) {
return list.stream().map(c -> c.toString()).collect(Collectors.joining()).toCharArray();
}
/*
* Transform array of int keys to array of Strings, mapping an int key to its text representation
*/
private static String[] intsToStrings(int[] ints) {
return Arrays.stream(ints).boxed().map(i -> KeyEvent.getKeyText(i)).toArray(String[]::new);
}
/*
* Transform array of int keys to array of Strings, mapping an int key to its hex representation
*/
private static String[] intsToHexStrings(int[] ints) {
return Arrays.stream(ints).boxed().map(i -> String.format("0x%1$04X", i)).toArray(String[]::new);
}
/*
* Transform array of char to array of Strings, mapping a char to its hex representation
*/
private static String[] charsToHexStrings(char[] chars) {
int[] result = new int[chars.length];
Arrays.setAll(result, i -> (int) chars[i]);
return intsToHexStrings(result);
}
}