Add abhijeet.k@samsung.com to AUTHORS list.
[chromium-blink-merge.git] / ash / drag_drop / drag_drop_controller.cc
blobbab52ee2bb270d8fca7b0caa086ee4c7a6c12083
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"
9 #include "ash/shell.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"
34 namespace ash {
35 namespace {
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
49 // |vertical_offset|.
50 gfx::Rect AdjustDragImageBoundsForScaleAndOffset(
51 const gfx::Rect& drag_image_bounds,
52 int vertical_offset,
53 float scale,
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);
76 } // namespace
78 class DragDropTrackerDelegate : public aura::WindowDelegate {
79 public:
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 {
96 return HTCAPTION;
98 bool ShouldDescendIntoChildForEventHandling(
99 aura::Window* child,
100 const gfx::Point& location) override {
101 return true;
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());
118 private:
119 DragDropController* drag_drop_controller_;
121 DISALLOW_COPY_AND_ASSIGN(DragDropTrackerDelegate);
124 ////////////////////////////////////////////////////////////////////////////////
125 // DragDropController, public:
127 DragDropController::DragDropController()
128 : drag_data_(NULL),
129 drag_operation_(0),
130 drag_window_(NULL),
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);
141 Cleanup();
142 if (cancel_animation_)
143 cancel_animation_->End();
144 if (drag_image_)
145 drag_image_.reset();
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,
153 int operation,
154 ui::DragDropTypes::DragEventSource source) {
155 if (IsDragDropInProgress())
156 return 0;
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())
162 return 0;
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();
188 drag_data_ = &data;
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));
215 drag_window_ = NULL;
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);
226 run_loop.Run();
229 if (drag_operation_ == 0) {
230 UMA_HISTOGRAM_ENUMERATION("Event.DragDrop.Cancel", source,
231 ui::DragDropTypes::DRAG_EVENT_SOURCE_COUNT);
232 } else {
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_) {
253 if (drag_window_) {
254 aura::client::DragDropDelegate* delegate =
255 aura::client::GetDragDropDelegate(drag_window_);
256 if (delegate)
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_);
267 if (delegate) {
268 ui::DropTargetEvent e(*drag_data_,
269 event.location(),
270 event.root_location(),
271 drag_operation_);
272 e.set_flags(event.flags());
273 delegate->OnDragEntered(e);
275 } else {
276 aura::client::DragDropDelegate* delegate =
277 aura::client::GetDragDropDelegate(drag_window_);
278 if (delegate) {
279 ui::DropTargetEvent e(*drag_data_,
280 event.location(),
281 event.root_location(),
282 drag_operation_);
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);
320 if (delegate) {
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);
327 else
328 drag_image_.reset();
329 } else {
330 drag_image_.reset();
333 Cleanup();
334 if (should_block_during_drag_drop_)
335 quit_closure_.Run();
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) {
348 DragCancel();
349 event->StopPropagation();
353 void DragDropController::OnMouseEvent(ui::MouseEvent* event) {
354 if (!IsDragDropInProgress())
355 return;
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();
362 return;
365 aura::Window* translated_target = drag_drop_tracker_->GetTarget(*event);
366 if (!translated_target) {
367 DragCancel();
368 event->StopPropagation();
369 return;
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());
376 break;
377 case ui::ET_MOUSE_RELEASED:
378 Drop(translated_target, *translated_event.get());
379 break;
380 default:
381 // We could also reach here because RootWindow may sometimes generate a
382 // bunch of fake mouse events
383 // (aura::RootWindow::PostMouseMoveEventAfterWindowChange).
384 break;
386 event->StopPropagation();
389 void DragDropController::OnTouchEvent(ui::TouchEvent* event) {
390 if (!IsDragDropInProgress())
391 return;
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())
399 return;
401 if (event->type() == ui::ET_TOUCH_CANCELLED)
402 DragCancel();
405 void DragDropController::OnGestureEvent(ui::GestureEvent* event) {
406 if (!IsDragDropInProgress())
407 return;
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)
419 return;
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) {
435 DragCancel();
436 event->SetHandled();
437 return;
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());
445 break;
446 case ui::ET_GESTURE_SCROLL_END:
447 case ui::ET_SCROLL_FLING_START:
448 Drop(translated_target, *translated_event.get());
449 break;
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
455 // the long tap.
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);
461 break;
462 default:
463 break;
465 event->SetHandled();
468 void DragDropController::OnWindowDestroyed(aura::Window* window) {
469 if (drag_window_ == window)
470 drag_window_ = NULL;
471 if (drag_source_window_ == window)
472 drag_source_window_ = NULL;
475 ////////////////////////////////////////////////////////////////////////////////
476 // DragDropController, protected:
478 gfx::LinearAnimation* DragDropController::CreateCancelAnimation(
479 int duration,
480 int frame_rate,
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())
494 drag_image_.reset();
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();
499 } else {
500 // See comment about this in OnGestureEvent().
501 base::MessageLoopForUI::current()->PostTask(
502 FROM_HERE,
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
514 // a drag/drop.
515 aura::client::DragDropDelegate* delegate = drag_window_?
516 aura::client::GetDragDropDelegate(drag_window_) : NULL;
517 if (delegate)
518 delegate->OnDragExited();
520 Cleanup();
521 drag_operation_ = 0;
522 StartCanceledAnimation(drag_cancel_animation_duration_ms);
523 if (should_block_during_drag_drop_)
524 quit_closure_.Run();
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,
545 this));
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() {
561 if (drag_window_)
562 drag_window_->RemoveObserver(this);
563 drag_window_ = NULL;
564 drag_data_ = NULL;
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();
570 } // namespace ash