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/wm/core/coordinate_conversion.h"
23 #include "ui/wm/core/masked_window_targeter.h"
27 // Constants defining the visual attributes of selection handles
29 // The distance by which a handle image is offset from the bottom of the
30 // selection/text baseline.
31 const int kSelectionHandleVerticalVisualOffset
= 2;
33 // When a handle is dragged, the drag position reported to the client view is
34 // offset vertically to represent the cursor position. This constant specifies
35 // the offset in pixels above the bottom of the selection (see pic below). This
36 // is required because say if this is zero, that means the drag position we
37 // report is right on the text baseline. In that case, a vertical movement of
38 // even one pixel will make the handle jump to the line below it. So when the
39 // user just starts dragging, the handle will jump to the next line if the user
40 // makes any vertical movement. So we have this non-zero offset to prevent this
43 // Editing handle widget showing the padding and difference between the position
44 // of the ET_GESTURE_SCROLL_UPDATE event and the drag position reported to the
47 // Selection Highlight --->_____|__|<-|---- Drag position reported to client
49 // Vertical Padding __| | <-|---- ET_GESTURE_SCROLL_UPDATE position
50 // |_ |_____|<--- Editing handle widget
56 const int kSelectionHandleVerticalDragOffset
= 5;
58 // Padding around the selection handle defining the area that will be included
59 // in the touch target to make dragging the handle easier (see pic above).
60 const int kSelectionHandleHorizPadding
= 10;
61 const int kSelectionHandleVertPadding
= 20;
63 const int kContextMenuTimoutMs
= 200;
65 const int kSelectionHandleQuickFadeDurationMs
= 50;
67 // Minimum height for selection handle bar. If the bar height is going to be
68 // less than this value, handle will not be shown.
69 const int kSelectionHandleBarMinHeight
= 5;
70 // Maximum amount that selection handle bar can stick out of client view's
72 const int kSelectionHandleBarBottomAllowance
= 3;
74 // Creates a widget to host SelectionHandleView.
75 views::Widget
* CreateTouchSelectionPopupWidget(
76 gfx::NativeView context
,
77 views::WidgetDelegate
* widget_delegate
) {
78 views::Widget
* widget
= new views::Widget
;
79 views::Widget::InitParams
params(views::Widget::InitParams::TYPE_POPUP
);
80 params
.opacity
= views::Widget::InitParams::TRANSLUCENT_WINDOW
;
81 params
.shadow_type
= views::Widget::InitParams::SHADOW_TYPE_NONE
;
82 params
.ownership
= views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET
;
83 params
.parent
= context
;
84 params
.delegate
= widget_delegate
;
89 gfx::Image
* GetCenterHandleImage() {
90 static gfx::Image
* handle_image
= nullptr;
92 handle_image
= &ui::ResourceBundle::GetSharedInstance().GetImageNamed(
93 IDR_TEXT_SELECTION_HANDLE_CENTER
);
98 gfx::Image
* GetLeftHandleImage() {
99 static gfx::Image
* handle_image
= nullptr;
101 handle_image
= &ui::ResourceBundle::GetSharedInstance().GetImageNamed(
102 IDR_TEXT_SELECTION_HANDLE_LEFT
);
107 gfx::Image
* GetRightHandleImage() {
108 static gfx::Image
* handle_image
= nullptr;
110 handle_image
= &ui::ResourceBundle::GetSharedInstance().GetImageNamed(
111 IDR_TEXT_SELECTION_HANDLE_RIGHT
);
116 // Return the appropriate handle image based on the bound's type
117 gfx::Image
* GetHandleImage(ui::SelectionBound::Type bound_type
) {
119 case ui::SelectionBound::LEFT
:
120 return GetLeftHandleImage();
121 case ui::SelectionBound::CENTER
:
122 return GetCenterHandleImage();
123 case ui::SelectionBound::RIGHT
:
124 return GetRightHandleImage();
126 NOTREACHED() << "Invalid touch handle bound type.";
131 // Calculates the bounds of the widget containing the selection handle based
132 // on the SelectionBound's type and location
133 gfx::Rect
GetSelectionWidgetBounds(const ui::SelectionBound
& bound
) {
134 gfx::Size image_size
= GetHandleImage(bound
.type())->Size();
135 int widget_width
= image_size
.width() + 2 * kSelectionHandleHorizPadding
;
136 int widget_height
= bound
.GetHeight() + image_size
.height() +
137 kSelectionHandleVerticalVisualOffset
+
138 kSelectionHandleVertPadding
;
139 // Due to the shape of the handle images, the widget is aligned differently to
140 // the selection bound depending on the type of the bound.
142 switch (bound
.type()) {
143 case ui::SelectionBound::LEFT
:
144 widget_left
= bound
.edge_top_rounded().x() - image_size
.width() -
145 kSelectionHandleHorizPadding
;
147 case ui::SelectionBound::RIGHT
:
148 widget_left
= bound
.edge_top_rounded().x() - kSelectionHandleHorizPadding
;
150 case ui::SelectionBound::CENTER
:
151 widget_left
= bound
.edge_top_rounded().x() - widget_width
/ 2;
154 NOTREACHED() << "Undefined bound type.";
158 widget_left
, bound
.edge_top_rounded().y(), widget_width
, widget_height
);
161 gfx::Size
GetMaxHandleImageSize() {
162 gfx::Rect center_rect
= gfx::Rect(GetCenterHandleImage()->Size());
163 gfx::Rect left_rect
= gfx::Rect(GetLeftHandleImage()->Size());
164 gfx::Rect right_rect
= gfx::Rect(GetRightHandleImage()->Size());
165 gfx::Rect union_rect
= center_rect
;
166 union_rect
.Union(left_rect
);
167 union_rect
.Union(right_rect
);
168 return union_rect
.size();
171 // Convenience methods to convert a |bound| from screen to the |client|'s
172 // coordinate system and vice versa.
173 // Note that this is not quite correct because it does not take into account
174 // transforms such as rotation and scaling. This should be in TouchEditable.
175 // TODO(varunjain): Fix this.
176 ui::SelectionBound
ConvertFromScreen(ui::TouchEditable
* client
,
177 const ui::SelectionBound
& bound
) {
178 ui::SelectionBound result
= bound
;
179 gfx::Point edge_bottom
= bound
.edge_bottom_rounded();
180 gfx::Point edge_top
= bound
.edge_top_rounded();
181 client
->ConvertPointFromScreen(&edge_bottom
);
182 client
->ConvertPointFromScreen(&edge_top
);
183 result
.SetEdge(edge_top
, edge_bottom
);
187 ui::SelectionBound
ConvertToScreen(ui::TouchEditable
* client
,
188 const ui::SelectionBound
& bound
) {
189 ui::SelectionBound result
= bound
;
190 gfx::Point edge_bottom
= bound
.edge_bottom_rounded();
191 gfx::Point edge_top
= bound
.edge_top_rounded();
192 client
->ConvertPointToScreen(&edge_bottom
);
193 client
->ConvertPointToScreen(&edge_top
);
194 result
.SetEdge(edge_top
, edge_bottom
);
198 gfx::Rect
BoundToRect(const ui::SelectionBound
& bound
) {
199 return gfx::BoundingRect(bound
.edge_top_rounded(),
200 bound
.edge_bottom_rounded());
207 typedef TouchSelectionControllerImpl::EditingHandleView EditingHandleView
;
209 class TouchHandleWindowTargeter
: public wm::MaskedWindowTargeter
{
211 TouchHandleWindowTargeter(aura::Window
* window
,
212 EditingHandleView
* handle_view
);
214 ~TouchHandleWindowTargeter() override
{}
217 // wm::MaskedWindowTargeter:
218 bool GetHitTestMask(aura::Window
* window
, gfx::Path
* mask
) const override
;
220 EditingHandleView
* handle_view_
;
222 DISALLOW_COPY_AND_ASSIGN(TouchHandleWindowTargeter
);
225 // A View that displays the text selection handle.
226 class TouchSelectionControllerImpl::EditingHandleView
227 : public views::WidgetDelegateView
{
229 EditingHandleView(TouchSelectionControllerImpl
* controller
,
230 gfx::NativeView context
,
231 bool is_cursor_handle
)
232 : controller_(controller
),
233 image_(GetCenterHandleImage()),
234 is_cursor_handle_(is_cursor_handle
),
235 draw_invisible_(false) {
236 widget_
.reset(CreateTouchSelectionPopupWidget(context
, this));
237 widget_
->SetContentsView(this);
239 aura::Window
* window
= widget_
->GetNativeWindow();
240 window
->SetEventTargeter(scoped_ptr
<ui::EventTargeter
>(
241 new TouchHandleWindowTargeter(window
, this)));
243 // We are owned by the TouchSelectionControllerImpl.
244 set_owned_by_client();
247 ~EditingHandleView() override
{ SetWidgetVisible(false, false); }
249 // Overridden from views::WidgetDelegateView:
250 bool WidgetHasHitTestMask() const override
{ return true; }
252 void GetWidgetHitTestMask(gfx::Path
* mask
) const override
{
253 gfx::Size image_size
= image_
->Size();
256 SkIntToScalar(selection_bound_
.GetHeight() +
257 kSelectionHandleVerticalVisualOffset
),
258 SkIntToScalar(image_size
.width()) + 2 * kSelectionHandleHorizPadding
,
259 SkIntToScalar(selection_bound_
.GetHeight() +
260 kSelectionHandleVerticalVisualOffset
+
261 image_size
.height() + kSelectionHandleVertPadding
));
264 void DeleteDelegate() override
{
265 // We are owned and deleted by TouchSelectionControllerImpl.
268 // Overridden from views::View:
269 void OnPaint(gfx::Canvas
* canvas
) override
{
273 // Draw the handle image.
274 canvas
->DrawImageInt(
275 *image_
->ToImageSkia(),
276 kSelectionHandleHorizPadding
,
277 selection_bound_
.GetHeight() + kSelectionHandleVerticalVisualOffset
);
280 void OnGestureEvent(ui::GestureEvent
* event
) override
{
282 switch (event
->type()) {
283 case ui::ET_GESTURE_SCROLL_BEGIN
: {
284 widget_
->SetCapture(this);
285 controller_
->SetDraggingHandle(this);
286 // Distance from the point which is |kSelectionHandleVerticalDragOffset|
287 // pixels above the bottom of the selection bound edge to the event
288 // location (aka the touch-drag point).
289 drag_offset_
= selection_bound_
.edge_bottom_rounded() -
290 gfx::Vector2d(0, kSelectionHandleVerticalDragOffset
) -
294 case ui::ET_GESTURE_SCROLL_UPDATE
: {
295 controller_
->SelectionHandleDragged(event
->location() + drag_offset_
);
298 case ui::ET_GESTURE_SCROLL_END
:
299 case ui::ET_SCROLL_FLING_START
:
300 widget_
->ReleaseCapture();
301 controller_
->SetDraggingHandle(nullptr);
308 gfx::Size
GetPreferredSize() const override
{
309 return GetSelectionWidgetBounds(selection_bound_
).size();
312 bool IsWidgetVisible() const {
313 return widget_
->IsVisible();
316 void SetWidgetVisible(bool visible
, bool quick
) {
317 if (widget_
->IsVisible() == visible
)
319 widget_
->SetVisibilityAnimationDuration(
320 base::TimeDelta::FromMilliseconds(
321 quick
? kSelectionHandleQuickFadeDurationMs
: 0));
328 void SetBoundInScreen(const ui::SelectionBound
& bound
) {
329 bool update_bound_type
= false;
330 // Cursor handle should always have the bound type CENTER
331 DCHECK(!is_cursor_handle_
|| bound
.type() == ui::SelectionBound::CENTER
);
333 if (bound
.type() != selection_bound_
.type()) {
334 // Unless this is a cursor handle, do not set the type to CENTER -
335 // selection handles corresponding to a selection should always use left
336 // or right handle image. If selection handles are dragged to be located
337 // at the same spot, the |bound|'s type here will be CENTER for both of
338 // them. In this case do not update the type of the |selection_bound_|.
339 if (bound
.type() != ui::SelectionBound::CENTER
|| is_cursor_handle_
)
340 update_bound_type
= true;
342 if (update_bound_type
) {
343 selection_bound_
.set_type(bound
.type());
344 image_
= GetHandleImage(bound
.type());
347 selection_bound_
.SetEdge(bound
.edge_top(), bound
.edge_bottom());
349 widget_
->SetBounds(GetSelectionWidgetBounds(selection_bound_
));
351 aura::Window
* window
= widget_
->GetNativeView();
352 gfx::Point edge_top
= selection_bound_
.edge_top_rounded();
353 gfx::Point edge_bottom
= selection_bound_
.edge_bottom_rounded();
354 wm::ConvertPointFromScreen(window
, &edge_top
);
355 wm::ConvertPointFromScreen(window
, &edge_bottom
);
356 selection_bound_
.SetEdge(edge_top
, edge_bottom
);
359 void SetDrawInvisible(bool draw_invisible
) {
360 if (draw_invisible_
== draw_invisible
)
362 draw_invisible_
= draw_invisible
;
367 scoped_ptr
<Widget
> widget_
;
368 TouchSelectionControllerImpl
* controller_
;
370 // In local coordinates
371 ui::SelectionBound selection_bound_
;
374 // If true, this is a handle corresponding to the single cursor, otherwise it
375 // is a handle corresponding to one of the two selection bounds.
376 bool is_cursor_handle_
;
378 // Offset applied to the scroll events location when calling
379 // TouchSelectionControllerImpl::SelectionHandleDragged while dragging the
381 gfx::Vector2d drag_offset_
;
383 // If set to true, the handle will not draw anything, hence providing an empty
384 // widget. We need this because we may want to stop showing the handle while
385 // it is being dragged. Since it is being dragged, we cannot destroy the
387 bool draw_invisible_
;
389 DISALLOW_COPY_AND_ASSIGN(EditingHandleView
);
392 TouchHandleWindowTargeter::TouchHandleWindowTargeter(
393 aura::Window
* window
,
394 EditingHandleView
* handle_view
)
395 : wm::MaskedWindowTargeter(window
),
396 handle_view_(handle_view
) {
399 bool TouchHandleWindowTargeter::GetHitTestMask(aura::Window
* window
,
400 gfx::Path
* mask
) const {
401 handle_view_
->GetWidgetHitTestMask(mask
);
405 TouchSelectionControllerImpl::TouchSelectionControllerImpl(
406 ui::TouchEditable
* client_view
)
407 : client_view_(client_view
),
408 client_widget_(nullptr),
409 selection_handle_1_(new EditingHandleView(this,
410 client_view
->GetNativeView(),
412 selection_handle_2_(new EditingHandleView(this,
413 client_view
->GetNativeView(),
415 cursor_handle_(new EditingHandleView(this,
416 client_view
->GetNativeView(),
418 context_menu_(nullptr),
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
);
426 client_widget_
->AddObserver(this);
427 aura::Env::GetInstance()->AddPreTargetHandler(this);
430 TouchSelectionControllerImpl::~TouchSelectionControllerImpl() {
431 UMA_HISTOGRAM_BOOLEAN("Event.TouchSelection.EndedWithAction",
434 aura::Env::GetInstance()->RemovePreTargetHandler(this);
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_
)
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()) {
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
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
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
);
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
);
518 cursor_handle_
->SetWidgetVisible(false, false);
520 selection_handle_1_
.get(), anchor
, screen_bound_anchor_clipped
);
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_
)
542 StartContextMenuTimer();
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
);
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
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
)
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
,
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",
608 base::TimeDelta::FromMilliseconds(500),
609 base::TimeDelta::FromSeconds(60),
612 client_view_
->ExecuteCommand(command_id
, event_flags
);
615 void TouchSelectionControllerImpl::OpenContextMenu() {
616 // Context menu should appear centered on top of the selected region.
617 const gfx::Rect rect
= context_menu_
->GetAnchorRect();
618 const gfx::Point
anchor(rect
.CenterPoint().x(), rect
.y());
620 client_view_
->OpenContextMenu(anchor
);
623 void TouchSelectionControllerImpl::OnMenuClosed(TouchEditingMenuView
* menu
) {
624 if (menu
== context_menu_
)
625 context_menu_
= nullptr;
628 void TouchSelectionControllerImpl::OnAncestorWindowTransformed(
629 aura::Window
* window
,
630 aura::Window
* ancestor
) {
631 client_view_
->DestroyTouchSelection();
634 void TouchSelectionControllerImpl::OnWidgetClosing(Widget
* widget
) {
635 DCHECK_EQ(client_widget_
, widget
);
636 client_widget_
= nullptr;
639 void TouchSelectionControllerImpl::OnWidgetBoundsChanged(
641 const gfx::Rect
& new_bounds
) {
642 DCHECK_EQ(client_widget_
, widget
);
646 void TouchSelectionControllerImpl::OnKeyEvent(ui::KeyEvent
* event
) {
647 client_view_
->DestroyTouchSelection();
650 void TouchSelectionControllerImpl::OnMouseEvent(ui::MouseEvent
* event
) {
651 aura::client::CursorClient
* cursor_client
= aura::client::GetCursorClient(
652 client_view_
->GetNativeView()->GetRootWindow());
653 if (!cursor_client
|| cursor_client
->IsMouseEventsEnabled())
654 client_view_
->DestroyTouchSelection();
657 void TouchSelectionControllerImpl::OnScrollEvent(ui::ScrollEvent
* event
) {
658 client_view_
->DestroyTouchSelection();
661 void TouchSelectionControllerImpl::ContextMenuTimerFired() {
662 // Get selection end points in client_view's space.
663 ui::SelectionBound b1_in_screen
= selection_bound_1_clipped_
;
664 ui::SelectionBound b2_in_screen
=
665 cursor_handle_
->IsWidgetVisible() ? b1_in_screen
666 : selection_bound_2_clipped_
;
667 // Convert from screen to client.
668 ui::SelectionBound b1
= ConvertFromScreen(client_view_
, b1_in_screen
);
669 ui::SelectionBound b2
= ConvertFromScreen(client_view_
, b2_in_screen
);
671 // if selection is completely inside the view, we display the context menu
672 // in the middle of the end points on the top. Else, we show it above the
673 // visible handle. If no handle is visible, we do not show the menu.
674 gfx::Rect menu_anchor
;
675 if (ShouldShowHandleFor(b1
) && ShouldShowHandleFor(b2
))
676 menu_anchor
= ui::RectBetweenSelectionBounds(b1_in_screen
, b2_in_screen
);
677 else if (ShouldShowHandleFor(b1
))
678 menu_anchor
= BoundToRect(b1_in_screen
);
679 else if (ShouldShowHandleFor(b2
))
680 menu_anchor
= BoundToRect(b2_in_screen
);
684 // Enlarge the anchor rect so that the menu is offset from the text at least
685 // by the same distance the handles are offset from the text.
686 menu_anchor
.Inset(0, -kSelectionHandleVerticalVisualOffset
);
688 DCHECK(!context_menu_
);
689 context_menu_
= TouchEditingMenuView::Create(this, menu_anchor
,
690 GetMaxHandleImageSize(),
691 client_view_
->GetNativeView());
694 void TouchSelectionControllerImpl::StartContextMenuTimer() {
695 if (context_menu_timer_
.IsRunning())
697 context_menu_timer_
.Start(
699 base::TimeDelta::FromMilliseconds(kContextMenuTimoutMs
),
701 &TouchSelectionControllerImpl::ContextMenuTimerFired
);
704 void TouchSelectionControllerImpl::UpdateContextMenu() {
705 // Hide context menu to be shown when the timer fires.
707 StartContextMenuTimer();
710 void TouchSelectionControllerImpl::HideContextMenu() {
712 context_menu_
->Close();
713 context_menu_
= nullptr;
714 context_menu_timer_
.Stop();
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();