1 // Copyright (c) 2012 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 "ash/drag_drop/drag_drop_controller.h"
7 #include "ash/drag_drop/drag_drop_tracker.h"
8 #include "ash/drag_drop/drag_image_view.h"
10 #include "base/bind.h"
11 #include "base/message_loop/message_loop.h"
12 #include "base/metrics/histogram_macros.h"
13 #include "base/run_loop.h"
14 #include "ui/aura/client/capture_client.h"
15 #include "ui/aura/env.h"
16 #include "ui/aura/window.h"
17 #include "ui/aura/window_delegate.h"
18 #include "ui/aura/window_event_dispatcher.h"
19 #include "ui/base/dragdrop/drag_drop_types.h"
20 #include "ui/base/dragdrop/os_exchange_data.h"
21 #include "ui/base/hit_test.h"
22 #include "ui/events/event.h"
23 #include "ui/events/event_utils.h"
24 #include "ui/gfx/animation/linear_animation.h"
25 #include "ui/gfx/geometry/point.h"
26 #include "ui/gfx/geometry/rect.h"
27 #include "ui/gfx/geometry/rect_conversions.h"
28 #include "ui/gfx/path.h"
29 #include "ui/views/views_delegate.h"
30 #include "ui/views/widget/native_widget_aura.h"
31 #include "ui/wm/core/coordinate_conversion.h"
32 #include "ui/wm/public/drag_drop_delegate.h"
37 // The duration of the drag cancel animation in millisecond.
38 const int kCancelAnimationDuration
= 250;
39 const int kTouchCancelAnimationDuration
= 20;
40 // The frame rate of the drag cancel animation in hertz.
41 const int kCancelAnimationFrameRate
= 60;
43 // For touch initiated dragging, we scale and shift drag image by the following:
44 static const float kTouchDragImageScale
= 1.2f
;
45 static const int kTouchDragImageVerticalOffset
= -25;
47 // Adjusts the drag image bounds such that the new bounds are scaled by |scale|
48 // and translated by the |drag_image_offset| and and additional
50 gfx::Rect
AdjustDragImageBoundsForScaleAndOffset(
51 const gfx::Rect
& drag_image_bounds
,
54 gfx::Vector2d
* drag_image_offset
) {
55 gfx::PointF final_origin
= drag_image_bounds
.origin();
56 gfx::SizeF final_size
= drag_image_bounds
.size();
57 final_size
.Scale(scale
);
58 drag_image_offset
->set_x(drag_image_offset
->x() * scale
);
59 drag_image_offset
->set_y(drag_image_offset
->y() * scale
);
60 float total_x_offset
= drag_image_offset
->x();
61 float total_y_offset
= drag_image_offset
->y() - vertical_offset
;
62 final_origin
.Offset(-total_x_offset
, -total_y_offset
);
63 return gfx::ToEnclosingRect(gfx::RectF(final_origin
, final_size
));
66 void DispatchGestureEndToWindow(aura::Window
* window
) {
67 if (window
&& window
->delegate()) {
68 ui::GestureEvent
gesture_end(0,
71 ui::EventTimeForNow(),
72 ui::GestureEventDetails(ui::ET_GESTURE_END
));
73 window
->delegate()->OnGestureEvent(&gesture_end
);
78 class DragDropTrackerDelegate
: public aura::WindowDelegate
{
80 explicit DragDropTrackerDelegate(DragDropController
* controller
)
81 : drag_drop_controller_(controller
) {}
82 ~DragDropTrackerDelegate() override
{}
84 // Overridden from WindowDelegate:
85 gfx::Size
GetMinimumSize() const override
{ return gfx::Size(); }
87 gfx::Size
GetMaximumSize() const override
{ return gfx::Size(); }
89 void OnBoundsChanged(const gfx::Rect
& old_bounds
,
90 const gfx::Rect
& new_bounds
) override
{}
91 ui::TextInputClient
* GetFocusedTextInputClient() override
{ return nullptr; }
92 gfx::NativeCursor
GetCursor(const gfx::Point
& point
) override
{
93 return gfx::kNullCursor
;
95 int GetNonClientComponent(const gfx::Point
& point
) const override
{
98 bool ShouldDescendIntoChildForEventHandling(
100 const gfx::Point
& location
) override
{
103 bool CanFocus() override
{ return true; }
104 void OnCaptureLost() override
{
105 if (drag_drop_controller_
->IsDragDropInProgress())
106 drag_drop_controller_
->DragCancel();
108 void OnPaint(const ui::PaintContext
& context
) override
{}
109 void OnDeviceScaleFactorChanged(float device_scale_factor
) override
{}
110 void OnWindowDestroying(aura::Window
* window
) override
{}
111 void OnWindowDestroyed(aura::Window
* window
) override
{}
112 void OnWindowTargetVisibilityChanged(bool visible
) override
{}
113 bool HasHitTestMask() const override
{ return true; }
114 void GetHitTestMask(gfx::Path
* mask
) const override
{
115 DCHECK(mask
->isEmpty());
119 DragDropController
* drag_drop_controller_
;
121 DISALLOW_COPY_AND_ASSIGN(DragDropTrackerDelegate
);
124 ////////////////////////////////////////////////////////////////////////////////
125 // DragDropController, public:
127 DragDropController::DragDropController()
131 drag_source_window_(NULL
),
132 should_block_during_drag_drop_(true),
133 drag_drop_window_delegate_(new DragDropTrackerDelegate(this)),
134 current_drag_event_source_(ui::DragDropTypes::DRAG_EVENT_SOURCE_MOUSE
),
135 weak_factory_(this) {
136 Shell::GetInstance()->PrependPreTargetHandler(this);
139 DragDropController::~DragDropController() {
140 Shell::GetInstance()->RemovePreTargetHandler(this);
142 if (cancel_animation_
)
143 cancel_animation_
->End();
148 int DragDropController::StartDragAndDrop(
149 const ui::OSExchangeData
& data
,
150 aura::Window
* root_window
,
151 aura::Window
* source_window
,
152 const gfx::Point
& screen_location
,
154 ui::DragDropTypes::DragEventSource source
) {
155 if (IsDragDropInProgress())
158 const ui::OSExchangeData::Provider
* provider
= &data
.provider();
159 // We do not support touch drag/drop without a drag image.
160 if (source
== ui::DragDropTypes::DRAG_EVENT_SOURCE_TOUCH
&&
161 provider
->GetDragImage().size().IsEmpty())
164 UMA_HISTOGRAM_ENUMERATION("Event.DragDrop.Start", source
,
165 ui::DragDropTypes::DRAG_EVENT_SOURCE_COUNT
);
167 current_drag_event_source_
= source
;
168 DragDropTracker
* tracker
=
169 new DragDropTracker(root_window
, drag_drop_window_delegate_
.get());
170 if (source
== ui::DragDropTypes::DRAG_EVENT_SOURCE_TOUCH
) {
171 // We need to transfer the current gesture sequence and the GR's touch event
172 // queue to the |drag_drop_tracker_|'s capture window so that when it takes
173 // capture, it still gets a valid gesture state.
174 ui::GestureRecognizer::Get()->TransferEventsTo(source_window
,
175 tracker
->capture_window());
176 // We also send a gesture end to the source window so it can clear state.
177 // TODO(varunjain): Remove this whole block when gesture sequence
178 // transferring is properly done in the GR (http://crbug.com/160558)
179 DispatchGestureEndToWindow(source_window
);
181 tracker
->TakeCapture();
182 drag_drop_tracker_
.reset(tracker
);
183 drag_source_window_
= source_window
;
184 if (drag_source_window_
)
185 drag_source_window_
->AddObserver(this);
186 pending_long_tap_
.reset();
189 drag_operation_
= operation
;
191 float drag_image_scale
= 1;
192 int drag_image_vertical_offset
= 0;
193 if (source
== ui::DragDropTypes::DRAG_EVENT_SOURCE_TOUCH
) {
194 drag_image_scale
= kTouchDragImageScale
;
195 drag_image_vertical_offset
= kTouchDragImageVerticalOffset
;
197 gfx::Point start_location
= screen_location
;
198 drag_image_final_bounds_for_cancel_animation_
= gfx::Rect(
199 start_location
- provider
->GetDragImageOffset(),
200 provider
->GetDragImage().size());
201 drag_image_
.reset(new DragImageView(source_window
->GetRootWindow(), source
));
202 drag_image_
->SetImage(provider
->GetDragImage());
203 drag_image_offset_
= provider
->GetDragImageOffset();
204 gfx::Rect
drag_image_bounds(start_location
, drag_image_
->GetPreferredSize());
205 drag_image_bounds
= AdjustDragImageBoundsForScaleAndOffset(drag_image_bounds
,
206 drag_image_vertical_offset
, drag_image_scale
, &drag_image_offset_
);
207 drag_image_
->SetBoundsInScreen(drag_image_bounds
);
208 drag_image_
->SetWidgetVisible(true);
209 if (source
== ui::DragDropTypes::DRAG_EVENT_SOURCE_TOUCH
) {
210 drag_image_
->SetTouchDragOperationHintPosition(gfx::Point(
211 drag_image_offset_
.x(),
212 drag_image_offset_
.y() + drag_image_vertical_offset
));
217 // Ends cancel animation if it's in progress.
218 if (cancel_animation_
)
219 cancel_animation_
->End();
221 if (should_block_during_drag_drop_
) {
222 base::RunLoop run_loop
;
223 quit_closure_
= run_loop
.QuitClosure();
224 base::MessageLoopForUI
* loop
= base::MessageLoopForUI::current();
225 base::MessageLoop::ScopedNestableTaskAllower
allow_nested(loop
);
229 if (drag_operation_
== 0) {
230 UMA_HISTOGRAM_ENUMERATION("Event.DragDrop.Cancel", source
,
231 ui::DragDropTypes::DRAG_EVENT_SOURCE_COUNT
);
233 UMA_HISTOGRAM_ENUMERATION("Event.DragDrop.Drop", source
,
234 ui::DragDropTypes::DRAG_EVENT_SOURCE_COUNT
);
237 if (!cancel_animation_
.get() || !cancel_animation_
->is_animating() ||
238 !pending_long_tap_
.get()) {
239 // If drag cancel animation is running, this cleanup is done when the
240 // animation completes.
241 if (drag_source_window_
)
242 drag_source_window_
->RemoveObserver(this);
243 drag_source_window_
= NULL
;
246 return drag_operation_
;
249 void DragDropController::DragUpdate(aura::Window
* target
,
250 const ui::LocatedEvent
& event
) {
251 int op
= ui::DragDropTypes::DRAG_NONE
;
252 if (target
!= drag_window_
) {
254 aura::client::DragDropDelegate
* delegate
=
255 aura::client::GetDragDropDelegate(drag_window_
);
257 delegate
->OnDragExited();
258 if (drag_window_
!= drag_source_window_
)
259 drag_window_
->RemoveObserver(this);
261 drag_window_
= target
;
262 // We are already an observer of |drag_source_window_| so no need to add.
263 if (drag_window_
!= drag_source_window_
)
264 drag_window_
->AddObserver(this);
265 aura::client::DragDropDelegate
* delegate
=
266 aura::client::GetDragDropDelegate(drag_window_
);
268 ui::DropTargetEvent
e(*drag_data_
,
270 event
.root_location(),
272 e
.set_flags(event
.flags());
273 delegate
->OnDragEntered(e
);
276 aura::client::DragDropDelegate
* delegate
=
277 aura::client::GetDragDropDelegate(drag_window_
);
279 ui::DropTargetEvent
e(*drag_data_
,
281 event
.root_location(),
283 e
.set_flags(event
.flags());
284 op
= delegate
->OnDragUpdated(e
);
285 gfx::NativeCursor cursor
= ui::kCursorNoDrop
;
286 if (op
& ui::DragDropTypes::DRAG_COPY
)
287 cursor
= ui::kCursorCopy
;
288 else if (op
& ui::DragDropTypes::DRAG_LINK
)
289 cursor
= ui::kCursorAlias
;
290 else if (op
& ui::DragDropTypes::DRAG_MOVE
)
291 cursor
= ui::kCursorGrabbing
;
292 ash::Shell::GetInstance()->cursor_manager()->SetCursor(cursor
);
296 DCHECK(drag_image_
.get());
297 if (drag_image_
->visible()) {
298 gfx::Point root_location_in_screen
= event
.root_location();
299 ::wm::ConvertPointToScreen(target
->GetRootWindow(),
300 &root_location_in_screen
);
301 drag_image_
->SetScreenPosition(
302 root_location_in_screen
- drag_image_offset_
);
303 drag_image_
->SetTouchDragOperation(op
);
307 void DragDropController::Drop(aura::Window
* target
,
308 const ui::LocatedEvent
& event
) {
309 ash::Shell::GetInstance()->cursor_manager()->SetCursor(ui::kCursorPointer
);
311 // We must guarantee that a target gets a OnDragEntered before Drop. WebKit
312 // depends on not getting a Drop without DragEnter. This behavior is
313 // consistent with drag/drop on other platforms.
314 if (target
!= drag_window_
)
315 DragUpdate(target
, event
);
316 DCHECK(target
== drag_window_
);
318 aura::client::DragDropDelegate
* delegate
=
319 aura::client::GetDragDropDelegate(target
);
321 ui::DropTargetEvent
e(
322 *drag_data_
, event
.location(), event
.root_location(), drag_operation_
);
323 e
.set_flags(event
.flags());
324 drag_operation_
= delegate
->OnPerformDrop(e
);
325 if (drag_operation_
== 0)
326 StartCanceledAnimation(kCancelAnimationDuration
);
334 if (should_block_during_drag_drop_
)
338 void DragDropController::DragCancel() {
339 DoDragCancel(kCancelAnimationDuration
);
342 bool DragDropController::IsDragDropInProgress() {
343 return !!drag_drop_tracker_
.get();
346 void DragDropController::OnKeyEvent(ui::KeyEvent
* event
) {
347 if (IsDragDropInProgress() && event
->key_code() == ui::VKEY_ESCAPE
) {
349 event
->StopPropagation();
353 void DragDropController::OnMouseEvent(ui::MouseEvent
* event
) {
354 if (!IsDragDropInProgress())
357 // If current drag session was not started by mouse, dont process this mouse
358 // event, but consume it so it does not interfere with current drag session.
359 if (current_drag_event_source_
!=
360 ui::DragDropTypes::DRAG_EVENT_SOURCE_MOUSE
) {
361 event
->StopPropagation();
365 aura::Window
* translated_target
= drag_drop_tracker_
->GetTarget(*event
);
366 if (!translated_target
) {
368 event
->StopPropagation();
371 scoped_ptr
<ui::LocatedEvent
> translated_event(
372 drag_drop_tracker_
->ConvertEvent(translated_target
, *event
));
373 switch (translated_event
->type()) {
374 case ui::ET_MOUSE_DRAGGED
:
375 DragUpdate(translated_target
, *translated_event
.get());
377 case ui::ET_MOUSE_RELEASED
:
378 Drop(translated_target
, *translated_event
.get());
381 // We could also reach here because RootWindow may sometimes generate a
382 // bunch of fake mouse events
383 // (aura::RootWindow::PostMouseMoveEventAfterWindowChange).
386 event
->StopPropagation();
389 void DragDropController::OnTouchEvent(ui::TouchEvent
* event
) {
390 if (!IsDragDropInProgress())
393 // If current drag session was not started by touch, dont process this touch
394 // event, but consume it so it does not interfere with current drag session.
395 if (current_drag_event_source_
!= ui::DragDropTypes::DRAG_EVENT_SOURCE_TOUCH
)
396 event
->StopPropagation();
398 if (event
->handled())
401 if (event
->type() == ui::ET_TOUCH_CANCELLED
)
405 void DragDropController::OnGestureEvent(ui::GestureEvent
* event
) {
406 if (!IsDragDropInProgress())
409 // No one else should handle gesture events when in drag drop. Note that it is
410 // not enough to just set ER_HANDLED because the dispatcher only stops
411 // dispatching when the event has ER_CONSUMED. If we just set ER_HANDLED, the
412 // event will still be dispatched to other handlers and we depend on
413 // individual handlers' kindness to not touch events marked ER_HANDLED (not
414 // all handlers are so kind and may cause bugs like crbug.com/236493).
415 event
->StopPropagation();
417 // If current drag session was not started by touch, dont process this event.
418 if (current_drag_event_source_
!= ui::DragDropTypes::DRAG_EVENT_SOURCE_TOUCH
)
421 // Apply kTouchDragImageVerticalOffset to the location.
422 ui::GestureEvent
touch_offset_event(*event
,
423 static_cast<aura::Window
*>(NULL
),
424 static_cast<aura::Window
*>(NULL
));
425 gfx::Point touch_offset_location
= touch_offset_event
.location();
426 gfx::Point touch_offset_root_location
= touch_offset_event
.root_location();
427 touch_offset_location
.Offset(0, kTouchDragImageVerticalOffset
);
428 touch_offset_root_location
.Offset(0, kTouchDragImageVerticalOffset
);
429 touch_offset_event
.set_location(touch_offset_location
);
430 touch_offset_event
.set_root_location(touch_offset_root_location
);
432 aura::Window
* translated_target
=
433 drag_drop_tracker_
->GetTarget(touch_offset_event
);
434 if (!translated_target
) {
439 scoped_ptr
<ui::LocatedEvent
> translated_event(
440 drag_drop_tracker_
->ConvertEvent(translated_target
, touch_offset_event
));
442 switch (event
->type()) {
443 case ui::ET_GESTURE_SCROLL_UPDATE
:
444 DragUpdate(translated_target
, *translated_event
.get());
446 case ui::ET_GESTURE_SCROLL_END
:
447 case ui::ET_SCROLL_FLING_START
:
448 Drop(translated_target
, *translated_event
.get());
450 case ui::ET_GESTURE_LONG_TAP
:
451 // Ideally we would want to just forward this long tap event to the
452 // |drag_source_window_|. However, webkit does not accept events while a
453 // drag drop is still in progress. The drag drop ends only when the nested
454 // message loop ends. Due to this stupidity, we have to defer forwarding
456 pending_long_tap_
.reset(
457 new ui::GestureEvent(*event
,
458 static_cast<aura::Window
*>(drag_drop_tracker_
->capture_window()),
459 static_cast<aura::Window
*>(drag_source_window_
)));
460 DoDragCancel(kTouchCancelAnimationDuration
);
468 void DragDropController::OnWindowDestroyed(aura::Window
* window
) {
469 if (drag_window_
== window
)
471 if (drag_source_window_
== window
)
472 drag_source_window_
= NULL
;
475 ////////////////////////////////////////////////////////////////////////////////
476 // DragDropController, protected:
478 gfx::LinearAnimation
* DragDropController::CreateCancelAnimation(
481 gfx::AnimationDelegate
* delegate
) {
482 return new gfx::LinearAnimation(duration
, frame_rate
, delegate
);
485 ////////////////////////////////////////////////////////////////////////////////
486 // DragDropController, private:
488 void DragDropController::AnimationEnded(const gfx::Animation
* animation
) {
489 cancel_animation_
.reset();
491 // By the time we finish animation, another drag/drop session may have
492 // started. We do not want to destroy the drag image in that case.
493 if (!IsDragDropInProgress())
495 if (pending_long_tap_
) {
496 // If not in a nested message loop, we can forward the long tap right now.
497 if (!should_block_during_drag_drop_
) {
498 ForwardPendingLongTap();
500 // See comment about this in OnGestureEvent().
501 base::MessageLoopForUI::current()->PostTask(
503 base::Bind(&DragDropController::ForwardPendingLongTap
,
504 weak_factory_
.GetWeakPtr()));
509 void DragDropController::DoDragCancel(int drag_cancel_animation_duration_ms
) {
510 ash::Shell::GetInstance()->cursor_manager()->SetCursor(ui::kCursorPointer
);
512 // |drag_window_| can be NULL if we have just started the drag and have not
513 // received any DragUpdates, or, if the |drag_window_| gets destroyed during
515 aura::client::DragDropDelegate
* delegate
= drag_window_
?
516 aura::client::GetDragDropDelegate(drag_window_
) : NULL
;
518 delegate
->OnDragExited();
522 StartCanceledAnimation(drag_cancel_animation_duration_ms
);
523 if (should_block_during_drag_drop_
)
527 void DragDropController::AnimationProgressed(const gfx::Animation
* animation
) {
528 gfx::Rect current_bounds
= animation
->CurrentValueBetween(
529 drag_image_initial_bounds_for_cancel_animation_
,
530 drag_image_final_bounds_for_cancel_animation_
);
531 drag_image_
->SetBoundsInScreen(current_bounds
);
534 void DragDropController::AnimationCanceled(const gfx::Animation
* animation
) {
535 AnimationEnded(animation
);
538 void DragDropController::StartCanceledAnimation(int animation_duration_ms
) {
539 DCHECK(drag_image_
.get());
540 drag_image_
->SetTouchDragOperationHintOff();
541 drag_image_initial_bounds_for_cancel_animation_
=
542 drag_image_
->GetBoundsInScreen();
543 cancel_animation_
.reset(CreateCancelAnimation(animation_duration_ms
,
544 kCancelAnimationFrameRate
,
546 cancel_animation_
->Start();
549 void DragDropController::ForwardPendingLongTap() {
550 if (drag_source_window_
&& drag_source_window_
->delegate()) {
551 drag_source_window_
->delegate()->OnGestureEvent(pending_long_tap_
.get());
552 DispatchGestureEndToWindow(drag_source_window_
);
554 pending_long_tap_
.reset();
555 if (drag_source_window_
)
556 drag_source_window_
->RemoveObserver(this);
557 drag_source_window_
= NULL
;
560 void DragDropController::Cleanup() {
562 drag_window_
->RemoveObserver(this);
565 // Cleanup can be called again while deleting DragDropTracker, so delete
566 // the pointer with a local variable to avoid double free.
567 scoped_ptr
<ash::DragDropTracker
> holder
= drag_drop_tracker_
.Pass();