1/*
2 * Copyright (C) 2010, 2011 Apple Inc. All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
6 * are met:
7 * 1. Redistributions of source code must retain the above copyright
8 *    notice, this list of conditions and the following disclaimer.
9 * 2. Redistributions in binary form must reproduce the above copyright
10 *    notice, this list of conditions and the following disclaimer in the
11 *    documentation and/or other materials provided with the distribution.
12 *
13 * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
14 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
15 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
17 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
23 * THE POSSIBILITY OF SUCH DAMAGE.
24 */
25
26#import "config.h"
27#import "WebEventFactory.h"
28
29#if USE(APPKIT)
30
31#import "WebKitSystemInterface.h"
32#import <wtf/ASCIICType.h>
33#import <WebCore/PlatformEventFactoryMac.h>
34#import <WebCore/Scrollbar.h>
35#import <WebCore/WindowsKeyboardCodes.h>
36
37using namespace WebCore;
38
39@interface NSEvent (WebNSEventDetails)
40- (NSInteger)_scrollCount;
41- (CGFloat)_unacceleratedScrollingDeltaX;
42- (CGFloat)_unacceleratedScrollingDeltaY;
43@end
44
45namespace WebKit {
46
47// FIXME: This is a huge copy/paste from WebCore/PlatformEventFactoryMac.mm. The code should be merged.
48
49static WebMouseEvent::Button currentMouseButton()
50{
51    NSUInteger pressedMouseButtons = [NSEvent pressedMouseButtons];
52    if (!pressedMouseButtons)
53        return WebMouseEvent::NoButton;
54    if (pressedMouseButtons == 1 << 0)
55        return WebMouseEvent::LeftButton;
56    if (pressedMouseButtons == 1 << 1)
57        return WebMouseEvent::RightButton;
58    return WebMouseEvent::MiddleButton;
59}
60
61static WebMouseEvent::Button mouseButtonForEvent(NSEvent *event)
62{
63    switch ([event type]) {
64        case NSLeftMouseDown:
65        case NSLeftMouseUp:
66        case NSLeftMouseDragged:
67            return WebMouseEvent::LeftButton;
68        case NSRightMouseDown:
69        case NSRightMouseUp:
70        case NSRightMouseDragged:
71            return WebMouseEvent::RightButton;
72        case NSOtherMouseDown:
73        case NSOtherMouseUp:
74        case NSOtherMouseDragged:
75            return WebMouseEvent::MiddleButton;
76        case NSMouseEntered:
77        case NSMouseExited:
78            return currentMouseButton();
79        default:
80            return WebMouseEvent::NoButton;
81    }
82}
83
84static WebEvent::Type mouseEventTypeForEvent(NSEvent* event)
85{
86    switch ([event type]) {
87        case NSLeftMouseDragged:
88        case NSMouseEntered:
89        case NSMouseExited:
90        case NSMouseMoved:
91        case NSOtherMouseDragged:
92        case NSRightMouseDragged:
93            return WebEvent::MouseMove;
94        case NSLeftMouseDown:
95        case NSRightMouseDown:
96        case NSOtherMouseDown:
97            return WebEvent::MouseDown;
98        case NSLeftMouseUp:
99        case NSRightMouseUp:
100        case NSOtherMouseUp:
101            return WebEvent::MouseUp;
102        default:
103            return WebEvent::MouseMove;
104    }
105}
106
107static int clickCountForEvent(NSEvent *event)
108{
109    switch ([event type]) {
110        case NSLeftMouseDown:
111        case NSLeftMouseUp:
112        case NSLeftMouseDragged:
113        case NSRightMouseDown:
114        case NSRightMouseUp:
115        case NSRightMouseDragged:
116        case NSOtherMouseDown:
117        case NSOtherMouseUp:
118        case NSOtherMouseDragged:
119            return [event clickCount];
120        default:
121            return 0;
122    }
123}
124
125static NSScreen *screenForWindow(NSWindow *window)
126{
127    NSScreen *screen = [window screen]; // nil if the window is off-screen
128    if (screen)
129        return screen;
130
131    NSArray *screens = [NSScreen screens];
132    if ([screens count] > 0)
133        return [screens objectAtIndex:0]; // screen containing the menubar
134
135    return nil;
136}
137
138static NSPoint flipScreenPoint(const NSPoint& screenPoint, NSScreen *screen)
139{
140    NSPoint flippedPoint = screenPoint;
141    flippedPoint.y = NSMaxY([screen frame]) - flippedPoint.y;
142    return flippedPoint;
143}
144
145static NSPoint globalPoint(const NSPoint& windowPoint, NSWindow *window)
146{
147    return flipScreenPoint([window convertBaseToScreen:windowPoint], screenForWindow(window));
148}
149
150static NSPoint globalPointForEvent(NSEvent *event)
151{
152    switch ([event type]) {
153        case NSLeftMouseDown:
154        case NSLeftMouseDragged:
155        case NSLeftMouseUp:
156        case NSMouseEntered:
157        case NSMouseExited:
158        case NSMouseMoved:
159        case NSOtherMouseDown:
160        case NSOtherMouseDragged:
161        case NSOtherMouseUp:
162        case NSRightMouseDown:
163        case NSRightMouseDragged:
164        case NSRightMouseUp:
165        case NSScrollWheel:
166            return globalPoint([event locationInWindow], [event window]);
167        default:
168            return NSZeroPoint;
169    }
170}
171
172static NSPoint pointForEvent(NSEvent *event, NSView *windowView)
173{
174    switch ([event type]) {
175        case NSLeftMouseDown:
176        case NSLeftMouseDragged:
177        case NSLeftMouseUp:
178        case NSMouseEntered:
179        case NSMouseExited:
180        case NSMouseMoved:
181        case NSOtherMouseDown:
182        case NSOtherMouseDragged:
183        case NSOtherMouseUp:
184        case NSRightMouseDown:
185        case NSRightMouseDragged:
186        case NSRightMouseUp:
187        case NSScrollWheel: {
188            // Note: This will have its origin at the bottom left of the window unless windowView is flipped.
189            // In those cases, the Y coordinate gets flipped by Widget::convertFromContainingWindow.
190            NSPoint location = [event locationInWindow];
191            if (windowView)
192                location = [windowView convertPoint:location fromView:nil];
193            return location;
194        }
195        default:
196            return NSZeroPoint;
197    }
198}
199
200static WebWheelEvent::Phase phaseForEvent(NSEvent *event)
201{
202    uint32_t phase = WebWheelEvent::PhaseNone;
203    if ([event phase] & NSEventPhaseBegan)
204        phase |= WebWheelEvent::PhaseBegan;
205    if ([event phase] & NSEventPhaseStationary)
206        phase |= WebWheelEvent::PhaseStationary;
207    if ([event phase] & NSEventPhaseChanged)
208        phase |= WebWheelEvent::PhaseChanged;
209    if ([event phase] & NSEventPhaseEnded)
210        phase |= WebWheelEvent::PhaseEnded;
211    if ([event phase] & NSEventPhaseCancelled)
212        phase |= WebWheelEvent::PhaseCancelled;
213#if __MAC_OS_X_VERSION_MIN_REQUIRED >= 1080
214    if ([event phase] & NSEventPhaseMayBegin)
215        phase |= WebWheelEvent::PhaseMayBegin;
216#endif
217
218    return static_cast<WebWheelEvent::Phase>(phase);
219}
220
221static WebWheelEvent::Phase momentumPhaseForEvent(NSEvent *event)
222{
223    uint32_t phase = WebWheelEvent::PhaseNone;
224
225    if ([event momentumPhase] & NSEventPhaseBegan)
226        phase |= WebWheelEvent::PhaseBegan;
227    if ([event momentumPhase] & NSEventPhaseStationary)
228        phase |= WebWheelEvent::PhaseStationary;
229    if ([event momentumPhase] & NSEventPhaseChanged)
230        phase |= WebWheelEvent::PhaseChanged;
231    if ([event momentumPhase] & NSEventPhaseEnded)
232        phase |= WebWheelEvent::PhaseEnded;
233    if ([event momentumPhase] & NSEventPhaseCancelled)
234        phase |= WebWheelEvent::PhaseCancelled;
235
236    return static_cast<WebWheelEvent::Phase>(phase);
237}
238
239#if ENABLE(GESTURE_EVENTS)
240static WebEvent::Type gestureEventTypeForEvent(NSEvent *event)
241{
242    switch ([event type]) {
243    case NSEventTypeBeginGesture:
244        return WebEvent::GestureScrollBegin;
245    case NSEventTypeEndGesture:
246        return WebEvent::GestureScrollEnd;
247    default:
248        ASSERT_NOT_REACHED();
249        return WebEvent::GestureScrollEnd;
250    }
251}
252#endif
253
254static inline String textFromEvent(NSEvent* event)
255{
256    if ([event type] == NSFlagsChanged)
257        return String("");
258    return String([event characters]);
259}
260
261static inline String unmodifiedTextFromEvent(NSEvent* event)
262{
263    if ([event type] == NSFlagsChanged)
264        return String("");
265    return String([event charactersIgnoringModifiers]);
266}
267
268static bool isKeypadEvent(NSEvent* event)
269{
270    // Check that this is the type of event that has a keyCode.
271    switch ([event type]) {
272        case NSKeyDown:
273        case NSKeyUp:
274        case NSFlagsChanged:
275            break;
276        default:
277            return false;
278    }
279
280    switch ([event keyCode]) {
281        case 71: // Clear
282        case 81: // =
283        case 75: // /
284        case 67: // *
285        case 78: // -
286        case 69: // +
287        case 76: // Enter
288        case 65: // .
289        case 82: // 0
290        case 83: // 1
291        case 84: // 2
292        case 85: // 3
293        case 86: // 4
294        case 87: // 5
295        case 88: // 6
296        case 89: // 7
297        case 91: // 8
298        case 92: // 9
299            return true;
300     }
301
302     return false;
303}
304
305static inline bool isKeyUpEvent(NSEvent *event)
306{
307    if ([event type] != NSFlagsChanged)
308        return [event type] == NSKeyUp;
309    // FIXME: This logic fails if the user presses both Shift keys at once, for example:
310    // we treat releasing one of them as keyDown.
311    switch ([event keyCode]) {
312        case 54: // Right Command
313        case 55: // Left Command
314            return ([event modifierFlags] & NSCommandKeyMask) == 0;
315
316        case 57: // Capslock
317            return ([event modifierFlags] & NSAlphaShiftKeyMask) == 0;
318
319        case 56: // Left Shift
320        case 60: // Right Shift
321            return ([event modifierFlags] & NSShiftKeyMask) == 0;
322
323        case 58: // Left Alt
324        case 61: // Right Alt
325            return ([event modifierFlags] & NSAlternateKeyMask) == 0;
326
327        case 59: // Left Ctrl
328        case 62: // Right Ctrl
329            return ([event modifierFlags] & NSControlKeyMask) == 0;
330
331        case 63: // Function
332            return ([event modifierFlags] & NSFunctionKeyMask) == 0;
333    }
334    return false;
335}
336
337static inline WebEvent::Modifiers modifiersForEvent(NSEvent *event)
338{
339    unsigned modifiers = 0;
340    if ([event modifierFlags] & NSAlphaShiftKeyMask)
341        modifiers |= WebEvent::CapsLockKey;
342    if ([event modifierFlags] & NSShiftKeyMask)
343        modifiers |= WebEvent::ShiftKey;
344    if ([event modifierFlags] & NSControlKeyMask)
345        modifiers |= WebEvent::ControlKey;
346    if ([event modifierFlags] & NSAlternateKeyMask)
347        modifiers |= WebEvent::AltKey;
348    if ([event modifierFlags] & NSCommandKeyMask)
349        modifiers |= WebEvent::MetaKey;
350    return (WebEvent::Modifiers)modifiers;
351}
352
353WebMouseEvent WebEventFactory::createWebMouseEvent(NSEvent *event, NSView *windowView)
354{
355    NSPoint position = pointForEvent(event, windowView);
356    NSPoint globalPosition = globalPointForEvent(event);
357
358    WebEvent::Type type                     = mouseEventTypeForEvent(event);
359    WebMouseEvent::Button button            = mouseButtonForEvent(event);
360    float deltaX                            = [event deltaX];
361    float deltaY                            = [event deltaY];
362    float deltaZ                            = [event deltaZ];
363    int clickCount                          = clickCountForEvent(event);
364    WebEvent::Modifiers modifiers           = modifiersForEvent(event);
365    double timestamp                        = [event timestamp];
366
367    return WebMouseEvent(type, button, IntPoint(position), IntPoint(globalPosition), deltaX, deltaY, deltaZ, clickCount, modifiers, timestamp);
368}
369
370WebWheelEvent WebEventFactory::createWebWheelEvent(NSEvent *event, NSView *windowView)
371{
372    NSPoint position = pointForEvent(event, windowView);
373    NSPoint globalPosition = globalPointForEvent(event);
374
375    BOOL continuous;
376    float deltaX = 0;
377    float deltaY = 0;
378    float wheelTicksX = 0;
379    float wheelTicksY = 0;
380
381    WKGetWheelEventDeltas(event, &deltaX, &deltaY, &continuous);
382
383    if (continuous) {
384        // smooth scroll events
385        wheelTicksX = deltaX / static_cast<float>(Scrollbar::pixelsPerLineStep());
386        wheelTicksY = deltaY / static_cast<float>(Scrollbar::pixelsPerLineStep());
387    } else {
388        // plain old wheel events
389        wheelTicksX = deltaX;
390        wheelTicksY = deltaY;
391        deltaX *= static_cast<float>(Scrollbar::pixelsPerLineStep());
392        deltaY *= static_cast<float>(Scrollbar::pixelsPerLineStep());
393    }
394
395    WebWheelEvent::Granularity granularity  = WebWheelEvent::ScrollByPixelWheelEvent;
396
397#if HAVE(INVERTED_WHEEL_EVENTS)
398    bool directionInvertedFromDevice        = [event isDirectionInvertedFromDevice];
399#else
400    bool directionInvertedFromDevice        = false;
401#endif
402
403    WebWheelEvent::Phase phase              = phaseForEvent(event);
404    WebWheelEvent::Phase momentumPhase      = momentumPhaseForEvent(event);
405    bool hasPreciseScrollingDeltas          = continuous;
406
407    uint32_t scrollCount;
408    FloatSize unacceleratedScrollingDelta;
409
410    static bool nsEventSupportsScrollCount = [NSEvent instancesRespondToSelector:@selector(_scrollCount)];
411    if (nsEventSupportsScrollCount) {
412        scrollCount = [event _scrollCount];
413        unacceleratedScrollingDelta = FloatSize([event _unacceleratedScrollingDeltaX], [event _unacceleratedScrollingDeltaY]);
414    } else {
415        scrollCount = 0;
416        unacceleratedScrollingDelta = FloatSize(deltaX, deltaY);
417    }
418
419    WebEvent::Modifiers modifiers           = modifiersForEvent(event);
420    double timestamp                        = [event timestamp];
421
422    return WebWheelEvent(WebEvent::Wheel, IntPoint(position), IntPoint(globalPosition), FloatSize(deltaX, deltaY), FloatSize(wheelTicksX, wheelTicksY), granularity, directionInvertedFromDevice, phase, momentumPhase, hasPreciseScrollingDeltas, scrollCount, unacceleratedScrollingDelta, modifiers, timestamp);
423}
424
425WebKeyboardEvent WebEventFactory::createWebKeyboardEvent(NSEvent *event, NSView *)
426{
427    WebEvent::Type type             = isKeyUpEvent(event) ? WebEvent::KeyUp : WebEvent::KeyDown;
428    String text                     = textFromEvent(event);
429    String unmodifiedText           = unmodifiedTextFromEvent(event);
430    String keyIdentifier            = keyIdentifierForKeyEvent(event);
431    int windowsVirtualKeyCode       = windowsKeyCodeForKeyEvent(event);
432    int nativeVirtualKeyCode        = [event keyCode];
433    int macCharCode                 = WKGetNSEventKeyChar(event);
434    bool autoRepeat                 = ([event type] != NSFlagsChanged) && [event isARepeat];
435    bool isKeypad                   = isKeypadEvent(event);
436    bool isSystemKey                = false; // SystemKey is always false on the Mac.
437    WebEvent::Modifiers modifiers   = modifiersForEvent(event);
438    double timestamp                = [event timestamp];
439
440    // Always use 13 for Enter/Return -- we don't want to use AppKit's different character for Enter.
441    if (windowsVirtualKeyCode == VK_RETURN) {
442        text = "\r";
443        unmodifiedText = "\r";
444    }
445
446    // AppKit sets text to "\x7F" for backspace, but the correct KeyboardEvent character code is 8.
447    if (windowsVirtualKeyCode == VK_BACK) {
448        text = "\x8";
449        unmodifiedText = "\x8";
450    }
451
452    // Always use 9 for Tab -- we don't want to use AppKit's different character for shift-tab.
453    if (windowsVirtualKeyCode == VK_TAB) {
454        text = "\x9";
455        unmodifiedText = "\x9";
456    }
457
458    return WebKeyboardEvent(type, text, unmodifiedText, keyIdentifier, windowsVirtualKeyCode, nativeVirtualKeyCode, macCharCode, autoRepeat, isKeypad, isSystemKey, modifiers, timestamp);
459}
460
461#if ENABLE(GESTURE_EVENTS)
462WebGestureEvent WebEventFactory::createWebGestureEvent(NSEvent *event, NSView *windowView)
463{
464    WebEvent::Type type             = gestureEventTypeForEvent(event);
465    NSPoint position                = pointForEvent(event, windowView);
466    NSPoint globalPosition          = globalPointForEvent(event);
467    WebEvent::Modifiers modifiers   = modifiersForEvent(event);
468    double timestamp                = [event timestamp];
469
470    return WebGestureEvent(type, IntPoint(position), IntPoint(globalPosition), modifiers, timestamp);
471}
472#endif
473
474} // namespace WebKit
475
476#endif // USE(APPKIT)
477