| // Copyright 2014 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "content/browser/renderer_host/input/stylus_text_selector.h" |
| |
| #include "ui/events/event_constants.h" |
| #include "ui/events/gesture_detection/gesture_detector.h" |
| #include "ui/events/gesture_detection/gesture_provider_config_helper.h" |
| #include "ui/events/gesture_detection/motion_event.h" |
| |
| using ui::GestureDetector; |
| using ui::MotionEvent; |
| |
| namespace content { |
| namespace { |
| std::unique_ptr<GestureDetector> CreateGestureDetector( |
| ui::GestureListener* listener) { |
| GestureDetector::Config config = |
| ui::GetGestureProviderConfig( |
| ui::GestureProviderConfigType::CURRENT_PLATFORM) |
| .gesture_detector_config; |
| |
| ui::DoubleTapListener* null_double_tap_listener = nullptr; |
| |
| // Doubletap, showpress and longpress detection are not required, and |
| // should be explicitly disabled for efficiency. |
| std::unique_ptr<ui::GestureDetector> detector( |
| new ui::GestureDetector(config, listener, null_double_tap_listener)); |
| detector->set_longpress_enabled(false); |
| detector->set_showpress_enabled(false); |
| |
| return detector; |
| } |
| |
| } // namespace |
| |
| StylusTextSelector::StylusTextSelector(StylusTextSelectorClient* client) |
| : client_(client), |
| text_selection_triggered_(false), |
| secondary_button_pressed_(false), |
| drag_state_(NO_DRAG), |
| anchor_x_(0.0f), |
| anchor_y_(0.0f) { |
| DCHECK(client); |
| } |
| |
| StylusTextSelector::~StylusTextSelector() { |
| } |
| |
| bool StylusTextSelector::OnTouchEvent(const MotionEvent& event) { |
| // Only trigger selection on ACTION_DOWN to prevent partial touch or gesture |
| // sequences from being forwarded. |
| if (event.GetAction() == MotionEvent::ACTION_DOWN) |
| text_selection_triggered_ = ShouldStartTextSelection(event); |
| |
| if (!text_selection_triggered_) |
| return false; |
| |
| // For Android version < M, stylus button pressed state is BUTTON_SECONDARY. |
| // From Android M, this state has changed to BUTTON_STYLUS_PRIMARY. |
| secondary_button_pressed_ = |
| event.GetButtonState() == MotionEvent::BUTTON_SECONDARY || |
| event.GetButtonState() == MotionEvent::BUTTON_STYLUS_PRIMARY; |
| |
| switch (event.GetAction()) { |
| case MotionEvent::ACTION_DOWN: |
| drag_state_ = NO_DRAG; |
| anchor_x_ = event.GetX(); |
| anchor_y_ = event.GetY(); |
| break; |
| |
| case MotionEvent::ACTION_MOVE: |
| if (!secondary_button_pressed_) { |
| if (drag_state_ == DRAGGING_WITH_BUTTON_PRESSED) |
| drag_state_ = DRAGGING_WITH_BUTTON_RELEASED; |
| anchor_x_ = event.GetX(); |
| anchor_y_ = event.GetY(); |
| } |
| break; |
| |
| case MotionEvent::ACTION_UP: |
| case MotionEvent::ACTION_CANCEL: |
| if (drag_state_ == DRAGGING_WITH_BUTTON_PRESSED || |
| drag_state_ == DRAGGING_WITH_BUTTON_RELEASED) |
| client_->OnStylusSelectEnd(event.GetX(), event.GetY()); |
| drag_state_ = NO_DRAG; |
| break; |
| |
| case MotionEvent::ACTION_POINTER_UP: |
| case MotionEvent::ACTION_POINTER_DOWN: |
| break; |
| case MotionEvent::ACTION_NONE: |
| case MotionEvent::ACTION_HOVER_ENTER: |
| case MotionEvent::ACTION_HOVER_EXIT: |
| case MotionEvent::ACTION_HOVER_MOVE: |
| case MotionEvent::ACTION_BUTTON_PRESS: |
| case MotionEvent::ACTION_BUTTON_RELEASE: |
| NOTREACHED(); |
| break; |
| } |
| |
| if (!gesture_detector_) |
| gesture_detector_ = CreateGestureDetector(this); |
| |
| gesture_detector_->OnTouchEvent(event); |
| |
| // Always return true, even if |gesture_detector_| technically doesn't |
| // consume the event. This prevents forwarding of a partial touch stream. |
| return true; |
| } |
| |
| bool StylusTextSelector::OnSingleTapUp(const MotionEvent& e, int tap_count) { |
| DCHECK(text_selection_triggered_); |
| DCHECK_NE(DRAGGING_WITH_BUTTON_PRESSED, drag_state_); |
| client_->OnStylusSelectTap(e.GetEventTime(), e.GetX(), e.GetY()); |
| return true; |
| } |
| |
| bool StylusTextSelector::OnScroll(const MotionEvent& e1, |
| const MotionEvent& e2, |
| const MotionEvent& secondary_pointer_down, |
| float distance_x, |
| float distance_y) { |
| DCHECK(text_selection_triggered_); |
| |
| // Return if Stylus button is not pressed. |
| if (!secondary_button_pressed_) |
| return true; |
| |
| if (drag_state_ == NO_DRAG || drag_state_ == DRAGGING_WITH_BUTTON_RELEASED) { |
| drag_state_ = DRAGGING_WITH_BUTTON_PRESSED; |
| client_->OnStylusSelectBegin(anchor_x_, anchor_y_, e2.GetX(), e2.GetY()); |
| } else { |
| client_->OnStylusSelectUpdate(e2.GetX(), e2.GetY()); |
| } |
| |
| return true; |
| } |
| |
| // static |
| bool StylusTextSelector::ShouldStartTextSelection(const MotionEvent& event) { |
| DCHECK_GT(event.GetPointerCount(), 0u); |
| // Currently we are supporting stylus-only cases. |
| const bool is_stylus = event.GetToolType(0) == MotionEvent::TOOL_TYPE_STYLUS; |
| |
| // For Android version < M, stylus button pressed state is BUTTON_SECONDARY. |
| // From Android M, this state has changed to BUTTON_STYLUS_PRIMARY. |
| const bool is_only_secondary_button_pressed = |
| event.GetButtonState() == MotionEvent::BUTTON_SECONDARY || |
| event.GetButtonState() == MotionEvent::BUTTON_STYLUS_PRIMARY; |
| return is_stylus && is_only_secondary_button_pressed; |
| } |
| |
| } // namespace content |