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