WebEvent.mm   [plain text]

/*
* Copyright (C) 2009, 2010 Apple Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
* THE POSSIBILITY OF SUCH DAMAGE.
*/

// FIXME: Rename this file to WebEventIOS.mm after we upstream the iOS port and remove the PLATFORM(IOS_FAMILY)-guard.

#import "config.h"
#import "WebEvent.h"

#import "KeyEventCocoa.h"
#import <wtf/Assertions.h>

#if PLATFORM(IOS_FAMILY)

#import "KeyEventCodesIOS.h"
#import "WAKAppKitStubs.h"
#import <pal/ios/UIKitSoftLink.h>
#import <pal/spi/cocoa/IOKitSPI.h>
#import <pal/spi/ios/GraphicsServicesSPI.h>
#import <pal/spi/ios/UIKitSPI.h>

using WebCore::windowsKeyCodeForKeyCode;
using WebCore::windowsKeyCodeForCharCode;

@implementation WebEvent

@synthesize type = _type;
@synthesize timestamp = _timestamp;
@synthesize wasHandled = _wasHandled;

- (WebEvent *)initWithMouseEventType:(WebEventType)type timeStamp:(CFTimeInterval)timeStamp location:(CGPoint)point
{
return [self initWithMouseEventType:type timeStamp:timeStamp location:point modifiers:0];
}

- (WebEvent *)initWithMouseEventType:(WebEventType)type timeStamp:(CFTimeInterval)timeStamp location:(CGPoint)point modifiers:(WebEventFlags)modifiers
{
self = [super init];
if (!self)
return nil;
_type = type;
_timestamp = timeStamp;
_locationInWindow = point;
_modifierFlags = modifiers;
return self;
}

- (WebEvent *)initWithScrollWheelEventWithTimeStamp:(CFTimeInterval)timeStamp
location:(CGPoint)point
deltaX:(float)deltaX
deltaY:(float)deltaY
{
self = [super init];
if (!self)
return nil;

_type = WebEventScrollWheel;
_timestamp = timeStamp;

_locationInWindow = point;
_deltaX = deltaX;
_deltaY = deltaY;

return self;
}

- (WebEvent *)initWithTouchEventType:(WebEventType)type
timeStamp:(CFTimeInterval)timeStamp
location:(CGPoint)point
modifiers:(WebEventFlags)modifiers
touchCount:(unsigned)touchCount
touchLocations:(NSArray *)touchLocations
touchIdentifiers:(NSArray *)touchIdentifiers
touchPhases:(NSArray *)touchPhases isGesture:(BOOL)isGesture
gestureScale:(float)gestureScale
gestureRotation:(float)gestureRotation
{
self = [super init];
if (!self)
return nil;

_type = type;
_timestamp = timeStamp;
_modifierFlags = modifiers;

// FIXME: <rdar://problem/7185284> TouchEvents may be in more than one window some day.
_locationInWindow = point;
_touchCount = touchCount;
_touchLocations = [touchLocations copy];
_touchIdentifiers = [touchIdentifiers copy];
_touchPhases = [touchPhases copy];
_isGesture = isGesture;
_gestureScale = gestureScale;
_gestureRotation = gestureRotation;

return self;
}

static int windowsKeyCodeForCharCodeIOS(unichar charCode)
{
// iPhone Specific Cases
// <rdar://7709408>: We get 10 ('\n') from UIKit when using the software keyboard
if (charCode == 10)
return 0x0D;

// General Case
return windowsKeyCodeForCharCode(charCode);
}

