Call InvalidationController#refreshRegisteredTypes() on Chrome startup
[chromium-blink-merge.git] / ash / drag_drop / drag_drop_controller.cc
blob795265ba0e4a7e8c6b37a8e7be573f9bd55a154b
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/widget/native_widget_aura.h"
30 #include "ui/wm/core/coordinate_conversion.h"
31 #include "ui/wm/public/drag_drop_delegate.h"
33 namespace ash {
34 namespace {
36 // The duration of the drag cancel animation in millisecond.
37 const int kCancelAnimationDuration = 250;
38 const int kTouchCancelAnimationDuration = 20;
39 // The frame rate of the drag cancel animation in hertz.
40 const int kCancelAnimationFrameRate = 60;
42 // For touch initiated dragging, we scale and shift drag image by the following:
43 static const float kTouchDragImageScale = 1.2f;
44 static const int kTouchDragImageVerticalOffset = -25;
46 // Adjusts the drag image bounds such that the new bounds are scaled by |scale|
47 // and translated by the |drag_image_offset| and and additional
48 // |vertical_offset|.
49 gfx::Rect AdjustDragImageBoundsForScaleAndOffset(
50 const gfx::Rect& drag_image_bounds,
51 int vertical_offset,
52 float scale,
53 gfx::Vector2d* drag_image_offset) {
54 gfx::PointF final_origin = drag_image_bounds.origin();
55 gfx::SizeF final_size = drag_image_bounds.size();
56 final_size.Scale(scale);
57 drag_image_offset->set_x(drag_image_offset->x() * scale);
58 drag_image_offset->set_y(drag_image_offset->y() * scale);
59 float total_x_offset = drag_image_offset->x();
60 float total_y_offset = drag_image_offset->y() - vertical_offset;
61 final_origin.Offset(-total_x_offset, -total_y_offset);
62 return gfx::ToEnclosingRect(gfx::RectF(final_origin, final_size));
65 void DispatchGestureEndToWindow(aura::Window* window) {
66 if (window && window->delegate()) {
67 ui::GestureEvent gesture_end(0,
70 ui::EventTimeForNow(),
71 ui::GestureEventDetails(ui::ET_GESTURE_END));
72 window->delegate()->OnGestureEvent(&gesture_end);
75 } // namespace
77 class DragDropTrackerDelegate : public aura::WindowDelegate {
78 public:
79 explicit DragDropTrackerDelegate(DragDropController* controller)
80 : drag_drop_controller_(controller) {}
81 ~DragDropTrackerDelegate() override {}
83 // Overridden from WindowDelegate:
84 gfx::Size GetMinimumSize() const override { return gfx::Size(); }
86 gfx::Size GetMaximumSize() const override { return gfx::Size(); }
88 void OnBoundsChanged(const gfx::Rect& old_bounds,
89 const gfx::Rect& new_bounds) override {}
90 ui::TextInputClient* GetFocusedTextInputClient() override { return nullptr; }
91 gfx::NativeCursor GetCursor(const gfx::Point& point) override {
92 return gfx::kNullCursor;
94 int GetNonClientComponent(const gfx::Point& point) const override {
95 return HTCAPTION;
97 bool ShouldDescendIntoChildForEventHandling(
98 aura::Window* child,
99 const gfx::Point& location) override {
100 return true;
102 bool CanFocus() override { return true; }
103 void OnCaptureLost() override {
104 if (drag_drop_controller_->IsDragDropInProgress())
105 drag_drop_controller_->DragCancel();
107 void OnPaint(const ui::PaintContext& context) override {}
108 void OnDeviceScaleFactorChanged(float device_scale_factor) override {}
109 void OnWindowDestroying(aura::Window* window) override {}
110 void OnWindowDestroyed(aura::Window* window) override {}
111 void OnWindowTargetVisibilityChanged(bool visible) override {}
112 bool HasHitTestMask() const override { return true; }
113 void GetHitTestMask(gfx::Path* mask) const override {
114 DCHECK(mask->isEmpty());
117 private:
118 DragDropController* drag_drop_controller_;
120 DISALLOW_COPY_AND_ASSIGN(DragDropTrackerDelegate);
123 ////////////////////////////////////////////////////////////////////////////////
124 // DragDropController, public:
126 DragDropController::DragDropController()
127 : drag_data_(NULL),
128 drag_operation_(0),
129 drag_window_(NULL),
130 drag_source_window_(NULL),
131 should_block_during_drag_drop_(true),
132 drag_drop_window_delegate_(new DragDropTrackerDelegate(this)),
133 current_drag_event_source_(ui::DragDropTypes::DRAG_EVENT_SOURCE_MOUSE),
134 weak_factory_(this) {
135 Shell::GetInstance()->PrependPreTargetHandler(this);
138 DragDropController::~DragDropController() {
139 Shell::GetInstance()->RemovePreTargetHandler(this);
140 Cleanup();
141 if (cancel_animation_)
142 cancel_animation_->End();
143 if (drag_image_)
144 drag_image_.reset();
147 int DragDropController::StartDragAndDrop(
148 const ui::OSExchangeData& data,
149 aura::Window* root_window,
150 aura::Window* source_window,
151 const gfx::Point& screen_location,
152 int operation,
153 ui::DragDropTypes::DragEventSource source) {
154 if (IsDragDropInProgress())
155 return 0;
157 const ui::OSExchangeData::Provider* provider = &data.provider();
158 // We do not support touch drag/drop without a drag image.
159 if (source == ui::DragDropTypes::DRAG_EVENT_SOURCE_TOUCH &&
160 provider->GetDragImage().size().IsEmpty())
161 return 0;
163 UMA_HISTOGRAM_ENUMERATION("Event.DragDrop.Start", source,
164 ui::DragDropTypes::DRAG_EVENT_SOURCE_COUNT);
166 current_drag_event_source_ = source;
167 DragDropTracker* tracker =
168 new DragDropTracker(root_window, drag_drop_window_delegate_.get());
169 if (source == ui::DragDropTypes::DRAG_EVENT_SOURCE_TOUCH) {
170 // We need to transfer the current gesture sequence and the GR's touch event
171 // queue to the |drag_drop_tracker_|'s capture window so that when it takes
172 // capture, it still gets a valid gesture state.
173 ui::GestureRecognizer::Get()->TransferEventsTo(source_window,
174 tracker->capture_window());
175 // We also send a gesture end to the source window so it can clear state.
176 // TODO(varunjain): Remove this whole block when gesture sequence
177 // transferring is properly done in the GR (http://crbug.com/160558)
178 DispatchGestureEndToWindow(source_window);
180 tracker->TakeCapture();
181 drag_drop_tracker_.reset(tracker);
182 drag_source_window_ = source_window;
183 if (drag_source_window_)
184 drag_source_window_->AddObserver(this);
185 pending_long_tap_.reset();
187 drag_data_ = &data;
188 drag_operation_ = operation;
190 float drag_image_scale = 1;
191 int drag_image_vertical_offset = 0;
192 if (source == ui::DragDropTypes::DRAG_EVENT_SOURCE_TOUCH) {
193 drag_image_scale = kTouchDragImageScale;
194 drag_image_vertical_offset = kTouchDragImageVerticalOffset;
196 gfx::Point start_location = screen_location;
197 drag_image_final_bounds_for_cancel_animation_ = gfx::Rect(
198 start_location - provider->GetDragImageOffset(),
199 provider->GetDragImage().size());
200 drag_image_.reset(new DragImageView(source_window->GetRootWindow(), source));
201 drag_image_->SetImage(provider->GetDragImage());
202 drag_image_offset_ = provider->GetDragImageOffset();
203 gfx::Rect drag_image_bounds(start_location, drag_image_->GetPreferredSize());
204 drag_image_bounds = AdjustDragImageBoundsForScaleAndOffset(drag_image_bounds,
205 drag_image_vertical_offset, drag_image_scale, &drag_image_offset_);
206 drag_image_->SetBoundsInScreen(drag_image_bounds);
207 drag_image_->SetWidgetVisible(true);
208 if (source == ui::DragDropTypes::DRAG_EVENT_SOURCE_TOUCH) {
209 drag_image_->SetTouchDragOperationHintPosition(gfx::Point(
210 drag_image_offset_.x(),
211 drag_image_offset_.y() + drag_image_vertical_offset));
214 drag_window_ = NULL;
216 // Ends cancel animation if it's in progress.
217 if (cancel_animation_)
218 cancel_animation_->End();
220 if (should_block_during_drag_drop_) {
221 base::RunLoop run_loop;
222 quit_closure_ = run_loop.QuitClosure();
223 base::MessageLoopForUI* loop = base::MessageLoopForUI::current();
224 base::MessageLoop::ScopedNestableTaskAllower allow_nested(loop);
225 run_loop.Run();
228 if (drag_operation_ == 0) {
229 UMA_HISTOGRAM_ENUMERATION("Event.DragDrop.Cancel", source,
230 ui::DragDropTypes::DRAG_EVENT_SOURCE_COUNT);
231 } else {
232 UMA_HISTOGRAM_ENUMERATION("Event.DragDrop.Drop", source,
233 ui::DragDropTypes::DRAG_EVENT_SOURCE_COUNT);
236 if (!cancel_animation_.get() || !cancel_animation_->is_animating() ||
237 !pending_long_tap_.get()) {
238 // If drag cancel animation is running, this cleanup is done when the
239 // animation completes.
240 if (drag_source_window_)
241 drag_source_window_->RemoveObserver(this);
242 drag_source_window_ = NULL;
245 return drag_operation_;
248 void DragDropController::DragUpdate(aura::Window* target,
249 const ui::LocatedEvent& event) {
250 int op = ui::DragDropTypes::DRAG_NONE;
251 if (target != drag_window_) {
252 if (drag_window_) {
253 aura::client::DragDropDelegate* delegate =
254 aura::client::GetDragDropDelegate(drag_window_);
255 if (delegate)
256 delegate->OnDragExited();
257 if (drag_window_ != drag_source_window_)
258 drag_window_->RemoveObserver(this);
260 drag_window_ = target;
261 // We are already an observer of |drag_source_window_| so no need to add.
262 if (drag_window_ != drag_source_window_)
263 drag_window_->AddObserver(this);
264 aura::client::DragDropDelegate* delegate =
265 aura::client::GetDragDropDelegate(drag_window_);
266 if (delegate) {
267 ui::DropTargetEvent e(*drag_data_,
268 event.location(),
269 event.root_location(),
270 drag_operation_);
271 e.set_flags(event.flags());
272 delegate->OnDragEntered(e);
274 } else {
275 aura::client::DragDropDelegate* delegate =
276 aura::client::GetDragDropDelegate(drag_window_);
277 if (delegate) {
278 ui::DropTargetEvent e(*drag_data_,
279 event.location(),
280 event.root_location(),
281 drag_operation_);
282 e.set_flags(event.flags());
283 op = delegate->OnDragUpdated(e);
284 gfx::NativeCursor cursor = ui::kCursorNoDrop;
285 if (op & ui::DragDropTypes::DRAG_COPY)
286 cursor = ui::kCursorCopy;
287 else if (op & ui::DragDropTypes::DRAG_LINK)
288 cursor = ui::kCursorAlias;
289 else if (op & ui::DragDropTypes::DRAG_MOVE)
290 cursor = ui::kCursorGrabbing;
291 ash::Shell::GetInstance()->cursor_manager()->SetCursor(cursor);
295 DCHECK(drag_image_.get());
296 if (drag_image_->visible()) {
297 gfx::Point root_location_in_screen = event.root_location();
298 ::wm::ConvertPointToScreen(target->GetRootWindow(),
299 &root_location_in_screen);
300 drag_image_->SetScreenPosition(
301 root_location_in_screen - drag_image_offset_);
302 drag_image_->SetTouchDragOperation(op);
306 void DragDropController::Drop(aura::Window* target,
307 const ui::LocatedEvent& event) {
308 ash::Shell::GetInstance()->cursor_manager()->SetCursor(ui::kCursorPointer);
310 // We must guarantee that a target gets a OnDragEntered before Drop. WebKit
311 // depends on not getting a Drop without DragEnter. This behavior is
312 // consistent with drag/drop on other platforms.
313 if (target != drag_window_)
314 DragUpdate(target, event);
315 DCHECK(target == drag_window_);
317 aura::client::DragDropDelegate* delegate =
318 aura::client::GetDragDropDelegate(target);
319 if (delegate) {
320 ui::DropTargetEvent e(
321 *drag_data_, event.location(), event.root_location(), drag_operation_);
322 e.set_flags(event.flags());
323 drag_operation_ = delegate->OnPerformDrop(e);
324 if (drag_operation_ == 0)
325 StartCanceledAnimation(kCancelAnimationDuration);
326 else
327 drag_image_.reset();
328 } else {
329 drag_image_.reset();
332 Cleanup();
333 if (should_block_during_drag_drop_)
334 quit_closure_.Run();
337 void DragDropController::DragCancel() {
338 DoDragCancel(kCancelAnimationDuration);
341 bool DragDropController::IsDragDropInProgress() {
342 return !!drag_drop_tracker_.get();
345 void DragDropController::OnKeyEvent(ui::KeyEvent* event) {
346 if (IsDragDropInProgress() && event->key_code() == ui::VKEY_ESCAPE) {
347 DragCancel();
348 event->StopPropagation();
352 void DragDropController::OnMouseEvent(ui::MouseEvent* event) {
353 if (!IsDragDropInProgress())
354 return;
356 // If current drag session was not started by mouse, dont process this mouse
357 // event, but consume it so it does not interfere with current drag session.
358 if (current_drag_event_source_ !=
359 ui::DragDropTypes::DRAG_EVENT_SOURCE_MOUSE) {
360 event->StopPropagation();
361 return;
364 aura::Window* translated_target = drag_drop_tracker_->GetTarget(*event);
365 if (!translated_target) {
366 DragCancel();
367 event->StopPropagation();
368 return;
370 scoped_ptr<ui::LocatedEvent> translated_event(
371 drag_drop_tracker_->ConvertEvent(translated_target, *event));
372 switch (translated_event->type()) {
373 case ui::ET_MOUSE_DRAGGED:
374 DragUpdate(translated_target, *translated_event.get());
375 break;
376 case ui::ET_MOUSE_RELEASED:
377 Drop(translated_target, *translated_event.get());
378 break;
379 default:
380 // We could also reach here because RootWindow may sometimes generate a
381 // bunch of fake mouse events
382 // (aura::RootWindow::PostMouseMoveEventAfterWindowChange).
383 break;
385 event->StopPropagation();
388 void DragDropController::OnTouchEvent(ui::TouchEvent* event) {
389 if (!IsDragDropInProgress())
390 return;
392 // If current drag session was not started by touch, dont process this touch
393 // event, but consume it so it does not interfere with current drag session.
394 if (current_drag_event_source_ != ui::DragDropTypes::DRAG_EVENT_SOURCE_TOUCH)
395 event->StopPropagation();
397 if (event->handled())
398 return;
400 if (event->type() == ui::ET_TOUCH_CANCELLED)
401 DragCancel();
404 void DragDropController::OnGestureEvent(ui::GestureEvent* event) {
405 if (!IsDragDropInProgress())
406 return;
408 // No one else should handle gesture events when in drag drop. Note that it is
409 // not enough to just set ER_HANDLED because the dispatcher only stops
410 // dispatching when the event has ER_CONSUMED. If we just set ER_HANDLED, the
411 // event will still be dispatched to other handlers and we depend on
412 // individual handlers' kindness to not touch events marked ER_HANDLED (not
413 // all handlers are so kind and may cause bugs like crbug.com/236493).
414 event->StopPropagation();
416 // If current drag session was not started by touch, dont process this event.
417 if (current_drag_event_source_ != ui::DragDropTypes::DRAG_EVENT_SOURCE_TOUCH)
418 return;
420 // Apply kTouchDragImageVerticalOffset to the location.
421 ui::GestureEvent touch_offset_event(*event,
422 static_cast<aura::Window*>(NULL),
423 static_cast<aura::Window*>(NULL));
424 gfx::Point touch_offset_location = touch_offset_event.location();
425 gfx::Point touch_offset_root_location = touch_offset_event.root_location();
426 touch_offset_location.Offset(0, kTouchDragImageVerticalOffset);
427 touch_offset_root_location.Offset(0, kTouchDragImageVerticalOffset);
428 touch_offset_event.set_location(touch_offset_location);
429 touch_offset_event.set_root_location(touch_offset_root_location);
431 aura::Window* translated_target =
432 drag_drop_tracker_->GetTarget(touch_offset_event);
433 if (!translated_target) {
434 DragCancel();
435 event->SetHandled();
436 return;
438 scoped_ptr<ui::LocatedEvent> translated_event(
439 drag_drop_tracker_->ConvertEvent(translated_target, touch_offset_event));
441 switch (event->type()) {
442 case ui::ET_GESTURE_SCROLL_UPDATE:
443 DragUpdate(translated_target, *translated_event.get());
444 break;
445 case ui::ET_GESTURE_SCROLL_END:
446 case ui::ET_SCROLL_FLING_START:
447 Drop(translated_target, *translated_event.get());
448 break;
449 case ui::ET_GESTURE_LONG_TAP:
450 // Ideally we would want to just forward this long tap event to the
451 // |drag_source_window_|. However, webkit does not accept events while a
452 // drag drop is still in progress. The drag drop ends only when the nested
453 // message loop ends. Due to this stupidity, we have to defer forwarding
454 // the long tap.
455 pending_long_tap_.reset(
456 new ui::GestureEvent(*event,
457 static_cast<aura::Window*>(drag_drop_tracker_->capture_window()),
458 static_cast<aura::Window*>(drag_source_window_)));
459 DoDragCancel(kTouchCancelAnimationDuration);
460 break;
461 default:
462 break;
464 event->SetHandled();
467 void DragDropController::OnWindowDestroyed(aura::Window* window) {
468 if (drag_window_ == window)
469 drag_window_ = NULL;
470 if (drag_source_window_ == window)
471 drag_source_window_ = NULL;
474 ////////////////////////////////////////////////////////////////////////////////
475 // DragDropController, protected:
477 gfx::LinearAnimation* DragDropController::CreateCancelAnimation(
478 int duration,
479 int frame_rate,
480 gfx::AnimationDelegate* delegate) {
481 return new gfx::LinearAnimation(duration, frame_rate, delegate);
484 ////////////////////////////////////////////////////////////////////////////////
485 // DragDropController, private:
487 void DragDropController::AnimationEnded(const gfx::Animation* animation) {
488 cancel_animation_.reset();
490 // By the time we finish animation, another drag/drop session may have
491 // started. We do not want to destroy the drag image in that case.
492 if (!IsDragDropInProgress())
493 drag_image_.reset();
494 if (pending_long_tap_) {
495 // If not in a nested message loop, we can forward the long tap right now.
496 if (!should_block_during_drag_drop_) {
497 ForwardPendingLongTap();
498 } else {
499 // See comment about this in OnGestureEvent().
500 base::MessageLoopForUI::current()->PostTask(
501 FROM_HERE,
502 base::Bind(&DragDropController::ForwardPendingLongTap,
503 weak_factory_.GetWeakPtr()));
508 void DragDropController::DoDragCancel(int drag_cancel_animation_duration_ms) {
509 ash::Shell::GetInstance()->cursor_manager()->SetCursor(ui::kCursorPointer);
511 // |drag_window_| can be NULL if we have just started the drag and have not
512 // received any DragUpdates, or, if the |drag_window_| gets destroyed during
513 // a drag/drop.
514 aura::client::DragDropDelegate* delegate = drag_window_?
515 aura::client::GetDragDropDelegate(drag_window_) : NULL;
516 if (delegate)
517 delegate->OnDragExited();
519 Cleanup();
520 drag_operation_ = 0;
521 StartCanceledAnimation(drag_cancel_animation_duration_ms);
522 if (should_block_during_drag_drop_)
523 quit_closure_.Run();
526 void DragDropController::AnimationProgressed(const gfx::Animation* animation) {
527 gfx::Rect current_bounds = animation->CurrentValueBetween(
528 drag_image_initial_bounds_for_cancel_animation_,
529 drag_image_final_bounds_for_cancel_animation_);
530 drag_image_->SetBoundsInScreen(current_bounds);
533 void DragDropController::AnimationCanceled(const gfx::Animation* animation) {
534 AnimationEnded(animation);
537 void DragDropController::StartCanceledAnimation(int animation_duration_ms) {
538 DCHECK(drag_image_.get());
539 drag_image_->SetTouchDragOperationHintOff();
540 drag_image_initial_bounds_for_cancel_animation_ =
541 drag_image_->GetBoundsInScreen();
542 cancel_animation_.reset(CreateCancelAnimation(animation_duration_ms,
543 kCancelAnimationFrameRate,
544 this));
545 cancel_animation_->Start();
548 void DragDropController::ForwardPendingLongTap() {
549 if (drag_source_window_ && drag_source_window_->delegate()) {
550 drag_source_window_->delegate()->OnGestureEvent(pending_long_tap_.get());
551 DispatchGestureEndToWindow(drag_source_window_);
553 pending_long_tap_.reset();
554 if (drag_source_window_)
555 drag_source_window_->RemoveObserver(this);
556 drag_source_window_ = NULL;
559 void DragDropController::Cleanup() {
560 if (drag_window_)
561 drag_window_->RemoveObserver(this);
562 drag_window_ = NULL;
563 drag_data_ = NULL;
564 // Cleanup can be called again while deleting DragDropTracker, so delete
565 // the pointer with a local variable to avoid double free.
566 scoped_ptr<ash::DragDropTracker> holder = drag_drop_tracker_.Pass();
569 } // namespace ash