TextCheckerMac.mm revision 81bc750723a18f21cd17d1b173cd2a4dda9cea6e
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#import "TextCheckerState.h"
30#import <wtf/RetainPtr.h>
31
32#ifndef BUILDING_ON_SNOW_LEOPARD
33#import <AppKit/NSTextChecker.h>
34#endif
35
36static NSString* const WebAutomaticSpellingCorrectionEnabled = @"WebAutomaticSpellingCorrectionEnabled";
37static NSString* const WebContinuousSpellCheckingEnabled = @"WebContinuousSpellCheckingEnabled";
38static NSString* const WebGrammarCheckingEnabled = @"WebGrammarCheckingEnabled";
39static NSString* const WebSmartInsertDeleteEnabled = @"WebSmartInsertDeleteEnabled";
40static NSString* const WebAutomaticQuoteSubstitutionEnabled = @"WebAutomaticQuoteSubstitutionEnabled";
41static NSString* const WebAutomaticDashSubstitutionEnabled = @"WebAutomaticDashSubstitutionEnabled";
42static NSString* const WebAutomaticLinkDetectionEnabled = @"WebAutomaticLinkDetectionEnabled";
43static NSString* const WebAutomaticTextReplacementEnabled = @"WebAutomaticTextReplacementEnabled";
44
45using namespace WebCore;
46
47namespace WebKit {
48
49TextCheckerState textCheckerState;
50
51static void initializeState()
52{
53    static bool didInitializeState;
54    if (didInitializeState)
55        return;
56
57    textCheckerState.isContinuousSpellCheckingEnabled = [[NSUserDefaults standardUserDefaults] boolForKey:WebContinuousSpellCheckingEnabled] && TextChecker::isContinuousSpellCheckingAllowed();
58    textCheckerState.isGrammarCheckingEnabled = [[NSUserDefaults standardUserDefaults] boolForKey:WebGrammarCheckingEnabled];
59    textCheckerState.isAutomaticSpellingCorrectionEnabled = [[NSUserDefaults standardUserDefaults] boolForKey:WebAutomaticSpellingCorrectionEnabled];
60    textCheckerState.isAutomaticQuoteSubstitutionEnabled = [[NSUserDefaults standardUserDefaults] boolForKey:WebAutomaticQuoteSubstitutionEnabled];
61    textCheckerState.isAutomaticDashSubstitutionEnabled = [[NSUserDefaults standardUserDefaults] boolForKey:WebAutomaticDashSubstitutionEnabled];
62    textCheckerState.isAutomaticLinkDetectionEnabled = [[NSUserDefaults standardUserDefaults] boolForKey:WebAutomaticLinkDetectionEnabled];
63    textCheckerState.isAutomaticTextReplacementEnabled = [[NSUserDefaults standardUserDefaults] boolForKey:WebAutomaticTextReplacementEnabled];
64
65#if !defined(BUILDING_ON_SNOW_LEOPARD)
66    if (![[NSUserDefaults standardUserDefaults] objectForKey:WebAutomaticSpellingCorrectionEnabled])
67        textCheckerState.isAutomaticSpellingCorrectionEnabled = [NSSpellChecker isAutomaticSpellingCorrectionEnabled];
68#endif
69
70    didInitializeState = true;
71}
72
73const TextCheckerState& TextChecker::state()
74{
75    initializeState();
76    return textCheckerState;
77}
78
79bool TextChecker::isContinuousSpellCheckingAllowed()
80{
81    static bool allowContinuousSpellChecking = true;
82    static bool readAllowContinuousSpellCheckingDefault = false;
83
84    if (!readAllowContinuousSpellCheckingDefault) {
85        if ([[NSUserDefaults standardUserDefaults] objectForKey:@"NSAllowContinuousSpellChecking"])
86            allowContinuousSpellChecking = [[NSUserDefaults standardUserDefaults] boolForKey:@"NSAllowContinuousSpellChecking"];
87
88        readAllowContinuousSpellCheckingDefault = true;
89    }
90
91    return allowContinuousSpellChecking;
92}
93
94void TextChecker::setContinuousSpellCheckingEnabled(bool isContinuousSpellCheckingEnabled)
95{
96    if (state().isContinuousSpellCheckingEnabled == isContinuousSpellCheckingEnabled)
97        return;
98
99    textCheckerState.isContinuousSpellCheckingEnabled = isContinuousSpellCheckingEnabled;
100    [[NSUserDefaults standardUserDefaults] setBool:isContinuousSpellCheckingEnabled forKey:WebContinuousSpellCheckingEnabled];
101
102    // FIXME: preflight the spell checker.
103}
104
105void TextChecker::setGrammarCheckingEnabled(bool isGrammarCheckingEnabled)
106{
107    if (state().isGrammarCheckingEnabled == isGrammarCheckingEnabled)
108        return;
109
110    textCheckerState.isGrammarCheckingEnabled = isGrammarCheckingEnabled;
111    [[NSUserDefaults standardUserDefaults] setBool:isGrammarCheckingEnabled forKey:WebGrammarCheckingEnabled];
112    [[NSSpellChecker sharedSpellChecker] updatePanels];
113
114    // We call preflightSpellChecker() when turning continuous spell checking on, but we don't need to do that here
115    // because grammar checking only occurs on code paths that already preflight spell checking appropriately.
116}
117
118void TextChecker::setAutomaticSpellingCorrectionEnabled(bool isAutomaticSpellingCorrectionEnabled)
119{
120    if (state().isAutomaticSpellingCorrectionEnabled == isAutomaticSpellingCorrectionEnabled)
121        return;
122
123    textCheckerState.isAutomaticSpellingCorrectionEnabled = isAutomaticSpellingCorrectionEnabled;
124    [[NSUserDefaults standardUserDefaults] setBool:isAutomaticSpellingCorrectionEnabled forKey:WebAutomaticSpellingCorrectionEnabled];
125
126    [[NSSpellChecker sharedSpellChecker] updatePanels];
127}
128
129void TextChecker::setAutomaticQuoteSubstitutionEnabled(bool isAutomaticQuoteSubstitutionEnabled)
130{
131    if (state().isAutomaticQuoteSubstitutionEnabled == isAutomaticQuoteSubstitutionEnabled)
132        return;
133
134    textCheckerState.isAutomaticQuoteSubstitutionEnabled = isAutomaticQuoteSubstitutionEnabled;
135    [[NSUserDefaults standardUserDefaults] setBool:isAutomaticQuoteSubstitutionEnabled forKey:WebAutomaticQuoteSubstitutionEnabled];
136
137    [[NSSpellChecker sharedSpellChecker] updatePanels];
138}
139
140void TextChecker::setAutomaticDashSubstitutionEnabled(bool isAutomaticDashSubstitutionEnabled)
141{
142    if (state().isAutomaticDashSubstitutionEnabled == isAutomaticDashSubstitutionEnabled)
143        return;
144
145    textCheckerState.isAutomaticDashSubstitutionEnabled = isAutomaticDashSubstitutionEnabled;
146    [[NSUserDefaults standardUserDefaults] setBool:isAutomaticDashSubstitutionEnabled forKey:WebAutomaticDashSubstitutionEnabled];
147
148    [[NSSpellChecker sharedSpellChecker] updatePanels];
149}
150
151void TextChecker::setAutomaticLinkDetectionEnabled(bool isAutomaticLinkDetectionEnabled)
152{
153    if (state().isAutomaticLinkDetectionEnabled == isAutomaticLinkDetectionEnabled)
154        return;
155
156    textCheckerState.isAutomaticLinkDetectionEnabled = isAutomaticLinkDetectionEnabled;
157    [[NSUserDefaults standardUserDefaults] setBool:isAutomaticLinkDetectionEnabled forKey:WebAutomaticLinkDetectionEnabled];
158
159    [[NSSpellChecker sharedSpellChecker] updatePanels];
160}
161
162void TextChecker::setAutomaticTextReplacementEnabled(bool isAutomaticTextReplacementEnabled)
163{
164    if (state().isAutomaticTextReplacementEnabled == isAutomaticTextReplacementEnabled)
165        return;
166
167    textCheckerState.isAutomaticTextReplacementEnabled = isAutomaticTextReplacementEnabled;
168    [[NSUserDefaults standardUserDefaults] setBool:isAutomaticTextReplacementEnabled forKey:WebAutomaticTextReplacementEnabled];
169
170    [[NSSpellChecker sharedSpellChecker] updatePanels];
171}
172
173static bool smartInsertDeleteEnabled;
174
175bool TextChecker::isSmartInsertDeleteEnabled()
176{
177    static bool readSmartInsertDeleteEnabledDefault;
178
179    if (!readSmartInsertDeleteEnabledDefault) {
180        smartInsertDeleteEnabled = [[NSUserDefaults standardUserDefaults] boolForKey:WebSmartInsertDeleteEnabled];
181
182        readSmartInsertDeleteEnabledDefault = true;
183    }
184
185    return smartInsertDeleteEnabled;
186}
187
188void TextChecker::setSmartInsertDeleteEnabled(bool flag)
189{
190    if (flag == isSmartInsertDeleteEnabled())
191        return;
192
193    smartInsertDeleteEnabled = flag;
194
195    [[NSUserDefaults standardUserDefaults] setBool:flag forKey:WebSmartInsertDeleteEnabled];
196}
197
198int64_t TextChecker::uniqueSpellDocumentTag()
199{
200    return [NSSpellChecker uniqueSpellDocumentTag];
201}
202
203void TextChecker::closeSpellDocumentWithTag(int64_t tag)
204{
205    [[NSSpellChecker sharedSpellChecker] closeSpellDocumentWithTag:tag];
206}
207
208Vector<TextCheckingResult> TextChecker::checkTextOfParagraph(int64_t spellDocumentTag, const UChar* text, int length, uint64_t checkingTypes)
209{
210    Vector<TextCheckingResult> results;
211
212    RetainPtr<NSString> textString(AdoptNS, [[NSString alloc] initWithCharactersNoCopy:const_cast<UChar*>(text) length:length freeWhenDone:NO]);
213    NSArray *incomingResults = [[NSSpellChecker sharedSpellChecker] checkString:textString .get()
214                                                                          range:NSMakeRange(0, length)
215                                                                          types:checkingTypes | NSTextCheckingTypeOrthography
216                                                                        options:nil
217                                                         inSpellDocumentWithTag:spellDocumentTag
218                                                                    orthography:NULL
219                                                                      wordCount:NULL];
220    for (NSTextCheckingResult *incomingResult in incomingResults) {
221        NSRange resultRange = [incomingResult range];
222        NSTextCheckingType resultType = [incomingResult resultType];
223        ASSERT(resultRange.location != NSNotFound);
224        ASSERT(resultRange.length > 0);
225        if (resultType == NSTextCheckingTypeSpelling && (checkingTypes & NSTextCheckingTypeSpelling)) {
226            TextCheckingResult result;
227            result.type = TextCheckingTypeSpelling;
228            result.location = resultRange.location;
229            result.length = resultRange.length;
230            results.append(result);
231        } else if (resultType == NSTextCheckingTypeGrammar && (checkingTypes & NSTextCheckingTypeGrammar)) {
232            TextCheckingResult result;
233            NSArray *details = [incomingResult grammarDetails];
234            result.type = TextCheckingTypeGrammar;
235            result.location = resultRange.location;
236            result.length = resultRange.length;
237            for (NSDictionary *incomingDetail in details) {
238                ASSERT(incomingDetail);
239                GrammarDetail detail;
240                NSValue *detailRangeAsNSValue = [incomingDetail objectForKey:NSGrammarRange];
241                ASSERT(detailRangeAsNSValue);
242                NSRange detailNSRange = [detailRangeAsNSValue rangeValue];
243                ASSERT(detailNSRange.location != NSNotFound);
244                ASSERT(detailNSRange.length > 0);
245                detail.location = detailNSRange.location;
246                detail.length = detailNSRange.length;
247                detail.userDescription = [incomingDetail objectForKey:NSGrammarUserDescription];
248                NSArray *guesses = [incomingDetail objectForKey:NSGrammarCorrections];
249                for (NSString *guess in guesses)
250                    detail.guesses.append(String(guess));
251                result.details.append(detail);
252            }
253            results.append(result);
254        } else if (resultType == NSTextCheckingTypeLink && (checkingTypes & NSTextCheckingTypeLink)) {
255            TextCheckingResult result;
256            result.type = TextCheckingTypeLink;
257            result.location = resultRange.location;
258            result.length = resultRange.length;
259            result.replacement = [[incomingResult URL] absoluteString];
260            results.append(result);
261        } else if (resultType == NSTextCheckingTypeQuote && (checkingTypes & NSTextCheckingTypeQuote)) {
262            TextCheckingResult result;
263            result.type = TextCheckingTypeQuote;
264            result.location = resultRange.location;
265            result.length = resultRange.length;
266            result.replacement = [incomingResult replacementString];
267            results.append(result);
268        } else if (resultType == NSTextCheckingTypeDash && (checkingTypes & NSTextCheckingTypeDash)) {
269            TextCheckingResult result;
270            result.type = TextCheckingTypeDash;
271            result.location = resultRange.location;
272            result.length = resultRange.length;
273            result.replacement = [incomingResult replacementString];
274            results.append(result);
275        } else if (resultType == NSTextCheckingTypeReplacement && (checkingTypes & NSTextCheckingTypeReplacement)) {
276            TextCheckingResult result;
277            result.type = TextCheckingTypeReplacement;
278            result.location = resultRange.location;
279            result.length = resultRange.length;
280            result.replacement = [incomingResult replacementString];
281            results.append(result);
282        } else if (resultType == NSTextCheckingTypeCorrection && (checkingTypes & NSTextCheckingTypeCorrection)) {
283            TextCheckingResult result;
284            result.type = TextCheckingTypeCorrection;
285            result.location = resultRange.location;
286            result.length = resultRange.length;
287            result.replacement = [incomingResult replacementString];
288            results.append(result);
289        }
290    }
291
292    return results;
293}
294
295void TextChecker::updateSpellingUIWithMisspelledWord(const String& misspelledWord)
296{
297    [[NSSpellChecker sharedSpellChecker] updateSpellingPanelWithMisspelledWord:misspelledWord];
298}
299
300void TextChecker::updateSpellingUIWithGrammarString(const String& badGrammarPhrase, const GrammarDetail& grammarDetail)
301{
302    RetainPtr<NSMutableArray> corrections(AdoptNS, [[NSMutableArray alloc] init]);
303    for (size_t i = 0; i < grammarDetail.guesses.size(); ++i) {
304        NSString *guess = grammarDetail.guesses[i];
305        [corrections.get() addObject:guess];
306    }
307
308    NSRange grammarRange = NSMakeRange(grammarDetail.location, grammarDetail.length);
309    NSString *grammarUserDescription = grammarDetail.userDescription;
310    RetainPtr<NSMutableDictionary> grammarDetailDict(AdoptNS, [[NSDictionary alloc] initWithObjectsAndKeys:[NSValue valueWithRange:grammarRange], NSGrammarRange, grammarUserDescription, NSGrammarUserDescription, corrections.get(), NSGrammarCorrections, nil]);
311
312    [[NSSpellChecker sharedSpellChecker] updateSpellingPanelWithGrammarString:badGrammarPhrase detail:grammarDetailDict.get()];
313}
314
315void TextChecker::getGuessesForWord(int64_t spellDocumentTag, const String& word, const String& context, Vector<String>& guesses)
316{
317#if !defined(BUILDING_ON_SNOW_LEOPARD)
318    NSString* language = nil;
319    NSOrthography* orthography = nil;
320    NSSpellChecker *checker = [NSSpellChecker sharedSpellChecker];
321    if (context.length()) {
322        [checker checkString:context range:NSMakeRange(0, context.length()) types:NSTextCheckingTypeOrthography options:0 inSpellDocumentWithTag:spellDocumentTag orthography:&orthography wordCount:0];
323        language = [checker languageForWordRange:NSMakeRange(0, context.length()) inString:context orthography:orthography];
324    }
325    NSArray* stringsArray = [checker guessesForWordRange:NSMakeRange(0, word.length()) inString:word language:language inSpellDocumentWithTag:spellDocumentTag];
326#else
327    NSArray* stringsArray = [[NSSpellChecker sharedSpellChecker] guessesForWord:word];
328#endif
329
330    for (NSString *guess in stringsArray)
331        guesses.append(guess);
332}
333
334void TextChecker::learnWord(const String& word)
335{
336    [[NSSpellChecker sharedSpellChecker] learnWord:word];
337}
338
339void TextChecker::ignoreWord(int64_t spellDocumentTag, const String& word)
340{
341    [[NSSpellChecker sharedSpellChecker] ignoreWord:word inSpellDocumentWithTag:spellDocumentTag];
342}
343
344} // namespace WebKit
345