blob: acecc8133666121fb24c5052de6f3ee362468fa8 [file] [log] [blame]
// 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_drop_tracker.h"
#include "ash/drag_drop/drag_image_view.h"
#include "ash/shell.h"
#include "ash/wm/coordinate_conversion.h"
#include "ash/wm/cursor_manager.h"
#include "base/message_loop.h"
#include "base/run_loop.h"
#include "ui/aura/client/capture_client.h"
#include "ui/aura/client/drag_drop_delegate.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/base/events/event.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),
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,
aura::RootWindow* root_window,
const gfx::Point& root_location,
int operation) {
DCHECK(!IsDragDropInProgress());
drag_drop_tracker_.reset(new DragDropTracker(root_window));
drag_data_ = &data;
drag_operation_ = operation;
const ui::OSExchangeDataProviderAura& provider =
static_cast<const ui::OSExchangeDataProviderAura&>(data.provider());
drag_image_.reset(new DragImageView);
drag_image_->SetImage(provider.drag_image());
drag_image_offset_ = provider.drag_image_offset();
drag_image_->SetBoundsInScreen(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_) {
base::RunLoop run_loop(aura::Env::GetInstance()->GetDispatcher());
quit_closure_ = run_loop.QuitClosure();
MessageLoopForUI* loop = MessageLoopForUI::current();
MessageLoop::ScopedNestableTaskAllower allow_nested(loop);
run_loop.Run();
}
#endif // !defined(OS_MACOSX)
return drag_operation_;
}
void DragDropController::DragUpdate(aura::Window* target,
const ui::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_))) {
ui::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_))) {
ui::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;
ash::Shell::GetInstance()->cursor_manager()->SetCursor(cursor);
}
}
DCHECK(drag_image_.get());
if (drag_image_->visible()) {
gfx::Point root_location_in_screen = event.root_location();
ash::wm::ConvertPointToScreen(target->GetRootWindow(),
&root_location_in_screen);
drag_image_->SetScreenPosition(
root_location_in_screen.Subtract(drag_image_offset_));
}
}
void DragDropController::Drop(aura::Window* target,
const ui::LocatedEvent& event) {
ash::Shell::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))) {
ui::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_)
quit_closure_.Run();
}
void DragDropController::DragCancel() {
ash::Shell::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_)
quit_closure_.Run();
}
bool DragDropController::IsDragDropInProgress() {
return !!drag_drop_tracker_.get();
}
bool DragDropController::PreHandleKeyEvent(aura::Window* target,
ui::KeyEvent* event) {
if (IsDragDropInProgress() && event->key_code() == ui::VKEY_ESCAPE) {
DragCancel();
return true;
}
return false;
}
bool DragDropController::PreHandleMouseEvent(aura::Window* target,
ui::MouseEvent* event) {
if (!IsDragDropInProgress())
return false;
aura::Window* translated_target = drag_drop_tracker_->GetTarget(*event);
if (!translated_target) {
DragCancel();
return true;
}
scoped_ptr<ui::MouseEvent> translated_event(
drag_drop_tracker_->ConvertMouseEvent(translated_target, *event));
switch (translated_event->type()) {
case ui::ET_MOUSE_DRAGGED:
DragUpdate(translated_target, *translated_event.get());
break;
case ui::ET_MOUSE_RELEASED:
Drop(translated_target, *translated_event.get());
break;
default:
// 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,
ui::TouchEvent* event) {
// TODO(sad): Also check for the touch-id.
// TODO(varunjain): Add code for supporting drag-and-drop across displays
// (http://crbug.com/114755).
if (!IsDragDropInProgress())
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::EventResult DragDropController::PreHandleGestureEvent(
aura::Window* target,
ui::GestureEvent* event) {
return ui::ER_UNHANDLED;
}
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());
// By the time we finish animation, another drag/drop session may have
// started. We do not want to destroy the drag image in that case.
if (!IsDragDropInProgress())
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;
// Cleanup can be called again while deleting DragDropTracker, so use Pass
// instead of reset to avoid double free.
drag_drop_tracker_.Pass();
}
} // namespace internal
} // namespace ash