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