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