blob: 033a0f54b70e9f55c1271cdf6472b80c5bd5e134 [file] [log] [blame]
// Copyright 2015 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/touch_selection_controller_client_aura.h"
#include <set>
#include "base/macros.h"
#include "content/browser/renderer_host/render_widget_host_delegate.h"
#include "content/browser/renderer_host/render_widget_host_impl.h"
#include "content/browser/renderer_host/render_widget_host_view_aura.h"
#include "content/common/view_messages.h"
#include "content/public/browser/render_view_host.h"
#include "content/public/common/context_menu_params.h"
#include "ui/aura/client/cursor_client.h"
#include "ui/aura/client/screen_position_client.h"
#include "ui/aura/env.h"
#include "ui/aura/window.h"
#include "ui/base/clipboard/clipboard.h"
#include "ui/events/event_observer.h"
#include "ui/gfx/geometry/point_conversions.h"
#include "ui/gfx/geometry/size_conversions.h"
#include "ui/strings/grit/ui_strings.h"
#include "ui/touch_selection/touch_handle_drawable_aura.h"
#include "ui/touch_selection/touch_selection_menu_runner.h"
namespace content {
namespace {
// Delay before showing the quick menu, in milliseconds.
const int kQuickMenuDelayInMs = 100;
gfx::Rect ConvertRectToScreen(aura::Window* window, const gfx::RectF& rect) {
gfx::Point origin = gfx::ToRoundedPoint(rect.origin());
gfx::Point bottom_right = gfx::ToRoundedPoint(rect.bottom_right());
aura::Window* root_window = window->GetRootWindow();
if (root_window) {
aura::client::ScreenPositionClient* screen_position_client =
aura::client::GetScreenPositionClient(root_window);
if (screen_position_client) {
screen_position_client->ConvertPointToScreen(window, &origin);
screen_position_client->ConvertPointToScreen(window, &bottom_right);
}
}
return gfx::Rect(origin.x(), origin.y(), bottom_right.x() - origin.x(),
bottom_right.y() - origin.y());
}
} // namespace
// An aura::Env event observer that hides touch selection ui on mouse and
// keyboard events, including those targeting windows outside the client.
class TouchSelectionControllerClientAura::EnvEventObserver
: public ui::EventObserver {
public:
EnvEventObserver(ui::TouchSelectionController* selection_controller,
aura::Window* window)
: selection_controller_(selection_controller), window_(window) {
// 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);
}
~EnvEventObserver() override {
aura::Env::GetInstance()->RemoveEventObserver(this);
}
private:
// ui::EventObserver:
void OnEvent(const ui::Event& event) override {
DCHECK_NE(ui::TouchSelectionController::INACTIVE,
selection_controller_->active_status());
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(window_->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;
}
}
selection_controller_->HideAndDisallowShowingAutomatically();
}
ui::TouchSelectionController* selection_controller_;
aura::Window* window_;
DISALLOW_COPY_AND_ASSIGN(EnvEventObserver);
};
TouchSelectionControllerClientAura::TouchSelectionControllerClientAura(
RenderWidgetHostViewAura* rwhva)
: rwhva_(rwhva),
internal_client_(rwhva),
active_client_(&internal_client_),
active_menu_client_(this),
quick_menu_timer_(
FROM_HERE,
base::TimeDelta::FromMilliseconds(kQuickMenuDelayInMs),
base::Bind(&TouchSelectionControllerClientAura::ShowQuickMenu,
base::Unretained(this))),
quick_menu_requested_(false),
touch_down_(false),
scroll_in_progress_(false),
handle_drag_in_progress_(false),
show_quick_menu_immediately_for_test_(false) {
DCHECK(rwhva_);
}
TouchSelectionControllerClientAura::~TouchSelectionControllerClientAura() {
for (auto& observer : observers_)
observer.OnManagerWillDestroy(this);
}
void TouchSelectionControllerClientAura::OnWindowMoved() {
UpdateQuickMenu();
}
void TouchSelectionControllerClientAura::OnTouchDown() {
touch_down_ = true;
UpdateQuickMenu();
}
void TouchSelectionControllerClientAura::OnTouchUp() {
touch_down_ = false;
UpdateQuickMenu();
}
void TouchSelectionControllerClientAura::OnScrollStarted() {
scroll_in_progress_ = true;
rwhva_->selection_controller()->SetTemporarilyHidden(true);
UpdateQuickMenu();
}
void TouchSelectionControllerClientAura::OnScrollCompleted() {
scroll_in_progress_ = false;
active_client_->DidScroll();
rwhva_->selection_controller()->SetTemporarilyHidden(false);
UpdateQuickMenu();
}
bool TouchSelectionControllerClientAura::HandleContextMenu(
const ContextMenuParams& params) {
if ((params.source_type == ui::MENU_SOURCE_LONG_PRESS ||
params.source_type == ui::MENU_SOURCE_LONG_TAP) &&
params.is_editable && params.selection_text.empty() &&
IsQuickMenuAvailable()) {
quick_menu_requested_ = true;
UpdateQuickMenu();
return true;
}
const bool from_touch = params.source_type == ui::MENU_SOURCE_LONG_PRESS ||
params.source_type == ui::MENU_SOURCE_LONG_TAP ||
params.source_type == ui::MENU_SOURCE_TOUCH;
if (from_touch && !params.selection_text.empty())
return true;
rwhva_->selection_controller()->HideAndDisallowShowingAutomatically();
return false;
}
void TouchSelectionControllerClientAura::DidStopFlinging() {
OnScrollCompleted();
}
void TouchSelectionControllerClientAura::UpdateClientSelectionBounds(
const gfx::SelectionBound& start,
const gfx::SelectionBound& end) {
UpdateClientSelectionBounds(start, end, &internal_client_, this);
}
void TouchSelectionControllerClientAura::UpdateClientSelectionBounds(
const gfx::SelectionBound& start,
const gfx::SelectionBound& end,
ui::TouchSelectionControllerClient* client,
ui::TouchSelectionMenuClient* menu_client) {
if (client != active_client_ &&
(start.type() == gfx::SelectionBound::EMPTY || !start.visible()) &&
(end.type() == gfx::SelectionBound::EMPTY || !end.visible()) &&
(manager_selection_start_.type() != gfx::SelectionBound::EMPTY ||
manager_selection_end_.type() != gfx::SelectionBound::EMPTY)) {
return;
}
active_client_ = client;
active_menu_client_ = menu_client;
manager_selection_start_ = start;
manager_selection_end_ = end;
// Notify TouchSelectionController if anything should change here. Only
// update if the client is different and not making a change to empty, or
// is the same client.
GetTouchSelectionController()->OnSelectionBoundsChanged(start, end);
}
void TouchSelectionControllerClientAura::InvalidateClient(
ui::TouchSelectionControllerClient* client) {
DCHECK(client != &internal_client_);
if (client == active_client_) {
active_client_ = &internal_client_;
active_menu_client_ = this;
}
}
ui::TouchSelectionController*
TouchSelectionControllerClientAura::GetTouchSelectionController() {
return rwhva_->selection_controller();
}
void TouchSelectionControllerClientAura::AddObserver(
TouchSelectionControllerClientManager::Observer* observer) {
observers_.AddObserver(observer);
}
void TouchSelectionControllerClientAura::RemoveObserver(
TouchSelectionControllerClientManager::Observer* observer) {
observers_.RemoveObserver(observer);
}
bool TouchSelectionControllerClientAura::IsQuickMenuAvailable() const {
return ui::TouchSelectionMenuRunner::GetInstance() &&
ui::TouchSelectionMenuRunner::GetInstance()->IsMenuAvailable(
active_menu_client_);
}
void TouchSelectionControllerClientAura::ShowQuickMenu() {
if (!ui::TouchSelectionMenuRunner::GetInstance())
return;
gfx::RectF rect = rwhva_->selection_controller()->GetRectBetweenBounds();
// Clip rect, which is in |rwhva_|'s window's coordinate space, to client
// bounds.
gfx::PointF origin = rect.origin();
gfx::PointF bottom_right = rect.bottom_right();
auto client_bounds = gfx::RectF(rwhva_->GetNativeView()->bounds());
origin.SetToMax(client_bounds.origin());
bottom_right.SetToMin(client_bounds.bottom_right());
if (origin.x() > bottom_right.x() || origin.y() > bottom_right.y())
return;
gfx::Vector2dF diagonal = bottom_right - origin;
gfx::SizeF size(diagonal.x(), diagonal.y());
gfx::RectF anchor_rect(origin, size);
// Calculate maximum handle image size;
gfx::SizeF max_handle_size =
rwhva_->selection_controller()->GetStartHandleRect().size();
max_handle_size.SetToMax(
rwhva_->selection_controller()->GetEndHandleRect().size());
aura::Window* parent = rwhva_->GetNativeView();
ui::TouchSelectionMenuRunner::GetInstance()->OpenMenu(
active_menu_client_, ConvertRectToScreen(parent, anchor_rect),
gfx::ToRoundedSize(max_handle_size), parent->GetToplevelWindow());
}
void TouchSelectionControllerClientAura::UpdateQuickMenu() {
bool menu_is_showing =
ui::TouchSelectionMenuRunner::GetInstance() &&
ui::TouchSelectionMenuRunner::GetInstance()->IsRunning();
// Hide the quick menu if there is any. This should happen even if the menu
// should be shown again, in order to update its location or content.
if (menu_is_showing)
ui::TouchSelectionMenuRunner::GetInstance()->CloseMenu();
else
quick_menu_timer_.Stop();
// Start timer to show quick menu if necessary.
if (ShouldShowQuickMenu()) {
if (show_quick_menu_immediately_for_test_)
ShowQuickMenu();
else
quick_menu_timer_.Reset();
}
}
bool TouchSelectionControllerClientAura::SupportsAnimation() const {
// We don't pass this to the active client, since it is assumed it will have
// the same behaviour as the Aura client.
return false;
}
bool TouchSelectionControllerClientAura::InternalClient::SupportsAnimation()
const {
NOTREACHED();
return false;
}
void TouchSelectionControllerClientAura::SetNeedsAnimate() {
NOTREACHED();
}
void TouchSelectionControllerClientAura::InternalClient::SetNeedsAnimate() {
NOTREACHED();
}
void TouchSelectionControllerClientAura::MoveCaret(
const gfx::PointF& position) {
active_client_->MoveCaret(position);
}
void TouchSelectionControllerClientAura::InternalClient::MoveCaret(
const gfx::PointF& position) {
RenderWidgetHostDelegate* host_delegate = rwhva_->host()->delegate();
if (host_delegate)
host_delegate->MoveCaret(gfx::ToRoundedPoint(position));
}
void TouchSelectionControllerClientAura::MoveRangeSelectionExtent(
const gfx::PointF& extent) {
active_client_->MoveRangeSelectionExtent(extent);
}
void TouchSelectionControllerClientAura::InternalClient::
MoveRangeSelectionExtent(const gfx::PointF& extent) {
RenderWidgetHostDelegate* host_delegate = rwhva_->host()->delegate();
if (host_delegate)
host_delegate->MoveRangeSelectionExtent(gfx::ToRoundedPoint(extent));
}
void TouchSelectionControllerClientAura::SelectBetweenCoordinates(
const gfx::PointF& base,
const gfx::PointF& extent) {
active_client_->SelectBetweenCoordinates(base, extent);
}
void TouchSelectionControllerClientAura::InternalClient::
SelectBetweenCoordinates(const gfx::PointF& base,
const gfx::PointF& extent) {
RenderWidgetHostDelegate* host_delegate = rwhva_->host()->delegate();
if (host_delegate) {
host_delegate->SelectRange(gfx::ToRoundedPoint(base),
gfx::ToRoundedPoint(extent));
}
}
void TouchSelectionControllerClientAura::OnSelectionEvent(
ui::SelectionEventType event) {
// This function (implicitly) uses active_menu_client_, so we don't go to the
// active view for this.
switch (event) {
case ui::SELECTION_HANDLES_SHOWN:
quick_menu_requested_ = true;
FALLTHROUGH;
case ui::INSERTION_HANDLE_SHOWN:
UpdateQuickMenu();
env_event_observer_ = std::make_unique<EnvEventObserver>(
rwhva_->selection_controller(), rwhva_->GetNativeView());
break;
case ui::SELECTION_HANDLES_CLEARED:
case ui::INSERTION_HANDLE_CLEARED:
env_event_observer_.reset();
quick_menu_requested_ = false;
UpdateQuickMenu();
break;
case ui::SELECTION_HANDLE_DRAG_STARTED:
case ui::INSERTION_HANDLE_DRAG_STARTED:
handle_drag_in_progress_ = true;
UpdateQuickMenu();
break;
case ui::SELECTION_HANDLE_DRAG_STOPPED:
case ui::INSERTION_HANDLE_DRAG_STOPPED:
handle_drag_in_progress_ = false;
UpdateQuickMenu();
break;
case ui::SELECTION_HANDLES_MOVED:
case ui::INSERTION_HANDLE_MOVED:
UpdateQuickMenu();
break;
case ui::INSERTION_HANDLE_TAPPED:
quick_menu_requested_ = !quick_menu_requested_;
UpdateQuickMenu();
break;
}
}
void TouchSelectionControllerClientAura::InternalClient::OnSelectionEvent(
ui::SelectionEventType event) {
NOTREACHED();
}
void TouchSelectionControllerClientAura::OnDragUpdate(
const gfx::PointF& position) {}
void TouchSelectionControllerClientAura::InternalClient::OnDragUpdate(
const gfx::PointF& position) {
NOTREACHED();
}
std::unique_ptr<ui::TouchHandleDrawable>
TouchSelectionControllerClientAura::CreateDrawable() {
// This function is purely related to the top-level view's window, so it
// is always handled here and never in
// TouchSelectionControllerClientChildFrame.
return std::unique_ptr<ui::TouchHandleDrawable>(
new ui::TouchHandleDrawableAura(rwhva_->GetNativeView()));
}
void TouchSelectionControllerClientAura::DidScroll() {}
std::unique_ptr<ui::TouchHandleDrawable>
TouchSelectionControllerClientAura::InternalClient::CreateDrawable() {
NOTREACHED();
return nullptr;
}
// Since the top-level client can only ever have its selection position changed
// by a mainframe scroll, or an actual change in the selection, and since both
// of these will initiate a compositor frame and thus the regular update
// process, there is nothing to do here.
void TouchSelectionControllerClientAura::InternalClient::DidScroll() {}
bool TouchSelectionControllerClientAura::IsCommandIdEnabled(
int command_id) const {
bool editable = rwhva_->GetTextInputType() != ui::TEXT_INPUT_TYPE_NONE;
bool readable = rwhva_->GetTextInputType() != ui::TEXT_INPUT_TYPE_PASSWORD;
bool has_selection = !rwhva_->GetSelectedText().empty();
switch (command_id) {
case IDS_APP_CUT:
return editable && readable && has_selection;
case IDS_APP_COPY:
return readable && has_selection;
case IDS_APP_PASTE: {
base::string16 result;
ui::Clipboard::GetForCurrentThread()->ReadText(
ui::CLIPBOARD_TYPE_COPY_PASTE, &result);
return editable && !result.empty();
}
default:
return false;
}
}
void TouchSelectionControllerClientAura::ExecuteCommand(int command_id,
int event_flags) {
rwhva_->selection_controller()->HideAndDisallowShowingAutomatically();
RenderWidgetHostDelegate* host_delegate = rwhva_->host()->delegate();
if (!host_delegate)
return;
switch (command_id) {
case IDS_APP_CUT:
host_delegate->Cut();
break;
case IDS_APP_COPY:
host_delegate->Copy();
break;
case IDS_APP_PASTE:
host_delegate->Paste();
break;
default:
NOTREACHED();
break;
}
}
void TouchSelectionControllerClientAura::RunContextMenu() {
gfx::RectF anchor_rect =
rwhva_->selection_controller()->GetRectBetweenBounds();
gfx::PointF anchor_point =
gfx::PointF(anchor_rect.CenterPoint().x(), anchor_rect.y());
RenderWidgetHostImpl* host = rwhva_->host();
host->ShowContextMenuAtPoint(gfx::ToRoundedPoint(anchor_point),
ui::MENU_SOURCE_TOUCH_EDIT_MENU);
// Hide selection handles after getting rect-between-bounds from touch
// selection controller; otherwise, rect would be empty and the above
// calculations would be invalid.
rwhva_->selection_controller()->HideAndDisallowShowingAutomatically();
}
bool TouchSelectionControllerClientAura::ShouldShowQuickMenu() {
return quick_menu_requested_ && !touch_down_ && !scroll_in_progress_ &&
!handle_drag_in_progress_ && IsQuickMenuAvailable();
}
base::string16 TouchSelectionControllerClientAura::GetSelectedText() {
return rwhva_->GetSelectedText();
}
} // namespace content