Chromium Code Reviews| 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..23e94823add559a08e535d2837cbc01c24913266 |
| --- /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 (handles_selection_clipboard_ && !render_text->selection().is_empty()) |
|
karandeepb
2016/10/26 02:27:39
Also added a couple of these checks.
|
| + delegate_->UpdateSelectionClipboard(); |
| +} |
| + |
| +void SelectionController::OnMouseCaptureLost() { |
| + gfx::RenderText* render_text = GetRenderText(); |
| + DCHECK(render_text); |
| + |
| + drag_selection_timer_.Stop(); |
| + |
| + if (handles_selection_clipboard_ && !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 |