// Copyright (c) 2012 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "ash/drag_drop/drag_drop_controller.h" #include "ash/drag_drop/drag_image_view.h" #include "ash/shell.h" #include "base/message_loop.h" #include "ui/aura/client/capture_client.h" #include "ui/aura/client/drag_drop_delegate.h" #include "ui/aura/cursor_manager.h" #include "ui/aura/env.h" #include "ui/aura/root_window.h" #include "ui/aura/window.h" #include "ui/base/dragdrop/drag_drop_types.h" #include "ui/base/dragdrop/os_exchange_data_provider_aura.h" #include "ui/compositor/layer.h" #include "ui/compositor/layer_animator.h" #include "ui/compositor/scoped_layer_animation_settings.h" #include "ui/gfx/point.h" #include "ui/gfx/rect.h" #include "ui/views/views_delegate.h" #include "ui/views/widget/native_widget_aura.h" namespace ash { namespace internal { using aura::RootWindow; namespace { const base::TimeDelta kDragDropAnimationDuration = base::TimeDelta::FromMilliseconds(250); } // namespace //////////////////////////////////////////////////////////////////////////////// // DragDropController, public: DragDropController::DragDropController() : drag_image_(NULL), drag_data_(NULL), drag_operation_(0), drag_window_(NULL), drag_drop_in_progress_(false), should_block_during_drag_drop_(true) { Shell::GetInstance()->AddEnvEventFilter(this); } DragDropController::~DragDropController() { Shell::GetInstance()->RemoveEnvEventFilter(this); Cleanup(); if (drag_image_.get()) drag_image_.reset(); } int DragDropController::StartDragAndDrop(const ui::OSExchangeData& data, const gfx::Point& root_location, int operation) { DCHECK(!drag_drop_in_progress_); // TODO(oshima): Add CaptureClient client API. aura::Window* capture_window = aura::client::GetCaptureWindow(Shell::GetPrimaryRootWindow()); if (capture_window) capture_window->ReleaseCapture(); drag_drop_in_progress_ = true; drag_data_ = &data; drag_operation_ = operation; const ui::OSExchangeDataProviderAura& provider = static_cast(data.provider()); drag_image_.reset(new DragImageView); drag_image_->SetImage(provider.drag_image()); drag_image_offset_ = provider.drag_image_offset(); drag_image_->SetScreenBounds(gfx::Rect( root_location.Subtract(drag_image_offset_), drag_image_->GetPreferredSize())); drag_image_->SetWidgetVisible(true); drag_window_ = NULL; drag_start_location_ = root_location.Subtract(drag_image_offset_); #if !defined(OS_MACOSX) if (should_block_during_drag_drop_) { MessageLoopForUI* loop = MessageLoopForUI::current(); MessageLoop::ScopedNestableTaskAllower allow_nested(loop); loop->RunWithDispatcher(aura::Env::GetInstance()->GetDispatcher()); } #endif // !defined(OS_MACOSX) return drag_operation_; } void DragDropController::DragUpdate(aura::Window* target, const aura::LocatedEvent& event) { aura::client::DragDropDelegate* delegate = NULL; if (target != drag_window_) { if (drag_window_) { if ((delegate = aura::client::GetDragDropDelegate(drag_window_))) delegate->OnDragExited(); drag_window_->RemoveObserver(this); } drag_window_ = target; drag_window_->AddObserver(this); if ((delegate = aura::client::GetDragDropDelegate(drag_window_))) { aura::DropTargetEvent e(*drag_data_, event.location(), event.root_location(), drag_operation_); e.set_flags(event.flags()); delegate->OnDragEntered(e); } } else { if ((delegate = aura::client::GetDragDropDelegate(drag_window_))) { aura::DropTargetEvent e(*drag_data_, event.location(), event.root_location(), drag_operation_); e.set_flags(event.flags()); int op = delegate->OnDragUpdated(e); gfx::NativeCursor cursor = ui::kCursorNoDrop; if (op & ui::DragDropTypes::DRAG_COPY) cursor = ui::kCursorCopy; else if (op & ui::DragDropTypes::DRAG_LINK) cursor = ui::kCursorAlias; else if (op & ui::DragDropTypes::DRAG_MOVE) cursor = ui::kCursorMove; aura::Env::GetInstance()->cursor_manager()->SetCursor(cursor); } } DCHECK(drag_image_.get()); if (drag_image_->visible()) { drag_image_->SetScreenPosition( event.root_location().Subtract(drag_image_offset_)); } } void DragDropController::Drop(aura::Window* target, const aura::LocatedEvent& event) { aura::Env::GetInstance()->cursor_manager()->SetCursor(ui::kCursorPointer); aura::client::DragDropDelegate* delegate = NULL; // We must guarantee that a target gets a OnDragEntered before Drop. WebKit // depends on not getting a Drop without DragEnter. This behavior is // consistent with drag/drop on other platforms. if (target != drag_window_) DragUpdate(target, event); DCHECK(target == drag_window_); if ((delegate = aura::client::GetDragDropDelegate(target))) { aura::DropTargetEvent e( *drag_data_, event.location(), event.root_location(), drag_operation_); e.set_flags(event.flags()); drag_operation_ = delegate->OnPerformDrop(e); if (drag_operation_ == 0) StartCanceledAnimation(); else drag_image_.reset(); } else { drag_image_.reset(); } Cleanup(); if (should_block_during_drag_drop_) MessageLoop::current()->QuitNow(); } void DragDropController::DragCancel() { aura::Env::GetInstance()->cursor_manager()->SetCursor(ui::kCursorPointer); // |drag_window_| can be NULL if we have just started the drag and have not // received any DragUpdates, or, if the |drag_window_| gets destroyed during // a drag/drop. aura::client::DragDropDelegate* delegate = drag_window_? aura::client::GetDragDropDelegate(drag_window_) : NULL; if (delegate) delegate->OnDragExited(); Cleanup(); drag_operation_ = 0; StartCanceledAnimation(); if (should_block_during_drag_drop_) MessageLoop::current()->QuitNow(); } bool DragDropController::IsDragDropInProgress() { return drag_drop_in_progress_; } bool DragDropController::PreHandleKeyEvent(aura::Window* target, aura::KeyEvent* event) { return false; } bool DragDropController::PreHandleMouseEvent(aura::Window* target, aura::MouseEvent* event) { if (!drag_drop_in_progress_) return false; switch (event->type()) { case ui::ET_MOUSE_DRAGGED: DragUpdate(target, *event); break; case ui::ET_MOUSE_RELEASED: Drop(target, *event); break; case ui::ET_MOUSE_EXITED: DragCancel(); break; default: // We could reach here if the user drops outside the root window. // We could also reach here because RootWindow may sometimes generate a // bunch of fake mouse events // (aura::RootWindow::PostMouseMoveEventAfterWindowChange). break; } return true; } ui::TouchStatus DragDropController::PreHandleTouchEvent( aura::Window* target, aura::TouchEvent* event) { // TODO(sad): Also check for the touch-id. if (!drag_drop_in_progress_) return ui::TOUCH_STATUS_UNKNOWN; switch (event->type()) { case ui::ET_TOUCH_MOVED: DragUpdate(target, *event); break; case ui::ET_TOUCH_RELEASED: Drop(target, *event); break; case ui::ET_TOUCH_CANCELLED: DragCancel(); break; default: return ui::TOUCH_STATUS_UNKNOWN; } return ui::TOUCH_STATUS_CONTINUE; } ui::GestureStatus DragDropController::PreHandleGestureEvent( aura::Window* target, aura::GestureEvent* event) { return ui::GESTURE_STATUS_UNKNOWN; } void DragDropController::OnWindowDestroyed(aura::Window* window) { if (drag_window_ == window) { drag_window_->RemoveObserver(this); drag_window_ = NULL; } } //////////////////////////////////////////////////////////////////////////////// // DragDropController, private: void DragDropController::OnImplicitAnimationsCompleted() { DCHECK(drag_image_.get()); drag_image_.reset(); } void DragDropController::StartCanceledAnimation() { aura::Window* window = drag_image_->GetWidget()->GetNativeView(); ui::LayerAnimator* animator = window->layer()->GetAnimator(); animator->set_preemption_strategy( ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET); // Stop waiting for any as yet unfinished implicit animations. StopObservingImplicitAnimations(); ui::ScopedLayerAnimationSettings animation_setter(animator); animation_setter.SetTransitionDuration(kDragDropAnimationDuration); animation_setter.AddObserver(this); window->SetBounds(gfx::Rect(drag_start_location_, window->bounds().size())); } void DragDropController::Cleanup() { if (drag_window_) drag_window_->RemoveObserver(this); drag_window_ = NULL; drag_data_ = NULL; drag_drop_in_progress_ = false; } } // namespace internal } // namespace ash