Home | History | Annotate | Download | only in status
      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