Supervised user whitelists: Cleanup
[chromium-blink-merge.git] / ui / touch_selection / touch_selection_controller.cc
blob3d52b4c3e423f276b65541c1491b0e260411fb86
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 "ui/touch_selection/touch_selection_controller.h"
7 #include "base/auto_reset.h"
8 #include "base/logging.h"
9 #include "base/metrics/histogram_macros.h"
11 namespace ui {
12 namespace {
14 gfx::Vector2dF ComputeLineOffsetFromBottom(const SelectionBound& bound) {
15 gfx::Vector2dF line_offset =
16 gfx::ScaleVector2d(bound.edge_top() - bound.edge_bottom(), 0.5f);
17 // An offset of 5 DIPs is sufficient for most line sizes. For small lines,
18 // using half the line height avoids synthesizing a point on a line above
19 // (or below) the intended line.
20 const gfx::Vector2dF kMaxLineOffset(5.f, 5.f);
21 line_offset.SetToMin(kMaxLineOffset);
22 line_offset.SetToMax(-kMaxLineOffset);
23 return line_offset;
26 TouchHandleOrientation ToTouchHandleOrientation(SelectionBound::Type type) {
27 switch (type) {
28 case SelectionBound::LEFT:
29 return TouchHandleOrientation::LEFT;
30 case SelectionBound::RIGHT:
31 return TouchHandleOrientation::RIGHT;
32 case SelectionBound::CENTER:
33 return TouchHandleOrientation::CENTER;
34 case SelectionBound::EMPTY:
35 return TouchHandleOrientation::UNDEFINED;
37 NOTREACHED() << "Invalid selection bound type: " << type;
38 return TouchHandleOrientation::UNDEFINED;
41 } // namespace
43 TouchSelectionController::TouchSelectionController(
44 TouchSelectionControllerClient* client,
45 base::TimeDelta tap_timeout,
46 float tap_slop,
47 bool show_on_tap_for_empty_editable)
48 : client_(client),
49 tap_timeout_(tap_timeout),
50 tap_slop_(tap_slop),
51 show_on_tap_for_empty_editable_(show_on_tap_for_empty_editable),
52 response_pending_input_event_(INPUT_EVENT_TYPE_NONE),
53 start_orientation_(TouchHandleOrientation::UNDEFINED),
54 end_orientation_(TouchHandleOrientation::UNDEFINED),
55 is_insertion_active_(false),
56 activate_insertion_automatically_(false),
57 is_selection_active_(false),
58 activate_selection_automatically_(false),
59 selection_empty_(false),
60 selection_editable_(false),
61 temporarily_hidden_(false),
62 selection_handle_dragged_(false) {
63 DCHECK(client_);
66 TouchSelectionController::~TouchSelectionController() {
69 void TouchSelectionController::OnSelectionBoundsChanged(
70 const SelectionBound& start,
71 const SelectionBound& end) {
72 if (start == start_ && end_ == end)
73 return;
75 start_ = start;
76 end_ = end;
77 start_orientation_ = ToTouchHandleOrientation(start_.type());
78 end_orientation_ = ToTouchHandleOrientation(end_.type());
80 if (!activate_selection_automatically_ &&
81 !activate_insertion_automatically_) {
82 DCHECK_EQ(INPUT_EVENT_TYPE_NONE, response_pending_input_event_);
83 return;
86 // Ensure that |response_pending_input_event_| is cleared after the method
87 // completes, while also making its current value available for the duration
88 // of the call.
89 InputEventType causal_input_event = response_pending_input_event_;
90 response_pending_input_event_ = INPUT_EVENT_TYPE_NONE;
91 base::AutoReset<InputEventType> auto_reset_response_pending_input_event(
92 &response_pending_input_event_, causal_input_event);
94 const bool is_selection_dragging =
95 is_selection_active_ && (start_selection_handle_->is_dragging() ||
96 end_selection_handle_->is_dragging());
98 // It's possible that the bounds temporarily overlap while a selection handle
99 // is being dragged, incorrectly reporting a CENTER orientation.
100 // TODO(jdduke): This safeguard is racy, as it's possible the delayed response
101 // from handle positioning occurs *after* the handle dragging has ceased.
102 // Instead, prevent selection -> insertion transitions without an intervening
103 // action or selection clearing of some sort, crbug.com/392696.
104 if (is_selection_dragging) {
105 if (start_orientation_ == TouchHandleOrientation::CENTER)
106 start_orientation_ = start_selection_handle_->orientation();
107 if (end_orientation_ == TouchHandleOrientation::CENTER)
108 end_orientation_ = end_selection_handle_->orientation();
111 if (GetStartPosition() != GetEndPosition() ||
112 (is_selection_dragging &&
113 start_orientation_ != TouchHandleOrientation::UNDEFINED &&
114 end_orientation_ != TouchHandleOrientation::UNDEFINED)) {
115 OnSelectionChanged();
116 return;
119 if (start_orientation_ == TouchHandleOrientation::CENTER &&
120 selection_editable_) {
121 OnInsertionChanged();
122 return;
125 HideAndDisallowShowingAutomatically();
128 bool TouchSelectionController::WillHandleTouchEvent(const MotionEvent& event) {
129 if (is_insertion_active_) {
130 DCHECK(insertion_handle_);
131 return insertion_handle_->WillHandleTouchEvent(event);
134 if (is_selection_active_) {
135 DCHECK(start_selection_handle_);
136 DCHECK(end_selection_handle_);
137 if (start_selection_handle_->is_dragging())
138 return start_selection_handle_->WillHandleTouchEvent(event);
140 if (end_selection_handle_->is_dragging())
141 return end_selection_handle_->WillHandleTouchEvent(event);
143 const gfx::PointF event_pos(event.GetX(), event.GetY());
144 if ((event_pos - GetStartPosition()).LengthSquared() <=
145 (event_pos - GetEndPosition()).LengthSquared())
146 return start_selection_handle_->WillHandleTouchEvent(event);
147 else
148 return end_selection_handle_->WillHandleTouchEvent(event);
151 return false;
154 void TouchSelectionController::OnLongPressEvent() {
155 response_pending_input_event_ = LONG_PRESS;
156 ShowSelectionHandlesAutomatically();
157 ShowInsertionHandleAutomatically();
158 ResetCachedValuesIfInactive();
161 void TouchSelectionController::AllowShowingFromCurrentSelection() {
162 if (is_selection_active_ || is_insertion_active_)
163 return;
165 activate_selection_automatically_ = true;
166 activate_insertion_automatically_ = true;
167 if (GetStartPosition() != GetEndPosition())
168 OnSelectionChanged();
169 else if (start_orientation_ == TouchHandleOrientation::CENTER &&
170 selection_editable_)
171 OnInsertionChanged();
174 void TouchSelectionController::OnTapEvent() {
175 response_pending_input_event_ = TAP;
176 ShowInsertionHandleAutomatically();
177 if (selection_empty_ && !show_on_tap_for_empty_editable_)
178 DeactivateInsertion();
179 ResetCachedValuesIfInactive();
182 void TouchSelectionController::HideAndDisallowShowingAutomatically() {
183 response_pending_input_event_ = INPUT_EVENT_TYPE_NONE;
184 DeactivateInsertion();
185 DeactivateSelection();
186 activate_insertion_automatically_ = false;
187 activate_selection_automatically_ = false;
190 void TouchSelectionController::SetTemporarilyHidden(bool hidden) {
191 if (temporarily_hidden_ == hidden)
192 return;
193 temporarily_hidden_ = hidden;
195 TouchHandle::AnimationStyle animation_style = GetAnimationStyle(true);
196 if (is_selection_active_) {
197 start_selection_handle_->SetVisible(GetStartVisible(), animation_style);
198 end_selection_handle_->SetVisible(GetEndVisible(), animation_style);
200 if (is_insertion_active_)
201 insertion_handle_->SetVisible(GetStartVisible(), animation_style);
204 void TouchSelectionController::OnSelectionEditable(bool editable) {
205 if (selection_editable_ == editable)
206 return;
207 selection_editable_ = editable;
208 ResetCachedValuesIfInactive();
209 if (!selection_editable_)
210 DeactivateInsertion();
213 void TouchSelectionController::OnSelectionEmpty(bool empty) {
214 if (selection_empty_ == empty)
215 return;
216 selection_empty_ = empty;
217 ResetCachedValuesIfInactive();
220 bool TouchSelectionController::Animate(base::TimeTicks frame_time) {
221 if (is_insertion_active_)
222 return insertion_handle_->Animate(frame_time);
224 if (is_selection_active_) {
225 bool needs_animate = start_selection_handle_->Animate(frame_time);
226 needs_animate |= end_selection_handle_->Animate(frame_time);
227 return needs_animate;
230 return false;
233 gfx::RectF TouchSelectionController::GetRectBetweenBounds() const {
234 // Short-circuit for efficiency.
235 if (!is_insertion_active_ && !is_selection_active_)
236 return gfx::RectF();
238 if (start_.visible() && !end_.visible())
239 return gfx::BoundingRect(start_.edge_top(), start_.edge_bottom());
241 if (end_.visible() && !start_.visible())
242 return gfx::BoundingRect(end_.edge_top(), end_.edge_bottom());
244 // If both handles are visible, or both are invisible, use the entire rect.
245 return RectFBetweenSelectionBounds(start_, end_);
248 gfx::RectF TouchSelectionController::GetStartHandleRect() const {
249 if (is_insertion_active_)
250 return insertion_handle_->GetVisibleBounds();
251 if (is_selection_active_)
252 return start_selection_handle_->GetVisibleBounds();
253 return gfx::RectF();
256 gfx::RectF TouchSelectionController::GetEndHandleRect() const {
257 if (is_insertion_active_)
258 return insertion_handle_->GetVisibleBounds();
259 if (is_selection_active_)
260 return end_selection_handle_->GetVisibleBounds();
261 return gfx::RectF();
264 const gfx::PointF& TouchSelectionController::GetStartPosition() const {
265 return start_.edge_bottom();
268 const gfx::PointF& TouchSelectionController::GetEndPosition() const {
269 return end_.edge_bottom();
272 void TouchSelectionController::OnHandleDragBegin(const TouchHandle& handle) {
273 if (&handle == insertion_handle_.get()) {
274 client_->OnSelectionEvent(INSERTION_DRAG_STARTED);
275 return;
278 gfx::PointF base, extent;
279 if (&handle == start_selection_handle_.get()) {
280 base = end_selection_handle_->position() + GetEndLineOffset();
281 extent = start_selection_handle_->position() + GetStartLineOffset();
282 } else {
283 base = start_selection_handle_->position() + GetStartLineOffset();
284 extent = end_selection_handle_->position() + GetEndLineOffset();
286 selection_handle_dragged_ = true;
288 // When moving the handle we want to move only the extent point. Before doing
289 // so we must make sure that the base point is set correctly.
290 client_->SelectBetweenCoordinates(base, extent);
291 client_->OnSelectionEvent(SELECTION_DRAG_STARTED);
294 void TouchSelectionController::OnHandleDragUpdate(const TouchHandle& handle,
295 const gfx::PointF& position) {
296 // As the position corresponds to the bottom left point of the selection
297 // bound, offset it by half the corresponding line height.
298 gfx::Vector2dF line_offset = &handle == start_selection_handle_.get()
299 ? GetStartLineOffset()
300 : GetEndLineOffset();
301 gfx::PointF line_position = position + line_offset;
302 if (&handle == insertion_handle_.get()) {
303 client_->MoveCaret(line_position);
304 } else {
305 client_->MoveRangeSelectionExtent(line_position);
309 void TouchSelectionController::OnHandleDragEnd(const TouchHandle& handle) {
310 if (&handle == insertion_handle_.get())
311 client_->OnSelectionEvent(INSERTION_DRAG_STOPPED);
312 else
313 client_->OnSelectionEvent(SELECTION_DRAG_STOPPED);
316 void TouchSelectionController::OnHandleTapped(const TouchHandle& handle) {
317 if (insertion_handle_ && &handle == insertion_handle_.get())
318 client_->OnSelectionEvent(INSERTION_TAPPED);
321 void TouchSelectionController::SetNeedsAnimate() {
322 client_->SetNeedsAnimate();
325 scoped_ptr<TouchHandleDrawable> TouchSelectionController::CreateDrawable() {
326 return client_->CreateDrawable();
329 base::TimeDelta TouchSelectionController::GetTapTimeout() const {
330 return tap_timeout_;
333 float TouchSelectionController::GetTapSlop() const {
334 return tap_slop_;
337 void TouchSelectionController::ShowInsertionHandleAutomatically() {
338 if (activate_insertion_automatically_)
339 return;
340 activate_insertion_automatically_ = true;
341 ResetCachedValuesIfInactive();
344 void TouchSelectionController::ShowSelectionHandlesAutomatically() {
345 if (activate_selection_automatically_)
346 return;
347 activate_selection_automatically_ = true;
348 ResetCachedValuesIfInactive();
351 void TouchSelectionController::OnInsertionChanged() {
352 DeactivateSelection();
354 if (response_pending_input_event_ == TAP && selection_empty_ &&
355 !show_on_tap_for_empty_editable_) {
356 HideAndDisallowShowingAutomatically();
357 return;
360 if (!activate_insertion_automatically_)
361 return;
363 const bool was_active = is_insertion_active_;
364 const gfx::PointF position = GetStartPosition();
365 if (!is_insertion_active_)
366 ActivateInsertion();
367 else
368 client_->OnSelectionEvent(INSERTION_MOVED);
370 insertion_handle_->SetVisible(GetStartVisible(),
371 GetAnimationStyle(was_active));
372 insertion_handle_->SetPosition(position);
375 void TouchSelectionController::OnSelectionChanged() {
376 DeactivateInsertion();
378 if (!activate_selection_automatically_)
379 return;
381 const bool was_active = is_selection_active_;
382 if (!is_selection_active_ || response_pending_input_event_ == LONG_PRESS)
383 ActivateSelection();
384 else
385 client_->OnSelectionEvent(SELECTION_MOVED);
387 const TouchHandle::AnimationStyle animation = GetAnimationStyle(was_active);
388 start_selection_handle_->SetVisible(GetStartVisible(), animation);
389 end_selection_handle_->SetVisible(GetEndVisible(), animation);
391 start_selection_handle_->SetPosition(GetStartPosition());
392 end_selection_handle_->SetPosition(GetEndPosition());
395 void TouchSelectionController::ActivateInsertion() {
396 DCHECK(!is_selection_active_);
398 if (!insertion_handle_)
399 insertion_handle_.reset(
400 new TouchHandle(this, TouchHandleOrientation::CENTER));
402 if (!is_insertion_active_) {
403 is_insertion_active_ = true;
404 insertion_handle_->SetEnabled(true);
405 client_->OnSelectionEvent(INSERTION_SHOWN);
409 void TouchSelectionController::DeactivateInsertion() {
410 if (!is_insertion_active_)
411 return;
412 DCHECK(insertion_handle_);
413 is_insertion_active_ = false;
414 insertion_handle_->SetEnabled(false);
415 client_->OnSelectionEvent(INSERTION_CLEARED);
418 void TouchSelectionController::ActivateSelection() {
419 DCHECK(!is_insertion_active_);
421 if (!start_selection_handle_) {
422 start_selection_handle_.reset(new TouchHandle(this, start_orientation_));
423 } else {
424 start_selection_handle_->SetEnabled(true);
425 start_selection_handle_->SetOrientation(start_orientation_);
428 if (!end_selection_handle_) {
429 end_selection_handle_.reset(new TouchHandle(this, end_orientation_));
430 } else {
431 end_selection_handle_->SetEnabled(true);
432 end_selection_handle_->SetOrientation(end_orientation_);
435 // As a long press received while a selection is already active may trigger
436 // an entirely new selection, notify the client but avoid sending an
437 // intervening SELECTION_CLEARED update to avoid unnecessary state changes.
438 if (!is_selection_active_ || response_pending_input_event_ == LONG_PRESS) {
439 if (is_selection_active_) {
440 // The active selection session finishes with the start of the new one.
441 LogSelectionEnd();
443 is_selection_active_ = true;
444 selection_handle_dragged_ = false;
445 selection_start_time_ = base::TimeTicks::Now();
446 response_pending_input_event_ = INPUT_EVENT_TYPE_NONE;
447 client_->OnSelectionEvent(SELECTION_SHOWN);
451 void TouchSelectionController::DeactivateSelection() {
452 if (!is_selection_active_)
453 return;
454 DCHECK(start_selection_handle_);
455 DCHECK(end_selection_handle_);
456 LogSelectionEnd();
457 start_selection_handle_->SetEnabled(false);
458 end_selection_handle_->SetEnabled(false);
459 is_selection_active_ = false;
460 client_->OnSelectionEvent(SELECTION_CLEARED);
463 void TouchSelectionController::ResetCachedValuesIfInactive() {
464 if (is_selection_active_ || is_insertion_active_)
465 return;
466 start_ = SelectionBound();
467 end_ = SelectionBound();
468 start_orientation_ = TouchHandleOrientation::UNDEFINED;
469 end_orientation_ = TouchHandleOrientation::UNDEFINED;
472 gfx::Vector2dF TouchSelectionController::GetStartLineOffset() const {
473 return ComputeLineOffsetFromBottom(start_);
476 gfx::Vector2dF TouchSelectionController::GetEndLineOffset() const {
477 return ComputeLineOffsetFromBottom(end_);
480 bool TouchSelectionController::GetStartVisible() const {
481 return start_.visible() && !temporarily_hidden_;
484 bool TouchSelectionController::GetEndVisible() const {
485 return end_.visible() && !temporarily_hidden_;
488 TouchHandle::AnimationStyle TouchSelectionController::GetAnimationStyle(
489 bool was_active) const {
490 return was_active && client_->SupportsAnimation()
491 ? TouchHandle::ANIMATION_SMOOTH
492 : TouchHandle::ANIMATION_NONE;
495 void TouchSelectionController::LogSelectionEnd() {
496 // TODO(mfomitchev): Once we are able to tell the difference between
497 // 'successful' and 'unsuccessful' selections - log
498 // Event.TouchSelection.Duration instead and get rid of
499 // Event.TouchSelectionD.WasDraggeduration.
500 if (selection_handle_dragged_) {
501 base::TimeDelta duration = base::TimeTicks::Now() - selection_start_time_;
502 UMA_HISTOGRAM_CUSTOM_TIMES("Event.TouchSelection.WasDraggedDuration",
503 duration,
504 base::TimeDelta::FromMilliseconds(500),
505 base::TimeDelta::FromSeconds(60),
506 60);
510 } // namespace ui