OLD | NEW |
| (Empty) |
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 (start == start_ && end_ == end) | |
59 return; | |
60 | |
61 start_ = start; | |
62 end_ = end; | |
63 start_orientation_ = ToTouchHandleOrientation(start_.type); | |
64 end_orientation_ = ToTouchHandleOrientation(end_.type); | |
65 | |
66 if (!activate_selection_automatically_ && | |
67 !activate_insertion_automatically_) { | |
68 DCHECK_EQ(INPUT_EVENT_TYPE_NONE, response_pending_input_event_); | |
69 return; | |
70 } | |
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::AllowShowingFromCurrentSelection() { | |
148 if (is_selection_active_ || is_insertion_active_) | |
149 return; | |
150 | |
151 activate_selection_automatically_ = true; | |
152 activate_insertion_automatically_ = true; | |
153 if (GetStartPosition() != GetEndPosition()) | |
154 OnSelectionChanged(); | |
155 else if (start_orientation_ == TOUCH_HANDLE_CENTER && selection_editable_) | |
156 OnInsertionChanged(); | |
157 } | |
158 | |
159 void TouchSelectionController::OnTapEvent() { | |
160 response_pending_input_event_ = TAP; | |
161 ShowInsertionHandleAutomatically(); | |
162 if (selection_empty_) | |
163 DeactivateInsertion(); | |
164 ResetCachedValuesIfInactive(); | |
165 } | |
166 | |
167 void TouchSelectionController::HideAndDisallowShowingAutomatically() { | |
168 response_pending_input_event_ = INPUT_EVENT_TYPE_NONE; | |
169 DeactivateInsertion(); | |
170 DeactivateSelection(); | |
171 activate_insertion_automatically_ = false; | |
172 activate_selection_automatically_ = false; | |
173 } | |
174 | |
175 void TouchSelectionController::SetTemporarilyHidden(bool hidden) { | |
176 if (temporarily_hidden_ == hidden) | |
177 return; | |
178 temporarily_hidden_ = hidden; | |
179 | |
180 TouchHandle::AnimationStyle animation_style = GetAnimationStyle(true); | |
181 if (is_selection_active_) { | |
182 start_selection_handle_->SetVisible(GetStartVisible(), animation_style); | |
183 end_selection_handle_->SetVisible(GetEndVisible(), animation_style); | |
184 } | |
185 if (is_insertion_active_) | |
186 insertion_handle_->SetVisible(GetStartVisible(), animation_style); | |
187 } | |
188 | |
189 void TouchSelectionController::OnSelectionEditable(bool editable) { | |
190 if (selection_editable_ == editable) | |
191 return; | |
192 selection_editable_ = editable; | |
193 ResetCachedValuesIfInactive(); | |
194 if (!selection_editable_) | |
195 DeactivateInsertion(); | |
196 } | |
197 | |
198 void TouchSelectionController::OnSelectionEmpty(bool empty) { | |
199 if (selection_empty_ == empty) | |
200 return; | |
201 selection_empty_ = empty; | |
202 ResetCachedValuesIfInactive(); | |
203 } | |
204 | |
205 bool TouchSelectionController::Animate(base::TimeTicks frame_time) { | |
206 if (is_insertion_active_) | |
207 return insertion_handle_->Animate(frame_time); | |
208 | |
209 if (is_selection_active_) { | |
210 bool needs_animate = start_selection_handle_->Animate(frame_time); | |
211 needs_animate |= end_selection_handle_->Animate(frame_time); | |
212 return needs_animate; | |
213 } | |
214 | |
215 return false; | |
216 } | |
217 | |
218 void TouchSelectionController::OnHandleDragBegin(const TouchHandle& handle) { | |
219 if (&handle == insertion_handle_.get()) { | |
220 client_->OnSelectionEvent(INSERTION_DRAG_STARTED, handle.position()); | |
221 return; | |
222 } | |
223 | |
224 gfx::PointF base, extent; | |
225 if (&handle == start_selection_handle_.get()) { | |
226 base = end_selection_handle_->position() + GetEndLineOffset(); | |
227 extent = start_selection_handle_->position() + GetStartLineOffset(); | |
228 } else { | |
229 base = start_selection_handle_->position() + GetStartLineOffset(); | |
230 extent = end_selection_handle_->position() + GetEndLineOffset(); | |
231 } | |
232 | |
233 // When moving the handle we want to move only the extent point. Before doing | |
234 // so we must make sure that the base point is set correctly. | |
235 client_->SelectBetweenCoordinates(base, extent); | |
236 | |
237 client_->OnSelectionEvent(SELECTION_DRAG_STARTED, handle.position()); | |
238 } | |
239 | |
240 void TouchSelectionController::OnHandleDragUpdate(const TouchHandle& handle, | |
241 const gfx::PointF& position) { | |
242 // As the position corresponds to the bottom left point of the selection | |
243 // bound, offset it by half the corresponding line height. | |
244 gfx::Vector2dF line_offset = &handle == end_selection_handle_.get() | |
245 ? GetStartLineOffset() | |
246 : GetEndLineOffset(); | |
247 gfx::PointF line_position = position + line_offset; | |
248 if (&handle == insertion_handle_.get()) { | |
249 client_->MoveCaret(line_position); | |
250 } else { | |
251 client_->MoveRangeSelectionExtent(line_position); | |
252 } | |
253 } | |
254 | |
255 void TouchSelectionController::OnHandleDragEnd(const TouchHandle& handle) { | |
256 if (&handle != insertion_handle_.get()) | |
257 client_->OnSelectionEvent(SELECTION_DRAG_STOPPED, handle.position()); | |
258 } | |
259 | |
260 void TouchSelectionController::OnHandleTapped(const TouchHandle& handle) { | |
261 if (insertion_handle_ && &handle == insertion_handle_.get()) | |
262 client_->OnSelectionEvent(INSERTION_TAPPED, handle.position()); | |
263 } | |
264 | |
265 void TouchSelectionController::SetNeedsAnimate() { | |
266 client_->SetNeedsAnimate(); | |
267 } | |
268 | |
269 scoped_ptr<TouchHandleDrawable> TouchSelectionController::CreateDrawable() { | |
270 return client_->CreateDrawable(); | |
271 } | |
272 | |
273 base::TimeDelta TouchSelectionController::GetTapTimeout() const { | |
274 return tap_timeout_; | |
275 } | |
276 | |
277 float TouchSelectionController::GetTapSlop() const { | |
278 return tap_slop_; | |
279 } | |
280 | |
281 void TouchSelectionController::ShowInsertionHandleAutomatically() { | |
282 if (activate_insertion_automatically_) | |
283 return; | |
284 activate_insertion_automatically_ = true; | |
285 ResetCachedValuesIfInactive(); | |
286 } | |
287 | |
288 void TouchSelectionController::ShowSelectionHandlesAutomatically() { | |
289 if (activate_selection_automatically_) | |
290 return; | |
291 activate_selection_automatically_ = true; | |
292 ResetCachedValuesIfInactive(); | |
293 } | |
294 | |
295 void TouchSelectionController::OnInsertionChanged() { | |
296 DeactivateSelection(); | |
297 | |
298 if (response_pending_input_event_ == TAP && selection_empty_) { | |
299 HideAndDisallowShowingAutomatically(); | |
300 return; | |
301 } | |
302 | |
303 if (!activate_insertion_automatically_) | |
304 return; | |
305 | |
306 const bool was_active = is_insertion_active_; | |
307 const gfx::PointF position = GetStartPosition(); | |
308 if (!is_insertion_active_) | |
309 ActivateInsertion(); | |
310 else | |
311 client_->OnSelectionEvent(INSERTION_MOVED, position); | |
312 | |
313 insertion_handle_->SetVisible(GetStartVisible(), | |
314 GetAnimationStyle(was_active)); | |
315 insertion_handle_->SetPosition(position); | |
316 } | |
317 | |
318 void TouchSelectionController::OnSelectionChanged() { | |
319 DeactivateInsertion(); | |
320 | |
321 if (!activate_selection_automatically_) | |
322 return; | |
323 | |
324 const bool was_active = is_selection_active_; | |
325 ActivateSelection(); | |
326 | |
327 const TouchHandle::AnimationStyle animation = GetAnimationStyle(was_active); | |
328 start_selection_handle_->SetVisible(GetStartVisible(), animation); | |
329 end_selection_handle_->SetVisible(GetEndVisible(), animation); | |
330 | |
331 start_selection_handle_->SetPosition(GetStartPosition()); | |
332 end_selection_handle_->SetPosition(GetEndPosition()); | |
333 } | |
334 | |
335 void TouchSelectionController::ActivateInsertion() { | |
336 DCHECK(!is_selection_active_); | |
337 | |
338 if (!insertion_handle_) | |
339 insertion_handle_.reset(new TouchHandle(this, TOUCH_HANDLE_CENTER)); | |
340 | |
341 if (!is_insertion_active_) { | |
342 is_insertion_active_ = true; | |
343 insertion_handle_->SetEnabled(true); | |
344 client_->OnSelectionEvent(INSERTION_SHOWN, GetStartPosition()); | |
345 } | |
346 } | |
347 | |
348 void TouchSelectionController::DeactivateInsertion() { | |
349 if (!is_insertion_active_) | |
350 return; | |
351 DCHECK(insertion_handle_); | |
352 is_insertion_active_ = false; | |
353 insertion_handle_->SetEnabled(false); | |
354 client_->OnSelectionEvent(INSERTION_CLEARED, gfx::PointF()); | |
355 } | |
356 | |
357 void TouchSelectionController::ActivateSelection() { | |
358 DCHECK(!is_insertion_active_); | |
359 | |
360 if (!start_selection_handle_) { | |
361 start_selection_handle_.reset(new TouchHandle(this, start_orientation_)); | |
362 } else { | |
363 start_selection_handle_->SetEnabled(true); | |
364 start_selection_handle_->SetOrientation(start_orientation_); | |
365 } | |
366 | |
367 if (!end_selection_handle_) { | |
368 end_selection_handle_.reset(new TouchHandle(this, end_orientation_)); | |
369 } else { | |
370 end_selection_handle_->SetEnabled(true); | |
371 end_selection_handle_->SetOrientation(end_orientation_); | |
372 } | |
373 | |
374 // As a long press received while a selection is already active may trigger | |
375 // an entirely new selection, notify the client but avoid sending an | |
376 // intervening SELECTION_CLEARED update to avoid unnecessary state changes. | |
377 if (!is_selection_active_ || response_pending_input_event_ == LONG_PRESS) { | |
378 is_selection_active_ = true; | |
379 response_pending_input_event_ = INPUT_EVENT_TYPE_NONE; | |
380 client_->OnSelectionEvent(SELECTION_SHOWN, GetStartPosition()); | |
381 } | |
382 } | |
383 | |
384 void TouchSelectionController::DeactivateSelection() { | |
385 if (!is_selection_active_) | |
386 return; | |
387 DCHECK(start_selection_handle_); | |
388 DCHECK(end_selection_handle_); | |
389 start_selection_handle_->SetEnabled(false); | |
390 end_selection_handle_->SetEnabled(false); | |
391 is_selection_active_ = false; | |
392 client_->OnSelectionEvent(SELECTION_CLEARED, gfx::PointF()); | |
393 } | |
394 | |
395 void TouchSelectionController::ResetCachedValuesIfInactive() { | |
396 if (is_selection_active_ || is_insertion_active_) | |
397 return; | |
398 start_ = cc::ViewportSelectionBound(); | |
399 end_ = cc::ViewportSelectionBound(); | |
400 start_orientation_ = TOUCH_HANDLE_ORIENTATION_UNDEFINED; | |
401 end_orientation_ = TOUCH_HANDLE_ORIENTATION_UNDEFINED; | |
402 } | |
403 | |
404 const gfx::PointF& TouchSelectionController::GetStartPosition() const { | |
405 return start_.edge_bottom; | |
406 } | |
407 | |
408 const gfx::PointF& TouchSelectionController::GetEndPosition() const { | |
409 return end_.edge_bottom; | |
410 } | |
411 | |
412 gfx::Vector2dF TouchSelectionController::GetStartLineOffset() const { | |
413 return gfx::ScaleVector2d(start_.edge_top - start_.edge_bottom, 0.5f); | |
414 } | |
415 | |
416 gfx::Vector2dF TouchSelectionController::GetEndLineOffset() const { | |
417 return gfx::ScaleVector2d(end_.edge_top - end_.edge_bottom, 0.5f); | |
418 } | |
419 | |
420 bool TouchSelectionController::GetStartVisible() const { | |
421 return start_.visible && !temporarily_hidden_; | |
422 } | |
423 | |
424 bool TouchSelectionController::GetEndVisible() const { | |
425 return end_.visible && !temporarily_hidden_; | |
426 } | |
427 | |
428 TouchHandle::AnimationStyle TouchSelectionController::GetAnimationStyle( | |
429 bool was_active) const { | |
430 return was_active && client_->SupportsAnimation() | |
431 ? TouchHandle::ANIMATION_SMOOTH | |
432 : TouchHandle::ANIMATION_NONE; | |
433 } | |
434 | |
435 } // namespace content | |
OLD | NEW |