blob: 2e05d26e10b427f970ce7e78b9bf64db78dafd31 [file] [log] [blame]
// Copyright 2016 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 "services/ui/ws/drag_controller.h"
#include <utility>
#include "base/logging.h"
#include "services/ui/public/interfaces/cursor.mojom.h"
#include "services/ui/ws/drag_cursor_updater.h"
#include "services/ui/ws/drag_source.h"
#include "services/ui/ws/drag_target_connection.h"
#include "services/ui/ws/event_dispatcher.h"
#include "services/ui/ws/server_window.h"
namespace ui {
namespace ws {
struct DragController::Operation {
OperationType type;
uint32_t event_flags;
gfx::Point screen_position;
};
struct DragController::WindowState {
// Set to true once we've observed the ServerWindow* that is the key to this
// instance in |window_state_|.
bool observed = false;
// If we're waiting for a response, this is the type of message. NONE means
// there's no outstanding
OperationType waiting_on_reply = OperationType::NONE;
// The operation that we'll send off if |waiting_on_reply| isn't NONE.
Operation queued_operation = {OperationType::NONE, 0, gfx::Point()};
// The current set of operations that this window accepts. This gets updated
// on each return message.
DropEffectBitmask bitmask = 0u;
};
DragController::DragController(
DragCursorUpdater* cursor_updater,
DragSource* source,
ServerWindow* source_window,
DragTargetConnection* source_connection,
int32_t drag_pointer,
const std::unordered_map<std::string, std::vector<uint8_t>>& mime_data,
DropEffectBitmask drag_operations)
: source_(source),
cursor_updater_(cursor_updater),
drag_operations_(drag_operations),
drag_pointer_id_(drag_pointer),
current_cursor_(ui::mojom::Cursor::NO_DROP),
source_window_(source_window),
source_connection_(source_connection),
mime_data_(mime_data),
weak_factory_(this) {
SetCurrentTargetWindow(nullptr);
EnsureWindowObserved(source_window_);
}
DragController::~DragController() {
for (auto& pair : window_state_) {
if (pair.second.observed)
pair.first->RemoveObserver(this);
}
}
void DragController::Cancel() {
MessageDragCompleted(false, ui::mojom::kDropEffectNone);
// |this| may be deleted now.
}
bool DragController::DispatchPointerEvent(const ui::PointerEvent& event,
ServerWindow* current_target) {
uint32_t event_flags =
event.flags() &
(ui::EF_SHIFT_DOWN | ui::EF_CONTROL_DOWN | ui::EF_ALT_DOWN);
gfx::Point screen_position = event.location();
if (waiting_for_final_drop_response_) {
// If we're waiting on a target window to respond to the final drag drop
// call, don't process any more pointer events.
return false;
}
if (event.pointer_id() != drag_pointer_id_)
return false;
// If |current_target| doesn't accept drags, walk its hierarchy up until we
// find one that does (or set to nullptr at the top of the tree).
while (current_target && !current_target->can_accept_drops())
current_target = current_target->parent();
if (current_target) {
// If we're non-null, we're about to use |current_target| in some
// way. Ensure that we receive notifications that this window has gone
// away.
EnsureWindowObserved(current_target);
}
if (current_target && current_target == current_target_window_ &&
event.type() != ET_POINTER_UP) {
QueueOperation(current_target, OperationType::OVER, event_flags,
screen_position);
} else if (current_target != current_target_window_) {
if (current_target_window_) {
QueueOperation(current_target_window_, OperationType::LEAVE, event_flags,
screen_position);
}
if (current_target) {
// TODO(erg): If we have a queued LEAVE operation, does this turn into a
// noop?
QueueOperation(current_target, OperationType::ENTER, event_flags,
screen_position);
}
SetCurrentTargetWindow(current_target);
}
if (event.type() == ET_POINTER_UP) {
if (current_target) {
QueueOperation(current_target, OperationType::DROP, event_flags,
screen_position);
waiting_for_final_drop_response_ = true;
} else {
// The pointer was released over no window or a window that doesn't
// accept drags.
MessageDragCompleted(false, ui::mojom::kDropEffectNone);
}
}
return true;
}
void DragController::OnWillDestroyDragTargetConnection(
DragTargetConnection* connection) {
called_on_drag_mime_types_.erase(connection);
}
void DragController::MessageDragCompleted(bool success,
DropEffect action_taken) {
for (DragTargetConnection* connection : called_on_drag_mime_types_)
connection->PerformOnDragDropDone();
called_on_drag_mime_types_.clear();
source_->OnDragCompleted(success, action_taken);
// |this| may be deleted now.
}
size_t DragController::GetSizeOfQueueForWindow(ServerWindow* window) {
auto it = window_state_.find(window);
if (it == window_state_.end())
return 0u;
if (it->second.waiting_on_reply == OperationType::NONE)
return 0u;
if (it->second.queued_operation.type == OperationType::NONE)
return 1u;
return 2u;
}
void DragController::SetWindowDropOperations(ServerWindow* window,
DropEffectBitmask bitmask) {
WindowState& state = window_state_[window];
state.bitmask = bitmask;
if (current_target_window_ == window) {
current_cursor_ = CursorForEffectBitmask(bitmask);
cursor_updater_->OnDragCursorUpdated();
}
}
ui::mojom::Cursor DragController::CursorForEffectBitmask(
DropEffectBitmask bitmask) {
DropEffectBitmask combined = bitmask & drag_operations_;
return combined == ui::mojom::kDropEffectNone
? ui::mojom::Cursor::NO_DROP
: ui::mojom::Cursor::COPY;
}
void DragController::SetCurrentTargetWindow(ServerWindow* current_target) {
current_target_window_ = current_target;
if (current_target_window_) {
// Immediately set the cursor to the last known set of operations (which
// could be none).
WindowState& state = window_state_[current_target_window_];
current_cursor_ = CursorForEffectBitmask(state.bitmask);
} else {
// Can't drop in empty areas.
current_cursor_ = ui::mojom::Cursor::NO_DROP;
}
cursor_updater_->OnDragCursorUpdated();
}
void DragController::EnsureWindowObserved(ServerWindow* window) {
if (!window)
return;
WindowState& state = window_state_[window];
if (!state.observed) {
state.observed = true;
window->AddObserver(this);
}
}
void DragController::QueueOperation(ServerWindow* window,
OperationType type,
uint32_t event_flags,
const gfx::Point& screen_position) {
// If this window doesn't have the mime data, send it.
DragTargetConnection* connection = source_->GetDragTargetForWindow(window);
if (connection != source_connection_ &&
!base::ContainsKey(called_on_drag_mime_types_, connection)) {
connection->PerformOnDragDropStart(mime_data_);
called_on_drag_mime_types_.insert(connection);
}
WindowState& state = window_state_[window];
// Set the queued operation to the incoming.
state.queued_operation = {type, event_flags, screen_position};
if (state.waiting_on_reply == OperationType::NONE) {
// Send the operation immediately.
DispatchOperation(window, &state);
}
}
void DragController::DispatchOperation(ServerWindow* target,
WindowState* state) {
DragTargetConnection* connection = source_->GetDragTargetForWindow(target);
DCHECK_EQ(OperationType::NONE, state->waiting_on_reply);
Operation& op = state->queued_operation;
switch (op.type) {
case OperationType::NONE: {
// NONE case to silence the compiler.
NOTREACHED();
break;
}
case OperationType::ENTER: {
connection->PerformOnDragEnter(
target, op.event_flags, op.screen_position, drag_operations_,
base::Bind(&DragController::OnDragStatusCompleted,
weak_factory_.GetWeakPtr(), target->id()));
state->waiting_on_reply = OperationType::ENTER;
break;
}
case OperationType::OVER: {
connection->PerformOnDragOver(
target, op.event_flags, op.screen_position, drag_operations_,
base::Bind(&DragController::OnDragStatusCompleted,
weak_factory_.GetWeakPtr(), target->id()));
state->waiting_on_reply = OperationType::OVER;
break;
}
case OperationType::LEAVE: {
connection->PerformOnDragLeave(target);
state->waiting_on_reply = OperationType::NONE;
break;
}
case OperationType::DROP: {
connection->PerformOnCompleteDrop(
target, op.event_flags, op.screen_position, drag_operations_,
base::Bind(&DragController::OnDragDropCompleted,
weak_factory_.GetWeakPtr(), target->id()));
state->waiting_on_reply = OperationType::DROP;
break;
}
}
state->queued_operation = {OperationType::NONE, 0, gfx::Point()};
}
void DragController::OnRespondToOperation(ServerWindow* window) {
WindowState& state = window_state_[window];
DCHECK_NE(OperationType::NONE, state.waiting_on_reply);
state.waiting_on_reply = OperationType::NONE;
if (state.queued_operation.type != OperationType::NONE)
DispatchOperation(window, &state);
}
void DragController::OnDragStatusCompleted(const WindowId& id,
DropEffectBitmask bitmask) {
ServerWindow* window = source_->GetWindowById(id);
if (!window) {
// The window has been deleted and its queue is empty.
return;
}
// We must remove the completed item.
OnRespondToOperation(window);
SetWindowDropOperations(window, bitmask);
}
void DragController::OnDragDropCompleted(const WindowId& id,
DropEffect action) {
ServerWindow* window = source_->GetWindowById(id);
if (!window) {
// The window has been deleted after we sent the drop message. It's really
// hard to recover from this so just signal to the source that our drag
// failed.
MessageDragCompleted(false, ui::mojom::kDropEffectNone);
return;
}
OnRespondToOperation(window);
MessageDragCompleted(action != 0u, action);
}
void DragController::OnWindowDestroying(ServerWindow* window) {
auto it = window_state_.find(window);
if (it != window_state_.end()) {
window->RemoveObserver(this);
window_state_.erase(it);
}
if (current_target_window_ == window)
SetCurrentTargetWindow(nullptr);
if (source_window_ == window) {
source_window_ = nullptr;
// Our source window is being deleted, fail the drag.
MessageDragCompleted(false, ui::mojom::kDropEffectNone);
}
}
} // namespace ws
} // namespace ui