1/*
2 * Copyright (C) 2005, 2007 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 *
8 * 1.  Redistributions of source code must retain the above copyright
9 *     notice, this list of conditions and the following disclaimer.
10 * 2.  Redistributions in binary form must reproduce the above copyright
11 *     notice, this list of conditions and the following disclaimer in the
12 *     documentation and/or other materials provided with the distribution.
13 * 3.  Neither the name of Apple Computer, Inc. ("Apple") nor the names of
14 *     its contributors may be used to endorse or promote products derived
15 *     from this software without specific prior written permission.
16 *
17 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
18 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
19 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
20 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
21 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
22 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
23 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
24 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 */
28
29#import "config.h"
30#import "TextInputController.h"
31
32#import "DumpRenderTreeMac.h"
33#import <AppKit/NSInputManager.h>
34#import <WebKit/WebDocument.h>
35#import <WebKit/WebFrame.h>
36#import <WebKit/WebFramePrivate.h>
37#import <WebKit/WebFrameView.h>
38#import <WebKit/WebHTMLViewPrivate.h>
39#import <WebKit/WebScriptObject.h>
40#import <WebKit/WebTypesInternal.h>
41#import <WebKit/WebView.h>
42
43@interface TextInputController (DumpRenderTreeInputMethodHandler)
44- (BOOL)interpretKeyEvents:(NSArray *)eventArray withSender:(WebHTMLView *)sender;
45@end
46
47@interface WebHTMLView (DumpRenderTreeInputMethodHandler)
48- (void)interpretKeyEvents:(NSArray *)eventArray;
49@end
50
51@interface WebHTMLView (WebKitSecretsTextInputControllerIsAwareOf)
52- (WebFrame *)_frame;
53@end
54
55@implementation WebHTMLView (DumpRenderTreeInputMethodHandler)
56- (void)interpretKeyEvents:(NSArray *)eventArray
57{
58    WebScriptObject *obj = [[self _frame] windowObject];
59    TextInputController *tic = [obj valueForKey:@"textInputController"];
60    if (![tic interpretKeyEvents:eventArray withSender:self])
61        [super interpretKeyEvents:eventArray];
62}
63@end
64
65@implementation NSMutableAttributedString (TextInputController)
66
67+ (BOOL)isSelectorExcludedFromWebScript:(SEL)aSelector
68{
69    if (aSelector == @selector(string)
70            || aSelector == @selector(getLength)
71            || aSelector == @selector(attributeNamesAtIndex:)
72            || aSelector == @selector(valueOfAttribute:atIndex:)
73            || aSelector == @selector(addAttribute:value:)
74            || aSelector == @selector(addAttribute:value:from:length:)
75            || aSelector == @selector(addColorAttribute:red:green:blue:alpha:)
76            || aSelector == @selector(addColorAttribute:red:green:blue:alpha:from:length:)
77            || aSelector == @selector(addFontAttribute:fontName:size:)
78            || aSelector == @selector(addFontAttribute:fontName:size:from:length:))
79        return NO;
80    return YES;
81}
82
83+ (NSString *)webScriptNameForSelector:(SEL)aSelector
84{
85    if (aSelector == @selector(getLength))
86        return @"length";
87    if (aSelector == @selector(attributeNamesAtIndex:))
88        return @"getAttributeNamesAtIndex";
89    if (aSelector == @selector(valueOfAttribute:atIndex:))
90        return @"getAttributeValueAtIndex";
91    if (aSelector == @selector(addAttribute:value:))
92        return @"addAttribute";
93    if (aSelector == @selector(addAttribute:value:from:length:))
94        return @"addAttributeForRange";
95    if (aSelector == @selector(addColorAttribute:red:green:blue:alpha:))
96        return @"addColorAttribute";
97    if (aSelector == @selector(addColorAttribute:red:green:blue:alpha:from:length:))
98        return @"addColorAttributeForRange";
99    if (aSelector == @selector(addFontAttribute:fontName:size:))
100        return @"addFontAttribute";
101    if (aSelector == @selector(addFontAttribute:fontName:size:from:length:))
102        return @"addFontAttributeForRange";
103
104    return nil;
105}
106
107- (int)getLength
108{
109    return (int)[self length];
110}
111
112- (NSArray *)attributeNamesAtIndex:(int)index
113{
114    NSDictionary *attributes = [self attributesAtIndex:(unsigned)index effectiveRange:nil];
115    return [attributes allKeys];
116}
117
118- (id)valueOfAttribute:(NSString *)attrName atIndex:(int)index
119{
120    return [self attribute:attrName atIndex:(unsigned)index effectiveRange:nil];
121}
122
123- (void)addAttribute:(NSString *)attrName value:(id)value
124{
125    [self addAttribute:attrName value:value range:NSMakeRange(0, [self length])];
126}
127
128- (void)addAttribute:(NSString *)attrName value:(id)value from:(int)from length:(int)length
129{
130    [self addAttribute:attrName value:value range:NSMakeRange((unsigned)from, (unsigned)length)];
131}
132
133- (void)addColorAttribute:(NSString *)attrName red:(float)red green:(float)green blue:(float)blue alpha:(float)alpha
134{
135    [self addAttribute:attrName value:[NSColor colorWithDeviceRed:red green:green blue:blue alpha:alpha] range:NSMakeRange(0, [self length])];
136}
137
138- (void)addColorAttribute:(NSString *)attrName red:(float)red green:(float)green blue:(float)blue alpha:(float)alpha from:(int)from length:(int)length
139{
140    [self addAttribute:attrName value:[NSColor colorWithDeviceRed:red green:green blue:blue alpha:alpha] range:NSMakeRange((unsigned)from, (unsigned)length)];
141}
142
143- (void)addFontAttribute:(NSString *)attrName fontName:(NSString *)fontName size:(float)fontSize
144{
145    [self addAttribute:attrName value:[NSFont fontWithName:fontName size:fontSize] range:NSMakeRange(0, [self length])];
146}
147
148- (void)addFontAttribute:(NSString *)attrName fontName:(NSString *)fontName size:(float)fontSize from:(int)from length:(int)length
149{
150    [self addAttribute:attrName value:[NSFont fontWithName:fontName size:fontSize] range:NSMakeRange((unsigned)from, (unsigned)length)];
151}
152
153@end
154
155@implementation TextInputController
156
157+ (BOOL)isSelectorExcludedFromWebScript:(SEL)aSelector
158{
159    if (aSelector == @selector(insertText:)
160            || aSelector == @selector(doCommand:)
161            || aSelector == @selector(setMarkedText:selectedFrom:length:)
162            || aSelector == @selector(unmarkText)
163            || aSelector == @selector(hasMarkedText)
164            || aSelector == @selector(conversationIdentifier)
165            || aSelector == @selector(substringFrom:length:)
166            || aSelector == @selector(attributedSubstringFrom:length:)
167            || aSelector == @selector(markedRange)
168            || aSelector == @selector(selectedRange)
169            || aSelector == @selector(firstRectForCharactersFrom:length:)
170            || aSelector == @selector(characterIndexForPointX:Y:)
171            || aSelector == @selector(validAttributesForMarkedText)
172            || aSelector == @selector(attributedStringWithString:)
173            || aSelector == @selector(setInputMethodHandler:))
174        return NO;
175    return YES;
176}
177
178+ (NSString *)webScriptNameForSelector:(SEL)aSelector
179{
180    if (aSelector == @selector(insertText:))
181        return @"insertText";
182    else if (aSelector == @selector(doCommand:))
183        return @"doCommand";
184    else if (aSelector == @selector(setMarkedText:selectedFrom:length:))
185        return @"setMarkedText";
186    else if (aSelector == @selector(substringFrom:length:))
187        return @"substringFromRange";
188    else if (aSelector == @selector(attributedSubstringFrom:length:))
189        return @"attributedSubstringFromRange";
190    else if (aSelector == @selector(firstRectForCharactersFrom:length:))
191        return @"firstRectForCharacterRange";
192    else if (aSelector == @selector(characterIndexForPointX:Y:))
193        return @"characterIndexForPoint";
194    else if (aSelector == @selector(attributedStringWithString:))
195        return @"makeAttributedString"; // just a factory method, doesn't call into NSTextInput
196    else if (aSelector == @selector(setInputMethodHandler:))
197        return @"setInputMethodHandler";
198
199    return nil;
200}
201
202- (id)initWithWebView:(WebView *)wv
203{
204    self = [super init];
205    webView = wv;
206    inputMethodView = nil;
207    inputMethodHandler = nil;
208    return self;
209}
210
211- (void)dealloc
212{
213    [inputMethodHandler release];
214    inputMethodHandler = nil;
215
216    [super dealloc];
217}
218
219- (NSObject <NSTextInput> *)textInput
220{
221    NSView <NSTextInput> *view = inputMethodView ? inputMethodView : (id)[[[webView mainFrame] frameView] documentView];
222    return [view conformsToProtocol:@protocol(NSTextInput)] ? view : nil;
223}
224
225- (void)insertText:(id)aString
226{
227    NSObject <NSTextInput> *textInput = [self textInput];
228
229    if (textInput)
230        [textInput insertText:aString];
231}
232
233- (void)doCommand:(NSString *)aCommand
234{
235    NSObject <NSTextInput> *textInput = [self textInput];
236
237    if (textInput)
238        [textInput doCommandBySelector:NSSelectorFromString(aCommand)];
239}
240
241- (void)setMarkedText:(NSString *)aString selectedFrom:(int)from length:(int)length
242{
243    NSObject <NSTextInput> *textInput = [self textInput];
244
245    if (textInput)
246        [textInput setMarkedText:aString selectedRange:NSMakeRange(from, length)];
247}
248
249- (void)unmarkText
250{
251    NSObject <NSTextInput> *textInput = [self textInput];
252
253    if (textInput)
254        [textInput unmarkText];
255}
256
257- (BOOL)hasMarkedText
258{
259    NSObject <NSTextInput> *textInput = [self textInput];
260
261    if (textInput)
262        return [textInput hasMarkedText];
263
264    return FALSE;
265}
266
267- (long)conversationIdentifier
268{
269    NSObject <NSTextInput> *textInput = [self textInput];
270
271    if (textInput)
272        return [textInput conversationIdentifier];
273
274    return 0;
275}
276
277- (NSString *)substringFrom:(int)from length:(int)length
278{
279    NSObject <NSTextInput> *textInput = [self textInput];
280
281    if (textInput)
282        return [[textInput attributedSubstringFromRange:NSMakeRange(from, length)] string];
283
284    return @"";
285}
286
287- (NSMutableAttributedString *)attributedSubstringFrom:(int)from length:(int)length
288{
289    NSObject <NSTextInput> *textInput = [self textInput];
290
291    NSMutableAttributedString *ret = [[[NSMutableAttributedString alloc] init] autorelease];
292
293    if (textInput)
294        [ret setAttributedString:[textInput attributedSubstringFromRange:NSMakeRange(from, length)]];
295
296    return ret;
297}
298
299- (NSArray *)markedRange
300{
301    NSObject <NSTextInput> *textInput = [self textInput];
302
303    if (textInput) {
304        NSRange range = [textInput markedRange];
305        return [NSArray arrayWithObjects:[NSNumber numberWithUnsignedInt:range.location], [NSNumber numberWithUnsignedInt:range.length], nil];
306    }
307
308    return nil;
309}
310
311- (NSArray *)selectedRange
312{
313    NSObject <NSTextInput> *textInput = [self textInput];
314
315    if (textInput) {
316        NSRange range = [textInput selectedRange];
317        return [NSArray arrayWithObjects:[NSNumber numberWithUnsignedInt:range.location], [NSNumber numberWithUnsignedInt:range.length], nil];
318    }
319
320    return nil;
321}
322
323
324- (NSArray *)firstRectForCharactersFrom:(int)from length:(int)length
325{
326    NSObject <NSTextInput> *textInput = [self textInput];
327
328    if (textInput) {
329        NSRect rect = [textInput firstRectForCharacterRange:NSMakeRange(from, length)];
330        if (rect.origin.x || rect.origin.y || rect.size.width || rect.size.height) {
331            rect.origin = [[webView window] convertScreenToBase:rect.origin];
332            rect = [webView convertRect:rect fromView:nil];
333        }
334        return [NSArray arrayWithObjects:
335                    [NSNumber numberWithFloat:rect.origin.x],
336                    [NSNumber numberWithFloat:rect.origin.y],
337                    [NSNumber numberWithFloat:rect.size.width],
338                    [NSNumber numberWithFloat:rect.size.height],
339                    nil];
340    }
341
342    return nil;
343}
344
345- (NSInteger)characterIndexForPointX:(float)x Y:(float)y
346{
347    NSObject <NSTextInput> *textInput = [self textInput];
348
349    if (textInput) {
350        NSPoint point = NSMakePoint(x, y);
351        point = [webView convertPoint:point toView:nil];
352        point = [[webView window] convertBaseToScreen:point];
353        NSInteger index = [textInput characterIndexForPoint:point];
354        if (index == NSNotFound)
355            return -1;
356
357        return index;
358    }
359
360    return 0;
361}
362
363- (NSArray *)validAttributesForMarkedText
364{
365    NSObject <NSTextInput> *textInput = [self textInput];
366
367    if (textInput)
368        return [textInput validAttributesForMarkedText];
369
370    return nil;
371}
372
373- (NSMutableAttributedString *)attributedStringWithString:(NSString *)aString
374{
375    return [[[NSMutableAttributedString alloc] initWithString:aString] autorelease];
376}
377
378- (void)setInputMethodHandler:(WebScriptObject *)handler
379{
380    if (inputMethodHandler == handler)
381        return;
382    [handler retain];
383    [inputMethodHandler release];
384    inputMethodHandler = handler;
385}
386
387- (BOOL)interpretKeyEvents:(NSArray *)eventArray withSender:(WebHTMLView *)sender
388{
389    if (!inputMethodHandler)
390        return NO;
391
392    inputMethodView = sender;
393
394    NSEvent *event = [eventArray objectAtIndex:0];
395    unsigned modifierFlags = [event modifierFlags];
396    NSMutableArray *modifiers = [[NSMutableArray alloc] init];
397    if (modifierFlags & NSAlphaShiftKeyMask)
398        [modifiers addObject:@"NSAlphaShiftKeyMask"];
399    if (modifierFlags & NSShiftKeyMask)
400        [modifiers addObject:@"NSShiftKeyMask"];
401    if (modifierFlags & NSControlKeyMask)
402        [modifiers addObject:@"NSControlKeyMask"];
403    if (modifierFlags & NSAlternateKeyMask)
404        [modifiers addObject:@"NSAlternateKeyMask"];
405    if (modifierFlags & NSCommandKeyMask)
406        [modifiers addObject:@"NSCommandKeyMask"];
407    if (modifierFlags & NSNumericPadKeyMask)
408        [modifiers addObject:@"NSNumericPadKeyMask"];
409    if (modifierFlags & NSHelpKeyMask)
410        [modifiers addObject:@"NSHelpKeyMask"];
411    if (modifierFlags & NSFunctionKeyMask)
412        [modifiers addObject:@"NSFunctionKeyMask"];
413
414    WebScriptObject* eventParam = [inputMethodHandler evaluateWebScript:@"new Object();"];
415    [eventParam setValue:[event characters] forKey:@"characters"];
416    [eventParam setValue:[event charactersIgnoringModifiers] forKey:@"charactersIgnoringModifiers"];
417    [eventParam setValue:[NSNumber numberWithBool:[event isARepeat]] forKey:@"isARepeat"];
418    [eventParam setValue:[NSNumber numberWithUnsignedShort:[event keyCode]] forKey:@"keyCode"];
419    [eventParam setValue:modifiers forKey:@"modifierFlags"];
420
421    [modifiers release];
422
423    id result = [inputMethodHandler callWebScriptMethod:@"call" withArguments:[NSArray arrayWithObjects:inputMethodHandler, eventParam, nil]];
424    if (![result respondsToSelector:@selector(boolValue)] || ![result boolValue])
425        [sender doCommandBySelector:@selector(noop:)]; // AppKit sends noop: if the ime does not handle an event
426
427    inputMethodView = nil;
428    return YES;
429}
430
431@end
432