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 bool TextChecker::substitutionsPanelIsShowing()
200 {
201     return [[[NSSpellChecker sharedSpellChecker] substitutionsPanel] isVisible];
202 }
203
204 void TextChecker::toggleSubstitutionsPanelIsShowing()
205 {
206     NSPanel *substitutionsPanel = [[NSSpellChecker sharedSpellChecker] substitutionsPanel];
207     if ([substitutionsPanel isVisible]) {
208         [substitutionsPanel orderOut:nil];
209         return;
210     }
211     [substitutionsPanel orderFront:nil];
212 }
213
214 int64_t TextChecker::uniqueSpellDocumentTag(WebPageProxy*)
215 {
216     return [NSSpellChecker uniqueSpellDocumentTag];
217 }
218
219 void TextChecker::closeSpellDocumentWithTag(int64_t tag)
220 {
221     [[NSSpellChecker sharedSpellChecker] closeSpellDocumentWithTag:tag];
222 }
223
224 #if USE(UNIFIED_TEXT_CHECKING)
225
226 Vector<TextCheckingResult> TextChecker::checkTextOfParagraph(int64_t spellDocumentTag, const UChar* text, int length, uint64_t checkingTypes)
227 {
228     Vector<TextCheckingResult> results;
229
230     RetainPtr<NSString> textString(AdoptNS, [[NSString alloc] initWithCharactersNoCopy:const_cast<UChar*>(text) length:length freeWhenDone:NO]);
231     NSArray *incomingResults = [[NSSpellChecker sharedSpellChecker] checkString:textString .get()
232                                                                           range:NSMakeRange(0, length)
233                                                                           types:checkingTypes | NSTextCheckingTypeOrthography
234                                                                         options:nil
235                                                          inSpellDocumentWithTag:spellDocumentTag 
236                                                                     orthography:NULL
237                                                                       wordCount:NULL];
238     for (NSTextCheckingResult *incomingResult in incomingResults) {
239         NSRange resultRange = [incomingResult range];
240         NSTextCheckingType resultType = [incomingResult resultType];
241         ASSERT(resultRange.location != NSNotFound);
242         ASSERT(resultRange.length > 0);
243         if (resultType == NSTextCheckingTypeSpelling && (checkingTypes & NSTextCheckingTypeSpelling)) {
244             TextCheckingResult result;
245             result.type = TextCheckingTypeSpelling;
246             result.location = resultRange.location;
247             result.length = resultRange.length;
248             results.append(result);
249         } else if (resultType == NSTextCheckingTypeGrammar && (checkingTypes & NSTextCheckingTypeGrammar)) {
250             TextCheckingResult result;
251             NSArray *details = [incomingResult grammarDetails];
252             result.type = TextCheckingTypeGrammar;
253             result.location = resultRange.location;
254             result.length = resultRange.length;
255             for (NSDictionary *incomingDetail in details) {
256                 ASSERT(incomingDetail);
257                 GrammarDetail detail;
258                 NSValue *detailRangeAsNSValue = [incomingDetail objectForKey:NSGrammarRange];
259                 ASSERT(detailRangeAsNSValue);
260                 NSRange detailNSRange = [detailRangeAsNSValue rangeValue];
261                 ASSERT(detailNSRange.location != NSNotFound);
262                 ASSERT(detailNSRange.length > 0);
263                 detail.location = detailNSRange.location;
264                 detail.length = detailNSRange.length;
265                 detail.userDescription = [incomingDetail objectForKey:NSGrammarUserDescription];
266                 NSArray *guesses = [incomingDetail objectForKey:NSGrammarCorrections];
267                 for (NSString *guess in guesses)
268                     detail.guesses.append(String(guess));
269                 result.details.append(detail);
270             }
271             results.append(result);
272         } else if (resultType == NSTextCheckingTypeLink && (checkingTypes & NSTextCheckingTypeLink)) {
273             TextCheckingResult result;
274             result.type = TextCheckingTypeLink;
275             result.location = resultRange.location;
276             result.length = resultRange.length;
277             result.replacement = [[incomingResult URL] absoluteString];
278             results.append(result);
279         } else if (resultType == NSTextCheckingTypeQuote && (checkingTypes & NSTextCheckingTypeQuote)) {
280             TextCheckingResult result;
281             result.type = TextCheckingTypeQuote;
282             result.location = resultRange.location;
283             result.length = resultRange.length;
284             result.replacement = [incomingResult replacementString];
285             results.append(result);
286         } else if (resultType == NSTextCheckingTypeDash && (checkingTypes & NSTextCheckingTypeDash)) {
287             TextCheckingResult result;
288             result.type = TextCheckingTypeDash;
289             result.location = resultRange.location;
290             result.length = resultRange.length;
291             result.replacement = [incomingResult replacementString];
292             results.append(result);
293         } else if (resultType == NSTextCheckingTypeReplacement && (checkingTypes & NSTextCheckingTypeReplacement)) {
294             TextCheckingResult result;
295             result.type = TextCheckingTypeReplacement;
296             result.location = resultRange.location;
297             result.length = resultRange.length;
298             result.replacement = [incomingResult replacementString];
299             results.append(result);
300         } else if (resultType == NSTextCheckingTypeCorrection && (checkingTypes & NSTextCheckingTypeCorrection)) {
301             TextCheckingResult result;
302             result.type = TextCheckingTypeCorrection;
303             result.location = resultRange.location;
304             result.length = resultRange.length;
305             result.replacement = [incomingResult replacementString];
306             results.append(result);
307         }
308     }
309
310     return results;
311 }
312
313 #endif
314
315 void TextChecker::checkSpellingOfString(int64_t, const UChar*, uint32_t, int32_t&, int32_t&)
316 {
317     // Mac uses checkTextOfParagraph instead.
318     notImplemented();
319 }
320
321 void TextChecker::checkGrammarOfString(int64_t, const UChar*, uint32_t, Vector<WebCore::GrammarDetail>&, int32_t&, int32_t&)
322 {
323     // Mac uses checkTextOfParagraph instead.
324     notImplemented();
325 }
326
327 bool TextChecker::spellingUIIsShowing()
328 {
329     return [[[NSSpellChecker sharedSpellChecker] spellingPanel] isVisible];
330 }
331
332 void TextChecker::toggleSpellingUIIsShowing()
333 {
334     NSPanel *spellingPanel = [[NSSpellChecker sharedSpellChecker] spellingPanel];
335     if ([spellingPanel isVisible])
336         [spellingPanel orderOut:nil];
337     else
338         [spellingPanel orderFront:nil];
339 }
340
341 void TextChecker::updateSpellingUIWithMisspelledWord(int64_t, const String& misspelledWord)
342 {
343     [[NSSpellChecker sharedSpellChecker] updateSpellingPanelWithMisspelledWord:misspelledWord];
344 }
345
346 void TextChecker::updateSpellingUIWithGrammarString(int64_t, const String& badGrammarPhrase, const GrammarDetail& grammarDetail)
347 {
348     RetainPtr<NSMutableArray> corrections(AdoptNS, [[NSMutableArray alloc] init]);
349     for (size_t i = 0; i < grammarDetail.guesses.size(); ++i) {
350         NSString *guess = grammarDetail.guesses[i];
351         [corrections.get() addObject:guess];
352     }
353
354     NSRange grammarRange = NSMakeRange(grammarDetail.location, grammarDetail.length);
355     NSString *grammarUserDescription = grammarDetail.userDescription;
356     RetainPtr<NSMutableDictionary> grammarDetailDict(AdoptNS, [[NSDictionary alloc] initWithObjectsAndKeys:[NSValue valueWithRange:grammarRange], NSGrammarRange, grammarUserDescription, NSGrammarUserDescription, corrections.get(), NSGrammarCorrections, nil]);
357
358     [[NSSpellChecker sharedSpellChecker] updateSpellingPanelWithGrammarString:badGrammarPhrase detail:grammarDetailDict.get()];
359 }
360
361 void TextChecker::getGuessesForWord(int64_t spellDocumentTag, const String& word, const String& context, Vector<String>& guesses)
362 {
363 #if !defined(BUILDING_ON_SNOW_LEOPARD)
364     NSString* language = nil;
365     NSOrthography* orthography = nil;
366     NSSpellChecker *checker = [NSSpellChecker sharedSpellChecker];
367     if (context.length()) {
368         [checker checkString:context range:NSMakeRange(0, context.length()) types:NSTextCheckingTypeOrthography options:0 inSpellDocumentWithTag:spellDocumentTag orthography:&orthography wordCount:0];
369         language = [checker languageForWordRange:NSMakeRange(0, context.length()) inString:context orthography:orthography];
370     }
371     NSArray* stringsArray = [checker guessesForWordRange:NSMakeRange(0, word.length()) inString:word language:language inSpellDocumentWithTag:spellDocumentTag];
372 #else
373     NSArray* stringsArray = [[NSSpellChecker sharedSpellChecker] guessesForWord:word];
374 #endif
375
376     for (NSString *guess in stringsArray)
377         guesses.append(guess);
378 }
379
380 void TextChecker::learnWord(int64_t, const String& word)
381 {
382     [[NSSpellChecker sharedSpellChecker] learnWord:word];
383 }
384
385 void TextChecker::ignoreWord(int64_t spellDocumentTag, const String& word)
386 {
387     [[NSSpellChecker sharedSpellChecker] ignoreWord:word inSpellDocumentWithTag:spellDocumentTag];
388 }
389
390 } // namespace WebKit