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