drag_drop_controller.cc revision 1e9bf3e0803691d0a228da41fc608347b6db4340
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
33namespace ash {
34namespace internal {
35
36namespace {
37// The duration of the drag cancel animation in millisecond.
38const int kCancelAnimationDuration = 250;
39const int kTouchCancelAnimationDuration = 20;
40// The frame rate of the drag cancel animation in hertz.
41const int kCancelAnimationFrameRate = 60;
42
43// For touch initiated dragging, we scale and shift drag image by the following:
44static const float kTouchDragImageScale = 1.2f;
45static 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|.
50gfx::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
66void 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
81class 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
138DragDropController::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
150DragDropController::~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
159int 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
250void 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
303void 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
333void DragDropController::DragCancel() {
334  DoDragCancel(kCancelAnimationDuration);
335}
336
337bool DragDropController::IsDragDropInProgress() {
338  return !!drag_drop_tracker_.get();
339}
340
341void DragDropController::OnKeyEvent(ui::KeyEvent* event) {
342  if (IsDragDropInProgress() && event->key_code() == ui::VKEY_ESCAPE) {
343    DragCancel();
344    event->StopPropagation();
345  }
346}
347
348void 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
384void 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
400void 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
463void 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
477gfx::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
487void 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
508void 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
526void 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
533void DragDropController::AnimationCanceled(const gfx::Animation* animation) {
534  AnimationEnded(animation);
535}
536
537void 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
548void 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
559void 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
571