blob: a49b55f7386b907970b7923fb3e933a039873c14 [file] [log] [blame]
// Copyright (c) 2012 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/input_method/candidate_window_controller_impl.h"
#include <string>
#include <vector>
#include "ash/shell.h"
#include "ash/shell_window_ids.h"
#include "ash/wm/window_animations.h"
#include "base/logging.h"
#include "base/memory/scoped_ptr.h"
#include "base/observer_list.h"
#include "chrome/browser/chromeos/input_method/candidate_window_view.h"
#include "chrome/browser/chromeos/input_method/delayable_widget.h"
#include "chrome/browser/chromeos/input_method/infolist_window_view.h"
#include "chrome/browser/chromeos/input_method/mode_indicator_controller.h"
#include "ui/views/widget/widget.h"
namespace chromeos {
namespace input_method {
namespace {
// The milliseconds of the delay to show the infolist window.
const int kInfolistShowDelayMilliSeconds = 500;
// The milliseconds of the delay to hide the infolist window.
const int kInfolistHideDelayMilliSeconds = 500;
} // namespace
CandidateWindowControllerImpl::CandidateWindowControllerImpl()
: candidate_window_view_(NULL),
latest_infolist_focused_index_(InfolistWindowView::InvalidFocusIndex()) {
IBusBridge::Get()->SetCandidateWindowHandler(this);
CreateView();
}
CandidateWindowControllerImpl::~CandidateWindowControllerImpl() {
IBusBridge::Get()->SetCandidateWindowHandler(NULL);
candidate_window_view_->RemoveObserver(this);
}
void CandidateWindowControllerImpl::CreateView() {
// Create a non-decorated frame.
frame_.reset(new views::Widget);
// The size is initially zero.
views::Widget::InitParams params(views::Widget::InitParams::TYPE_POPUP);
// |frame_| and |infolist_window_| are owned by controller impl so
// they should use WIDGET_OWNS_NATIVE_WIDGET ownership.
params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
// Show the candidate window always on top
params.parent = ash::Shell::GetContainer(
ash::Shell::GetTargetRootWindow(),
ash::internal::kShellWindowId_InputMethodContainer);
frame_->Init(params);
views::corewm::SetWindowVisibilityAnimationType(
frame_->GetNativeView(),
views::corewm::WINDOW_VISIBILITY_ANIMATION_TYPE_FADE);
// Create the candidate window.
candidate_window_view_ = new CandidateWindowView(frame_.get());
candidate_window_view_->Init();
candidate_window_view_->AddObserver(this);
frame_->SetContentsView(candidate_window_view_);
// Create the infolist window.
infolist_window_.reset(new DelayableWidget);
infolist_window_->Init(params);
views::corewm::SetWindowVisibilityAnimationType(
infolist_window_->GetNativeView(),
views::corewm::WINDOW_VISIBILITY_ANIMATION_TYPE_FADE);
InfolistWindowView* infolist_view = new InfolistWindowView;
infolist_view->Init();
infolist_window_->SetContentsView(infolist_view);
// Create the mode indicator controller.
mode_indicator_controller_.reset(
new ModeIndicatorController(InputMethodManager::Get()));
}
void CandidateWindowControllerImpl::Hide() {
// To hide the candidate window we have to call HideLookupTable and
// HideAuxiliaryText. Without calling HideAuxiliaryText the
// auxiliary text area will remain.
candidate_window_view_->HideLookupTable();
candidate_window_view_->HideAuxiliaryText();
infolist_window_->Hide();
}
void CandidateWindowControllerImpl::SetCursorBounds(
const gfx::Rect& cursor_bounds,
const gfx::Rect& composition_head) {
// A workaround for http://crosbug.com/6460. We should ignore very short Y
// move to prevent the window from shaking up and down.
const int kKeepPositionThreshold = 2; // px
const gfx::Rect& last_bounds =
candidate_window_view_->cursor_bounds();
const int delta_y = abs(last_bounds.y() - cursor_bounds.y());
if ((last_bounds.x() == cursor_bounds.x()) &&
(delta_y <= kKeepPositionThreshold)) {
DVLOG(1) << "Ignored set_cursor_bounds signal to prevent window shake";
return;
}
// Remember the cursor bounds.
candidate_window_view_->set_cursor_bounds(cursor_bounds);
candidate_window_view_->set_composition_head_bounds(composition_head);
// Move the window per the cursor bounds.
candidate_window_view_->ResizeAndMoveParentFrame();
UpdateInfolistBounds();
// Mode indicator controller also needs the cursor bounds.
mode_indicator_controller_->SetCursorBounds(cursor_bounds);
}
void CandidateWindowControllerImpl::UpdateAuxiliaryText(
const std::string& utf8_text,
bool visible) {
// If it's not visible, hide the auxiliary text and return.
if (!visible) {
candidate_window_view_->HideAuxiliaryText();
return;
}
candidate_window_view_->UpdateAuxiliaryText(utf8_text);
candidate_window_view_->ShowAuxiliaryText();
}
void CandidateWindowControllerImpl::FocusStateChanged(bool is_focused) {
mode_indicator_controller_->FocusStateChanged(is_focused);
}
// static
void CandidateWindowControllerImpl::ConvertLookupTableToInfolistEntry(
const CandidateWindow& candidate_window,
std::vector<InfolistWindowView::Entry>* infolist_entries,
size_t* focused_index) {
DCHECK(focused_index);
DCHECK(infolist_entries);
*focused_index = InfolistWindowView::InvalidFocusIndex();
infolist_entries->clear();
const size_t cursor_index_in_page =
candidate_window.cursor_position() % candidate_window.page_size();
for (size_t i = 0; i < candidate_window.candidates().size(); ++i) {
const CandidateWindow::Entry& ibus_entry =
candidate_window.candidates()[i];
if (ibus_entry.description_title.empty() &&
ibus_entry.description_body.empty())
continue;
InfolistWindowView::Entry entry;
entry.title = ibus_entry.description_title;
entry.body = ibus_entry.description_body;
infolist_entries->push_back(entry);
if (i == cursor_index_in_page)
*focused_index = infolist_entries->size() - 1;
}
}
// static
bool CandidateWindowControllerImpl::ShouldUpdateInfolist(
const std::vector<InfolistWindowView::Entry>& old_entries,
size_t old_focused_index,
const std::vector<InfolistWindowView::Entry>& new_entries,
size_t new_focused_index) {
if (old_entries.empty() && new_entries.empty())
return false;
if (old_entries.size() != new_entries.size())
return true;
if (old_focused_index != new_focused_index)
return true;
for (size_t i = 0; i < old_entries.size(); ++i) {
if (old_entries[i].title != new_entries[i].title ||
old_entries[i].body != new_entries[i].body ) {
return true;
}
}
return false;
}
void CandidateWindowControllerImpl::UpdateLookupTable(
const CandidateWindow& candidate_window,
bool visible) {
// If it's not visible, hide the lookup table and return.
if (!visible) {
candidate_window_view_->HideLookupTable();
infolist_window_->Hide();
// TODO(nona): Introduce unittests for crbug.com/170036.
latest_infolist_entries_.clear();
return;
}
candidate_window_view_->UpdateCandidates(candidate_window);
candidate_window_view_->ShowLookupTable();
size_t focused_index = 0;
std::vector<InfolistWindowView::Entry> infolist_entries;
ConvertLookupTableToInfolistEntry(candidate_window, &infolist_entries,
&focused_index);
// If there is no infolist entry, just hide.
if (infolist_entries.empty()) {
infolist_window_->Hide();
return;
}
// If there is no change, just return.
if (!ShouldUpdateInfolist(latest_infolist_entries_,
latest_infolist_focused_index_,
infolist_entries,
focused_index)) {
return;
}
latest_infolist_entries_ = infolist_entries;
latest_infolist_focused_index_ = focused_index;
InfolistWindowView* view = static_cast<InfolistWindowView*>(
infolist_window_->GetContentsView());
if (!view) {
DLOG(ERROR) << "Contents View is not InfolistWindowView.";
return;
}
view->Relayout(infolist_entries, focused_index);
UpdateInfolistBounds();
if (focused_index < infolist_entries.size())
infolist_window_->DelayShow(kInfolistShowDelayMilliSeconds);
else
infolist_window_->DelayHide(kInfolistHideDelayMilliSeconds);
}
void CandidateWindowControllerImpl::UpdateInfolistBounds() {
InfolistWindowView* view = static_cast<InfolistWindowView*>(
infolist_window_->GetContentsView());
if (!view)
return;
const gfx::Rect current_bounds =
infolist_window_->GetClientAreaBoundsInScreen();
gfx::Rect new_bounds;
new_bounds.set_size(view->GetPreferredSize());
new_bounds.set_origin(GetInfolistWindowPosition(
frame_->GetClientAreaBoundsInScreen(),
ash::Shell::GetScreen()->GetDisplayNearestWindow(
infolist_window_->GetNativeView()).work_area(),
new_bounds.size()));
if (current_bounds != new_bounds)
infolist_window_->SetBounds(new_bounds);
}
void CandidateWindowControllerImpl::UpdatePreeditText(
const std::string& utf8_text, unsigned int cursor, bool visible) {
// If it's not visible, hide the preedit text and return.
if (!visible || utf8_text.empty()) {
candidate_window_view_->HidePreeditText();
return;
}
candidate_window_view_->UpdatePreeditText(utf8_text);
candidate_window_view_->ShowPreeditText();
}
void CandidateWindowControllerImpl::OnCandidateCommitted(int index) {
FOR_EACH_OBSERVER(CandidateWindowController::Observer, observers_,
CandidateClicked(index));
}
void CandidateWindowControllerImpl::OnCandidateWindowOpened() {
FOR_EACH_OBSERVER(CandidateWindowController::Observer, observers_,
CandidateWindowOpened());
}
void CandidateWindowControllerImpl::OnCandidateWindowClosed() {
FOR_EACH_OBSERVER(CandidateWindowController::Observer, observers_,
CandidateWindowClosed());
}
void CandidateWindowControllerImpl::AddObserver(
CandidateWindowController::Observer* observer) {
observers_.AddObserver(observer);
}
void CandidateWindowControllerImpl::RemoveObserver(
CandidateWindowController::Observer* observer) {
observers_.RemoveObserver(observer);
}
// static
gfx::Point CandidateWindowControllerImpl::GetInfolistWindowPosition(
const gfx::Rect& candidate_window_view_rect,
const gfx::Rect& screen_rect,
const gfx::Size& infolist_window_size) {
gfx::Point result(candidate_window_view_rect.right(),
candidate_window_view_rect.y());
if (candidate_window_view_rect.right() + infolist_window_size.width() >
screen_rect.right())
result.set_x(candidate_window_view_rect.x() - infolist_window_size.width());
if (candidate_window_view_rect.y() + infolist_window_size.height() >
screen_rect.bottom())
result.set_y(screen_rect.bottom() - infolist_window_size.height());
return result;
}
} // namespace input_method
} // namespace chromeos