Update To 11.40.268.0
[platform/framework/web/crosswalk.git] / src / content / browser / renderer_host / input / touch_selection_controller.cc
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.
4
5 #include "content/browser/renderer_host/input/touch_selection_controller.h"
6
7 #include "base/auto_reset.h"
8 #include "base/logging.h"
9 #include "third_party/WebKit/public/web/WebInputEvent.h"
10
11 namespace content {
12 namespace {
13
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;
24   }
25   NOTREACHED() << "Invalid selection bound type: " << type;
26   return TOUCH_HANDLE_ORIENTATION_UNDEFINED;
27 }
28
29 }  // namespace
30
31 TouchSelectionController::TouchSelectionController(
32     TouchSelectionControllerClient* client,
33     base::TimeDelta tap_timeout,
34     float tap_slop)
35     : client_(client),
36       tap_timeout_(tap_timeout),
37       tap_slop_(tap_slop),
38       response_pending_input_event_(INPUT_EVENT_TYPE_NONE),
39       start_orientation_(TOUCH_HANDLE_ORIENTATION_UNDEFINED),
40       end_orientation_(TOUCH_HANDLE_ORIENTATION_UNDEFINED),
41       is_insertion_active_(false),
42       activate_insertion_automatically_(false),
43       is_selection_active_(false),
44       activate_selection_automatically_(false),
45       selection_empty_(false),
46       selection_editable_(false),
47       temporarily_hidden_(false) {
48   DCHECK(client_);
49   HideAndDisallowShowingAutomatically();
50 }
51
52 TouchSelectionController::~TouchSelectionController() {
53 }
54
55 void TouchSelectionController::OnSelectionBoundsChanged(
56     const cc::ViewportSelectionBound& start,
57     const cc::ViewportSelectionBound& end) {
58   if (!activate_selection_automatically_ &&
59       !activate_insertion_automatically_) {
60     DCHECK_EQ(INPUT_EVENT_TYPE_NONE, response_pending_input_event_);
61     return;
62   }
63
64   if (start == start_ && end_ == end)
65     return;
66
67   start_ = start;
68   end_ = end;
69   start_orientation_ = ToTouchHandleOrientation(start_.type);
70   end_orientation_ = ToTouchHandleOrientation(end_.type);
71
72   // Ensure that |response_pending_input_event_| is cleared after the method
73   // completes, while also making its current value available for the duration
74   // of the call.
75   InputEventType causal_input_event = response_pending_input_event_;
76   response_pending_input_event_ = INPUT_EVENT_TYPE_NONE;
77   base::AutoReset<InputEventType> auto_reset_response_pending_input_event(
78       &response_pending_input_event_, causal_input_event);
79
80   const bool is_selection_dragging =
81       is_selection_active_ && (start_selection_handle_->is_dragging() ||
82                                end_selection_handle_->is_dragging());
83
84   // It's possible that the bounds temporarily overlap while a selection handle
85   // is being dragged, incorrectly reporting a CENTER orientation.
86   // TODO(jdduke): This safeguard is racy, as it's possible the delayed response
87   // from handle positioning occurs *after* the handle dragging has ceased.
88   // Instead, prevent selection -> insertion transitions without an intervening
89   // action or selection clearing of some sort, crbug.com/392696.
90   if (is_selection_dragging) {
91     if (start_orientation_ == TOUCH_HANDLE_CENTER)
92       start_orientation_ = start_selection_handle_->orientation();
93     if (end_orientation_ == TOUCH_HANDLE_CENTER)
94       end_orientation_ = end_selection_handle_->orientation();
95   }
96
97   if (GetStartPosition() != GetEndPosition() ||
98       (is_selection_dragging &&
99        start_orientation_ != TOUCH_HANDLE_ORIENTATION_UNDEFINED &&
100        end_orientation_ != TOUCH_HANDLE_ORIENTATION_UNDEFINED)) {
101     OnSelectionChanged();
102     return;
103   }
104
105   if (start_orientation_ == TOUCH_HANDLE_CENTER && selection_editable_) {
106     OnInsertionChanged();
107     return;
108   }
109
110   HideAndDisallowShowingAutomatically();
111 }
112
113 bool TouchSelectionController::WillHandleTouchEvent(
114     const ui::MotionEvent& event) {
115   if (is_insertion_active_) {
116     DCHECK(insertion_handle_);
117     return insertion_handle_->WillHandleTouchEvent(event);
118   }
119
120   if (is_selection_active_) {
121     DCHECK(start_selection_handle_);
122     DCHECK(end_selection_handle_);
123     if (start_selection_handle_->is_dragging())
124       return start_selection_handle_->WillHandleTouchEvent(event);
125
126     if (end_selection_handle_->is_dragging())
127       return end_selection_handle_->WillHandleTouchEvent(event);
128
129     const gfx::PointF event_pos(event.GetX(), event.GetY());
130     if ((event_pos - GetStartPosition()).LengthSquared() <=
131         (event_pos - GetEndPosition()).LengthSquared())
132       return start_selection_handle_->WillHandleTouchEvent(event);
133     else
134       return end_selection_handle_->WillHandleTouchEvent(event);
135   }
136
137   return false;
138 }
139
140 void TouchSelectionController::OnLongPressEvent() {
141   response_pending_input_event_ = LONG_PRESS;
142   ShowSelectionHandlesAutomatically();
143   ShowInsertionHandleAutomatically();
144   ResetCachedValuesIfInactive();
145 }
146
147 void TouchSelectionController::OnTapEvent() {
148   response_pending_input_event_ = TAP;
149   ShowInsertionHandleAutomatically();
150   if (selection_empty_)
151     DeactivateInsertion();
152   ResetCachedValuesIfInactive();
153 }
154
155 void TouchSelectionController::HideAndDisallowShowingAutomatically() {
156   response_pending_input_event_ = INPUT_EVENT_TYPE_NONE;
157   DeactivateInsertion();
158   DeactivateSelection();
159   activate_insertion_automatically_ = false;
160   activate_selection_automatically_ = false;
161 }
162
163 void TouchSelectionController::SetTemporarilyHidden(bool hidden) {
164   if (temporarily_hidden_ == hidden)
165     return;
166   temporarily_hidden_ = hidden;
167
168   TouchHandle::AnimationStyle animation_style = GetAnimationStyle(true);
169   if (is_selection_active_) {
170     start_selection_handle_->SetVisible(GetStartVisible(), animation_style);
171     end_selection_handle_->SetVisible(GetEndVisible(), animation_style);
172   }
173   if (is_insertion_active_)
174     insertion_handle_->SetVisible(GetStartVisible(), animation_style);
175 }
176
177 void TouchSelectionController::OnSelectionEditable(bool editable) {
178   if (selection_editable_ == editable)
179     return;
180   selection_editable_ = editable;
181   ResetCachedValuesIfInactive();
182   if (!selection_editable_)
183     DeactivateInsertion();
184 }
185
186 void TouchSelectionController::OnSelectionEmpty(bool empty) {
187   if (selection_empty_ == empty)
188     return;
189   selection_empty_ = empty;
190   ResetCachedValuesIfInactive();
191 }
192
193 bool TouchSelectionController::Animate(base::TimeTicks frame_time) {
194   if (is_insertion_active_)
195     return insertion_handle_->Animate(frame_time);
196
197   if (is_selection_active_) {
198     bool needs_animate = start_selection_handle_->Animate(frame_time);
199     needs_animate |= end_selection_handle_->Animate(frame_time);
200     return needs_animate;
201   }
202
203   return false;
204 }
205
206 void TouchSelectionController::OnHandleDragBegin(const TouchHandle& handle) {
207   if (&handle == insertion_handle_.get()) {
208     client_->OnSelectionEvent(INSERTION_DRAG_STARTED, handle.position());
209     return;
210   }
211
212   gfx::PointF base, extent;
213   if (&handle == start_selection_handle_.get()) {
214     base = end_selection_handle_->position() + GetEndLineOffset();
215     extent = start_selection_handle_->position() + GetStartLineOffset();
216   } else {
217     base = start_selection_handle_->position() + GetStartLineOffset();
218     extent = end_selection_handle_->position() + GetEndLineOffset();
219   }
220
221   // When moving the handle we want to move only the extent point. Before doing
222   // so we must make sure that the base point is set correctly.
223   client_->SelectBetweenCoordinates(base, extent);
224
225   client_->OnSelectionEvent(SELECTION_DRAG_STARTED, handle.position());
226 }
227
228 void TouchSelectionController::OnHandleDragUpdate(const TouchHandle& handle,
229                                                   const gfx::PointF& position) {
230   // As the position corresponds to the bottom left point of the selection
231   // bound, offset it by half the corresponding line height.
232   gfx::Vector2dF line_offset = &handle == end_selection_handle_.get()
233                                    ? GetStartLineOffset()
234                                    : GetEndLineOffset();
235   gfx::PointF line_position = position + line_offset;
236   if (&handle == insertion_handle_.get()) {
237     client_->MoveCaret(line_position);
238   } else {
239     client_->MoveRangeSelectionExtent(line_position);
240   }
241 }
242
243 void TouchSelectionController::OnHandleDragEnd(const TouchHandle& handle) {
244   if (&handle != insertion_handle_.get())
245     client_->OnSelectionEvent(SELECTION_DRAG_STOPPED, handle.position());
246 }
247
248 void TouchSelectionController::OnHandleTapped(const TouchHandle& handle) {
249   if (insertion_handle_ && &handle == insertion_handle_.get())
250     client_->OnSelectionEvent(INSERTION_TAPPED, handle.position());
251 }
252
253 void TouchSelectionController::SetNeedsAnimate() {
254   client_->SetNeedsAnimate();
255 }
256
257 scoped_ptr<TouchHandleDrawable> TouchSelectionController::CreateDrawable() {
258   return client_->CreateDrawable();
259 }
260
261 base::TimeDelta TouchSelectionController::GetTapTimeout() const {
262   return tap_timeout_;
263 }
264
265 float TouchSelectionController::GetTapSlop() const {
266   return tap_slop_;
267 }
268
269 void TouchSelectionController::ShowInsertionHandleAutomatically() {
270   if (activate_insertion_automatically_)
271     return;
272   activate_insertion_automatically_ = true;
273   ResetCachedValuesIfInactive();
274 }
275
276 void TouchSelectionController::ShowSelectionHandlesAutomatically() {
277   if (activate_selection_automatically_)
278     return;
279   activate_selection_automatically_ = true;
280   ResetCachedValuesIfInactive();
281 }
282
283 void TouchSelectionController::OnInsertionChanged() {
284   DeactivateSelection();
285
286   if (response_pending_input_event_ == TAP && selection_empty_) {
287     HideAndDisallowShowingAutomatically();
288     return;
289   }
290
291   if (!activate_insertion_automatically_)
292     return;
293
294   const bool was_active = is_insertion_active_;
295   const gfx::PointF position = GetStartPosition();
296   if (!is_insertion_active_)
297     ActivateInsertion();
298   else
299     client_->OnSelectionEvent(INSERTION_MOVED, position);
300
301   insertion_handle_->SetVisible(GetStartVisible(),
302                                 GetAnimationStyle(was_active));
303   insertion_handle_->SetPosition(position);
304 }
305
306 void TouchSelectionController::OnSelectionChanged() {
307   DeactivateInsertion();
308
309   if (!activate_selection_automatically_)
310     return;
311
312   const bool was_active = is_selection_active_;
313   ActivateSelection();
314
315   const TouchHandle::AnimationStyle animation = GetAnimationStyle(was_active);
316   start_selection_handle_->SetVisible(GetStartVisible(), animation);
317   end_selection_handle_->SetVisible(GetEndVisible(), animation);
318
319   start_selection_handle_->SetPosition(GetStartPosition());
320   end_selection_handle_->SetPosition(GetEndPosition());
321 }
322
323 void TouchSelectionController::ActivateInsertion() {
324   DCHECK(!is_selection_active_);
325
326   if (!insertion_handle_)
327     insertion_handle_.reset(new TouchHandle(this, TOUCH_HANDLE_CENTER));
328
329   if (!is_insertion_active_) {
330     is_insertion_active_ = true;
331     insertion_handle_->SetEnabled(true);
332     client_->OnSelectionEvent(INSERTION_SHOWN, GetStartPosition());
333   }
334 }
335
336 void TouchSelectionController::DeactivateInsertion() {
337   if (!is_insertion_active_)
338     return;
339   DCHECK(insertion_handle_);
340   is_insertion_active_ = false;
341   insertion_handle_->SetEnabled(false);
342   client_->OnSelectionEvent(INSERTION_CLEARED, gfx::PointF());
343 }
344
345 void TouchSelectionController::ActivateSelection() {
346   DCHECK(!is_insertion_active_);
347
348   if (!start_selection_handle_) {
349     start_selection_handle_.reset(new TouchHandle(this, start_orientation_));
350   } else {
351     start_selection_handle_->SetEnabled(true);
352     start_selection_handle_->SetOrientation(start_orientation_);
353   }
354
355   if (!end_selection_handle_) {
356     end_selection_handle_.reset(new TouchHandle(this, end_orientation_));
357   } else {
358     end_selection_handle_->SetEnabled(true);
359     end_selection_handle_->SetOrientation(end_orientation_);
360   }
361
362   // As a long press received while a selection is already active may trigger
363   // an entirely new selection, notify the client but avoid sending an
364   // intervening SELECTION_CLEARED update to avoid unnecessary state changes.
365   if (!is_selection_active_ || response_pending_input_event_ == LONG_PRESS) {
366     is_selection_active_ = true;
367     response_pending_input_event_ = INPUT_EVENT_TYPE_NONE;
368     client_->OnSelectionEvent(SELECTION_SHOWN, GetStartPosition());
369   }
370 }
371
372 void TouchSelectionController::DeactivateSelection() {
373   if (!is_selection_active_)
374     return;
375   DCHECK(start_selection_handle_);
376   DCHECK(end_selection_handle_);
377   start_selection_handle_->SetEnabled(false);
378   end_selection_handle_->SetEnabled(false);
379   is_selection_active_ = false;
380   client_->OnSelectionEvent(SELECTION_CLEARED, gfx::PointF());
381 }
382
383 void TouchSelectionController::ResetCachedValuesIfInactive() {
384   if (is_selection_active_ || is_insertion_active_)
385     return;
386   start_ = cc::ViewportSelectionBound();
387   end_ = cc::ViewportSelectionBound();
388   start_orientation_ = TOUCH_HANDLE_ORIENTATION_UNDEFINED;
389   end_orientation_ = TOUCH_HANDLE_ORIENTATION_UNDEFINED;
390 }
391
392 const gfx::PointF& TouchSelectionController::GetStartPosition() const {
393   return start_.edge_bottom;
394 }
395
396 const gfx::PointF& TouchSelectionController::GetEndPosition() const {
397   return end_.edge_bottom;
398 }
399
400 gfx::Vector2dF TouchSelectionController::GetStartLineOffset() const {
401   return gfx::ScaleVector2d(start_.edge_top - start_.edge_bottom, 0.5f);
402 }
403
404 gfx::Vector2dF TouchSelectionController::GetEndLineOffset() const {
405   return gfx::ScaleVector2d(end_.edge_top - end_.edge_bottom, 0.5f);
406 }
407
408 bool TouchSelectionController::GetStartVisible() const {
409   return start_.visible && !temporarily_hidden_;
410 }
411
412 bool TouchSelectionController::GetEndVisible() const {
413   return end_.visible && !temporarily_hidden_;
414 }
415
416 TouchHandle::AnimationStyle TouchSelectionController::GetAnimationStyle(
417     bool was_active) const {
418   return was_active && client_->SupportsAnimation()
419              ? TouchHandle::ANIMATION_SMOOTH
420              : TouchHandle::ANIMATION_NONE;
421 }
422
423 }  // namespace content