Mark //testing/perf target testonly.
[chromium-blink-merge.git] / ash / drag_drop / drag_drop_controller.cc
blobdedfffdbf2a6ed9cf6acb9aa19cff14f3ca9bc94
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(gfx::Canvas* canvas) 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& root_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 = root_location;
198 ::wm::ConvertPointToScreen(root_window, &start_location);
199 drag_image_final_bounds_for_cancel_animation_ = gfx::Rect(
200 start_location - provider->GetDragImageOffset(),
201 provider->GetDragImage().size());
202 drag_image_.reset(new DragImageView(source_window->GetRootWindow(), source));
203 drag_image_->SetImage(provider->GetDragImage());
204 drag_image_offset_ = provider->GetDragImageOffset();
205 gfx::Rect drag_image_bounds(start_location, drag_image_->GetPreferredSize());
206 drag_image_bounds = AdjustDragImageBoundsForScaleAndOffset(drag_image_bounds,
207 drag_image_vertical_offset, drag_image_scale, &drag_image_offset_);
208 drag_image_->SetBoundsInScreen(drag_image_bounds);
209 drag_image_->SetWidgetVisible(true);
210 if (source == ui::DragDropTypes::DRAG_EVENT_SOURCE_TOUCH) {
211 drag_image_->SetTouchDragOperationHintPosition(gfx::Point(
212 drag_image_offset_.x(),
213 drag_image_offset_.y() + drag_image_vertical_offset));
216 drag_window_ = NULL;
218 // Ends cancel animation if it's in progress.
219 if (cancel_animation_)
220 cancel_animation_->End();
222 if (should_block_during_drag_drop_) {
223 base::RunLoop run_loop;
224 quit_closure_ = run_loop.QuitClosure();
225 base::MessageLoopForUI* loop = base::MessageLoopForUI::current();
226 base::MessageLoop::ScopedNestableTaskAllower allow_nested(loop);
227 run_loop.Run();
230 if (drag_operation_ == 0) {
231 UMA_HISTOGRAM_ENUMERATION("Event.DragDrop.Cancel", source,
232 ui::DragDropTypes::DRAG_EVENT_SOURCE_COUNT);
233 } else {
234 UMA_HISTOGRAM_ENUMERATION("Event.DragDrop.Drop", source,
235 ui::DragDropTypes::DRAG_EVENT_SOURCE_COUNT);
238 if (!cancel_animation_.get() || !cancel_animation_->is_animating() ||
239 !pending_long_tap_.get()) {
240 // If drag cancel animation is running, this cleanup is done when the
241 // animation completes.
242 if (drag_source_window_)
243 drag_source_window_->RemoveObserver(this);
244 drag_source_window_ = NULL;
247 return drag_operation_;
250 void DragDropController::DragUpdate(aura::Window* target,
251 const ui::LocatedEvent& event) {
252 int op = ui::DragDropTypes::DRAG_NONE;
253 if (target != drag_window_) {
254 if (drag_window_) {
255 aura::client::DragDropDelegate* delegate =
256 aura::client::GetDragDropDelegate(drag_window_);
257 if (delegate)
258 delegate->OnDragExited();
259 if (drag_window_ != drag_source_window_)
260 drag_window_->RemoveObserver(this);
262 drag_window_ = target;
263 // We are already an observer of |drag_source_window_| so no need to add.
264 if (drag_window_ != drag_source_window_)
265 drag_window_->AddObserver(this);
266 aura::client::DragDropDelegate* delegate =
267 aura::client::GetDragDropDelegate(drag_window_);
268 if (delegate) {
269 ui::DropTargetEvent e(*drag_data_,
270 event.location(),
271 event.root_location(),
272 drag_operation_);
273 e.set_flags(event.flags());
274 delegate->OnDragEntered(e);
276 } else {
277 aura::client::DragDropDelegate* delegate =
278 aura::client::GetDragDropDelegate(drag_window_);
279 if (delegate) {
280 ui::DropTargetEvent e(*drag_data_,
281 event.location(),
282 event.root_location(),
283 drag_operation_);
284 e.set_flags(event.flags());
285 op = delegate->OnDragUpdated(e);
286 gfx::NativeCursor cursor = ui::kCursorNoDrop;
287 if (op & ui::DragDropTypes::DRAG_COPY)
288 cursor = ui::kCursorCopy;
289 else if (op & ui::DragDropTypes::DRAG_LINK)
290 cursor = ui::kCursorAlias;
291 else if (op & ui::DragDropTypes::DRAG_MOVE)
292 cursor = ui::kCursorGrabbing;
293 ash::Shell::GetInstance()->cursor_manager()->SetCursor(cursor);
297 DCHECK(drag_image_.get());
298 if (drag_image_->visible()) {
299 gfx::Point root_location_in_screen = event.root_location();
300 ::wm::ConvertPointToScreen(target->GetRootWindow(),
301 &root_location_in_screen);
302 drag_image_->SetScreenPosition(
303 root_location_in_screen - drag_image_offset_);
304 drag_image_->SetTouchDragOperation(op);
308 void DragDropController::Drop(aura::Window* target,
309 const ui::LocatedEvent& event) {
310 ash::Shell::GetInstance()->cursor_manager()->SetCursor(ui::kCursorPointer);
312 // We must guarantee that a target gets a OnDragEntered before Drop. WebKit
313 // depends on not getting a Drop without DragEnter. This behavior is
314 // consistent with drag/drop on other platforms.
315 if (target != drag_window_)
316 DragUpdate(target, event);
317 DCHECK(target == drag_window_);
319 aura::client::DragDropDelegate* delegate =
320 aura::client::GetDragDropDelegate(target);
321 if (delegate) {
322 ui::DropTargetEvent e(
323 *drag_data_, event.location(), event.root_location(), drag_operation_);
324 e.set_flags(event.flags());
325 drag_operation_ = delegate->OnPerformDrop(e);
326 if (drag_operation_ == 0)
327 StartCanceledAnimation(kCancelAnimationDuration);
328 else
329 drag_image_.reset();
330 } else {
331 drag_image_.reset();
334 Cleanup();
335 if (should_block_during_drag_drop_)
336 quit_closure_.Run();
339 void DragDropController::DragCancel() {
340 DoDragCancel(kCancelAnimationDuration);
343 bool DragDropController::IsDragDropInProgress() {
344 return !!drag_drop_tracker_.get();
347 void DragDropController::OnKeyEvent(ui::KeyEvent* event) {
348 if (IsDragDropInProgress() && event->key_code() == ui::VKEY_ESCAPE) {
349 DragCancel();
350 event->StopPropagation();
354 void DragDropController::OnMouseEvent(ui::MouseEvent* event) {
355 if (!IsDragDropInProgress())
356 return;
358 // If current drag session was not started by mouse, dont process this mouse
359 // event, but consume it so it does not interfere with current drag session.
360 if (current_drag_event_source_ !=
361 ui::DragDropTypes::DRAG_EVENT_SOURCE_MOUSE) {
362 event->StopPropagation();
363 return;
366 aura::Window* translated_target = drag_drop_tracker_->GetTarget(*event);
367 if (!translated_target) {
368 DragCancel();
369 event->StopPropagation();
370 return;
372 scoped_ptr<ui::LocatedEvent> translated_event(
373 drag_drop_tracker_->ConvertEvent(translated_target, *event));
374 switch (translated_event->type()) {
375 case ui::ET_MOUSE_DRAGGED:
376 DragUpdate(translated_target, *translated_event.get());
377 break;
378 case ui::ET_MOUSE_RELEASED:
379 Drop(translated_target, *translated_event.get());
380 break;
381 default:
382 // We could also reach here because RootWindow may sometimes generate a
383 // bunch of fake mouse events
384 // (aura::RootWindow::PostMouseMoveEventAfterWindowChange).
385 break;
387 event->StopPropagation();
390 void DragDropController::OnTouchEvent(ui::TouchEvent* event) {
391 if (!IsDragDropInProgress())
392 return;
394 // If current drag session was not started by touch, dont process this touch
395 // event, but consume it so it does not interfere with current drag session.
396 if (current_drag_event_source_ != ui::DragDropTypes::DRAG_EVENT_SOURCE_TOUCH)
397 event->StopPropagation();
399 if (event->handled())
400 return;
402 if (event->type() == ui::ET_TOUCH_CANCELLED)
403 DragCancel();
406 void DragDropController::OnGestureEvent(ui::GestureEvent* event) {
407 if (!IsDragDropInProgress())
408 return;
410 // No one else should handle gesture events when in drag drop. Note that it is
411 // not enough to just set ER_HANDLED because the dispatcher only stops
412 // dispatching when the event has ER_CONSUMED. If we just set ER_HANDLED, the
413 // event will still be dispatched to other handlers and we depend on
414 // individual handlers' kindness to not touch events marked ER_HANDLED (not
415 // all handlers are so kind and may cause bugs like crbug.com/236493).
416 event->StopPropagation();
418 // If current drag session was not started by touch, dont process this event.
419 if (current_drag_event_source_ != ui::DragDropTypes::DRAG_EVENT_SOURCE_TOUCH)
420 return;
422 // Apply kTouchDragImageVerticalOffset to the location.
423 ui::GestureEvent touch_offset_event(*event,
424 static_cast<aura::Window*>(NULL),
425 static_cast<aura::Window*>(NULL));
426 gfx::Point touch_offset_location = touch_offset_event.location();
427 gfx::Point touch_offset_root_location = touch_offset_event.root_location();
428 touch_offset_location.Offset(0, kTouchDragImageVerticalOffset);
429 touch_offset_root_location.Offset(0, kTouchDragImageVerticalOffset);
430 touch_offset_event.set_location(touch_offset_location);
431 touch_offset_event.set_root_location(touch_offset_root_location);
433 aura::Window* translated_target =
434 drag_drop_tracker_->GetTarget(touch_offset_event);
435 if (!translated_target) {
436 DragCancel();
437 event->SetHandled();
438 return;
440 scoped_ptr<ui::LocatedEvent> translated_event(
441 drag_drop_tracker_->ConvertEvent(translated_target, touch_offset_event));
443 switch (event->type()) {
444 case ui::ET_GESTURE_SCROLL_UPDATE:
445 DragUpdate(translated_target, *translated_event.get());
446 break;
447 case ui::ET_GESTURE_SCROLL_END:
448 case ui::ET_SCROLL_FLING_START:
449 Drop(translated_target, *translated_event.get());
450 break;
451 case ui::ET_GESTURE_LONG_TAP:
452 // Ideally we would want to just forward this long tap event to the
453 // |drag_source_window_|. However, webkit does not accept events while a
454 // drag drop is still in progress. The drag drop ends only when the nested
455 // message loop ends. Due to this stupidity, we have to defer forwarding
456 // the long tap.
457 pending_long_tap_.reset(
458 new ui::GestureEvent(*event,
459 static_cast<aura::Window*>(drag_drop_tracker_->capture_window()),
460 static_cast<aura::Window*>(drag_source_window_)));
461 DoDragCancel(kTouchCancelAnimationDuration);
462 break;
463 default:
464 break;
466 event->SetHandled();
469 void DragDropController::OnWindowDestroyed(aura::Window* window) {
470 if (drag_window_ == window)
471 drag_window_ = NULL;
472 if (drag_source_window_ == window)
473 drag_source_window_ = NULL;
476 ////////////////////////////////////////////////////////////////////////////////
477 // DragDropController, protected:
479 gfx::LinearAnimation* DragDropController::CreateCancelAnimation(
480 int duration,
481 int frame_rate,
482 gfx::AnimationDelegate* delegate) {
483 return new gfx::LinearAnimation(duration, frame_rate, delegate);
486 ////////////////////////////////////////////////////////////////////////////////
487 // DragDropController, private:
489 void DragDropController::AnimationEnded(const gfx::Animation* animation) {
490 cancel_animation_.reset();
492 // By the time we finish animation, another drag/drop session may have
493 // started. We do not want to destroy the drag image in that case.
494 if (!IsDragDropInProgress())
495 drag_image_.reset();
496 if (pending_long_tap_) {
497 // If not in a nested message loop, we can forward the long tap right now.
498 if (!should_block_during_drag_drop_) {
499 ForwardPendingLongTap();
500 } else {
501 // See comment about this in OnGestureEvent().
502 base::MessageLoopForUI::current()->PostTask(
503 FROM_HERE,
504 base::Bind(&DragDropController::ForwardPendingLongTap,
505 weak_factory_.GetWeakPtr()));
510 void DragDropController::DoDragCancel(int drag_cancel_animation_duration_ms) {
511 ash::Shell::GetInstance()->cursor_manager()->SetCursor(ui::kCursorPointer);
513 // |drag_window_| can be NULL if we have just started the drag and have not
514 // received any DragUpdates, or, if the |drag_window_| gets destroyed during
515 // a drag/drop.
516 aura::client::DragDropDelegate* delegate = drag_window_?
517 aura::client::GetDragDropDelegate(drag_window_) : NULL;
518 if (delegate)
519 delegate->OnDragExited();
521 Cleanup();
522 drag_operation_ = 0;
523 StartCanceledAnimation(drag_cancel_animation_duration_ms);
524 if (should_block_during_drag_drop_)
525 quit_closure_.Run();
528 void DragDropController::AnimationProgressed(const gfx::Animation* animation) {
529 gfx::Rect current_bounds = animation->CurrentValueBetween(
530 drag_image_initial_bounds_for_cancel_animation_,
531 drag_image_final_bounds_for_cancel_animation_);
532 drag_image_->SetBoundsInScreen(current_bounds);
535 void DragDropController::AnimationCanceled(const gfx::Animation* animation) {
536 AnimationEnded(animation);
539 void DragDropController::StartCanceledAnimation(int animation_duration_ms) {
540 DCHECK(drag_image_.get());
541 drag_image_->SetTouchDragOperationHintOff();
542 drag_image_initial_bounds_for_cancel_animation_ =
543 drag_image_->GetBoundsInScreen();
544 cancel_animation_.reset(CreateCancelAnimation(animation_duration_ms,
545 kCancelAnimationFrameRate,
546 this));
547 cancel_animation_->Start();
550 void DragDropController::ForwardPendingLongTap() {
551 if (drag_source_window_ && drag_source_window_->delegate()) {
552 drag_source_window_->delegate()->OnGestureEvent(pending_long_tap_.get());
553 DispatchGestureEndToWindow(drag_source_window_);
555 pending_long_tap_.reset();
556 if (drag_source_window_)
557 drag_source_window_->RemoveObserver(this);
558 drag_source_window_ = NULL;
561 void DragDropController::Cleanup() {
562 if (drag_window_)
563 drag_window_->RemoveObserver(this);
564 drag_window_ = NULL;
565 drag_data_ = NULL;
566 // Cleanup can be called again while deleting DragDropTracker, so delete
567 // the pointer with a local variable to avoid double free.
568 scoped_ptr<ash::DragDropTracker> holder = drag_drop_tracker_.Pass();
571 } // namespace ash