Remove even more Mountain Lion support
[WebKit-https.git] / Tools / DumpRenderTree / mac / TextInputController.m
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 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 #if PLATFORM(MAC)
33 // FIXME: <rdar://problem/5106287> DumpRenderTree: fix TextInputController to work with iOS and re-enable tests
34
35 #import "DumpRenderTreeMac.h"
36 #import <AppKit/NSInputManager.h>
37 #import <AppKit/NSTextAlternatives.h>
38
39 #if __MAC_OS_X_VERSION_MIN_REQUIRED >= 101000
40 #define SUPPORT_INSERTION_UNDO_GROUPING
41 #if __has_include(<AppKit/NSTextInputContext_Private.h>)
42 #import <AppKit/NSTextInputContext_Private.h>
43 #else
44 NSString *NSTextInsertionUndoableAttributeName;
45 #endif
46 #endif
47
48 #import <WebKit/WebDocument.h>
49 #import <WebKit/WebFrame.h>
50 #import <WebKit/WebFramePrivate.h>
51 #import <WebKit/WebFrameView.h>
52 #import <WebKit/WebHTMLViewPrivate.h>
53 #import <WebKit/WebScriptObject.h>
54 #import <WebKit/WebTypesInternal.h>
55 #import <WebKit/WebView.h>
56
57 @interface TextInputController (DumpRenderTreeInputMethodHandler)
58 - (BOOL)interpretKeyEvents:(NSArray *)eventArray withSender:(WebHTMLView *)sender;
59 @end
60
61 @interface WebHTMLView (DumpRenderTreeInputMethodHandler)
62 - (void)interpretKeyEvents:(NSArray *)eventArray;
63 @end
64
65 @interface WebHTMLView (WebKitSecretsTextInputControllerIsAwareOf)
66 - (WebFrame *)_frame;
67 - (NSAttributedString *)_attributeStringFromDOMRange:(DOMRange *)range;
68 @end
69
70 @implementation WebHTMLView (DumpRenderTreeInputMethodHandler)
71 - (void)interpretKeyEvents:(NSArray *)eventArray
72 {
73     WebScriptObject *obj = [[self _frame] windowObject];
74     TextInputController *tic = [obj valueForKey:@"textInputController"];
75     if (![tic interpretKeyEvents:eventArray withSender:self])
76         [super interpretKeyEvents:eventArray];
77 }
78 @end
79
80 @interface WebNSRange : NSObject {
81 @private
82     NSRange _range;
83 }
84 - (id)initWithNSRange:(NSRange)range;
85 - (unsigned)location;
86 - (unsigned)length;
87 @end
88
89 @implementation WebNSRange
90
91 - (id)initWithNSRange:(NSRange)range
92 {
93     self = [super init];
94     if (!self)
95         return self;
96
97     _range = range;
98     return self;
99 }
100
101 - (unsigned)location
102 {
103     return _range.location;
104 }
105
106 - (unsigned)length
107 {
108     return _range.length;
109 }
110
111 + (BOOL)isSelectorExcludedFromWebScript:(SEL)selector
112 {
113     return !(selector == @selector(location) || selector == @selector(length));
114 }
115
116 @end
117
118 @implementation NSMutableAttributedString (TextInputController)
119
120 + (BOOL)isSelectorExcludedFromWebScript:(SEL)aSelector
121 {
122     if (aSelector == @selector(string)
123             || aSelector == @selector(getLength)
124             || aSelector == @selector(ranges)
125             || aSelector == @selector(attributeNamesAtIndex:)
126             || aSelector == @selector(valueOfAttribute:atIndex:)
127             || aSelector == @selector(addAttribute:value:)
128             || aSelector == @selector(addAttribute:value:from:length:)
129             || aSelector == @selector(addColorAttribute:red:green:blue:alpha:)
130             || aSelector == @selector(addColorAttribute:red:green:blue:alpha:from:length:)
131             || aSelector == @selector(addFontAttribute:fontName:size:)
132             || aSelector == @selector(addFontAttribute:fontName:size:from:length:))
133         return NO;
134     return YES;
135 }
136
137 + (NSString *)webScriptNameForSelector:(SEL)aSelector
138 {
139     if (aSelector == @selector(getLength))
140         return @"length";
141     if (aSelector == @selector(ranges))
142         return @"ranges";
143     if (aSelector == @selector(attributeNamesAtIndex:))
144         return @"getAttributeNamesAtIndex";
145     if (aSelector == @selector(valueOfAttribute:atIndex:))
146         return @"getAttributeValueAtIndex";
147     if (aSelector == @selector(addAttribute:value:))
148         return @"addAttribute";
149     if (aSelector == @selector(addAttribute:value:from:length:))
150         return @"addAttributeForRange";
151     if (aSelector == @selector(addColorAttribute:red:green:blue:alpha:))
152         return @"addColorAttribute";
153     if (aSelector == @selector(addColorAttribute:red:green:blue:alpha:from:length:))
154         return @"addColorAttributeForRange";
155     if (aSelector == @selector(addFontAttribute:fontName:size:))
156         return @"addFontAttribute";
157     if (aSelector == @selector(addFontAttribute:fontName:size:from:length:))
158         return @"addFontAttributeForRange";
159
160     return nil;
161 }
162
163 - (int)getLength
164 {
165     return (int)[self length];
166 }
167
168 - (NSArray *)ranges
169 {
170     NSMutableArray *array = [NSMutableArray array];
171     [self enumerateAttributesInRange:NSMakeRange(0, [self length]) options:0 usingBlock:^(NSDictionary *attributes, NSRange range, BOOL *stop) {
172         WebNSRange *webRange = [[WebNSRange alloc] initWithNSRange:range];
173         [array addObject:webRange];
174         [webRange release];
175     }];
176     return array;
177 }
178
179 - (NSArray *)attributeNamesAtIndex:(int)index
180 {
181     NSDictionary *attributes = [self attributesAtIndex:(unsigned)index effectiveRange:nil];
182     return [attributes allKeys];
183 }
184
185 - (id)valueOfAttribute:(NSString *)attrName atIndex:(int)index
186 {
187     return [self attribute:attrName atIndex:(unsigned)index effectiveRange:nil];
188 }
189
190 - (void)addAttribute:(NSString *)attrName value:(id)value
191 {
192     [self addAttribute:attrName value:value range:NSMakeRange(0, [self length])];
193 }
194
195 - (void)addAttribute:(NSString *)attrName value:(id)value from:(int)from length:(int)length
196 {
197     [self addAttribute:attrName value:value range:NSMakeRange((unsigned)from, (unsigned)length)];
198 }
199
200 - (void)addColorAttribute:(NSString *)attrName red:(float)red green:(float)green blue:(float)blue alpha:(float)alpha
201 {
202     [self addAttribute:attrName value:[NSColor colorWithDeviceRed:red green:green blue:blue alpha:alpha] range:NSMakeRange(0, [self length])];
203 }
204
205 - (void)addColorAttribute:(NSString *)attrName red:(float)red green:(float)green blue:(float)blue alpha:(float)alpha from:(int)from length:(int)length
206 {
207     [self addAttribute:attrName value:[NSColor colorWithDeviceRed:red green:green blue:blue alpha:alpha] range:NSMakeRange((unsigned)from, (unsigned)length)];
208 }
209
210 - (void)addFontAttribute:(NSString *)attrName fontName:(NSString *)fontName size:(float)fontSize
211 {
212     [self addAttribute:attrName value:[NSFont fontWithName:fontName size:fontSize] range:NSMakeRange(0, [self length])];
213 }
214
215 - (void)addFontAttribute:(NSString *)attrName fontName:(NSString *)fontName size:(float)fontSize from:(int)from length:(int)length
216 {
217     [self addAttribute:attrName value:[NSFont fontWithName:fontName size:fontSize] range:NSMakeRange((unsigned)from, (unsigned)length)];
218 }
219
220 @end
221
222 @implementation TextInputController
223
224 + (BOOL)isSelectorExcludedFromWebScript:(SEL)aSelector
225 {
226     if (aSelector == @selector(insertText:)
227             || aSelector == @selector(doCommand:)
228             || aSelector == @selector(setMarkedText:selectedFrom:length:)
229             || aSelector == @selector(unmarkText)
230             || aSelector == @selector(hasMarkedText)
231             || aSelector == @selector(conversationIdentifier)
232             || aSelector == @selector(substringFrom:length:)
233             || aSelector == @selector(attributedSubstringFrom:length:)
234             || aSelector == @selector(legacyAttributedString:)
235             || aSelector == @selector(markedRange)
236             || aSelector == @selector(selectedRange)
237             || aSelector == @selector(firstRectForCharactersFrom:length:)
238             || aSelector == @selector(characterIndexForPointX:Y:)
239             || aSelector == @selector(validAttributesForMarkedText)
240             || aSelector == @selector(attributedStringWithString:)
241             || aSelector == @selector(setInputMethodHandler:)
242             || aSelector == @selector(dictatedStringWithPrimaryString:alternative:alternativeOffset:alternativeLength:)
243             || aSelector == @selector(stringWithUndoGroupingInsertion:))
244         return NO;
245     return YES;
246 }
247
248 + (NSString *)webScriptNameForSelector:(SEL)aSelector
249 {
250     if (aSelector == @selector(insertText:))
251         return @"insertText";
252     else if (aSelector == @selector(doCommand:))
253         return @"doCommand";
254     else if (aSelector == @selector(setMarkedText:selectedFrom:length:))
255         return @"setMarkedText";
256     else if (aSelector == @selector(substringFrom:length:))
257         return @"substringFromRange";
258     else if (aSelector == @selector(attributedSubstringFrom:length:))
259         return @"attributedSubstringFromRange";
260     else if (aSelector == @selector(legacyAttributedString:))
261         return @"legacyAttributedString";
262     else if (aSelector == @selector(firstRectForCharactersFrom:length:))
263         return @"firstRectForCharacterRange";
264     else if (aSelector == @selector(characterIndexForPointX:Y:))
265         return @"characterIndexForPoint";
266     else if (aSelector == @selector(attributedStringWithString:))
267         return @"makeAttributedString"; // just a factory method, doesn't call into NSTextInput
268     else if (aSelector == @selector(setInputMethodHandler:))
269         return @"setInputMethodHandler";
270     else if (aSelector == @selector(dictatedStringWithPrimaryString:alternative:alternativeOffset:alternativeLength:))
271         return @"makeDictatedString";
272     else if (aSelector == @selector(stringWithUndoGroupingInsertion:))
273         return @"makeUndoGroupingInsertionString";
274
275     return nil;
276 }
277
278 - (id)initWithWebView:(WebView *)wv
279 {
280     self = [super init];
281     webView = wv;
282     inputMethodView = nil;
283     inputMethodHandler = nil;
284     return self;
285 }
286
287 - (void)dealloc
288 {
289     [inputMethodHandler release];
290     inputMethodHandler = nil;
291     
292     [super dealloc];
293 }
294
295 - (NSObject <NSTextInput> *)textInput
296 {
297     NSView <NSTextInput> *view = inputMethodView ? inputMethodView : (id)[[[webView mainFrame] frameView] documentView];
298     return [view conformsToProtocol:@protocol(NSTextInput)] ? view : nil;
299 }
300
301 - (void)insertText:(id)aString
302 {
303     NSObject <NSTextInput> *textInput = [self textInput];
304
305     if (textInput)
306         [textInput insertText:aString];
307 }
308
309 - (void)doCommand:(NSString *)aCommand
310 {
311     NSObject <NSTextInput> *textInput = [self textInput];
312
313     if (textInput)
314         [textInput doCommandBySelector:NSSelectorFromString(aCommand)];
315 }
316
317 - (void)setMarkedText:(NSString *)aString selectedFrom:(int)from length:(int)length
318 {
319     NSObject <NSTextInput> *textInput = [self textInput];
320  
321     if (textInput)
322         [textInput setMarkedText:aString selectedRange:NSMakeRange(from, length)];
323 }
324
325 - (void)unmarkText
326 {
327     NSObject <NSTextInput> *textInput = [self textInput];
328
329     if (textInput)
330         [textInput unmarkText];
331 }
332
333 - (BOOL)hasMarkedText
334 {
335     NSObject <NSTextInput> *textInput = [self textInput];
336
337     if (textInput)
338         return [textInput hasMarkedText];
339
340     return FALSE;
341 }
342
343 - (long)conversationIdentifier
344 {
345     NSObject <NSTextInput> *textInput = [self textInput];
346
347     if (textInput)
348         return [textInput conversationIdentifier];
349
350     return 0;
351 }
352
353 - (NSString *)substringFrom:(int)from length:(int)length
354 {
355     NSObject <NSTextInput> *textInput = [self textInput];
356
357     if (textInput)
358         return [[textInput attributedSubstringFromRange:NSMakeRange(from, length)] string];
359     
360     return @"";
361 }
362
363 - (NSMutableAttributedString *)attributedSubstringFrom:(int)from length:(int)length
364 {
365     NSObject <NSTextInput> *textInput = [self textInput];
366
367     NSMutableAttributedString *ret = [[[NSMutableAttributedString alloc] init] autorelease];
368
369     if (textInput)
370         [ret setAttributedString:[textInput attributedSubstringFromRange:NSMakeRange(from, length)]];
371     
372     return ret;
373 }
374
375 - (NSMutableAttributedString *)legacyAttributedString:(DOMRange*)range
376 {
377     NSMutableAttributedString *string = [[[NSMutableAttributedString alloc] init] autorelease];
378     id documentView = [[[webView mainFrame] frameView] documentView];
379     if (![documentView isKindOfClass:[WebHTMLView class]])
380         return string;
381
382     [string setAttributedString:[(WebHTMLView *)documentView _attributeStringFromDOMRange:range]];
383     return string;
384 }
385
386 - (NSArray *)markedRange
387 {
388     NSObject <NSTextInput> *textInput = [self textInput];
389
390     if (textInput) {
391         NSRange range = [textInput markedRange];
392         return [NSArray arrayWithObjects:[NSNumber numberWithUnsignedInt:range.location], [NSNumber numberWithUnsignedInt:range.length], nil];
393     }
394
395     return nil;
396 }
397
398 - (NSArray *)selectedRange
399 {
400     NSObject <NSTextInput> *textInput = [self textInput];
401
402     if (textInput) {
403         NSRange range = [textInput selectedRange];
404         return [NSArray arrayWithObjects:[NSNumber numberWithUnsignedInt:range.location], [NSNumber numberWithUnsignedInt:range.length], nil];
405     }
406
407     return nil;
408 }
409   
410
411 - (NSArray *)firstRectForCharactersFrom:(int)from length:(int)length
412 {
413     NSObject <NSTextInput> *textInput = [self textInput];
414
415     if (textInput) {
416         NSRect rect = [textInput firstRectForCharacterRange:NSMakeRange(from, length)];
417         if (rect.origin.x || rect.origin.y || rect.size.width || rect.size.height) {
418             rect.origin = [[webView window] convertScreenToBase:rect.origin];
419             rect = [webView convertRect:rect fromView:nil];
420         }
421         return [NSArray arrayWithObjects:
422                     [NSNumber numberWithFloat:rect.origin.x],
423                     [NSNumber numberWithFloat:rect.origin.y],
424                     [NSNumber numberWithFloat:rect.size.width],
425                     [NSNumber numberWithFloat:rect.size.height],
426                     nil];
427     }
428
429     return nil;
430 }
431
432 - (NSInteger)characterIndexForPointX:(float)x Y:(float)y
433 {
434     NSObject <NSTextInput> *textInput = [self textInput];
435
436     if (textInput) {
437         NSPoint point = NSMakePoint(x, y);
438         point = [webView convertPoint:point toView:nil];
439         point = [[webView window] convertBaseToScreen:point];
440         NSInteger index = [textInput characterIndexForPoint:point];
441         if (index == NSNotFound)
442             return -1;
443
444         return index;
445     }
446
447     return 0;
448 }
449
450 - (NSArray *)validAttributesForMarkedText
451 {
452     NSObject <NSTextInput> *textInput = [self textInput];
453
454     if (textInput)
455         return [textInput validAttributesForMarkedText];
456
457     return nil;
458 }
459
460 - (NSMutableAttributedString *)attributedStringWithString:(NSString *)aString
461 {
462     return [[[NSMutableAttributedString alloc] initWithString:aString] autorelease];
463 }
464
465 - (NSMutableAttributedString*)stringWithUndoGroupingInsertion:(NSString*)aString
466 {
467 #if defined(SUPPORT_INSERTION_UNDO_GROUPING)
468     NSMutableAttributedString* attributedString = [self dictatedStringWithPrimaryString:aString alternative:@"test" alternativeOffset:0 alternativeLength:1];
469     [attributedString addAttribute:NSTextInsertionUndoableAttributeName value:@YES range:NSMakeRange(0, [attributedString length])];
470     return attributedString;
471 #else
472     return nil;
473 #endif
474 }
475
476 - (NSMutableAttributedString*)dictatedStringWithPrimaryString:(NSString*)aString alternative:(NSString*)alternative alternativeOffset:(int)offset alternativeLength:(int)length
477 {
478     NSMutableAttributedString* dictatedString = [self attributedStringWithString:aString];
479     NSRange rangeWithAlternative = NSMakeRange((NSUInteger)offset, (NSUInteger)length);
480     NSString* subStringWithAlternative = [aString substringWithRange:rangeWithAlternative];
481     if (!subStringWithAlternative)
482         return nil;
483
484     NSTextAlternatives* alternativeObject = [[[NSTextAlternatives alloc] initWithPrimaryString:subStringWithAlternative alternativeStrings:[NSArray arrayWithObject:alternative]] autorelease];
485     if (!alternativeObject)
486         return nil;
487
488     [dictatedString addAttribute:NSTextAlternativesAttributeName value:alternativeObject range:rangeWithAlternative];
489
490     return dictatedString;
491 }
492
493 - (void)setInputMethodHandler:(WebScriptObject *)handler
494 {
495     if (inputMethodHandler == handler)
496         return;
497     [handler retain];
498     [inputMethodHandler release];
499     inputMethodHandler = handler;
500 }
501
502 - (BOOL)interpretKeyEvents:(NSArray *)eventArray withSender:(WebHTMLView *)sender
503 {
504     if (!inputMethodHandler)
505         return NO;
506     
507     inputMethodView = sender;
508     
509     NSEvent *event = [eventArray objectAtIndex:0];
510     unsigned modifierFlags = [event modifierFlags]; 
511     NSMutableArray *modifiers = [[NSMutableArray alloc] init];
512     if (modifierFlags & NSAlphaShiftKeyMask)
513         [modifiers addObject:@"NSAlphaShiftKeyMask"];
514     if (modifierFlags & NSShiftKeyMask)
515         [modifiers addObject:@"NSShiftKeyMask"];
516     if (modifierFlags & NSControlKeyMask)
517         [modifiers addObject:@"NSControlKeyMask"];
518     if (modifierFlags & NSAlternateKeyMask)
519         [modifiers addObject:@"NSAlternateKeyMask"];
520     if (modifierFlags & NSCommandKeyMask)
521         [modifiers addObject:@"NSCommandKeyMask"];
522     if (modifierFlags & NSNumericPadKeyMask)
523         [modifiers addObject:@"NSNumericPadKeyMask"];
524     if (modifierFlags & NSHelpKeyMask)
525         [modifiers addObject:@"NSHelpKeyMask"];
526     if (modifierFlags & NSFunctionKeyMask)
527         [modifiers addObject:@"NSFunctionKeyMask"];
528     
529     WebScriptObject* eventParam = [inputMethodHandler evaluateWebScript:@"new Object();"];
530     [eventParam setValue:[event characters] forKey:@"characters"];
531     [eventParam setValue:[event charactersIgnoringModifiers] forKey:@"charactersIgnoringModifiers"];
532     [eventParam setValue:[NSNumber numberWithBool:[event isARepeat]] forKey:@"isARepeat"];
533     [eventParam setValue:[NSNumber numberWithUnsignedShort:[event keyCode]] forKey:@"keyCode"];
534     [eventParam setValue:modifiers forKey:@"modifierFlags"];
535
536     [modifiers release];
537     
538     id result = [inputMethodHandler callWebScriptMethod:@"call" withArguments:[NSArray arrayWithObjects:inputMethodHandler, eventParam, nil]];
539     if (![result respondsToSelector:@selector(boolValue)] || ![result boolValue]) 
540         [sender doCommandBySelector:@selector(noop:)]; // AppKit sends noop: if the ime does not handle an event
541     
542     inputMethodView = nil;    
543     return YES;
544 }
545
546 @end
547
548 #endif // !PLATFORM(IOS)