Revert "Reland c91b178b07b0d - Delete dead signin code (SigninGlobalError)"
[chromium-blink-merge.git] / chrome / browser / spellchecker / spellcheck_platform_mac.mm
blob094b638d1ddf32aa13f4fd5d2667267dd3851ef8
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.
5 // Integration with OS X built-in spellchecker.
7 #include "chrome/browser/spellchecker/spellcheck_platform.h"
9 #import <Cocoa/Cocoa.h>
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"
22 using base::TimeTicks;
23 using content::BrowserMessageFilter;
24 using content::BrowserThread;
26 namespace {
27 // The number of characters in the first part of the language code.
28 const unsigned int kShortLanguageCodeSize = 2;
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   }
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);
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)];
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     }
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   }
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");
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);
100 } // namespace
102 namespace spellcheck_platform {
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   }
112 bool SpellCheckerAvailable() {
113   // If this file was compiled, then we know that we are on OS X 10.5 at least
114   // and can safely return true here.
115   return true;
118 bool SpellCheckerProvidesPanel() {
119   // OS X has a Spelling Panel, so we can return true here.
120   return true;
123 bool SpellingPanelVisible() {
124   // This should only be called from the main thread.
125   DCHECK([NSThread currentThread] == [NSThread mainThread]);
126   return [[SharedSpellChecker() spellingPanel] isVisible];
129 void ShowSpellingPanel(bool show) {
130   if (show) {
131     [[SharedSpellChecker() spellingPanel]
132         performSelectorOnMainThread:@selector(makeKeyAndOrderFront:)
133                          withObject:nil
134                       waitUntilDone:YES];
135   } else {
136     [[SharedSpellChecker() spellingPanel]
137         performSelectorOnMainThread:@selector(close)
138                          withObject:nil
139                       waitUntilDone:YES];
140   }
143 void UpdateSpellingPanelWithMisspelledWord(const base::string16& word) {
144   NSString * word_to_display = base::SysUTF16ToNSString(word);
145   [SharedSpellChecker()
146       performSelectorOnMainThread:
147         @selector(updateSpellingPanelWithMisspelledWord:)
148                        withObject:word_to_display
149                     waitUntilDone:YES];
152 bool PlatformSupportsLanguage(const std::string& current_language) {
153   // First, convert the language to an OS X language code.
154   NSString* mac_lang_code = ConvertLanguageCodeToMac(current_language);
156   // Then grab the languages available.
157   NSArray* availableLanguages = [SharedSpellChecker() availableLanguages];
159   // Return true if the given language is supported by OS X.
160   return [availableLanguages containsObject:mac_lang_code];
163 void SetLanguage(const std::string& lang_to_set) {
164   // Do not set any language right now, since Chrome should honor the
165   // system spellcheck settings. (http://crbug.com/166046)
166   // Fix this once Chrome actually allows setting a spellcheck language
167   // in chrome://settings.
168   //  NSString* NS_lang_to_set = ConvertLanguageCodeToMac(lang_to_set);
169   //  [SharedSpellChecker() setLanguage:NS_lang_to_set];
172 static int last_seen_tag_;
174 bool CheckSpelling(const base::string16& word_to_check, int tag) {
175   last_seen_tag_ = tag;
177   // -[NSSpellChecker checkSpellingOfString] returns an NSRange that
178   // we can look at to determine if a word is misspelled.
179   NSRange spell_range = {0,0};
181   // Convert the word to an NSString.
182   NSString* NS_word_to_check = base::SysUTF16ToNSString(word_to_check);
183   // Check the spelling, starting at the beginning of the word.
184   spell_range = [SharedSpellChecker()
185                   checkSpellingOfString:NS_word_to_check startingAt:0
186                   language:nil wrap:NO inSpellDocumentWithTag:tag
187                   wordCount:NULL];
189   // If the length of the misspelled word == 0,
190   // then there is no misspelled word.
191   bool word_correct = (spell_range.length == 0);
192   return word_correct;
195 void FillSuggestionList(const base::string16& wrong_word,
196                         std::vector<base::string16>* optional_suggestions) {
197   NSString* ns_wrong_word = base::SysUTF16ToNSString(wrong_word);
198   NSSpellChecker* checker = SharedSpellChecker();
199   NSString* language = [checker language];
200   NSArray* guesses =
201       [checker guessesForWordRange:NSMakeRange(0, [ns_wrong_word length])
202                           inString:ns_wrong_word
203                           language:language
204             inSpellDocumentWithTag:last_seen_tag_];
205   int i = 0;
206   for (NSString* guess in guesses) {
207     optional_suggestions->push_back(base::SysNSStringToUTF16(guess));
208     if (++i >= chrome::spellcheck_common::kMaxSuggestions)
209       break;
210   }
213 void AddWord(const base::string16& word) {
214     NSString* word_to_add = base::SysUTF16ToNSString(word);
215   [SharedSpellChecker() learnWord:word_to_add];
218 void RemoveWord(const base::string16& word) {
219   NSString *word_to_remove = base::SysUTF16ToNSString(word);
220   [SharedSpellChecker() unlearnWord:word_to_remove];
223 int GetDocumentTag() {
224   NSInteger doc_tag = [NSSpellChecker uniqueSpellDocumentTag];
225   return static_cast<int>(doc_tag);
228 void IgnoreWord(const base::string16& word) {
229   [SharedSpellChecker() ignoreWord:base::SysUTF16ToNSString(word)
230             inSpellDocumentWithTag:last_seen_tag_];
233 void CloseDocumentWithTag(int tag) {
234   [SharedSpellChecker() closeSpellDocumentWithTag:static_cast<NSInteger>(tag)];
237 void RequestTextCheck(int document_tag,
238                       const base::string16& text,
239                       TextCheckCompleteCallback callback) {
240   NSString* text_to_check = base::SysUTF16ToNSString(text);
241   NSRange range_to_check = NSMakeRange(0, [text_to_check length]);
243   [SharedSpellChecker()
244       requestCheckingOfString:text_to_check
245                         range:range_to_check
246                         types:NSTextCheckingTypeSpelling
247                       options:nil
248        inSpellDocumentWithTag:document_tag
249             completionHandler:^(NSInteger,
250                                 NSArray *results,
251                                 NSOrthography*,
252                                 NSInteger) {
253           std::vector<SpellCheckResult> check_results;
254           for (NSTextCheckingResult* result in results) {
255             // Deliberately ignore non-spelling results. OSX at the very least
256             // delivers a result of NSTextCheckingTypeOrthography for the
257             // whole fragment, which underlines the entire checked range.
258             if ([result resultType] != NSTextCheckingTypeSpelling)
259               continue;
261             // In this use case, the spell checker should never
262             // return anything but a single range per result.
263             check_results.push_back(SpellCheckResult(
264                 SpellCheckResult::SPELLING,
265                 [result range].location,
266                 [result range].length));
267           }
268           // TODO(groby): Verify we don't need to post from here.
269           callback.Run(check_results);
270       }];
273 class SpellcheckerStateInternal {
274  public:
275   SpellcheckerStateInternal();
276   ~SpellcheckerStateInternal();
278  private:
279   BOOL automaticallyIdentifiesLanguages_;
280   NSString* language_;
283 SpellcheckerStateInternal::SpellcheckerStateInternal() {
284   language_ = [SharedSpellChecker() language];
285   automaticallyIdentifiesLanguages_ =
286       [SharedSpellChecker() automaticallyIdentifiesLanguages];
287   [SharedSpellChecker() setLanguage:@"en"];
288   [SharedSpellChecker() setAutomaticallyIdentifiesLanguages:NO];
291 SpellcheckerStateInternal::~SpellcheckerStateInternal() {
292   [SharedSpellChecker() setLanguage:language_];
293   [SharedSpellChecker() setAutomaticallyIdentifiesLanguages:
294       automaticallyIdentifiesLanguages_];
297 ScopedEnglishLanguageForTest::ScopedEnglishLanguageForTest()
298     : state_(new SpellcheckerStateInternal) {
301 ScopedEnglishLanguageForTest::~ScopedEnglishLanguageForTest() {
302   delete state_;
305 }  // namespace spellcheck_platform