Home | History | Annotate | Download | only in input
      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
    418