1 // Copyright (c) 2011 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/status/input_method_menu.h" 6 7 #include <string> 8 #include <vector> 9 10 #include "base/string_split.h" 11 #include "base/string_util.h" 12 #include "base/time.h" 13 #include "base/utf_string_conversions.h" 14 #include "chrome/browser/browser_process.h" 15 #include "chrome/browser/chromeos/cros/cros_library.h" 16 #include "chrome/browser/chromeos/input_method/input_method_util.h" 17 #include "chrome/browser/chromeos/language_preferences.h" 18 #include "chrome/browser/metrics/user_metrics.h" 19 #include "chrome/browser/prefs/pref_service.h" 20 #include "chrome/common/pref_names.h" 21 #include "content/common/notification_service.h" 22 #include "grit/generated_resources.h" 23 #include "grit/theme_resources.h" 24 #include "ui/base/l10n/l10n_util.h" 25 #include "ui/base/resource/resource_bundle.h" 26 27 // The language menu consists of 3 parts (in this order): 28 // 29 // (1) input method names. The size of the list is always >= 1. 30 // (2) input method properties. This list might be empty. 31 // (3) "Customize language and input..." button. 32 // 33 // Example of the menu (Japanese): 34 // 35 // ============================== (border of the popup window) 36 // [ ] English (|index| in the following functions is 0) 37 // [*] Japanese 38 // [ ] Chinese (Simplified) 39 // ------------------------------ (separator) 40 // [*] Hiragana (index = 5, The property has 2 radio groups) 41 // [ ] Katakana 42 // [ ] HalfWidthKatakana 43 // [*] Roman 44 // [ ] Kana 45 // ------------------------------ (separator) 46 // Customize language and input...(index = 11) 47 // ============================== (border of the popup window) 48 // 49 // Example of the menu (Simplified Chinese): 50 // 51 // ============================== (border of the popup window) 52 // [ ] English 53 // [ ] Japanese 54 // [*] Chinese (Simplified) 55 // ------------------------------ (separator) 56 // Switch to full letter mode (The property has 2 command buttons) 57 // Switch to half punctuation mode 58 // ------------------------------ (separator) 59 // Customize language and input... 60 // ============================== (border of the popup window) 61 // 62 63 namespace { 64 65 // Constants to specify the type of items in |model_|. 66 enum { 67 COMMAND_ID_INPUT_METHODS = 0, // English, Chinese, Japanese, Arabic, ... 68 COMMAND_ID_IME_PROPERTIES, // Hiragana, Katakana, ... 69 COMMAND_ID_CUSTOMIZE_LANGUAGE, // "Customize language and input..." button. 70 }; 71 72 // A group ID for IME properties starts from 0. We use the huge value for the 73 // input method list to avoid conflict. 74 const int kRadioGroupLanguage = 1 << 16; 75 const int kRadioGroupNone = -1; 76 77 // A mapping from an input method id to a string for the language indicator. The 78 // mapping is necessary since some input methods belong to the same language. 79 // For example, both "xkb:us::eng" and "xkb:us:dvorak:eng" are for US English. 80 const struct { 81 const char* input_method_id; 82 const char* indicator_text; 83 } kMappingFromIdToIndicatorText[] = { 84 // To distinguish from "xkb:us::eng" 85 { "xkb:us:altgr-intl:eng", "EXTD" }, 86 { "xkb:us:dvorak:eng", "DV" }, 87 { "xkb:us:intl:eng", "INTL" }, 88 { "xkb:us:colemak:eng", "CO" }, 89 { "xkb:de:neo:ger", "NEO" }, 90 // To distinguish from "xkb:gb::eng" 91 { "xkb:gb:dvorak:eng", "DV" }, 92 // To distinguish from "xkb:jp::jpn" 93 { "mozc", "\xe3\x81\x82" }, // U+3042, Japanese Hiragana letter A in UTF-8. 94 { "mozc-dv", "\xe3\x81\x82" }, 95 { "mozc-jp", "\xe3\x81\x82" }, 96 // For simplified Chinese input methods 97 { "pinyin", "\xe6\x8b\xbc" }, // U+62FC 98 // For traditional Chinese input methods 99 { "mozc-chewing", "\xe9\x85\xb7" }, // U+9177 100 { "m17n:zh:cangjie", "\xe5\x80\x89" }, // U+5009 101 { "m17n:zh:quick", "\xe9\x80\x9f" }, // U+901F 102 // For Hangul input method. 103 { "hangul", "\xed\x95\x9c" }, // U+D55C 104 }; 105 const size_t kMappingFromIdToIndicatorTextLen = 106 ARRAYSIZE_UNSAFE(kMappingFromIdToIndicatorText); 107 108 // Returns the language name for the given |language_code|. 109 std::wstring GetLanguageName(const std::string& language_code) { 110 const string16 language_name = l10n_util::GetDisplayNameForLocale( 111 language_code, g_browser_process->GetApplicationLocale(), true); 112 return UTF16ToWide(language_name); 113 } 114 115 } // namespace 116 117 namespace chromeos { 118 119 //////////////////////////////////////////////////////////////////////////////// 120 // InputMethodMenu 121 122 InputMethodMenu::InputMethodMenu(PrefService* pref_service, 123 StatusAreaHost::ScreenMode screen_mode, 124 bool for_out_of_box_experience_dialog) 125 : input_method_descriptors_(CrosLibrary::Get()->GetInputMethodLibrary()-> 126 GetActiveInputMethods()), 127 model_(NULL), 128 // Be aware that the constructor of |input_method_menu_| calls 129 // GetItemCount() in this class. Therefore, GetItemCount() have to return 130 // 0 when |model_| is NULL. 131 ALLOW_THIS_IN_INITIALIZER_LIST(input_method_menu_(this)), 132 minimum_input_method_menu_width_(0), 133 pref_service_(pref_service), 134 screen_mode_(screen_mode), 135 for_out_of_box_experience_dialog_(for_out_of_box_experience_dialog) { 136 DCHECK(input_method_descriptors_.get() && 137 !input_method_descriptors_->empty()); 138 139 // Sync current and previous input methods on Chrome prefs with ibus-daemon. 140 if (pref_service_ && (screen_mode_ == StatusAreaHost::kBrowserMode)) { 141 previous_input_method_pref_.Init( 142 prefs::kLanguagePreviousInputMethod, pref_service, this); 143 current_input_method_pref_.Init( 144 prefs::kLanguageCurrentInputMethod, pref_service, this); 145 } 146 147 InputMethodLibrary* library = CrosLibrary::Get()->GetInputMethodLibrary(); 148 library->AddObserver(this); // FirstObserverIsAdded() might be called back. 149 150 if (screen_mode_ == StatusAreaHost::kLoginMode) { 151 // This button is for the login screen. 152 registrar_.Add(this, 153 NotificationType::LOGIN_USER_CHANGED, 154 NotificationService::AllSources()); 155 } 156 } 157 158 InputMethodMenu::~InputMethodMenu() { 159 // RemoveObserver() is no-op if |this| object is already removed from the 160 // observer list. 161 CrosLibrary::Get()->GetInputMethodLibrary()->RemoveObserver(this); 162 } 163 164 //////////////////////////////////////////////////////////////////////////////// 165 // ui::MenuModel implementation: 166 167 int InputMethodMenu::GetCommandIdAt(int index) const { 168 return index; 169 } 170 171 bool InputMethodMenu::IsItemDynamicAt(int index) const { 172 // Menu content for the language button could change time by time. 173 return true; 174 } 175 176 bool InputMethodMenu::GetAcceleratorAt( 177 int index, ui::Accelerator* accelerator) const { 178 // Views for Chromium OS does not support accelerators yet. 179 return false; 180 } 181 182 bool InputMethodMenu::IsItemCheckedAt(int index) const { 183 DCHECK_GE(index, 0); 184 DCHECK(input_method_descriptors_.get()); 185 186 if (IndexIsInInputMethodList(index)) { 187 const InputMethodDescriptor& input_method 188 = input_method_descriptors_->at(index); 189 return input_method == CrosLibrary::Get()->GetInputMethodLibrary()-> 190 current_input_method(); 191 } 192 193 if (GetPropertyIndex(index, &index)) { 194 const ImePropertyList& property_list 195 = CrosLibrary::Get()->GetInputMethodLibrary()->current_ime_properties(); 196 return property_list.at(index).is_selection_item_checked; 197 } 198 199 // Separator(s) or the "Customize language and input..." button. 200 return false; 201 } 202 203 int InputMethodMenu::GetGroupIdAt(int index) const { 204 DCHECK_GE(index, 0); 205 206 if (IndexIsInInputMethodList(index)) { 207 return for_out_of_box_experience_dialog_ ? 208 kRadioGroupNone : kRadioGroupLanguage; 209 } 210 211 if (GetPropertyIndex(index, &index)) { 212 const ImePropertyList& property_list 213 = CrosLibrary::Get()->GetInputMethodLibrary()->current_ime_properties(); 214 return property_list.at(index).selection_item_id; 215 } 216 217 return kRadioGroupNone; 218 } 219 220 bool InputMethodMenu::HasIcons() const { 221 // We don't support icons on Chrome OS. 222 return false; 223 } 224 225 bool InputMethodMenu::GetIconAt(int index, SkBitmap* icon) { 226 return false; 227 } 228 229 ui::ButtonMenuItemModel* InputMethodMenu::GetButtonMenuItemAt( 230 int index) const { 231 return NULL; 232 } 233 234 bool InputMethodMenu::IsEnabledAt(int index) const { 235 // Just return true so all input method names and input method propertie names 236 // could be clicked. 237 return true; 238 } 239 240 ui::MenuModel* InputMethodMenu::GetSubmenuModelAt(int index) const { 241 // We don't use nested menus. 242 return NULL; 243 } 244 245 void InputMethodMenu::HighlightChangedTo(int index) { 246 // Views for Chromium OS does not support this interface yet. 247 } 248 249 void InputMethodMenu::MenuWillShow() { 250 // Views for Chromium OS does not support this interface yet. 251 } 252 253 void InputMethodMenu::SetMenuModelDelegate(ui::MenuModelDelegate* delegate) { 254 // Not needed for current usage. 255 } 256 257 int InputMethodMenu::GetItemCount() const { 258 if (!model_.get()) { 259 // Model is not constructed yet. This means that 260 // InputMethodMenu is being constructed. Return zero. 261 return 0; 262 } 263 return model_->GetItemCount(); 264 } 265 266 ui::MenuModel::ItemType InputMethodMenu::GetTypeAt(int index) const { 267 DCHECK_GE(index, 0); 268 269 if (IndexPointsToConfigureImeMenuItem(index)) { 270 return ui::MenuModel::TYPE_COMMAND; // "Customize language and input" 271 } 272 273 if (IndexIsInInputMethodList(index)) { 274 return for_out_of_box_experience_dialog_ ? 275 ui::MenuModel::TYPE_COMMAND : ui::MenuModel::TYPE_RADIO; 276 } 277 278 if (GetPropertyIndex(index, &index)) { 279 const ImePropertyList& property_list 280 = CrosLibrary::Get()->GetInputMethodLibrary()->current_ime_properties(); 281 if (property_list.at(index).is_selection_item) { 282 return ui::MenuModel::TYPE_RADIO; 283 } 284 return ui::MenuModel::TYPE_COMMAND; 285 } 286 287 return ui::MenuModel::TYPE_SEPARATOR; 288 } 289 290 string16 InputMethodMenu::GetLabelAt(int index) const { 291 DCHECK_GE(index, 0); 292 DCHECK(input_method_descriptors_.get()); 293 294 // We use IDS_OPTIONS_SETTINGS_LANGUAGES_CUSTOMIZE here as the button 295 // opens the same dialog that is opened from the main options dialog. 296 if (IndexPointsToConfigureImeMenuItem(index)) { 297 return l10n_util::GetStringUTF16(IDS_OPTIONS_SETTINGS_LANGUAGES_CUSTOMIZE); 298 } 299 300 std::wstring name; 301 if (IndexIsInInputMethodList(index)) { 302 name = GetTextForMenu(input_method_descriptors_->at(index)); 303 } else if (GetPropertyIndex(index, &index)) { 304 InputMethodLibrary* library = CrosLibrary::Get()->GetInputMethodLibrary(); 305 const ImePropertyList& property_list = library->current_ime_properties(); 306 const std::string& input_method_id = library->current_input_method().id; 307 return input_method::GetStringUTF16( 308 property_list.at(index).label, input_method_id); 309 } 310 311 return WideToUTF16(name); 312 } 313 314 void InputMethodMenu::ActivatedAt(int index) { 315 DCHECK_GE(index, 0); 316 DCHECK(input_method_descriptors_.get()); 317 318 if (IndexPointsToConfigureImeMenuItem(index)) { 319 OpenConfigUI(); 320 return; 321 } 322 323 if (IndexIsInInputMethodList(index)) { 324 // Inter-IME switching. 325 const InputMethodDescriptor& input_method 326 = input_method_descriptors_->at(index); 327 CrosLibrary::Get()->GetInputMethodLibrary()->ChangeInputMethod( 328 input_method.id); 329 UserMetrics::RecordAction( 330 UserMetricsAction("LanguageMenuButton_InputMethodChanged")); 331 return; 332 } 333 334 if (GetPropertyIndex(index, &index)) { 335 // Intra-IME switching (e.g. Japanese-Hiragana to Japanese-Katakana). 336 const ImePropertyList& property_list 337 = CrosLibrary::Get()->GetInputMethodLibrary()->current_ime_properties(); 338 const std::string key = property_list.at(index).key; 339 if (property_list.at(index).is_selection_item) { 340 // Radio button is clicked. 341 const int id = property_list.at(index).selection_item_id; 342 // First, deactivate all other properties in the same radio group. 343 for (int i = 0; i < static_cast<int>(property_list.size()); ++i) { 344 if (i != index && id == property_list.at(i).selection_item_id) { 345 CrosLibrary::Get()->GetInputMethodLibrary()->SetImePropertyActivated( 346 property_list.at(i).key, false); 347 } 348 } 349 // Then, activate the property clicked. 350 CrosLibrary::Get()->GetInputMethodLibrary()->SetImePropertyActivated( 351 key, true); 352 } else { 353 // Command button like "Switch to half punctuation mode" is clicked. 354 // We can always use "Deactivate" for command buttons. 355 CrosLibrary::Get()->GetInputMethodLibrary()->SetImePropertyActivated( 356 key, false); 357 } 358 return; 359 } 360 361 LOG(ERROR) << "Unexpected index: " << index; 362 } 363 364 //////////////////////////////////////////////////////////////////////////////// 365 // views::ViewMenuDelegate implementation: 366 367 void InputMethodMenu::RunMenu( 368 views::View* unused_source, const gfx::Point& pt) { 369 PrepareForMenuOpen(); 370 input_method_menu_.RunMenuAt(pt, views::Menu2::ALIGN_TOPRIGHT); 371 } 372 373 //////////////////////////////////////////////////////////////////////////////// 374 // InputMethodLibrary::Observer implementation: 375 376 void InputMethodMenu::InputMethodChanged( 377 InputMethodLibrary* obj, 378 const InputMethodDescriptor& current_input_method, 379 size_t num_active_input_methods) { 380 UpdateUIFromInputMethod(current_input_method, num_active_input_methods); 381 } 382 383 void InputMethodMenu::PreferenceUpdateNeeded( 384 InputMethodLibrary* obj, 385 const InputMethodDescriptor& previous_input_method, 386 const InputMethodDescriptor& current_input_method) { 387 if (screen_mode_ == StatusAreaHost::kBrowserMode) { 388 if (pref_service_) { // make sure we're not in unit tests. 389 // Sometimes (e.g. initial boot) |previous_input_method.id| is empty. 390 previous_input_method_pref_.SetValue(previous_input_method.id); 391 current_input_method_pref_.SetValue(current_input_method.id); 392 pref_service_->ScheduleSavePersistentPrefs(); 393 } 394 } else if (screen_mode_ == StatusAreaHost::kLoginMode) { 395 if (g_browser_process && g_browser_process->local_state()) { 396 g_browser_process->local_state()->SetString( 397 language_prefs::kPreferredKeyboardLayout, current_input_method.id); 398 g_browser_process->local_state()->SavePersistentPrefs(); 399 } 400 } 401 } 402 403 void InputMethodMenu::PropertyListChanged( 404 InputMethodLibrary* obj, 405 const ImePropertyList& current_ime_properties) { 406 // Usual order of notifications of input method change is: 407 // 1. RegisterProperties(empty) 408 // 2. RegisterProperties(list-of-new-properties) 409 // 3. GlobalInputMethodChanged 410 // However, due to the asynchronicity, we occasionally (but rarely) face to 411 // 1. RegisterProperties(empty) 412 // 2. GlobalInputMethodChanged 413 // 3. RegisterProperties(list-of-new-properties) 414 // this order. On this unusual case, we must rebuild the menu after the last 415 // RegisterProperties. For the other cases, no rebuild is needed. Actually 416 // it is better to be avoided. Otherwise users can sometimes observe the 417 // awkward clear-then-register behavior. 418 if (!current_ime_properties.empty()) { 419 InputMethodLibrary* library = CrosLibrary::Get()->GetInputMethodLibrary(); 420 const InputMethodDescriptor& input_method = library->current_input_method(); 421 size_t num_active_input_methods = library->GetNumActiveInputMethods(); 422 UpdateUIFromInputMethod(input_method, num_active_input_methods); 423 } 424 } 425 426 void InputMethodMenu::FirstObserverIsAdded(InputMethodLibrary* obj) { 427 // NOTICE: Since this function might be called from the constructor of this 428 // class, it's better to avoid calling virtual functions. 429 430 if (pref_service_ && (screen_mode_ == StatusAreaHost::kBrowserMode)) { 431 // Get the input method name in the Preferences file which was in use last 432 // time, and switch to the method. We remember two input method names in the 433 // preference so that the Control+space hot-key could work fine from the 434 // beginning. InputMethodChanged() will be called soon and the indicator 435 // will be updated. 436 InputMethodLibrary* library = CrosLibrary::Get()->GetInputMethodLibrary(); 437 const std::string previous_input_method_id = 438 previous_input_method_pref_.GetValue(); 439 if (!previous_input_method_id.empty()) { 440 library->ChangeInputMethod(previous_input_method_id); 441 } 442 const std::string current_input_method_id = 443 current_input_method_pref_.GetValue(); 444 if (!current_input_method_id.empty()) { 445 library->ChangeInputMethod(current_input_method_id); 446 } 447 } 448 } 449 450 void InputMethodMenu::PrepareForMenuOpen() { 451 UserMetrics::RecordAction(UserMetricsAction("LanguageMenuButton_Open")); 452 PrepareMenu(); 453 } 454 455 void InputMethodMenu::PrepareMenu() { 456 input_method_descriptors_.reset(CrosLibrary::Get()->GetInputMethodLibrary()-> 457 GetActiveInputMethods()); 458 RebuildModel(); 459 input_method_menu_.Rebuild(); 460 if (minimum_input_method_menu_width_ > 0) { 461 input_method_menu_.SetMinimumWidth(minimum_input_method_menu_width_); 462 } 463 } 464 465 void InputMethodMenu::ActiveInputMethodsChanged( 466 InputMethodLibrary* obj, 467 const InputMethodDescriptor& current_input_method, 468 size_t num_active_input_methods) { 469 // Update the icon if active input methods are changed. See also 470 // comments in UpdateUI() in input_method_menu_button.cc. 471 UpdateUIFromInputMethod(current_input_method, num_active_input_methods); 472 } 473 474 void InputMethodMenu::UpdateUIFromInputMethod( 475 const InputMethodDescriptor& input_method, 476 size_t num_active_input_methods) { 477 const std::wstring name = GetTextForIndicator(input_method); 478 const std::wstring tooltip = GetTextForMenu(input_method); 479 UpdateUI(input_method.id, name, tooltip, num_active_input_methods); 480 } 481 482 void InputMethodMenu::RebuildModel() { 483 model_.reset(new ui::SimpleMenuModel(NULL)); 484 string16 dummy_label = UTF8ToUTF16(""); 485 // Indicates if separator's needed before each section. 486 bool need_separator = false; 487 488 if (!input_method_descriptors_->empty()) { 489 // We "abuse" the command_id and group_id arguments of AddRadioItem method. 490 // A COMMAND_ID_XXX enum value is passed as command_id, and array index of 491 // |input_method_descriptors_| or |property_list| is passed as group_id. 492 for (size_t i = 0; i < input_method_descriptors_->size(); ++i) { 493 model_->AddRadioItem(COMMAND_ID_INPUT_METHODS, dummy_label, i); 494 } 495 496 need_separator = true; 497 } 498 499 const ImePropertyList& property_list 500 = CrosLibrary::Get()->GetInputMethodLibrary()->current_ime_properties(); 501 if (!property_list.empty()) { 502 if (need_separator) { 503 model_->AddSeparator(); 504 } 505 for (size_t i = 0; i < property_list.size(); ++i) { 506 model_->AddRadioItem(COMMAND_ID_IME_PROPERTIES, dummy_label, i); 507 } 508 need_separator = true; 509 } 510 511 if (ShouldSupportConfigUI()) { 512 // Note: We use AddSeparator() for separators, and AddRadioItem() for all 513 // other items even if an item is not actually a radio item. 514 if (need_separator) { 515 model_->AddSeparator(); 516 } 517 model_->AddRadioItem(COMMAND_ID_CUSTOMIZE_LANGUAGE, dummy_label, 518 0 /* dummy */); 519 } 520 } 521 522 bool InputMethodMenu::IndexIsInInputMethodList(int index) const { 523 DCHECK_GE(index, 0); 524 DCHECK(model_.get()); 525 if (index >= model_->GetItemCount()) { 526 return false; 527 } 528 529 return ((model_->GetTypeAt(index) == ui::MenuModel::TYPE_RADIO) && 530 (model_->GetCommandIdAt(index) == COMMAND_ID_INPUT_METHODS) && 531 input_method_descriptors_.get() && 532 (index < static_cast<int>(input_method_descriptors_->size()))); 533 } 534 535 bool InputMethodMenu::GetPropertyIndex(int index, int* property_index) const { 536 DCHECK_GE(index, 0); 537 DCHECK(property_index); 538 DCHECK(model_.get()); 539 if (index >= model_->GetItemCount()) { 540 return false; 541 } 542 543 if ((model_->GetTypeAt(index) == ui::MenuModel::TYPE_RADIO) && 544 (model_->GetCommandIdAt(index) == COMMAND_ID_IME_PROPERTIES)) { 545 const int tmp_property_index = model_->GetGroupIdAt(index); 546 const ImePropertyList& property_list 547 = CrosLibrary::Get()->GetInputMethodLibrary()->current_ime_properties(); 548 if (tmp_property_index < static_cast<int>(property_list.size())) { 549 *property_index = tmp_property_index; 550 return true; 551 } 552 } 553 return false; 554 } 555 556 bool InputMethodMenu::IndexPointsToConfigureImeMenuItem(int index) const { 557 DCHECK_GE(index, 0); 558 DCHECK(model_.get()); 559 if (index >= model_->GetItemCount()) { 560 return false; 561 } 562 563 return ((model_->GetTypeAt(index) == ui::MenuModel::TYPE_RADIO) && 564 (model_->GetCommandIdAt(index) == COMMAND_ID_CUSTOMIZE_LANGUAGE)); 565 } 566 567 std::wstring InputMethodMenu::GetTextForIndicator( 568 const InputMethodDescriptor& input_method) { 569 // For the status area, we use two-letter, upper-case language code like 570 // "US" and "JP". 571 std::wstring text; 572 573 // Check special cases first. 574 for (size_t i = 0; i < kMappingFromIdToIndicatorTextLen; ++i) { 575 if (kMappingFromIdToIndicatorText[i].input_method_id == input_method.id) { 576 text = UTF8ToWide(kMappingFromIdToIndicatorText[i].indicator_text); 577 break; 578 } 579 } 580 581 // Display the keyboard layout name when using a keyboard layout. 582 if (text.empty() && input_method::IsKeyboardLayout(input_method.id)) { 583 const size_t kMaxKeyboardLayoutNameLen = 2; 584 const std::wstring keyboard_layout = UTF8ToWide( 585 input_method::GetKeyboardLayoutName(input_method.id)); 586 text = StringToUpperASCII(keyboard_layout).substr( 587 0, kMaxKeyboardLayoutNameLen); 588 } 589 590 // TODO(yusukes): Some languages have two or more input methods. For example, 591 // Thai has 3, Vietnamese has 4. If these input methods could be activated at 592 // the same time, we should do either of the following: 593 // (1) Add mappings to |kMappingFromIdToIndicatorText| 594 // (2) Add suffix (1, 2, ...) to |text| when ambiguous. 595 596 if (text.empty()) { 597 const size_t kMaxLanguageNameLen = 2; 598 std::string language_code = 599 input_method::GetLanguageCodeFromDescriptor(input_method); 600 601 // Use "CN" for simplified Chinese and "TW" for traditonal Chinese, 602 // rather than "ZH". 603 if (StartsWithASCII(language_code, "zh-", false)) { 604 std::vector<std::string> portions; 605 base::SplitString(language_code, '-', &portions); 606 if (portions.size() >= 2 && !portions[1].empty()) { 607 language_code = portions[1]; 608 } 609 } 610 611 text = StringToUpperASCII(UTF8ToWide(language_code)).substr( 612 0, kMaxLanguageNameLen); 613 } 614 DCHECK(!text.empty()); 615 return text; 616 } 617 618 std::wstring InputMethodMenu::GetTextForMenu( 619 const InputMethodDescriptor& input_method) { 620 // We don't show language here. Name of keyboard layout or input method 621 // usually imply (or explicitly include) its language. 622 623 // Special case for Dutch, French and German: these languages have multiple 624 // keyboard layouts and share the same laout of keyboard (Belgian). We need to 625 // show explicitly the language for the layout. 626 // For Arabic and Hindi: they share "Standard Input Method". 627 const std::string language_code 628 = input_method::GetLanguageCodeFromDescriptor(input_method); 629 std::wstring text; 630 if (language_code == "ar" || 631 language_code == "hi" || 632 language_code == "nl" || 633 language_code == "fr" || 634 language_code == "de") { 635 text = GetLanguageName(language_code) + L" - "; 636 } 637 text += input_method::GetString(input_method.display_name, input_method.id); 638 639 DCHECK(!text.empty()); 640 return text; 641 } 642 643 void InputMethodMenu::RegisterPrefs(PrefService* local_state) { 644 local_state->RegisterStringPref(language_prefs::kPreferredKeyboardLayout, ""); 645 } 646 647 void InputMethodMenu::Observe(NotificationType type, 648 const NotificationSource& source, 649 const NotificationDetails& details) { 650 if (type == NotificationType::LOGIN_USER_CHANGED) { 651 // When a user logs in, we should remove |this| object from the observer 652 // list so that PreferenceUpdateNeeded() does not update the local state 653 // anymore. 654 CrosLibrary::Get()->GetInputMethodLibrary()->RemoveObserver(this); 655 } 656 } 657 658 void InputMethodMenu::SetMinimumWidth(int width) { 659 // On the OOBE network selection screen, fixed width menu would be preferable. 660 minimum_input_method_menu_width_ = width; 661 } 662 663 } // namespace chromeos 664