Upstream version 9.38.198.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/logging.h"
8 #include "third_party/WebKit/public/web/WebInputEvent.h"
9
10 namespace content {
11
12 TouchSelectionController::TouchSelectionController(
13     TouchSelectionControllerClient* client)
14     : client_(client),
15       last_input_event_type_(INPUT_EVENT_TYPE_NONE),
16       start_orientation_(TOUCH_HANDLE_ORIENTATION_UNDEFINED),
17       start_visible_(false),
18       end_orientation_(TOUCH_HANDLE_ORIENTATION_UNDEFINED),
19       end_visible_(false),
20       is_insertion_active_(false),
21       activate_insertion_automatically_(false),
22       is_selection_active_(false),
23       activate_selection_automatically_(false),
24       selection_empty_(false),
25       selection_editable_(false),
26       temporarily_hidden_(false) {
27   DCHECK(client_);
28   HideAndDisallowShowingAutomatically();
29 }
30
31 TouchSelectionController::~TouchSelectionController() {
32 }
33
34 void TouchSelectionController::OnSelectionBoundsChanged(
35     const gfx::RectF& start_rect,
36     TouchHandleOrientation start_orientation,
37     bool start_visible,
38     const gfx::RectF& end_rect,
39     TouchHandleOrientation end_orientation,
40     bool end_visible) {
41   if (!activate_selection_automatically_ && !activate_insertion_automatically_)
42     return;
43
44   if (start_rect_ == start_rect && end_rect_ == end_rect &&
45       start_orientation_ == start_orientation &&
46       end_orientation_ == end_orientation && start_visible_ == start_visible &&
47       end_visible_ == end_visible)
48     return;
49
50   start_rect_ = start_rect;
51   start_orientation_ = start_orientation;
52   start_visible_ = start_visible;
53   end_rect_ = end_rect;
54   end_orientation_ = end_orientation;
55   end_visible_ = end_visible;
56
57   const bool is_selection_dragging =
58       is_selection_active_ && (start_selection_handle_->is_dragging() ||
59                                end_selection_handle_->is_dragging());
60
61   // It's possible that the bounds temporarily overlap while a selection handle
62   // is being dragged, incorrectly reporting a CENTER orientation.
63   // TODO(jdduke): This safeguard is racy, as it's possible the delayed response
64   // from handle positioning occurs *after* the handle dragging has ceased.
65   // Instead, prevent selection -> insertion transitions without an intervening
66   // action or selection clearing of some sort, crbug.com/392696.
67   if (is_selection_dragging) {
68     if (start_orientation_ == TOUCH_HANDLE_CENTER)
69       start_orientation_ = start_selection_handle_->orientation();
70     if (end_orientation_ == TOUCH_HANDLE_CENTER)
71       end_orientation_ = end_selection_handle_->orientation();
72   }
73
74   const gfx::PointF start = GetStartPosition();
75   const gfx::PointF end = GetEndPosition();
76   if (start != end ||
77       (is_selection_dragging &&
78        start_orientation_ != TOUCH_HANDLE_ORIENTATION_UNDEFINED &&
79        end_orientation_ != TOUCH_HANDLE_ORIENTATION_UNDEFINED)) {
80     OnSelectionChanged();
81     return;
82   }
83
84   if (start_orientation_ == TOUCH_HANDLE_CENTER && selection_editable_) {
85     OnInsertionChanged();
86     return;
87   }
88
89   HideAndDisallowShowingAutomatically();
90 }
91
92 bool TouchSelectionController::WillHandleTouchEvent(
93     const ui::MotionEvent& event) {
94   if (is_insertion_active_) {
95     DCHECK(insertion_handle_);
96     return insertion_handle_->WillHandleTouchEvent(event);
97   }
98
99   if (is_selection_active_) {
100     DCHECK(start_selection_handle_);
101     DCHECK(end_selection_handle_);
102     if (start_selection_handle_->is_dragging())
103       return start_selection_handle_->WillHandleTouchEvent(event);
104
105     if (end_selection_handle_->is_dragging())
106       return end_selection_handle_->WillHandleTouchEvent(event);
107
108     const gfx::PointF event_pos(event.GetX(), event.GetY());
109     if ((event_pos - GetStartPosition()).LengthSquared() <=
110         (event_pos - GetEndPosition()).LengthSquared())
111       return start_selection_handle_->WillHandleTouchEvent(event);
112     else
113       return end_selection_handle_->WillHandleTouchEvent(event);
114   }
115
116   return false;
117 }
118
119 void TouchSelectionController::OnLongPressEvent() {
120   last_input_event_type_ = LONG_PRESS;
121   ShowSelectionHandlesAutomatically();
122   ShowInsertionHandleAutomatically();
123   ResetCachedValuesIfInactive();
124 }
125
126 void TouchSelectionController::OnTapEvent() {
127   last_input_event_type_ = TAP;
128   activate_selection_automatically_ = false;
129   DeactivateSelection();
130   ShowInsertionHandleAutomatically();
131   ResetCachedValuesIfInactive();
132 }
133
134 void TouchSelectionController::HideAndDisallowShowingAutomatically() {
135   last_input_event_type_ = INPUT_EVENT_TYPE_NONE;
136   DeactivateInsertion();
137   DeactivateSelection();
138   activate_insertion_automatically_ = false;
139   activate_selection_automatically_ = false;
140 }
141
142 void TouchSelectionController::SetTemporarilyHidden(bool hidden) {
143   if (temporarily_hidden_ == hidden)
144     return;
145   temporarily_hidden_ = hidden;
146
147   TouchHandle::AnimationStyle animation_style = GetAnimationStyle(true);
148   if (is_selection_active_) {
149     start_selection_handle_->SetVisible(GetStartVisible(), animation_style);
150     end_selection_handle_->SetVisible(GetEndVisible(), animation_style);
151   }
152   if (is_insertion_active_)
153     insertion_handle_->SetVisible(GetStartVisible(), animation_style);
154 }
155
156 void TouchSelectionController::OnSelectionEditable(bool editable) {
157   if (selection_editable_ == editable)
158     return;
159   selection_editable_ = editable;
160   ResetCachedValuesIfInactive();
161   if (!selection_editable_)
162     DeactivateInsertion();
163 }
164
165 void TouchSelectionController::OnSelectionEmpty(bool empty) {
166   if (selection_empty_ == empty)
167     return;
168   selection_empty_ = empty;
169   ResetCachedValuesIfInactive();
170 }
171
172 bool TouchSelectionController::Animate(base::TimeTicks frame_time) {
173   if (is_insertion_active_)
174     return insertion_handle_->Animate(frame_time);
175
176   if (is_selection_active_) {
177     bool needs_animate = start_selection_handle_->Animate(frame_time);
178     needs_animate |= end_selection_handle_->Animate(frame_time);
179     return needs_animate;
180   }
181
182   return false;
183 }
184
185 void TouchSelectionController::OnHandleDragBegin(const TouchHandle& handle) {
186   if (&handle == insertion_handle_.get()) {
187     client_->OnSelectionEvent(INSERTION_DRAG_STARTED, handle.position());
188     return;
189   }
190
191   if (&handle == start_selection_handle_.get()) {
192     fixed_handle_position_ = end_selection_handle_->position() -
193                              gfx::Vector2dF(0, GetEndLineHeight() / 2.f);
194   } else {
195     fixed_handle_position_ = start_selection_handle_->position() -
196                              gfx::Vector2dF(0, GetStartLineHeight() / 2.f);
197   }
198   client_->OnSelectionEvent(SELECTION_DRAG_STARTED, handle.position());
199 }
200
201 void TouchSelectionController::OnHandleDragUpdate(const TouchHandle& handle,
202                                                   const gfx::PointF& position) {
203   // As the position corresponds to the bottom left point of the selection
204   // bound, offset it by half the corresponding line height.
205   float half_line_height = &handle == end_selection_handle_.get()
206                                ? GetEndLineHeight() / 2.f
207                                : GetStartLineHeight() / 2.f;
208   gfx::PointF line_position = position - gfx::Vector2dF(0, half_line_height);
209   if (&handle == insertion_handle_.get()) {
210     client_->MoveCaret(line_position);
211   } else {
212     client_->SelectBetweenCoordinates(fixed_handle_position_, line_position);
213   }
214 }
215
216 void TouchSelectionController::OnHandleDragEnd(const TouchHandle& handle) {
217   if (&handle != insertion_handle_.get())
218     client_->OnSelectionEvent(SELECTION_DRAG_STOPPED, handle.position());
219 }
220
221 void TouchSelectionController::OnHandleTapped(const TouchHandle& handle) {
222   if (insertion_handle_ && &handle == insertion_handle_.get())
223     client_->OnSelectionEvent(INSERTION_TAPPED, handle.position());
224 }
225
226 void TouchSelectionController::SetNeedsAnimate() {
227   client_->SetNeedsAnimate();
228 }
229
230 scoped_ptr<TouchHandleDrawable> TouchSelectionController::CreateDrawable() {
231   return client_->CreateDrawable();
232 }
233
234 void TouchSelectionController::ShowInsertionHandleAutomatically() {
235   if (activate_insertion_automatically_)
236     return;
237   activate_insertion_automatically_ = true;
238   ResetCachedValuesIfInactive();
239 }
240
241 void TouchSelectionController::ShowSelectionHandlesAutomatically() {
242   if (activate_selection_automatically_)
243     return;
244   activate_selection_automatically_ = true;
245   ResetCachedValuesIfInactive();
246 }
247
248 void TouchSelectionController::OnInsertionChanged() {
249   DeactivateSelection();
250
251   if (last_input_event_type_ == TAP && selection_empty_) {
252     HideAndDisallowShowingAutomatically();
253     return;
254   }
255
256   if (!activate_insertion_automatically_)
257     return;
258
259   const bool was_active = is_insertion_active_;
260   const gfx::PointF position = GetStartPosition();
261   if (!is_insertion_active_)
262     ActivateInsertion();
263   else
264     client_->OnSelectionEvent(INSERTION_MOVED, position);
265
266   insertion_handle_->SetVisible(GetStartVisible(),
267                                 GetAnimationStyle(was_active));
268   insertion_handle_->SetPosition(position);
269 }
270
271 void TouchSelectionController::OnSelectionChanged() {
272   DeactivateInsertion();
273
274   if (!activate_selection_automatically_)
275     return;
276
277   const bool was_active = is_selection_active_;
278   ActivateSelection();
279
280   const TouchHandle::AnimationStyle animation = GetAnimationStyle(was_active);
281   start_selection_handle_->SetVisible(GetStartVisible(), animation);
282   end_selection_handle_->SetVisible(GetEndVisible(), animation);
283
284   start_selection_handle_->SetPosition(GetStartPosition());
285   end_selection_handle_->SetPosition(GetEndPosition());
286 }
287
288 void TouchSelectionController::ActivateInsertion() {
289   DCHECK(!is_selection_active_);
290
291   if (!insertion_handle_)
292     insertion_handle_.reset(new TouchHandle(this, TOUCH_HANDLE_CENTER));
293
294   if (!is_insertion_active_) {
295     is_insertion_active_ = true;
296     insertion_handle_->SetEnabled(true);
297     client_->OnSelectionEvent(INSERTION_SHOWN, GetStartPosition());
298   }
299 }
300
301 void TouchSelectionController::DeactivateInsertion() {
302   if (!is_insertion_active_)
303     return;
304   DCHECK(insertion_handle_);
305   is_insertion_active_ = false;
306   insertion_handle_->SetEnabled(false);
307   client_->OnSelectionEvent(INSERTION_CLEARED, gfx::PointF());
308 }
309
310 void TouchSelectionController::ActivateSelection() {
311   DCHECK(!is_insertion_active_);
312
313   if (!start_selection_handle_) {
314     start_selection_handle_.reset(new TouchHandle(this, start_orientation_));
315   } else {
316     start_selection_handle_->SetEnabled(true);
317     start_selection_handle_->SetOrientation(start_orientation_);
318   }
319
320   if (!end_selection_handle_) {
321     end_selection_handle_.reset(new TouchHandle(this, end_orientation_));
322   } else {
323     end_selection_handle_->SetEnabled(true);
324     end_selection_handle_->SetOrientation(end_orientation_);
325   }
326
327   if (!is_selection_active_) {
328     is_selection_active_ = true;
329     client_->OnSelectionEvent(SELECTION_SHOWN, GetStartPosition());
330   }
331 }
332
333 void TouchSelectionController::DeactivateSelection() {
334   if (!is_selection_active_)
335     return;
336   DCHECK(start_selection_handle_);
337   DCHECK(end_selection_handle_);
338   start_selection_handle_->SetEnabled(false);
339   end_selection_handle_->SetEnabled(false);
340   is_selection_active_ = false;
341   client_->OnSelectionEvent(SELECTION_CLEARED, gfx::PointF());
342 }
343
344 void TouchSelectionController::ResetCachedValuesIfInactive() {
345   if (is_selection_active_ || is_insertion_active_)
346     return;
347   start_rect_ = gfx::RectF();
348   end_rect_ = gfx::RectF();
349   start_orientation_ = TOUCH_HANDLE_ORIENTATION_UNDEFINED;
350   end_orientation_ = TOUCH_HANDLE_ORIENTATION_UNDEFINED;
351   start_visible_ = false;
352   end_visible_ = false;
353 }
354
355 gfx::PointF TouchSelectionController::GetStartPosition() const {
356   return start_rect_.bottom_left();
357 }
358
359 gfx::PointF TouchSelectionController::GetEndPosition() const {
360   return end_rect_.bottom_left();
361 }
362
363 float TouchSelectionController::GetStartLineHeight() const {
364   return start_rect_.height();
365 }
366
367 float TouchSelectionController::GetEndLineHeight() const {
368   return end_rect_.height();
369 }
370
371 bool TouchSelectionController::GetStartVisible() const {
372   return start_visible_ && !temporarily_hidden_;
373 }
374
375 bool TouchSelectionController::GetEndVisible() const {
376   return end_visible_ && !temporarily_hidden_;
377 }
378
379 TouchHandle::AnimationStyle TouchSelectionController::GetAnimationStyle(
380     bool was_active) const {
381   return was_active && client_->SupportsAnimation()
382              ? TouchHandle::ANIMATION_SMOOTH
383              : TouchHandle::ANIMATION_NONE;
384 }
385
386 }  // namespace content