普通文本  |  664行  |  23.68 KB

// Copyright (c) 2011 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.

#include "chrome/browser/chromeos/status/input_method_menu.h"

#include <string>
#include <vector>

#include "base/string_split.h"
#include "base/string_util.h"
#include "base/time.h"
#include "base/utf_string_conversions.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/chromeos/cros/cros_library.h"
#include "chrome/browser/chromeos/input_method/input_method_util.h"
#include "chrome/browser/chromeos/language_preferences.h"
#include "chrome/browser/metrics/user_metrics.h"
#include "chrome/browser/prefs/pref_service.h"
#include "chrome/common/pref_names.h"
#include "content/common/notification_service.h"
#include "grit/generated_resources.h"
#include "grit/theme_resources.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/resource/resource_bundle.h"

// The language menu consists of 3 parts (in this order):
//
//   (1) input method names. The size of the list is always >= 1.
//   (2) input method properties. This list might be empty.
//   (3) "Customize language and input..." button.
//
// Example of the menu (Japanese):
//
// ============================== (border of the popup window)
// [ ] English                    (|index| in the following functions is 0)
// [*] Japanese
// [ ] Chinese (Simplified)
// ------------------------------ (separator)
// [*] Hiragana                   (index = 5, The property has 2 radio groups)
// [ ] Katakana
// [ ] HalfWidthKatakana
// [*] Roman
// [ ] Kana
// ------------------------------ (separator)
// Customize language and input...(index = 11)
// ============================== (border of the popup window)
//
// Example of the menu (Simplified Chinese):
//
// ============================== (border of the popup window)
// [ ] English
// [ ] Japanese
// [*] Chinese (Simplified)
// ------------------------------ (separator)
// Switch to full letter mode     (The property has 2 command buttons)
// Switch to half punctuation mode
// ------------------------------ (separator)
// Customize language and input...
// ============================== (border of the popup window)
//

namespace {

// Constants to specify the type of items in |model_|.
enum {
  COMMAND_ID_INPUT_METHODS = 0,  // English, Chinese, Japanese, Arabic, ...
  COMMAND_ID_IME_PROPERTIES,  // Hiragana, Katakana, ...
  COMMAND_ID_CUSTOMIZE_LANGUAGE,  // "Customize language and input..." button.
};

// A group ID for IME properties starts from 0. We use the huge value for the
// input method list to avoid conflict.
const int kRadioGroupLanguage = 1 << 16;
const int kRadioGroupNone = -1;

// A mapping from an input method id to a string for the language indicator. The
// mapping is necessary since some input methods belong to the same language.
// For example, both "xkb:us::eng" and "xkb:us:dvorak:eng" are for US English.
const struct {
  const char* input_method_id;
  const char* indicator_text;
} kMappingFromIdToIndicatorText[] = {
  // To distinguish from "xkb:us::eng"
  { "xkb:us:altgr-intl:eng", "EXTD" },
  { "xkb:us:dvorak:eng", "DV" },
  { "xkb:us:intl:eng", "INTL" },
  { "xkb:us:colemak:eng", "CO" },
  { "xkb:de:neo:ger", "NEO" },
  // To distinguish from "xkb:gb::eng"
  { "xkb:gb:dvorak:eng", "DV" },
  // To distinguish from "xkb:jp::jpn"
  { "mozc", "\xe3\x81\x82" },  // U+3042, Japanese Hiragana letter A in UTF-8.
  { "mozc-dv", "\xe3\x81\x82" },
  { "mozc-jp", "\xe3\x81\x82" },
  // For simplified Chinese input methods
  { "pinyin", "\xe6\x8b\xbc" },  // U+62FC
  // For traditional Chinese input methods
  { "mozc-chewing", "\xe9\x85\xb7" },  // U+9177
  { "m17n:zh:cangjie", "\xe5\x80\x89" },  // U+5009
  { "m17n:zh:quick", "\xe9\x80\x9f" },  // U+901F
  // For Hangul input method.
  { "hangul", "\xed\x95\x9c" },  // U+D55C
};
const size_t kMappingFromIdToIndicatorTextLen =
    ARRAYSIZE_UNSAFE(kMappingFromIdToIndicatorText);

// Returns the language name for the given |language_code|.
std::wstring GetLanguageName(const std::string& language_code) {
  const string16 language_name = l10n_util::GetDisplayNameForLocale(
      language_code, g_browser_process->GetApplicationLocale(), true);
  return UTF16ToWide(language_name);
}

}  // namespace

