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