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
26package sun.lwawt.macosx;
27
28import sun.awt.SunToolkit;
29import sun.lwawt.LWWindowPeer;
30import sun.lwawt.PlatformEventNotifier;
31
32import java.awt.Toolkit;
33import java.awt.event.MouseEvent;
34import java.awt.event.InputEvent;
35import java.awt.event.MouseWheelEvent;
36import java.awt.event.KeyEvent;
37import java.util.Locale;
38
39/**
40 * Translates NSEvents/NPCocoaEvents into AWT events.
41 */
42final 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 || roundDeltaY != 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 ? roundDeltaY : roundDeltaX;
109        if (delta != 0.0 || roundDelta != 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        double accumulatedDelta;
269        boolean accumulate;
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                if (scrollPhase == NSEvent.SCROLL_PHASE_BEGAN) {
281                    accumulatedDelta = 0;
282                    accumulate = true;
283                }
284                else if (scrollPhase == NSEvent.SCROLL_PHASE_MOMENTUM_BEGAN) {
285                    accumulate = true;
286                }
287                if (accumulate) {
288
289                    accumulatedDelta += delta;
290
291                    roundDelta = (int) Math.round(accumulatedDelta);
292
293                    accumulatedDelta -= roundDelta;
294
295                    if (scrollPhase == NSEvent.SCROLL_PHASE_ENDED) {
296                        accumulate = false;
297                    }
298                }
299            }
300
301            return roundDelta;
302        }
303    }
304}
305