1 // Copyright (c) 2006-2009 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 // This file implements the interface defined in spellchecker_platform_engine.h 6 // for the OS X platform. 7 8 #include "chrome/browser/spellchecker_platform_engine.h" 9 10 #import <Cocoa/Cocoa.h> 11 12 #include "base/logging.h" 13 #include "base/metrics/histogram.h" 14 #include "base/task.h" 15 #include "base/time.h" 16 #include "base/sys_string_conversions.h" 17 #include "chrome/common/spellcheck_common.h" 18 #include "chrome/common/spellcheck_messages.h" 19 #include "content/browser/browser_thread.h" 20 #include "content/browser/browser_message_filter.h" 21 #include "third_party/WebKit/Source/WebKit/chromium/public/WebTextCheckingResult.h" 22 23 using base::TimeTicks; 24 namespace { 25 // The number of characters in the first part of the language code. 26 const unsigned int kShortLanguageCodeSize = 2; 27 28 // TextCheckingTask is reserved for spell checking against large size 29 // of text, which possible contains multiple paragrpahs. Checking 30 // that size of text might take time, and should be done as a task on 31 // the FILE thread. 32 // 33 // The result of the check is returned back as a 34 // SpellCheckMsg_RespondTextCheck message. 35 class TextCheckingTask : public Task { 36 public: 37 TextCheckingTask(BrowserMessageFilter* destination, 38 int route_id, 39 int identifier, 40 const string16& text, 41 int document_tag) 42 : destination_(destination), 43 route_id_(route_id), 44 identifier_(identifier), 45 text_(text), 46 document_tag_(document_tag) { 47 } 48 49 virtual void Run() { 50 // TODO(morrita): Use [NSSpellChecker requestCheckingOfString] 51 // when the build target goes up to 10.6 52 std::vector<WebKit::WebTextCheckingResult> check_results; 53 NSString* text_to_check = base::SysUTF16ToNSString(text_); 54 size_t starting_at = 0; 55 while (starting_at < text_.size()) { 56 NSRange range = [[NSSpellChecker sharedSpellChecker] 57 checkSpellingOfString:text_to_check 58 startingAt:starting_at 59 language:nil 60 wrap:NO 61 inSpellDocumentWithTag:document_tag_ 62 wordCount:NULL]; 63 if (range.length == 0) 64 break; 65 check_results.push_back(WebKit::WebTextCheckingResult( 66 WebKit::WebTextCheckingResult::ErrorSpelling, 67 range.location, 68 range.length)); 69 starting_at = range.location + range.length; 70 } 71 72 BrowserThread::PostTask( 73 BrowserThread::IO, 74 FROM_HERE, 75 NewRunnableMethod( 76 destination_.get(), 77 &BrowserMessageFilter::Send, 78 new SpellCheckMsg_RespondTextCheck(route_id_, 79 identifier_, 80 document_tag_, 81 check_results))); 82 } 83 84 private: 85 scoped_refptr<BrowserMessageFilter> destination_; 86 int route_id_; 87 int identifier_; 88 string16 text_; 89 int document_tag_; 90 }; 91 92 // A private utility function to convert hunspell language codes to OS X 93 // language codes. 94 NSString* ConvertLanguageCodeToMac(const std::string& hunspell_lang_code) { 95 NSString* whole_code = base::SysUTF8ToNSString(hunspell_lang_code); 96 97 if ([whole_code length] > kShortLanguageCodeSize) { 98 NSString* lang_code = [whole_code 99 substringToIndex:kShortLanguageCodeSize]; 100 // Add 1 here to skip the underscore. 101 NSString* region_code = [whole_code 102 substringFromIndex:(kShortLanguageCodeSize + 1)]; 103 104 // Check for the special case of en-US and pt-PT, since OS X lists these 105 // as just en and pt respectively. 106 // TODO(pwicks): Find out if there are other special cases for languages 107 // not installed on the system by default. Are there others like pt-PT? 108 if (([lang_code isEqualToString:@"en"] && 109 [region_code isEqualToString:@"US"]) || 110 ([lang_code isEqualToString:@"pt"] && 111 [region_code isEqualToString:@"PT"])) { 112 return lang_code; 113 } 114 115 // Otherwise, just build a string that uses an underscore instead of a 116 // dash between the language and the region code, since this is the 117 // format that OS X uses. 118 NSString* os_x_language = 119 [NSString stringWithFormat:@"%@_%@", lang_code, region_code]; 120 return os_x_language; 121 } else { 122 // Special case for Polish. 123 if ([whole_code isEqualToString:@"pl"]) { 124 return @"pl_PL"; 125 } 126 // This is just a language code with the same format as OS X 127 // language code. 128 return whole_code; 129 } 130 } 131 132 std::string ConvertLanguageCodeFromMac(NSString* lang_code) { 133 // TODO(pwicks):figure out what to do about Multilingual 134 // Guards for strange cases. 135 if ([lang_code isEqualToString:@"en"]) return std::string("en-US"); 136 if ([lang_code isEqualToString:@"pt"]) return std::string("pt-PT"); 137 if ([lang_code isEqualToString:@"pl_PL"]) return std::string("pl"); 138 139 if ([lang_code length] > kShortLanguageCodeSize && 140 [lang_code characterAtIndex:kShortLanguageCodeSize] == '_') { 141 return base::SysNSStringToUTF8([NSString stringWithFormat:@"%@-%@", 142 [lang_code substringToIndex:kShortLanguageCodeSize], 143 [lang_code substringFromIndex:(kShortLanguageCodeSize + 1)]]); 144 } 145 return base::SysNSStringToUTF8(lang_code); 146 } 147 148 } // namespace 149 150 namespace SpellCheckerPlatform { 151 152 void GetAvailableLanguages(std::vector<std::string>* spellcheck_languages) { 153 NSArray* availableLanguages = [[NSSpellChecker sharedSpellChecker] 154 availableLanguages]; 155 for (NSString* lang_code in availableLanguages) { 156 spellcheck_languages->push_back( 157 ConvertLanguageCodeFromMac(lang_code)); 158 } 159 } 160 161 bool SpellCheckerAvailable() { 162 // If this file was compiled, then we know that we are on OS X 10.5 at least 163 // and can safely return true here. 164 return true; 165 } 166 167 bool SpellCheckerProvidesPanel() { 168 // OS X has a Spelling Panel, so we can return true here. 169 return true; 170 } 171 172 bool SpellingPanelVisible() { 173 // This should only be called from the main thread. 174 DCHECK([NSThread currentThread] == [NSThread mainThread]); 175 return [[[NSSpellChecker sharedSpellChecker] spellingPanel] isVisible]; 176 } 177 178 void ShowSpellingPanel(bool show) { 179 if (show) { 180 [[[NSSpellChecker sharedSpellChecker] spellingPanel] 181 performSelectorOnMainThread:@selector(makeKeyAndOrderFront:) 182 withObject:nil 183 waitUntilDone:YES]; 184 } else { 185 [[[NSSpellChecker sharedSpellChecker] spellingPanel] 186 performSelectorOnMainThread:@selector(close) 187 withObject:nil 188 waitUntilDone:YES]; 189 } 190 } 191 192 void UpdateSpellingPanelWithMisspelledWord(const string16& word) { 193 NSString * word_to_display = base::SysUTF16ToNSString(word); 194 [[NSSpellChecker sharedSpellChecker] 195 performSelectorOnMainThread: 196 @selector(updateSpellingPanelWithMisspelledWord:) 197 withObject:word_to_display 198 waitUntilDone:YES]; 199 } 200 201 void Init() { 202 } 203 204 bool PlatformSupportsLanguage(const std::string& current_language) { 205 // First, convert the language to an OS X language code. 206 NSString* mac_lang_code = ConvertLanguageCodeToMac(current_language); 207 208 // Then grab the languages available. 209 NSArray* availableLanguages; 210 availableLanguages = [[NSSpellChecker sharedSpellChecker] 211 availableLanguages]; 212 213 // Return true if the given language is supported by OS X. 214 return [availableLanguages containsObject:mac_lang_code]; 215 } 216 217 void SetLanguage(const std::string& lang_to_set) { 218 NSString* NS_lang_to_set = ConvertLanguageCodeToMac(lang_to_set); 219 [[NSSpellChecker sharedSpellChecker] setLanguage:NS_lang_to_set]; 220 } 221 222 static int last_seen_tag_; 223 224 bool CheckSpelling(const string16& word_to_check, int tag) { 225 last_seen_tag_ = tag; 226 227 // [[NSSpellChecker sharedSpellChecker] checkSpellingOfString] returns an 228 // NSRange that we can look at to determine if a word is misspelled. 229 NSRange spell_range = {0,0}; 230 231 // Convert the word to an NSString. 232 NSString* NS_word_to_check = base::SysUTF16ToNSString(word_to_check); 233 // Check the spelling, starting at the beginning of the word. 234 spell_range = [[NSSpellChecker sharedSpellChecker] 235 checkSpellingOfString:NS_word_to_check startingAt:0 236 language:nil wrap:NO inSpellDocumentWithTag:tag 237 wordCount:NULL]; 238 239 // If the length of the misspelled word == 0, 240 // then there is no misspelled word. 241 bool word_correct = (spell_range.length == 0); 242 return word_correct; 243 } 244 245 void FillSuggestionList(const string16& wrong_word, 246 std::vector<string16>* optional_suggestions) { 247 NSString* NS_wrong_word = base::SysUTF16ToNSString(wrong_word); 248 TimeTicks begin_time = TimeTicks::Now(); 249 // The suggested words for |wrong_word|. 250 NSArray* guesses = 251 [[NSSpellChecker sharedSpellChecker] guessesForWord:NS_wrong_word]; 252 DHISTOGRAM_TIMES("Spellcheck.SuggestTime", 253 TimeTicks::Now() - begin_time); 254 255 for (int i = 0; i < static_cast<int>([guesses count]); i++) { 256 if (i < SpellCheckCommon::kMaxSuggestions) { 257 optional_suggestions->push_back(base::SysNSStringToUTF16( 258 [guesses objectAtIndex:i])); 259 } 260 } 261 } 262 263 void AddWord(const string16& word) { 264 NSString* word_to_add = base::SysUTF16ToNSString(word); 265 [[NSSpellChecker sharedSpellChecker] learnWord:word_to_add]; 266 } 267 268 void RemoveWord(const string16& word) { 269 NSString *word_to_remove = base::SysUTF16ToNSString(word); 270 [[NSSpellChecker sharedSpellChecker] unlearnWord:word_to_remove]; 271 } 272 273 int GetDocumentTag() { 274 NSInteger doc_tag = [NSSpellChecker uniqueSpellDocumentTag]; 275 return static_cast<int>(doc_tag); 276 } 277 278 void IgnoreWord(const string16& word) { 279 [[NSSpellChecker sharedSpellChecker] ignoreWord:base::SysUTF16ToNSString(word) 280 inSpellDocumentWithTag:last_seen_tag_]; 281 } 282 283 void CloseDocumentWithTag(int tag) { 284 [[NSSpellChecker sharedSpellChecker] 285 closeSpellDocumentWithTag:static_cast<NSInteger>(tag)]; 286 } 287 288 void RequestTextCheck(int route_id, 289 int identifier, 290 int document_tag, 291 const string16& text, BrowserMessageFilter* destination) { 292 BrowserThread::PostTask( 293 BrowserThread::FILE, FROM_HERE, 294 new TextCheckingTask( 295 destination, route_id, identifier, text, document_tag)); 296 } 297 298 } // namespace SpellCheckerPlatform 299