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