static NSString *normalizedStringWithAppKitCompatibilityMapping(NSString *characters, uint16_t keyCode)
{
auto makeNSStringWithCharacter = [] (unichar c) { return [NSString stringWithCharacters:&c length:1]; };

switch (keyCode) {
case kHIDUsage_KeyboardUpArrow:
return makeNSStringWithCharacter(NSUpArrowFunctionKey);
case kHIDUsage_KeyboardDownArrow:
return makeNSStringWithCharacter(NSDownArrowFunctionKey);
case kHIDUsage_KeyboardLeftArrow:
return makeNSStringWithCharacter(NSLeftArrowFunctionKey);
case kHIDUsage_KeyboardRightArrow:
return makeNSStringWithCharacter(NSRightArrowFunctionKey);
case kHIDUsage_KeyboardPageUp:
return makeNSStringWithCharacter(NSPageUpFunctionKey);
case kHIDUsage_KeyboardPageDown:
return makeNSStringWithCharacter(NSPageDownFunctionKey);
case kHIDUsage_KeyboardEscape:
return @"\x1B";
case kHIDUsage_KeypadNumLock: // Num Lock / Clear
return makeNSStringWithCharacter(NSClearLineFunctionKey);
case kHIDUsage_KeyboardDeleteForward:
return makeNSStringWithCharacter(NSDeleteFunctionKey);
case kHIDUsage_KeyboardEnd:
return makeNSStringWithCharacter(NSEndFunctionKey);
case kHIDUsage_KeyboardInsert:
return makeNSStringWithCharacter(NSInsertFunctionKey);
case kHIDUsage_KeyboardHome:
return makeNSStringWithCharacter(NSHomeFunctionKey);
}
if (keyCode >= kHIDUsage_KeyboardF1 && keyCode <= kHIDUsage_KeyboardF12)
return makeNSStringWithCharacter(NSF1FunctionKey + (keyCode - kHIDUsage_KeyboardF1));
if (keyCode >= kHIDUsage_KeyboardF13 && keyCode <= kHIDUsage_KeyboardF24)
return makeNSStringWithCharacter(NSF13FunctionKey + (keyCode - kHIDUsage_KeyboardF13));
return characters;
}

- (WebEvent *)initWithKeyEventType:(WebEventType)type
timeStamp:(CFTimeInterval)timeStamp
characters:(NSString *)characters
charactersIgnoringModifiers:(NSString *)charactersIgnoringModifiers
modifiers:(WebEventFlags)modifiers
isRepeating:(BOOL)repeating
withFlags:(NSUInteger)flags
withInputManagerHint:(NSString *)hint
keyCode:(uint16_t)keyCode
isTabKey:(BOOL)tabKey
{
self = [super init];
if (!self)
return nil;

_type = type;
_timestamp = timeStamp;
_modifierFlags = modifiers;
_keyboardFlags = flags;
_inputManagerHint = [hint retain];

BOOL flagsChanged = _keyboardFlags & WebEventKeyboardInputModifierFlagsChanged;
if (!flagsChanged) {
// Map Command + . to Escape since Apple Smart Keyboards lack an Escape key.
// FIXME: This doesn't work for some keyboard layouts, like French. See <rdar://problem/51047011>.
if ([charactersIgnoringModifiers isEqualToString:@"."] && (modifiers & WebEventFlagMaskCommandKey)) {
keyCode = kHIDUsage_KeyboardEscape;
_modifierFlags &= ~WebEventFlagMaskCommandKey;
}
}

if (keyCode)
_keyCode = windowsKeyCodeForKeyCode(keyCode);
else if ([charactersIgnoringModifiers length] == 1) {
// This event is likely for a software keyboard-generated event.
_keyCode = windowsKeyCodeForCharCodeIOS([charactersIgnoringModifiers characterAtIndex:0]);
}

if (!flagsChanged) {
_characters = [normalizedStringWithAppKitCompatibilityMapping(characters, keyCode) retain];
_charactersIgnoringModifiers = [normalizedStringWithAppKitCompatibilityMapping(charactersIgnoringModifiers, keyCode) retain];
_tabKey = tabKey;
_keyRepeating = repeating;
}

return self;
}

- (void)dealloc
{
[_characters release];
[_charactersIgnoringModifiers release];
[_inputManagerHint release];

[_touchLocations release];
[_touchIdentifiers release];
[_touchPhases release];

[super dealloc];
}

- (NSString *)_typeDescription
{
switch (_type) {
case WebEventMouseDown:
return @"WebEventMouseDown";
case WebEventMouseUp:
return @"WebEventMouseUp";
case WebEventMouseMoved:
return @"WebEventMouseMoved";
case WebEventScrollWheel:
return @"WebEventScrollWheel";
case WebEventKeyDown:
return @"WebEventKeyDown";
case WebEventKeyUp:
return @"WebEventKeyUp";
case WebEventTouchBegin:
return @"WebEventTouchBegin";
case WebEventTouchChange:
return @"WebEventTouchChange";
case WebEventTouchEnd:
return @"WebEventTouchEnd";
case WebEventTouchCancel:
return @"WebEventTouchCancel";
default:
ASSERT_NOT_REACHED();
}
return @"Unknown";
}

