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 "ash/wm/coordinate_conversion.h"
11 #include "base/bind.h"
12 #include "base/message_loop/message_loop.h"
13 #include "base/run_loop.h"
14 #include "ui/aura/client/capture_client.h"
15 #include "ui/aura/client/drag_drop_delegate.h"
16 #include "ui/aura/env.h"
17 #include "ui/aura/root_window.h"
18 #include "ui/aura/window.h"
19 #include "ui/aura/window_delegate.h"
20 #include "ui/base/dragdrop/drag_drop_types.h"
21 #include "ui/base/dragdrop/os_exchange_data.h"
22 #include "ui/base/hit_test.h"
23 #include "ui/events/event.h"
24 #include "ui/events/event_utils.h"
25 #include "ui/gfx/animation/linear_animation.h"
26 #include "ui/gfx/path.h"
27 #include "ui/gfx/point.h"
28 #include "ui/gfx/rect.h"
29 #include "ui/gfx/rect_conversions.h"
30 #include "ui/views/views_delegate.h"
31 #include "ui/views/widget/native_widget_aura.h"
36 using aura::RootWindow
;
39 // The duration of the drag cancel animation in millisecond.
40 const int kCancelAnimationDuration
= 250;
41 const int kTouchCancelAnimationDuration
= 20;
42 // The frame rate of the drag cancel animation in hertz.
43 const int kCancelAnimationFrameRate
= 60;
45 // For touch initiated dragging, we scale and shift drag image by the following:
46 static const float kTouchDragImageScale
= 1.2f
;
47 static const int kTouchDragImageVerticalOffset
= -25;
49 // Adjusts the drag image bounds such that the new bounds are scaled by |scale|
50 // and translated by the |drag_image_offset| and and additional
52 gfx::Rect
AdjustDragImageBoundsForScaleAndOffset(
53 const gfx::Rect
& drag_image_bounds
,
56 gfx::Vector2d
* drag_image_offset
) {
57 gfx::PointF final_origin
= drag_image_bounds
.origin();
58 gfx::SizeF final_size
= drag_image_bounds
.size();
59 final_size
.Scale(scale
);
60 drag_image_offset
->set_x(drag_image_offset
->x() * scale
);
61 drag_image_offset
->set_y(drag_image_offset
->y() * scale
);
62 float total_x_offset
= drag_image_offset
->x();
63 float total_y_offset
= drag_image_offset
->y() - vertical_offset
;
64 final_origin
.Offset(-total_x_offset
, -total_y_offset
);
65 return gfx::ToEnclosingRect(gfx::RectF(final_origin
, final_size
));
68 void DispatchGestureEndToWindow(aura::Window
* window
) {
69 if (window
&& window
->delegate()) {
70 ui::GestureEvent
gesture_end(
75 ui::EventTimeForNow(),
76 ui::GestureEventDetails(ui::ET_GESTURE_END
, 0, 0),
78 window
->delegate()->OnGestureEvent(&gesture_end
);
83 class DragDropTrackerDelegate
: public aura::WindowDelegate
{
85 explicit DragDropTrackerDelegate(DragDropController
* controller
)
86 : drag_drop_controller_(controller
) {}
87 virtual ~DragDropTrackerDelegate() {}
89 // Overridden from WindowDelegate:
90 virtual gfx::Size
GetMinimumSize() const OVERRIDE
{
94 virtual gfx::Size
GetMaximumSize() const OVERRIDE
{
98 virtual void OnBoundsChanged(const gfx::Rect
& old_bounds
,
99 const gfx::Rect
& new_bounds
) OVERRIDE
{}
100 virtual gfx::NativeCursor
GetCursor(const gfx::Point
& point
) OVERRIDE
{
101 return gfx::kNullCursor
;
103 virtual int GetNonClientComponent(const gfx::Point
& point
) const OVERRIDE
{
106 virtual bool ShouldDescendIntoChildForEventHandling(
108 const gfx::Point
& location
) OVERRIDE
{
111 virtual bool CanFocus() OVERRIDE
{ return true; }
112 virtual void OnCaptureLost() OVERRIDE
{
113 if (drag_drop_controller_
->IsDragDropInProgress())
114 drag_drop_controller_
->DragCancel();
116 virtual void OnPaint(gfx::Canvas
* canvas
) OVERRIDE
{
118 virtual void OnDeviceScaleFactorChanged(float device_scale_factor
) OVERRIDE
{}
119 virtual void OnWindowDestroying() OVERRIDE
{}
120 virtual void OnWindowDestroyed() OVERRIDE
{}
121 virtual void OnWindowTargetVisibilityChanged(bool visible
) OVERRIDE
{}
122 virtual bool HasHitTestMask() const OVERRIDE
{
125 virtual void GetHitTestMask(gfx::Path
* mask
) const OVERRIDE
{
126 DCHECK(mask
->isEmpty());
128 virtual void DidRecreateLayer(ui::Layer
* old_layer
,
129 ui::Layer
* new_layer
) OVERRIDE
{}
132 DragDropController
* drag_drop_controller_
;
134 DISALLOW_COPY_AND_ASSIGN(DragDropTrackerDelegate
);
137 ////////////////////////////////////////////////////////////////////////////////
138 // DragDropController, public:
140 DragDropController::DragDropController()
144 drag_source_window_(NULL
),
145 should_block_during_drag_drop_(true),
146 drag_drop_window_delegate_(new DragDropTrackerDelegate(this)),
147 current_drag_event_source_(ui::DragDropTypes::DRAG_EVENT_SOURCE_MOUSE
),
148 weak_factory_(this) {
149 Shell::GetInstance()->PrependPreTargetHandler(this);
152 DragDropController::~DragDropController() {
153 Shell::GetInstance()->RemovePreTargetHandler(this);
155 if (cancel_animation_
)
156 cancel_animation_
->End();
161 int DragDropController::StartDragAndDrop(
162 const ui::OSExchangeData
& data
,
163 aura::RootWindow
* root_window
,
164 aura::Window
* source_window
,
165 const gfx::Point
& root_location
,
167 ui::DragDropTypes::DragEventSource source
) {
168 if (IsDragDropInProgress())
171 const ui::OSExchangeData::Provider
* provider
= &data
.provider();
172 // We do not support touch drag/drop without a drag image.
173 if (source
== ui::DragDropTypes::DRAG_EVENT_SOURCE_TOUCH
&&
174 provider
->GetDragImage().size().IsEmpty())
177 current_drag_event_source_
= source
;
178 DragDropTracker
* tracker
=
179 new DragDropTracker(root_window
, drag_drop_window_delegate_
.get());
180 if (source
== ui::DragDropTypes::DRAG_EVENT_SOURCE_TOUCH
) {
181 // We need to transfer the current gesture sequence and the GR's touch event
182 // queue to the |drag_drop_tracker_|'s capture window so that when it takes
183 // capture, it still gets a valid gesture state.
184 root_window
->gesture_recognizer()->TransferEventsTo(source_window
,
185 tracker
->capture_window());
186 // We also send a gesture end to the source window so it can clear state.
187 // TODO(varunjain): Remove this whole block when gesture sequence
188 // transferring is properly done in the GR (http://crbug.com/160558)
189 DispatchGestureEndToWindow(source_window
);
191 tracker
->TakeCapture();
192 drag_drop_tracker_
.reset(tracker
);
193 drag_source_window_
= source_window
;
194 if (drag_source_window_
)
195 drag_source_window_
->AddObserver(this);
196 pending_long_tap_
.reset();
199 drag_operation_
= operation
;
201 float drag_image_scale
= 1;
202 int drag_image_vertical_offset
= 0;
203 if (source
== ui::DragDropTypes::DRAG_EVENT_SOURCE_TOUCH
) {
204 drag_image_scale
= kTouchDragImageScale
;
205 drag_image_vertical_offset
= kTouchDragImageVerticalOffset
;
207 gfx::Point start_location
= root_location
;
208 ash::wm::ConvertPointToScreen(root_window
, &start_location
);
209 drag_image_final_bounds_for_cancel_animation_
= gfx::Rect(
210 start_location
- provider
->GetDragImageOffset(),
211 provider
->GetDragImage().size());
212 drag_image_
.reset(new DragImageView(source_window
->GetRootWindow()));
213 drag_image_
->SetImage(provider
->GetDragImage());
214 drag_image_offset_
= provider
->GetDragImageOffset();
215 gfx::Rect
drag_image_bounds(start_location
, drag_image_
->GetPreferredSize());
216 drag_image_bounds
= AdjustDragImageBoundsForScaleAndOffset(drag_image_bounds
,
217 drag_image_vertical_offset
, drag_image_scale
, &drag_image_offset_
);
218 drag_image_
->SetBoundsInScreen(drag_image_bounds
);
219 drag_image_
->SetWidgetVisible(true);
223 // Ends cancel animation if it's in progress.
224 if (cancel_animation_
)
225 cancel_animation_
->End();
227 if (should_block_during_drag_drop_
) {
228 base::RunLoop
run_loop(aura::Env::GetInstance()->GetDispatcher());
229 quit_closure_
= run_loop
.QuitClosure();
230 base::MessageLoopForUI
* loop
= base::MessageLoopForUI::current();
231 base::MessageLoop::ScopedNestableTaskAllower
allow_nested(loop
);
235 if (!cancel_animation_
.get() || !cancel_animation_
->is_animating() ||
236 !pending_long_tap_
.get()) {
237 // If drag cancel animation is running, this cleanup is done when the
238 // animation completes.
239 if (drag_source_window_
)
240 drag_source_window_
->RemoveObserver(this);
241 drag_source_window_
= NULL
;
244 return drag_operation_
;
247 void DragDropController::DragUpdate(aura::Window
* target
,
248 const ui::LocatedEvent
& event
) {
249 aura::client::DragDropDelegate
* delegate
= NULL
;
250 if (target
!= drag_window_
) {
252 if ((delegate
= aura::client::GetDragDropDelegate(drag_window_
)))
253 delegate
->OnDragExited();
254 if (drag_window_
!= drag_source_window_
)
255 drag_window_
->RemoveObserver(this);
257 drag_window_
= target
;
258 // We are already an observer of |drag_source_window_| so no need to add.
259 if (drag_window_
!= drag_source_window_
)
260 drag_window_
->AddObserver(this);
261 if ((delegate
= aura::client::GetDragDropDelegate(drag_window_
))) {
262 ui::DropTargetEvent
e(*drag_data_
,
264 event
.root_location(),
266 e
.set_flags(event
.flags());
267 delegate
->OnDragEntered(e
);
270 if ((delegate
= aura::client::GetDragDropDelegate(drag_window_
))) {
271 ui::DropTargetEvent
e(*drag_data_
,
273 event
.root_location(),
275 e
.set_flags(event
.flags());
276 int op
= delegate
->OnDragUpdated(e
);
277 gfx::NativeCursor cursor
= ui::kCursorNoDrop
;
278 if (op
& ui::DragDropTypes::DRAG_COPY
)
279 cursor
= ui::kCursorCopy
;
280 else if (op
& ui::DragDropTypes::DRAG_LINK
)
281 cursor
= ui::kCursorAlias
;
282 else if (op
& ui::DragDropTypes::DRAG_MOVE
)
283 cursor
= ui::kCursorGrabbing
;
284 ash::Shell::GetInstance()->cursor_manager()->SetCursor(cursor
);
288 DCHECK(drag_image_
.get());
289 if (drag_image_
->visible()) {
290 gfx::Point root_location_in_screen
= event
.root_location();
291 ash::wm::ConvertPointToScreen(target
->GetRootWindow(),
292 &root_location_in_screen
);
293 drag_image_
->SetScreenPosition(
294 root_location_in_screen
- drag_image_offset_
);
298 void DragDropController::Drop(aura::Window
* target
,
299 const ui::LocatedEvent
& event
) {
300 ash::Shell::GetInstance()->cursor_manager()->SetCursor(ui::kCursorPointer
);
301 aura::client::DragDropDelegate
* delegate
= NULL
;
303 // We must guarantee that a target gets a OnDragEntered before Drop. WebKit
304 // depends on not getting a Drop without DragEnter. This behavior is
305 // consistent with drag/drop on other platforms.
306 if (target
!= drag_window_
)
307 DragUpdate(target
, event
);
308 DCHECK(target
== drag_window_
);
310 if ((delegate
= aura::client::GetDragDropDelegate(target
))) {
311 ui::DropTargetEvent
e(
312 *drag_data_
, event
.location(), event
.root_location(), drag_operation_
);
313 e
.set_flags(event
.flags());
314 drag_operation_
= delegate
->OnPerformDrop(e
);
315 if (drag_operation_
== 0)
316 StartCanceledAnimation(kCancelAnimationDuration
);
324 if (should_block_during_drag_drop_
)
328 void DragDropController::DragCancel() {
329 DoDragCancel(kCancelAnimationDuration
);
332 bool DragDropController::IsDragDropInProgress() {
333 return !!drag_drop_tracker_
.get();
336 void DragDropController::OnKeyEvent(ui::KeyEvent
* event
) {
337 if (IsDragDropInProgress() && event
->key_code() == ui::VKEY_ESCAPE
) {
339 event
->StopPropagation();
343 void DragDropController::OnMouseEvent(ui::MouseEvent
* event
) {
344 if (!IsDragDropInProgress())
347 // If current drag session was not started by mouse, dont process this mouse
348 // event, but consume it so it does not interfere with current drag session.
349 if (current_drag_event_source_
!=
350 ui::DragDropTypes::DRAG_EVENT_SOURCE_MOUSE
) {
351 event
->StopPropagation();
355 aura::Window
* translated_target
= drag_drop_tracker_
->GetTarget(*event
);
356 if (!translated_target
) {
358 event
->StopPropagation();
361 scoped_ptr
<ui::LocatedEvent
> translated_event(
362 drag_drop_tracker_
->ConvertEvent(translated_target
, *event
));
363 switch (translated_event
->type()) {
364 case ui::ET_MOUSE_DRAGGED
:
365 DragUpdate(translated_target
, *translated_event
.get());
367 case ui::ET_MOUSE_RELEASED
:
368 Drop(translated_target
, *translated_event
.get());
371 // We could also reach here because RootWindow may sometimes generate a
372 // bunch of fake mouse events
373 // (aura::RootWindow::PostMouseMoveEventAfterWindowChange).
376 event
->StopPropagation();
379 void DragDropController::OnTouchEvent(ui::TouchEvent
* event
) {
380 if (!IsDragDropInProgress())
383 // If current drag session was not started by touch, dont process this touch
384 // event, but consume it so it does not interfere with current drag session.
385 if (current_drag_event_source_
!= ui::DragDropTypes::DRAG_EVENT_SOURCE_TOUCH
)
386 event
->StopPropagation();
388 if (event
->handled())
391 if (event
->type() == ui::ET_TOUCH_CANCELLED
)
395 void DragDropController::OnGestureEvent(ui::GestureEvent
* event
) {
396 if (!IsDragDropInProgress())
399 // No one else should handle gesture events when in drag drop. Note that it is
400 // not enough to just set ER_HANDLED because the dispatcher only stops
401 // dispatching when the event has ER_CONSUMED. If we just set ER_HANDLED, the
402 // event will still be dispatched to other handlers and we depend on
403 // individual handlers' kindness to not touch events marked ER_HANDLED (not
404 // all handlers are so kind and may cause bugs like crbug.com/236493).
405 event
->StopPropagation();
407 // If current drag session was not started by touch, dont process this event.
408 if (current_drag_event_source_
!= ui::DragDropTypes::DRAG_EVENT_SOURCE_TOUCH
)
411 // Apply kTouchDragImageVerticalOffset to the location.
412 ui::GestureEvent
touch_offset_event(*event
,
413 static_cast<aura::Window
*>(NULL
),
414 static_cast<aura::Window
*>(NULL
));
415 gfx::Point touch_offset_location
= touch_offset_event
.location();
416 gfx::Point touch_offset_root_location
= touch_offset_event
.root_location();
417 touch_offset_location
.Offset(0, kTouchDragImageVerticalOffset
);
418 touch_offset_root_location
.Offset(0, kTouchDragImageVerticalOffset
);
419 touch_offset_event
.set_location(touch_offset_location
);
420 touch_offset_event
.set_root_location(touch_offset_root_location
);
422 aura::Window
* translated_target
=
423 drag_drop_tracker_
->GetTarget(touch_offset_event
);
424 if (!translated_target
) {
429 scoped_ptr
<ui::LocatedEvent
> translated_event(
430 drag_drop_tracker_
->ConvertEvent(translated_target
, touch_offset_event
));
432 switch (event
->type()) {
433 case ui::ET_GESTURE_SCROLL_UPDATE
:
434 DragUpdate(translated_target
, *translated_event
.get());
436 case ui::ET_GESTURE_SCROLL_END
:
437 Drop(translated_target
, *translated_event
.get());
439 case ui::ET_SCROLL_FLING_START
:
440 case ui::ET_GESTURE_LONG_TAP
:
441 // Ideally we would want to just forward this long tap event to the
442 // |drag_source_window_|. However, webkit does not accept events while a
443 // drag drop is still in progress. The drag drop ends only when the nested
444 // message loop ends. Due to this stupidity, we have to defer forwarding
446 pending_long_tap_
.reset(
447 new ui::GestureEvent(*event
,
448 static_cast<aura::Window
*>(drag_drop_tracker_
->capture_window()),
449 static_cast<aura::Window
*>(drag_source_window_
)));
450 DoDragCancel(kTouchCancelAnimationDuration
);
458 void DragDropController::OnWindowDestroyed(aura::Window
* window
) {
459 if (drag_window_
== window
) {
460 drag_window_
->RemoveObserver(this);
463 if (drag_source_window_
== window
) {
464 drag_source_window_
->RemoveObserver(this);
465 drag_source_window_
= NULL
;
469 ////////////////////////////////////////////////////////////////////////////////
470 // DragDropController, protected:
472 gfx::LinearAnimation
* DragDropController::CreateCancelAnimation(
475 gfx::AnimationDelegate
* delegate
) {
476 return new gfx::LinearAnimation(duration
, frame_rate
, delegate
);
479 ////////////////////////////////////////////////////////////////////////////////
480 // DragDropController, private:
482 void DragDropController::AnimationEnded(const gfx::Animation
* animation
) {
483 cancel_animation_
.reset();
485 // By the time we finish animation, another drag/drop session may have
486 // started. We do not want to destroy the drag image in that case.
487 if (!IsDragDropInProgress())
489 if (pending_long_tap_
) {
490 // If not in a nested message loop, we can forward the long tap right now.
491 if (!should_block_during_drag_drop_
)
492 ForwardPendingLongTap();
494 // See comment about this in OnGestureEvent().
495 base::MessageLoopForUI::current()->PostTask(
497 base::Bind(&DragDropController::ForwardPendingLongTap
,
498 weak_factory_
.GetWeakPtr()));
503 void DragDropController::DoDragCancel(int drag_cancel_animation_duration_ms
) {
504 ash::Shell::GetInstance()->cursor_manager()->SetCursor(ui::kCursorPointer
);
506 // |drag_window_| can be NULL if we have just started the drag and have not
507 // received any DragUpdates, or, if the |drag_window_| gets destroyed during
509 aura::client::DragDropDelegate
* delegate
= drag_window_
?
510 aura::client::GetDragDropDelegate(drag_window_
) : NULL
;
512 delegate
->OnDragExited();
516 StartCanceledAnimation(drag_cancel_animation_duration_ms
);
517 if (should_block_during_drag_drop_
)
521 void DragDropController::AnimationProgressed(const gfx::Animation
* animation
) {
522 gfx::Rect current_bounds
= animation
->CurrentValueBetween(
523 drag_image_initial_bounds_for_cancel_animation_
,
524 drag_image_final_bounds_for_cancel_animation_
);
525 drag_image_
->SetBoundsInScreen(current_bounds
);
528 void DragDropController::AnimationCanceled(const gfx::Animation
* animation
) {
529 AnimationEnded(animation
);
532 void DragDropController::StartCanceledAnimation(int animation_duration_ms
) {
533 DCHECK(drag_image_
.get());
534 drag_image_initial_bounds_for_cancel_animation_
=
535 drag_image_
->GetBoundsInScreen();
536 cancel_animation_
.reset(CreateCancelAnimation(animation_duration_ms
,
537 kCancelAnimationFrameRate
,
539 cancel_animation_
->Start();
542 void DragDropController::ForwardPendingLongTap() {
543 if (drag_source_window_
&& drag_source_window_
->delegate()) {
544 drag_source_window_
->delegate()->OnGestureEvent(pending_long_tap_
.get());
545 DispatchGestureEndToWindow(drag_source_window_
);
547 pending_long_tap_
.reset();
548 if (drag_source_window_
)
549 drag_source_window_
->RemoveObserver(this);
550 drag_source_window_
= NULL
;
553 void DragDropController::Cleanup() {
555 drag_window_
->RemoveObserver(this);
558 // Cleanup can be called again while deleting DragDropTracker, so use Pass
559 // instead of reset to avoid double free.
560 drag_drop_tracker_
.Pass();
563 } // namespace internal