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