- (NSString *)_modiferFlagsDescription
{
switch (_modifierFlags) {
case WebEventMouseDown:
return @"WebEventMouseDown";
case WebEventMouseUp:
return @"WebEventMouseUp";
case WebEventMouseMoved:
return @"WebEventMouseMoved";
case WebEventScrollWheel:
return @"WebEventScrollWheel";
case WebEventKeyDown:
return @"WebEventKeyDown";
case WebEventKeyUp:
return @"WebEventKeyUp";
case WebEventTouchBegin:
return @"WebEventTouchBegin";
case WebEventTouchChange:
return @"WebEventTouchChange";
case WebEventTouchEnd:
return @"WebEventTouchEnd";
case WebEventTouchCancel:
return @"WebEventTouchCancel";
default:
ASSERT_NOT_REACHED();
}
return @"Unknown";
}

- (NSString *)_touchLocationsDescription:(NSArray *)locations
{
BOOL shouldAddComma = NO;
NSMutableString *description = [NSMutableString string];
for (NSValue *value in locations) {
CGPoint point = [value pointValue];
[description appendFormat:@"%@(%f, %f)", (shouldAddComma ? @", " : @""), point.x, point.y];
shouldAddComma = YES;
}
return description;
}

- (NSString *)_touchIdentifiersDescription
{
BOOL shouldAddComma = NO;
NSMutableString *description = [NSMutableString string];
for (NSNumber *identifier in _touchIdentifiers) {
[description appendFormat:@"%@%u", (shouldAddComma ? @", " : @""), [identifier unsignedIntValue]];
shouldAddComma = YES;
}
return description;
}

- (NSString *)_touchPhaseDescription:(WebEventTouchPhaseType)phase
{
switch (phase) {
case WebEventTouchPhaseBegan:
return @"WebEventTouchPhaseBegan";
case WebEventTouchPhaseMoved:
return @"WebEventTouchPhaseMoved";
case WebEventTouchPhaseStationary:
return @"WebEventTouchPhaseStationary";
case WebEventTouchPhaseEnded:
return @"WebEventTouchPhaseEnded";
case WebEventTouchPhaseCancelled:
return @"WebEventTouchPhaseCancelled";
default:
ASSERT_NOT_REACHED();
}
return @"Unknown";
}

- (NSString *)_touchPhasesDescription
{
BOOL shouldAddComma = NO;
NSMutableString *description = [NSMutableString string];
for (NSNumber *phase in _touchPhases) {
[description appendFormat:@"%@%@", (shouldAddComma ? @", " : @""), [self _touchPhaseDescription:static_cast<WebEventTouchPhaseType>([phase unsignedIntValue])]];
shouldAddComma = YES;
}
return description;
}

- (NSString *)_eventDescription
{
switch (_type) {
case WebEventMouseDown:
case WebEventMouseUp:
case WebEventMouseMoved:
return [NSString stringWithFormat:@"location: (%f, %f)", _locationInWindow.x, _locationInWindow.y];
case WebEventScrollWheel:
return [NSString stringWithFormat:@"location: (%f, %f) deltaX: %f deltaY: %f", _locationInWindow.x, _locationInWindow.y, _deltaX, _deltaY];
case WebEventKeyDown:
case WebEventKeyUp:
if (_keyboardFlags & WebEventKeyboardInputModifierFlagsChanged)
return [NSString stringWithFormat:@"flags: %d keyboardFlags: %lu keyCode %d", _modifierFlags, static_cast<unsigned long>(_keyboardFlags), _keyCode];
return [NSString stringWithFormat:@"chars: %@ charsNoModifiers: %@ flags: %d repeating: %d keyboardFlags: %lu keyCode %d, isTab: %d", _characters, _charactersIgnoringModifiers, _modifierFlags, _keyRepeating, static_cast<unsigned long>(_keyboardFlags), _keyCode, _tabKey];
case WebEventTouchBegin:
case WebEventTouchChange:
case WebEventTouchEnd:
case WebEventTouchCancel:
return [NSString stringWithFormat:@"location: (%f, %f) count: %d locations: %@ identifiers: %@ phases: %@ isGesture: %d scale: %f rotation: %f", _locationInWindow.x, _locationInWindow.y, _touchCount, [self _touchLocationsDescription:_touchLocations], [self _touchIdentifiersDescription], [self _touchPhasesDescription], (_isGesture ? 1 : 0), _gestureScale, _gestureRotation];
default:
ASSERT_NOT_REACHED();
}
return @"Unknown";
}

