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