Battery Status API: add UMA logging for Linux.
[chromium-blink-merge.git] / content / browser / renderer_host / input / touch_selection_controller.cc
blobf27d6908bc96a6aec6e6dc0ee20ed40a93120e28
1 // Copyright 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "content/browser/renderer_host/input/touch_selection_controller.h"
7 #include "base/auto_reset.h"
8 #include "base/logging.h"
9 #include "third_party/WebKit/public/web/WebInputEvent.h"
11 namespace content {
12 namespace {
14 TouchHandleOrientation ToTouchHandleOrientation(cc::SelectionBoundType type) {
15 switch (type) {
16 case cc::SELECTION_BOUND_LEFT:
17 return TOUCH_HANDLE_LEFT;
18 case cc::SELECTION_BOUND_RIGHT:
19 return TOUCH_HANDLE_RIGHT;
20 case cc::SELECTION_BOUND_CENTER:
21 return TOUCH_HANDLE_CENTER;
22 case cc::SELECTION_BOUND_EMPTY:
23 return TOUCH_HANDLE_ORIENTATION_UNDEFINED;
25 NOTREACHED() << "Invalid selection bound type: " << type;
26 return TOUCH_HANDLE_ORIENTATION_UNDEFINED;
29 } // namespace
31 TouchSelectionController::TouchSelectionController(
32 TouchSelectionControllerClient* client)
33 : client_(client),
34 response_pending_input_event_(INPUT_EVENT_TYPE_NONE),
35 start_orientation_(TOUCH_HANDLE_ORIENTATION_UNDEFINED),
36 end_orientation_(TOUCH_HANDLE_ORIENTATION_UNDEFINED),
37 is_insertion_active_(false),
38 activate_insertion_automatically_(false),
39 is_selection_active_(false),
40 activate_selection_automatically_(false),
41 selection_empty_(false),
42 selection_editable_(false),
43 temporarily_hidden_(false) {
44 DCHECK(client_);
45 HideAndDisallowShowingAutomatically();
48 TouchSelectionController::~TouchSelectionController() {
51 void TouchSelectionController::OnSelectionBoundsChanged(
52 const cc::ViewportSelectionBound& start,
53 const cc::ViewportSelectionBound& end) {
54 if (!activate_selection_automatically_ &&
55 !activate_insertion_automatically_) {
56 DCHECK_EQ(INPUT_EVENT_TYPE_NONE, response_pending_input_event_);
57 return;
60 if (start == start_ && end_ == end)
61 return;
63 start_ = start;
64 end_ = end;
65 start_orientation_ = ToTouchHandleOrientation(start_.type);
66 end_orientation_ = ToTouchHandleOrientation(end_.type);
68 // Ensure that |response_pending_input_event_| is cleared after the method
69 // completes, while also making its current value available for the duration
70 // of the call.
71 InputEventType causal_input_event = response_pending_input_event_;
72 response_pending_input_event_ = INPUT_EVENT_TYPE_NONE;
73 base::AutoReset<InputEventType> auto_reset_response_pending_input_event(
74 &response_pending_input_event_, causal_input_event);
76 const bool is_selection_dragging =
77 is_selection_active_ && (start_selection_handle_->is_dragging() ||
78 end_selection_handle_->is_dragging());
80 // It's possible that the bounds temporarily overlap while a selection handle
81 // is being dragged, incorrectly reporting a CENTER orientation.
82 // TODO(jdduke): This safeguard is racy, as it's possible the delayed response
83 // from handle positioning occurs *after* the handle dragging has ceased.
84 // Instead, prevent selection -> insertion transitions without an intervening
85 // action or selection clearing of some sort, crbug.com/392696.
86 if (is_selection_dragging) {
87 if (start_orientation_ == TOUCH_HANDLE_CENTER)
88 start_orientation_ = start_selection_handle_->orientation();
89 if (end_orientation_ == TOUCH_HANDLE_CENTER)
90 end_orientation_ = end_selection_handle_->orientation();
93 if (GetStartPosition() != GetEndPosition() ||
94 (is_selection_dragging &&
95 start_orientation_ != TOUCH_HANDLE_ORIENTATION_UNDEFINED &&
96 end_orientation_ != TOUCH_HANDLE_ORIENTATION_UNDEFINED)) {
97 OnSelectionChanged();
98 return;
101 if (start_orientation_ == TOUCH_HANDLE_CENTER && selection_editable_) {
102 OnInsertionChanged();
103 return;
106 HideAndDisallowShowingAutomatically();
109 bool TouchSelectionController::WillHandleTouchEvent(
110 const ui::MotionEvent& event) {
111 if (is_insertion_active_) {
112 DCHECK(insertion_handle_);
113 return insertion_handle_->WillHandleTouchEvent(event);
116 if (is_selection_active_) {
117 DCHECK(start_selection_handle_);
118 DCHECK(end_selection_handle_);
119 if (start_selection_handle_->is_dragging())
120 return start_selection_handle_->WillHandleTouchEvent(event);
122 if (end_selection_handle_->is_dragging())
123 return end_selection_handle_->WillHandleTouchEvent(event);
125 const gfx::PointF event_pos(event.GetX(), event.GetY());
126 if ((event_pos - GetStartPosition()).LengthSquared() <=
127 (event_pos - GetEndPosition()).LengthSquared())
128 return start_selection_handle_->WillHandleTouchEvent(event);
129 else
130 return end_selection_handle_->WillHandleTouchEvent(event);
133 return false;
136 void TouchSelectionController::OnLongPressEvent() {
137 response_pending_input_event_ = LONG_PRESS;
138 ShowSelectionHandlesAutomatically();
139 ShowInsertionHandleAutomatically();
140 ResetCachedValuesIfInactive();
143 void TouchSelectionController::OnTapEvent() {
144 response_pending_input_event_ = TAP;
145 ShowInsertionHandleAutomatically();
146 if (selection_empty_)
147 DeactivateInsertion();
148 ResetCachedValuesIfInactive();
151 void TouchSelectionController::HideAndDisallowShowingAutomatically() {
152 response_pending_input_event_ = INPUT_EVENT_TYPE_NONE;
153 DeactivateInsertion();
154 DeactivateSelection();
155 activate_insertion_automatically_ = false;
156 activate_selection_automatically_ = false;
159 void TouchSelectionController::SetTemporarilyHidden(bool hidden) {
160 if (temporarily_hidden_ == hidden)
161 return;
162 temporarily_hidden_ = hidden;
164 TouchHandle::AnimationStyle animation_style = GetAnimationStyle(true);
165 if (is_selection_active_) {
166 start_selection_handle_->SetVisible(GetStartVisible(), animation_style);
167 end_selection_handle_->SetVisible(GetEndVisible(), animation_style);
169 if (is_insertion_active_)
170 insertion_handle_->SetVisible(GetStartVisible(), animation_style);
173 void TouchSelectionController::OnSelectionEditable(bool editable) {
174 if (selection_editable_ == editable)
175 return;
176 selection_editable_ = editable;
177 ResetCachedValuesIfInactive();
178 if (!selection_editable_)
179 DeactivateInsertion();
182 void TouchSelectionController::OnSelectionEmpty(bool empty) {
183 if (selection_empty_ == empty)
184 return;
185 selection_empty_ = empty;
186 ResetCachedValuesIfInactive();
189 bool TouchSelectionController::Animate(base::TimeTicks frame_time) {
190 if (is_insertion_active_)
191 return insertion_handle_->Animate(frame_time);
193 if (is_selection_active_) {
194 bool needs_animate = start_selection_handle_->Animate(frame_time);
195 needs_animate |= end_selection_handle_->Animate(frame_time);
196 return needs_animate;
199 return false;
202 void TouchSelectionController::OnHandleDragBegin(const TouchHandle& handle) {
203 if (&handle == insertion_handle_.get()) {
204 client_->OnSelectionEvent(INSERTION_DRAG_STARTED, handle.position());
205 return;
208 if (&handle == start_selection_handle_.get()) {
209 fixed_handle_position_ =
210 end_selection_handle_->position() + GetEndLineOffset();
211 } else {
212 fixed_handle_position_ =
213 start_selection_handle_->position() + GetStartLineOffset();
215 client_->OnSelectionEvent(SELECTION_DRAG_STARTED, handle.position());
218 void TouchSelectionController::OnHandleDragUpdate(const TouchHandle& handle,
219 const gfx::PointF& position) {
220 // As the position corresponds to the bottom left point of the selection
221 // bound, offset it by half the corresponding line height.
222 gfx::Vector2dF line_offset = &handle == end_selection_handle_.get()
223 ? GetStartLineOffset()
224 : GetEndLineOffset();
225 gfx::PointF line_position = position + line_offset;
226 if (&handle == insertion_handle_.get()) {
227 client_->MoveCaret(line_position);
228 } else {
229 client_->SelectBetweenCoordinates(fixed_handle_position_, line_position);
233 void TouchSelectionController::OnHandleDragEnd(const TouchHandle& handle) {
234 if (&handle != insertion_handle_.get())
235 client_->OnSelectionEvent(SELECTION_DRAG_STOPPED, handle.position());
238 void TouchSelectionController::OnHandleTapped(const TouchHandle& handle) {
239 if (insertion_handle_ && &handle == insertion_handle_.get())
240 client_->OnSelectionEvent(INSERTION_TAPPED, handle.position());
243 void TouchSelectionController::SetNeedsAnimate() {
244 client_->SetNeedsAnimate();
247 scoped_ptr<TouchHandleDrawable> TouchSelectionController::CreateDrawable() {
248 return client_->CreateDrawable();
251 void TouchSelectionController::ShowInsertionHandleAutomatically() {
252 if (activate_insertion_automatically_)
253 return;
254 activate_insertion_automatically_ = true;
255 ResetCachedValuesIfInactive();
258 void TouchSelectionController::ShowSelectionHandlesAutomatically() {
259 if (activate_selection_automatically_)
260 return;
261 activate_selection_automatically_ = true;
262 ResetCachedValuesIfInactive();
265 void TouchSelectionController::OnInsertionChanged() {
266 DeactivateSelection();
268 if (response_pending_input_event_ == TAP && selection_empty_) {
269 HideAndDisallowShowingAutomatically();
270 return;
273 if (!activate_insertion_automatically_)
274 return;
276 const bool was_active = is_insertion_active_;
277 const gfx::PointF position = GetStartPosition();
278 if (!is_insertion_active_)
279 ActivateInsertion();
280 else
281 client_->OnSelectionEvent(INSERTION_MOVED, position);
283 insertion_handle_->SetVisible(GetStartVisible(),
284 GetAnimationStyle(was_active));
285 insertion_handle_->SetPosition(position);
288 void TouchSelectionController::OnSelectionChanged() {
289 DeactivateInsertion();
291 if (!activate_selection_automatically_)
292 return;
294 const bool was_active = is_selection_active_;
295 ActivateSelection();
297 const TouchHandle::AnimationStyle animation = GetAnimationStyle(was_active);
298 start_selection_handle_->SetVisible(GetStartVisible(), animation);
299 end_selection_handle_->SetVisible(GetEndVisible(), animation);
301 start_selection_handle_->SetPosition(GetStartPosition());
302 end_selection_handle_->SetPosition(GetEndPosition());
305 void TouchSelectionController::ActivateInsertion() {
306 DCHECK(!is_selection_active_);
308 if (!insertion_handle_)
309 insertion_handle_.reset(new TouchHandle(this, TOUCH_HANDLE_CENTER));
311 if (!is_insertion_active_) {
312 is_insertion_active_ = true;
313 insertion_handle_->SetEnabled(true);
314 client_->OnSelectionEvent(INSERTION_SHOWN, GetStartPosition());
318 void TouchSelectionController::DeactivateInsertion() {
319 if (!is_insertion_active_)
320 return;
321 DCHECK(insertion_handle_);
322 is_insertion_active_ = false;
323 insertion_handle_->SetEnabled(false);
324 client_->OnSelectionEvent(INSERTION_CLEARED, gfx::PointF());
327 void TouchSelectionController::ActivateSelection() {
328 DCHECK(!is_insertion_active_);
330 if (!start_selection_handle_) {
331 start_selection_handle_.reset(new TouchHandle(this, start_orientation_));
332 } else {
333 start_selection_handle_->SetEnabled(true);
334 start_selection_handle_->SetOrientation(start_orientation_);
337 if (!end_selection_handle_) {
338 end_selection_handle_.reset(new TouchHandle(this, end_orientation_));
339 } else {
340 end_selection_handle_->SetEnabled(true);
341 end_selection_handle_->SetOrientation(end_orientation_);
344 // As a long press received while a selection is already active may trigger
345 // an entirely new selection, notify the client but avoid sending an
346 // intervening SELECTION_CLEARED update to avoid unnecessary state changes.
347 if (!is_selection_active_ || response_pending_input_event_ == LONG_PRESS) {
348 is_selection_active_ = true;
349 response_pending_input_event_ = INPUT_EVENT_TYPE_NONE;
350 client_->OnSelectionEvent(SELECTION_SHOWN, GetStartPosition());
354 void TouchSelectionController::DeactivateSelection() {
355 if (!is_selection_active_)
356 return;
357 DCHECK(start_selection_handle_);
358 DCHECK(end_selection_handle_);
359 start_selection_handle_->SetEnabled(false);
360 end_selection_handle_->SetEnabled(false);
361 is_selection_active_ = false;
362 client_->OnSelectionEvent(SELECTION_CLEARED, gfx::PointF());
365 void TouchSelectionController::ResetCachedValuesIfInactive() {
366 if (is_selection_active_ || is_insertion_active_)
367 return;
368 start_ = cc::ViewportSelectionBound();
369 end_ = cc::ViewportSelectionBound();
370 start_orientation_ = TOUCH_HANDLE_ORIENTATION_UNDEFINED;
371 end_orientation_ = TOUCH_HANDLE_ORIENTATION_UNDEFINED;
374 const gfx::PointF& TouchSelectionController::GetStartPosition() const {
375 return start_.edge_bottom;
378 const gfx::PointF& TouchSelectionController::GetEndPosition() const {
379 return end_.edge_bottom;
382 gfx::Vector2dF TouchSelectionController::GetStartLineOffset() const {
383 return gfx::ScaleVector2d(start_.edge_top - start_.edge_bottom, 0.5f);
386 gfx::Vector2dF TouchSelectionController::GetEndLineOffset() const {
387 return gfx::ScaleVector2d(end_.edge_top - end_.edge_bottom, 0.5f);
390 bool TouchSelectionController::GetStartVisible() const {
391 return start_.visible && !temporarily_hidden_;
394 bool TouchSelectionController::GetEndVisible() const {
395 return end_.visible && !temporarily_hidden_;
398 TouchHandle::AnimationStyle TouchSelectionController::GetAnimationStyle(
399 bool was_active) const {
400 return was_active && client_->SupportsAnimation()
401 ? TouchHandle::ANIMATION_SMOOTH
402 : TouchHandle::ANIMATION_NONE;
405 } // namespace content