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 #include "chrome/browser/chromeos/input_method/input_method_util.h"
6
7 #include <algorithm>
8 #include <functional>
9 #include <map>
10 #include <utility>
11
12 #include "base/basictypes.h"
13 #include "base/memory/scoped_ptr.h"
14 #include "base/prefs/pref_service.h"
15 #include "base/strings/string_split.h"
16 #include "base/strings/string_util.h"
17 #include "base/strings/utf_string_conversions.h"
18 #include "chromeos/ime/component_extension_ime_manager.h"
19 #include "chromeos/ime/extension_ime_util.h"
20 #include "chromeos/ime/input_method_delegate.h"
21 // TODO(nona): move this header from this file.
22 #include "grit/generated_resources.h"
23
24 namespace {
25
26 // A mapping from an input method id to a string for the language indicator. The
27 // mapping is necessary since some input methods belong to the same language.
28 // For example, both "xkb:us::eng" and "xkb:us:dvorak:eng" are for US English.
29 const struct {
30 const char* input_method_id;
31 const char* indicator_text;
32 } kMappingFromIdToIndicatorText[] = {
33 // To distinguish from "xkb:us::eng"
34 { "xkb:us:altgr-intl:eng", "EXTD" },
35 { "xkb:us:dvorak:eng", "DV" },
36 { "xkb:us:intl:eng", "INTL" },
37 { "xkb:us:colemak:eng", "CO" },
38 { "english-m", "??" },
39 { "xkb:de:neo:ger", "NEO" },
40 // To distinguish from "xkb:es::spa"
41 { "xkb:es:cat:cat", "CAS" },
42 // To distinguish from "xkb:gb::eng"
43 { "xkb:gb:dvorak:eng", "DV" },
44 // To distinguish from "xkb:jp::jpn"
45 // TODO(nona): Make following variables configurable. http://crbug.com/232260.
46 { "_comp_ime_fpfbhcjppmaeaijcidgiibchfbnhbeljnacl_mozc_us", "\xe3\x81\x82" },
47 { "_comp_ime_fpfbhcjppmaeaijcidgiibchfbnhbeljnacl_mozc_jp", "\xe3\x81\x82" },
48 { "_comp_ime_bbaiamgfapehflhememkfglaehiobjnknacl_mozc_us", "\xe3\x81\x82" },
49 { "_comp_ime_bbaiamgfapehflhememkfglaehiobjnknacl_mozc_jp", "\xe3\x81\x82" },
50 // For simplified Chinese input methods
51 { "pinyin", "\xe6\x8b\xbc" }, // U+62FC
52 { "_comp_ime_cpgalbafkoofkjmaeonnfijgpfennjjnzh-t-i0-pinyin",
53 "\xe6\x8b\xbc" },
54 { "_comp_ime_nmblnjkfdkabgdofidlkienfnnbjhnabzh-t-i0-pinyin",
55 "\xe6\x8b\xbc" },
56 { "_comp_ime_gjhclobljhjhgoebiipblnmdodbmpdgdzh-t-i0-wubi-1986",
57 "\xe4\xba\x94" }, // U+4E94
58 { "pinyin-dv", "\xe6\x8b\xbc" },
59 // For traditional Chinese input methods
60 { "mozc-chewing", "\xe9\x85\xb7" }, // U+9177
61 { "_comp_ime_ekbifjdfhkmdeeajnolmgdlmkllopefizh-hant-t-i0-und",
62 "\xE6\xB3\xA8" }, // U+6CE8
63 { "_comp_ime_goedamlknlnjaengojinmfgpmdjmkooozh-hant-t-i0-und",
64 "\xE6\xB3\xA8" }, // U+6CE8
65 { "m17n:zh:cangjie", "\xe5\x80\x89" }, // U+5009
66 { "_comp_ime_aeebooiibjahgpgmhkeocbeekccfknbjzh-hant-t-i0-cangjie-1987",
67 "\xe5\x80\x89" }, // U+5009
68 { "_comp_ime_gjhclobljhjhgoebiipblnmdodbmpdgdzh-hant-t-i0-cangjie-1987",
69 "\xe5\x80\x89" }, // U+5009
70 { "m17n:zh:quick", "\xe9\x80\x9f" }, // U+901F
71 // For Hangul input method.
72 { "mozc-hangul", "\xed\x95\x9c" }, // U+D55C
73 { "_comp_ime_bdgdidmhaijohebebipajioienkglgfohangul_2set", "\xed\x95\x9c" },
74 { "_comp_ime_bdgdidmhaijohebebipajioienkglgfohangul_3set390",
75 "\xed\x95\x9c" },
76 { "_comp_ime_bdgdidmhaijohebebipajioienkglgfohangul_3setfinal",
77 "\xed\x95\x9c" },
78 { "_comp_ime_bdgdidmhaijohebebipajioienkglgfohangul_3setnoshift",
79 "\xed\x95\x9c" },
80 { "_comp_ime_bdgdidmhaijohebebipajioienkglgfohangul_romaja", "\xed\x95\x9c" },
81 };
82
83 const size_t kMappingFromIdToIndicatorTextLen =
84 ARRAYSIZE_UNSAFE(kMappingFromIdToIndicatorText);
85
86 // A mapping from an input method id to a resource id for a
87 // medium length language indicator.
88 // For those languages that want to display a slightly longer text in the
89 // "Your input method has changed to..." bubble than in the status tray.
90 // If an entry is not found in this table the short name is used.
91 const struct {
92 const char* input_method_id;
93 const int resource_id;
94 } kMappingImeIdToMediumLenNameResourceId[] = {
95 { "m17n:zh:cangjie", IDS_LANGUAGES_MEDIUM_LEN_NAME_CHINESE_TRADITIONAL },
96 { "m17n:zh:quick", IDS_LANGUAGES_MEDIUM_LEN_NAME_CHINESE_TRADITIONAL },
97 { "mozc-chewing", IDS_LANGUAGES_MEDIUM_LEN_NAME_CHINESE_TRADITIONAL },
98 { "mozc-hangul", IDS_LANGUAGES_MEDIUM_LEN_NAME_KOREAN },
99 { "pinyin", IDS_LANGUAGES_MEDIUM_LEN_NAME_CHINESE_SIMPLIFIED },
100 { "pinyin-dv", IDS_LANGUAGES_MEDIUM_LEN_NAME_CHINESE_SIMPLIFIED },
101 { "_comp_ime_cpgalbafkoofkjmaeonnfijgpfennjjnzh-t-i0-pinyin",
102 IDS_LANGUAGES_MEDIUM_LEN_NAME_CHINESE_SIMPLIFIED},
103 { "_comp_ime_nmblnjkfdkabgdofidlkienfnnbjhnabzh-t-i0-pinyin",
104 IDS_LANGUAGES_MEDIUM_LEN_NAME_CHINESE_SIMPLIFIED },
105 { "_comp_ime_gjhclobljhjhgoebiipblnmdodbmpdgdzh-t-i0-wubi-1986",
106 IDS_LANGUAGES_MEDIUM_LEN_NAME_CHINESE_SIMPLIFIED },
107 { "_comp_ime_ekbifjdfhkmdeeajnolmgdlmkllopefizh-hant-t-i0-und",
108 IDS_LANGUAGES_MEDIUM_LEN_NAME_CHINESE_TRADITIONAL },
109 { "_comp_ime_goedamlknlnjaengojinmfgpmdjmkooozh-hant-t-i0-und",
110 IDS_LANGUAGES_MEDIUM_LEN_NAME_CHINESE_TRADITIONAL },
111 { "_comp_ime_aeebooiibjahgpgmhkeocbeekccfknbjzh-hant-t-i0-cangjie-1987",
112 IDS_LANGUAGES_MEDIUM_LEN_NAME_CHINESE_TRADITIONAL },
113 { "_comp_ime_gjhclobljhjhgoebiipblnmdodbmpdgdzh-hant-t-i0-cangjie-1987",
114 IDS_LANGUAGES_MEDIUM_LEN_NAME_CHINESE_TRADITIONAL },
115 };
116 const size_t kMappingImeIdToMediumLenNameResourceIdLen =
117 ARRAYSIZE_UNSAFE(kMappingImeIdToMediumLenNameResourceId);
118
119 // Due to asynchronous initialization of component extension manager,
120 // GetFirstLogingInputMethodIds may miss component extension IMEs. To enable
121 // component extension IME as the first loging input method, we have to prepare
122 // component extension IME IDs.
123 const struct {
124 const char* locale;
125 const char* layout;
126 const char* input_method_id;
127 } kDefaultInputMethodRecommendation[] = {
128 { "ja", "us", "_comp_ime_fpfbhcjppmaeaijcidgiibchfbnhbeljnacl_mozc_us" },
129 { "ja", "jp", "_comp_ime_fpfbhcjppmaeaijcidgiibchfbnhbeljnacl_mozc_jp" },
130 { "zh-CN", "us", "_comp_ime_nmblnjkfdkabgdofidlkienfnnbjhnabzh-t-i0-pinyin" },
131 { "zh-TW", "us",
132 "_comp_ime_goedamlknlnjaengojinmfgpmdjmkooozh-hant-t-i0-und" },
133 { "th", "us", "_comp_ime_jhffeifommiaekmbkkjlpmilogcfdohpvkd_th" },
134 { "vi", "us", "_comp_ime_jhffeifommiaekmbkkjlpmilogcfdohpvkd_vi_tcvn" },
135 { "vi", "us", "_comp_ime_jhffeifommiaekmbkkjlpmilogcfdohpvkd_vi_tcvn" },
136 };
137
138 } // namespace
139
140 namespace chromeos {
141
142 extern const char* kExtensionImePrefix;
143
144 namespace input_method {
145
146 namespace {
147
148 const struct EnglishToResouceId {
149 const char* english_string_from_ibus;
150 int resource_id;
151 } kEnglishToResourceIdArray[] = {
152 // For ibus-mozc-hangul
153 { "Hanja mode", IDS_STATUSBAR_IME_KOREAN_HANJA_INPUT_MODE },
154 { "Hangul mode", IDS_STATUSBAR_IME_KOREAN_HANGUL_INPUT_MODE },
155
156 // For ibus-mozc-pinyin.
157 { "Full/Half width",
158 IDS_STATUSBAR_IME_CHINESE_PINYIN_TOGGLE_FULL_HALF },
159 { "Full/Half width punctuation",
160 IDS_STATUSBAR_IME_CHINESE_PINYIN_TOGGLE_FULL_HALF_PUNCTUATION },
161 // TODO(hsumita): Fixes a typo
162 { "Simplfied/Traditional Chinese",
163 IDS_STATUSBAR_IME_CHINESE_PINYIN_TOGGLE_S_T_CHINESE },
164 { "Chinese",
165 IDS_STATUSBAR_IME_CHINESE_PINYIN_TOGGLE_CHINESE_ENGLISH },
166
167 // For ibus-mozc-chewing.
168 { "English",
169 IDS_STATUSBAR_IME_CHINESE_MOZC_CHEWING_ENGLISH_MODE },
170 { "_Chinese",
171 IDS_STATUSBAR_IME_CHINESE_MOZC_CHEWING_CHINESE_MODE },
172 { "Full-width English",
173 IDS_STATUSBAR_IME_CHINESE_MOZC_CHEWING_FULL_WIDTH_ENGLISH_MODE },
174
175 // For the "Languages and Input" dialog.
176 { "m17n:ar:kbd", IDS_OPTIONS_SETTINGS_LANGUAGES_M17N_STANDARD_INPUT_METHOD },
177 { "m17n:hi:itrans", // also uses the "STANDARD_INPUT_METHOD" id.
178 IDS_OPTIONS_SETTINGS_LANGUAGES_M17N_STANDARD_INPUT_METHOD },
179 { "m17n:zh:cangjie",
180 IDS_OPTIONS_SETTINGS_LANGUAGES_M17N_CHINESE_CANGJIE_INPUT_METHOD },
181 { "m17n:zh:quick",
182 IDS_OPTIONS_SETTINGS_LANGUAGES_M17N_CHINESE_QUICK_INPUT_METHOD },
183 { "m17n:fa:isiri",
184 IDS_OPTIONS_SETTINGS_LANGUAGES_M17N_PERSIAN_ISIRI_2901_INPUT_METHOD },
185 { "m17n:th:kesmanee",
186 IDS_OPTIONS_SETTINGS_LANGUAGES_M17N_THAI_KESMANEE_INPUT_METHOD },
187 { "m17n:th:tis820",
188 IDS_OPTIONS_SETTINGS_LANGUAGES_M17N_THAI_TIS820_INPUT_METHOD },
189 { "m17n:th:pattachote",
190 IDS_OPTIONS_SETTINGS_LANGUAGES_M17N_THAI_PATTACHOTE_INPUT_METHOD },
191 { "m17n:vi:tcvn",
192 IDS_OPTIONS_SETTINGS_LANGUAGES_M17N_VIETNAMESE_TCVN_INPUT_METHOD },
193 { "m17n:vi:telex",
194 IDS_OPTIONS_SETTINGS_LANGUAGES_M17N_VIETNAMESE_TELEX_INPUT_METHOD },
195 { "m17n:vi:viqr",
196 IDS_OPTIONS_SETTINGS_LANGUAGES_M17N_VIETNAMESE_VIQR_INPUT_METHOD },
197 { "m17n:vi:vni",
198 IDS_OPTIONS_SETTINGS_LANGUAGES_M17N_VIETNAMESE_VNI_INPUT_METHOD },
199 { "m17n:bn:itrans",
200 IDS_OPTIONS_SETTINGS_LANGUAGES_M17N_STANDARD_INPUT_METHOD },
201 { "m17n:gu:itrans",
202 IDS_OPTIONS_SETTINGS_LANGUAGES_M17N_STANDARD_INPUT_METHOD },
203 { "m17n:ml:itrans",
204 IDS_OPTIONS_SETTINGS_LANGUAGES_M17N_STANDARD_INPUT_METHOD },
205 { "m17n:mr:itrans",
206 IDS_OPTIONS_SETTINGS_LANGUAGES_M17N_STANDARD_INPUT_METHOD },
207 { "m17n:ta:phonetic",
208 IDS_OPTIONS_SETTINGS_LANGUAGES_M17N_TAMIL_PHONETIC },
209 { "m17n:ta:inscript",
210 IDS_OPTIONS_SETTINGS_LANGUAGES_M17N_TAMIL_INSCRIPT },
211 { "m17n:ta:tamil99",
212 IDS_OPTIONS_SETTINGS_LANGUAGES_M17N_TAMIL_TAMIL99 },
213 { "m17n:ta:itrans",
214 IDS_OPTIONS_SETTINGS_LANGUAGES_M17N_TAMIL_ITRANS },
215 { "m17n:ta:typewriter",
216 IDS_OPTIONS_SETTINGS_LANGUAGES_M17N_TAMIL_TYPEWRITER },
217 { "m17n:am:sera",
218 IDS_OPTIONS_SETTINGS_LANGUAGES_M17N_STANDARD_INPUT_METHOD },
219 { "m17n:te:itrans",
220 IDS_OPTIONS_SETTINGS_LANGUAGES_M17N_STANDARD_INPUT_METHOD },
221 { "m17n:kn:itrans",
222 IDS_OPTIONS_SETTINGS_LANGUAGES_M17N_STANDARD_INPUT_METHOD },
223
224 { "mozc-chewing",
225 IDS_OPTIONS_SETTINGS_LANGUAGES_CHEWING_INPUT_METHOD },
226 { "pinyin", IDS_OPTIONS_SETTINGS_LANGUAGES_PINYIN_INPUT_METHOD },
227 { "pinyin-dv",
228 IDS_OPTIONS_SETTINGS_LANGUAGES_PINYIN_DV_INPUT_METHOD },
229 { "zinnia-japanese",
230 IDS_OPTIONS_SETTINGS_LANGUAGES_JAPANESE_HANDWRITING_INPUT_METHOD },
231 { "mozc-hangul", IDS_OPTIONS_SETTINGS_LANGUAGES_KOREAN_INPUT_METHOD },
232
233 // For ibus-xkb-layouts engine: third_party/ibus-xkb-layouts/files
234 { "xkb:jp::jpn", IDS_STATUSBAR_LAYOUT_JAPAN },
235 { "xkb:si::slv", IDS_STATUSBAR_LAYOUT_SLOVENIA },
236 { "xkb:de::ger", IDS_STATUSBAR_LAYOUT_GERMANY },
237 { "xkb:de:neo:ger", IDS_STATUSBAR_LAYOUT_GERMANY_NEO2 },
238 { "xkb:it::ita", IDS_STATUSBAR_LAYOUT_ITALY },
239 { "xkb:ee::est", IDS_STATUSBAR_LAYOUT_ESTONIA },
240 { "xkb:hu::hun", IDS_STATUSBAR_LAYOUT_HUNGARY },
241 { "xkb:pl::pol", IDS_STATUSBAR_LAYOUT_POLAND },
242 { "xkb:dk::dan", IDS_STATUSBAR_LAYOUT_DENMARK },
243 { "xkb:hr::scr", IDS_STATUSBAR_LAYOUT_CROATIA },
244 { "xkb:br::por", IDS_STATUSBAR_LAYOUT_BRAZIL },
245 { "xkb:rs::srp", IDS_STATUSBAR_LAYOUT_SERBIA },
246 { "xkb:cz::cze", IDS_STATUSBAR_LAYOUT_CZECHIA },
247 { "xkb:cz:qwerty:cze", IDS_STATUSBAR_LAYOUT_CZECHIA_QWERTY },
248 { "xkb:us:dvorak:eng", IDS_STATUSBAR_LAYOUT_USA_DVORAK },
249 { "xkb:us:colemak:eng", IDS_STATUSBAR_LAYOUT_USA_COLEMAK },
250 { "xkb:ro::rum", IDS_STATUSBAR_LAYOUT_ROMANIA },
251 { "xkb:us::eng", IDS_STATUSBAR_LAYOUT_USA },
252 { "xkb:us:altgr-intl:eng", IDS_STATUSBAR_LAYOUT_USA_EXTENDED },
253 { "xkb:us:intl:eng", IDS_STATUSBAR_LAYOUT_USA_INTERNATIONAL },
254 { "xkb:lt::lit", IDS_STATUSBAR_LAYOUT_LITHUANIA },
255 { "xkb:gb:extd:eng", IDS_STATUSBAR_LAYOUT_UNITED_KINGDOM },
256 { "xkb:gb:dvorak:eng", IDS_STATUSBAR_LAYOUT_UNITED_KINGDOM_DVORAK },
257 { "xkb:sk::slo", IDS_STATUSBAR_LAYOUT_SLOVAKIA },
258 { "xkb:ru::rus", IDS_STATUSBAR_LAYOUT_RUSSIA },
259 { "xkb:ru:phonetic:rus", IDS_STATUSBAR_LAYOUT_RUSSIA_PHONETIC },
260 { "xkb:gr::gre", IDS_STATUSBAR_LAYOUT_GREECE },
261 { "xkb:be::fra", IDS_STATUSBAR_LAYOUT_BELGIUM },
262 { "xkb:be::ger", IDS_STATUSBAR_LAYOUT_BELGIUM },
263 { "xkb:be::nld", IDS_STATUSBAR_LAYOUT_BELGIUM },
264 { "xkb:bg::bul", IDS_STATUSBAR_LAYOUT_BULGARIA },
265 { "xkb:bg:phonetic:bul", IDS_STATUSBAR_LAYOUT_BULGARIA_PHONETIC },
266 { "xkb:ch::ger", IDS_STATUSBAR_LAYOUT_SWITZERLAND },
267 { "xkb:ch:fr:fra", IDS_STATUSBAR_LAYOUT_SWITZERLAND_FRENCH },
268 { "xkb:tr::tur", IDS_STATUSBAR_LAYOUT_TURKEY },
269 { "xkb:pt::por", IDS_STATUSBAR_LAYOUT_PORTUGAL },
270 { "xkb:es::spa", IDS_STATUSBAR_LAYOUT_SPAIN },
271 { "xkb:fi::fin", IDS_STATUSBAR_LAYOUT_FINLAND },
272 { "xkb:ua::ukr", IDS_STATUSBAR_LAYOUT_UKRAINE },
273 { "xkb:es:cat:cat", IDS_STATUSBAR_LAYOUT_SPAIN_CATALAN },
274 { "xkb:fr::fra", IDS_STATUSBAR_LAYOUT_FRANCE },
275 { "xkb:no::nob", IDS_STATUSBAR_LAYOUT_NORWAY },
276 { "xkb:se::swe", IDS_STATUSBAR_LAYOUT_SWEDEN },
277 { "xkb:nl::nld", IDS_STATUSBAR_LAYOUT_NETHERLANDS },
278 { "xkb:latam::spa", IDS_STATUSBAR_LAYOUT_LATIN_AMERICAN },
279 { "xkb:lv:apostrophe:lav", IDS_STATUSBAR_LAYOUT_LATVIA },
280 { "xkb:ca::fra", IDS_STATUSBAR_LAYOUT_CANADA },
281 { "xkb:ca:eng:eng", IDS_STATUSBAR_LAYOUT_CANADA_ENGLISH },
282 { "xkb:il::heb", IDS_STATUSBAR_LAYOUT_ISRAEL },
283 { "xkb:kr:kr104:kor", IDS_STATUSBAR_LAYOUT_KOREA_104 },
284 { "xkb:is::ice", IDS_STATUSBAR_LAYOUT_ICELANDIC },
285 { "xkb:ca:multix:fra", IDS_STATUSBAR_LAYOUT_CANADIAN_MULTILINGUAL },
286 { "xkb:by::bel", IDS_STATUSBAR_LAYOUT_BELARUSIAN },
287 { "xkb:am:phonetic:arm", IDS_STATUSBAR_LAYOUT_ARMENIAN_PHONETIC },
288 { "xkb:ge::geo", IDS_STATUSBAR_LAYOUT_GEORGIAN },
289 { "xkb:mn::mon", IDS_STATUSBAR_LAYOUT_MONGOLIAN },
290
291 { "english-m", IDS_STATUSBAR_LAYOUT_USA_MYSTERY },
292 };
293 const size_t kEnglishToResourceIdArraySize =
294 arraysize(kEnglishToResourceIdArray);
295
296 } // namespace
297
InputMethodUtil(InputMethodDelegate * delegate,scoped_ptr<InputMethodDescriptors> supported_input_methods)298 InputMethodUtil::InputMethodUtil(
299 InputMethodDelegate* delegate,
300 scoped_ptr<InputMethodDescriptors> supported_input_methods)
301 : supported_input_methods_(supported_input_methods.Pass()),
302 delegate_(delegate) {
303 ReloadInternalMaps();
304
305 // Initialize a map from English string to Chrome string resource ID as well.
306 for (size_t i = 0; i < kEnglishToResourceIdArraySize; ++i) {
307 const EnglishToResouceId& map_entry = kEnglishToResourceIdArray[i];
308 const bool result = english_to_resource_id_.insert(std::make_pair(
309 map_entry.english_string_from_ibus, map_entry.resource_id)).second;
310 DCHECK(result) << "Duplicated string is found: "
311 << map_entry.english_string_from_ibus;
312 }
313 }
314
~InputMethodUtil()315 InputMethodUtil::~InputMethodUtil() {
316 }
317
TranslateStringInternal(const std::string & english_string,base::string16 * out_string) const318 bool InputMethodUtil::TranslateStringInternal(
319 const std::string& english_string, base::string16 *out_string) const {
320 DCHECK(out_string);
321 HashType::const_iterator iter = english_to_resource_id_.find(english_string);
322 if (iter == english_to_resource_id_.end()) {
323 // TODO(yusukes): Write Autotest which checks if all display names and all
324 // property names for supported input methods are listed in the resource
325 // ID array (crosbug.com/4572).
326 LOG(ERROR) << "Resource ID is not found for: " << english_string;
327 return false;
328 }
329
330 *out_string = delegate_->GetLocalizedString(iter->second);
331 return true;
332 }
333
TranslateString(const std::string & english_string) const334 base::string16 InputMethodUtil::TranslateString(
335 const std::string& english_string) const {
336 base::string16 localized_string;
337 if (TranslateStringInternal(english_string, &localized_string)) {
338 return localized_string;
339 }
340 return UTF8ToUTF16(english_string);
341 }
342
IsValidInputMethodId(const std::string & input_method_id) const343 bool InputMethodUtil::IsValidInputMethodId(
344 const std::string& input_method_id) const {
345 // We can't check the component extension is whilelisted or not here because
346 // it might not be initialized.
347 return GetInputMethodDescriptorFromId(input_method_id) != NULL ||
348 extension_ime_util::IsComponentExtensionIME(input_method_id);
349 }
350
351 // static
IsKeyboardLayout(const std::string & input_method_id)352 bool InputMethodUtil::IsKeyboardLayout(const std::string& input_method_id) {
353 const bool kCaseInsensitive = false;
354 return StartsWithASCII(input_method_id, "xkb:", kCaseInsensitive);
355 }
356
GetLanguageCodeFromInputMethodId(const std::string & input_method_id) const357 std::string InputMethodUtil::GetLanguageCodeFromInputMethodId(
358 const std::string& input_method_id) const {
359 // The code should be compatible with one of codes used for UI languages,
360 // defined in app/l10_util.cc.
361 const char kDefaultLanguageCode[] = "en-US";
362 std::map<std::string, std::string>::const_iterator iter
363 = id_to_language_code_.find(input_method_id);
364 return (iter == id_to_language_code_.end()) ?
365 // Returning |kDefaultLanguageCode| here is not for Chrome OS but for
366 // Ubuntu where the ibus-xkb-layouts engine could be missing.
367 kDefaultLanguageCode : iter->second;
368 }
369
GetKeyboardLayoutName(const std::string & input_method_id) const370 std::string InputMethodUtil::GetKeyboardLayoutName(
371 const std::string& input_method_id) const {
372 InputMethodIdToDescriptorMap::const_iterator iter
373 = id_to_descriptor_.find(input_method_id);
374 return (iter == id_to_descriptor_.end()) ?
375 "" : iter->second.GetPreferredKeyboardLayout();
376 }
377
GetInputMethodDisplayNameFromId(const std::string & input_method_id) const378 std::string InputMethodUtil::GetInputMethodDisplayNameFromId(
379 const std::string& input_method_id) const {
380 base::string16 display_name;
381 if (!extension_ime_util::IsExtensionIME(input_method_id) &&
382 TranslateStringInternal(input_method_id, &display_name)) {
383 return UTF16ToUTF8(display_name);
384 }
385 // Return an empty string if the display name is not found.
386 return "";
387 }
388
GetInputMethodShortName(const InputMethodDescriptor & input_method) const389 base::string16 InputMethodUtil::GetInputMethodShortName(
390 const InputMethodDescriptor& input_method) const {
391 // For the status area, we use two-letter, upper-case language code like
392 // "US" and "JP".
393 base::string16 text;
394
395 // Check special cases first.
396 for (size_t i = 0; i < kMappingFromIdToIndicatorTextLen; ++i) {
397 if (kMappingFromIdToIndicatorText[i].input_method_id == input_method.id()) {
398 text = UTF8ToUTF16(kMappingFromIdToIndicatorText[i].indicator_text);
399 break;
400 }
401 }
402
403 // Display the keyboard layout name when using a keyboard layout.
404 if (text.empty() &&
405 IsKeyboardLayout(input_method.id())) {
406 const size_t kMaxKeyboardLayoutNameLen = 2;
407 const base::string16 keyboard_layout =
408 UTF8ToUTF16(GetKeyboardLayoutName(input_method.id()));
409 text = StringToUpperASCII(keyboard_layout).substr(
410 0, kMaxKeyboardLayoutNameLen);
411 }
412
413 // TODO(yusukes): Some languages have two or more input methods. For example,
414 // Thai has 3, Vietnamese has 4. If these input methods could be activated at
415 // the same time, we should do either of the following:
416 // (1) Add mappings to |kMappingFromIdToIndicatorText|
417 // (2) Add suffix (1, 2, ...) to |text| when ambiguous.
418
419 if (text.empty()) {
420 const size_t kMaxLanguageNameLen = 2;
421 DCHECK(!input_method.language_codes().empty());
422 const std::string language_code = input_method.language_codes().at(0);
423 text = StringToUpperASCII(UTF8ToUTF16(language_code)).substr(
424 0, kMaxLanguageNameLen);
425 }
426 DCHECK(!text.empty());
427 return text;
428 }
429
GetInputMethodMediumName(const InputMethodDescriptor & input_method) const430 base::string16 InputMethodUtil::GetInputMethodMediumName(
431 const InputMethodDescriptor& input_method) const {
432 // For the "Your input method has changed to..." bubble. In most cases
433 // it uses the same name as the short name, unless found in a table
434 // for medium length names.
435 for (size_t i = 0; i < kMappingImeIdToMediumLenNameResourceIdLen; ++i) {
436 if (kMappingImeIdToMediumLenNameResourceId[i].input_method_id ==
437 input_method.id()) {
438 return delegate_->GetLocalizedString(
439 kMappingImeIdToMediumLenNameResourceId[i].resource_id);
440 }
441 }
442 return GetInputMethodShortName(input_method);
443 }
444
GetInputMethodLongName(const InputMethodDescriptor & input_method) const445 base::string16 InputMethodUtil::GetInputMethodLongName(
446 const InputMethodDescriptor& input_method) const {
447 if (!input_method.name().empty()) {
448 // If the descriptor has a name, use it.
449 return UTF8ToUTF16(input_method.name());
450 }
451
452 // We don't show language here. Name of keyboard layout or input method
453 // usually imply (or explicitly include) its language.
454
455 // Special case for German, French and Dutch: these languages have multiple
456 // keyboard layouts and share the same layout of keyboard (Belgian). We need
457 // to show explicitly the language for the layout. For Arabic, Amharic, and
458 // Indic languages: they share "Standard Input Method".
459 const base::string16 standard_input_method_text =
460 delegate_->GetLocalizedString(
461 IDS_OPTIONS_SETTINGS_LANGUAGES_M17N_STANDARD_INPUT_METHOD);
462 DCHECK(!input_method.language_codes().empty());
463 const std::string language_code = input_method.language_codes().at(0);
464
465 base::string16 text = TranslateString(input_method.id());
466 if (text == standard_input_method_text ||
467 language_code == "de" ||
468 language_code == "fr" ||
469 language_code == "nl") {
470 const base::string16 language_name = delegate_->GetDisplayLanguageName(
471 language_code);
472
473 text = language_name + UTF8ToUTF16(" - ") + text;
474 }
475
476 DCHECK(!text.empty());
477 return text;
478 }
479
GetInputMethodDescriptorFromId(const std::string & input_method_id) const480 const InputMethodDescriptor* InputMethodUtil::GetInputMethodDescriptorFromId(
481 const std::string& input_method_id) const {
482 InputMethodIdToDescriptorMap::const_iterator iter
483 = id_to_descriptor_.find(input_method_id);
484 return (iter == id_to_descriptor_.end()) ? NULL : &(iter->second);
485 }
486
GetInputMethodIdsFromLanguageCode(const std::string & normalized_language_code,InputMethodType type,std::vector<std::string> * out_input_method_ids) const487 bool InputMethodUtil::GetInputMethodIdsFromLanguageCode(
488 const std::string& normalized_language_code,
489 InputMethodType type,
490 std::vector<std::string>* out_input_method_ids) const {
491 return GetInputMethodIdsFromLanguageCodeInternal(
492 language_code_to_ids_,
493 normalized_language_code, type, out_input_method_ids);
494 }
495
GetInputMethodIdsFromLanguageCodeInternal(const std::multimap<std::string,std::string> & language_code_to_ids,const std::string & normalized_language_code,InputMethodType type,std::vector<std::string> * out_input_method_ids) const496 bool InputMethodUtil::GetInputMethodIdsFromLanguageCodeInternal(
497 const std::multimap<std::string, std::string>& language_code_to_ids,
498 const std::string& normalized_language_code,
499 InputMethodType type,
500 std::vector<std::string>* out_input_method_ids) const {
501 DCHECK(out_input_method_ids);
502 out_input_method_ids->clear();
503
504 bool result = false;
505 std::pair<LanguageCodeToIdsMap::const_iterator,
506 LanguageCodeToIdsMap::const_iterator> range =
507 language_code_to_ids.equal_range(normalized_language_code);
508 for (LanguageCodeToIdsMap::const_iterator iter = range.first;
509 iter != range.second; ++iter) {
510 const std::string& input_method_id = iter->second;
511 if ((type == kAllInputMethods) || IsKeyboardLayout(input_method_id)) {
512 out_input_method_ids->push_back(input_method_id);
513 result = true;
514 }
515 }
516 if ((type == kAllInputMethods) && !result) {
517 DVLOG(1) << "Unknown language code: " << normalized_language_code;
518 }
519 return result;
520 }
521
GetFirstLoginInputMethodIds(const std::string & language_code,const InputMethodDescriptor & current_input_method,std::vector<std::string> * out_input_method_ids) const522 void InputMethodUtil::GetFirstLoginInputMethodIds(
523 const std::string& language_code,
524 const InputMethodDescriptor& current_input_method,
525 std::vector<std::string>* out_input_method_ids) const {
526 out_input_method_ids->clear();
527
528 // First, add the current keyboard layout (one used on the login screen).
529 out_input_method_ids->push_back(current_input_method.id());
530
531 const std::string current_layout
532 = current_input_method.GetPreferredKeyboardLayout();
533 for (size_t i = 0; i < ARRAYSIZE_UNSAFE(kDefaultInputMethodRecommendation);
534 ++i) {
535 if (kDefaultInputMethodRecommendation[i].locale == language_code &&
536 kDefaultInputMethodRecommendation[i].layout == current_layout) {
537 out_input_method_ids->push_back(
538 kDefaultInputMethodRecommendation[i].input_method_id);
539 return;
540 }
541 }
542
543 // Second, find the most popular input method associated with the
544 // current UI language. The input method IDs returned from
545 // GetInputMethodIdsFromLanguageCode() are sorted by popularity, hence
546 // our basic strategy is to pick the first one, but it's a bit more
547 // complicated as shown below.
548 std::string most_popular_id;
549 std::vector<std::string> input_method_ids;
550 // This returns the input methods sorted by popularity.
551 GetInputMethodIdsFromLanguageCode(
552 language_code, kAllInputMethods, &input_method_ids);
553 for (size_t i = 0; i < input_method_ids.size(); ++i) {
554 const std::string& input_method_id = input_method_ids[i];
555 // Pick the first one.
556 if (most_popular_id.empty())
557 most_popular_id = input_method_id;
558
559 // Check if there is one that matches the current keyboard layout, but
560 // not the current keyboard itself. This is useful if there are
561 // multiple keyboard layout choices for one input method. For
562 // instance, Mozc provides three choices: mozc (US keyboard), mozc-jp
563 // (JP keyboard), mozc-dv (Dvorak).
564 const InputMethodDescriptor* descriptor =
565 GetInputMethodDescriptorFromId(input_method_id);
566 if (descriptor &&
567 descriptor->id() != current_input_method.id() &&
568 descriptor->GetPreferredKeyboardLayout() ==
569 current_input_method.GetPreferredKeyboardLayout()) {
570 most_popular_id = input_method_id;
571 break;
572 }
573 }
574 // Add the most popular input method ID, if it's different from the
575 // current input method.
576 if (most_popular_id != current_input_method.id() &&
577 // TODO(yusukes): Remove this hack when we remove the "english-m" IME.
578 most_popular_id != "english-m") {
579 out_input_method_ids->push_back(most_popular_id);
580 }
581 }
582
GetLanguageCodesFromInputMethodIds(const std::vector<std::string> & input_method_ids,std::vector<std::string> * out_language_codes) const583 void InputMethodUtil::GetLanguageCodesFromInputMethodIds(
584 const std::vector<std::string>& input_method_ids,
585 std::vector<std::string>* out_language_codes) const {
586 out_language_codes->clear();
587
588 for (size_t i = 0; i < input_method_ids.size(); ++i) {
589 const std::string& input_method_id = input_method_ids[i];
590 const InputMethodDescriptor* input_method =
591 GetInputMethodDescriptorFromId(input_method_id);
592 if (!input_method) {
593 DVLOG(1) << "Unknown input method ID: " << input_method_ids[i];
594 continue;
595 }
596 DCHECK(!input_method->language_codes().empty());
597 const std::string language_code = input_method->language_codes().at(0);
598 // Add it if it's not already present.
599 if (std::count(out_language_codes->begin(), out_language_codes->end(),
600 language_code) == 0) {
601 out_language_codes->push_back(language_code);
602 }
603 }
604 }
605
GetHardwareInputMethodId() const606 std::string InputMethodUtil::GetHardwareInputMethodId() const {
607 const std::string input_method_id = delegate_->GetHardwareKeyboardLayout();
608
609 if (input_method_id.empty()) {
610 // This is totally fine if it's empty. The hardware keyboard layout is
611 // not stored if startup_manifest.json (OEM customization data) is not
612 // present (ex. Cr48 doen't have that file).
613 return GetFallbackInputMethodDescriptor().id();
614 }
615 return input_method_id;
616 }
617
SetComponentExtensions(const InputMethodDescriptors & imes)618 void InputMethodUtil::SetComponentExtensions(
619 const InputMethodDescriptors& imes) {
620 component_extension_ime_id_to_descriptor_.clear();
621 for (size_t i = 0; i < imes.size(); ++i) {
622 const InputMethodDescriptor& input_method = imes.at(i);
623 DCHECK(!input_method.language_codes().empty());
624 const std::string language_code = input_method.language_codes().at(0);
625 id_to_language_code_.insert(
626 std::make_pair(input_method.id(), language_code));
627 id_to_descriptor_.insert(
628 std::make_pair(input_method.id(), input_method));
629 }
630 }
631
GetFallbackInputMethodDescriptor()632 InputMethodDescriptor InputMethodUtil::GetFallbackInputMethodDescriptor() {
633 std::vector<std::string> layouts;
634 layouts.push_back("us");
635 std::vector<std::string> languages;
636 languages.push_back("en-US");
637 return InputMethodDescriptor("xkb:us::eng",
638 "",
639 layouts,
640 languages,
641 true, // login keyboard.
642 GURL(), // options page, not available.
643 GURL()); // input view page, not available.
644 }
645
ReloadInternalMaps()646 void InputMethodUtil::ReloadInternalMaps() {
647 if (supported_input_methods_->size() <= 1) {
648 DVLOG(1) << "GetSupportedInputMethods returned a fallback ID";
649 // TODO(yusukes): Handle this error in nicer way.
650 }
651
652 // Clear the existing maps.
653 language_code_to_ids_.clear();
654 id_to_language_code_.clear();
655 id_to_descriptor_.clear();
656 xkb_id_to_descriptor_.clear();
657
658 for (size_t i = 0; i < supported_input_methods_->size(); ++i) {
659 const InputMethodDescriptor& input_method =
660 supported_input_methods_->at(i);
661 const std::vector<std::string>& language_codes =
662 input_method.language_codes();
663 for (size_t i = 0; i < language_codes.size(); ++i) {
664 language_code_to_ids_.insert(
665 std::make_pair(language_codes[i], input_method.id()));
666 // Remember the pairs.
667 id_to_language_code_.insert(
668 std::make_pair(input_method.id(), language_codes[i]));
669 }
670 id_to_descriptor_.insert(
671 std::make_pair(input_method.id(), input_method));
672 if (IsKeyboardLayout(input_method.id())) {
673 xkb_id_to_descriptor_.insert(
674 std::make_pair(input_method.GetPreferredKeyboardLayout(),
675 input_method));
676 }
677 }
678 }
679
680 } // namespace input_method
681 } // namespace chromeos
682