selection_controller.cc 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248
  1. // Copyright 2016 The Chromium Authors. All rights reserved.
  2. // Use of this source code is governed by a BSD-style license that can be
  3. // found in the LICENSE file.
  4. #include "ui/views/selection_controller.h"
  5. #include <algorithm>
  6. #include <vector>
  7. #include "base/cxx17_backports.h"
  8. #include "build/build_config.h"
  9. #include "ui/base/clipboard/clipboard.h"
  10. #include "ui/events/event.h"
  11. #include "ui/gfx/render_text.h"
  12. #include "ui/views/metrics.h"
  13. #include "ui/views/selection_controller_delegate.h"
  14. #include "ui/views/style/platform_style.h"
  15. #include "ui/views/view.h"
  16. namespace views {
  17. SelectionController::SelectionController(SelectionControllerDelegate* delegate)
  18. : aggregated_clicks_(0),
  19. delegate_(delegate),
  20. handles_selection_clipboard_(false) {
  21. // If selection clipboard is used, update it on a text selection.
  22. if (ui::Clipboard::IsSupportedClipboardBuffer(
  23. ui::ClipboardBuffer::kSelection)) {
  24. set_handles_selection_clipboard(true);
  25. }
  26. DCHECK(delegate);
  27. }
  28. bool SelectionController::OnMousePressed(
  29. const ui::MouseEvent& event,
  30. bool handled,
  31. InitialFocusStateOnMousePress initial_focus_state) {
  32. gfx::RenderText* render_text = GetRenderText();
  33. DCHECK(render_text);
  34. TrackMouseClicks(event);
  35. if (handled)
  36. return true;
  37. if (event.IsOnlyLeftMouseButton()) {
  38. first_drag_location_ = event.location();
  39. if (delegate_->SupportsDrag())
  40. delegate_->SetTextBeingDragged(false);
  41. switch (aggregated_clicks_) {
  42. case 0:
  43. // If the click location is within an existing selection, it may be a
  44. // potential drag and drop.
  45. if (delegate_->SupportsDrag() &&
  46. render_text->IsPointInSelection(event.location())) {
  47. delegate_->SetTextBeingDragged(true);
  48. } else {
  49. delegate_->OnBeforePointerAction();
  50. const bool selection_changed = render_text->MoveCursorToPoint(
  51. event.location(), event.IsShiftDown());
  52. delegate_->OnAfterPointerAction(false, selection_changed);
  53. }
  54. break;
  55. case 1:
  56. // Select the word at the click location on a double click.
  57. SelectWord(event.location());
  58. double_click_word_ = render_text->selection();
  59. break;
  60. case 2:
  61. // Select all the text on a triple click.
  62. SelectAll();
  63. break;
  64. default:
  65. NOTREACHED();
  66. }
  67. }
  68. if (event.IsOnlyRightMouseButton()) {
  69. if (PlatformStyle::kSelectAllOnRightClickWhenUnfocused &&
  70. initial_focus_state == InitialFocusStateOnMousePress::kUnFocused) {
  71. SelectAll();
  72. } else if (PlatformStyle::kSelectWordOnRightClick &&
  73. !render_text->IsPointInSelection(event.location()) &&
  74. IsInsideText(event.location())) {
  75. SelectWord(event.location());
  76. }
  77. }
  78. if (handles_selection_clipboard_ && event.IsOnlyMiddleMouseButton() &&
  79. !delegate_->IsReadOnly()) {
  80. delegate_->OnBeforePointerAction();
  81. const bool selection_changed =
  82. render_text->MoveCursorToPoint(event.location(), false);
  83. const bool text_changed = delegate_->PasteSelectionClipboard();
  84. delegate_->OnAfterPointerAction(text_changed,
  85. selection_changed | text_changed);
  86. }
  87. return true;
  88. }
  89. bool SelectionController::OnMouseDragged(const ui::MouseEvent& event) {
  90. DCHECK(GetRenderText());
  91. // If |drag_selection_timer_| is running, |last_drag_location_| will be used
  92. // to update the selection.
  93. last_drag_location_ = event.location();
  94. // Don't adjust the cursor on a potential drag and drop.
  95. if (delegate_->HasTextBeingDragged() || !event.IsOnlyLeftMouseButton())
  96. return true;
  97. // A timer is used to continuously scroll while selecting beyond side edges.
  98. const int x = event.location().x();
  99. const int width = delegate_->GetViewWidth();
  100. const int drag_selection_delay = delegate_->GetDragSelectionDelay();
  101. if ((x >= 0 && x <= width) || drag_selection_delay == 0) {
  102. drag_selection_timer_.Stop();
  103. SelectThroughLastDragLocation();
  104. } else if (!drag_selection_timer_.IsRunning()) {
  105. // Select through the edge of the visible text, then start the scroll timer.
  106. last_drag_location_.set_x(base::clamp(x, 0, width));
  107. SelectThroughLastDragLocation();
  108. drag_selection_timer_.Start(
  109. FROM_HERE, base::Milliseconds(drag_selection_delay), this,
  110. &SelectionController::SelectThroughLastDragLocation);
  111. }
  112. return true;
  113. }
  114. void SelectionController::OnMouseReleased(const ui::MouseEvent& event) {
  115. gfx::RenderText* render_text = GetRenderText();
  116. DCHECK(render_text);
  117. drag_selection_timer_.Stop();
  118. // Cancel suspected drag initiations, the user was clicking in the selection.
  119. if (delegate_->HasTextBeingDragged()) {
  120. delegate_->OnBeforePointerAction();
  121. const bool selection_changed =
  122. render_text->MoveCursorToPoint(event.location(), false);
  123. delegate_->OnAfterPointerAction(false, selection_changed);
  124. }
  125. if (delegate_->SupportsDrag())
  126. delegate_->SetTextBeingDragged(false);
  127. if (handles_selection_clipboard_ && !render_text->selection().is_empty())
  128. delegate_->UpdateSelectionClipboard();
  129. }
  130. void SelectionController::OnMouseCaptureLost() {
  131. gfx::RenderText* render_text = GetRenderText();
  132. DCHECK(render_text);
  133. drag_selection_timer_.Stop();
  134. if (handles_selection_clipboard_ && !render_text->selection().is_empty())
  135. delegate_->UpdateSelectionClipboard();
  136. }
  137. void SelectionController::OffsetDoubleClickWord(size_t offset) {
  138. double_click_word_.set_start(double_click_word_.start() + offset);
  139. double_click_word_.set_end(double_click_word_.end() + offset);
  140. }
  141. void SelectionController::TrackMouseClicks(const ui::MouseEvent& event) {
  142. if (event.IsOnlyLeftMouseButton()) {
  143. base::TimeDelta time_delta = event.time_stamp() - last_click_time_;
  144. if (!last_click_time_.is_null() &&
  145. time_delta.InMilliseconds() <= GetDoubleClickInterval() &&
  146. !View::ExceededDragThreshold(event.root_location() -
  147. last_click_root_location_)) {
  148. // Upon clicking after a triple click, the count should go back to
  149. // double click and alternate between double and triple. This assignment
  150. // maps 0 to 1, 1 to 2, 2 to 1.
  151. aggregated_clicks_ = (aggregated_clicks_ % 2) + 1;
  152. } else {
  153. aggregated_clicks_ = 0;
  154. }
  155. last_click_time_ = event.time_stamp();
  156. last_click_root_location_ = event.root_location();
  157. }
  158. }
  159. void SelectionController::SelectWord(const gfx::Point& point) {
  160. gfx::RenderText* render_text = GetRenderText();
  161. DCHECK(render_text);
  162. delegate_->OnBeforePointerAction();
  163. render_text->MoveCursorToPoint(point, false);
  164. render_text->SelectWord();
  165. delegate_->OnAfterPointerAction(false, true);
  166. }
  167. void SelectionController::SelectAll() {
  168. gfx::RenderText* render_text = GetRenderText();
  169. DCHECK(render_text);
  170. delegate_->OnBeforePointerAction();
  171. render_text->SelectAll(false);
  172. delegate_->OnAfterPointerAction(false, true);
  173. }
  174. gfx::RenderText* SelectionController::GetRenderText() {
  175. return delegate_->GetRenderTextForSelectionController();
  176. }
  177. void SelectionController::SelectThroughLastDragLocation() {
  178. gfx::RenderText* render_text = GetRenderText();
  179. DCHECK(render_text);
  180. delegate_->OnBeforePointerAction();
  181. // Note that |first_drag_location_| is only used when
  182. // RenderText::kDragToEndIfOutsideVerticalBounds, which is platform-specific.
  183. render_text->MoveCursorToPoint(last_drag_location_, true,
  184. first_drag_location_);
  185. if (aggregated_clicks_ == 1) {
  186. render_text->SelectWord();
  187. // Expand the selection so the initially selected word remains selected.
  188. gfx::Range selection = render_text->selection();
  189. const size_t min =
  190. std::min(selection.GetMin(), double_click_word_.GetMin());
  191. const size_t max =
  192. std::max(selection.GetMax(), double_click_word_.GetMax());
  193. const bool reversed = selection.is_reversed();
  194. selection.set_start(reversed ? max : min);
  195. selection.set_end(reversed ? min : max);
  196. render_text->SelectRange(selection);
  197. }
  198. delegate_->OnAfterPointerAction(false, true);
  199. }
  200. bool SelectionController::IsInsideText(const gfx::Point& point) {
  201. gfx::RenderText* render_text = GetRenderText();
  202. std::vector<gfx::Rect> bounds_rects = render_text->GetSubstringBounds(
  203. gfx::Range(0, render_text->text().length()));
  204. for (const auto& bounds : bounds_rects)
  205. if (bounds.Contains(point))
  206. return true;
  207. return false;
  208. }
  209. } // namespace views