c2ce3787e10798ecc4e821c01aa65dbaf8d32f14
[WebKit-https.git] / Source / WebKit2 / UIProcess / mac / TextCheckerMac.mm
1 /*
2  * Copyright (C) 2010, 2011 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  * 1. Redistributions of source code must retain the above copyright
8  *    notice, this list of conditions and the following disclaimer.
9  * 2. Redistributions in binary form must reproduce the above copyright
10  *    notice, this list of conditions and the following disclaimer in the
11  *    documentation and/or other materials provided with the distribution.
12  *
13  * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
14  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
15  * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
17  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
23  * THE POSSIBILITY OF SUCH DAMAGE.
24  */
25
26 #import "config.h"
27 #import "TextChecker.h"
28
29 #if PLATFORM(MAC)
30
31 #import "TextCheckerState.h"
32 #import <WebCore/NotImplemented.h>
33 #import <wtf/RetainPtr.h>
34
35 @interface NSSpellChecker (WebNSSpellCheckerDetails)
36 - (NSString *)languageForWordRange:(NSRange)range inString:(NSString *)string orthography:(NSOrthography *)orthography;
37 @end
38
39 static NSString* const WebAutomaticSpellingCorrectionEnabled = @"WebAutomaticSpellingCorrectionEnabled";
40 static NSString* const WebContinuousSpellCheckingEnabled = @"WebContinuousSpellCheckingEnabled";
41 static NSString* const WebGrammarCheckingEnabled = @"WebGrammarCheckingEnabled";
42 static NSString* const WebSmartInsertDeleteEnabled = @"WebSmartInsertDeleteEnabled";
43 static NSString* const WebAutomaticQuoteSubstitutionEnabled = @"WebAutomaticQuoteSubstitutionEnabled";
44 static NSString* const WebAutomaticDashSubstitutionEnabled = @"WebAutomaticDashSubstitutionEnabled";
45 static NSString* const WebAutomaticLinkDetectionEnabled = @"WebAutomaticLinkDetectionEnabled";
46 static NSString* const WebAutomaticTextReplacementEnabled = @"WebAutomaticTextReplacementEnabled";
47
48 using namespace WebCore;
49
50 namespace WebKit {
51
52 TextCheckerState textCheckerState;
53
54 static bool shouldAutomaticTextReplacementBeEnabled()
55 {
56     NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
57     if (![defaults objectForKey:WebAutomaticTextReplacementEnabled])
58         return [NSSpellChecker isAutomaticTextReplacementEnabled];
59     return [defaults boolForKey:WebAutomaticTextReplacementEnabled];
60 }
61
62 static bool shouldAutomaticSpellingCorrectionBeEnabled()
63 {
64     NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
65     if (![defaults objectForKey:WebAutomaticSpellingCorrectionEnabled])
66         return [NSSpellChecker isAutomaticTextReplacementEnabled];
67     return [defaults boolForKey:WebAutomaticSpellingCorrectionEnabled];
68 }
69
70 static bool shouldAutomaticQuoteSubstitutionBeEnabled()
71 {
72     NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
73 #if __MAC_OS_X_VERSION_MIN_REQUIRED >= 1090
74     if (![defaults objectForKey:WebAutomaticQuoteSubstitutionEnabled])
75         return [NSSpellChecker isAutomaticQuoteSubstitutionEnabled];
76 #endif
77     return [defaults boolForKey:WebAutomaticQuoteSubstitutionEnabled];
78 }
79
80 static bool shouldAutomaticDashSubstitutionBeEnabled()
81 {
82     NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
83 #if __MAC_OS_X_VERSION_MIN_REQUIRED >= 1090
84     if (![defaults objectForKey:WebAutomaticDashSubstitutionEnabled])
85         return [NSSpellChecker isAutomaticDashSubstitutionEnabled];
86 #endif
87     return [defaults boolForKey:WebAutomaticDashSubstitutionEnabled];
88 }
89
90 static void initializeState()
91 {
92     static bool didInitializeState = false;
93
94     if (didInitializeState)
95         return;
96
97     textCheckerState.isContinuousSpellCheckingEnabled = [[NSUserDefaults standardUserDefaults] boolForKey:WebContinuousSpellCheckingEnabled] && TextChecker::isContinuousSpellCheckingAllowed();
98     textCheckerState.isGrammarCheckingEnabled = [[NSUserDefaults standardUserDefaults] boolForKey:WebGrammarCheckingEnabled];
99     textCheckerState.isAutomaticTextReplacementEnabled = shouldAutomaticTextReplacementBeEnabled();
100     textCheckerState.isAutomaticSpellingCorrectionEnabled = shouldAutomaticSpellingCorrectionBeEnabled();
101     textCheckerState.isAutomaticQuoteSubstitutionEnabled = shouldAutomaticQuoteSubstitutionBeEnabled();
102     textCheckerState.isAutomaticDashSubstitutionEnabled = shouldAutomaticDashSubstitutionBeEnabled();
103     textCheckerState.isAutomaticLinkDetectionEnabled = [[NSUserDefaults standardUserDefaults] boolForKey:WebAutomaticLinkDetectionEnabled];
104
105     didInitializeState = true;
106 }
107
108 const TextCheckerState& TextChecker::state()
109 {
110     initializeState();
111     return textCheckerState;
112 }
113
114 bool TextChecker::isContinuousSpellCheckingAllowed()
115 {
116     static bool allowContinuousSpellChecking = true;
117     static bool readAllowContinuousSpellCheckingDefault = false;
118
119     if (!readAllowContinuousSpellCheckingDefault) {
120         if ([[NSUserDefaults standardUserDefaults] objectForKey:@"NSAllowContinuousSpellChecking"])
121             allowContinuousSpellChecking = [[NSUserDefaults standardUserDefaults] boolForKey:@"NSAllowContinuousSpellChecking"];
122
123         readAllowContinuousSpellCheckingDefault = true;
124     }
125
126     return allowContinuousSpellChecking;
127 }
128
129 void TextChecker::setContinuousSpellCheckingEnabled(bool isContinuousSpellCheckingEnabled)
130 {
131     if (state().isContinuousSpellCheckingEnabled == isContinuousSpellCheckingEnabled)
132         return;
133                                                                                       
134     textCheckerState.isContinuousSpellCheckingEnabled = isContinuousSpellCheckingEnabled;
135     [[NSUserDefaults standardUserDefaults] setBool:isContinuousSpellCheckingEnabled forKey:WebContinuousSpellCheckingEnabled];
136
137     // FIXME: preflight the spell checker.
138 }
139
140 void TextChecker::setGrammarCheckingEnabled(bool isGrammarCheckingEnabled)
141 {
142     if (state().isGrammarCheckingEnabled == isGrammarCheckingEnabled)
143         return;
144
145     textCheckerState.isGrammarCheckingEnabled = isGrammarCheckingEnabled;
146     [[NSUserDefaults standardUserDefaults] setBool:isGrammarCheckingEnabled forKey:WebGrammarCheckingEnabled];    
147     [[NSSpellChecker sharedSpellChecker] updatePanels];
148     
149     // We call preflightSpellChecker() when turning continuous spell checking on, but we don't need to do that here
150     // because grammar checking only occurs on code paths that already preflight spell checking appropriately.
151 }
152
153 void TextChecker::setAutomaticSpellingCorrectionEnabled(bool isAutomaticSpellingCorrectionEnabled)
154 {
155     if (state().isAutomaticSpellingCorrectionEnabled == isAutomaticSpellingCorrectionEnabled)
156         return;
157
158     textCheckerState.isAutomaticSpellingCorrectionEnabled = isAutomaticSpellingCorrectionEnabled;
159     [[NSUserDefaults standardUserDefaults] setBool:isAutomaticSpellingCorrectionEnabled forKey:WebAutomaticSpellingCorrectionEnabled];    
160
161     [[NSSpellChecker sharedSpellChecker] updatePanels];
162 }
163
164 void TextChecker::setAutomaticQuoteSubstitutionEnabled(bool isAutomaticQuoteSubstitutionEnabled)
165 {
166     if (state().isAutomaticQuoteSubstitutionEnabled == isAutomaticQuoteSubstitutionEnabled)
167         return;
168
169     textCheckerState.isAutomaticQuoteSubstitutionEnabled = isAutomaticQuoteSubstitutionEnabled;
170     [[NSUserDefaults standardUserDefaults] setBool:isAutomaticQuoteSubstitutionEnabled forKey:WebAutomaticQuoteSubstitutionEnabled];    
171     
172     [[NSSpellChecker sharedSpellChecker] updatePanels];
173 }
174
175 void TextChecker::setAutomaticDashSubstitutionEnabled(bool isAutomaticDashSubstitutionEnabled)
176 {
177     if (state().isAutomaticDashSubstitutionEnabled == isAutomaticDashSubstitutionEnabled)
178         return;
179
180     textCheckerState.isAutomaticDashSubstitutionEnabled = isAutomaticDashSubstitutionEnabled;
181     [[NSUserDefaults standardUserDefaults] setBool:isAutomaticDashSubstitutionEnabled forKey:WebAutomaticDashSubstitutionEnabled];
182
183     [[NSSpellChecker sharedSpellChecker] updatePanels];
184 }
185
186 void TextChecker::setAutomaticLinkDetectionEnabled(bool isAutomaticLinkDetectionEnabled)
187 {
188     if (state().isAutomaticLinkDetectionEnabled == isAutomaticLinkDetectionEnabled)
189         return;
190     
191     textCheckerState.isAutomaticLinkDetectionEnabled = isAutomaticLinkDetectionEnabled;
192     [[NSUserDefaults standardUserDefaults] setBool:isAutomaticLinkDetectionEnabled forKey:WebAutomaticLinkDetectionEnabled];
193     
194     [[NSSpellChecker sharedSpellChecker] updatePanels];
195 }
196
197 void TextChecker::setAutomaticTextReplacementEnabled(bool isAutomaticTextReplacementEnabled)
198 {
199     if (state().isAutomaticTextReplacementEnabled == isAutomaticTextReplacementEnabled)
200         return;
201     
202     textCheckerState.isAutomaticTextReplacementEnabled = isAutomaticTextReplacementEnabled;
203     [[NSUserDefaults standardUserDefaults] setBool:isAutomaticTextReplacementEnabled forKey:WebAutomaticTextReplacementEnabled];
204
205     [[NSSpellChecker sharedSpellChecker] updatePanels];
206 }
207
208 static bool smartInsertDeleteEnabled;
209     
210 bool TextChecker::isSmartInsertDeleteEnabled()
211 {
212     static bool readSmartInsertDeleteEnabledDefault;
213
214     if (!readSmartInsertDeleteEnabledDefault) {
215         smartInsertDeleteEnabled = ![[NSUserDefaults standardUserDefaults] objectForKey:WebSmartInsertDeleteEnabled] || [[NSUserDefaults standardUserDefaults] boolForKey:WebSmartInsertDeleteEnabled];
216
217         readSmartInsertDeleteEnabledDefault = true;
218     }
219
220     return smartInsertDeleteEnabled;
221 }
222
223 void TextChecker::setSmartInsertDeleteEnabled(bool flag)
224 {
225     if (flag == isSmartInsertDeleteEnabled())
226         return;
227
228     smartInsertDeleteEnabled = flag;
229
230     [[NSUserDefaults standardUserDefaults] setBool:flag forKey:WebSmartInsertDeleteEnabled];
231 }
232
233 void TextChecker::didChangeAutomaticTextReplacementEnabled()
234 {
235     textCheckerState.isAutomaticTextReplacementEnabled = shouldAutomaticTextReplacementBeEnabled();
236     [[NSSpellChecker sharedSpellChecker] updatePanels];
237 }
238
239 void TextChecker::didChangeAutomaticSpellingCorrectionEnabled()
240 {
241     textCheckerState.isAutomaticSpellingCorrectionEnabled = shouldAutomaticSpellingCorrectionBeEnabled();
242     [[NSSpellChecker sharedSpellChecker] updatePanels];
243 }
244
245 void TextChecker::didChangeAutomaticQuoteSubstitutionEnabled()
246 {
247 #if __MAC_OS_X_VERSION_MIN_REQUIRED >= 1090
248     textCheckerState.isAutomaticQuoteSubstitutionEnabled = shouldAutomaticQuoteSubstitutionBeEnabled();
249     [[NSSpellChecker sharedSpellChecker] updatePanels];
250 #endif
251 }
252
253 void TextChecker::didChangeAutomaticDashSubstitutionEnabled()
254 {
255 #if __MAC_OS_X_VERSION_MIN_REQUIRED >= 1090
256     textCheckerState.isAutomaticDashSubstitutionEnabled = shouldAutomaticDashSubstitutionBeEnabled();
257     [[NSSpellChecker sharedSpellChecker] updatePanels];
258 #endif
259 }
260
261 bool TextChecker::substitutionsPanelIsShowing()
262 {
263     return [[[NSSpellChecker sharedSpellChecker] substitutionsPanel] isVisible];
264 }
265
266 void TextChecker::toggleSubstitutionsPanelIsShowing()
267 {
268     NSPanel *substitutionsPanel = [[NSSpellChecker sharedSpellChecker] substitutionsPanel];
269     if ([substitutionsPanel isVisible]) {
270         [substitutionsPanel orderOut:nil];
271         return;
272     }
273     [substitutionsPanel orderFront:nil];
274 }
275
276 void TextChecker::continuousSpellCheckingEnabledStateChanged(bool enabled)
277 {
278     textCheckerState.isContinuousSpellCheckingEnabled = enabled;
279 }
280
281 void TextChecker::grammarCheckingEnabledStateChanged(bool enabled)
282 {
283     textCheckerState.isGrammarCheckingEnabled = enabled;
284 }
285
286 int64_t TextChecker::uniqueSpellDocumentTag(WebPageProxy*)
287 {
288     return [NSSpellChecker uniqueSpellDocumentTag];
289 }
290
291 void TextChecker::closeSpellDocumentWithTag(int64_t tag)
292 {
293     [[NSSpellChecker sharedSpellChecker] closeSpellDocumentWithTag:tag];
294 }
295
296 #if USE(UNIFIED_TEXT_CHECKING)
297
298 Vector<TextCheckingResult> TextChecker::checkTextOfParagraph(int64_t spellDocumentTag, const UChar* text, int length, uint64_t checkingTypes)
299 {
300     Vector<TextCheckingResult> results;
301
302     RetainPtr<NSString> textString = adoptNS([[NSString alloc] initWithCharactersNoCopy:const_cast<UChar*>(text) length:length freeWhenDone:NO]);
303     NSArray *incomingResults = [[NSSpellChecker sharedSpellChecker] checkString:textString .get()
304                                                                           range:NSMakeRange(0, length)
305                                                                           types:checkingTypes | NSTextCheckingTypeOrthography
306                                                                         options:nil
307                                                          inSpellDocumentWithTag:spellDocumentTag 
308                                                                     orthography:NULL
309                                                                       wordCount:NULL];
310     for (NSTextCheckingResult *incomingResult in incomingResults) {
311         NSRange resultRange = [incomingResult range];
312         NSTextCheckingType resultType = [incomingResult resultType];
313         ASSERT(resultRange.location != NSNotFound);
314         ASSERT(resultRange.length > 0);
315         if (resultType == NSTextCheckingTypeSpelling && (checkingTypes & NSTextCheckingTypeSpelling)) {
316             TextCheckingResult result;
317             result.type = TextCheckingTypeSpelling;
318             result.location = resultRange.location;
319             result.length = resultRange.length;
320             results.append(result);
321         } else if (resultType == NSTextCheckingTypeGrammar && (checkingTypes & NSTextCheckingTypeGrammar)) {
322             TextCheckingResult result;
323             NSArray *details = [incomingResult grammarDetails];
324             result.type = TextCheckingTypeGrammar;
325             result.location = resultRange.location;
326             result.length = resultRange.length;
327             for (NSDictionary *incomingDetail in details) {
328                 ASSERT(incomingDetail);
329                 GrammarDetail detail;
330                 NSValue *detailRangeAsNSValue = [incomingDetail objectForKey:NSGrammarRange];
331                 ASSERT(detailRangeAsNSValue);
332                 NSRange detailNSRange = [detailRangeAsNSValue rangeValue];
333                 ASSERT(detailNSRange.location != NSNotFound);
334                 ASSERT(detailNSRange.length > 0);
335                 detail.location = detailNSRange.location;
336                 detail.length = detailNSRange.length;
337                 detail.userDescription = [incomingDetail objectForKey:NSGrammarUserDescription];
338                 NSArray *guesses = [incomingDetail objectForKey:NSGrammarCorrections];
339                 for (NSString *guess in guesses)
340                     detail.guesses.append(String(guess));
341                 result.details.append(detail);
342             }
343             results.append(result);
344         } else if (resultType == NSTextCheckingTypeLink && (checkingTypes & NSTextCheckingTypeLink)) {
345             TextCheckingResult result;
346             result.type = TextCheckingTypeLink;
347             result.location = resultRange.location;
348             result.length = resultRange.length;
349             result.replacement = [[incomingResult URL] absoluteString];
350             results.append(result);
351         } else if (resultType == NSTextCheckingTypeQuote && (checkingTypes & NSTextCheckingTypeQuote)) {
352             TextCheckingResult result;
353             result.type = TextCheckingTypeQuote;
354             result.location = resultRange.location;
355             result.length = resultRange.length;
356             result.replacement = [incomingResult replacementString];
357             results.append(result);
358         } else if (resultType == NSTextCheckingTypeDash && (checkingTypes & NSTextCheckingTypeDash)) {
359             TextCheckingResult result;
360             result.type = TextCheckingTypeDash;
361             result.location = resultRange.location;
362             result.length = resultRange.length;
363             result.replacement = [incomingResult replacementString];
364             results.append(result);
365         } else if (resultType == NSTextCheckingTypeReplacement && (checkingTypes & NSTextCheckingTypeReplacement)) {
366             TextCheckingResult result;
367             result.type = TextCheckingTypeReplacement;
368             result.location = resultRange.location;
369             result.length = resultRange.length;
370             result.replacement = [incomingResult replacementString];
371             results.append(result);
372         } else if (resultType == NSTextCheckingTypeCorrection && (checkingTypes & NSTextCheckingTypeCorrection)) {
373             TextCheckingResult result;
374             result.type = TextCheckingTypeCorrection;
375             result.location = resultRange.location;
376             result.length = resultRange.length;
377             result.replacement = [incomingResult replacementString];
378             results.append(result);
379         }
380     }
381
382     return results;
383 }
384
385 #endif
386
387 void TextChecker::checkSpellingOfString(int64_t, const UChar*, uint32_t, int32_t&, int32_t&)
388 {
389     // Mac uses checkTextOfParagraph instead.
390     notImplemented();
391 }
392
393 void TextChecker::checkGrammarOfString(int64_t, const UChar*, uint32_t, Vector<WebCore::GrammarDetail>&, int32_t&, int32_t&)
394 {
395     // Mac uses checkTextOfParagraph instead.
396     notImplemented();
397 }
398
399 bool TextChecker::spellingUIIsShowing()
400 {
401     return [[[NSSpellChecker sharedSpellChecker] spellingPanel] isVisible];
402 }
403
404 void TextChecker::toggleSpellingUIIsShowing()
405 {
406     NSPanel *spellingPanel = [[NSSpellChecker sharedSpellChecker] spellingPanel];
407     if ([spellingPanel isVisible])
408         [spellingPanel orderOut:nil];
409     else
410         [spellingPanel orderFront:nil];
411 }
412
413 void TextChecker::updateSpellingUIWithMisspelledWord(int64_t, const String& misspelledWord)
414 {
415     [[NSSpellChecker sharedSpellChecker] updateSpellingPanelWithMisspelledWord:misspelledWord];
416 }
417
418 void TextChecker::updateSpellingUIWithGrammarString(int64_t, const String& badGrammarPhrase, const GrammarDetail& grammarDetail)
419 {
420     RetainPtr<NSMutableArray> corrections = adoptNS([[NSMutableArray alloc] init]);
421     for (size_t i = 0; i < grammarDetail.guesses.size(); ++i) {
422         NSString *guess = grammarDetail.guesses[i];
423         [corrections addObject:guess];
424     }
425
426     NSRange grammarRange = NSMakeRange(grammarDetail.location, grammarDetail.length);
427     NSString *grammarUserDescription = grammarDetail.userDescription;
428     RetainPtr<NSDictionary> grammarDetailDict = adoptNS([[NSDictionary alloc] initWithObjectsAndKeys:[NSValue valueWithRange:grammarRange], NSGrammarRange, grammarUserDescription, NSGrammarUserDescription, corrections.get(), NSGrammarCorrections, nil]);
429
430     [[NSSpellChecker sharedSpellChecker] updateSpellingPanelWithGrammarString:badGrammarPhrase detail:grammarDetailDict.get()];
431 }
432
433 void TextChecker::getGuessesForWord(int64_t spellDocumentTag, const String& word, const String& context, Vector<String>& guesses)
434 {
435     NSString* language = nil;
436     NSOrthography* orthography = nil;
437     NSSpellChecker *checker = [NSSpellChecker sharedSpellChecker];
438     if (context.length()) {
439         [checker checkString:context range:NSMakeRange(0, context.length()) types:NSTextCheckingTypeOrthography options:0 inSpellDocumentWithTag:spellDocumentTag orthography:&orthography wordCount:0];
440         language = [checker languageForWordRange:NSMakeRange(0, context.length()) inString:context orthography:orthography];
441     }
442     NSArray* stringsArray = [checker guessesForWordRange:NSMakeRange(0, word.length()) inString:word language:language inSpellDocumentWithTag:spellDocumentTag];
443
444     for (NSString *guess in stringsArray)
445         guesses.append(guess);
446 }
447
448 void TextChecker::learnWord(int64_t, const String& word)
449 {
450     [[NSSpellChecker sharedSpellChecker] learnWord:word];
451 }
452
453 void TextChecker::ignoreWord(int64_t spellDocumentTag, const String& word)
454 {
455     [[NSSpellChecker sharedSpellChecker] ignoreWord:word inSpellDocumentWithTag:spellDocumentTag];
456 }
457
458 void TextChecker::requestCheckingOfString(PassRefPtr<TextCheckerCompletion>)
459 {
460     notImplemented();
461 }
462
463 } // namespace WebKit
464
465 #endif // PLATFORM(MAC)