// Copyright 2012 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package org.eclipse.che.ide.util.input;
import elemental.events.KeyboardEvent.KeyCode;
import elemental.js.util.JsArrayOfInt;
import org.eclipse.che.ide.util.browser.UserAgent;
import org.eclipse.che.ide.util.input.SignalEvent.KeySignalType;
/**
* Provides a consistent map from developer-defined strings and web browser
* event.keyCode values to an internal representation.
* <p/>
* The internal representation is to map key codes to 7-bit ascii where
* possible, and map non-ascii keys like page up to the Unicode Private Use Area
* U+E000...U+F8FF
* <p/>
* NOTE: a...z are returned as uppercase A...Z, and only unshifted versions of
* symbols are returned from the keyboard.
*/
public class KeyCodeMap {
/** Map from keyCode to upper case UTF-16 */
private static final JsArrayOfInt lowerToUpper;
static {
lowerToUpper = JsArrayOfInt.create();
// special characters
lowerToUpper.set('1', '!');
lowerToUpper.set('2', '@');
lowerToUpper.set('3', '#');
lowerToUpper.set('4', '$');
lowerToUpper.set('5', '%');
lowerToUpper.set('6', '^');
lowerToUpper.set('7', '&');
lowerToUpper.set('8', '*');
lowerToUpper.set('9', '(');
lowerToUpper.set('0', ')');
lowerToUpper.set('`', '~');
lowerToUpper.set('-', '_');
lowerToUpper.set('=', '+');
lowerToUpper.set('[', '{');
lowerToUpper.set(']', '}');
lowerToUpper.set('\\', '|');
lowerToUpper.set(';', ':');
lowerToUpper.set('\'', '"');
lowerToUpper.set(',', '<');
lowerToUpper.set('.', '>');
lowerToUpper.set('/', '?');
}
/** Internal representation of non-ascii characters */
public static final int UNICODE_PRIVATE_START = 0xE000;
public static final int UNICODE_PRIVATE_END = 0xF8FF;
public static final int ARROW_UP = UNICODE_PRIVATE_START + 0;
public static final int ARROW_DOWN = UNICODE_PRIVATE_START + 1;
public static final int ARROW_LEFT = UNICODE_PRIVATE_START + 2;
public static final int ARROW_RIGHT = UNICODE_PRIVATE_START + 3;
public static final int INSERT = UNICODE_PRIVATE_START + 4;
public static final int DELETE = UNICODE_PRIVATE_START + 5;
public static final int HOME = UNICODE_PRIVATE_START + 6;
public static final int END = UNICODE_PRIVATE_START + 7;
public static final int PAGE_UP = UNICODE_PRIVATE_START + 8;
public static final int PAGE_DOWN = UNICODE_PRIVATE_START + 9;
public static final int MAC_META = UNICODE_PRIVATE_START + 10;
public static final int F1 = UNICODE_PRIVATE_START + 11;
public static final int F2 = F1 + 1;
public static final int F3 = F1 + 2;
public static final int F4 = F1 + 3;
public static final int F5 = F1 + 4;
public static final int F6 = F1 + 5;
public static final int F7 = F1 + 6;
public static final int F8 = F1 + 7;
public static final int F9 = F1 + 8;
public static final int F10 = F1 + 9;
public static final int F11 = F1 + 10;
public static final int F12 = F1 + 11;
/** Bind browser field events */
public static final int EVENT_COPY = UNICODE_PRIVATE_START + 100;
public static final int EVENT_CUT = UNICODE_PRIVATE_START + 101;
public static final int EVENT_PASTE = UNICODE_PRIVATE_START + 102;
/** These map to ascii, but aren't easily written in a string representation */
public static final int ENTER = 10;
public static final int TAB = 9;
public static final int ESC = 27;
public static final int BACKSPACE = 8;
/** Map from keyCode to ascii (for characters like :?><'[]) */
private static final JsArrayOfInt keyCodeToAscii;
static {
keyCodeToAscii = JsArrayOfInt.create();
keyCodeToAscii.set(KeyCode.SEMICOLON, ';');
keyCodeToAscii.set(KeyCode.EQUALS, '=');
keyCodeToAscii.set(KeyCode.COMMA, ',');
keyCodeToAscii.set(KeyCode.DASH, '-');
keyCodeToAscii.set(KeyCode.PERIOD, '.');
keyCodeToAscii.set(KeyCode.SLASH, '/');
keyCodeToAscii.set(KeyCode.APOSTROPHE, '`');
keyCodeToAscii.set(KeyCode.OPEN_SQUARE_BRACKET, '[');
keyCodeToAscii.set(KeyCode.CLOSE_SQUARE_BRACKET, ']');
keyCodeToAscii.set(KeyCode.BACKSLASH, '\\');
keyCodeToAscii.set(KeyCode.SINGLE_QUOTE, '\'');
keyCodeToAscii.set(KeyCode.UP, ARROW_UP);
keyCodeToAscii.set(KeyCode.DOWN, ARROW_DOWN);
keyCodeToAscii.set(KeyCode.LEFT, ARROW_LEFT);
keyCodeToAscii.set(KeyCode.RIGHT, ARROW_RIGHT);
keyCodeToAscii.set(KeyCode.INSERT, INSERT);
keyCodeToAscii.set(KeyCode.DELETE, DELETE);
keyCodeToAscii.set(KeyCode.HOME, HOME);
keyCodeToAscii.set(KeyCode.END, END);
keyCodeToAscii.set(KeyCode.PAGE_UP, PAGE_UP);
keyCodeToAscii.set(KeyCode.PAGE_DOWN, PAGE_DOWN);
keyCodeToAscii.set(KeyCode.META, MAC_META); // left meta
keyCodeToAscii.set(KeyCode.META + 1, MAC_META); // right meta
keyCodeToAscii.set(KeyCode.CONTEXT_MENU, MAC_META);
}
private KeyCodeMap() {
}
/** Is this a letter/symbol that changes if the SHIFT key is pressed? */
public static boolean needsShift(int keycode) {
if ('A' <= keycode && keycode <= 'Z') {
// upper case letter
return true;
}
if (lowerToUpper.contains(keycode)) {
// special character !@#$%...
return true;
}
return false;
}
/**
* Map from event.keyCode to internal representation (ascii+special keys)
* <p/>
* NOTE(wetherbeei): SignalEvent tends to return correct ascii values from
* keyPress events where possible, then keyCodes when they aren't available
*/
public static int getKeyFromEvent(SignalEvent event) {
int ascii = event.getKeyCode();
if (ascii > 255) {
// out of handling range - all keycodes handled are under 256
return ascii;
}
KeySignalType type = event.getKeySignalType();
if (type != KeySignalType.INPUT) {
// convert these non-ascii characters to new unicode private area
if (KeyCode.F1 <= ascii && ascii <= KeyCode.F12) {
ascii = (ascii - KeyCode.F1) + F1;
}
if (keyCodeToAscii.isSet(ascii)) {
ascii = keyCodeToAscii.get(ascii);
}
}
// map enter \r (0x0D) to \n (0x0A)
if (ascii == 0x0D) {
ascii = 0x0A;
}
/*
* Platform/browser specific modifications
*
* Firefox captures combos using keyPress, which returns the correct case of
* the pressed key in ascii. Other browsers are captured on keyDown and only
* return the keyCode value (upper case or 0-9) of the pressed key
*
* TODO: test on other browsers.
*/
if (UserAgent.isFirefox() && event.getType().equals("keypress")) {
// this is a combo event, leave it alone in firefox
} else if (event.getType().equals("keydown")) {
// other browsers keydown combo captured, need to convert keyCode to ascii
// upper case letters to lower if no shift key
if (!event.getShiftKey()) {
if ('A' <= ascii && ascii <= 'Z') {
ascii = ascii - 'A' + 'a';
}
} else {
// shift key, check for additional symbol changes
if (lowerToUpper.isSet(ascii)) {
ascii = lowerToUpper.get(ascii);
}
}
}
// anything in other ranges will be passed through
return ascii;
}
/**
* Is this a printable ascii character? Includes control flow characters like
* newline (\n) and carriage return (\r).
*/
public static boolean isPrintable(int letter) {
if ((letter < 0x20 && letter != 0x0A && letter != 0x0D) || letter == 0x7F) {
// control characters less than 0x20, but not \n 0x0A, \r 0x0D
// or delete key 0x7F
return false; // not printable
}
if (UNICODE_PRIVATE_START <= letter && letter <= UNICODE_PRIVATE_END) {
// reserved internal range for events, not printable
return false;
}
// everything else is printable
return true;
}
/**
* Returns a String describing the keyCode, such as "HOME", "F1" or "A".
*
* @return a string containing a text description for a physical key,
* identified by its keyCode
*/
public static String getKeyText(int keyCode){
if((keyCode >='0' && keyCode<='9') ||
(keyCode >='A' && keyCode<='Z') ||
(keyCode >='a' && keyCode<='z')) {
return String.valueOf((char)keyCode);
}
switch (keyCode){
case F1: return "F1";
case F2: return "F2";
case F3: return "F3";
case F4: return "F4";
case F5: return "F5";
case F6: return "F6";
case F7: return "F7";
case F8: return "F8";
case F9: return "F9";
case F10: return "F10";
case F11: return "F11";
case F12: return "F12";
case INSERT: return "Insert";
case ENTER: return "Enter";
case ARROW_LEFT: return "←";
case ARROW_RIGHT: return "→";
case ARROW_UP: return "↑";
case ARROW_DOWN: return "↓";
case DELETE: return "Delete";
//todo add others keys
}
return "";
}
}