blob: f4d4a3dcc6f0067038d67f32105e0616585097b9 [file] [log] [blame]
// Copyright 2018 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 "ui/views/touchui/touch_selection_controller_impl.h"
#include <set>
#include "base/logging.h"
#include "base/metrics/histogram_macros.h"
#include "base/time/time.h"
#include "ui/aura/client/cursor_client.h"
#include "ui/aura/env.h"
#include "ui/aura/mus/window_port_mus.h"
#include "ui/aura/window.h"
#include "ui/aura/window_targeter.h"
#include "ui/base/resource/resource_bundle.h"
#include "ui/gfx/canvas.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/gfx/geometry/size.h"
#include "ui/gfx/image/image.h"
#include "ui/resources/grit/ui_resources.h"
#include "ui/views/views_delegate.h"
#include "ui/views/widget/widget.h"
#include "ui/views/widget/widget_delegate.h"
#include "ui/wm/core/coordinate_conversion.h"
namespace {
// Constants defining the visual attributes of selection handles
// The distance by which a handle image is offset from the bottom of the
// selection/text baseline.
const int kSelectionHandleVerticalVisualOffset = 2;
// When a handle is dragged, the drag position reported to the client view is
// offset vertically to represent the cursor position. This constant specifies
// the offset in pixels above the bottom of the selection (see pic below). This
// is required because say if this is zero, that means the drag position we
// report is right on the text baseline. In that case, a vertical movement of
// even one pixel will make the handle jump to the line below it. So when the
// user just starts dragging, the handle will jump to the next line if the user
// makes any vertical movement. So we have this non-zero offset to prevent this
// jumping.
//
// Editing handle widget showing the padding and difference between the position
// of the ET_GESTURE_SCROLL_UPDATE event and the drag position reported to the
// client:
// ___________
// Selection Highlight --->_____|__|<-|---- Drag position reported to client
// _ | O |
// Vertical Padding __| | <-|---- ET_GESTURE_SCROLL_UPDATE position
// |_ |_____|<--- Editing handle widget
//
// | |
// T
// Horizontal Padding
//
const int kSelectionHandleVerticalDragOffset = 5;
// Padding around the selection handle defining the area that will be included
// in the touch target to make dragging the handle easier (see pic above).
const int kSelectionHandleHorizPadding = 10;
const int kSelectionHandleVertPadding = 20;
const int kQuickMenuTimoutMs = 200;
const int kSelectionHandleQuickFadeDurationMs = 50;
// Minimum height for selection handle bar. If the bar height is going to be
// less than this value, handle will not be shown.
const int kSelectionHandleBarMinHeight = 5;
// Maximum amount that selection handle bar can stick out of client view's
// boundaries.
const int kSelectionHandleBarBottomAllowance = 3;
// Creates a widget to host SelectionHandleView.
views::Widget* CreateTouchSelectionPopupWidget(
gfx::NativeView parent,
views::WidgetDelegate* widget_delegate) {
views::Widget* widget = new views::Widget;
views::Widget::InitParams params(views::Widget::InitParams::TYPE_POPUP);
params.opacity = views::Widget::InitParams::TRANSLUCENT_WINDOW;
params.shadow_type = views::Widget::InitParams::SHADOW_TYPE_NONE;
params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
params.parent = parent;
params.delegate = widget_delegate;
widget->Init(params);
return widget;
}
gfx::Image* GetCenterHandleImage() {
static gfx::Image* handle_image = nullptr;
if (!handle_image) {
handle_image = &ui::ResourceBundle::GetSharedInstance().GetImageNamed(
IDR_TEXT_SELECTION_HANDLE_CENTER);
}
return handle_image;
}
gfx::Image* GetLeftHandleImage() {
static gfx::Image* handle_image = nullptr;
if (!handle_image) {
handle_image = &ui::ResourceBundle::GetSharedInstance().GetImageNamed(
IDR_TEXT_SELECTION_HANDLE_LEFT);
}
return handle_image;
}
gfx::Image* GetRightHandleImage() {
static gfx::Image* handle_image = nullptr;
if (!handle_image) {
handle_image = &ui::ResourceBundle::GetSharedInstance().GetImageNamed(
IDR_TEXT_SELECTION_HANDLE_RIGHT);
}
return handle_image;
}
// Return the appropriate handle image based on the bound's type
gfx::Image* GetHandleImage(gfx::SelectionBound::Type bound_type) {
switch (bound_type) {
case gfx::SelectionBound::LEFT:
return GetLeftHandleImage();
case gfx::SelectionBound::CENTER:
return GetCenterHandleImage();
case gfx::SelectionBound::RIGHT:
return GetRightHandleImage();
default:
NOTREACHED() << "Invalid touch handle bound type: " << bound_type;
return nullptr;
}
}
// Calculates the bounds of the widget containing the selection handle based
// on the SelectionBound's type and location.
gfx::Rect GetSelectionWidgetBounds(const gfx::SelectionBound& bound) {
gfx::Size image_size = GetHandleImage(bound.type())->Size();
int widget_width = image_size.width() + 2 * kSelectionHandleHorizPadding;
// Extend the widget height to handle touch events below the painted image.
int widget_height = bound.GetHeight() + image_size.height() +
kSelectionHandleVerticalVisualOffset +
kSelectionHandleVertPadding;
// Due to the shape of the handle images, the widget is aligned differently to
// the selection bound depending on the type of the bound.
int widget_left = 0;
switch (bound.type()) {
case gfx::SelectionBound::LEFT:
widget_left = bound.edge_top_rounded().x() - image_size.width() -
kSelectionHandleHorizPadding;
break;
case gfx::SelectionBound::RIGHT:
widget_left = bound.edge_top_rounded().x() - kSelectionHandleHorizPadding;
break;
case gfx::SelectionBound::CENTER:
widget_left = bound.edge_top_rounded().x() - widget_width / 2;
break;
default:
NOTREACHED() << "Undefined bound type.";
break;
}
return gfx::Rect(
widget_left, bound.edge_top_rounded().y(), widget_width, widget_height);
}
gfx::Size GetMaxHandleImageSize() {
gfx::Rect center_rect = gfx::Rect(GetCenterHandleImage()->Size());
gfx::Rect left_rect = gfx::Rect(GetLeftHandleImage()->Size());
gfx::Rect right_rect = gfx::Rect(GetRightHandleImage()->Size());
gfx::Rect union_rect = center_rect;
union_rect.Union(left_rect);
union_rect.Union(right_rect);
return union_rect.size();
}
// Convenience methods to convert a |bound| from screen to the |client|'s
// coordinate system and vice versa.
// Note that this is not quite correct because it does not take into account
// transforms such as rotation and scaling. This should be in TouchEditable.
// TODO(varunjain): Fix this.
gfx::SelectionBound ConvertFromScreen(ui::TouchEditable* client,
const gfx::SelectionBound& bound) {
gfx::SelectionBound result = bound;
gfx::Point edge_bottom = bound.edge_bottom_rounded();
gfx::Point edge_top = bound.edge_top_rounded();
client->ConvertPointFromScreen(&edge_bottom);
client->ConvertPointFromScreen(&edge_top);
result.SetEdge(gfx::PointF(edge_top), gfx::PointF(edge_bottom));
return result;
}
gfx::SelectionBound ConvertToScreen(ui::TouchEditable* client,
const gfx::SelectionBound& bound) {
gfx::SelectionBound result = bound;
gfx::Point edge_bottom = bound.edge_bottom_rounded();
gfx::Point edge_top = bound.edge_top_rounded();
client->ConvertPointToScreen(&edge_bottom);
client->ConvertPointToScreen(&edge_top);
result.SetEdge(gfx::PointF(edge_top), gfx::PointF(edge_bottom));
return result;
}
gfx::Rect BoundToRect(const gfx::SelectionBound& bound) {
return gfx::BoundingRect(bound.edge_top_rounded(),
bound.edge_bottom_rounded());
}
} // namespace
namespace views {
using EditingHandleView = TouchSelectionControllerImpl::EditingHandleView;
// A View that displays the text selection handle.
class TouchSelectionControllerImpl::EditingHandleView
: public WidgetDelegateView {
public:
EditingHandleView(TouchSelectionControllerImpl* controller,
gfx::NativeView parent,
bool is_cursor_handle)
: controller_(controller),
image_(GetCenterHandleImage()),
is_cursor_handle_(is_cursor_handle),
draw_invisible_(false),
weak_ptr_factory_(this) {
widget_.reset(CreateTouchSelectionPopupWidget(parent, this));
targeter_ = new aura::WindowTargeter();
aura::Window* window = widget_->GetNativeWindow();
// For Mus clients, adjust targeting of the handle's client root window,
// constructed by the window server for the handle's "content" window.
if (window->env()->mode() == aura::Env::Mode::MUS)
window = window->GetRootWindow();
window->SetEventTargeter(std::unique_ptr<aura::WindowTargeter>(targeter_));
// We are owned by the TouchSelectionControllerImpl.
set_owned_by_client();
}
~EditingHandleView() override { SetWidgetVisible(false, false); }
gfx::SelectionBound::Type selection_bound_type() {
return selection_bound_.type();
}
// WidgetDelegateView:
void DeleteDelegate() override {
// We are owned and deleted by TouchSelectionControllerImpl.
}
// View:
void OnPaint(gfx::Canvas* canvas) override {
if (draw_invisible_)
return;
// Draw the handle image.
canvas->DrawImageInt(
*image_->ToImageSkia(),
kSelectionHandleHorizPadding,
selection_bound_.GetHeight() + kSelectionHandleVerticalVisualOffset);
}
void OnGestureEvent(ui::GestureEvent* event) override {
event->SetHandled();
switch (event->type()) {
case ui::ET_GESTURE_SCROLL_BEGIN: {
widget_->SetCapture(this);
controller_->SetDraggingHandle(this);
// Distance from the point which is |kSelectionHandleVerticalDragOffset|
// pixels above the bottom of the selection bound edge to the event
// location (aka the touch-drag point).
drag_offset_ = selection_bound_.edge_bottom_rounded() -
gfx::Vector2d(0, kSelectionHandleVerticalDragOffset) -
event->location();
break;
}
case ui::ET_GESTURE_SCROLL_UPDATE: {
controller_->SelectionHandleDragged(event->location() + drag_offset_);
break;
}
case ui::ET_GESTURE_SCROLL_END:
case ui::ET_SCROLL_FLING_START: {
// Use a weak pointer to the handle to make sure the handle and its
// owning selection controller is not destroyed by the capture release
// to diagnose a crash on Windows (see crbug.com/459423)
// TODO(mohsen): Delete the diagnostics code when the crash is fixed.
base::WeakPtr<EditingHandleView> weak_ptr =
weak_ptr_factory_.GetWeakPtr();
widget_->ReleaseCapture();
CHECK(weak_ptr);
controller_->SetDraggingHandle(nullptr);
break;
}
default:
break;
}
}
gfx::Size CalculatePreferredSize() const override {
// This function will be called during widget initialization, i.e. before
// SetBoundInScreen has been called. No-op in that case.
if (selection_bound_.type() == gfx::SelectionBound::EMPTY)
return gfx::Size();
return GetSelectionWidgetBounds(selection_bound_).size();
}
bool IsWidgetVisible() const {
return widget_->IsVisible();
}
void SetWidgetVisible(bool visible, bool quick) {
if (widget_->IsVisible() == visible)
return;
widget_->SetVisibilityAnimationDuration(
base::TimeDelta::FromMilliseconds(
quick ? kSelectionHandleQuickFadeDurationMs : 0));
if (visible)
widget_->Show();
else
widget_->Hide();
}
// If |is_visible| is true, this will update the widget and trigger a repaint
// if necessary. Otherwise this will only update the internal state:
// |selection_bound_| and |image_|, so that the state is valid for the time
// this becomes visible.
void SetBoundInScreen(const gfx::SelectionBound& bound, bool is_visible) {
bool update_bound_type = false;
// Cursor handle should always have the bound type CENTER
DCHECK(!is_cursor_handle_ || bound.type() == gfx::SelectionBound::CENTER);
if (bound.type() != selection_bound_.type()) {
// Unless this is a cursor handle, do not set the type to CENTER -
// selection handles corresponding to a selection should always use left
// or right handle image. If selection handles are dragged to be located
// at the same spot, the |bound|'s type here will be CENTER for both of
// them. In this case do not update the type of the |selection_bound_|.
if (bound.type() != gfx::SelectionBound::CENTER || is_cursor_handle_)
update_bound_type = true;
}
if (update_bound_type) {
selection_bound_.set_type(bound.type());
image_ = GetHandleImage(bound.type());
if (is_visible)
SchedulePaint();
}
if (is_visible) {
selection_bound_.SetEdge(bound.edge_top(), bound.edge_bottom());
widget_->SetBounds(GetSelectionWidgetBounds(selection_bound_));
aura::Window* window = widget_->GetNativeView();
gfx::Point edge_top = selection_bound_.edge_top_rounded();
gfx::Point edge_bottom = selection_bound_.edge_bottom_rounded();
wm::ConvertPointFromScreen(window, &edge_top);
wm::ConvertPointFromScreen(window, &edge_bottom);
selection_bound_.SetEdge(gfx::PointF(edge_top), gfx::PointF(edge_bottom));
}
const gfx::Insets insets(
selection_bound_.GetHeight() + kSelectionHandleVerticalVisualOffset, 0,
0, 0);
targeter_->SetInsets(insets, insets);
}
void SetDrawInvisible(bool draw_invisible) {
if (draw_invisible_ == draw_invisible)
return;
draw_invisible_ = draw_invisible;
SchedulePaint();
}
private:
std::unique_ptr<Widget> widget_;
TouchSelectionControllerImpl* controller_;
// A WindowTargeter that shifts the hit-test target below the apparent bounds
// to make dragging easier. The |widget_|'s NativeWindow takes ownership over
// the |targeter_| but since the |widget_|'s lifetime is known to this class,
// it can safely access the |targeter_|.
aura::WindowTargeter* targeter_;
// In local coordinates
gfx::SelectionBound selection_bound_;
gfx::Image* image_;
// If true, this is a handle corresponding to the single cursor, otherwise it
// is a handle corresponding to one of the two selection bounds.
bool is_cursor_handle_;
// Offset applied to the scroll events location when calling
// TouchSelectionControllerImpl::SelectionHandleDragged while dragging the
// handle.
gfx::Vector2d drag_offset_;
// If set to true, the handle will not draw anything, hence providing an empty
// widget. We need this because we may want to stop showing the handle while
// it is being dragged. Since it is being dragged, we cannot destroy the
// handle.
bool draw_invisible_;
base::WeakPtrFactory<EditingHandleView> weak_ptr_factory_;
DISALLOW_COPY_AND_ASSIGN(EditingHandleView);
};
TouchSelectionControllerImpl::TouchSelectionControllerImpl(
ui::TouchEditable* client_view)
: client_view_(client_view),
selection_handle_1_(
new EditingHandleView(this, client_view->GetNativeView(), false)),
selection_handle_2_(
new EditingHandleView(this, client_view->GetNativeView(), false)),
cursor_handle_(
new EditingHandleView(this, client_view->GetNativeView(), true)) {
selection_start_time_ = base::TimeTicks::Now();
aura::Window* client_window = client_view_->GetNativeView();
client_widget_ = Widget::GetTopLevelWidgetForNativeView(client_window);
// Observe client widget moves and resizes to update the selection handles.
if (client_widget_)
client_widget_->AddObserver(this);
// Observe certain event types sent to any event target, to hide this ui.
aura::Env* env = aura::Env::GetInstance();
std::set<ui::EventType> types = {ui::ET_MOUSE_PRESSED, ui::ET_MOUSE_MOVED,
ui::ET_KEY_PRESSED, ui::ET_MOUSEWHEEL};
env->AddEventObserver(this, env, types);
}
TouchSelectionControllerImpl::~TouchSelectionControllerImpl() {
UMA_HISTOGRAM_BOOLEAN("Event.TouchSelection.EndedWithAction",
command_executed_);
HideQuickMenu();
aura::Env::GetInstance()->RemoveEventObserver(this);
if (client_widget_)
client_widget_->RemoveObserver(this);
}
void TouchSelectionControllerImpl::SelectionChanged() {
gfx::SelectionBound anchor, focus;
client_view_->GetSelectionEndPoints(&anchor, &focus);
gfx::SelectionBound screen_bound_anchor =
ConvertToScreen(client_view_, anchor);
gfx::SelectionBound screen_bound_focus = ConvertToScreen(client_view_, focus);
gfx::Rect client_bounds = client_view_->GetBounds();
if (anchor.edge_top().y() < client_bounds.y()) {
auto anchor_edge_top = gfx::PointF(anchor.edge_top_rounded());
anchor_edge_top.set_y(client_bounds.y());
anchor.SetEdgeTop(anchor_edge_top);
}
if (focus.edge_top().y() < client_bounds.y()) {
auto focus_edge_top = gfx::PointF(focus.edge_top_rounded());
focus_edge_top.set_y(client_bounds.y());
focus.SetEdgeTop(focus_edge_top);
}
gfx::SelectionBound screen_bound_anchor_clipped =
ConvertToScreen(client_view_, anchor);
gfx::SelectionBound screen_bound_focus_clipped =
ConvertToScreen(client_view_, focus);
if (screen_bound_anchor_clipped == selection_bound_1_clipped_ &&
screen_bound_focus_clipped == selection_bound_2_clipped_)
return;
selection_bound_1_ = screen_bound_anchor;
selection_bound_2_ = screen_bound_focus;
selection_bound_1_clipped_ = screen_bound_anchor_clipped;
selection_bound_2_clipped_ = screen_bound_focus_clipped;
if (client_view_->DrawsHandles()) {
UpdateQuickMenu();
return;
}
if (dragging_handle_) {
// We need to reposition only the selection handle that is being dragged.
// The other handle stays the same. Also, the selection handle being dragged
// will always be at the end of selection, while the other handle will be at
// the start.
// If the new location of this handle is out of client view, its widget
// should not get hidden, since it should still receive touch events.
// Hence, we are not using |SetHandleBound()| method here.
dragging_handle_->SetBoundInScreen(screen_bound_focus_clipped, true);
// Temporary fix for selection handle going outside a window. On a webpage,
// the page should scroll if the selection handle is dragged outside the
// window. That does not happen currently. So we just hide the handle for
// now.
// TODO(varunjain): Fix this: crbug.com/269003
dragging_handle_->SetDrawInvisible(!ShouldShowHandleFor(focus));
if (dragging_handle_ != cursor_handle_.get()) {
// The non-dragging-handle might have recently become visible.
EditingHandleView* non_dragging_handle = selection_handle_1_.get();
if (dragging_handle_ == selection_handle_1_.get()) {
non_dragging_handle = selection_handle_2_.get();
// if handle 1 is being dragged, it is corresponding to the end of
// selection and the other handle to the start of selection.
selection_bound_1_ = screen_bound_focus;
selection_bound_2_ = screen_bound_anchor;
selection_bound_1_clipped_ = screen_bound_focus_clipped;
selection_bound_2_clipped_ = screen_bound_anchor_clipped;
}
SetHandleBound(non_dragging_handle, anchor, screen_bound_anchor_clipped);
}
} else {
UpdateQuickMenu();
// Check if there is any selection at all.
if (screen_bound_anchor.edge_top() == screen_bound_focus.edge_top() &&
screen_bound_anchor.edge_bottom() == screen_bound_focus.edge_bottom()) {
selection_handle_1_->SetWidgetVisible(false, false);
selection_handle_2_->SetWidgetVisible(false, false);
SetHandleBound(cursor_handle_.get(), anchor, screen_bound_anchor_clipped);
return;
}
cursor_handle_->SetWidgetVisible(false, false);
SetHandleBound(
selection_handle_1_.get(), anchor, screen_bound_anchor_clipped);
SetHandleBound(
selection_handle_2_.get(), focus, screen_bound_focus_clipped);
}
}
bool TouchSelectionControllerImpl::IsHandleDragInProgress() {
return !!dragging_handle_;
}
void TouchSelectionControllerImpl::HideHandles(bool quick) {
selection_handle_1_->SetWidgetVisible(false, quick);
selection_handle_2_->SetWidgetVisible(false, quick);
cursor_handle_->SetWidgetVisible(false, quick);
}
void TouchSelectionControllerImpl::SetDraggingHandle(
EditingHandleView* handle) {
dragging_handle_ = handle;
if (dragging_handle_)
HideQuickMenu();
else
StartQuickMenuTimer();
}
void TouchSelectionControllerImpl::SelectionHandleDragged(
const gfx::Point& drag_pos) {
DCHECK(dragging_handle_);
gfx::Point drag_pos_in_client = drag_pos;
ConvertPointToClientView(dragging_handle_, &drag_pos_in_client);
if (dragging_handle_ == cursor_handle_.get()) {
client_view_->MoveCaretTo(drag_pos_in_client);
return;
}
// Find the stationary selection handle.
gfx::SelectionBound anchor_bound =
selection_handle_1_.get() == dragging_handle_ ? selection_bound_2_
: selection_bound_1_;
// Find selection end points in client_view's coordinate system.
gfx::Point p2 = anchor_bound.edge_top_rounded();
p2.Offset(0, anchor_bound.GetHeight() / 2);
client_view_->ConvertPointFromScreen(&p2);
// Instruct client_view to select the region between p1 and p2. The position
// of |fixed_handle| is the start and that of |dragging_handle| is the end
// of selection.
client_view_->SelectRect(p2, drag_pos_in_client);
}
void TouchSelectionControllerImpl::ConvertPointToClientView(
EditingHandleView* source, gfx::Point* point) {
View::ConvertPointToScreen(source, point);
client_view_->ConvertPointFromScreen(point);
}
void TouchSelectionControllerImpl::SetHandleBound(
EditingHandleView* handle,
const gfx::SelectionBound& bound,
const gfx::SelectionBound& bound_in_screen) {
handle->SetWidgetVisible(ShouldShowHandleFor(bound), false);
handle->SetBoundInScreen(bound_in_screen, handle->IsWidgetVisible());
}
bool TouchSelectionControllerImpl::ShouldShowHandleFor(
const gfx::SelectionBound& bound) const {
if (bound.GetHeight() < kSelectionHandleBarMinHeight)
return false;
gfx::Rect client_bounds = client_view_->GetBounds();
client_bounds.Inset(0, 0, 0, -kSelectionHandleBarBottomAllowance);
return client_bounds.Contains(BoundToRect(bound));
}
bool TouchSelectionControllerImpl::IsCommandIdEnabled(int command_id) const {
return client_view_->IsCommandIdEnabled(command_id);
}
void TouchSelectionControllerImpl::ExecuteCommand(int command_id,
int event_flags) {
command_executed_ = true;
base::TimeDelta duration = base::TimeTicks::Now() - selection_start_time_;
// Note that we only log the duration stats for the 'successful' selections,
// i.e. selections ending with the execution of a command.
UMA_HISTOGRAM_CUSTOM_TIMES("Event.TouchSelection.Duration",
duration,
base::TimeDelta::FromMilliseconds(500),
base::TimeDelta::FromSeconds(60),
60);
client_view_->ExecuteCommand(command_id, event_flags);
}
void TouchSelectionControllerImpl::RunContextMenu() {
// Context menu should appear centered on top of the selected region.
const gfx::Rect rect = GetQuickMenuAnchorRect();
const gfx::Point anchor(rect.CenterPoint().x(), rect.y());
client_view_->OpenContextMenu(anchor);
}
bool TouchSelectionControllerImpl::ShouldShowQuickMenu() {
NOTREACHED();
return false;
}
base::string16 TouchSelectionControllerImpl::GetSelectedText() {
NOTREACHED();
return base::string16();
}
void TouchSelectionControllerImpl::OnWidgetClosing(Widget* widget) {
DCHECK_EQ(client_widget_, widget);
client_widget_->RemoveObserver(this);
client_widget_ = nullptr;
}
void TouchSelectionControllerImpl::OnWidgetBoundsChanged(
Widget* widget,
const gfx::Rect& new_bounds) {
DCHECK_EQ(client_widget_, widget);
SelectionChanged();
}
void TouchSelectionControllerImpl::OnEvent(const ui::Event& event) {
if (event.IsMouseEvent()) {
// Check IsMouseEventsEnabled, except on Mus, where it's disabled on touch
// events in this client, but not re-enabled on mouse events elsewhere.
auto* cursor = aura::client::GetCursorClient(
client_view_->GetNativeView()->GetRootWindow());
if (cursor && !cursor->IsMouseEventsEnabled() &&
aura::Env::GetInstance()->mode() != aura::Env::Mode::MUS) {
return;
}
// Windows OS unhandled WM_POINTER* may be redispatched as WM_MOUSE*.
// Avoid adjusting the handles on synthesized events or events generated
// from touch as this can clear an active selection generated by the pen.
if ((event.flags() & (ui::EF_IS_SYNTHESIZED | ui::EF_FROM_TOUCH)) ||
event.AsMouseEvent()->pointer_details().pointer_type ==
ui::EventPointerType::POINTER_TYPE_PEN) {
return;
}
}
client_view_->DestroyTouchSelection();
}
void TouchSelectionControllerImpl::QuickMenuTimerFired() {
gfx::Rect menu_anchor = GetQuickMenuAnchorRect();
if (menu_anchor == gfx::Rect())
return;
ui::TouchSelectionMenuRunner::GetInstance()->OpenMenu(
this, menu_anchor, GetMaxHandleImageSize(),
client_view_->GetNativeView());
}
void TouchSelectionControllerImpl::StartQuickMenuTimer() {
if (quick_menu_timer_.IsRunning())
return;
quick_menu_timer_.Start(
FROM_HERE,
base::TimeDelta::FromMilliseconds(kQuickMenuTimoutMs),
this,
&TouchSelectionControllerImpl::QuickMenuTimerFired);
}
void TouchSelectionControllerImpl::UpdateQuickMenu() {
// Hide quick menu to be shown when the timer fires.
HideQuickMenu();
StartQuickMenuTimer();
}
void TouchSelectionControllerImpl::HideQuickMenu() {
if (ui::TouchSelectionMenuRunner::GetInstance()->IsRunning())
ui::TouchSelectionMenuRunner::GetInstance()->CloseMenu();
quick_menu_timer_.Stop();
}
gfx::Rect TouchSelectionControllerImpl::GetQuickMenuAnchorRect() const {
// Get selection end points in client_view's space.
gfx::SelectionBound b1_in_screen = selection_bound_1_clipped_;
gfx::SelectionBound b2_in_screen = cursor_handle_->IsWidgetVisible()
? b1_in_screen
: selection_bound_2_clipped_;
// Convert from screen to client.
gfx::SelectionBound b1 = ConvertFromScreen(client_view_, b1_in_screen);
gfx::SelectionBound b2 = ConvertFromScreen(client_view_, b2_in_screen);
// if selection is completely inside the view, we display the quick menu in
// the middle of the end points on the top. Else, we show it above the visible
// handle. If no handle is visible, we do not show the menu.
gfx::Rect menu_anchor;
if (ShouldShowHandleFor(b1) && ShouldShowHandleFor(b2))
menu_anchor = gfx::RectBetweenSelectionBounds(b1_in_screen, b2_in_screen);
else if (ShouldShowHandleFor(b1))
menu_anchor = BoundToRect(b1_in_screen);
else if (ShouldShowHandleFor(b2))
menu_anchor = BoundToRect(b2_in_screen);
else
return menu_anchor;
// Enlarge the anchor rect so that the menu is offset from the text at least
// by the same distance the handles are offset from the text.
menu_anchor.Inset(0, -kSelectionHandleVerticalVisualOffset);
return menu_anchor;
}
gfx::NativeView TouchSelectionControllerImpl::GetCursorHandleNativeView() {
return cursor_handle_->GetWidget()->GetNativeView();
}
gfx::SelectionBound::Type
TouchSelectionControllerImpl::GetSelectionHandle1Type() {
return selection_handle_1_->selection_bound_type();
}
gfx::Rect TouchSelectionControllerImpl::GetSelectionHandle1Bounds() {
return selection_handle_1_->GetBoundsInScreen();
}
gfx::Rect TouchSelectionControllerImpl::GetSelectionHandle2Bounds() {
return selection_handle_2_->GetBoundsInScreen();
}
gfx::Rect TouchSelectionControllerImpl::GetCursorHandleBounds() {
return cursor_handle_->GetBoundsInScreen();
}
bool TouchSelectionControllerImpl::IsSelectionHandle1Visible() {
return selection_handle_1_->IsWidgetVisible();
}
bool TouchSelectionControllerImpl::IsSelectionHandle2Visible() {
return selection_handle_2_->IsWidgetVisible();
}
bool TouchSelectionControllerImpl::IsCursorHandleVisible() {
return cursor_handle_->IsWidgetVisible();
}
gfx::Rect TouchSelectionControllerImpl::GetExpectedHandleBounds(
const gfx::SelectionBound& bound) {
return GetSelectionWidgetBounds(bound);
}
WidgetDelegateView* TouchSelectionControllerImpl::GetHandle1View() {
return selection_handle_1_.get();
}
WidgetDelegateView* TouchSelectionControllerImpl::GetHandle2View() {
return selection_handle_2_.get();
}
} // namespace views