/* * Copyright (c) 2011, 2016, Oracle and/or its affiliates. All rights reserved. * 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. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * 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. */ package sun.lwawt.macosx; import sun.awt.SunToolkit; import sun.lwawt.LWWindowPeer; import sun.lwawt.PlatformEventNotifier; import java.awt.Toolkit; import java.awt.event.MouseEvent; import java.awt.event.InputEvent; import java.awt.event.MouseWheelEvent; import java.awt.event.KeyEvent; import java.util.Locale; /** * Translates NSEvents/NPCocoaEvents into AWT events. */ final class CPlatformResponder { private final PlatformEventNotifier eventNotifier; private final boolean isNpapiCallback; private int lastKeyPressCode = KeyEvent.VK_UNDEFINED; private final DeltaAccumulator deltaAccumulatorX = new DeltaAccumulator(); private final DeltaAccumulator deltaAccumulatorY = new DeltaAccumulator(); CPlatformResponder(final PlatformEventNotifier eventNotifier, final boolean isNpapiCallback) { this.eventNotifier = eventNotifier; this.isNpapiCallback = isNpapiCallback; } /** * Handles mouse events. */ void handleMouseEvent(int eventType, int modifierFlags, int buttonNumber, int clickCount, int x, int y, int absX, int absY) { final SunToolkit tk = (SunToolkit)Toolkit.getDefaultToolkit(); if ((buttonNumber > 2 && !tk.areExtraMouseButtonsEnabled()) || buttonNumber > tk.getNumberOfButtons() - 1) { return; } int jeventType = isNpapiCallback ? NSEvent.npToJavaEventType(eventType) : NSEvent.nsToJavaEventType(eventType); int jbuttonNumber = MouseEvent.NOBUTTON; int jclickCount = 0; if (jeventType != MouseEvent.MOUSE_MOVED && jeventType != MouseEvent.MOUSE_ENTERED && jeventType != MouseEvent.MOUSE_EXITED) { jbuttonNumber = NSEvent.nsToJavaButton(buttonNumber); jclickCount = clickCount; } int jmodifiers = NSEvent.nsToJavaModifiers(modifierFlags); boolean jpopupTrigger = NSEvent.isPopupTrigger(jmodifiers); eventNotifier.notifyMouseEvent(jeventType, System.currentTimeMillis(), jbuttonNumber, x, y, absX, absY, jmodifiers, jclickCount, jpopupTrigger, null); } /** * Handles scroll events. */ void handleScrollEvent(final int x, final int y, final int absX, final int absY, final int modifierFlags, final double deltaX, final double deltaY, final int scrollPhase) { int jmodifiers = NSEvent.nsToJavaModifiers(modifierFlags); final boolean isShift = (jmodifiers & InputEvent.SHIFT_DOWN_MASK) != 0; int roundDeltaX = deltaAccumulatorX.getRoundedDelta(deltaX, scrollPhase); int roundDeltaY = deltaAccumulatorY.getRoundedDelta(deltaY, scrollPhase); // Vertical scroll. if (!isShift && (deltaY != 0.0 || roundDeltaY != 0)) { dispatchScrollEvent(x, y, absX, absY, jmodifiers, roundDeltaY, deltaY); } // Horizontal scroll or shirt+vertical scroll. final double delta = isShift && deltaY != 0.0 ? deltaY : deltaX; final int roundDelta = isShift && roundDeltaY != 0.0 ? roundDeltaY : roundDeltaX; if (delta != 0.0 || roundDelta != 0) { jmodifiers |= InputEvent.SHIFT_DOWN_MASK; dispatchScrollEvent(x, y, absX, absY, jmodifiers, roundDelta, delta); } } private void dispatchScrollEvent(final int x, final int y, final int absX, final int absY, final int modifiers, final int roundDelta, final double delta) { final long when = System.currentTimeMillis(); final int scrollType = MouseWheelEvent.WHEEL_UNIT_SCROLL; final int scrollAmount = 1; // invert the wheelRotation for the peer eventNotifier.notifyMouseWheelEvent(when, x, y, absX, absY, modifiers, scrollType, scrollAmount, -roundDelta, -delta, null); } /** * Handles key events. */ void handleKeyEvent(int eventType, int modifierFlags, String chars, String charsIgnoringModifiers, short keyCode, boolean needsKeyTyped, boolean needsKeyReleased) { boolean isFlagsChangedEvent = isNpapiCallback ? (eventType == CocoaConstants.NPCocoaEventFlagsChanged) : (eventType == CocoaConstants.NSFlagsChanged); int jeventType = KeyEvent.KEY_PRESSED; int jkeyCode = KeyEvent.VK_UNDEFINED; int jkeyLocation = KeyEvent.KEY_LOCATION_UNKNOWN; boolean postsTyped = false; boolean spaceKeyTyped = false; char testChar = KeyEvent.CHAR_UNDEFINED; boolean isDeadChar = (chars!= null && chars.length() == 0); if (isFlagsChangedEvent) { int[] in = new int[] {modifierFlags, keyCode}; int[] out = new int[3]; // [jkeyCode, jkeyLocation, jkeyType] NSEvent.nsKeyModifiersToJavaKeyInfo(in, out); jkeyCode = out[0]; jkeyLocation = out[1]; jeventType = out[2]; } else { if (chars != null && chars.length() > 0) { testChar = chars.charAt(0); //Check if String chars contains SPACE character. if (chars.trim().isEmpty()) { spaceKeyTyped = true; } } char testCharIgnoringModifiers = charsIgnoringModifiers != null && charsIgnoringModifiers.length() > 0 ? charsIgnoringModifiers.charAt(0) : KeyEvent.CHAR_UNDEFINED; int[] in = new int[] {testCharIgnoringModifiers, isDeadChar ? 1 : 0, modifierFlags, keyCode}; int[] out = new int[3]; // [jkeyCode, jkeyLocation, deadChar] postsTyped = NSEvent.nsToJavaKeyInfo(in, out); if (!postsTyped) { testChar = KeyEvent.CHAR_UNDEFINED; } if(isDeadChar){ testChar = (char) out[2]; if(testChar == 0){ return; } } // If Pinyin Simplified input method is selected, CAPS_LOCK key is supposed to switch // input to latin letters. // It is necessary to use testCharIgnoringModifiers instead of testChar for event // generation in such case to avoid uppercase letters in text components. LWCToolkit lwcToolkit = (LWCToolkit)Toolkit.getDefaultToolkit(); if (lwcToolkit.getLockingKeyState(KeyEvent.VK_CAPS_LOCK) && Locale.SIMPLIFIED_CHINESE.equals(lwcToolkit.getDefaultKeyboardLocale())) { testChar = testCharIgnoringModifiers; } jkeyCode = out[0]; jkeyLocation = out[1]; jeventType = isNpapiCallback ? NSEvent.npToJavaEventType(eventType) : NSEvent.nsToJavaEventType(eventType); } char javaChar = NSEvent.nsToJavaChar(testChar, modifierFlags, spaceKeyTyped); // Some keys may generate a KEY_TYPED, but we can't determine // what that character is. That's likely a bug, but for now we // just check for CHAR_UNDEFINED. if (javaChar == KeyEvent.CHAR_UNDEFINED) { postsTyped = false; } int jmodifiers = NSEvent.nsToJavaModifiers(modifierFlags); long when = System.currentTimeMillis(); if (jeventType == KeyEvent.KEY_PRESSED) { lastKeyPressCode = jkeyCode; } eventNotifier.notifyKeyEvent(jeventType, when, jmodifiers, jkeyCode, javaChar, jkeyLocation); // Current browser may be sending input events, so don't // post the KEY_TYPED here. postsTyped &= needsKeyTyped; // That's the reaction on the PRESSED (not RELEASED) event as it comes to // appear in MacOSX. // Modifier keys (shift, etc) don't want to send TYPED events. // On the other hand we don't want to generate keyTyped events // for clipboard related shortcuts like Meta + [CVX] if (jeventType == KeyEvent.KEY_PRESSED && postsTyped && (jmodifiers & KeyEvent.META_DOWN_MASK) == 0) { // Enter and Space keys finish the input method processing, // KEY_TYPED and KEY_RELEASED events for them are synthesized in handleInputEvent. if (needsKeyReleased && (jkeyCode == KeyEvent.VK_ENTER || jkeyCode == KeyEvent.VK_SPACE)) { return; } eventNotifier.notifyKeyEvent(KeyEvent.KEY_TYPED, when, jmodifiers, KeyEvent.VK_UNDEFINED, javaChar, KeyEvent.KEY_LOCATION_UNKNOWN); //If events come from Firefox, released events should also be generated. if (needsKeyReleased) { eventNotifier.notifyKeyEvent(KeyEvent.KEY_RELEASED, when, jmodifiers, jkeyCode, javaChar, KeyEvent.KEY_LOCATION_UNKNOWN); } } } void handleInputEvent(String text) { if (text != null) { int index = 0, length = text.length(); char c = 0; while (index < length) { c = text.charAt(index); eventNotifier.notifyKeyEvent(KeyEvent.KEY_TYPED, System.currentTimeMillis(), 0, KeyEvent.VK_UNDEFINED, c, KeyEvent.KEY_LOCATION_UNKNOWN); index++; } eventNotifier.notifyKeyEvent(KeyEvent.KEY_RELEASED, System.currentTimeMillis(), 0, lastKeyPressCode, c, KeyEvent.KEY_LOCATION_UNKNOWN); } } void handleWindowFocusEvent(boolean gained, LWWindowPeer opposite) { eventNotifier.notifyActivation(gained, opposite); } static class DeltaAccumulator { static final double MIN_THRESHOLD = 0.1; static final double MAX_THRESHOLD = 0.8; double accumulatedDelta; int getRoundedDelta(double delta, int scrollPhase) { int roundDelta = (int) Math.round(delta); if (scrollPhase == NSEvent.SCROLL_PHASE_UNSUPPORTED) { // mouse wheel if (roundDelta == 0 && delta != 0) { roundDelta = delta > 0 ? 1 : -1; } } else { // trackpad boolean begin = scrollPhase == NSEvent.SCROLL_PHASE_BEGAN; boolean end = scrollPhase == NSEvent.SCROLL_MASK_PHASE_ENDED || scrollPhase == NSEvent.SCROLL_MASK_PHASE_CANCELLED; if (begin) { accumulatedDelta = 0; } accumulatedDelta += delta; double absAccumulatedDelta = Math.abs(accumulatedDelta); if (absAccumulatedDelta > MAX_THRESHOLD) { roundDelta = (int) Math.round(accumulatedDelta); accumulatedDelta -= roundDelta; } if (end) { if (roundDelta == 0 && absAccumulatedDelta > MIN_THRESHOLD) { roundDelta = accumulatedDelta > 0 ? 1 : -1; } accumulatedDelta = 0; } } return roundDelta; } } }