chromium/chrome/browser/ui/ash/input_method/ime_controller_client_impl.cc

// Copyright 2017 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "chrome/browser/ui/ash/input_method/ime_controller_client_impl.h"

#include <memory>
#include <vector>

#include "base/check_op.h"
#include "base/strings/utf_string_conversions.h"
#include "ui/base/ime/ash/extension_ime_util.h"
#include "ui/base/ime/ash/ime_bridge.h"
#include "ui/base/ime/ash/ime_keyboard.h"
#include "ui/base/ime/ash/input_method_descriptor.h"
#include "ui/base/ime/ash/input_method_util.h"

namespace {

using ::ash::input_method::InputMethodDescriptor;
using ::ash::input_method::InputMethodManager;
using ::ash::input_method::InputMethodUtil;
using ::ui::ime::InputMethodMenuManager;

ImeControllerClientImpl* g_ime_controller_client_instance = nullptr;

}  // namespace

ImeControllerClientImpl::ImeControllerClientImpl(InputMethodManager* manager)
    : input_method_manager_(manager) {
  DCHECK(input_method_manager_);
  input_method_manager_->AddObserver(this);
  input_method_manager_->AddImeMenuObserver(this);
  if (input_method_manager_->GetImeKeyboard())
    observation_.Observe(input_method_manager_->GetImeKeyboard());
  InputMethodMenuManager::GetInstance()->AddObserver(this);

  // This does not need to send the initial state to ash because that happens
  // via observers when the InputMethodManager initializes its list of IMEs.

  DCHECK(!g_ime_controller_client_instance);
  g_ime_controller_client_instance = this;
}

ImeControllerClientImpl::~ImeControllerClientImpl() {
  ime_controller_->SetClient(nullptr);
  DCHECK_EQ(this, g_ime_controller_client_instance);
  g_ime_controller_client_instance = nullptr;

  InputMethodMenuManager::GetInstance()->RemoveObserver(this);
  input_method_manager_->RemoveImeMenuObserver(this);
  input_method_manager_->RemoveObserver(this);
}

void ImeControllerClientImpl::Init() {
  // Connect to the controller in ash.
  ime_controller_ = ash::ImeController::Get();
  DCHECK(ime_controller_);
  InitAndSetClient();
}

// static
ImeControllerClientImpl* ImeControllerClientImpl::Get() {
  return g_ime_controller_client_instance;
}

void ImeControllerClientImpl::SetImesManagedByPolicy(bool managed) {
  ime_controller_->SetImesManagedByPolicy(managed);
}

// ash::mojom::ImeControllerClient:
void ImeControllerClientImpl::SwitchToNextIme() {
  InputMethodManager::State* state =
      input_method_manager_->GetActiveIMEState().get();
  if (state)
    state->SwitchToNextInputMethod();
}

void ImeControllerClientImpl::SwitchToLastUsedIme() {
  InputMethodManager::State* state =
      input_method_manager_->GetActiveIMEState().get();
  if (state)
    state->SwitchToLastUsedInputMethod();
}

void ImeControllerClientImpl::SwitchImeById(const std::string& id,
                                            bool show_message) {
  InputMethodManager::State* state =
      input_method_manager_->GetActiveIMEState().get();
  if (state)
    state->ChangeInputMethod(id, show_message);
}

void ImeControllerClientImpl::ActivateImeMenuItem(const std::string& key) {
  input_method_manager_->ActivateInputMethodMenuItem(key);
}

void ImeControllerClientImpl::SetCapsLockEnabled(bool caps_enabled) {
  ash::input_method::ImeKeyboard* keyboard =
      InputMethodManager::Get()->GetImeKeyboard();
  if (keyboard)
    keyboard->SetCapsLockEnabled(caps_enabled);
}

void ImeControllerClientImpl::OverrideKeyboardKeyset(
    ash::input_method::ImeKeyset keyset,
    OverrideKeyboardKeysetCallback callback) {
  input_method_manager_->OverrideKeyboardKeyset(keyset);
  std::move(callback).Run();
}

void ImeControllerClientImpl::ShowModeIndicator() {
  // Get the short name of the changed input method (e.g. US, JA, etc.)
  const InputMethodDescriptor descriptor =
      input_method_manager_->GetActiveIMEState()->GetCurrentInputMethod();
  const std::u16string short_name = descriptor.GetIndicator();

  ash::IMECandidateWindowHandlerInterface* cw_handler =
      ash::IMEBridge::Get()->GetCandidateWindowHandler();
  if (!cw_handler)
    return;

  gfx::Rect anchor_bounds = cw_handler->GetCursorBounds();
  if (anchor_bounds == gfx::Rect()) {
    // TODO(shuchen): Show the mode indicator in the right bottom of the
    // display when the launch bar is hidden and the focus is out.  To
    // implement it, we should consider to use message center or system
    // notification.  Note, launch bar can be vertical and can be placed
    // right/left side of display.
    return;
  }

  // Call to Ash to show the mode indicator view with the given anchor
  // bounds and short name.
  ime_controller_->ShowModeIndicator(anchor_bounds, short_name);
}