namespace chromeos {

////////////////////////////////////////////////////////////////////////////////
// InputMethodMenu

InputMethodMenu::InputMethodMenu(PrefService* pref_service,
                                 StatusAreaHost::ScreenMode screen_mode,
                                 bool for_out_of_box_experience_dialog)
    : input_method_descriptors_(CrosLibrary::Get()->GetInputMethodLibrary()->
                                GetActiveInputMethods()),
      model_(NULL),
      // Be aware that the constructor of |input_method_menu_| calls
      // GetItemCount() in this class. Therefore, GetItemCount() have to return
      // 0 when |model_| is NULL.
      ALLOW_THIS_IN_INITIALIZER_LIST(input_method_menu_(this)),
      minimum_input_method_menu_width_(0),
      pref_service_(pref_service),
      screen_mode_(screen_mode),
      for_out_of_box_experience_dialog_(for_out_of_box_experience_dialog) {
  DCHECK(input_method_descriptors_.get() &&
         !input_method_descriptors_->empty());

  // Sync current and previous input methods on Chrome prefs with ibus-daemon.
  if (pref_service_ && (screen_mode_ == StatusAreaHost::kBrowserMode)) {
    previous_input_method_pref_.Init(
        prefs::kLanguagePreviousInputMethod, pref_service, this);
    current_input_method_pref_.Init(
        prefs::kLanguageCurrentInputMethod, pref_service, this);
  }

  InputMethodLibrary* library = CrosLibrary::Get()->GetInputMethodLibrary();
  library->AddObserver(this);  // FirstObserverIsAdded() might be called back.

  if (screen_mode_ == StatusAreaHost::kLoginMode) {
    // This button is for the login screen.
    registrar_.Add(this,
                   NotificationType::LOGIN_USER_CHANGED,
                   NotificationService::AllSources());
  }
}

InputMethodMenu::~InputMethodMenu() {
  // RemoveObserver() is no-op if |this| object is already removed from the
  // observer list.
  CrosLibrary::Get()->GetInputMethodLibrary()->RemoveObserver(this);
}

////////////////////////////////////////////////////////////////////////////////
// ui::MenuModel implementation:

int InputMethodMenu::GetCommandIdAt(int index) const {
  return index;
}

bool InputMethodMenu::IsItemDynamicAt(int index) const {
  // Menu content for the language button could change time by time.
  return true;
}

bool InputMethodMenu::GetAcceleratorAt(
    int index, ui::Accelerator* accelerator) const {
  // Views for Chromium OS does not support accelerators yet.
  return false;
}

bool InputMethodMenu::IsItemCheckedAt(int index) const {
  DCHECK_GE(index, 0);
  DCHECK(input_method_descriptors_.get());

  if (IndexIsInInputMethodList(index)) {
    const InputMethodDescriptor& input_method
        = input_method_descriptors_->at(index);
    return input_method == CrosLibrary::Get()->GetInputMethodLibrary()->
          current_input_method();
  }

  if (GetPropertyIndex(index, &index)) {
    const ImePropertyList& property_list
        = CrosLibrary::Get()->GetInputMethodLibrary()->current_ime_properties();
    return property_list.at(index).is_selection_item_checked;
  }

  // Separator(s) or the "Customize language and input..." button.
  return false;
}

int InputMethodMenu::GetGroupIdAt(int index) const {
  DCHECK_GE(index, 0);

  if (IndexIsInInputMethodList(index)) {
    return for_out_of_box_experience_dialog_ ?
        kRadioGroupNone : kRadioGroupLanguage;
  }

  if (GetPropertyIndex(index, &index)) {
    const ImePropertyList& property_list
        = CrosLibrary::Get()->GetInputMethodLibrary()->current_ime_properties();
    return property_list.at(index).selection_item_id;
  }

  return kRadioGroupNone;
}

bool InputMethodMenu::HasIcons() const  {
  // We don't support icons on Chrome OS.
  return false;
}

bool InputMethodMenu::GetIconAt(int index, SkBitmap* icon) {
  return false;
}

ui::ButtonMenuItemModel* InputMethodMenu::GetButtonMenuItemAt(
    int index) const {
  return NULL;
}

bool InputMethodMenu::IsEnabledAt(int index) const {
  // Just return true so all input method names and input method propertie names
  // could be clicked.
  return true;
}

ui::MenuModel* InputMethodMenu::GetSubmenuModelAt(int index) const {
  // We don't use nested menus.
  return NULL;
}

void InputMethodMenu::HighlightChangedTo(int index) {
  // Views for Chromium OS does not support this interface yet.
}

void InputMethodMenu::MenuWillShow() {
  // Views for Chromium OS does not support this interface yet.
}

void InputMethodMenu::SetMenuModelDelegate(ui::MenuModelDelegate* delegate) {
  // Not needed for current usage.
}

int InputMethodMenu::GetItemCount() const {
  if (!model_.get()) {
    // Model is not constructed yet. This means that
    // InputMethodMenu is being constructed. Return zero.
    return 0;
  }
  return model_->GetItemCount();
}

ui::MenuModel::ItemType InputMethodMenu::GetTypeAt(int index) const {
  DCHECK_GE(index, 0);

  if (IndexPointsToConfigureImeMenuItem(index)) {
    return ui::MenuModel::TYPE_COMMAND;  // "Customize language and input"
  }

  if (IndexIsInInputMethodList(index)) {
    return for_out_of_box_experience_dialog_ ?
        ui::MenuModel::TYPE_COMMAND : ui::MenuModel::TYPE_RADIO;
  }

  if (GetPropertyIndex(index, &index)) {
    const ImePropertyList& property_list
        = CrosLibrary::Get()->GetInputMethodLibrary()->current_ime_properties();
    if (property_list.at(index).is_selection_item) {
      return ui::MenuModel::TYPE_RADIO;
    }
    return ui::MenuModel::TYPE_COMMAND;
  }

  return ui::MenuModel::TYPE_SEPARATOR;
}

string16 InputMethodMenu::GetLabelAt(int index) const {
  DCHECK_GE(index, 0);
  DCHECK(input_method_descriptors_.get());

  // We use IDS_OPTIONS_SETTINGS_LANGUAGES_CUSTOMIZE here as the button
  // opens the same dialog that is opened from the main options dialog.
  if (IndexPointsToConfigureImeMenuItem(index)) {
    return l10n_util::GetStringUTF16(IDS_OPTIONS_SETTINGS_LANGUAGES_CUSTOMIZE);
  }

  std::wstring name;
  if (IndexIsInInputMethodList(index)) {
    name = GetTextForMenu(input_method_descriptors_->at(index));
  } else if (GetPropertyIndex(index, &index)) {
    InputMethodLibrary* library = CrosLibrary::Get()->GetInputMethodLibrary();
    const ImePropertyList& property_list = library->current_ime_properties();
    const std::string& input_method_id = library->current_input_method().id;
    return input_method::GetStringUTF16(
        property_list.at(index).label, input_method_id);
  }

  return WideToUTF16(name);
}

void InputMethodMenu::ActivatedAt(int index) {
  DCHECK_GE(index, 0);
  DCHECK(input_method_descriptors_.get());

  if (IndexPointsToConfigureImeMenuItem(index)) {
    OpenConfigUI();
    return;
  }

  if (IndexIsInInputMethodList(index)) {
    // Inter-IME switching.
    const InputMethodDescriptor& input_method
        = input_method_descriptors_->at(index);
    CrosLibrary::Get()->GetInputMethodLibrary()->ChangeInputMethod(
        input_method.id);
    UserMetrics::RecordAction(
        UserMetricsAction("LanguageMenuButton_InputMethodChanged"));
    return;
  }

  if (GetPropertyIndex(index, &index)) {
    // Intra-IME switching (e.g. Japanese-Hiragana to Japanese-Katakana).
    const ImePropertyList& property_list
        = CrosLibrary::Get()->GetInputMethodLibrary()->current_ime_properties();
    const std::string key = property_list.at(index).key;
    if (property_list.at(index).is_selection_item) {
      // Radio button is clicked.
      const int id = property_list.at(index).selection_item_id;
      // First, deactivate all other properties in the same radio group.
      for (int i = 0; i < static_cast<int>(property_list.size()); ++i) {
        if (i != index && id == property_list.at(i).selection_item_id) {
          CrosLibrary::Get()->GetInputMethodLibrary()->SetImePropertyActivated(
              property_list.at(i).key, false);
        }
      }
      // Then, activate the property clicked.
      CrosLibrary::Get()->GetInputMethodLibrary()->SetImePropertyActivated(
          key, true);
    } else {
      // Command button like "Switch to half punctuation mode" is clicked.
      // We can always use "Deactivate" for command buttons.
      CrosLibrary::Get()->GetInputMethodLibrary()->SetImePropertyActivated(
          key, false);
    }
    return;
  }

  LOG(ERROR) << "Unexpected index: " << index;
}

////////////////////////////////////////////////////////////////////////////////
// views::ViewMenuDelegate implementation:

void InputMethodMenu::RunMenu(
    views::View* unused_source, const gfx::Point& pt) {
  PrepareForMenuOpen();
  input_method_menu_.RunMenuAt(pt, views::Menu2::ALIGN_TOPRIGHT);
}

////////////////////////////////////////////////////////////////////////////////
// InputMethodLibrary::Observer implementation:

void InputMethodMenu::InputMethodChanged(
    InputMethodLibrary* obj,
    const InputMethodDescriptor& current_input_method,
    size_t num_active_input_methods) {
  UpdateUIFromInputMethod(current_input_method, num_active_input_methods);
}

void InputMethodMenu::PreferenceUpdateNeeded(
    InputMethodLibrary* obj,
    const InputMethodDescriptor& previous_input_method,
    const InputMethodDescriptor& current_input_method) {
  if (screen_mode_ == StatusAreaHost::kBrowserMode) {
    if (pref_service_) {  // make sure we're not in unit tests.
      // Sometimes (e.g. initial boot) |previous_input_method.id| is empty.
      previous_input_method_pref_.SetValue(previous_input_method.id);
      current_input_method_pref_.SetValue(current_input_method.id);
      pref_service_->ScheduleSavePersistentPrefs();
    }
  } else if (screen_mode_ == StatusAreaHost::kLoginMode) {
    if (g_browser_process && g_browser_process->local_state()) {
      g_browser_process->local_state()->SetString(
          language_prefs::kPreferredKeyboardLayout, current_input_method.id);
      g_browser_process->local_state()->SavePersistentPrefs();
    }
  }
}

void InputMethodMenu::PropertyListChanged(
    InputMethodLibrary* obj,
    const ImePropertyList& current_ime_properties) {
  // Usual order of notifications of input method change is:
  // 1. RegisterProperties(empty)
  // 2. RegisterProperties(list-of-new-properties)
  // 3. GlobalInputMethodChanged
  // However, due to the asynchronicity, we occasionally (but rarely) face to
  // 1. RegisterProperties(empty)
  // 2. GlobalInputMethodChanged
  // 3. RegisterProperties(list-of-new-properties)
  // this order. On this unusual case, we must rebuild the menu after the last
  // RegisterProperties. For the other cases, no rebuild is needed. Actually
  // it is better to be avoided. Otherwise users can sometimes observe the
  // awkward clear-then-register behavior.
  if (!current_ime_properties.empty()) {
    InputMethodLibrary* library = CrosLibrary::Get()->GetInputMethodLibrary();
    const InputMethodDescriptor& input_method = library->current_input_method();
    size_t num_active_input_methods = library->GetNumActiveInputMethods();
    UpdateUIFromInputMethod(input_method, num_active_input_methods);
  }
}

void InputMethodMenu::FirstObserverIsAdded(InputMethodLibrary* obj) {
  // NOTICE: Since this function might be called from the constructor of this
  // class, it's better to avoid calling virtual functions.

  if (pref_service_ && (screen_mode_ == StatusAreaHost::kBrowserMode)) {
    // Get the input method name in the Preferences file which was in use last
    // time, and switch to the method. We remember two input method names in the
    // preference so that the Control+space hot-key could work fine from the
    // beginning. InputMethodChanged() will be called soon and the indicator
    // will be updated.
    InputMethodLibrary* library = CrosLibrary::Get()->GetInputMethodLibrary();
    const std::string previous_input_method_id =
        previous_input_method_pref_.GetValue();
    if (!previous_input_method_id.empty()) {
      library->ChangeInputMethod(previous_input_method_id);
    }
    const std::string current_input_method_id =
        current_input_method_pref_.GetValue();
    if (!current_input_method_id.empty()) {
      library->ChangeInputMethod(current_input_method_id);
    }
  }
}

void InputMethodMenu::PrepareForMenuOpen() {
  UserMetrics::RecordAction(UserMetricsAction("LanguageMenuButton_Open"));
  PrepareMenu();
}

void InputMethodMenu::PrepareMenu() {
  input_method_descriptors_.reset(CrosLibrary::Get()->GetInputMethodLibrary()->
                                  GetActiveInputMethods());
  RebuildModel();
  input_method_menu_.Rebuild();
  if (minimum_input_method_menu_width_ > 0) {
    input_method_menu_.SetMinimumWidth(minimum_input_method_menu_width_);
  }
}

void InputMethodMenu::ActiveInputMethodsChanged(
    InputMethodLibrary* obj,
    const InputMethodDescriptor& current_input_method,
    size_t num_active_input_methods) {
  // Update the icon if active input methods are changed. See also
  // comments in UpdateUI() in input_method_menu_button.cc.
  UpdateUIFromInputMethod(current_input_method, num_active_input_methods);
}

void InputMethodMenu::UpdateUIFromInputMethod(
    const InputMethodDescriptor& input_method,
    size_t num_active_input_methods) {
  const std::wstring name = GetTextForIndicator(input_method);
  const std::wstring tooltip = GetTextForMenu(input_method);
  UpdateUI(input_method.id, name, tooltip, num_active_input_methods);
}

void InputMethodMenu::RebuildModel() {
  model_.reset(new ui::SimpleMenuModel(NULL));
  string16 dummy_label = UTF8ToUTF16("");
  // Indicates if separator's needed before each section.
  bool need_separator = false;

  if (!input_method_descriptors_->empty()) {
    // We "abuse" the command_id and group_id arguments of AddRadioItem method.
    // A COMMAND_ID_XXX enum value is passed as command_id, and array index of
    // |input_method_descriptors_| or |property_list| is passed as group_id.
    for (size_t i = 0; i < input_method_descriptors_->size(); ++i) {
      model_->AddRadioItem(COMMAND_ID_INPUT_METHODS, dummy_label, i);
    }

    need_separator = true;
  }

  const ImePropertyList& property_list
      = CrosLibrary::Get()->GetInputMethodLibrary()->current_ime_properties();
  if (!property_list.empty()) {
    if (need_separator) {
      model_->AddSeparator();
    }
    for (size_t i = 0; i < property_list.size(); ++i) {
      model_->AddRadioItem(COMMAND_ID_IME_PROPERTIES, dummy_label, i);
    }
    need_separator = true;
  }

  if (ShouldSupportConfigUI()) {
    // Note: We use AddSeparator() for separators, and AddRadioItem() for all
    // other items even if an item is not actually a radio item.
    if (need_separator) {
      model_->AddSeparator();
    }
    model_->AddRadioItem(COMMAND_ID_CUSTOMIZE_LANGUAGE, dummy_label,
                         0 /* dummy */);
  }
}

bool InputMethodMenu::IndexIsInInputMethodList(int index) const {
  DCHECK_GE(index, 0);
  DCHECK(model_.get());
  if (index >= model_->GetItemCount()) {
    return false;
  }

  return ((model_->GetTypeAt(index) == ui::MenuModel::TYPE_RADIO) &&
          (model_->GetCommandIdAt(index) == COMMAND_ID_INPUT_METHODS) &&
          input_method_descriptors_.get() &&
          (index < static_cast<int>(input_method_descriptors_->size())));
}

bool InputMethodMenu::GetPropertyIndex(int index, int* property_index) const {
  DCHECK_GE(index, 0);
  DCHECK(property_index);
  DCHECK(model_.get());
  if (index >= model_->GetItemCount()) {
    return false;
  }

  if ((model_->GetTypeAt(index) == ui::MenuModel::TYPE_RADIO) &&
      (model_->GetCommandIdAt(index) == COMMAND_ID_IME_PROPERTIES)) {
    const int tmp_property_index = model_->GetGroupIdAt(index);
    const ImePropertyList& property_list
        = CrosLibrary::Get()->GetInputMethodLibrary()->current_ime_properties();
    if (tmp_property_index < static_cast<int>(property_list.size())) {
      *property_index = tmp_property_index;
      return true;
    }
  }
  return false;
}

bool InputMethodMenu::IndexPointsToConfigureImeMenuItem(int index) const {
  DCHECK_GE(index, 0);
  DCHECK(model_.get());
  if (index >= model_->GetItemCount()) {
    return false;
  }

  return ((model_->GetTypeAt(index) == ui::MenuModel::TYPE_RADIO) &&
          (model_->GetCommandIdAt(index) == COMMAND_ID_CUSTOMIZE_LANGUAGE));
}

std::wstring InputMethodMenu::GetTextForIndicator(
    const InputMethodDescriptor& input_method) {
  // For the status area, we use two-letter, upper-case language code like
  // "US" and "JP".
  std::wstring text;

  // Check special cases first.
  for (size_t i = 0; i < kMappingFromIdToIndicatorTextLen; ++i) {
    if (kMappingFromIdToIndicatorText[i].input_method_id == input_method.id) {
      text = UTF8ToWide(kMappingFromIdToIndicatorText[i].indicator_text);
      break;
    }
  }

  // Display the keyboard layout name when using a keyboard layout.
  if (text.empty() && input_method::IsKeyboardLayout(input_method.id)) {
    const size_t kMaxKeyboardLayoutNameLen = 2;
    const std::wstring keyboard_layout = UTF8ToWide(
        input_method::GetKeyboardLayoutName(input_method.id));
    text = StringToUpperASCII(keyboard_layout).substr(
        0, kMaxKeyboardLayoutNameLen);
  }

  // TODO(yusukes): Some languages have two or more input methods. For example,
  // Thai has 3, Vietnamese has 4. If these input methods could be activated at
  // the same time, we should do either of the following:
  //   (1) Add mappings to |kMappingFromIdToIndicatorText|
  //   (2) Add suffix (1, 2, ...) to |text| when ambiguous.

  if (text.empty()) {
    const size_t kMaxLanguageNameLen = 2;
    std::string language_code =
        input_method::GetLanguageCodeFromDescriptor(input_method);

    // Use "CN" for simplified Chinese and "TW" for traditonal Chinese,
    // rather than "ZH".
    if (StartsWithASCII(language_code, "zh-", false)) {
      std::vector<std::string> portions;
      base::SplitString(language_code, '-', &portions);
      if (portions.size() >= 2 && !portions[1].empty()) {
        language_code = portions[1];
      }
    }

    text = StringToUpperASCII(UTF8ToWide(language_code)).substr(
        0, kMaxLanguageNameLen);
  }
  DCHECK(!text.empty());
  return text;
}

std::wstring InputMethodMenu::GetTextForMenu(
    const InputMethodDescriptor& input_method) {
  // We don't show language here.  Name of keyboard layout or input method
  // usually imply (or explicitly include) its language.

  // Special case for Dutch, French and German: these languages have multiple
  // keyboard layouts and share the same laout of keyboard (Belgian). We need to
  // show explicitly the language for the layout.
  // For Arabic and Hindi: they share "Standard Input Method".
  const std::string language_code
      = input_method::GetLanguageCodeFromDescriptor(input_method);
  std::wstring text;
  if (language_code == "ar" ||
      language_code == "hi" ||
      language_code == "nl" ||
      language_code == "fr" ||
      language_code == "de") {
    text = GetLanguageName(language_code) + L" - ";
  }
  text += input_method::GetString(input_method.display_name, input_method.id);

  DCHECK(!text.empty());
  return text;
}

void InputMethodMenu::RegisterPrefs(PrefService* local_state) {
  local_state->RegisterStringPref(language_prefs::kPreferredKeyboardLayout, "");
}

void InputMethodMenu::Observe(NotificationType type,
                              const NotificationSource& source,
                              const NotificationDetails& details) {
  if (type == NotificationType::LOGIN_USER_CHANGED) {
    // When a user logs in, we should remove |this| object from the observer
    // list so that PreferenceUpdateNeeded() does not update the local state
    // anymore.
    CrosLibrary::Get()->GetInputMethodLibrary()->RemoveObserver(this);
  }
}

void InputMethodMenu::SetMinimumWidth(int width) {
  // On the OOBE network selection screen, fixed width menu would be preferable.
  minimum_input_method_menu_width_ = width;
}

}  // namespace chromeos