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