// ash::input_method::InputMethodManager::Observer:
void ImeControllerClientImpl::InputMethodChanged(InputMethodManager* manager,
                                                 Profile* profile,
                                                 bool show_message) {
  RefreshIme();
  if (show_message)
    ShowModeIndicator();
}

// ash::input_method::InputMethodManager::ImeMenuObserver:
void ImeControllerClientImpl::ImeMenuActivationChanged(bool is_active) {
  ime_controller_->ShowImeMenuOnShelf(is_active);
}

void ImeControllerClientImpl::ImeMenuListChanged() {
  RefreshIme();
}

void ImeControllerClientImpl::ImeMenuItemsChanged(
    const std::string& engine_id,
    const std::vector<InputMethodManager::MenuItem>& items) {}

// ui::ime::InputMethodMenuManager::Observer:
void ImeControllerClientImpl::InputMethodMenuItemChanged(
    InputMethodMenuManager* manager) {
  RefreshIme();
}

// ash::input_method::ImeKeyboard::Observer:
void ImeControllerClientImpl::OnCapsLockChanged(bool enabled) {
  ime_controller_->UpdateCapsLockState(enabled);
}

void ImeControllerClientImpl::OnLayoutChanging(const std::string& layout_name) {
  ime_controller_->OnKeyboardLayoutNameChanged(layout_name);
}

void ImeControllerClientImpl::InitAndSetClient() {
  ime_controller_->SetClient(this);

  // Now that the client is set, flush state from observed objects to
  // the ImeController, now that it will hear it.
  input_method_manager_->NotifyObserversImeExtraInputStateChange();
  if (const ash::input_method::ImeKeyboard* keyboard =
          input_method_manager_->GetImeKeyboard()) {
    ime_controller_->OnKeyboardLayoutNameChanged(
        keyboard->GetCurrentKeyboardLayoutName());
  }
}

ash::ImeInfo ImeControllerClientImpl::GetAshImeInfo(
    const InputMethodDescriptor& ime) const {
  InputMethodUtil* util = input_method_manager_->GetInputMethodUtil();
  ash::ImeInfo info;
  info.id = ime.id();
  info.name = util->GetInputMethodLongName(ime);
  info.short_name = ime.GetIndicator();
  info.third_party = ash::extension_ime_util::IsExtensionIME(ime.id());
  return info;
}

void ImeControllerClientImpl::RefreshIme() {
  InputMethodManager::State* state =
      input_method_manager_->GetActiveIMEState().get();
  if (!state) {
    const std::string empty_ime_id;
    ime_controller_->RefreshIme(empty_ime_id, std::vector<ash::ImeInfo>(),
                                std::vector<ash::ImeMenuItem>());
    return;
  }

  const std::string current_ime_id = state->GetCurrentInputMethod().id();

  std::vector<ash::ImeInfo> available_imes;
  std::vector<InputMethodDescriptor> enabled_ime_descriptors =
      state->GetEnabledInputMethodsSortedByLocalizedDisplayNames();
  for (const InputMethodDescriptor& descriptor : enabled_ime_descriptors) {
    ash::ImeInfo info = GetAshImeInfo(descriptor);
    available_imes.push_back(std::move(info));
  }

  std::vector<ash::ImeMenuItem> ash_menu_items;
  ui::ime::InputMethodMenuItemList menu_list =
      ui::ime::InputMethodMenuManager::GetInstance()
          ->GetCurrentInputMethodMenuItemList();
  for (const ui::ime::InputMethodMenuItem& menu_item : menu_list) {
    ash::ImeMenuItem ash_item;
    ash_item.key = menu_item.key;
    ash_item.label = base::UTF8ToUTF16(menu_item.label);
    ash_item.checked = menu_item.is_selection_item_checked;
    ash_menu_items.push_back(std::move(ash_item));
  }
  ime_controller_->RefreshIme(current_ime_id, std::move(available_imes),
                              std::move(ash_menu_items));
}

void ImeControllerClientImpl::OnExtraInputEnabledStateChange(
    bool is_extra_input_options_enabled,
    bool is_emoji_enabled,
    bool is_handwriting_enabled,
    bool is_voice_enabled) {
  if (ime_controller_) {
    ime_controller_->SetExtraInputOptionsEnabledState(
        is_extra_input_options_enabled, is_emoji_enabled,
        is_handwriting_enabled, is_voice_enabled);
  }
}