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 |