- (NSString *)description
{
return [NSString stringWithFormat:@"%@ type: %@ - %@", [super description], [self _typeDescription], [self _eventDescription]];
}

- (CGPoint)locationInWindow
{
ASSERT_WITH_MESSAGE(_type == WebEventMouseDown || _type == WebEventMouseUp || _type == WebEventMouseMoved || _type == WebEventScrollWheel
// FIXME: <rdar://problem/7185284> TouchEvents may be in more than one window some day.
|| _type == WebEventTouchBegin || _type == WebEventTouchChange || _type == WebEventTouchEnd || _type == WebEventTouchCancel
, "WebEventType: %d", _type);
return _locationInWindow;
}

- (NSString *)characters
{
ASSERT(_type == WebEventKeyDown || _type == WebEventKeyUp);
ASSERT(!(_keyboardFlags & WebEventKeyboardInputModifierFlagsChanged));
return [[_characters retain] autorelease];
}

- (NSString *)charactersIgnoringModifiers
{
ASSERT(_type == WebEventKeyDown || _type == WebEventKeyUp);
ASSERT(!(_keyboardFlags & WebEventKeyboardInputModifierFlagsChanged));
return [[_charactersIgnoringModifiers retain] autorelease];
}

- (NSString *)inputManagerHint
{
return [[_inputManagerHint retain] autorelease];
}

- (WebEventFlags)modifierFlags
{
return _modifierFlags;
}

- (BOOL)isKeyRepeating
{
ASSERT(_type == WebEventKeyDown || _type == WebEventKeyUp);
return _keyRepeating;
}

- (WebKeyboardInputFlags)keyboardFlags
{
ASSERT(_type == WebEventKeyDown || _type == WebEventKeyUp);
return _keyboardFlags;
}

- (uint16_t)keyCode
{
ASSERT(_type == WebEventKeyDown || _type == WebEventKeyUp);
return _keyCode;
}

- (BOOL)isTabKey
{
ASSERT(_type == WebEventKeyDown || _type == WebEventKeyUp);
return _tabKey;
}

- (float)deltaX
{
ASSERT(_type == WebEventScrollWheel);
return _deltaX;
}

- (float)deltaY
{
ASSERT(_type == WebEventScrollWheel);
return _deltaY;
}

// Touch
- (unsigned)touchCount
{
ASSERT(_type == WebEventTouchBegin || _type == WebEventTouchChange || _type == WebEventTouchEnd || _type == WebEventTouchCancel);
return _touchCount;
}

- (NSArray *)touchLocations
{
ASSERT(_type == WebEventTouchBegin || _type == WebEventTouchChange || _type == WebEventTouchEnd || _type == WebEventTouchCancel);
return _touchLocations;
}

- (NSArray *)touchIdentifiers
{
ASSERT(_type == WebEventTouchBegin || _type == WebEventTouchChange || _type == WebEventTouchEnd || _type == WebEventTouchCancel);
return _touchIdentifiers;
}

- (NSArray *)touchPhases
{
ASSERT(_type == WebEventTouchBegin || _type == WebEventTouchChange || _type == WebEventTouchEnd || _type == WebEventTouchCancel);
return _touchPhases;
}

// Gesture
- (BOOL)isGesture
{
ASSERT(_type == WebEventTouchBegin || _type == WebEventTouchChange || _type == WebEventTouchEnd || _type == WebEventTouchCancel);
return _isGesture;
}

- (float)gestureScale
{
ASSERT(_type == WebEventTouchBegin || _type == WebEventTouchChange || _type == WebEventTouchEnd || _type == WebEventTouchCancel);
return _gestureScale;
}

- (float)gestureRotation
{
ASSERT(_type == WebEventTouchBegin || _type == WebEventTouchChange || _type == WebEventTouchEnd || _type == WebEventTouchCancel);
return _gestureRotation;
}

+ (WebEventFlags)modifierFlags
{
return GSEventIsHardwareKeyboardAttached() ? GSKeyboardGetModifierState([PAL::getUIApplicationClass() sharedApplication]._hardwareKeyboard) : 0;
}

@end

#endif // PLATFORM(IOS_FAMILY)