| OLD | NEW |
| (Empty) |
| 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | |
| 2 // Use of this source code is governed by a BSD-style license that can be | |
| 3 // found in the LICENSE file. | |
| 4 | |
| 5 // Integration with OS X built-in spellchecker. | |
| 6 | |
| 7 #include "chrome/browser/spellchecker/spellcheck_platform.h" | |
| 8 | |
| 9 #import <Cocoa/Cocoa.h> | |
| 10 | |
| 11 #include "base/bind.h" | |
| 12 #include "base/bind_helpers.h" | |
| 13 #include "base/logging.h" | |
| 14 #include "base/mac/foundation_util.h" | |
| 15 #include "base/strings/sys_string_conversions.h" | |
| 16 #include "base/time/time.h" | |
| 17 #include "chrome/common/spellcheck_common.h" | |
| 18 #include "chrome/common/spellcheck_result.h" | |
| 19 #include "content/public/browser/browser_message_filter.h" | |
| 20 #include "content/public/browser/browser_thread.h" | |
| 21 | |
| 22 using base::TimeTicks; | |
| 23 using content::BrowserMessageFilter; | |
| 24 using content::BrowserThread; | |
| 25 | |
| 26 namespace { | |
| 27 // The number of characters in the first part of the language code. | |
| 28 const unsigned int kShortLanguageCodeSize = 2; | |
| 29 | |
| 30 // +[NSSpellChecker sharedSpellChecker] can throw exceptions depending | |
| 31 // on the state of the pasteboard, or possibly as a result of | |
| 32 // third-party code (when setting up services entries). The following | |
| 33 // receives nil if an exception is thrown, in which case | |
| 34 // spell-checking will not work, but it also will not crash the | |
| 35 // browser. | |
| 36 NSSpellChecker* SharedSpellChecker() { | |
| 37 @try { | |
| 38 return [NSSpellChecker sharedSpellChecker]; | |
| 39 } @catch (id exception) { | |
| 40 return nil; | |
| 41 } | |
| 42 } | |
| 43 | |
| 44 // A private utility function to convert hunspell language codes to OS X | |
| 45 // language codes. | |
| 46 NSString* ConvertLanguageCodeToMac(const std::string& hunspell_lang_code) { | |
| 47 NSString* whole_code = base::SysUTF8ToNSString(hunspell_lang_code); | |
| 48 | |
| 49 if ([whole_code length] > kShortLanguageCodeSize) { | |
| 50 NSString* lang_code = [whole_code | |
| 51 substringToIndex:kShortLanguageCodeSize]; | |
| 52 // Add 1 here to skip the underscore. | |
| 53 NSString* region_code = [whole_code | |
| 54 substringFromIndex:(kShortLanguageCodeSize + 1)]; | |
| 55 | |
| 56 // Check for the special case of en-US and pt-PT, since OS X lists these | |
| 57 // as just en and pt respectively. | |
| 58 // TODO(pwicks): Find out if there are other special cases for languages | |
| 59 // not installed on the system by default. Are there others like pt-PT? | |
| 60 if (([lang_code isEqualToString:@"en"] && | |
| 61 [region_code isEqualToString:@"US"]) || | |
| 62 ([lang_code isEqualToString:@"pt"] && | |
| 63 [region_code isEqualToString:@"PT"])) { | |
| 64 return lang_code; | |
| 65 } | |
| 66 | |
| 67 // Otherwise, just build a string that uses an underscore instead of a | |
| 68 // dash between the language and the region code, since this is the | |
| 69 // format that OS X uses. | |
| 70 NSString* os_x_language = | |
| 71 [NSString stringWithFormat:@"%@_%@", lang_code, region_code]; | |
| 72 return os_x_language; | |
| 73 } else { | |
| 74 // Special case for Polish. | |
| 75 if ([whole_code isEqualToString:@"pl"]) { | |
| 76 return @"pl_PL"; | |
| 77 } | |
| 78 // This is just a language code with the same format as OS X | |
| 79 // language code. | |
| 80 return whole_code; | |
| 81 } | |
| 82 } | |
| 83 | |
| 84 std::string ConvertLanguageCodeFromMac(NSString* lang_code) { | |
| 85 // TODO(pwicks):figure out what to do about Multilingual | |
| 86 // Guards for strange cases. | |
| 87 if ([lang_code isEqualToString:@"en"]) return std::string("en-US"); | |
| 88 if ([lang_code isEqualToString:@"pt"]) return std::string("pt-PT"); | |
| 89 if ([lang_code isEqualToString:@"pl_PL"]) return std::string("pl"); | |
| 90 | |
| 91 if ([lang_code length] > kShortLanguageCodeSize && | |
| 92 [lang_code characterAtIndex:kShortLanguageCodeSize] == '_') { | |
| 93 return base::SysNSStringToUTF8([NSString stringWithFormat:@"%@-%@", | |
| 94 [lang_code substringToIndex:kShortLanguageCodeSize], | |
| 95 [lang_code substringFromIndex:(kShortLanguageCodeSize + 1)]]); | |
| 96 } | |
| 97 return base::SysNSStringToUTF8(lang_code); | |
| 98 } | |
| 99 | |
| 100 } // namespace | |
| 101 | |
| 102 namespace spellcheck_platform { | |
| 103 | |
| 104 void GetAvailableLanguages(std::vector<std::string>* spellcheck_languages) { | |
| 105 NSArray* availableLanguages = [SharedSpellChecker() availableLanguages]; | |
| 106 for (NSString* lang_code in availableLanguages) { | |
| 107 spellcheck_languages->push_back( | |
| 108 ConvertLanguageCodeFromMac(lang_code)); | |
| 109 } | |
| 110 } | |
| 111 | |
| 112 std::string GetSpellCheckerLanguage() { | |
| 113 return ConvertLanguageCodeFromMac([SharedSpellChecker() language]); | |
| 114 } | |
| 115 | |
| 116 bool SpellCheckerAvailable() { | |
| 117 // If this file was compiled, then we know that we are on OS X 10.5 at least | |
| 118 // and can safely return true here. | |
| 119 return true; | |
| 120 } | |
| 121 | |
| 122 bool SpellCheckerProvidesPanel() { | |
| 123 // OS X has a Spelling Panel, so we can return true here. | |
| 124 return true; | |
| 125 } | |
| 126 | |
| 127 bool SpellingPanelVisible() { | |
| 128 // This should only be called from the main thread. | |
| 129 DCHECK([NSThread currentThread] == [NSThread mainThread]); | |
| 130 return [[SharedSpellChecker() spellingPanel] isVisible]; | |
| 131 } | |
| 132 | |
| 133 void ShowSpellingPanel(bool show) { | |
| 134 if (show) { | |
| 135 [[SharedSpellChecker() spellingPanel] | |
| 136 performSelectorOnMainThread:@selector(makeKeyAndOrderFront:) | |
| 137 withObject:nil | |
| 138 waitUntilDone:YES]; | |
| 139 } else { | |
| 140 [[SharedSpellChecker() spellingPanel] | |
| 141 performSelectorOnMainThread:@selector(close) | |
| 142 withObject:nil | |
| 143 waitUntilDone:YES]; | |
| 144 } | |
| 145 } | |
| 146 | |
| 147 void UpdateSpellingPanelWithMisspelledWord(const base::string16& word) { | |
| 148 NSString * word_to_display = base::SysUTF16ToNSString(word); | |
| 149 [SharedSpellChecker() | |
| 150 performSelectorOnMainThread: | |
| 151 @selector(updateSpellingPanelWithMisspelledWord:) | |
| 152 withObject:word_to_display | |
| 153 waitUntilDone:YES]; | |
| 154 } | |
| 155 | |
| 156 bool PlatformSupportsLanguage(const std::string& current_language) { | |
| 157 // First, convert the language to an OS X language code. | |
| 158 NSString* mac_lang_code = ConvertLanguageCodeToMac(current_language); | |
| 159 | |
| 160 // Then grab the languages available. | |
| 161 NSArray* availableLanguages = [SharedSpellChecker() availableLanguages]; | |
| 162 | |
| 163 // Return true if the given language is supported by OS X. | |
| 164 return [availableLanguages containsObject:mac_lang_code]; | |
| 165 } | |
| 166 | |
| 167 void SetLanguage(const std::string& lang_to_set) { | |
| 168 // Do not set any language right now, since Chrome should honor the | |
| 169 // system spellcheck settings. (http://crbug.com/166046) | |
| 170 // Fix this once Chrome actually allows setting a spellcheck language | |
| 171 // in chrome://settings. | |
| 172 // NSString* NS_lang_to_set = ConvertLanguageCodeToMac(lang_to_set); | |
| 173 // [SharedSpellChecker() setLanguage:NS_lang_to_set]; | |
| 174 } | |
| 175 | |
| 176 static int last_seen_tag_; | |
| 177 | |
| 178 bool CheckSpelling(const base::string16& word_to_check, int tag) { | |
| 179 last_seen_tag_ = tag; | |
| 180 | |
| 181 // -[NSSpellChecker checkSpellingOfString] returns an NSRange that | |
| 182 // we can look at to determine if a word is misspelled. | |
| 183 NSRange spell_range = {0,0}; | |
| 184 | |
| 185 // Convert the word to an NSString. | |
| 186 NSString* NS_word_to_check = base::SysUTF16ToNSString(word_to_check); | |
| 187 // Check the spelling, starting at the beginning of the word. | |
| 188 spell_range = [SharedSpellChecker() | |
| 189 checkSpellingOfString:NS_word_to_check startingAt:0 | |
| 190 language:nil wrap:NO inSpellDocumentWithTag:tag | |
| 191 wordCount:NULL]; | |
| 192 | |
| 193 // If the length of the misspelled word == 0, | |
| 194 // then there is no misspelled word. | |
| 195 bool word_correct = (spell_range.length == 0); | |
| 196 return word_correct; | |
| 197 } | |
| 198 | |
| 199 void FillSuggestionList(const base::string16& wrong_word, | |
| 200 std::vector<base::string16>* optional_suggestions) { | |
| 201 NSString* ns_wrong_word = base::SysUTF16ToNSString(wrong_word); | |
| 202 NSSpellChecker* checker = SharedSpellChecker(); | |
| 203 NSString* language = [checker language]; | |
| 204 NSArray* guesses = | |
| 205 [checker guessesForWordRange:NSMakeRange(0, [ns_wrong_word length]) | |
| 206 inString:ns_wrong_word | |
| 207 language:language | |
| 208 inSpellDocumentWithTag:last_seen_tag_]; | |
| 209 int i = 0; | |
| 210 for (NSString* guess in guesses) { | |
| 211 optional_suggestions->push_back(base::SysNSStringToUTF16(guess)); | |
| 212 if (++i >= chrome::spellcheck_common::kMaxSuggestions) | |
| 213 break; | |
| 214 } | |
| 215 } | |
| 216 | |
| 217 void AddWord(const base::string16& word) { | |
| 218 NSString* word_to_add = base::SysUTF16ToNSString(word); | |
| 219 [SharedSpellChecker() learnWord:word_to_add]; | |
| 220 } | |
| 221 | |
| 222 void RemoveWord(const base::string16& word) { | |
| 223 NSString *word_to_remove = base::SysUTF16ToNSString(word); | |
| 224 [SharedSpellChecker() unlearnWord:word_to_remove]; | |
| 225 } | |
| 226 | |
| 227 int GetDocumentTag() { | |
| 228 NSInteger doc_tag = [NSSpellChecker uniqueSpellDocumentTag]; | |
| 229 return static_cast<int>(doc_tag); | |
| 230 } | |
| 231 | |
| 232 void IgnoreWord(const base::string16& word) { | |
| 233 [SharedSpellChecker() ignoreWord:base::SysUTF16ToNSString(word) | |
| 234 inSpellDocumentWithTag:last_seen_tag_]; | |
| 235 } | |
| 236 | |
| 237 void CloseDocumentWithTag(int tag) { | |
| 238 [SharedSpellChecker() closeSpellDocumentWithTag:static_cast<NSInteger>(tag)]; | |
| 239 } | |
| 240 | |
| 241 void RequestTextCheck(int document_tag, | |
| 242 const base::string16& text, | |
| 243 TextCheckCompleteCallback callback) { | |
| 244 NSString* text_to_check = base::SysUTF16ToNSString(text); | |
| 245 NSRange range_to_check = NSMakeRange(0, [text_to_check length]); | |
| 246 | |
| 247 [SharedSpellChecker() | |
| 248 requestCheckingOfString:text_to_check | |
| 249 range:range_to_check | |
| 250 types:NSTextCheckingTypeSpelling | |
| 251 options:nil | |
| 252 inSpellDocumentWithTag:document_tag | |
| 253 completionHandler:^(NSInteger, | |
| 254 NSArray *results, | |
| 255 NSOrthography*, | |
| 256 NSInteger) { | |
| 257 std::vector<SpellCheckResult> check_results; | |
| 258 for (NSTextCheckingResult* result in results) { | |
| 259 // Deliberately ignore non-spelling results. OSX at the very least | |
| 260 // delivers a result of NSTextCheckingTypeOrthography for the | |
| 261 // whole fragment, which underlines the entire checked range. | |
| 262 if ([result resultType] != NSTextCheckingTypeSpelling) | |
| 263 continue; | |
| 264 | |
| 265 // In this use case, the spell checker should never | |
| 266 // return anything but a single range per result. | |
| 267 check_results.push_back(SpellCheckResult( | |
| 268 SpellCheckResult::SPELLING, | |
| 269 [result range].location, | |
| 270 [result range].length)); | |
| 271 } | |
| 272 // TODO(groby): Verify we don't need to post from here. | |
| 273 callback.Run(check_results); | |
| 274 }]; | |
| 275 } | |
| 276 | |
| 277 class SpellcheckerStateInternal { | |
| 278 public: | |
| 279 SpellcheckerStateInternal(); | |
| 280 ~SpellcheckerStateInternal(); | |
| 281 | |
| 282 private: | |
| 283 BOOL automaticallyIdentifiesLanguages_; | |
| 284 NSString* language_; | |
| 285 }; | |
| 286 | |
| 287 SpellcheckerStateInternal::SpellcheckerStateInternal() { | |
| 288 language_ = [SharedSpellChecker() language]; | |
| 289 automaticallyIdentifiesLanguages_ = | |
| 290 [SharedSpellChecker() automaticallyIdentifiesLanguages]; | |
| 291 [SharedSpellChecker() setLanguage:@"en"]; | |
| 292 [SharedSpellChecker() setAutomaticallyIdentifiesLanguages:NO]; | |
| 293 } | |
| 294 | |
| 295 SpellcheckerStateInternal::~SpellcheckerStateInternal() { | |
| 296 [SharedSpellChecker() setLanguage:language_]; | |
| 297 [SharedSpellChecker() setAutomaticallyIdentifiesLanguages: | |
| 298 automaticallyIdentifiesLanguages_]; | |
| 299 } | |
| 300 | |
| 301 ScopedEnglishLanguageForTest::ScopedEnglishLanguageForTest() | |
| 302 : state_(new SpellcheckerStateInternal) { | |
| 303 } | |
| 304 | |
| 305 ScopedEnglishLanguageForTest::~ScopedEnglishLanguageForTest() { | |
| 306 delete state_; | |
| 307 } | |
| 308 | |
| 309 } // namespace spellcheck_platform | |
| OLD | NEW |