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