Home | History | Annotate | Download | only in mac
      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 
     39 static NSString* const WebAutomaticSpellingCorrectionEnabled = @"WebAutomaticSpellingCorrectionEnabled";
     40 static NSString* const WebContinuousSpellCheckingEnabled = @"WebContinuousSpellCheckingEnabled";
     41 static NSString* const WebGrammarCheckingEnabled = @"WebGrammarCheckingEnabled";
     42 static NSString* const WebSmartInsertDeleteEnabled = @"WebSmartInsertDeleteEnabled";
     43 static NSString* const WebAutomaticQuoteSubstitutionEnabled = @"WebAutomaticQuoteSubstitutionEnabled";
     44 static NSString* const WebAutomaticDashSubstitutionEnabled = @"WebAutomaticDashSubstitutionEnabled";
     45 static NSString* const WebAutomaticLinkDetectionEnabled = @"WebAutomaticLinkDetectionEnabled";
     46 static NSString* const WebAutomaticTextReplacementEnabled = @"WebAutomaticTextReplacementEnabled";
     47 
     48 using namespace WebCore;
     49 
     50 namespace WebKit {
     51 
     52 TextCheckerState textCheckerState;
     53 
     54 static 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 
     76 const TextCheckerState& TextChecker::state()
     77 {
     78     initializeState();
     79     return textCheckerState;
     80 }
     81 
     82 bool 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 
     97 void 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 
    108 void 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 
    121 void 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 
    132 void 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 
    143 void 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 
    154 void 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 
    165 void 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 
    176 static bool smartInsertDeleteEnabled;
    177 
    178 bool 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 
    191 void 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 
    201 bool TextChecker::substitutionsPanelIsShowing()
    202 {
    203     return [[[NSSpellChecker sharedSpellChecker] substitutionsPanel] isVisible];
    204 }
    205 
    206 void 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 
    216 int64_t TextChecker::uniqueSpellDocumentTag(WebPageProxy*)
    217 {
    218     return [NSSpellChecker uniqueSpellDocumentTag];
    219 }
    220 
    221 void TextChecker::closeSpellDocumentWithTag(int64_t tag)
    222 {
    223     [[NSSpellChecker sharedSpellChecker] closeSpellDocumentWithTag:tag];
    224 }
    225 
    226 #if USE(UNIFIED_TEXT_CHECKING)
    227 
    228 Vector<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 
    317 void TextChecker::checkSpellingOfString(int64_t, const UChar*, uint32_t, int32_t&, int32_t&)
    318 {
    319     // Mac uses checkTextOfParagraph instead.
    320     notImplemented();
    321 }
    322 
    323 void 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 
    329 bool TextChecker::spellingUIIsShowing()
    330 {
    331     return [[[NSSpellChecker sharedSpellChecker] spellingPanel] isVisible];
    332 }
    333 
    334 void 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 
    343 void TextChecker::updateSpellingUIWithMisspelledWord(int64_t, const String& misspelledWord)
    344 {
    345     [[NSSpellChecker sharedSpellChecker] updateSpellingPanelWithMisspelledWord:misspelledWord];
    346 }
    347 
    348 void 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 
    363 void 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 
    382 void TextChecker::learnWord(int64_t, const String& word)
    383 {
    384     [[NSSpellChecker sharedSpellChecker] learnWord:word];
    385 }
    386 
    387 void TextChecker::ignoreWord(int64_t spellDocumentTag, const String& word)
    388 {
    389     [[NSSpellChecker sharedSpellChecker] ignoreWord:word inSpellDocumentWithTag:spellDocumentTag];
    390 }
    391 
    392 } // namespace WebKit
    393