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