Add ICU message format support
[chromium-blink-merge.git] / ui / views / touchui / touch_selection_controller_impl.cc
blob3e96d9efb3974339570cf0616b839a97fea4972c
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/resources/grit/views_resources.h"
22 #include "ui/views/widget/widget.h"
23 #include "ui/views/widget/widget_delegate.h"
24 #include "ui/wm/core/coordinate_conversion.h"
25 #include "ui/wm/core/masked_window_targeter.h"
27 namespace {
29 // Constants defining the visual attributes of selection handles
31 // The distance by which a handle image is offset from the bottom of the
32 // selection/text baseline.
33 const int kSelectionHandleVerticalVisualOffset = 2;
35 // When a handle is dragged, the drag position reported to the client view is
36 // offset vertically to represent the cursor position. This constant specifies
37 // the offset in pixels above the bottom of the selection (see pic below). This
38 // is required because say if this is zero, that means the drag position we
39 // report is right on the text baseline. In that case, a vertical movement of
40 // even one pixel will make the handle jump to the line below it. So when the
41 // user just starts dragging, the handle will jump to the next line if the user
42 // makes any vertical movement. So we have this non-zero offset to prevent this
43 // jumping.
45 // Editing handle widget showing the padding and difference between the position
46 // of the ET_GESTURE_SCROLL_UPDATE event and the drag position reported to the
47 // client:
48 // ___________
49 // Selection Highlight --->_____|__|<-|---- Drag position reported to client
50 // _ | O |
51 // Vertical Padding __| | <-|---- ET_GESTURE_SCROLL_UPDATE position
52 // |_ |_____|<--- Editing handle widget
54 // | |
55 // T
56 // Horizontal Padding
58 const int kSelectionHandleVerticalDragOffset = 5;
60 // Padding around the selection handle defining the area that will be included
61 // in the touch target to make dragging the handle easier (see pic above).
62 const int kSelectionHandleHorizPadding = 10;
63 const int kSelectionHandleVertPadding = 20;
65 const int kQuickMenuTimoutMs = 200;
67 const int kSelectionHandleQuickFadeDurationMs = 50;
69 // Minimum height for selection handle bar. If the bar height is going to be
70 // less than this value, handle will not be shown.
71 const int kSelectionHandleBarMinHeight = 5;
72 // Maximum amount that selection handle bar can stick out of client view's
73 // boundaries.
74 const int kSelectionHandleBarBottomAllowance = 3;
76 // Creates a widget to host SelectionHandleView.
77 views::Widget* CreateTouchSelectionPopupWidget(
78 gfx::NativeView context,
79 views::WidgetDelegate* widget_delegate) {
80 views::Widget* widget = new views::Widget;
81 views::Widget::InitParams params(views::Widget::InitParams::TYPE_POPUP);
82 params.opacity = views::Widget::InitParams::TRANSLUCENT_WINDOW;
83 params.shadow_type = views::Widget::InitParams::SHADOW_TYPE_NONE;
84 params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
85 params.parent = context;
86 params.delegate = widget_delegate;
87 widget->Init(params);
88 return widget;
91 gfx::Image* GetCenterHandleImage() {
92 static gfx::Image* handle_image = nullptr;
93 if (!handle_image) {
94 handle_image = &ui::ResourceBundle::GetSharedInstance().GetImageNamed(
95 IDR_TEXT_SELECTION_HANDLE_CENTER);
97 return handle_image;
100 gfx::Image* GetLeftHandleImage() {
101 static gfx::Image* handle_image = nullptr;
102 if (!handle_image) {
103 handle_image = &ui::ResourceBundle::GetSharedInstance().GetImageNamed(
104 IDR_TEXT_SELECTION_HANDLE_LEFT);
106 return handle_image;
109 gfx::Image* GetRightHandleImage() {
110 static gfx::Image* handle_image = nullptr;
111 if (!handle_image) {
112 handle_image = &ui::ResourceBundle::GetSharedInstance().GetImageNamed(
113 IDR_TEXT_SELECTION_HANDLE_RIGHT);
115 return handle_image;
118 // Return the appropriate handle image based on the bound's type
119 gfx::Image* GetHandleImage(ui::SelectionBound::Type bound_type) {
120 switch(bound_type) {
121 case ui::SelectionBound::LEFT:
122 return GetLeftHandleImage();
123 case ui::SelectionBound::CENTER:
124 return GetCenterHandleImage();
125 case ui::SelectionBound::RIGHT:
126 return GetRightHandleImage();
127 default:
128 NOTREACHED() << "Invalid touch handle bound type.";
129 return nullptr;
133 // Calculates the bounds of the widget containing the selection handle based
134 // on the SelectionBound's type and location
135 gfx::Rect GetSelectionWidgetBounds(const ui::SelectionBound& bound) {
136 gfx::Size image_size = GetHandleImage(bound.type())->Size();
137 int widget_width = image_size.width() + 2 * kSelectionHandleHorizPadding;
138 int widget_height = bound.GetHeight() + image_size.height() +
139 kSelectionHandleVerticalVisualOffset +
140 kSelectionHandleVertPadding;
141 // Due to the shape of the handle images, the widget is aligned differently to
142 // the selection bound depending on the type of the bound.
143 int widget_left = 0;
144 switch (bound.type()) {
145 case ui::SelectionBound::LEFT:
146 widget_left = bound.edge_top_rounded().x() - image_size.width() -
147 kSelectionHandleHorizPadding;
148 break;
149 case ui::SelectionBound::RIGHT:
150 widget_left = bound.edge_top_rounded().x() - kSelectionHandleHorizPadding;
151 break;
152 case ui::SelectionBound::CENTER:
153 widget_left = bound.edge_top_rounded().x() - widget_width / 2;
154 break;
155 default:
156 NOTREACHED() << "Undefined bound type.";
157 break;
159 return gfx::Rect(
160 widget_left, bound.edge_top_rounded().y(), widget_width, widget_height);
163 gfx::Size GetMaxHandleImageSize() {
164 gfx::Rect center_rect = gfx::Rect(GetCenterHandleImage()->Size());
165 gfx::Rect left_rect = gfx::Rect(GetLeftHandleImage()->Size());
166 gfx::Rect right_rect = gfx::Rect(GetRightHandleImage()->Size());
167 gfx::Rect union_rect = center_rect;
168 union_rect.Union(left_rect);
169 union_rect.Union(right_rect);
170 return union_rect.size();
173 // Convenience methods to convert a |bound| from screen to the |client|'s
174 // coordinate system and vice versa.
175 // Note that this is not quite correct because it does not take into account
176 // transforms such as rotation and scaling. This should be in TouchEditable.
177 // TODO(varunjain): Fix this.
178 ui::SelectionBound ConvertFromScreen(ui::TouchEditable* client,
179 const ui::SelectionBound& bound) {
180 ui::SelectionBound result = bound;
181 gfx::Point edge_bottom = bound.edge_bottom_rounded();
182 gfx::Point edge_top = bound.edge_top_rounded();
183 client->ConvertPointFromScreen(&edge_bottom);
184 client->ConvertPointFromScreen(&edge_top);
185 result.SetEdge(edge_top, edge_bottom);
186 return result;
189 ui::SelectionBound ConvertToScreen(ui::TouchEditable* client,
190 const ui::SelectionBound& bound) {
191 ui::SelectionBound result = bound;
192 gfx::Point edge_bottom = bound.edge_bottom_rounded();
193 gfx::Point edge_top = bound.edge_top_rounded();
194 client->ConvertPointToScreen(&edge_bottom);
195 client->ConvertPointToScreen(&edge_top);
196 result.SetEdge(edge_top, edge_bottom);
197 return result;
200 gfx::Rect BoundToRect(const ui::SelectionBound& bound) {
201 return gfx::BoundingRect(bound.edge_top_rounded(),
202 bound.edge_bottom_rounded());
205 } // namespace
207 namespace views {
209 typedef TouchSelectionControllerImpl::EditingHandleView EditingHandleView;
211 class TouchHandleWindowTargeter : public wm::MaskedWindowTargeter {
212 public:
213 TouchHandleWindowTargeter(aura::Window* window,
214 EditingHandleView* handle_view);
216 ~TouchHandleWindowTargeter() override {}
218 private:
219 // wm::MaskedWindowTargeter:
220 bool GetHitTestMask(aura::Window* window, gfx::Path* mask) const override;
222 EditingHandleView* handle_view_;
224 DISALLOW_COPY_AND_ASSIGN(TouchHandleWindowTargeter);
227 // A View that displays the text selection handle.
228 class TouchSelectionControllerImpl::EditingHandleView
229 : public views::WidgetDelegateView {
230 public:
231 EditingHandleView(TouchSelectionControllerImpl* controller,
232 gfx::NativeView context,
233 bool is_cursor_handle)
234 : controller_(controller),
235 image_(GetCenterHandleImage()),
236 is_cursor_handle_(is_cursor_handle),
237 draw_invisible_(false) {
238 widget_.reset(CreateTouchSelectionPopupWidget(context, this));
239 widget_->SetContentsView(this);
241 aura::Window* window = widget_->GetNativeWindow();
242 window->SetEventTargeter(scoped_ptr<ui::EventTargeter>(
243 new TouchHandleWindowTargeter(window, this)));
245 // We are owned by the TouchSelectionControllerImpl.
246 set_owned_by_client();
249 ~EditingHandleView() override { SetWidgetVisible(false, false); }
251 // Overridden from views::WidgetDelegateView:
252 bool WidgetHasHitTestMask() const override { return true; }
254 void GetWidgetHitTestMask(gfx::Path* mask) const override {
255 gfx::Size image_size = image_->Size();
256 mask->addRect(
257 SkIntToScalar(0),
258 SkIntToScalar(selection_bound_.GetHeight() +
259 kSelectionHandleVerticalVisualOffset),
260 SkIntToScalar(image_size.width()) + 2 * kSelectionHandleHorizPadding,
261 SkIntToScalar(selection_bound_.GetHeight() +
262 kSelectionHandleVerticalVisualOffset +
263 image_size.height() + kSelectionHandleVertPadding));
266 void DeleteDelegate() override {
267 // We are owned and deleted by TouchSelectionControllerImpl.
270 // Overridden from views::View:
271 void OnPaint(gfx::Canvas* canvas) override {
272 if (draw_invisible_)
273 return;
275 // Draw the handle image.
276 canvas->DrawImageInt(
277 *image_->ToImageSkia(),
278 kSelectionHandleHorizPadding,
279 selection_bound_.GetHeight() + kSelectionHandleVerticalVisualOffset);
282 void OnGestureEvent(ui::GestureEvent* event) override {
283 event->SetHandled();
284 switch (event->type()) {
285 case ui::ET_GESTURE_SCROLL_BEGIN: {
286 widget_->SetCapture(this);
287 controller_->SetDraggingHandle(this);
288 // Distance from the point which is |kSelectionHandleVerticalDragOffset|
289 // pixels above the bottom of the selection bound edge to the event
290 // location (aka the touch-drag point).
291 drag_offset_ = selection_bound_.edge_bottom_rounded() -
292 gfx::Vector2d(0, kSelectionHandleVerticalDragOffset) -
293 event->location();
294 break;
296 case ui::ET_GESTURE_SCROLL_UPDATE: {
297 controller_->SelectionHandleDragged(event->location() + drag_offset_);
298 break;
300 case ui::ET_GESTURE_SCROLL_END:
301 case ui::ET_SCROLL_FLING_START:
302 widget_->ReleaseCapture();
303 controller_->SetDraggingHandle(nullptr);
304 break;
305 default:
306 break;
310 gfx::Size GetPreferredSize() const override {
311 return GetSelectionWidgetBounds(selection_bound_).size();
314 bool IsWidgetVisible() const {
315 return widget_->IsVisible();
318 void SetWidgetVisible(bool visible, bool quick) {
319 if (widget_->IsVisible() == visible)
320 return;
321 widget_->SetVisibilityAnimationDuration(
322 base::TimeDelta::FromMilliseconds(
323 quick ? kSelectionHandleQuickFadeDurationMs : 0));
324 if (visible)
325 widget_->Show();
326 else
327 widget_->Hide();
330 void SetBoundInScreen(const ui::SelectionBound& bound) {
331 bool update_bound_type = false;
332 // Cursor handle should always have the bound type CENTER
333 DCHECK(!is_cursor_handle_ || bound.type() == ui::SelectionBound::CENTER);
335 if (bound.type() != selection_bound_.type()) {
336 // Unless this is a cursor handle, do not set the type to CENTER -
337 // selection handles corresponding to a selection should always use left
338 // or right handle image. If selection handles are dragged to be located
339 // at the same spot, the |bound|'s type here will be CENTER for both of
340 // them. In this case do not update the type of the |selection_bound_|.
341 if (bound.type() != ui::SelectionBound::CENTER || is_cursor_handle_)
342 update_bound_type = true;
344 if (update_bound_type) {
345 selection_bound_.set_type(bound.type());
346 image_ = GetHandleImage(bound.type());
347 SchedulePaint();
349 selection_bound_.SetEdge(bound.edge_top(), bound.edge_bottom());
351 widget_->SetBounds(GetSelectionWidgetBounds(selection_bound_));
353 aura::Window* window = widget_->GetNativeView();
354 gfx::Point edge_top = selection_bound_.edge_top_rounded();
355 gfx::Point edge_bottom = selection_bound_.edge_bottom_rounded();
356 wm::ConvertPointFromScreen(window, &edge_top);
357 wm::ConvertPointFromScreen(window, &edge_bottom);
358 selection_bound_.SetEdge(edge_top, edge_bottom);
361 void SetDrawInvisible(bool draw_invisible) {
362 if (draw_invisible_ == draw_invisible)
363 return;
364 draw_invisible_ = draw_invisible;
365 SchedulePaint();
368 private:
369 scoped_ptr<Widget> widget_;
370 TouchSelectionControllerImpl* controller_;
372 // In local coordinates
373 ui::SelectionBound selection_bound_;
374 gfx::Image* image_;
376 // If true, this is a handle corresponding to the single cursor, otherwise it
377 // is a handle corresponding to one of the two selection bounds.
378 bool is_cursor_handle_;
380 // Offset applied to the scroll events location when calling
381 // TouchSelectionControllerImpl::SelectionHandleDragged while dragging the
382 // handle.
383 gfx::Vector2d drag_offset_;
385 // If set to true, the handle will not draw anything, hence providing an empty
386 // widget. We need this because we may want to stop showing the handle while
387 // it is being dragged. Since it is being dragged, we cannot destroy the
388 // handle.
389 bool draw_invisible_;
391 DISALLOW_COPY_AND_ASSIGN(EditingHandleView);
394 TouchHandleWindowTargeter::TouchHandleWindowTargeter(
395 aura::Window* window,
396 EditingHandleView* handle_view)
397 : wm::MaskedWindowTargeter(window),
398 handle_view_(handle_view) {
401 bool TouchHandleWindowTargeter::GetHitTestMask(aura::Window* window,
402 gfx::Path* mask) const {
403 handle_view_->GetWidgetHitTestMask(mask);
404 return true;
407 TouchSelectionControllerImpl::TouchSelectionControllerImpl(
408 ui::TouchEditable* client_view)
409 : client_view_(client_view),
410 client_widget_(nullptr),
411 selection_handle_1_(new EditingHandleView(this,
412 client_view->GetNativeView(),
413 false)),
414 selection_handle_2_(new EditingHandleView(this,
415 client_view->GetNativeView(),
416 false)),
417 cursor_handle_(new EditingHandleView(this,
418 client_view->GetNativeView(),
419 true)),
420 command_executed_(false),
421 dragging_handle_(nullptr) {
422 selection_start_time_ = base::TimeTicks::Now();
423 aura::Window* client_window = client_view_->GetNativeView();
424 client_window->AddObserver(this);
425 client_widget_ = Widget::GetTopLevelWidgetForNativeView(client_window);
426 if (client_widget_)
427 client_widget_->AddObserver(this);
428 aura::Env::GetInstance()->AddPreTargetHandler(this);
431 TouchSelectionControllerImpl::~TouchSelectionControllerImpl() {
432 UMA_HISTOGRAM_BOOLEAN("Event.TouchSelection.EndedWithAction",
433 command_executed_);
434 HideQuickMenu();
435 aura::Env::GetInstance()->RemovePreTargetHandler(this);
436 if (client_widget_)
437 client_widget_->RemoveObserver(this);
438 client_view_->GetNativeView()->RemoveObserver(this);
441 void TouchSelectionControllerImpl::SelectionChanged() {
442 ui::SelectionBound anchor, focus;
443 client_view_->GetSelectionEndPoints(&anchor, &focus);
444 ui::SelectionBound screen_bound_anchor =
445 ConvertToScreen(client_view_, anchor);
446 ui::SelectionBound screen_bound_focus = ConvertToScreen(client_view_, focus);
447 gfx::Rect client_bounds = client_view_->GetBounds();
448 if (anchor.edge_top().y() < client_bounds.y()) {
449 gfx::Point anchor_edge_top = anchor.edge_top_rounded();
450 anchor_edge_top.set_y(client_bounds.y());
451 anchor.SetEdgeTop(anchor_edge_top);
453 if (focus.edge_top().y() < client_bounds.y()) {
454 gfx::Point focus_edge_top = focus.edge_top_rounded();
455 focus_edge_top.set_y(client_bounds.y());
456 focus.SetEdgeTop(focus_edge_top);
458 ui::SelectionBound screen_bound_anchor_clipped =
459 ConvertToScreen(client_view_, anchor);
460 ui::SelectionBound screen_bound_focus_clipped =
461 ConvertToScreen(client_view_, focus);
462 if (screen_bound_anchor_clipped == selection_bound_1_clipped_ &&
463 screen_bound_focus_clipped == selection_bound_2_clipped_)
464 return;
466 selection_bound_1_ = screen_bound_anchor;
467 selection_bound_2_ = screen_bound_focus;
468 selection_bound_1_clipped_ = screen_bound_anchor_clipped;
469 selection_bound_2_clipped_ = screen_bound_focus_clipped;
471 if (client_view_->DrawsHandles()) {
472 UpdateQuickMenu();
473 return;
476 if (dragging_handle_) {
477 // We need to reposition only the selection handle that is being dragged.
478 // The other handle stays the same. Also, the selection handle being dragged
479 // will always be at the end of selection, while the other handle will be at
480 // the start.
481 // If the new location of this handle is out of client view, its widget
482 // should not get hidden, since it should still receive touch events.
483 // Hence, we are not using |SetHandleBound()| method here.
484 dragging_handle_->SetBoundInScreen(screen_bound_focus_clipped);
486 // Temporary fix for selection handle going outside a window. On a webpage,
487 // the page should scroll if the selection handle is dragged outside the
488 // window. That does not happen currently. So we just hide the handle for
489 // now.
490 // TODO(varunjain): Fix this: crbug.com/269003
491 dragging_handle_->SetDrawInvisible(!ShouldShowHandleFor(focus));
493 if (dragging_handle_ != cursor_handle_.get()) {
494 // The non-dragging-handle might have recently become visible.
495 EditingHandleView* non_dragging_handle = selection_handle_1_.get();
496 if (dragging_handle_ == selection_handle_1_) {
497 non_dragging_handle = selection_handle_2_.get();
498 // if handle 1 is being dragged, it is corresponding to the end of
499 // selection and the other handle to the start of selection.
500 selection_bound_1_ = screen_bound_focus;
501 selection_bound_2_ = screen_bound_anchor;
502 selection_bound_1_clipped_ = screen_bound_focus_clipped;
503 selection_bound_2_clipped_ = screen_bound_anchor_clipped;
505 SetHandleBound(non_dragging_handle, anchor, screen_bound_anchor_clipped);
507 } else {
508 UpdateQuickMenu();
510 // Check if there is any selection at all.
511 if (screen_bound_anchor.edge_top() == screen_bound_focus.edge_top() &&
512 screen_bound_anchor.edge_bottom() == screen_bound_focus.edge_bottom()) {
513 selection_handle_1_->SetWidgetVisible(false, false);
514 selection_handle_2_->SetWidgetVisible(false, false);
515 SetHandleBound(cursor_handle_.get(), anchor, screen_bound_anchor_clipped);
516 return;
519 cursor_handle_->SetWidgetVisible(false, false);
520 SetHandleBound(
521 selection_handle_1_.get(), anchor, screen_bound_anchor_clipped);
522 SetHandleBound(
523 selection_handle_2_.get(), focus, screen_bound_focus_clipped);
527 bool TouchSelectionControllerImpl::IsHandleDragInProgress() {
528 return !!dragging_handle_;
531 void TouchSelectionControllerImpl::HideHandles(bool quick) {
532 selection_handle_1_->SetWidgetVisible(false, quick);
533 selection_handle_2_->SetWidgetVisible(false, quick);
534 cursor_handle_->SetWidgetVisible(false, quick);
537 void TouchSelectionControllerImpl::SetDraggingHandle(
538 EditingHandleView* handle) {
539 dragging_handle_ = handle;
540 if (dragging_handle_)
541 HideQuickMenu();
542 else
543 StartQuickMenuTimer();
546 void TouchSelectionControllerImpl::SelectionHandleDragged(
547 const gfx::Point& drag_pos) {
548 DCHECK(dragging_handle_);
549 gfx::Point drag_pos_in_client = drag_pos;
550 ConvertPointToClientView(dragging_handle_, &drag_pos_in_client);
552 if (dragging_handle_ == cursor_handle_.get()) {
553 client_view_->MoveCaretTo(drag_pos_in_client);
554 return;
557 // Find the stationary selection handle.
558 ui::SelectionBound anchor_bound =
559 selection_handle_1_ == dragging_handle_ ? selection_bound_2_
560 : selection_bound_1_;
562 // Find selection end points in client_view's coordinate system.
563 gfx::Point p2 = anchor_bound.edge_top_rounded();
564 p2.Offset(0, anchor_bound.GetHeight() / 2);
565 client_view_->ConvertPointFromScreen(&p2);
567 // Instruct client_view to select the region between p1 and p2. The position
568 // of |fixed_handle| is the start and that of |dragging_handle| is the end
569 // of selection.
570 client_view_->SelectRect(p2, drag_pos_in_client);
573 void TouchSelectionControllerImpl::ConvertPointToClientView(
574 EditingHandleView* source, gfx::Point* point) {
575 View::ConvertPointToScreen(source, point);
576 client_view_->ConvertPointFromScreen(point);
579 void TouchSelectionControllerImpl::SetHandleBound(
580 EditingHandleView* handle,
581 const ui::SelectionBound& bound,
582 const ui::SelectionBound& bound_in_screen) {
583 handle->SetWidgetVisible(ShouldShowHandleFor(bound), false);
584 if (handle->IsWidgetVisible())
585 handle->SetBoundInScreen(bound_in_screen);
588 bool TouchSelectionControllerImpl::ShouldShowHandleFor(
589 const ui::SelectionBound& bound) const {
590 if (bound.GetHeight() < kSelectionHandleBarMinHeight)
591 return false;
592 gfx::Rect client_bounds = client_view_->GetBounds();
593 client_bounds.Inset(0, 0, 0, -kSelectionHandleBarBottomAllowance);
594 return client_bounds.Contains(BoundToRect(bound));
597 bool TouchSelectionControllerImpl::IsCommandIdEnabled(int command_id) const {
598 return client_view_->IsCommandIdEnabled(command_id);
601 void TouchSelectionControllerImpl::ExecuteCommand(int command_id,
602 int event_flags) {
603 command_executed_ = true;
604 base::TimeDelta duration = base::TimeTicks::Now() - selection_start_time_;
605 // Note that we only log the duration stats for the 'successful' selections,
606 // i.e. selections ending with the execution of a command.
607 UMA_HISTOGRAM_CUSTOM_TIMES("Event.TouchSelection.Duration",
608 duration,
609 base::TimeDelta::FromMilliseconds(500),
610 base::TimeDelta::FromSeconds(60),
611 60);
612 client_view_->ExecuteCommand(command_id, event_flags);
615 void TouchSelectionControllerImpl::RunContextMenu() {
616 // Context menu should appear centered on top of the selected region.
617 const gfx::Rect rect = GetQuickMenuAnchorRect();
618 const gfx::Point anchor(rect.CenterPoint().x(), rect.y());
619 client_view_->OpenContextMenu(anchor);
622 void TouchSelectionControllerImpl::OnAncestorWindowTransformed(
623 aura::Window* window,
624 aura::Window* ancestor) {
625 client_view_->DestroyTouchSelection();
628 void TouchSelectionControllerImpl::OnWidgetClosing(Widget* widget) {
629 DCHECK_EQ(client_widget_, widget);
630 client_widget_->RemoveObserver(this);
631 client_widget_ = nullptr;
634 void TouchSelectionControllerImpl::OnWidgetBoundsChanged(
635 Widget* widget,
636 const gfx::Rect& new_bounds) {
637 DCHECK_EQ(client_widget_, widget);
638 SelectionChanged();
641 void TouchSelectionControllerImpl::OnKeyEvent(ui::KeyEvent* event) {
642 client_view_->DestroyTouchSelection();
645 void TouchSelectionControllerImpl::OnMouseEvent(ui::MouseEvent* event) {
646 aura::client::CursorClient* cursor_client = aura::client::GetCursorClient(
647 client_view_->GetNativeView()->GetRootWindow());
648 if (!cursor_client || cursor_client->IsMouseEventsEnabled())
649 client_view_->DestroyTouchSelection();
652 void TouchSelectionControllerImpl::OnScrollEvent(ui::ScrollEvent* event) {
653 client_view_->DestroyTouchSelection();
656 void TouchSelectionControllerImpl::QuickMenuTimerFired() {
657 gfx::Rect menu_anchor = GetQuickMenuAnchorRect();
658 if (menu_anchor == gfx::Rect())
659 return;
661 ui::TouchSelectionMenuRunner::GetInstance()->OpenMenu(
662 this, menu_anchor, GetMaxHandleImageSize(),
663 client_view_->GetNativeView());
666 void TouchSelectionControllerImpl::StartQuickMenuTimer() {
667 if (quick_menu_timer_.IsRunning())
668 return;
669 quick_menu_timer_.Start(
670 FROM_HERE,
671 base::TimeDelta::FromMilliseconds(kQuickMenuTimoutMs),
672 this,
673 &TouchSelectionControllerImpl::QuickMenuTimerFired);
676 void TouchSelectionControllerImpl::UpdateQuickMenu() {
677 // Hide quick menu to be shown when the timer fires.
678 HideQuickMenu();
679 StartQuickMenuTimer();
682 void TouchSelectionControllerImpl::HideQuickMenu() {
683 if (ui::TouchSelectionMenuRunner::GetInstance()->IsRunning())
684 ui::TouchSelectionMenuRunner::GetInstance()->CloseMenu();
685 quick_menu_timer_.Stop();
688 gfx::Rect TouchSelectionControllerImpl::GetQuickMenuAnchorRect() const {
689 // Get selection end points in client_view's space.
690 ui::SelectionBound b1_in_screen = selection_bound_1_clipped_;
691 ui::SelectionBound b2_in_screen = cursor_handle_->IsWidgetVisible()
692 ? b1_in_screen
693 : selection_bound_2_clipped_;
694 // Convert from screen to client.
695 ui::SelectionBound b1 = ConvertFromScreen(client_view_, b1_in_screen);
696 ui::SelectionBound b2 = ConvertFromScreen(client_view_, b2_in_screen);
698 // if selection is completely inside the view, we display the quick menu in
699 // the middle of the end points on the top. Else, we show it above the visible
700 // handle. If no handle is visible, we do not show the menu.
701 gfx::Rect menu_anchor;
702 if (ShouldShowHandleFor(b1) && ShouldShowHandleFor(b2))
703 menu_anchor = ui::RectBetweenSelectionBounds(b1_in_screen, b2_in_screen);
704 else if (ShouldShowHandleFor(b1))
705 menu_anchor = BoundToRect(b1_in_screen);
706 else if (ShouldShowHandleFor(b2))
707 menu_anchor = BoundToRect(b2_in_screen);
708 else
709 return menu_anchor;
711 // Enlarge the anchor rect so that the menu is offset from the text at least
712 // by the same distance the handles are offset from the text.
713 menu_anchor.Inset(0, -kSelectionHandleVerticalVisualOffset);
715 return menu_anchor;
718 gfx::NativeView TouchSelectionControllerImpl::GetCursorHandleNativeView() {
719 return cursor_handle_->GetWidget()->GetNativeView();
722 gfx::Rect TouchSelectionControllerImpl::GetSelectionHandle1Bounds() {
723 return selection_handle_1_->GetBoundsInScreen();
726 gfx::Rect TouchSelectionControllerImpl::GetSelectionHandle2Bounds() {
727 return selection_handle_2_->GetBoundsInScreen();
730 gfx::Rect TouchSelectionControllerImpl::GetCursorHandleBounds() {
731 return cursor_handle_->GetBoundsInScreen();
734 bool TouchSelectionControllerImpl::IsSelectionHandle1Visible() {
735 return selection_handle_1_->IsWidgetVisible();
738 bool TouchSelectionControllerImpl::IsSelectionHandle2Visible() {
739 return selection_handle_2_->IsWidgetVisible();
742 bool TouchSelectionControllerImpl::IsCursorHandleVisible() {
743 return cursor_handle_->IsWidgetVisible();
746 gfx::Rect TouchSelectionControllerImpl::GetExpectedHandleBounds(
747 const ui::SelectionBound& bound) {
748 return GetSelectionWidgetBounds(bound);
751 views::WidgetDelegateView* TouchSelectionControllerImpl::GetHandle1View() {
752 return selection_handle_1_.get();
755 views::WidgetDelegateView* TouchSelectionControllerImpl::GetHandle2View() {
756 return selection_handle_2_.get();
759 } // namespace views