1 /* 2 * Copyright (c) 2011, 2016, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package sun.lwawt.macosx; 27 28 import sun.awt.SunToolkit; 29 import sun.lwawt.LWWindowPeer; 30 import sun.lwawt.PlatformEventNotifier; 31 32 import java.awt.Toolkit; 33 import java.awt.event.MouseEvent; 34 import java.awt.event.InputEvent; 35 import java.awt.event.MouseWheelEvent; 36 import java.awt.event.KeyEvent; 37 import java.util.Locale; 38 39 /** 40 * Translates NSEvents/NPCocoaEvents into AWT events. 41 */ 42 final class CPlatformResponder { 43 44 private final PlatformEventNotifier eventNotifier; 45 private final boolean isNpapiCallback; 46 private int lastKeyPressCode = KeyEvent.VK_UNDEFINED; 47 private final DeltaAccumulator deltaAccumulatorX = new DeltaAccumulator(); 48 private final DeltaAccumulator deltaAccumulatorY = new DeltaAccumulator(); 49 50 CPlatformResponder(final PlatformEventNotifier eventNotifier, 51 final boolean isNpapiCallback) { 52 this.eventNotifier = eventNotifier; 53 this.isNpapiCallback = isNpapiCallback; 54 } 55 56 /** 57 * Handles mouse events. 58 */ 59 void handleMouseEvent(int eventType, int modifierFlags, int buttonNumber, 60 int clickCount, int x, int y, int absX, int absY) { 61 final SunToolkit tk = (SunToolkit)Toolkit.getDefaultToolkit(); 62 if ((buttonNumber > 2 && !tk.areExtraMouseButtonsEnabled()) 63 || buttonNumber > tk.getNumberOfButtons() - 1) { 64 return; 65 } 66 67 int jeventType = isNpapiCallback ? NSEvent.npToJavaEventType(eventType) : 68 NSEvent.nsToJavaEventType(eventType); 69 70 int jbuttonNumber = MouseEvent.NOBUTTON; 71 int jclickCount = 0; 72 73 if (jeventType != MouseEvent.MOUSE_MOVED && 74 jeventType != MouseEvent.MOUSE_ENTERED && 75 jeventType != MouseEvent.MOUSE_EXITED) 76 { 77 jbuttonNumber = NSEvent.nsToJavaButton(buttonNumber); 78 jclickCount = clickCount; 79 } 80 81 int jmodifiers = NSEvent.nsToJavaModifiers(modifierFlags); 82 boolean jpopupTrigger = NSEvent.isPopupTrigger(jmodifiers); 83 84 eventNotifier.notifyMouseEvent(jeventType, System.currentTimeMillis(), jbuttonNumber, 85 x, y, absX, absY, jmodifiers, jclickCount, 86 jpopupTrigger, null); 87 } 88 89 /** 90 * Handles scroll events. 91 */ 92 void handleScrollEvent(final int x, final int y, final int absX, 93 final int absY, final int modifierFlags, 94 final double deltaX, final double deltaY, 95 final int scrollPhase) { 96 int jmodifiers = NSEvent.nsToJavaModifiers(modifierFlags); 97 final boolean isShift = (jmodifiers & InputEvent.SHIFT_DOWN_MASK) != 0; 98 99 int roundDeltaX = deltaAccumulatorX.getRoundedDelta(deltaX, scrollPhase); 100 int roundDeltaY = deltaAccumulatorY.getRoundedDelta(deltaY, scrollPhase); 101 102 // Vertical scroll. 103 if (!isShift && deltaY != 0.0) { 104 dispatchScrollEvent(x, y, absX, absY, jmodifiers, roundDeltaY, deltaY); 105 } 106 // Horizontal scroll or shirt+vertical scroll. 107 final double delta = isShift && deltaY != 0.0 ? deltaY : deltaX; 108 final int roundDelta = isShift && roundDeltaY != 0.0 ? roundDeltaY : roundDeltaX; 109 if (delta != 0.0) { 110 jmodifiers |= InputEvent.SHIFT_DOWN_MASK; 111 dispatchScrollEvent(x, y, absX, absY, jmodifiers, roundDelta, delta); 112 } 113 } 114 115 private void dispatchScrollEvent(final int x, final int y, final int absX, 116 final int absY, final int modifiers, 117 final int roundDelta, final double delta) { 118 final long when = System.currentTimeMillis(); 119 final int scrollType = MouseWheelEvent.WHEEL_UNIT_SCROLL; 120 final int scrollAmount = 1; 121 // invert the wheelRotation for the peer 122 eventNotifier.notifyMouseWheelEvent(when, x, y, absX, absY, modifiers, 123 scrollType, scrollAmount, 124 -roundDelta, -delta, null); 125 } 126 127 /** 128 * Handles key events. 129 */ 130 void handleKeyEvent(int eventType, int modifierFlags, String chars, String charsIgnoringModifiers, 131 short keyCode, boolean needsKeyTyped, boolean needsKeyReleased) { 132 boolean isFlagsChangedEvent = 133 isNpapiCallback ? (eventType == CocoaConstants.NPCocoaEventFlagsChanged) : 134 (eventType == CocoaConstants.NSFlagsChanged); 135 136 int jeventType = KeyEvent.KEY_PRESSED; 137 int jkeyCode = KeyEvent.VK_UNDEFINED; 138 int jkeyLocation = KeyEvent.KEY_LOCATION_UNKNOWN; 139 boolean postsTyped = false; 140 boolean spaceKeyTyped = false; 141 142 char testChar = KeyEvent.CHAR_UNDEFINED; 143 boolean isDeadChar = (chars!= null && chars.length() == 0); 144 145 if (isFlagsChangedEvent) { 146 int[] in = new int[] {modifierFlags, keyCode}; 147 int[] out = new int[3]; // [jkeyCode, jkeyLocation, jkeyType] 148 149 NSEvent.nsKeyModifiersToJavaKeyInfo(in, out); 150 151 jkeyCode = out[0]; 152 jkeyLocation = out[1]; 153 jeventType = out[2]; 154 } else { 155 if (chars != null && chars.length() > 0) { 156 testChar = chars.charAt(0); 157 158 //Check if String chars contains SPACE character. 159 if (chars.trim().isEmpty()) { 160 spaceKeyTyped = true; 161 } 162 } 163 164 char testCharIgnoringModifiers = charsIgnoringModifiers != null && charsIgnoringModifiers.length() > 0 ? 165 charsIgnoringModifiers.charAt(0) : KeyEvent.CHAR_UNDEFINED; 166 167 int[] in = new int[] {testCharIgnoringModifiers, isDeadChar ? 1 : 0, modifierFlags, keyCode}; 168 int[] out = new int[3]; // [jkeyCode, jkeyLocation, deadChar] 169 170 postsTyped = NSEvent.nsToJavaKeyInfo(in, out); 171 if (!postsTyped) { 172 testChar = KeyEvent.CHAR_UNDEFINED; 173 } 174 175 if(isDeadChar){ 176 testChar = (char) out[2]; 177 if(testChar == 0){ 178 return; 179 } 180 } 181 182 // If Pinyin Simplified input method is selected, CAPS_LOCK key is supposed to switch 183 // input to latin letters. 184 // It is necessary to use testCharIgnoringModifiers instead of testChar for event 185 // generation in such case to avoid uppercase letters in text components. 186 LWCToolkit lwcToolkit = (LWCToolkit)Toolkit.getDefaultToolkit(); 187 if (lwcToolkit.getLockingKeyState(KeyEvent.VK_CAPS_LOCK) && 188 Locale.SIMPLIFIED_CHINESE.equals(lwcToolkit.getDefaultKeyboardLocale())) { 189 testChar = testCharIgnoringModifiers; 190 } 191 192 jkeyCode = out[0]; 193 jkeyLocation = out[1]; 194 jeventType = isNpapiCallback ? NSEvent.npToJavaEventType(eventType) : 195 NSEvent.nsToJavaEventType(eventType); 196 } 197 198 char javaChar = NSEvent.nsToJavaChar(testChar, modifierFlags, spaceKeyTyped); 199 // Some keys may generate a KEY_TYPED, but we can't determine 200 // what that character is. That's likely a bug, but for now we 201 // just check for CHAR_UNDEFINED. 202 if (javaChar == KeyEvent.CHAR_UNDEFINED) { 203 postsTyped = false; 204 } 205 206 int jmodifiers = NSEvent.nsToJavaModifiers(modifierFlags); 207 long when = System.currentTimeMillis(); 208 209 if (jeventType == KeyEvent.KEY_PRESSED) { 210 lastKeyPressCode = jkeyCode; 211 } 212 eventNotifier.notifyKeyEvent(jeventType, when, jmodifiers, 213 jkeyCode, javaChar, jkeyLocation); 214 215 // Current browser may be sending input events, so don't 216 // post the KEY_TYPED here. 217 postsTyped &= needsKeyTyped; 218 219 // That's the reaction on the PRESSED (not RELEASED) event as it comes to 220 // appear in MacOSX. 221 // Modifier keys (shift, etc) don't want to send TYPED events. 222 // On the other hand we don't want to generate keyTyped events 223 // for clipboard related shortcuts like Meta + [CVX] 224 if (jeventType == KeyEvent.KEY_PRESSED && postsTyped && 225 (jmodifiers & KeyEvent.META_DOWN_MASK) == 0) { 226 // Enter and Space keys finish the input method processing, 227 // KEY_TYPED and KEY_RELEASED events for them are synthesized in handleInputEvent. 228 if (needsKeyReleased && (jkeyCode == KeyEvent.VK_ENTER || jkeyCode == KeyEvent.VK_SPACE)) { 229 return; 230 } 231 eventNotifier.notifyKeyEvent(KeyEvent.KEY_TYPED, when, jmodifiers, 232 KeyEvent.VK_UNDEFINED, javaChar, 233 KeyEvent.KEY_LOCATION_UNKNOWN); 234 //If events come from Firefox, released events should also be generated. 235 if (needsKeyReleased) { 236 eventNotifier.notifyKeyEvent(KeyEvent.KEY_RELEASED, when, jmodifiers, 237 jkeyCode, javaChar, 238 KeyEvent.KEY_LOCATION_UNKNOWN); 239 } 240 } 241 } 242 243 void handleInputEvent(String text) { 244 if (text != null) { 245 int index = 0, length = text.length(); 246 char c = 0; 247 while (index < length) { 248 c = text.charAt(index); 249 eventNotifier.notifyKeyEvent(KeyEvent.KEY_TYPED, 250 System.currentTimeMillis(), 251 0, KeyEvent.VK_UNDEFINED, c, 252 KeyEvent.KEY_LOCATION_UNKNOWN); 253 index++; 254 } 255 eventNotifier.notifyKeyEvent(KeyEvent.KEY_RELEASED, 256 System.currentTimeMillis(), 257 0, lastKeyPressCode, c, 258 KeyEvent.KEY_LOCATION_UNKNOWN); 259 } 260 } 261 262 void handleWindowFocusEvent(boolean gained, LWWindowPeer opposite) { 263 eventNotifier.notifyActivation(gained, opposite); 264 } 265 266 static class DeltaAccumulator { 267 268 static final double DELTA_THRESHOLD = 0.8; 269 double accumulatedDelta; 270 271 int getRoundedDelta(double delta, int scrollPhase) { 272 273 int roundDelta = (int) Math.round(delta); 274 275 if (scrollPhase == NSEvent.SCROLL_PHASE_UNSUPPORTED) { // mouse wheel 276 if (roundDelta == 0 && delta != 0) { 277 roundDelta = delta > 0 ? 1 : -1; 278 } 279 } else { // trackpad 280 boolean begin = scrollPhase == NSEvent.SCROLL_PHASE_BEGAN; 281 boolean end = scrollPhase == NSEvent.SCROLL_MASK_PHASE_ENDED 282 || scrollPhase == NSEvent.SCROLL_MASK_PHASE_CANCELLED; 283 284 if (begin) { 285 accumulatedDelta = delta; 286 } else { 287 accumulatedDelta += delta; 288 } 289 290 if (Math.abs(accumulatedDelta) > DELTA_THRESHOLD) { 291 roundDelta = (int) Math.round(accumulatedDelta); 292 accumulatedDelta -= roundDelta; 293 } 294 295 if (end) { 296 if (roundDelta == 0 && accumulatedDelta != 0) { 297 roundDelta = accumulatedDelta > 0 ? 1 : -1; 298 } 299 accumulatedDelta = 0; 300 } 301 } 302 303 return roundDelta; 304 } 305 } 306 }