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