Update To 11.40.268.0
[platform/framework/web/crosswalk.git] / src / ash / drag_drop / drag_drop_controller.cc
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.
4
5 #include "ash/drag_drop/drag_drop_controller.h"
6
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/run_loop.h"
13 #include "ui/aura/client/capture_client.h"
14 #include "ui/aura/env.h"
15 #include "ui/aura/window.h"
16 #include "ui/aura/window_delegate.h"
17 #include "ui/aura/window_event_dispatcher.h"
18 #include "ui/base/dragdrop/drag_drop_types.h"
19 #include "ui/base/dragdrop/os_exchange_data.h"
20 #include "ui/base/hit_test.h"
21 #include "ui/events/event.h"
22 #include "ui/events/event_utils.h"
23 #include "ui/gfx/animation/linear_animation.h"
24 #include "ui/gfx/path.h"
25 #include "ui/gfx/point.h"
26 #include "ui/gfx/rect.h"
27 #include "ui/gfx/rect_conversions.h"
28 #include "ui/views/views_delegate.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"
32
33 namespace ash {
34 namespace {
35
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;
41
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;
45
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));
63 }
64
65 void DispatchGestureEndToWindow(aura::Window* window) {
66   if (window && window->delegate()) {
67     ui::GestureEvent gesture_end(0,
68                                  0,
69                                  0,
70                                  ui::EventTimeForNow(),
71                                  ui::GestureEventDetails(ui::ET_GESTURE_END));
72     window->delegate()->OnGestureEvent(&gesture_end);
73   }
74 }
75 }  // namespace
76
77 class DragDropTrackerDelegate : public aura::WindowDelegate {
78  public:
79   explicit DragDropTrackerDelegate(DragDropController* controller)
80       : drag_drop_controller_(controller) {}
81   ~DragDropTrackerDelegate() override {}
82
83   // Overridden from WindowDelegate:
84   gfx::Size GetMinimumSize() const override { return gfx::Size(); }
85
86   gfx::Size GetMaximumSize() const override { return gfx::Size(); }
87
88   void OnBoundsChanged(const gfx::Rect& old_bounds,
89                        const gfx::Rect& new_bounds) override {}
90   gfx::NativeCursor GetCursor(const gfx::Point& point) override {
91     return gfx::kNullCursor;
92   }
93   int GetNonClientComponent(const gfx::Point& point) const override {
94     return HTCAPTION;
95   }
96   bool ShouldDescendIntoChildForEventHandling(
97       aura::Window* child,
98       const gfx::Point& location) override {
99     return true;
100   }
101   bool CanFocus() override { return true; }
102   void OnCaptureLost() override {
103     if (drag_drop_controller_->IsDragDropInProgress())
104       drag_drop_controller_->DragCancel();
105   }
106   void OnPaint(gfx::Canvas* canvas) override {}
107   void OnDeviceScaleFactorChanged(float device_scale_factor) override {}
108   void OnWindowDestroying(aura::Window* window) override {}
109   void OnWindowDestroyed(aura::Window* window) override {}
110   void OnWindowTargetVisibilityChanged(bool visible) override {}
111   bool HasHitTestMask() const override { return true; }
112   void GetHitTestMask(gfx::Path* mask) const override {
113     DCHECK(mask->isEmpty());
114   }
115
116  private:
117   DragDropController* drag_drop_controller_;
118
119   DISALLOW_COPY_AND_ASSIGN(DragDropTrackerDelegate);
120 };
121
122 ////////////////////////////////////////////////////////////////////////////////
123 // DragDropController, public:
124
125 DragDropController::DragDropController()
126     : drag_data_(NULL),
127       drag_operation_(0),
128       drag_window_(NULL),
129       drag_source_window_(NULL),
130       should_block_during_drag_drop_(true),
131       drag_drop_window_delegate_(new DragDropTrackerDelegate(this)),
132       current_drag_event_source_(ui::DragDropTypes::DRAG_EVENT_SOURCE_MOUSE),
133       weak_factory_(this) {
134   Shell::GetInstance()->PrependPreTargetHandler(this);
135 }
136
137 DragDropController::~DragDropController() {
138   Shell::GetInstance()->RemovePreTargetHandler(this);
139   Cleanup();
140   if (cancel_animation_)
141     cancel_animation_->End();
142   if (drag_image_)
143     drag_image_.reset();
144 }
145
146 int DragDropController::StartDragAndDrop(
147     const ui::OSExchangeData& data,
148     aura::Window* root_window,
149     aura::Window* source_window,
150     const gfx::Point& root_location,
151     int operation,
152     ui::DragDropTypes::DragEventSource source) {
153   if (IsDragDropInProgress())
154     return 0;
155
156   const ui::OSExchangeData::Provider* provider = &data.provider();
157   // We do not support touch drag/drop without a drag image.
158   if (source == ui::DragDropTypes::DRAG_EVENT_SOURCE_TOUCH &&
159       provider->GetDragImage().size().IsEmpty())
160     return 0;
161
162   current_drag_event_source_ = source;
163   DragDropTracker* tracker =
164       new DragDropTracker(root_window, drag_drop_window_delegate_.get());
165   if (source == ui::DragDropTypes::DRAG_EVENT_SOURCE_TOUCH) {
166     // We need to transfer the current gesture sequence and the GR's touch event
167     // queue to the |drag_drop_tracker_|'s capture window so that when it takes
168     // capture, it still gets a valid gesture state.
169     ui::GestureRecognizer::Get()->TransferEventsTo(source_window,
170         tracker->capture_window());
171     // We also send a gesture end to the source window so it can clear state.
172     // TODO(varunjain): Remove this whole block when gesture sequence
173     // transferring is properly done in the GR (http://crbug.com/160558)
174     DispatchGestureEndToWindow(source_window);
175   }
176   tracker->TakeCapture();
177   drag_drop_tracker_.reset(tracker);
178   drag_source_window_ = source_window;
179   if (drag_source_window_)
180     drag_source_window_->AddObserver(this);
181   pending_long_tap_.reset();
182
183   drag_data_ = &data;
184   drag_operation_ = operation;
185
186   float drag_image_scale = 1;
187   int drag_image_vertical_offset = 0;
188   if (source == ui::DragDropTypes::DRAG_EVENT_SOURCE_TOUCH) {
189     drag_image_scale = kTouchDragImageScale;
190     drag_image_vertical_offset = kTouchDragImageVerticalOffset;
191   }
192   gfx::Point start_location = root_location;
193   ::wm::ConvertPointToScreen(root_window, &start_location);
194   drag_image_final_bounds_for_cancel_animation_ = gfx::Rect(
195       start_location - provider->GetDragImageOffset(),
196       provider->GetDragImage().size());
197   drag_image_.reset(new DragImageView(source_window->GetRootWindow(), source));
198   drag_image_->SetImage(provider->GetDragImage());
199   drag_image_offset_ = provider->GetDragImageOffset();
200   gfx::Rect drag_image_bounds(start_location, drag_image_->GetPreferredSize());
201   drag_image_bounds = AdjustDragImageBoundsForScaleAndOffset(drag_image_bounds,
202       drag_image_vertical_offset, drag_image_scale, &drag_image_offset_);
203   drag_image_->SetBoundsInScreen(drag_image_bounds);
204   drag_image_->SetWidgetVisible(true);
205   if (source == ui::DragDropTypes::DRAG_EVENT_SOURCE_TOUCH) {
206     drag_image_->SetTouchDragOperationHintPosition(gfx::Point(
207         drag_image_offset_.x(),
208         drag_image_offset_.y() + drag_image_vertical_offset));
209   }
210
211   drag_window_ = NULL;
212
213   // Ends cancel animation if it's in progress.
214   if (cancel_animation_)
215     cancel_animation_->End();
216
217   if (should_block_during_drag_drop_) {
218     base::RunLoop run_loop;
219     quit_closure_ = run_loop.QuitClosure();
220     base::MessageLoopForUI* loop = base::MessageLoopForUI::current();
221     base::MessageLoop::ScopedNestableTaskAllower allow_nested(loop);
222     run_loop.Run();
223   }
224
225   if (!cancel_animation_.get() || !cancel_animation_->is_animating() ||
226       !pending_long_tap_.get()) {
227     // If drag cancel animation is running, this cleanup is done when the
228     // animation completes.
229     if (drag_source_window_)
230       drag_source_window_->RemoveObserver(this);
231     drag_source_window_ = NULL;
232   }
233
234   return drag_operation_;
235 }
236
237 void DragDropController::DragUpdate(aura::Window* target,
238                                     const ui::LocatedEvent& event) {
239   int op = ui::DragDropTypes::DRAG_NONE;
240   if (target != drag_window_) {
241     if (drag_window_) {
242       aura::client::DragDropDelegate* delegate =
243           aura::client::GetDragDropDelegate(drag_window_);
244       if (delegate)
245         delegate->OnDragExited();
246       if (drag_window_ != drag_source_window_)
247         drag_window_->RemoveObserver(this);
248     }
249     drag_window_ = target;
250     // We are already an observer of |drag_source_window_| so no need to add.
251     if (drag_window_ != drag_source_window_)
252       drag_window_->AddObserver(this);
253     aura::client::DragDropDelegate* delegate =
254         aura::client::GetDragDropDelegate(drag_window_);
255     if (delegate) {
256       ui::DropTargetEvent e(*drag_data_,
257                             event.location(),
258                             event.root_location(),
259                             drag_operation_);
260       e.set_flags(event.flags());
261       delegate->OnDragEntered(e);
262     }
263   } else {
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       op = delegate->OnDragUpdated(e);
273       gfx::NativeCursor cursor = ui::kCursorNoDrop;
274       if (op & ui::DragDropTypes::DRAG_COPY)
275         cursor = ui::kCursorCopy;
276       else if (op & ui::DragDropTypes::DRAG_LINK)
277         cursor = ui::kCursorAlias;
278       else if (op & ui::DragDropTypes::DRAG_MOVE)
279         cursor = ui::kCursorGrabbing;
280       ash::Shell::GetInstance()->cursor_manager()->SetCursor(cursor);
281     }
282   }
283
284   DCHECK(drag_image_.get());
285   if (drag_image_->visible()) {
286     gfx::Point root_location_in_screen = event.root_location();
287     ::wm::ConvertPointToScreen(target->GetRootWindow(),
288                                &root_location_in_screen);
289     drag_image_->SetScreenPosition(
290         root_location_in_screen - drag_image_offset_);
291     drag_image_->SetTouchDragOperation(op);
292   }
293 }
294
295 void DragDropController::Drop(aura::Window* target,
296                               const ui::LocatedEvent& event) {
297   ash::Shell::GetInstance()->cursor_manager()->SetCursor(ui::kCursorPointer);
298
299   // We must guarantee that a target gets a OnDragEntered before Drop. WebKit
300   // depends on not getting a Drop without DragEnter. This behavior is
301   // consistent with drag/drop on other platforms.
302   if (target != drag_window_)
303     DragUpdate(target, event);
304   DCHECK(target == drag_window_);
305
306   aura::client::DragDropDelegate* delegate =
307       aura::client::GetDragDropDelegate(target);
308   if (delegate) {
309     ui::DropTargetEvent e(
310         *drag_data_, event.location(), event.root_location(), drag_operation_);
311     e.set_flags(event.flags());
312     drag_operation_ = delegate->OnPerformDrop(e);
313     if (drag_operation_ == 0)
314       StartCanceledAnimation(kCancelAnimationDuration);
315     else
316       drag_image_.reset();
317   } else {
318     drag_image_.reset();
319   }
320
321   Cleanup();
322   if (should_block_during_drag_drop_)
323     quit_closure_.Run();
324 }
325
326 void DragDropController::DragCancel() {
327   DoDragCancel(kCancelAnimationDuration);
328 }
329
330 bool DragDropController::IsDragDropInProgress() {
331   return !!drag_drop_tracker_.get();
332 }
333
334 void DragDropController::OnKeyEvent(ui::KeyEvent* event) {
335   if (IsDragDropInProgress() && event->key_code() == ui::VKEY_ESCAPE) {
336     DragCancel();
337     event->StopPropagation();
338   }
339 }
340
341 void DragDropController::OnMouseEvent(ui::MouseEvent* event) {
342   if (!IsDragDropInProgress())
343     return;
344
345   // If current drag session was not started by mouse, dont process this mouse
346   // event, but consume it so it does not interfere with current drag session.
347   if (current_drag_event_source_ !=
348       ui::DragDropTypes::DRAG_EVENT_SOURCE_MOUSE) {
349     event->StopPropagation();
350     return;
351   }
352
353   aura::Window* translated_target = drag_drop_tracker_->GetTarget(*event);
354   if (!translated_target) {
355     DragCancel();
356     event->StopPropagation();
357     return;
358   }
359   scoped_ptr<ui::LocatedEvent> translated_event(
360       drag_drop_tracker_->ConvertEvent(translated_target, *event));
361   switch (translated_event->type()) {
362     case ui::ET_MOUSE_DRAGGED:
363       DragUpdate(translated_target, *translated_event.get());
364       break;
365     case ui::ET_MOUSE_RELEASED:
366       Drop(translated_target, *translated_event.get());
367       break;
368     default:
369       // We could also reach here because RootWindow may sometimes generate a
370       // bunch of fake mouse events
371       // (aura::RootWindow::PostMouseMoveEventAfterWindowChange).
372       break;
373   }
374   event->StopPropagation();
375 }
376
377 void DragDropController::OnTouchEvent(ui::TouchEvent* event) {
378   if (!IsDragDropInProgress())
379     return;
380
381   // If current drag session was not started by touch, dont process this touch
382   // event, but consume it so it does not interfere with current drag session.
383   if (current_drag_event_source_ != ui::DragDropTypes::DRAG_EVENT_SOURCE_TOUCH)
384     event->StopPropagation();
385
386   if (event->handled())
387     return;
388
389   if (event->type() == ui::ET_TOUCH_CANCELLED)
390     DragCancel();
391 }
392
393 void DragDropController::OnGestureEvent(ui::GestureEvent* event) {
394   if (!IsDragDropInProgress())
395     return;
396
397   // No one else should handle gesture events when in drag drop. Note that it is
398   // not enough to just set ER_HANDLED because the dispatcher only stops
399   // dispatching when the event has ER_CONSUMED. If we just set ER_HANDLED, the
400   // event will still be dispatched to other handlers and we depend on
401   // individual handlers' kindness to not touch events marked ER_HANDLED (not
402   // all handlers are so kind and may cause bugs like crbug.com/236493).
403   event->StopPropagation();
404
405   // If current drag session was not started by touch, dont process this event.
406   if (current_drag_event_source_ != ui::DragDropTypes::DRAG_EVENT_SOURCE_TOUCH)
407     return;
408
409   // Apply kTouchDragImageVerticalOffset to the location.
410   ui::GestureEvent touch_offset_event(*event,
411                                       static_cast<aura::Window*>(NULL),
412                                       static_cast<aura::Window*>(NULL));
413   gfx::Point touch_offset_location = touch_offset_event.location();
414   gfx::Point touch_offset_root_location = touch_offset_event.root_location();
415   touch_offset_location.Offset(0, kTouchDragImageVerticalOffset);
416   touch_offset_root_location.Offset(0, kTouchDragImageVerticalOffset);
417   touch_offset_event.set_location(touch_offset_location);
418   touch_offset_event.set_root_location(touch_offset_root_location);
419
420   aura::Window* translated_target =
421       drag_drop_tracker_->GetTarget(touch_offset_event);
422   if (!translated_target) {
423     DragCancel();
424     event->SetHandled();
425     return;
426   }
427   scoped_ptr<ui::LocatedEvent> translated_event(
428       drag_drop_tracker_->ConvertEvent(translated_target, touch_offset_event));
429
430   switch (event->type()) {
431     case ui::ET_GESTURE_SCROLL_UPDATE:
432       DragUpdate(translated_target, *translated_event.get());
433       break;
434     case ui::ET_GESTURE_SCROLL_END:
435       Drop(translated_target, *translated_event.get());
436       break;
437     case ui::ET_SCROLL_FLING_START:
438     case ui::ET_GESTURE_LONG_TAP:
439       // Ideally we would want to just forward this long tap event to the
440       // |drag_source_window_|. However, webkit does not accept events while a
441       // drag drop is still in progress. The drag drop ends only when the nested
442       // message loop ends. Due to this stupidity, we have to defer forwarding
443       // the long tap.
444       pending_long_tap_.reset(
445           new ui::GestureEvent(*event,
446               static_cast<aura::Window*>(drag_drop_tracker_->capture_window()),
447               static_cast<aura::Window*>(drag_source_window_)));
448       DoDragCancel(kTouchCancelAnimationDuration);
449       break;
450     default:
451       break;
452   }
453   event->SetHandled();
454 }
455
456 void DragDropController::OnWindowDestroyed(aura::Window* window) {
457   if (drag_window_ == window)
458     drag_window_ = NULL;
459   if (drag_source_window_ == window)
460     drag_source_window_ = NULL;
461 }
462
463 ////////////////////////////////////////////////////////////////////////////////
464 // DragDropController, protected:
465
466 gfx::LinearAnimation* DragDropController::CreateCancelAnimation(
467     int duration,
468     int frame_rate,
469     gfx::AnimationDelegate* delegate) {
470   return new gfx::LinearAnimation(duration, frame_rate, delegate);
471 }
472
473 ////////////////////////////////////////////////////////////////////////////////
474 // DragDropController, private:
475
476 void DragDropController::AnimationEnded(const gfx::Animation* animation) {
477   cancel_animation_.reset();
478
479   // By the time we finish animation, another drag/drop session may have
480   // started. We do not want to destroy the drag image in that case.
481   if (!IsDragDropInProgress())
482     drag_image_.reset();
483   if (pending_long_tap_) {
484     // If not in a nested message loop, we can forward the long tap right now.
485     if (!should_block_during_drag_drop_)
486       ForwardPendingLongTap();
487     else {
488       // See comment about this in OnGestureEvent().
489       base::MessageLoopForUI::current()->PostTask(
490           FROM_HERE,
491           base::Bind(&DragDropController::ForwardPendingLongTap,
492                      weak_factory_.GetWeakPtr()));
493     }
494   }
495 }
496
497 void DragDropController::DoDragCancel(int drag_cancel_animation_duration_ms) {
498   ash::Shell::GetInstance()->cursor_manager()->SetCursor(ui::kCursorPointer);
499
500   // |drag_window_| can be NULL if we have just started the drag and have not
501   // received any DragUpdates, or, if the |drag_window_| gets destroyed during
502   // a drag/drop.
503   aura::client::DragDropDelegate* delegate = drag_window_?
504       aura::client::GetDragDropDelegate(drag_window_) : NULL;
505   if (delegate)
506     delegate->OnDragExited();
507
508   Cleanup();
509   drag_operation_ = 0;
510   StartCanceledAnimation(drag_cancel_animation_duration_ms);
511   if (should_block_during_drag_drop_)
512     quit_closure_.Run();
513 }
514
515 void DragDropController::AnimationProgressed(const gfx::Animation* animation) {
516   gfx::Rect current_bounds = animation->CurrentValueBetween(
517       drag_image_initial_bounds_for_cancel_animation_,
518       drag_image_final_bounds_for_cancel_animation_);
519   drag_image_->SetBoundsInScreen(current_bounds);
520 }
521
522 void DragDropController::AnimationCanceled(const gfx::Animation* animation) {
523   AnimationEnded(animation);
524 }
525
526 void DragDropController::StartCanceledAnimation(int animation_duration_ms) {
527   DCHECK(drag_image_.get());
528   drag_image_->SetTouchDragOperationHintOff();
529   drag_image_initial_bounds_for_cancel_animation_ =
530       drag_image_->GetBoundsInScreen();
531   cancel_animation_.reset(CreateCancelAnimation(animation_duration_ms,
532                                                 kCancelAnimationFrameRate,
533                                                 this));
534   cancel_animation_->Start();
535 }
536
537 void DragDropController::ForwardPendingLongTap() {
538   if (drag_source_window_ && drag_source_window_->delegate()) {
539     drag_source_window_->delegate()->OnGestureEvent(pending_long_tap_.get());
540     DispatchGestureEndToWindow(drag_source_window_);
541   }
542   pending_long_tap_.reset();
543   if (drag_source_window_)
544     drag_source_window_->RemoveObserver(this);
545   drag_source_window_ = NULL;
546 }
547
548 void DragDropController::Cleanup() {
549   if (drag_window_)
550     drag_window_->RemoveObserver(this);
551   drag_window_ = NULL;
552   drag_data_ = NULL;
553   // Cleanup can be called again while deleting DragDropTracker, so delete
554   // the pointer with a local variable to avoid double free.
555   scoped_ptr<ash::DragDropTracker> holder = drag_drop_tracker_.Pass();
556 }
557
558 }  // namespace ash