/** * Copyright 2008 Google Inc. * * 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.waveprotocol.wave.client.common.util; import com.google.gwt.dom.client.Element; import com.google.gwt.event.dom.client.KeyCodes; import com.google.gwt.user.client.DOM; import com.google.gwt.user.client.Event; import org.waveprotocol.wave.client.common.util.UserAgent; import java.util.HashMap; import java.util.Map; /** * Wraps GWT's events class to give prettier access to event properties. * * NOTE(user): we expect a future version of GWT to do this directly on * their Event class, at which point we can probably deprecate this * wrapper * */ @SuppressWarnings({ "serial" }) // TODO(danilatos,user): Remove this class, since a lot of this functionality // is exposed by GWT's new event system (i.e. NativeEvent). public class EventWrapper { /** * The event we are wrapping */ private final Event event; /** * Modifier values, greater than 16 bits, to add to the keypresses * to distinguish them. First shift */ private final static int SHIFT = 1 << 17; /** * Alt */ private final static int ALT = 1 << 18; /** * Ctrl */ private final static int CTRL = 1 << 19; /** * Meta */ private final static int META = 1 << 20; /** (Not defined in GWT's KeyCodes) */ public final static int KEY_INSERT = 45; /** * Map of keypresses and modifiers to our KeyCombo enum values. * http://www.quirksmode.org/js/keys.html has some useful info about this. * TODO(danilatos): Implement an IntMap JSO and use that */ private final static Map<Integer, KeyCombo> keyMap = new HashMap<Integer, KeyCombo>() { { // Tab put(9, KeyCombo.TAB); put(9 + SHIFT, KeyCombo.SHIFT_TAB); // Space bar put(32, KeyCombo.SPACE); put(32 + SHIFT, KeyCombo.SHIFT_SPACE); put(32 + CTRL, KeyCombo.CTRL_SPACE); put(32 + SHIFT + CTRL, KeyCombo.CTRL_SHIFT_SPACE); // Escape put(KeyCodes.KEY_ESCAPE, KeyCombo.ESC); // Enter put(KeyCodes.KEY_ENTER, KeyCombo.ENTER); put(KeyCodes.KEY_ENTER + SHIFT, KeyCombo.SHIFT_ENTER); put(KeyCodes.KEY_ENTER + CTRL, KeyCombo.CTRL_ENTER); // Backspace put(KeyCodes.KEY_BACKSPACE, KeyCombo.BACKSPACE); put(KeyCodes.KEY_BACKSPACE + SHIFT, KeyCombo.SHIFT_BACKSPACE); // Delete put(KeyCodes.KEY_DELETE, KeyCombo.DELETE); put(KeyCodes.KEY_DELETE + SHIFT, KeyCombo.SHIFT_DELETE); // Insert put(KEY_INSERT, KeyCombo.INSERT); put(KEY_INSERT + SHIFT, KeyCombo.SHIFT_INSERT); put(KEY_INSERT + CTRL, KeyCombo.CTRL_INSERT); // Ctrl-equals put('=' + CTRL, KeyCombo.CTRL_EQUALS); // Ctrl-alpha combos put('B' + CTRL, KeyCombo.CTRL_B); put('D' + CTRL, KeyCombo.CTRL_D); put('F' + CTRL, KeyCombo.CTRL_F); put('I' + CTRL, KeyCombo.CTRL_I); put('G' + CTRL, KeyCombo.CTRL_G); put('H' + CTRL, KeyCombo.CTRL_H); put('K' + CTRL, KeyCombo.CTRL_K); put('O' + CTRL, KeyCombo.CTRL_O); put('U' + CTRL, KeyCombo.CTRL_U); put('W' + CTRL, KeyCombo.CTRL_W); put('A' + CTRL, KeyCombo.CTRL_A); put('R' + CTRL, KeyCombo.CTRL_R); put('E' + CTRL, KeyCombo.CTRL_E); put('L' + CTRL, KeyCombo.CTRL_L); put('L' + CTRL + SHIFT, KeyCombo.CTRL_SHIFT_L); put('F' + CTRL + ALT, KeyCombo.CTRL_ALT_F); put('D' + CTRL + ALT, KeyCombo.CTRL_ALT_D); put('G' + CTRL + ALT, KeyCombo.CTRL_ALT_G); put('S' + CTRL + ALT, KeyCombo.CTRL_ALT_S); // Canned responses put('!' + CTRL + SHIFT, KeyCombo.CTRL_SHIFT_1); put('1' + CTRL + SHIFT, KeyCombo.CTRL_SHIFT_1); put('@' + CTRL + SHIFT, KeyCombo.CTRL_SHIFT_2); put('2' + CTRL + SHIFT, KeyCombo.CTRL_SHIFT_2); put('#' + CTRL + SHIFT, KeyCombo.CTRL_SHIFT_3); put('3' + CTRL + SHIFT, KeyCombo.CTRL_SHIFT_3); // Allowable ORDER combos // ORDER is the Apple key on OS X and the Ctrl key in Windows and Linux. // It is the key modifier used to perform actions such as copy/paste, // save, print etc. // XXX(user): These override the CTRL/META combos! Fix it! { final int orderKey = UserAgent.isMac() ? META : CTRL; put('A' + orderKey, KeyCombo.ORDER_A); put('B' + orderKey, KeyCombo.ORDER_B); put('C' + orderKey, KeyCombo.ORDER_C); put('D' + orderKey, KeyCombo.ORDER_D); put('F' + orderKey, KeyCombo.ORDER_F); put('G' + orderKey, KeyCombo.ORDER_G); put('I' + orderKey, KeyCombo.ORDER_I); put('K' + orderKey, KeyCombo.ORDER_K); put('L' + orderKey, KeyCombo.ORDER_L); put('N' + orderKey, KeyCombo.ORDER_N); put('O' + orderKey, KeyCombo.ORDER_O); put('P' + orderKey, KeyCombo.ORDER_P); put('R' + orderKey, KeyCombo.ORDER_R); put('T' + orderKey, KeyCombo.ORDER_T); put('U' + orderKey, KeyCombo.ORDER_U); put('V' + orderKey, KeyCombo.ORDER_V); put('W' + orderKey, KeyCombo.ORDER_W); put('X' + orderKey, KeyCombo.ORDER_X); put('Z' + orderKey, KeyCombo.ORDER_Z); put('Q' + orderKey, KeyCombo.ORDER_Q); put('R' + orderKey + SHIFT, KeyCombo.ORDER_SHIFT_R); put('V' + orderKey + SHIFT, KeyCombo.ORDER_SHIFT_V); // Plaintext paste in Safari put('V' | orderKey | ALT | SHIFT, KeyCombo.ORDER_ALT_SHIFT_V); } // Key navigation put(KeyCodes.KEY_DOWN, KeyCombo.DOWN); put(KeyCodes.KEY_UP, KeyCombo.UP); put(KeyCodes.KEY_LEFT, KeyCombo.LEFT); put(KeyCodes.KEY_RIGHT, KeyCombo.RIGHT); put(KeyCodes.KEY_PAGEUP, KeyCombo.PAGE_UP); put(KeyCodes.KEY_PAGEDOWN, KeyCombo.PAGE_DOWN); put(KeyCodes.KEY_HOME, KeyCombo.HOME); put(KeyCodes.KEY_END, KeyCombo.END); // Safari key navigation // TODO(user): consider using deferred binding put(63233, KeyCombo.DOWN); put(63232, KeyCombo.UP); put(63234, KeyCombo.LEFT); put(63235, KeyCombo.RIGHT); put(63276, KeyCombo.PAGE_UP); put(63277, KeyCombo.PAGE_DOWN); put(63273, KeyCombo.HOME); put(63275, KeyCombo.END); // Meta combos put(KeyCodes.KEY_LEFT + META, KeyCombo.META_LEFT); put(KeyCodes.KEY_RIGHT + META, KeyCombo.META_RIGHT); put(KeyCodes.KEY_HOME + META, KeyCombo.META_HOME); } }; /** * @return true is even is a submit */ public boolean isSubmit() { return event != null && isKeyEvent() && getKeyCombo() == KeyCombo.SHIFT_ENTER; } /** * Constructor * * @param event */ public EventWrapper(Event event) { this.event = event; } /** * @return True if alt key was pressed */ public boolean getAltKey() { return DOM.eventGetAltKey(event); } /** * @return The mouse buttons that were depressed as a bit-field, defined * by {@link Event#BUTTON_LEFT}, {@link Event#BUTTON_MIDDLE}, and * {@link Event#BUTTON_RIGHT} */ public int getButton() { return DOM.eventGetButton(event); } /** * @return {@link #getKeyCode()} cast to char */ public char getCharCode() { return (char) getKeyCode(); } /** * @return The mouse x-position within the browser window's client area. */ public int getClientX() { return DOM.eventGetClientX(event); } /** * @return The mouse y-position within the browser window's client area. */ public int getClientY() { return DOM.eventGetClientY(event); } /** * @return True if ctrl key was pressed */ public boolean getCtrlKey() { return DOM.eventGetCtrlKey(event); } /** * @return The current event that is being fired. The current event is only * available within the lifetime of the onBrowserEvent function. Once the * onBrowserEvent method returns, the current event is reset to null. */ public static EventWrapper getCurrentEvent() { Event current = DOM.eventGetCurrentEvent(); return current != null ? new EventWrapper(DOM.eventGetCurrentEvent()) : null; } /** * @return The event's current target element. This is the element * whose listener fired last, not the element which fired the event * initially. */ public Element getCurrentTarget() { return DOM.eventGetCurrentTarget(event); } /** * @return The event */ public Event getEvent() { return event; } /** * @return The element from which the mouse pointer was moved * (only valid for {@link Event#ONMOUSEOVER}). */ public static Element getFromElement(Event event) { return DOM.eventGetFromElement(event); } /** * @return The key code associated with this event. For * {@link Event#ONKEYPRESS}, the Unicode value of the character generated. * For {@link Event#ONKEYDOWN} and {@link Event#ONKEYUP}, the code * associated with the physical key. */ public int getKeyCode() { return getKeyCode(event); } /** * Wrapper for GWT's get[Key Char]Code() that conflates the two values. If * there is no keyCode present, it returns charCode instead. This matches the * values in {{@link #keyMap} above. */ public static int getKeyCode(Event evt) { int keyCode = evt.getKeyCode(); if (keyCode == 0) { keyCode = evt.getCharCode(); } return keyCode; } /** * Semi-deprecated (will be deprecated once event signal has a mechanism for * fast switching). * @return An encoding of the event's keycode and modifiers */ public KeyCombo getKeyCombo() { return getKeyCombo(getKeyCode(), getCtrlKey(), getShiftKey(), getAltKey(), getMetaKey()); } public static KeyCombo getKeyCombo(SignalEvent signal) { return getKeyCombo(signal.getKeyCode(), signal.getCtrlKey(), signal.getShiftKey(), signal.getAltKey(), signal.getMetaKey()); } /** * @return the key-combo representation of the key event. */ public static KeyCombo getKeyCombo(Event evt) { return getKeyCombo(getKeyCode(evt), DOM.eventGetCtrlKey(evt), DOM.eventGetShiftKey(evt), DOM.eventGetAltKey(evt), DOM.eventGetMetaKey(evt)); } private static KeyCombo getKeyCombo(int keyCode, boolean ctrl, boolean shift, boolean alt, boolean meta) { int gwtCode = keyCode; gwtCode += (ctrl ? CTRL : 0) + (shift ? SHIFT : 0) + (alt ? ALT : 0) + + (meta ? META : 0); if (!keyMap.containsKey(gwtCode) && keyCode >= 'a' && keyCode <= 'z') { // HACK(danilatos): make it work cross-browser with the event signal updates. // get rid of this class soon. gwtCode += 'A' - 'a'; } return keyMap.containsKey(gwtCode) ? keyMap.get(gwtCode) : KeyCombo.OTHER; } /** * Converts the parameters to {@link KeyCodes} events to a KeyCombo. * * @return the key-combo representation of the key event. */ public static KeyCombo getKeyCombo(char keyCode, int modifiers) { int gwtCode = keyCode + (((modifiers & KeyCodes.KEY_CTRL) != 0) ? CTRL : 0) + (((modifiers & KeyCodes.KEY_SHIFT) != 0) ? SHIFT : 0) + (((modifiers & KeyCodes.KEY_ALT) != 0) ? ALT : 0); return keyMap.containsKey(gwtCode) ? keyMap.get(gwtCode) : KeyCombo.OTHER; } /** * @return True if the metakey was pressed */ public boolean getMetaKey() { return DOM.eventGetMetaKey(event); } /** * @return The velocity of the mouse wheel associated with the event * along the Y axis. */ public static int getMouseWheelVelocityY(Event event) { return DOM.eventGetMouseWheelVelocityY(event); } /** * @return True if this was an auto-repeat event */ public boolean getRepeat() { return DOM.eventGetRepeat(event); } /** * @return The mouse x-position on the user's display */ public int getScreenX() { return DOM.eventGetScreenX(event); } /** * @return The mouse y-position on the user's display */ public int getScreenY() { return DOM.eventGetScreenY(event); } /** * @return True if the shift key was pressed */ public boolean getShiftKey() { return DOM.eventGetShiftKey(event); } /** * @return The element that was the actual target of the event. */ public Element getTarget() { return DOM.eventGetTarget(event); } /** * @return The element to which the mouse pointer was moved * (only valid for {@link Event#ONMOUSEOUT}). */ public static Element getToElement(Event event) { return DOM.eventGetToElement(event); } /** * @return The event's type */ public int getType() { return DOM.eventGetType(event); } /** * @return true if event type is a focus event */ public boolean isFocusEvent() { return (getType() & Event.FOCUSEVENTS) != 0; } /** * @return true if event type is a key event */ public boolean isKeyEvent() { return (getType() & Event.KEYEVENTS) != 0; } /** * @return true if event type is a mouse event */ public boolean isMouseEvent() { return (getType() & Event.MOUSEEVENTS) != 0; } /** * @return The event's type string */ public String getTypeString() { return DOM.eventGetTypeString(event); } /** * Prevents default for current event */ public static void preventCurrentEventDefault() { getCurrentEvent().event.preventDefault(); } /** * @return A string describing which modifier keys were pressed, * and whether this was a repeat event, e.g., " shift ctrl" */ @SuppressWarnings("deprecation") public static String modifiers(Event event) { // repeat is deprecated, but useful for debugging return (event.getAltKey() ? " alt" : "") + (event.getShiftKey() ? " shift" : "") + (event.getCtrlKey() ? " ctrl" : "") + (event.getMetaKey() ? " meta" : "") + ((event.getTypeInt() == Event.ONKEYDOWN) && event.getRepeat() ? " repeat" : ""); } /** * @return A string describing which mouse buttons were pressed, * e.g., " left" */ private static String mouseButtons(Event event) { if (event.getButton() == -1) { return ""; } else { return ((event.getButton() & Event.BUTTON_LEFT) != 0 ? " left" : "") + ((event.getButton() & Event.BUTTON_MIDDLE) != 0 ? " middle" : "") + ((event.getButton() & Event.BUTTON_RIGHT) != 0 ? " right" : ""); } } /** * @return String describing the event's key code, e.g., * " 64 'a'" */ private static String key(Event event) { return " " + event.getKeyCode() + " '" + (char) event.getKeyCode() + "'"; } /** * @return A string describing the client x,y position, * e.g., " (100, 100)" */ private static String mousePoint(Event event) { return " (" + event.getClientX() + ", " + event.getClientY() + ")"; } @Override public String toString() { return asString(event); } public static String asString(Event event) { // Start with the event type string String string = DOM.eventGetTypeString(event); // Next type-specific fields switch (event.getTypeInt()) { case Event.ONKEYPRESS: case Event.ONKEYUP: case Event.ONKEYDOWN: string += key(event) + modifiers(event); break; case Event.ONCLICK: case Event.ONDBLCLICK: case Event.ONMOUSEMOVE: string += mousePoint(event) + modifiers(event); break; case Event.ONMOUSEDOWN: case Event.ONMOUSEUP: string += mousePoint(event) + mouseButtons(event) + modifiers(event); break; case Event.ONMOUSEOUT: string += mousePoint(event) + modifiers(event) + " to: " + getToElement(event); break; case Event.ONMOUSEOVER: string += mousePoint(event) + modifiers(event) + " from: " + getFromElement(event); break; case Event.ONMOUSEWHEEL: string += " " + getMouseWheelVelocityY(event) + mousePoint(event) + modifiers(event); break; case Event.ONFOCUS: case Event.ONBLUR: case Event.ONCHANGE: case Event.ONERROR: case Event.ONLOAD: case Event.ONLOSECAPTURE: case Event.ONSCROLL: break; } return string; } /** * In safari, there is X velocity and Y velocity. GWT code return the combination of the 2. * This code only return the Y velocity. * @return The y velocity of the mouse event and only the y velocity. */ public static int getMouseWheelVelocityYOnly(Event event) { if (!UserAgent.isSafari()) { return event.getMouseWheelVelocityY(); } else { return nativeGetMouseWheelVelocityYOnly(event); } } private static native int nativeGetMouseWheelVelocityYOnly(Event evt) /*-{ // wheelDeltaY is not standard and only available in newer safari if (evt.wheelDeltaY == undefined) { // The following line is copied from DOMImplSafari.getMouseWheelVelocityY return Math.round(-evt.wheelDelta / 40) || 0; } else { return Math.round(-evt.wheelDeltaY / 40) || 0; } }-*/; }