blob: f78079446369e78c9f48ce0815ef1ccc8df0b99d [file] [log] [blame]
/*
* Copyright (C) 2005, 2007 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.
* 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of
* its contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY APPLE 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 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.
*/
#import "config.h"
#import "TextInputController.h"
#import "DumpRenderTreeMac.h"
#import <AppKit/NSInputManager.h>
#import <WebKit/WebDocument.h>
#import <WebKit/WebFrame.h>
#import <WebKit/WebFramePrivate.h>
#import <WebKit/WebFrameView.h>
#import <WebKit/WebHTMLViewPrivate.h>
#import <WebKit/WebScriptObject.h>
#import <WebKit/WebTypesInternal.h>
#import <WebKit/WebView.h>
@interface TextInputController (DumpRenderTreeInputMethodHandler)
- (BOOL)interpretKeyEvents:(NSArray *)eventArray withSender:(WebHTMLView *)sender;
@end
@interface WebHTMLView (DumpRenderTreeInputMethodHandler)
- (void)interpretKeyEvents:(NSArray *)eventArray;
@end
@interface WebHTMLView (WebKitSecretsTextInputControllerIsAwareOf)
- (WebFrame *)_frame;
@end
@implementation WebHTMLView (DumpRenderTreeInputMethodHandler)
- (void)interpretKeyEvents:(NSArray *)eventArray
{
WebScriptObject *obj = [[self _frame] windowObject];
TextInputController *tic = [obj valueForKey:@"textInputController"];
if (![tic interpretKeyEvents:eventArray withSender:self])
[super interpretKeyEvents:eventArray];
}
@end
@implementation NSMutableAttributedString (TextInputController)
+ (BOOL)isSelectorExcludedFromWebScript:(SEL)aSelector
{
if (aSelector == @selector(string)
|| aSelector == @selector(getLength)
|| aSelector == @selector(attributeNamesAtIndex:)
|| aSelector == @selector(valueOfAttribute:atIndex:)
|| aSelector == @selector(addAttribute:value:)
|| aSelector == @selector(addAttribute:value:from:length:)
|| aSelector == @selector(addColorAttribute:red:green:blue:alpha:)
|| aSelector == @selector(addColorAttribute:red:green:blue:alpha:from:length:)
|| aSelector == @selector(addFontAttribute:fontName:size:)
|| aSelector == @selector(addFontAttribute:fontName:size:from:length:))
return NO;
return YES;
}
+ (NSString *)webScriptNameForSelector:(SEL)aSelector
{
if (aSelector == @selector(getLength))
return @"length";
if (aSelector == @selector(attributeNamesAtIndex:))
return @"getAttributeNamesAtIndex";
if (aSelector == @selector(valueOfAttribute:atIndex:))
return @"getAttributeValueAtIndex";
if (aSelector == @selector(addAttribute:value:))
return @"addAttribute";
if (aSelector == @selector(addAttribute:value:from:length:))
return @"addAttributeForRange";
if (aSelector == @selector(addColorAttribute:red:green:blue:alpha:))
return @"addColorAttribute";
if (aSelector == @selector(addColorAttribute:red:green:blue:alpha:from:length:))
return @"addColorAttributeForRange";
if (aSelector == @selector(addFontAttribute:fontName:size:))
return @"addFontAttribute";
if (aSelector == @selector(addFontAttribute:fontName:size:from:length:))
return @"addFontAttributeForRange";
return nil;
}
- (int)getLength
{
return (int)[self length];
}
- (NSArray *)attributeNamesAtIndex:(int)index
{
NSDictionary *attributes = [self attributesAtIndex:(unsigned)index effectiveRange:nil];
return [attributes allKeys];
}
- (id)valueOfAttribute:(NSString *)attrName atIndex:(int)index
{
return [self attribute:attrName atIndex:(unsigned)index effectiveRange:nil];
}
- (void)addAttribute:(NSString *)attrName value:(id)value
{
[self addAttribute:attrName value:value range:NSMakeRange(0, [self length])];
}
- (void)addAttribute:(NSString *)attrName value:(id)value from:(int)from length:(int)length
{
[self addAttribute:attrName value:value range:NSMakeRange((unsigned)from, (unsigned)length)];
}
- (void)addColorAttribute:(NSString *)attrName red:(float)red green:(float)green blue:(float)blue alpha:(float)alpha
{
[self addAttribute:attrName value:[NSColor colorWithDeviceRed:red green:green blue:blue alpha:alpha] range:NSMakeRange(0, [self length])];
}
- (void)addColorAttribute:(NSString *)attrName red:(float)red green:(float)green blue:(float)blue alpha:(float)alpha from:(int)from length:(int)length
{
[self addAttribute:attrName value:[NSColor colorWithDeviceRed:red green:green blue:blue alpha:alpha] range:NSMakeRange((unsigned)from, (unsigned)length)];
}
- (void)addFontAttribute:(NSString *)attrName fontName:(NSString *)fontName size:(float)fontSize
{
[self addAttribute:attrName value:[NSFont fontWithName:fontName size:fontSize] range:NSMakeRange(0, [self length])];
}
- (void)addFontAttribute:(NSString *)attrName fontName:(NSString *)fontName size:(float)fontSize from:(int)from length:(int)length
{
[self addAttribute:attrName value:[NSFont fontWithName:fontName size:fontSize] range:NSMakeRange((unsigned)from, (unsigned)length)];
}
@end
@implementation TextInputController
+ (BOOL)isSelectorExcludedFromWebScript:(SEL)aSelector
{
if (aSelector == @selector(insertText:)
|| aSelector == @selector(doCommand:)
|| aSelector == @selector(setMarkedText:selectedFrom:length:)
|| aSelector == @selector(unmarkText)
|| aSelector == @selector(hasMarkedText)
|| aSelector == @selector(conversationIdentifier)
|| aSelector == @selector(substringFrom:length:)
|| aSelector == @selector(attributedSubstringFrom:length:)
|| aSelector == @selector(markedRange)
|| aSelector == @selector(selectedRange)
|| aSelector == @selector(firstRectForCharactersFrom:length:)
|| aSelector == @selector(characterIndexForPointX:Y:)
|| aSelector == @selector(validAttributesForMarkedText)
|| aSelector == @selector(attributedStringWithString:)
|| aSelector == @selector(setInputMethodHandler:))
return NO;
return YES;
}
+ (NSString *)webScriptNameForSelector:(SEL)aSelector
{
if (aSelector == @selector(insertText:))
return @"insertText";
else if (aSelector == @selector(doCommand:))
return @"doCommand";
else if (aSelector == @selector(setMarkedText:selectedFrom:length:))
return @"setMarkedText";
else if (aSelector == @selector(substringFrom:length:))
return @"substringFromRange";
else if (aSelector == @selector(attributedSubstringFrom:length:))
return @"attributedSubstringFromRange";
else if (aSelector == @selector(firstRectForCharactersFrom:length:))
return @"firstRectForCharacterRange";
else if (aSelector == @selector(characterIndexForPointX:Y:))
return @"characterIndexForPoint";
else if (aSelector == @selector(attributedStringWithString:))
return @"makeAttributedString"; // just a factory method, doesn't call into NSTextInput
else if (aSelector == @selector(setInputMethodHandler:))
return @"setInputMethodHandler";
return nil;
}
- (id)initWithWebView:(WebView *)wv
{
self = [super init];
webView = wv;
inputMethodView = nil;
inputMethodHandler = nil;
return self;
}
- (void)dealloc
{
[inputMethodHandler release];
inputMethodHandler = nil;
[super dealloc];
}
- (NSObject <NSTextInput> *)textInput
{
NSView <NSTextInput> *view = inputMethodView ? inputMethodView : (id)[[[webView mainFrame] frameView] documentView];
return [view conformsToProtocol:@protocol(NSTextInput)] ? view : nil;
}
- (void)insertText:(id)aString
{
NSObject <NSTextInput> *textInput = [self textInput];
if (textInput)
[textInput insertText:aString];
}
- (void)doCommand:(NSString *)aCommand
{
NSObject <NSTextInput> *textInput = [self textInput];
if (textInput)
[textInput doCommandBySelector:NSSelectorFromString(aCommand)];
}
- (void)setMarkedText:(NSString *)aString selectedFrom:(int)from length:(int)length
{
NSObject <NSTextInput> *textInput = [self textInput];
if (textInput)
[textInput setMarkedText:aString selectedRange:NSMakeRange(from, length)];
}
- (void)unmarkText
{
NSObject <NSTextInput> *textInput = [self textInput];
if (textInput)
[textInput unmarkText];
}
- (BOOL)hasMarkedText
{
NSObject <NSTextInput> *textInput = [self textInput];
if (textInput)
return [textInput hasMarkedText];
return FALSE;
}
- (long)conversationIdentifier
{
NSObject <NSTextInput> *textInput = [self textInput];
if (textInput)
return [textInput conversationIdentifier];
return 0;
}
- (NSString *)substringFrom:(int)from length:(int)length
{
NSObject <NSTextInput> *textInput = [self textInput];
if (textInput)
return [[textInput attributedSubstringFromRange:NSMakeRange(from, length)] string];
return @"";
}
- (NSMutableAttributedString *)attributedSubstringFrom:(int)from length:(int)length
{
NSObject <NSTextInput> *textInput = [self textInput];
NSMutableAttributedString *ret = [[[NSMutableAttributedString alloc] init] autorelease];
if (textInput)
[ret setAttributedString:[textInput attributedSubstringFromRange:NSMakeRange(from, length)]];
return ret;
}
- (NSArray *)markedRange
{
NSObject <NSTextInput> *textInput = [self textInput];
if (textInput) {
NSRange range = [textInput markedRange];
return [NSArray arrayWithObjects:[NSNumber numberWithUnsignedInt:range.location], [NSNumber numberWithUnsignedInt:range.length], nil];
}
return nil;
}
- (NSArray *)selectedRange
{
NSObject <NSTextInput> *textInput = [self textInput];
if (textInput) {
NSRange range = [textInput selectedRange];
return [NSArray arrayWithObjects:[NSNumber numberWithUnsignedInt:range.location], [NSNumber numberWithUnsignedInt:range.length], nil];
}
return nil;
}
- (NSArray *)firstRectForCharactersFrom:(int)from length:(int)length
{
NSObject <NSTextInput> *textInput = [self textInput];
if (textInput) {
NSRect rect = [textInput firstRectForCharacterRange:NSMakeRange(from, length)];
if (rect.origin.x || rect.origin.y || rect.size.width || rect.size.height) {
rect.origin = [[webView window] convertScreenToBase:rect.origin];
rect = [webView convertRect:rect fromView:nil];
}
return [NSArray arrayWithObjects:
[NSNumber numberWithFloat:rect.origin.x],
[NSNumber numberWithFloat:rect.origin.y],
[NSNumber numberWithFloat:rect.size.width],
[NSNumber numberWithFloat:rect.size.height],
nil];
}
return nil;
}
- (NSInteger)characterIndexForPointX:(float)x Y:(float)y
{
NSObject <NSTextInput> *textInput = [self textInput];
if (textInput) {
NSPoint point = NSMakePoint(x, y);
point = [webView convertPoint:point toView:nil];
point = [[webView window] convertBaseToScreen:point];
NSInteger index = [textInput characterIndexForPoint:point];
if (index == NSNotFound)
return -1;
return index;
}
return 0;
}
- (NSArray *)validAttributesForMarkedText
{
NSObject <NSTextInput> *textInput = [self textInput];
if (textInput)
return [textInput validAttributesForMarkedText];
return nil;
}
- (NSMutableAttributedString *)attributedStringWithString:(NSString *)aString
{
return [[[NSMutableAttributedString alloc] initWithString:aString] autorelease];
}
- (void)setInputMethodHandler:(WebScriptObject *)handler
{
if (inputMethodHandler == handler)
return;
[handler retain];
[inputMethodHandler release];
inputMethodHandler = handler;
}
- (BOOL)interpretKeyEvents:(NSArray *)eventArray withSender:(WebHTMLView *)sender
{
if (!inputMethodHandler)
return NO;
inputMethodView = sender;
NSEvent *event = [eventArray objectAtIndex:0];
unsigned modifierFlags = [event modifierFlags];
NSMutableArray *modifiers = [[NSMutableArray alloc] init];
if (modifierFlags & NSAlphaShiftKeyMask)
[modifiers addObject:@"NSAlphaShiftKeyMask"];
if (modifierFlags & NSShiftKeyMask)
[modifiers addObject:@"NSShiftKeyMask"];
if (modifierFlags & NSControlKeyMask)
[modifiers addObject:@"NSControlKeyMask"];
if (modifierFlags & NSAlternateKeyMask)
[modifiers addObject:@"NSAlternateKeyMask"];
if (modifierFlags & NSCommandKeyMask)
[modifiers addObject:@"NSCommandKeyMask"];
if (modifierFlags & NSNumericPadKeyMask)
[modifiers addObject:@"NSNumericPadKeyMask"];
if (modifierFlags & NSHelpKeyMask)
[modifiers addObject:@"NSHelpKeyMask"];
if (modifierFlags & NSFunctionKeyMask)
[modifiers addObject:@"NSFunctionKeyMask"];
WebScriptObject* eventParam = [inputMethodHandler evaluateWebScript:@"new Object();"];
[eventParam setValue:[event characters] forKey:@"characters"];
[eventParam setValue:[event charactersIgnoringModifiers] forKey:@"charactersIgnoringModifiers"];
[eventParam setValue:[NSNumber numberWithBool:[event isARepeat]] forKey:@"isARepeat"];
[eventParam setValue:[NSNumber numberWithUnsignedShort:[event keyCode]] forKey:@"keyCode"];
[eventParam setValue:modifiers forKey:@"modifierFlags"];
[modifiers release];
id result = [inputMethodHandler callWebScriptMethod:@"call" withArguments:[NSArray arrayWithObjects:inputMethodHandler, eventParam, nil]];
if (![result respondsToSelector:@selector(boolValue)] || ![result boolValue])
[sender doCommandBySelector:@selector(noop:)]; // AppKit sends noop: if the ime does not handle an event
inputMethodView = nil;
return YES;
}
@end