Don't show supervised user as "already on this device" while they're being imported.
[chromium-blink-merge.git] / ui / views / touchui / touch_selection_controller_impl.cc
blob40787289eb8f0660f4da4fec5ac1b2279e186278
1 // Copyright (c) 2013 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.
5 #include "ui/views/touchui/touch_selection_controller_impl.h"
7 #include "base/metrics/histogram_macros.h"
8 #include "base/time/time.h"
9 #include "ui/aura/client/cursor_client.h"
10 #include "ui/aura/env.h"
11 #include "ui/aura/window.h"
12 #include "ui/base/resource/resource_bundle.h"
13 #include "ui/gfx/canvas.h"
14 #include "ui/gfx/geometry/rect.h"
15 #include "ui/gfx/geometry/size.h"
16 #include "ui/gfx/image/image.h"
17 #include "ui/gfx/path.h"
18 #include "ui/gfx/screen.h"
19 #include "ui/resources/grit/ui_resources.h"
20 #include "ui/strings/grit/ui_strings.h"
21 #include "ui/views/widget/widget.h"
22 #include "ui/views/widget/widget_delegate.h"
23 #include "ui/wm/core/coordinate_conversion.h"
24 #include "ui/wm/core/masked_window_targeter.h"
26 namespace {
28 // Constants defining the visual attributes of selection handles
30 // The distance by which a handle image is offset from the bottom of the
31 // selection/text baseline.
32 const int kSelectionHandleVerticalVisualOffset = 2;
34 // When a handle is dragged, the drag position reported to the client view is
35 // offset vertically to represent the cursor position. This constant specifies
36 // the offset in pixels above the bottom of the selection (see pic below). This
37 // is required because say if this is zero, that means the drag position we
38 // report is right on the text baseline. In that case, a vertical movement of
39 // even one pixel will make the handle jump to the line below it. So when the
40 // user just starts dragging, the handle will jump to the next line if the user
41 // makes any vertical movement. So we have this non-zero offset to prevent this
42 // jumping.
44 // Editing handle widget showing the padding and difference between the position
45 // of the ET_GESTURE_SCROLL_UPDATE event and the drag position reported to the
46 // client:
47 // ___________
48 // Selection Highlight --->_____|__|<-|---- Drag position reported to client
49 // _ | O |
50 // Vertical Padding __| | <-|---- ET_GESTURE_SCROLL_UPDATE position
51 // |_ |_____|<--- Editing handle widget
53 // | |
54 // T
55 // Horizontal Padding
57 const int kSelectionHandleVerticalDragOffset = 5;
59 // Padding around the selection handle defining the area that will be included
60 // in the touch target to make dragging the handle easier (see pic above).
61 const int kSelectionHandleHorizPadding = 10;
62 const int kSelectionHandleVertPadding = 20;
64 const int kQuickMenuTimoutMs = 200;
66 const int kSelectionHandleQuickFadeDurationMs = 50;
68 // Minimum height for selection handle bar. If the bar height is going to be
69 // less than this value, handle will not be shown.
70 const int kSelectionHandleBarMinHeight = 5;
71 // Maximum amount that selection handle bar can stick out of client view's
72 // boundaries.
73 const int kSelectionHandleBarBottomAllowance = 3;
75 // Creates a widget to host SelectionHandleView.
76 views::Widget* CreateTouchSelectionPopupWidget(
77 gfx::NativeView context,
78 views::WidgetDelegate* widget_delegate) {
79 views::Widget* widget = new views::Widget;
80 views::Widget::InitParams params(views::Widget::InitParams::TYPE_POPUP);
81 params.opacity = views::Widget::InitParams::TRANSLUCENT_WINDOW;
82 params.shadow_type = views::Widget::InitParams::SHADOW_TYPE_NONE;
83 params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
84 params.parent = context;
85 params.delegate = widget_delegate;
86 widget->Init(params);
87 return widget;
90 gfx::Image* GetCenterHandleImage() {
91 static gfx::Image* handle_image = nullptr;
92 if (!handle_image) {
93 handle_image = &ui::ResourceBundle::GetSharedInstance().GetImageNamed(
94 IDR_TEXT_SELECTION_HANDLE_CENTER);
96 return handle_image;
99 gfx::Image* GetLeftHandleImage() {
100 static gfx::Image* handle_image = nullptr;
101 if (!handle_image) {
102 handle_image = &ui::ResourceBundle::GetSharedInstance().GetImageNamed(
103 IDR_TEXT_SELECTION_HANDLE_LEFT);
105 return handle_image;
108 gfx::Image* GetRightHandleImage() {
109 static gfx::Image* handle_image = nullptr;
110 if (!handle_image) {
111 handle_image = &ui::ResourceBundle::GetSharedInstance().GetImageNamed(
112 IDR_TEXT_SELECTION_HANDLE_RIGHT);
114 return handle_image;
117 // Return the appropriate handle image based on the bound's type
118 gfx::Image* GetHandleImage(ui::SelectionBound::Type bound_type) {
119 switch(bound_type) {
120 case ui::SelectionBound::LEFT:
121 return GetLeftHandleImage();
122 case ui::SelectionBound::CENTER:
123 return GetCenterHandleImage();
124 case ui::SelectionBound::RIGHT:
125 return GetRightHandleImage();
126 default:
127 NOTREACHED() << "Invalid touch handle bound type.";
128 return nullptr;
132 // Calculates the bounds of the widget containing the selection handle based
133 // on the SelectionBound's type and location
134 gfx::Rect GetSelectionWidgetBounds(const ui::SelectionBound& bound) {
135 gfx::Size image_size = GetHandleImage(bound.type())->Size();
136 int widget_width = image_size.width() + 2 * kSelectionHandleHorizPadding;
137 int widget_height = bound.GetHeight() + image_size.height() +
138 kSelectionHandleVerticalVisualOffset +
139 kSelectionHandleVertPadding;
140 // Due to the shape of the handle images, the widget is aligned differently to
141 // the selection bound depending on the type of the bound.
142 int widget_left = 0;
143 switch (bound.type()) {
144 case ui::SelectionBound::LEFT:
145 widget_left = bound.edge_top_rounded().x() - image_size.width() -
146 kSelectionHandleHorizPadding;
147 break;
148 case ui::SelectionBound::RIGHT:
149 widget_left = bound.edge_top_rounded().x() - kSelectionHandleHorizPadding;
150 break;
151 case ui::SelectionBound::CENTER:
152 widget_left = bound.edge_top_rounded().x() - widget_width / 2;
153 break;
154 default:
155 NOTREACHED() << "Undefined bound type.";
156 break;
158 return gfx::Rect(
159 widget_left, bound.edge_top_rounded().y(), widget_width, widget_height);
162 gfx::Size GetMaxHandleImageSize() {
163 gfx::Rect center_rect = gfx::Rect(GetCenterHandleImage()->Size());
164 gfx::Rect left_rect = gfx::Rect(GetLeftHandleImage()->Size());
165 gfx::Rect right_rect = gfx::Rect(GetRightHandleImage()->Size());
166 gfx::Rect union_rect = center_rect;
167 union_rect.Union(left_rect);
168 union_rect.Union(right_rect);
169 return union_rect.size();
172 // Convenience methods to convert a |bound| from screen to the |client|'s
173 // coordinate system and vice versa.
174 // Note that this is not quite correct because it does not take into account
175 // transforms such as rotation and scaling. This should be in TouchEditable.
176 // TODO(varunjain): Fix this.
177 ui::SelectionBound ConvertFromScreen(ui::TouchEditable* client,
178 const ui::SelectionBound& bound) {
179 ui::SelectionBound result = bound;
180 gfx::Point edge_bottom = bound.edge_bottom_rounded();
181 gfx::Point edge_top = bound.edge_top_rounded();
182 client->ConvertPointFromScreen(&edge_bottom);
183 client->ConvertPointFromScreen(&edge_top);
184 result.SetEdge(edge_top, edge_bottom);
185 return result;
188 ui::SelectionBound ConvertToScreen(ui::TouchEditable* client,
189 const ui::SelectionBound& bound) {
190 ui::SelectionBound result = bound;
191 gfx::Point edge_bottom = bound.edge_bottom_rounded();
192 gfx::Point edge_top = bound.edge_top_rounded();
193 client->ConvertPointToScreen(&edge_bottom);
194 client->ConvertPointToScreen(&edge_top);
195 result.SetEdge(edge_top, edge_bottom);
196 return result;
199 gfx::Rect BoundToRect(const ui::SelectionBound& bound) {
200 return gfx::BoundingRect(bound.edge_top_rounded(),
201 bound.edge_bottom_rounded());
204 } // namespace
206 namespace views {
208 typedef TouchSelectionControllerImpl::EditingHandleView EditingHandleView;
210 class TouchHandleWindowTargeter : public wm::MaskedWindowTargeter {
211 public:
212 TouchHandleWindowTargeter(aura::Window* window,
213 EditingHandleView* handle_view);
215 ~TouchHandleWindowTargeter() override {}
217 private:
218 // wm::MaskedWindowTargeter:
219 bool GetHitTestMask(aura::Window* window, gfx::Path* mask) const override;
221 EditingHandleView* handle_view_;
223 DISALLOW_COPY_AND_ASSIGN(TouchHandleWindowTargeter);
226 // A View that displays the text selection handle.
227 class TouchSelectionControllerImpl::EditingHandleView
228 : public views::WidgetDelegateView {
229 public:
230 EditingHandleView(TouchSelectionControllerImpl* controller,
231 gfx::NativeView context,
232 bool is_cursor_handle)
233 : controller_(controller),
234 image_(GetCenterHandleImage()),
235 is_cursor_handle_(is_cursor_handle),
236 draw_invisible_(false) {
237 widget_.reset(CreateTouchSelectionPopupWidget(context, this));
238 widget_->SetContentsView(this);
240 aura::Window* window = widget_->GetNativeWindow();
241 window->SetEventTargeter(scoped_ptr<ui::EventTargeter>(
242 new TouchHandleWindowTargeter(window, this)));
244 // We are owned by the TouchSelectionControllerImpl.
245 set_owned_by_client();
248 ~EditingHandleView() override { SetWidgetVisible(false, false); }
250 // Overridden from views::WidgetDelegateView:
251 bool WidgetHasHitTestMask() const override { return true; }
253 void GetWidgetHitTestMask(gfx::Path* mask) const override {
254 gfx::Size image_size = image_->Size();
255 mask->addRect(
256 SkIntToScalar(0),
257 SkIntToScalar(selection_bound_.GetHeight() +
258 kSelectionHandleVerticalVisualOffset),
259 SkIntToScalar(image_size.width()) + 2 * kSelectionHandleHorizPadding,
260 SkIntToScalar(selection_bound_.GetHeight() +
261 kSelectionHandleVerticalVisualOffset +
262 image_size.height() + kSelectionHandleVertPadding));
265 void DeleteDelegate() override {
266 // We are owned and deleted by TouchSelectionControllerImpl.
269 // Overridden from views::View:
270 void OnPaint(gfx::Canvas* canvas) override {
271 if (draw_invisible_)
272 return;
274 // Draw the handle image.
275 canvas->DrawImageInt(
276 *image_->ToImageSkia(),
277 kSelectionHandleHorizPadding,
278 selection_bound_.GetHeight() + kSelectionHandleVerticalVisualOffset);
281 void OnGestureEvent(ui::GestureEvent* event) override {
282 event->SetHandled();
283 switch (event->type()) {
284 case ui::ET_GESTURE_SCROLL_BEGIN: {
285 widget_->SetCapture(this);
286 controller_->SetDraggingHandle(this);
287 // Distance from the point which is |kSelectionHandleVerticalDragOffset|
288 // pixels above the bottom of the selection bound edge to the event
289 // location (aka the touch-drag point).
290 drag_offset_ = selection_bound_.edge_bottom_rounded() -
291 gfx::Vector2d(0, kSelectionHandleVerticalDragOffset) -
292 event->location();
293 break;
295 case ui::ET_GESTURE_SCROLL_UPDATE: {
296 controller_->SelectionHandleDragged(event->location() + drag_offset_);
297 break;
299 case ui::ET_GESTURE_SCROLL_END:
300 case ui::ET_SCROLL_FLING_START:
301 widget_->ReleaseCapture();
302 controller_->SetDraggingHandle(nullptr);
303 break;
304 default:
305 break;
309 gfx::Size GetPreferredSize() const override {
310 return GetSelectionWidgetBounds(selection_bound_).size();
313 bool IsWidgetVisible() const {
314 return widget_->IsVisible();
317 void SetWidgetVisible(bool visible, bool quick) {
318 if (widget_->IsVisible() == visible)
319 return;
320 widget_->SetVisibilityAnimationDuration(
321 base::TimeDelta::FromMilliseconds(
322 quick ? kSelectionHandleQuickFadeDurationMs : 0));
323 if (visible)
324 widget_->Show();
325 else
326 widget_->Hide();
329 void SetBoundInScreen(const ui::SelectionBound& bound) {
330 bool update_bound_type = false;
331 // Cursor handle should always have the bound type CENTER
332 DCHECK(!is_cursor_handle_ || bound.type() == ui::SelectionBound::CENTER);
334 if (bound.type() != selection_bound_.type()) {
335 // Unless this is a cursor handle, do not set the type to CENTER -
336 // selection handles corresponding to a selection should always use left
337 // or right handle image. If selection handles are dragged to be located
338 // at the same spot, the |bound|'s type here will be CENTER for both of
339 // them. In this case do not update the type of the |selection_bound_|.
340 if (bound.type() != ui::SelectionBound::CENTER || is_cursor_handle_)
341 update_bound_type = true;
343 if (update_bound_type) {
344 selection_bound_.set_type(bound.type());
345 image_ = GetHandleImage(bound.type());
346 SchedulePaint();
348 selection_bound_.SetEdge(bound.edge_top(), bound.edge_bottom());
350 widget_->SetBounds(GetSelectionWidgetBounds(selection_bound_));
352 aura::Window* window = widget_->GetNativeView();
353 gfx::Point edge_top = selection_bound_.edge_top_rounded();
354 gfx::Point edge_bottom = selection_bound_.edge_bottom_rounded();
355 wm::ConvertPointFromScreen(window, &edge_top);
356 wm::ConvertPointFromScreen(window, &edge_bottom);
357 selection_bound_.SetEdge(edge_top, edge_bottom);
360 void SetDrawInvisible(bool draw_invisible) {
361 if (draw_invisible_ == draw_invisible)
362 return;
363 draw_invisible_ = draw_invisible;
364 SchedulePaint();
367 private:
368 scoped_ptr<Widget> widget_;
369 TouchSelectionControllerImpl* controller_;
371 // In local coordinates
372 ui::SelectionBound selection_bound_;
373 gfx::Image* image_;
375 // If true, this is a handle corresponding to the single cursor, otherwise it
376 // is a handle corresponding to one of the two selection bounds.
377 bool is_cursor_handle_;
379 // Offset applied to the scroll events location when calling
380 // TouchSelectionControllerImpl::SelectionHandleDragged while dragging the
381 // handle.
382 gfx::Vector2d drag_offset_;
384 // If set to true, the handle will not draw anything, hence providing an empty
385 // widget. We need this because we may want to stop showing the handle while
386 // it is being dragged. Since it is being dragged, we cannot destroy the
387 // handle.
388 bool draw_invisible_;
390 DISALLOW_COPY_AND_ASSIGN(EditingHandleView);
393 TouchHandleWindowTargeter::TouchHandleWindowTargeter(
394 aura::Window* window,
395 EditingHandleView* handle_view)
396 : wm::MaskedWindowTargeter(window),
397 handle_view_(handle_view) {
400 bool TouchHandleWindowTargeter::GetHitTestMask(aura::Window* window,
401 gfx::Path* mask) const {
402 handle_view_->GetWidgetHitTestMask(mask);
403 return true;
406 TouchSelectionControllerImpl::TouchSelectionControllerImpl(
407 ui::TouchEditable* client_view)
408 : client_view_(client_view),
409 client_widget_(nullptr),
410 selection_handle_1_(new EditingHandleView(this,
411 client_view->GetNativeView(),
412 false)),
413 selection_handle_2_(new EditingHandleView(this,
414 client_view->GetNativeView(),
415 false)),
416 cursor_handle_(new EditingHandleView(this,
417 client_view->GetNativeView(),
418 true)),
419 command_executed_(false),
420 dragging_handle_(nullptr) {
421 selection_start_time_ = base::TimeTicks::Now();
422 aura::Window* client_window = client_view_->GetNativeView();
423 client_window->AddObserver(this);
424 client_widget_ = Widget::GetTopLevelWidgetForNativeView(client_window);
425 if (client_widget_)
426 client_widget_->AddObserver(this);
427 aura::Env::GetInstance()->AddPreTargetHandler(this);
430 TouchSelectionControllerImpl::~TouchSelectionControllerImpl() {
431 UMA_HISTOGRAM_BOOLEAN("Event.TouchSelection.EndedWithAction",
432 command_executed_);
433 HideQuickMenu();
434 aura::Env::GetInstance()->RemovePreTargetHandler(this);
435 if (client_widget_)
436 client_widget_->RemoveObserver(this);
437 client_view_->GetNativeView()->RemoveObserver(this);
440 void TouchSelectionControllerImpl::SelectionChanged() {
441 ui::SelectionBound anchor, focus;
442 client_view_->GetSelectionEndPoints(&anchor, &focus);
443 ui::SelectionBound screen_bound_anchor =
444 ConvertToScreen(client_view_, anchor);
445 ui::SelectionBound screen_bound_focus = ConvertToScreen(client_view_, focus);
446 gfx::Rect client_bounds = client_view_->GetBounds();
447 if (anchor.edge_top().y() < client_bounds.y()) {
448 gfx::Point anchor_edge_top = anchor.edge_top_rounded();
449 anchor_edge_top.set_y(client_bounds.y());
450 anchor.SetEdgeTop(anchor_edge_top);
452 if (focus.edge_top().y() < client_bounds.y()) {
453 gfx::Point focus_edge_top = focus.edge_top_rounded();
454 focus_edge_top.set_y(client_bounds.y());
455 focus.SetEdgeTop(focus_edge_top);
457 ui::SelectionBound screen_bound_anchor_clipped =
458 ConvertToScreen(client_view_, anchor);
459 ui::SelectionBound screen_bound_focus_clipped =
460 ConvertToScreen(client_view_, focus);
461 if (screen_bound_anchor_clipped == selection_bound_1_clipped_ &&
462 screen_bound_focus_clipped == selection_bound_2_clipped_)
463 return;
465 selection_bound_1_ = screen_bound_anchor;
466 selection_bound_2_ = screen_bound_focus;
467 selection_bound_1_clipped_ = screen_bound_anchor_clipped;
468 selection_bound_2_clipped_ = screen_bound_focus_clipped;
470 if (client_view_->DrawsHandles()) {
471 UpdateQuickMenu();
472 return;
475 if (dragging_handle_) {
476 // We need to reposition only the selection handle that is being dragged.
477 // The other handle stays the same. Also, the selection handle being dragged
478 // will always be at the end of selection, while the other handle will be at
479 // the start.
480 // If the new location of this handle is out of client view, its widget
481 // should not get hidden, since it should still receive touch events.
482 // Hence, we are not using |SetHandleBound()| method here.
483 dragging_handle_->SetBoundInScreen(screen_bound_focus_clipped);
485 // Temporary fix for selection handle going outside a window. On a webpage,
486 // the page should scroll if the selection handle is dragged outside the
487 // window. That does not happen currently. So we just hide the handle for
488 // now.
489 // TODO(varunjain): Fix this: crbug.com/269003
490 dragging_handle_->SetDrawInvisible(!ShouldShowHandleFor(focus));
492 if (dragging_handle_ != cursor_handle_.get()) {
493 // The non-dragging-handle might have recently become visible.
494 EditingHandleView* non_dragging_handle = selection_handle_1_.get();
495 if (dragging_handle_ == selection_handle_1_) {
496 non_dragging_handle = selection_handle_2_.get();
497 // if handle 1 is being dragged, it is corresponding to the end of
498 // selection and the other handle to the start of selection.
499 selection_bound_1_ = screen_bound_focus;
500 selection_bound_2_ = screen_bound_anchor;
501 selection_bound_1_clipped_ = screen_bound_focus_clipped;
502 selection_bound_2_clipped_ = screen_bound_anchor_clipped;
504 SetHandleBound(non_dragging_handle, anchor, screen_bound_anchor_clipped);
506 } else {
507 UpdateQuickMenu();
509 // Check if there is any selection at all.
510 if (screen_bound_anchor.edge_top() == screen_bound_focus.edge_top() &&
511 screen_bound_anchor.edge_bottom() == screen_bound_focus.edge_bottom()) {
512 selection_handle_1_->SetWidgetVisible(false, false);
513 selection_handle_2_->SetWidgetVisible(false, false);
514 SetHandleBound(cursor_handle_.get(), anchor, screen_bound_anchor_clipped);
515 return;
518 cursor_handle_->SetWidgetVisible(false, false);
519 SetHandleBound(
520 selection_handle_1_.get(), anchor, screen_bound_anchor_clipped);
521 SetHandleBound(
522 selection_handle_2_.get(), focus, screen_bound_focus_clipped);
526 bool TouchSelectionControllerImpl::IsHandleDragInProgress() {
527 return !!dragging_handle_;
530 void TouchSelectionControllerImpl::HideHandles(bool quick) {
531 selection_handle_1_->SetWidgetVisible(false, quick);
532 selection_handle_2_->SetWidgetVisible(false, quick);
533 cursor_handle_->SetWidgetVisible(false, quick);
536 void TouchSelectionControllerImpl::SetDraggingHandle(
537 EditingHandleView* handle) {
538 dragging_handle_ = handle;
539 if (dragging_handle_)
540 HideQuickMenu();
541 else
542 StartQuickMenuTimer();
545 void TouchSelectionControllerImpl::SelectionHandleDragged(
546 const gfx::Point& drag_pos) {
547 DCHECK(dragging_handle_);
548 gfx::Point drag_pos_in_client = drag_pos;
549 ConvertPointToClientView(dragging_handle_, &drag_pos_in_client);
551 if (dragging_handle_ == cursor_handle_.get()) {
552 client_view_->MoveCaretTo(drag_pos_in_client);
553 return;
556 // Find the stationary selection handle.
557 ui::SelectionBound anchor_bound =
558 selection_handle_1_ == dragging_handle_ ? selection_bound_2_
559 : selection_bound_1_;
561 // Find selection end points in client_view's coordinate system.
562 gfx::Point p2 = anchor_bound.edge_top_rounded();
563 p2.Offset(0, anchor_bound.GetHeight() / 2);
564 client_view_->ConvertPointFromScreen(&p2);
566 // Instruct client_view to select the region between p1 and p2. The position
567 // of |fixed_handle| is the start and that of |dragging_handle| is the end
568 // of selection.
569 client_view_->SelectRect(p2, drag_pos_in_client);
572 void TouchSelectionControllerImpl::ConvertPointToClientView(
573 EditingHandleView* source, gfx::Point* point) {
574 View::ConvertPointToScreen(source, point);
575 client_view_->ConvertPointFromScreen(point);
578 void TouchSelectionControllerImpl::SetHandleBound(
579 EditingHandleView* handle,
580 const ui::SelectionBound& bound,
581 const ui::SelectionBound& bound_in_screen) {
582 handle->SetWidgetVisible(ShouldShowHandleFor(bound), false);
583 if (handle->IsWidgetVisible())
584 handle->SetBoundInScreen(bound_in_screen);
587 bool TouchSelectionControllerImpl::ShouldShowHandleFor(
588 const ui::SelectionBound& bound) const {
589 if (bound.GetHeight() < kSelectionHandleBarMinHeight)
590 return false;
591 gfx::Rect client_bounds = client_view_->GetBounds();
592 client_bounds.Inset(0, 0, 0, -kSelectionHandleBarBottomAllowance);
593 return client_bounds.Contains(BoundToRect(bound));
596 bool TouchSelectionControllerImpl::IsCommandIdEnabled(int command_id) const {
597 return client_view_->IsCommandIdEnabled(command_id);
600 void TouchSelectionControllerImpl::ExecuteCommand(int command_id,
601 int event_flags) {
602 command_executed_ = true;
603 base::TimeDelta duration = base::TimeTicks::Now() - selection_start_time_;
604 // Note that we only log the duration stats for the 'successful' selections,
605 // i.e. selections ending with the execution of a command.
606 UMA_HISTOGRAM_CUSTOM_TIMES("Event.TouchSelection.Duration",
607 duration,
608 base::TimeDelta::FromMilliseconds(500),
609 base::TimeDelta::FromSeconds(60),
610 60);
611 client_view_->ExecuteCommand(command_id, event_flags);
614 void TouchSelectionControllerImpl::RunContextMenu() {
615 // Context menu should appear centered on top of the selected region.
616 const gfx::Rect rect = GetQuickMenuAnchorRect();
617 const gfx::Point anchor(rect.CenterPoint().x(), rect.y());
618 client_view_->OpenContextMenu(anchor);
621 void TouchSelectionControllerImpl::OnAncestorWindowTransformed(
622 aura::Window* window,
623 aura::Window* ancestor) {
624 client_view_->DestroyTouchSelection();
627 void TouchSelectionControllerImpl::OnWidgetClosing(Widget* widget) {
628 DCHECK_EQ(client_widget_, widget);
629 client_widget_->RemoveObserver(this);
630 client_widget_ = nullptr;
633 void TouchSelectionControllerImpl::OnWidgetBoundsChanged(
634 Widget* widget,
635 const gfx::Rect& new_bounds) {
636 DCHECK_EQ(client_widget_, widget);
637 SelectionChanged();
640 void TouchSelectionControllerImpl::OnKeyEvent(ui::KeyEvent* event) {
641 client_view_->DestroyTouchSelection();
644 void TouchSelectionControllerImpl::OnMouseEvent(ui::MouseEvent* event) {
645 aura::client::CursorClient* cursor_client = aura::client::GetCursorClient(
646 client_view_->GetNativeView()->GetRootWindow());
647 if (!cursor_client || cursor_client->IsMouseEventsEnabled())
648 client_view_->DestroyTouchSelection();
651 void TouchSelectionControllerImpl::OnScrollEvent(ui::ScrollEvent* event) {
652 client_view_->DestroyTouchSelection();
655 void TouchSelectionControllerImpl::QuickMenuTimerFired() {
656 gfx::Rect menu_anchor = GetQuickMenuAnchorRect();
657 if (menu_anchor == gfx::Rect())
658 return;
660 ui::TouchSelectionMenuRunner::GetInstance()->OpenMenu(
661 this, menu_anchor, GetMaxHandleImageSize(),
662 client_view_->GetNativeView());
665 void TouchSelectionControllerImpl::StartQuickMenuTimer() {
666 if (quick_menu_timer_.IsRunning())
667 return;
668 quick_menu_timer_.Start(
669 FROM_HERE,
670 base::TimeDelta::FromMilliseconds(kQuickMenuTimoutMs),
671 this,
672 &TouchSelectionControllerImpl::QuickMenuTimerFired);
675 void TouchSelectionControllerImpl::UpdateQuickMenu() {
676 // Hide quick menu to be shown when the timer fires.
677 HideQuickMenu();
678 StartQuickMenuTimer();
681 void TouchSelectionControllerImpl::HideQuickMenu() {
682 if (ui::TouchSelectionMenuRunner::GetInstance()->IsRunning())
683 ui::TouchSelectionMenuRunner::GetInstance()->CloseMenu();
684 quick_menu_timer_.Stop();
687 gfx::Rect TouchSelectionControllerImpl::GetQuickMenuAnchorRect() const {
688 // Get selection end points in client_view's space.
689 ui::SelectionBound b1_in_screen = selection_bound_1_clipped_;
690 ui::SelectionBound b2_in_screen = cursor_handle_->IsWidgetVisible()
691 ? b1_in_screen
692 : selection_bound_2_clipped_;
693 // Convert from screen to client.
694 ui::SelectionBound b1 = ConvertFromScreen(client_view_, b1_in_screen);
695 ui::SelectionBound b2 = ConvertFromScreen(client_view_, b2_in_screen);
697 // if selection is completely inside the view, we display the quick menu in
698 // the middle of the end points on the top. Else, we show it above the visible
699 // handle. If no handle is visible, we do not show the menu.
700 gfx::Rect menu_anchor;
701 if (ShouldShowHandleFor(b1) && ShouldShowHandleFor(b2))
702 menu_anchor = ui::RectBetweenSelectionBounds(b1_in_screen, b2_in_screen);
703 else if (ShouldShowHandleFor(b1))
704 menu_anchor = BoundToRect(b1_in_screen);
705 else if (ShouldShowHandleFor(b2))
706 menu_anchor = BoundToRect(b2_in_screen);
707 else
708 return menu_anchor;
710 // Enlarge the anchor rect so that the menu is offset from the text at least
711 // by the same distance the handles are offset from the text.
712 menu_anchor.Inset(0, -kSelectionHandleVerticalVisualOffset);
714 return menu_anchor;
717 gfx::NativeView TouchSelectionControllerImpl::GetCursorHandleNativeView() {
718 return cursor_handle_->GetWidget()->GetNativeView();
721 gfx::Rect TouchSelectionControllerImpl::GetSelectionHandle1Bounds() {
722 return selection_handle_1_->GetBoundsInScreen();
725 gfx::Rect TouchSelectionControllerImpl::GetSelectionHandle2Bounds() {
726 return selection_handle_2_->GetBoundsInScreen();
729 gfx::Rect TouchSelectionControllerImpl::GetCursorHandleBounds() {
730 return cursor_handle_->GetBoundsInScreen();
733 bool TouchSelectionControllerImpl::IsSelectionHandle1Visible() {
734 return selection_handle_1_->IsWidgetVisible();
737 bool TouchSelectionControllerImpl::IsSelectionHandle2Visible() {
738 return selection_handle_2_->IsWidgetVisible();
741 bool TouchSelectionControllerImpl::IsCursorHandleVisible() {
742 return cursor_handle_->IsWidgetVisible();
745 gfx::Rect TouchSelectionControllerImpl::GetExpectedHandleBounds(
746 const ui::SelectionBound& bound) {
747 return GetSelectionWidgetBounds(bound);
750 views::WidgetDelegateView* TouchSelectionControllerImpl::GetHandle1View() {
751 return selection_handle_1_.get();
754 views::WidgetDelegateView* TouchSelectionControllerImpl::GetHandle2View() {
755 return selection_handle_2_.get();
758 } // namespace views