Upstream version 10.39.225.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   if (&handle == start_selection_handle_.get()) {
213     fixed_handle_position_ =
214         end_selection_handle_->position() + GetEndLineOffset();
215   } else {
216     fixed_handle_position_ =
217         start_selection_handle_->position() + GetStartLineOffset();
218   }
219   client_->OnSelectionEvent(SELECTION_DRAG_STARTED, handle.position());
220 }
221
222 void TouchSelectionController::OnHandleDragUpdate(const TouchHandle& handle,
223                                                   const gfx::PointF& position) {
224   // As the position corresponds to the bottom left point of the selection
225   // bound, offset it by half the corresponding line height.
226   gfx::Vector2dF line_offset = &handle == end_selection_handle_.get()
227                                    ? GetStartLineOffset()
228                                    : GetEndLineOffset();
229   gfx::PointF line_position = position + line_offset;
230   if (&handle == insertion_handle_.get()) {
231     client_->MoveCaret(line_position);
232   } else {
233     client_->SelectBetweenCoordinates(fixed_handle_position_, line_position);
234   }
235 }
236
237 void TouchSelectionController::OnHandleDragEnd(const TouchHandle& handle) {
238   if (&handle != insertion_handle_.get())
239     client_->OnSelectionEvent(SELECTION_DRAG_STOPPED, handle.position());
240 }
241
242 void TouchSelectionController::OnHandleTapped(const TouchHandle& handle) {
243   if (insertion_handle_ && &handle == insertion_handle_.get())
244     client_->OnSelectionEvent(INSERTION_TAPPED, handle.position());
245 }
246
247 void TouchSelectionController::SetNeedsAnimate() {
248   client_->SetNeedsAnimate();
249 }
250
251 scoped_ptr<TouchHandleDrawable> TouchSelectionController::CreateDrawable() {
252   return client_->CreateDrawable();
253 }
254
255 base::TimeDelta TouchSelectionController::GetTapTimeout() const {
256   return tap_timeout_;
257 }
258
259 float TouchSelectionController::GetTapSlop() const {
260   return tap_slop_;
261 }
262
263 void TouchSelectionController::ShowInsertionHandleAutomatically() {
264   if (activate_insertion_automatically_)
265     return;
266   activate_insertion_automatically_ = true;
267   ResetCachedValuesIfInactive();
268 }
269
270 void TouchSelectionController::ShowSelectionHandlesAutomatically() {
271   if (activate_selection_automatically_)
272     return;
273   activate_selection_automatically_ = true;
274   ResetCachedValuesIfInactive();
275 }
276
277 void TouchSelectionController::OnInsertionChanged() {
278   DeactivateSelection();
279
280   if (response_pending_input_event_ == TAP && selection_empty_) {
281     HideAndDisallowShowingAutomatically();
282     return;
283   }
284
285   if (!activate_insertion_automatically_)
286     return;
287
288   const bool was_active = is_insertion_active_;
289   const gfx::PointF position = GetStartPosition();
290   if (!is_insertion_active_)
291     ActivateInsertion();
292   else
293     client_->OnSelectionEvent(INSERTION_MOVED, position);
294
295   insertion_handle_->SetVisible(GetStartVisible(),
296                                 GetAnimationStyle(was_active));
297   insertion_handle_->SetPosition(position);
298 }
299
300 void TouchSelectionController::OnSelectionChanged() {
301   DeactivateInsertion();
302
303   if (!activate_selection_automatically_)
304     return;
305
306   const bool was_active = is_selection_active_;
307   ActivateSelection();
308
309   const TouchHandle::AnimationStyle animation = GetAnimationStyle(was_active);
310   start_selection_handle_->SetVisible(GetStartVisible(), animation);
311   end_selection_handle_->SetVisible(GetEndVisible(), animation);
312
313   start_selection_handle_->SetPosition(GetStartPosition());
314   end_selection_handle_->SetPosition(GetEndPosition());
315 }
316
317 void TouchSelectionController::ActivateInsertion() {
318   DCHECK(!is_selection_active_);
319
320   if (!insertion_handle_)
321     insertion_handle_.reset(new TouchHandle(this, TOUCH_HANDLE_CENTER));
322
323   if (!is_insertion_active_) {
324     is_insertion_active_ = true;
325     insertion_handle_->SetEnabled(true);
326     client_->OnSelectionEvent(INSERTION_SHOWN, GetStartPosition());
327   }
328 }
329
330 void TouchSelectionController::DeactivateInsertion() {
331   if (!is_insertion_active_)
332     return;
333   DCHECK(insertion_handle_);
334   is_insertion_active_ = false;
335   insertion_handle_->SetEnabled(false);
336   client_->OnSelectionEvent(INSERTION_CLEARED, gfx::PointF());
337 }
338
339 void TouchSelectionController::ActivateSelection() {
340   DCHECK(!is_insertion_active_);
341
342   if (!start_selection_handle_) {
343     start_selection_handle_.reset(new TouchHandle(this, start_orientation_));
344   } else {
345     start_selection_handle_->SetEnabled(true);
346     start_selection_handle_->SetOrientation(start_orientation_);
347   }
348
349   if (!end_selection_handle_) {
350     end_selection_handle_.reset(new TouchHandle(this, end_orientation_));
351   } else {
352     end_selection_handle_->SetEnabled(true);
353     end_selection_handle_->SetOrientation(end_orientation_);
354   }
355
356   // As a long press received while a selection is already active may trigger
357   // an entirely new selection, notify the client but avoid sending an
358   // intervening SELECTION_CLEARED update to avoid unnecessary state changes.
359   if (!is_selection_active_ || response_pending_input_event_ == LONG_PRESS) {
360     is_selection_active_ = true;
361     response_pending_input_event_ = INPUT_EVENT_TYPE_NONE;
362     client_->OnSelectionEvent(SELECTION_SHOWN, GetStartPosition());
363   }
364 }
365
366 void TouchSelectionController::DeactivateSelection() {
367   if (!is_selection_active_)
368     return;
369   DCHECK(start_selection_handle_);
370   DCHECK(end_selection_handle_);
371   start_selection_handle_->SetEnabled(false);
372   end_selection_handle_->SetEnabled(false);
373   is_selection_active_ = false;
374   client_->OnSelectionEvent(SELECTION_CLEARED, gfx::PointF());
375 }
376
377 void TouchSelectionController::ResetCachedValuesIfInactive() {
378   if (is_selection_active_ || is_insertion_active_)
379     return;
380   start_ = cc::ViewportSelectionBound();
381   end_ = cc::ViewportSelectionBound();
382   start_orientation_ = TOUCH_HANDLE_ORIENTATION_UNDEFINED;
383   end_orientation_ = TOUCH_HANDLE_ORIENTATION_UNDEFINED;
384 }
385
386 const gfx::PointF& TouchSelectionController::GetStartPosition() const {
387   return start_.edge_bottom;
388 }
389
390 const gfx::PointF& TouchSelectionController::GetEndPosition() const {
391   return end_.edge_bottom;
392 }
393
394 gfx::Vector2dF TouchSelectionController::GetStartLineOffset() const {
395   return gfx::ScaleVector2d(start_.edge_top - start_.edge_bottom, 0.5f);
396 }
397
398 gfx::Vector2dF TouchSelectionController::GetEndLineOffset() const {
399   return gfx::ScaleVector2d(end_.edge_top - end_.edge_bottom, 0.5f);
400 }
401
402 bool TouchSelectionController::GetStartVisible() const {
403   return start_.visible && !temporarily_hidden_;
404 }
405
406 bool TouchSelectionController::GetEndVisible() const {
407   return end_.visible && !temporarily_hidden_;
408 }
409
410 TouchHandle::AnimationStyle TouchSelectionController::GetAnimationStyle(
411     bool was_active) const {
412   return was_active && client_->SupportsAnimation()
413              ? TouchHandle::ANIMATION_SMOOTH
414              : TouchHandle::ANIMATION_NONE;
415 }
416
417 }  // namespace content