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