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 33 namespace ash { 34 namespace internal { 35 36 namespace { 37 // The duration of the drag cancel animation in millisecond. 38 const int kCancelAnimationDuration = 250; 39 const int kTouchCancelAnimationDuration = 20; 40 // The frame rate of the drag cancel animation in hertz. 41 const int kCancelAnimationFrameRate = 60; 42 43 // For touch initiated dragging, we scale and shift drag image by the following: 44 static const float kTouchDragImageScale = 1.2f; 45 static 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|. 50 gfx::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 66 void 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 81 class 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 138 DragDropController::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 150 DragDropController::~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 159 int 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 250 void 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 303 void 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 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 internal 566 } // namespace ash 567