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