Chromium Code Reviews| Index: ui/base/ime/android/cursor_anchor_info_controller.cc |
| diff --git a/ui/base/ime/android/cursor_anchor_info_controller.cc b/ui/base/ime/android/cursor_anchor_info_controller.cc |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..ca4488dabace7fad9cab7fe1149ef8b984140718 |
| --- /dev/null |
| +++ b/ui/base/ime/android/cursor_anchor_info_controller.cc |
| @@ -0,0 +1,329 @@ |
| +// Copyright (c) 2014 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 "ui/base/ime/android/cursor_anchor_info_controller.h" |
| + |
| +#include "base/android/build_info.h" |
| +#include "base/strings/string_piece.h" |
| +#include "base/strings/stringprintf.h" |
| +#include "cc/output/compositor_frame_metadata.h" |
| +#include "cc/output/viewport_selection_bound.h" |
| +#include "ui/base/ime/android/cursor_anchor_info_builder.h" |
| +#include "ui/base/ime/android/cursor_anchor_info_sender.h" |
| + |
| +namespace ui { |
| +namespace { |
| + |
| +class CursorAnchorInfoControllerImpl final |
| + : public CursorAnchorInfoController { |
| + public: |
| + CursorAnchorInfoControllerImpl( |
| + CursorAnchorInfoSender* sender, |
| + scoped_ptr<CursorAnchorInfoBuilder> builder); |
| + |
| + ~CursorAnchorInfoControllerImpl() override; |
| + |
| + void OnFocusedNodeChanged(bool is_editable_node) override; |
| + |
| + void OnTextInputStateChanged( |
| + const base::string16& text, |
| + const gfx::Range& selection_range, |
| + const gfx::Range& composition_range) override; |
| + |
| + void OnCompositionRangeChanged( |
| + const gfx::Range& range, |
| + const std::vector<gfx::Rect>& character_bounds) override; |
| + |
| + void OnFrameMetadataUpdated( |
| + const cc::CompositorFrameMetadata& frame_metadata, |
| + const gfx::Vector2d& view_offset) override; |
| + |
| + bool OnRequestCursorUpdates(uint32 cursor_update_mode) override; |
| + |
| + private: |
| + void UpdateBuilder(); |
| + void ScheduleUpdate(); |
| + bool HasPendingUpdate() const; |
| + void ClearPendingUpdate(); |
| + void SetMonitorMode(bool enabled); |
| + |
| + // Doesn't own. |
| + CursorAnchorInfoSender* cursor_anchor_info_sender_; |
| + scoped_ptr<CursorAnchorInfoBuilder> cursor_anchor_info_builder_; |
| + |
| + bool editable_; |
| + |
| + base::string16 text_; |
| + gfx::Range selection_range_; |
| + gfx::Range composition_range_; |
| + std::vector<gfx::Rect> character_bounds_; |
| + gfx::Range character_bounds_range_; |
| + bool has_insertion_marker_; |
| + bool is_insertion_marker_visible_; |
| + gfx::RectF insertion_marker_rect_; |
| + bool has_coordinate_info_; |
| + float scale_; |
| + gfx::Vector2dF translation_; |
| + |
| + bool has_pending_request_; |
| + bool monitor_mode_enabled_; |
| + |
| + DISALLOW_COPY_AND_ASSIGN(CursorAnchorInfoControllerImpl); |
| +}; |
| + |
| +CursorAnchorInfoControllerImpl::CursorAnchorInfoControllerImpl( |
| + CursorAnchorInfoSender* sender, |
| + scoped_ptr<CursorAnchorInfoBuilder> builder) |
| + : cursor_anchor_info_sender_(sender), |
| + cursor_anchor_info_builder_(builder.Pass()), |
| + editable_(false), |
| + selection_range_(gfx::Range::InvalidRange()), |
| + composition_range_(gfx::Range::InvalidRange()), |
| + has_insertion_marker_(false), |
| + is_insertion_marker_visible_(false), |
| + has_coordinate_info_(false), |
| + scale_(1.0f), |
| + has_pending_request_(false), |
| + monitor_mode_enabled_(false) { |
| +} |
| + |
| +CursorAnchorInfoControllerImpl::~CursorAnchorInfoControllerImpl() { |
| +} |
| + |
| +void CursorAnchorInfoControllerImpl::OnFocusedNodeChanged( |
| + bool is_editable_node) { |
| + editable_ = is_editable_node; |
| + text_.clear(); |
| + selection_range_ = gfx::Range::InvalidRange(); |
| + composition_range_ = gfx::Range::InvalidRange(); |
| + character_bounds_.clear(); |
| + character_bounds_range_ = gfx::Range::InvalidRange(); |
| + has_insertion_marker_ = false; |
| + is_insertion_marker_visible_= false; |
| + insertion_marker_rect_.SetRect(0.0f, 0.0f, 0.0f, 0.0f); |
| + has_coordinate_info_ = false; |
| + scale_ = 1.0f; |
| + translation_ = gfx::Vector2d(); |
| +} |
| + |
| +void CursorAnchorInfoControllerImpl::OnTextInputStateChanged( |
| + const base::string16& text, |
| + const gfx::Range& selection_range, |
| + const gfx::Range& composition_range) { |
| + if (!editable_) |
| + return; |
| + |
| + if (monitor_mode_enabled_) { |
| + const bool changed = |
| + text_ != text |
| + || selection_range_ != selection_range |
| + || composition_range_ != composition_range; |
| + if (changed) |
| + has_pending_request_ = true; |
| + } |
| + |
| + text_ = text; |
| + selection_range_ = selection_range; |
| + composition_range_ = composition_range; |
| +} |
| + |
| +void CursorAnchorInfoControllerImpl::OnCompositionRangeChanged( |
| + const gfx::Range& range, |
| + const std::vector<gfx::Rect>& character_bounds) { |
| + if (!editable_) |
| + return; |
| + |
| + if (monitor_mode_enabled_) { |
| + const bool changed = |
| + character_bounds_ != character_bounds |
| + || character_bounds_range_ != range; |
| + if (changed) |
| + has_pending_request_ = true; |
| + } |
| + |
| + character_bounds_ = character_bounds; |
| + character_bounds_range_ = range; |
| +} |
| + |
| +void CursorAnchorInfoControllerImpl::OnFrameMetadataUpdated( |
| + const cc::CompositorFrameMetadata& frame_metadata, |
| + const gfx::Vector2d& view_offset) { |
| + if (!editable_) |
| + return; |
| + |
| + const auto& selection_start = frame_metadata.selection_start; |
| + |
| + const float scale = |
| + frame_metadata.device_scale_factor * frame_metadata.page_scale_factor; |
| + const auto translation = |
| + gfx::ScaleVector2d(frame_metadata.root_scroll_offset, -scale) |
|
jdduke (slow)
2015/01/12 16:33:44
Hmm, it looks like you're trying to synchronize th
yukawa
2015/01/12 17:08:30
I cannot check it right now as I'm in my home, but
|
| + + gfx::Vector2dF(view_offset.x(), view_offset.y()) |
| + + frame_metadata.location_bar_offset |
| + + frame_metadata.location_bar_content_translation; |
| + |
| + const bool has_insertion_marker = |
| + selection_start.type == cc::SELECTION_BOUND_CENTER; |
| + const bool is_insertion_marker_visible = selection_start.visible; |
| + const gfx::PointF insertion_marker_origin = has_insertion_marker |
| + ? selection_start.edge_top |
| + : gfx::PointF(0.0f, 0.0f); |
| + const float insertion_marker_height = has_insertion_marker |
| + ? selection_start.edge_bottom.y() - selection_start.edge_top.y() |
| + : 0.0f; |
| + const gfx::SizeF insertion_marker_size(0.0f, insertion_marker_height); |
| + const gfx::RectF insertion_marker_rect(insertion_marker_origin, |
| + insertion_marker_size); |
| + if (monitor_mode_enabled_) { |
| + const bool changed = |
| + !has_coordinate_info_ |
| + || scale_ != scale |
| + || translation_ != translation |
| + || has_insertion_marker_ != has_insertion_marker |
| + || is_insertion_marker_visible_ != is_insertion_marker_visible |
| + || insertion_marker_rect_ != insertion_marker_rect; |
| + if (changed) |
| + has_pending_request_ = true; |
| + } |
| + |
| + scale_ = scale; |
| + translation_ = translation; |
| + has_insertion_marker_ = has_insertion_marker; |
| + is_insertion_marker_visible_ = is_insertion_marker_visible; |
| + insertion_marker_rect_ = insertion_marker_rect; |
| + has_coordinate_info_ = true; |
| + |
| + if (cursor_anchor_info_sender_ |
| + && cursor_anchor_info_builder_ |
| + && HasPendingUpdate()) { |
| + UpdateBuilder(); |
| + cursor_anchor_info_sender_->SendCursorAnchorInfo( |
| + cursor_anchor_info_builder_.get()); |
| + ClearPendingUpdate(); |
| + } |
| +} |
| + |
| +bool CursorAnchorInfoControllerImpl::OnRequestCursorUpdates( |
| + uint32 cursor_update_mode) { |
| + if (!editable_) |
| + return false; |
| + |
| + // Should return false if at least one unknown bit is set, because we don't |
| + // know what is requested by such a bit. |
| + const static uint32 kKnownFlags = |
| + kCursorUpdateModeImmediate | kCursorUpdateModeMonitor; |
| + if ((cursor_update_mode & ~kKnownFlags) != 0) |
| + return false; |
| + |
| + const bool update_immediate = cursor_update_mode & kCursorUpdateModeImmediate; |
| + const bool update_monitor = cursor_update_mode & kCursorUpdateModeMonitor; |
| + |
| + SetMonitorMode(update_monitor); |
| + if (update_immediate) |
| + ScheduleUpdate(); |
| + |
| + if (cursor_anchor_info_sender_ |
| + && cursor_anchor_info_builder_ |
| + && HasPendingUpdate()) { |
| + UpdateBuilder(); |
| + cursor_anchor_info_sender_->SendCursorAnchorInfo( |
| + cursor_anchor_info_builder_.get()); |
| + ClearPendingUpdate(); |
| + } |
| + |
| + return true; |
| +} |
| + |
| +void CursorAnchorInfoControllerImpl::ScheduleUpdate() { |
| + has_pending_request_ = true; |
| +} |
| + |
| +bool CursorAnchorInfoControllerImpl::HasPendingUpdate() const { |
| + return has_pending_request_ && has_coordinate_info_; |
| +} |
| + |
| +void CursorAnchorInfoControllerImpl::ClearPendingUpdate() { |
| + has_pending_request_ = false; |
| +} |
| + |
| +void CursorAnchorInfoControllerImpl::SetMonitorMode(bool enabled) { |
| + monitor_mode_enabled_ = enabled; |
| +} |
| + |
| +void CursorAnchorInfoControllerImpl::UpdateBuilder() { |
| + DCHECK(HasPendingUpdate()); |
|
jdduke (slow)
2015/01/14 17:17:09
Is it necessary to blast all values to the builder
yukawa
2015/01/14 22:21:59
Basically we can, but one problem is that the resu
|
| + |
| + cursor_anchor_info_builder_->Reset(); |
| + |
| + if (selection_range_.IsValid()) |
| + cursor_anchor_info_builder_->SetSelectionRange(selection_range_); |
| + |
| + if (composition_range_.IsValid()) { |
| + const auto composition_text = base::StringPiece16(text_).substr( |
| + composition_range_.start(), |
| + composition_range_.length()); |
| + cursor_anchor_info_builder_->SetComposingText( |
| + composition_range_.start(), |
| + composition_text); |
| + } |
| + if (character_bounds_range_.IsValid()) { |
| + const int index_offset = character_bounds_range_.start(); |
| + for (size_t i = 0; i < character_bounds_.size(); ++i) { |
| + cursor_anchor_info_builder_->AddCharacterBounds( |
| + index_offset + i, |
| + character_bounds_[i], |
| + CursorAnchorInfoBuilder::kFlagHasVisibleRegion); |
| + } |
| + } |
| + |
| + if (has_insertion_marker_) { |
| + const uint32 flags = is_insertion_marker_visible_ |
| + ? CursorAnchorInfoBuilder::kFlagHasVisibleRegion |
| + : CursorAnchorInfoBuilder::kFlagHasInvisibleRegion; |
| + cursor_anchor_info_builder_->SetInsertionMarkerLocation( |
| + insertion_marker_rect_.x(), |
| + insertion_marker_rect_.y(), |
| + insertion_marker_rect_.bottom(), |
| + insertion_marker_rect_.bottom(), |
| + flags); |
| + } |
| + |
| + gfx::Matrix3F matrix = gfx::Matrix3F::Zeros(); |
| + matrix.set(scale_, 0.0f, translation_.x(), |
| + 0.0f, scale_, translation_.y(), |
| + 0.0f, 0.0f, 1.0f); |
| + cursor_anchor_info_builder_->SetMatrix(matrix); |
| +} |
| + |
| +} // namespace |
| + |
| +CursorAnchorInfoController::CursorAnchorInfoController() { |
| +} |
| + |
| +CursorAnchorInfoController::~CursorAnchorInfoController() { |
| +} |
| + |
| +// static |
| +scoped_ptr<CursorAnchorInfoController> |
| +CursorAnchorInfoController::Create(CursorAnchorInfoSender* sender) { |
| + static bool use_cursor_anchor_info = |
| + base::android::BuildInfo::GetInstance()->sdk_int() >= 21; |
| + if (!use_cursor_anchor_info) |
| + return scoped_ptr<ui::CursorAnchorInfoController>(nullptr); |
| + |
| + return scoped_ptr<ui::CursorAnchorInfoController>( |
| + new ui::CursorAnchorInfoControllerImpl( |
| + sender, |
| + CursorAnchorInfoBuilder::Create())); |
| +} |
| + |
| +// static |
| +scoped_ptr<CursorAnchorInfoController> |
| +CursorAnchorInfoController::CreateForTest( |
| + CursorAnchorInfoSender* sender, |
| + scoped_ptr<ui::CursorAnchorInfoBuilder> builder) { |
| + return scoped_ptr<CursorAnchorInfoController>( |
| + new ui::CursorAnchorInfoControllerImpl(sender, builder.Pass())); |
| +} |
| + |
| +} // namespace ui |