1 /* 2 * Copyright (c) 2011, 2014, 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 absoluteX, 61 int absoluteY) { 62 final SunToolkit tk = (SunToolkit)Toolkit.getDefaultToolkit(); 63 if ((buttonNumber > 2 && !tk.areExtraMouseButtonsEnabled()) 64 || buttonNumber > tk.getNumberOfButtons() - 1) { 65 return; 66 } 67 68 int jeventType = isNpapiCallback ? NSEvent.npToJavaEventType(eventType) : 69 NSEvent.nsToJavaEventType(eventType); 70 71 int jbuttonNumber = MouseEvent.NOBUTTON; 72 int jclickCount = 0; 73 74 if (jeventType != MouseEvent.MOUSE_MOVED && 75 jeventType != MouseEvent.MOUSE_ENTERED && 76 jeventType != MouseEvent.MOUSE_EXITED) 77 { 78 jbuttonNumber = NSEvent.nsToJavaButton(buttonNumber); 79 jclickCount = clickCount; 80 } 81 82 int jmodifiers = NSEvent.nsToJavaMouseModifiers(buttonNumber, 83 modifierFlags); 84 boolean jpopupTrigger = NSEvent.isPopupTrigger(jmodifiers); 85 86 eventNotifier.notifyMouseEvent(jeventType, System.currentTimeMillis(), jbuttonNumber, 87 x, y, absoluteX, absoluteY, jmodifiers, jclickCount, 88 jpopupTrigger, null); 89 } 90 91 /** 92 * Handles scroll events. 93 */ 94 void handleScrollEvent(final int x, final int y, final int modifierFlags, 95 final double deltaX, final double deltaY, 96 final int scrollPhase) { 97 final int buttonNumber = CocoaConstants.kCGMouseButtonCenter; 98 int jmodifiers = NSEvent.nsToJavaMouseModifiers(buttonNumber, 99 modifierFlags); 100 final boolean isShift = (jmodifiers & InputEvent.SHIFT_DOWN_MASK) != 0; 101 102 int roundDeltaX = deltaAccumulatorX.getRoundedDelta(deltaX, scrollPhase); 103 int roundDeltaY = deltaAccumulatorY.getRoundedDelta(deltaY, scrollPhase); 104 105 // Vertical scroll. 106 if (!isShift && (deltaY != 0.0 || roundDeltaY != 0)) { 107 dispatchScrollEvent(x, y, jmodifiers, roundDeltaY, deltaY); 108 } 109 // Horizontal scroll or shirt+vertical scroll. 110 final double delta = isShift && deltaY != 0.0 ? deltaY : deltaX; 111 final int roundDelta = isShift && roundDeltaY != 0 ? roundDeltaY : roundDeltaX; 112 if (delta != 0.0 || roundDelta != 0) { 113 jmodifiers |= InputEvent.SHIFT_DOWN_MASK; 114 dispatchScrollEvent(x, y, jmodifiers, roundDelta, delta); 115 } 116 } 117 118 private void dispatchScrollEvent(final int x, final int y, 119 final int modifiers, 120 final int roundDelta, final double delta) { 121 final long when = System.currentTimeMillis(); 122 final int scrollType = MouseWheelEvent.WHEEL_UNIT_SCROLL; 123 final int scrollAmount = 1; 124 // invert the wheelRotation for the peer 125 eventNotifier.notifyMouseWheelEvent(when, x, y, modifiers, scrollType, 126 scrollAmount, -roundDelta, -delta, null); 127 } 128 129 /** 130 * Handles key events. 131 */ 132 void handleKeyEvent(int eventType, int modifierFlags, String chars, String charsIgnoringModifiers, 133 short keyCode, boolean needsKeyTyped, boolean needsKeyReleased) { 134 boolean isFlagsChangedEvent = 135 isNpapiCallback ? (eventType == CocoaConstants.NPCocoaEventFlagsChanged) : 136 (eventType == CocoaConstants.NSFlagsChanged); 137 138 int jeventType = KeyEvent.KEY_PRESSED; 139 int jkeyCode = KeyEvent.VK_UNDEFINED; 140 int jkeyLocation = KeyEvent.KEY_LOCATION_UNKNOWN; 141 boolean postsTyped = false; 142 143 char testChar = KeyEvent.CHAR_UNDEFINED; 144 boolean isDeadChar = (chars!= null && chars.length() == 0); 145 146 if (isFlagsChangedEvent) { 147 int[] in = new int[] {modifierFlags, keyCode}; 148 int[] out = new int[3]; // [jkeyCode, jkeyLocation, jkeyType] 149 150 NSEvent.nsKeyModifiersToJavaKeyInfo(in, out); 151 152 jkeyCode = out[0]; 153 jkeyLocation = out[1]; 154 jeventType = out[2]; 155 } else { 156 if (chars != null && chars.length() > 0) { 157 testChar = chars.charAt(0); 158 } 159 160 char testCharIgnoringModifiers = charsIgnoringModifiers != null && charsIgnoringModifiers.length() > 0 ? 161 charsIgnoringModifiers.charAt(0) : KeyEvent.CHAR_UNDEFINED; 162 163 int[] in = new int[] {testCharIgnoringModifiers, isDeadChar ? 1 : 0, modifierFlags, keyCode}; 164 int[] out = new int[3]; // [jkeyCode, jkeyLocation, deadChar] 165 166 postsTyped = NSEvent.nsToJavaKeyInfo(in, out); 167 if (!postsTyped) { 168 testChar = KeyEvent.CHAR_UNDEFINED; 169 } 170 171 if(isDeadChar){ 172 testChar = (char) out[2]; 173 if(testChar == 0){ 174 return; 175 } 176 } 177 178 // If Pinyin Simplified input method is selected, CAPS_LOCK key is supposed to switch 179 // input to latin letters. 180 // It is necessary to use testCharIgnoringModifiers instead of testChar for event 181 // generation in such case to avoid uppercase letters in text components. 182 LWCToolkit lwcToolkit = (LWCToolkit)Toolkit.getDefaultToolkit(); 183 if (lwcToolkit.getLockingKeyState(KeyEvent.VK_CAPS_LOCK) && 184 Locale.SIMPLIFIED_CHINESE.equals(lwcToolkit.getDefaultKeyboardLocale())) { 185 testChar = testCharIgnoringModifiers; 186 } 187 188 jkeyCode = out[0]; 189 jkeyLocation = out[1]; 190 jeventType = isNpapiCallback ? NSEvent.npToJavaEventType(eventType) : 191 NSEvent.nsToJavaEventType(eventType); 192 } 193 194 char javaChar = NSEvent.nsToJavaChar(testChar, modifierFlags); 195 // Some keys may generate a KEY_TYPED, but we can't determine 196 // what that character is. That's likely a bug, but for now we 197 // just check for CHAR_UNDEFINED. 198 if (javaChar == KeyEvent.CHAR_UNDEFINED) { 199 postsTyped = false; 200 } 201 202 203 int jmodifiers = NSEvent.nsToJavaKeyModifiers(modifierFlags); 204 long when = System.currentTimeMillis(); 205 206 if (jeventType == KeyEvent.KEY_PRESSED) { 207 lastKeyPressCode = jkeyCode; 208 } 209 eventNotifier.notifyKeyEvent(jeventType, when, jmodifiers, 210 jkeyCode, javaChar, jkeyLocation); 211 212 // Current browser may be sending input events, so don't 213 // post the KEY_TYPED here. 214 postsTyped &= needsKeyTyped; 215 216 // That's the reaction on the PRESSED (not RELEASED) event as it comes to 217 // appear in MacOSX. 218 // Modifier keys (shift, etc) don't want to send TYPED events. 219 // On the other hand we don't want to generate keyTyped events 220 // for clipboard related shortcuts like Meta + [CVX] 221 if (jeventType == KeyEvent.KEY_PRESSED && postsTyped && 222 (jmodifiers & KeyEvent.META_DOWN_MASK) == 0) { 223 // Enter and Space keys finish the input method processing, 224 // KEY_TYPED and KEY_RELEASED events for them are synthesized in handleInputEvent. 225 if (needsKeyReleased && (jkeyCode == KeyEvent.VK_ENTER || jkeyCode == KeyEvent.VK_SPACE)) { 226 return; 227 } 228 eventNotifier.notifyKeyEvent(KeyEvent.KEY_TYPED, when, jmodifiers, 229 KeyEvent.VK_UNDEFINED, javaChar, 230 KeyEvent.KEY_LOCATION_UNKNOWN); 231 //If events come from Firefox, released events should also be generated. 232 if (needsKeyReleased) { 233 eventNotifier.notifyKeyEvent(KeyEvent.KEY_RELEASED, when, jmodifiers, 234 jkeyCode, javaChar, 235 KeyEvent.KEY_LOCATION_UNKNOWN); 236 } 237 } 238 } 239 240 void handleInputEvent(String text) { 241 if (text != null) { 242 int index = 0, length = text.length(); 243 char c = 0; 244 while (index < length) { 245 c = text.charAt(index); 246 eventNotifier.notifyKeyEvent(KeyEvent.KEY_TYPED, 247 System.currentTimeMillis(), 248 0, KeyEvent.VK_UNDEFINED, c, 249 KeyEvent.KEY_LOCATION_UNKNOWN); 250 index++; 251 } 252 eventNotifier.notifyKeyEvent(KeyEvent.KEY_RELEASED, 253 System.currentTimeMillis(), 254 0, lastKeyPressCode, c, 255 KeyEvent.KEY_LOCATION_UNKNOWN); 256 } 257 } 258 259 void handleWindowFocusEvent(boolean gained, LWWindowPeer opposite) { 260 eventNotifier.notifyActivation(gained, opposite); 261 } 262 263 static class DeltaAccumulator { 264 265 static final double MIN_THRESHOLD = 0.1; 266 static final double MAX_THRESHOLD = 0.5; 267 double accumulatedDelta; 268 269 int getRoundedDelta(double delta, int scrollPhase) { 270 271 int roundDelta = (int) Math.round(delta); 272 273 if (scrollPhase == NSEvent.SCROLL_PHASE_UNSUPPORTED) { // mouse wheel 274 if (roundDelta == 0 && delta != 0) { 275 roundDelta = delta > 0 ? 1 : -1; 276 } 277 } else { // trackpad 278 boolean begin = scrollPhase == NSEvent.SCROLL_PHASE_BEGAN; 279 boolean end = scrollPhase == NSEvent.SCROLL_MASK_PHASE_ENDED 280 || scrollPhase == NSEvent.SCROLL_MASK_PHASE_CANCELLED; 281 282 if (begin) { 283 accumulatedDelta = 0; 284 } 285 286 accumulatedDelta += delta; 287 288 double absAccumulatedDelta = Math.abs(accumulatedDelta); 289 if (absAccumulatedDelta > MAX_THRESHOLD) { 290 roundDelta = (int) Math.round(accumulatedDelta); 291 accumulatedDelta -= roundDelta; 292 } 293 294 if (end) { 295 if (roundDelta == 0 && absAccumulatedDelta > MIN_THRESHOLD) { 296 roundDelta = accumulatedDelta > 0 ? 1 : -1; 297 } 298 } 299 } 300 301 return roundDelta; 302 } 303 } 304 }