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