1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 #include "ash/drag_drop/drag_drop_controller.h" 6 7 #include "ash/drag_drop/drag_drop_tracker.h" 8 #include "ash/drag_drop/drag_image_view.h" 9 #include "ash/shell.h" 10 #include "base/bind.h" 11 #include "base/message_loop/message_loop.h" 12 #include "base/run_loop.h" 13 #include "ui/aura/client/capture_client.h" 14 #include "ui/aura/env.h" 15 #include "ui/aura/window.h" 16 #include "ui/aura/window_delegate.h" 17 #include "ui/aura/window_event_dispatcher.h" 18 #include "ui/base/dragdrop/drag_drop_types.h" 19 #include "ui/base/dragdrop/os_exchange_data.h" 20 #include "ui/base/hit_test.h" 21 #include "ui/events/event.h" 22 #include "ui/events/event_utils.h" 23 #include "ui/gfx/animation/linear_animation.h" 24 #include "ui/gfx/path.h" 25 #include "ui/gfx/point.h" 26 #include "ui/gfx/rect.h" 27 #include "ui/gfx/rect_conversions.h" 28 #include "ui/views/views_delegate.h" 29 #include "ui/views/widget/native_widget_aura.h" 30 #include "ui/wm/core/coordinate_conversion.h" 31 #include "ui/wm/public/drag_drop_delegate.h" 32 33 namespace ash { 34 namespace { 35 36 // The duration of the drag cancel animation in millisecond. 37 const int kCancelAnimationDuration = 250; 38 const int kTouchCancelAnimationDuration = 20; 39 // The frame rate of the drag cancel animation in hertz. 40 const int kCancelAnimationFrameRate = 60; 41 42 // For touch initiated dragging, we scale and shift drag image by the following: 43 static const float kTouchDragImageScale = 1.2f; 44 static const int kTouchDragImageVerticalOffset = -25; 45 46 // Adjusts the drag image bounds such that the new bounds are scaled by |scale| 47 // and translated by the |drag_image_offset| and and additional 48 // |vertical_offset|. 49 gfx::Rect AdjustDragImageBoundsForScaleAndOffset( 50 const gfx::Rect& drag_image_bounds, 51 int vertical_offset, 52 float scale, 53 gfx::Vector2d* drag_image_offset) { 54 gfx::PointF final_origin = drag_image_bounds.origin(); 55 gfx::SizeF final_size = drag_image_bounds.size(); 56 final_size.Scale(scale); 57 drag_image_offset->set_x(drag_image_offset->x() * scale); 58 drag_image_offset->set_y(drag_image_offset->y() * scale); 59 float total_x_offset = drag_image_offset->x(); 60 float total_y_offset = drag_image_offset->y() - vertical_offset; 61 final_origin.Offset(-total_x_offset, -total_y_offset); 62 return gfx::ToEnclosingRect(gfx::RectF(final_origin, final_size)); 63 } 64 65 void DispatchGestureEndToWindow(aura::Window* window) { 66 if (window && window->delegate()) { 67 ui::GestureEvent gesture_end(0, 68 0, 69 0, 70 ui::EventTimeForNow(), 71 ui::GestureEventDetails(ui::ET_GESTURE_END)); 72 window->delegate()->OnGestureEvent(&gesture_end); 73 } 74 } 75 } // namespace 76 77 class DragDropTrackerDelegate : public aura::WindowDelegate { 78 public: 79 explicit DragDropTrackerDelegate(DragDropController* controller) 80 : drag_drop_controller_(controller) {} 81 virtual ~DragDropTrackerDelegate() {} 82 83 // Overridden from WindowDelegate: 84 virtual gfx::Size GetMinimumSize() const OVERRIDE { 85 return gfx::Size(); 86 } 87 88 virtual gfx::Size GetMaximumSize() const OVERRIDE { 89 return gfx::Size(); 90 } 91 92 virtual void OnBoundsChanged(const gfx::Rect& old_bounds, 93 const gfx::Rect& new_bounds) OVERRIDE {} 94 virtual gfx::NativeCursor GetCursor(const gfx::Point& point) OVERRIDE { 95 return gfx::kNullCursor; 96 } 97 virtual int GetNonClientComponent(const gfx::Point& point) const OVERRIDE { 98 return HTCAPTION; 99 } 100 virtual bool ShouldDescendIntoChildForEventHandling( 101 aura::Window* child, 102 const gfx::Point& location) OVERRIDE { 103 return true; 104 } 105 virtual bool CanFocus() OVERRIDE { return true; } 106 virtual void OnCaptureLost() OVERRIDE { 107 if (drag_drop_controller_->IsDragDropInProgress()) 108 drag_drop_controller_->DragCancel(); 109 } 110 virtual void OnPaint(gfx::Canvas* canvas) OVERRIDE { 111 } 112 virtual void OnDeviceScaleFactorChanged(float device_scale_factor) OVERRIDE {} 113 virtual void OnWindowDestroying(aura::Window* window) OVERRIDE {} 114 virtual void OnWindowDestroyed(aura::Window* window) OVERRIDE {} 115 virtual void OnWindowTargetVisibilityChanged(bool visible) OVERRIDE {} 116 virtual bool HasHitTestMask() const OVERRIDE { 117 return true; 118 } 119 virtual void GetHitTestMask(gfx::Path* mask) const OVERRIDE { 120 DCHECK(mask->isEmpty()); 121 } 122 123 private: 124 DragDropController* drag_drop_controller_; 125 126 DISALLOW_COPY_AND_ASSIGN(DragDropTrackerDelegate); 127 }; 128 129 //////////////////////////////////////////////////////////////////////////////// 130 // DragDropController, public: 131 132 DragDropController::DragDropController() 133 : drag_data_(NULL), 134 drag_operation_(0), 135 drag_window_(NULL), 136 drag_source_window_(NULL), 137 should_block_during_drag_drop_(true), 138 drag_drop_window_delegate_(new DragDropTrackerDelegate(this)), 139 current_drag_event_source_(ui::DragDropTypes::DRAG_EVENT_SOURCE_MOUSE), 140 weak_factory_(this) { 141 Shell::GetInstance()->PrependPreTargetHandler(this); 142 } 143 144 DragDropController::~DragDropController() { 145 Shell::GetInstance()->RemovePreTargetHandler(this); 146 Cleanup(); 147 if (cancel_animation_) 148 cancel_animation_->End(); 149 if (drag_image_) 150 drag_image_.reset(); 151 } 152 153 int DragDropController::StartDragAndDrop( 154 const ui::OSExchangeData& data, 155 aura::Window* root_window, 156 aura::Window* source_window, 157 const gfx::Point& root_location, 158 int operation, 159 ui::DragDropTypes::DragEventSource source) { 160 if (IsDragDropInProgress()) 161 return 0; 162 163 const ui::OSExchangeData::Provider* provider = &data.provider(); 164 // We do not support touch drag/drop without a drag image. 165 if (source == ui::DragDropTypes::DRAG_EVENT_SOURCE_TOUCH && 166 provider->GetDragImage().size().IsEmpty()) 167 return 0; 168 169 current_drag_event_source_ = source; 170 DragDropTracker* tracker = 171 new DragDropTracker(root_window, drag_drop_window_delegate_.get()); 172 if (source == ui::DragDropTypes::DRAG_EVENT_SOURCE_TOUCH) { 173 // We need to transfer the current gesture sequence and the GR's touch event 174 // queue to the |drag_drop_tracker_|'s capture window so that when it takes 175 // capture, it still gets a valid gesture state. 176 ui::GestureRecognizer::Get()->TransferEventsTo(source_window, 177 tracker->capture_window()); 178 // We also send a gesture end to the source window so it can clear state. 179 // TODO(varunjain): Remove this whole block when gesture sequence 180 // transferring is properly done in the GR (http://crbug.com/160558) 181 DispatchGestureEndToWindow(source_window); 182 } 183 tracker->TakeCapture(); 184 drag_drop_tracker_.reset(tracker); 185 drag_source_window_ = source_window; 186 if (drag_source_window_) 187 drag_source_window_->AddObserver(this); 188 pending_long_tap_.reset(); 189 190 drag_data_ = &data; 191 drag_operation_ = operation; 192 193 float drag_image_scale = 1; 194 int drag_image_vertical_offset = 0; 195 if (source == ui::DragDropTypes::DRAG_EVENT_SOURCE_TOUCH) { 196 drag_image_scale = kTouchDragImageScale; 197 drag_image_vertical_offset = kTouchDragImageVerticalOffset; 198 } 199 gfx::Point start_location = root_location; 200 ::wm::ConvertPointToScreen(root_window, &start_location); 201 drag_image_final_bounds_for_cancel_animation_ = gfx::Rect( 202 start_location - provider->GetDragImageOffset(), 203 provider->GetDragImage().size()); 204 drag_image_.reset(new DragImageView(source_window->GetRootWindow(), source)); 205 drag_image_->SetImage(provider->GetDragImage()); 206 drag_image_offset_ = provider->GetDragImageOffset(); 207 gfx::Rect drag_image_bounds(start_location, drag_image_->GetPreferredSize()); 208 drag_image_bounds = AdjustDragImageBoundsForScaleAndOffset(drag_image_bounds, 209 drag_image_vertical_offset, drag_image_scale, &drag_image_offset_); 210 drag_image_->SetBoundsInScreen(drag_image_bounds); 211 drag_image_->SetWidgetVisible(true); 212 if (source == ui::DragDropTypes::DRAG_EVENT_SOURCE_TOUCH) { 213 drag_image_->SetTouchDragOperationHintPosition(gfx::Point( 214 drag_image_offset_.x(), 215 drag_image_offset_.y() + drag_image_vertical_offset)); 216 } 217 218 drag_window_ = NULL; 219 220 // Ends cancel animation if it's in progress. 221 if (cancel_animation_) 222 cancel_animation_->End(); 223 224 if (should_block_during_drag_drop_) { 225 base::RunLoop run_loop; 226 quit_closure_ = run_loop.QuitClosure(); 227 base::MessageLoopForUI* loop = base::MessageLoopForUI::current(); 228 base::MessageLoop::ScopedNestableTaskAllower allow_nested(loop); 229 run_loop.Run(); 230 } 231 232 if (!cancel_animation_.get() || !cancel_animation_->is_animating() || 233 !pending_long_tap_.get()) { 234 // If drag cancel animation is running, this cleanup is done when the 235 // animation completes. 236 if (drag_source_window_) 237 drag_source_window_->RemoveObserver(this); 238 drag_source_window_ = NULL; 239 } 240 241 return drag_operation_; 242 } 243 244 void DragDropController::DragUpdate(aura::Window* target, 245 const ui::LocatedEvent& event) { 246 int op = ui::DragDropTypes::DRAG_NONE; 247 if (target != drag_window_) { 248 if (drag_window_) { 249 aura::client::DragDropDelegate* delegate = 250 aura::client::GetDragDropDelegate(drag_window_); 251 if (delegate) 252 delegate->OnDragExited(); 253 if (drag_window_ != drag_source_window_) 254 drag_window_->RemoveObserver(this); 255 } 256 drag_window_ = target; 257 // We are already an observer of |drag_source_window_| so no need to add. 258 if (drag_window_ != drag_source_window_) 259 drag_window_->AddObserver(this); 260 aura::client::DragDropDelegate* delegate = 261 aura::client::GetDragDropDelegate(drag_window_); 262 if (delegate) { 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 aura::client::DragDropDelegate* delegate = 272 aura::client::GetDragDropDelegate(drag_window_); 273 if (delegate) { 274 ui::DropTargetEvent e(*drag_data_, 275 event.location(), 276 event.root_location(), 277 drag_operation_); 278 e.set_flags(event.flags()); 279 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 ::wm::ConvertPointToScreen(target->GetRootWindow(), 295 &root_location_in_screen); 296 drag_image_->SetScreenPosition( 297 root_location_in_screen - drag_image_offset_); 298 drag_image_->SetTouchDragOperation(op); 299 } 300 } 301 302 void DragDropController::Drop(aura::Window* target, 303 const ui::LocatedEvent& event) { 304 ash::Shell::GetInstance()->cursor_manager()->SetCursor(ui::kCursorPointer); 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 aura::client::DragDropDelegate* delegate = 314 aura::client::GetDragDropDelegate(target); 315 if (delegate) { 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 333 void DragDropController::DragCancel() { 334 DoDragCancel(kCancelAnimationDuration); 335 } 336 337 bool DragDropController::IsDragDropInProgress() { 338 return !!drag_drop_tracker_.get(); 339 } 340 341 void DragDropController::OnKeyEvent(ui::KeyEvent* event) { 342 if (IsDragDropInProgress() && event->key_code() == ui::VKEY_ESCAPE) { 343 DragCancel(); 344 event->StopPropagation(); 345 } 346 } 347 348 void 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 384 void 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 400 void 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 463 void DragDropController::OnWindowDestroyed(aura::Window* window) { 464 if (drag_window_ == window) 465 drag_window_ = NULL; 466 if (drag_source_window_ == window) 467 drag_source_window_ = NULL; 468 } 469 470 //////////////////////////////////////////////////////////////////////////////// 471 // DragDropController, protected: 472 473 gfx::LinearAnimation* DragDropController::CreateCancelAnimation( 474 int duration, 475 int frame_rate, 476 gfx::AnimationDelegate* delegate) { 477 return new gfx::LinearAnimation(duration, frame_rate, delegate); 478 } 479 480 //////////////////////////////////////////////////////////////////////////////// 481 // DragDropController, private: 482 483 void DragDropController::AnimationEnded(const gfx::Animation* animation) { 484 cancel_animation_.reset(); 485 486 // By the time we finish animation, another drag/drop session may have 487 // started. We do not want to destroy the drag image in that case. 488 if (!IsDragDropInProgress()) 489 drag_image_.reset(); 490 if (pending_long_tap_) { 491 // If not in a nested message loop, we can forward the long tap right now. 492 if (!should_block_during_drag_drop_) 493 ForwardPendingLongTap(); 494 else { 495 // See comment about this in OnGestureEvent(). 496 base::MessageLoopForUI::current()->PostTask( 497 FROM_HERE, 498 base::Bind(&DragDropController::ForwardPendingLongTap, 499 weak_factory_.GetWeakPtr())); 500 } 501 } 502 } 503 504 void DragDropController::DoDragCancel(int drag_cancel_animation_duration_ms) { 505 ash::Shell::GetInstance()->cursor_manager()->SetCursor(ui::kCursorPointer); 506 507 // |drag_window_| can be NULL if we have just started the drag and have not 508 // received any DragUpdates, or, if the |drag_window_| gets destroyed during 509 // a drag/drop. 510 aura::client::DragDropDelegate* delegate = drag_window_? 511 aura::client::GetDragDropDelegate(drag_window_) : NULL; 512 if (delegate) 513 delegate->OnDragExited(); 514 515 Cleanup(); 516 drag_operation_ = 0; 517 StartCanceledAnimation(drag_cancel_animation_duration_ms); 518 if (should_block_during_drag_drop_) 519 quit_closure_.Run(); 520 } 521 522 void DragDropController::AnimationProgressed(const gfx::Animation* animation) { 523 gfx::Rect current_bounds = animation->CurrentValueBetween( 524 drag_image_initial_bounds_for_cancel_animation_, 525 drag_image_final_bounds_for_cancel_animation_); 526 drag_image_->SetBoundsInScreen(current_bounds); 527 } 528 529 void DragDropController::AnimationCanceled(const gfx::Animation* animation) { 530 AnimationEnded(animation); 531 } 532 533 void DragDropController::StartCanceledAnimation(int animation_duration_ms) { 534 DCHECK(drag_image_.get()); 535 drag_image_->SetTouchDragOperationHintOff(); 536 drag_image_initial_bounds_for_cancel_animation_ = 537 drag_image_->GetBoundsInScreen(); 538 cancel_animation_.reset(CreateCancelAnimation(animation_duration_ms, 539 kCancelAnimationFrameRate, 540 this)); 541 cancel_animation_->Start(); 542 } 543 544 void DragDropController::ForwardPendingLongTap() { 545 if (drag_source_window_ && drag_source_window_->delegate()) { 546 drag_source_window_->delegate()->OnGestureEvent(pending_long_tap_.get()); 547 DispatchGestureEndToWindow(drag_source_window_); 548 } 549 pending_long_tap_.reset(); 550 if (drag_source_window_) 551 drag_source_window_->RemoveObserver(this); 552 drag_source_window_ = NULL; 553 } 554 555 void DragDropController::Cleanup() { 556 if (drag_window_) 557 drag_window_->RemoveObserver(this); 558 drag_window_ = NULL; 559 drag_data_ = NULL; 560 // Cleanup can be called again while deleting DragDropTracker, so use Pass 561 // instead of reset to avoid double free. 562 drag_drop_tracker_.Pass(); 563 } 564 565 } // namespace ash 566