| Index: ui/views/selection_controller.cc
|
| diff --git a/ui/views/selection_controller.cc b/ui/views/selection_controller.cc
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..1a41e1c9fdd6f9193909013358049b0be077a55e
|
| --- /dev/null
|
| +++ b/ui/views/selection_controller.cc
|
| @@ -0,0 +1,203 @@
|
| +// Copyright 2016 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/selection_controller.h"
|
| +
|
| +#include <algorithm>
|
| +
|
| +#include "ui/events/event.h"
|
| +#include "ui/gfx/render_text.h"
|
| +#include "ui/views/metrics.h"
|
| +#include "ui/views/selection_controller_delegate.h"
|
| +#include "ui/views/style/platform_style.h"
|
| +#include "ui/views/view.h"
|
| +
|
| +namespace views {
|
| +
|
| +SelectionController::SelectionController(SelectionControllerDelegate* delegate)
|
| + : aggregated_clicks_(0),
|
| + delegate_(delegate),
|
| + handles_selection_clipboard_(false) {
|
| + DCHECK(delegate);
|
| +}
|
| +
|
| +bool SelectionController::OnMousePressed(const ui::MouseEvent& event,
|
| + bool handled) {
|
| + gfx::RenderText* render_text = GetRenderText();
|
| + DCHECK(render_text);
|
| +
|
| + TrackMouseClicks(event);
|
| + if (handled)
|
| + return true;
|
| +
|
| + if (event.IsOnlyLeftMouseButton()) {
|
| + delegate_->SetTextBeingDragged(false);
|
| + switch (aggregated_clicks_) {
|
| + case 0:
|
| + // If the click location is within an existing selection, it may be a
|
| + // potential drag and drop.
|
| + if (render_text->IsPointInSelection(event.location())) {
|
| + delegate_->SetTextBeingDragged(true);
|
| + } else {
|
| + delegate_->OnBeforePointerAction();
|
| + const bool selection_changed =
|
| + render_text->MoveCursorTo(event.location(), event.IsShiftDown());
|
| + delegate_->OnAfterPointerAction(false, selection_changed);
|
| + }
|
| + break;
|
| + case 1:
|
| + // Select the word at the click location on a double click.
|
| + delegate_->OnBeforePointerAction();
|
| + render_text->MoveCursorTo(event.location(), false);
|
| + render_text->SelectWord();
|
| + delegate_->OnAfterPointerAction(false, true);
|
| + double_click_word_ = render_text->selection();
|
| + break;
|
| + case 2:
|
| + // Select all the text on a triple click.
|
| + delegate_->OnBeforePointerAction();
|
| + render_text->SelectAll(false);
|
| + delegate_->OnAfterPointerAction(false, true);
|
| + break;
|
| + default:
|
| + NOTREACHED();
|
| + }
|
| + }
|
| +
|
| + if (handles_selection_clipboard_ && event.IsOnlyMiddleMouseButton()) {
|
| + if (render_text->IsPointInSelection(event.location())) {
|
| + delegate_->OnBeforePointerAction();
|
| + render_text->ClearSelection();
|
| + delegate_->UpdateSelectionClipboard();
|
| + delegate_->OnAfterPointerAction(false, true);
|
| + } else if (!delegate_->IsReadOnly()) {
|
| + delegate_->OnBeforePointerAction();
|
| + const bool selection_changed =
|
| + render_text->MoveCursorTo(event.location(), false);
|
| + const bool text_changed = delegate_->PasteSelectionClipboard();
|
| + delegate_->OnAfterPointerAction(text_changed,
|
| + selection_changed | text_changed);
|
| + }
|
| + }
|
| +
|
| + return true;
|
| +}
|
| +
|
| +bool SelectionController::OnMouseDragged(const ui::MouseEvent& event) {
|
| + DCHECK(GetRenderText());
|
| + // If |drag_selection_timer_| is running, |last_drag_location_| will be used
|
| + // to update the selection.
|
| + last_drag_location_ = event.location();
|
| +
|
| + // Don't adjust the cursor on a potential drag and drop.
|
| + if (delegate_->HasTextBeingDragged() || !event.IsOnlyLeftMouseButton())
|
| + return true;
|
| +
|
| + // A timer is used to continuously scroll while selecting beyond side edges.
|
| + const int x = event.location().x();
|
| + const int width = delegate_->GetViewWidth();
|
| + const int drag_selection_delay = delegate_->GetDragSelectionDelay();
|
| + if ((x >= 0 && x <= width) || drag_selection_delay == 0) {
|
| + drag_selection_timer_.Stop();
|
| + SelectThroughLastDragLocation();
|
| + } else if (!drag_selection_timer_.IsRunning()) {
|
| + // Select through the edge of the visible text, then start the scroll timer.
|
| + last_drag_location_.set_x(std::min(std::max(0, x), width));
|
| + SelectThroughLastDragLocation();
|
| +
|
| + drag_selection_timer_.Start(
|
| + FROM_HERE, base::TimeDelta::FromMilliseconds(drag_selection_delay),
|
| + this, &SelectionController::SelectThroughLastDragLocation);
|
| + }
|
| +
|
| + return true;
|
| +}
|
| +
|
| +void SelectionController::OnMouseReleased(const ui::MouseEvent& event) {
|
| + gfx::RenderText* render_text = GetRenderText();
|
| + DCHECK(render_text);
|
| +
|
| + drag_selection_timer_.Stop();
|
| +
|
| + // Cancel suspected drag initiations, the user was clicking in the selection.
|
| + if (delegate_->HasTextBeingDragged()) {
|
| + delegate_->OnBeforePointerAction();
|
| + const bool selection_changed =
|
| + render_text->MoveCursorTo(event.location(), false);
|
| + delegate_->OnAfterPointerAction(false, selection_changed);
|
| + }
|
| + delegate_->SetTextBeingDragged(false);
|
| +
|
| + if (!render_text->selection().is_empty())
|
| + delegate_->UpdateSelectionClipboard();
|
| +}
|
| +
|
| +void SelectionController::OnMouseCaptureLost() {
|
| + gfx::RenderText* render_text = GetRenderText();
|
| + DCHECK(render_text);
|
| +
|
| + drag_selection_timer_.Stop();
|
| +
|
| + if (!render_text->selection().is_empty())
|
| + delegate_->UpdateSelectionClipboard();
|
| +}
|
| +
|
| +void SelectionController::TrackMouseClicks(const ui::MouseEvent& event) {
|
| + if (event.IsOnlyLeftMouseButton()) {
|
| + base::TimeDelta time_delta = event.time_stamp() - last_click_time_;
|
| + if (!last_click_time_.is_null() &&
|
| + time_delta.InMilliseconds() <= GetDoubleClickInterval() &&
|
| + !View::ExceededDragThreshold(event.location() - last_click_location_)) {
|
| + // Upon clicking after a triple click, the count should go back to
|
| + // double click and alternate between double and triple. This assignment
|
| + // maps 0 to 1, 1 to 2, 2 to 1.
|
| + aggregated_clicks_ = (aggregated_clicks_ % 2) + 1;
|
| + } else {
|
| + aggregated_clicks_ = 0;
|
| + }
|
| + last_click_time_ = event.time_stamp();
|
| + last_click_location_ = event.location();
|
| + }
|
| +}
|
| +
|
| +gfx::RenderText* SelectionController::GetRenderText() {
|
| + return delegate_->GetRenderTextForSelectionController();
|
| +}
|
| +
|
| +void SelectionController::SelectThroughLastDragLocation() {
|
| + gfx::RenderText* render_text = GetRenderText();
|
| + DCHECK(render_text);
|
| +
|
| + delegate_->OnBeforePointerAction();
|
| +
|
| + // TODO(karandeepb): See if this can be handled at the RenderText level.
|
| + const bool drags_to_end = PlatformStyle::kTextfieldDragVerticallyDragsToEnd;
|
| + if (drags_to_end && last_drag_location_.y() < 0) {
|
| + render_text->MoveCursor(gfx::LINE_BREAK, gfx::CURSOR_LEFT,
|
| + gfx::SELECTION_RETAIN);
|
| + } else if (drags_to_end &&
|
| + last_drag_location_.y() > delegate_->GetViewHeight()) {
|
| + render_text->MoveCursor(gfx::LINE_BREAK, gfx::CURSOR_RIGHT,
|
| + gfx::SELECTION_RETAIN);
|
| + } else {
|
| + render_text->MoveCursorTo(last_drag_location_, true);
|
| + }
|
| +
|
| + if (aggregated_clicks_ == 1) {
|
| + render_text->SelectWord();
|
| + // Expand the selection so the initially selected word remains selected.
|
| + gfx::Range selection = render_text->selection();
|
| + const size_t min =
|
| + std::min(selection.GetMin(), double_click_word_.GetMin());
|
| + const size_t max =
|
| + std::max(selection.GetMax(), double_click_word_.GetMax());
|
| + const bool reversed = selection.is_reversed();
|
| + selection.set_start(reversed ? max : min);
|
| + selection.set_end(reversed ? min : max);
|
| + render_text->SelectRange(selection);
|
| + }
|
| + delegate_->OnAfterPointerAction(false, true);
|
| +}
|
| +
|
| +} // namespace views
|
|
|