| Index: services/ui/ws/drag_controller.cc
|
| diff --git a/services/ui/ws/drag_controller.cc b/services/ui/ws/drag_controller.cc
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..c1b6c1ee9b7e4b82ad69645d7413cbccb9767ae9
|
| --- /dev/null
|
| +++ b/services/ui/ws/drag_controller.cc
|
| @@ -0,0 +1,290 @@
|
| +// 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 "base/logging.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;
|
| +
|
| + // If we're waiting for a response, this is the type of message. TYPE_NONE
|
| + // means there's no outstanding
|
| + OperationType waiting_on_reply;
|
| +
|
| + // The operation that we'll send off if |waiting_on_reply| isn't TYPE_NONE.
|
| + Operation queued_operation;
|
| +};
|
| +
|
| +DragController::DragController(
|
| + DragSource* source,
|
| + ServerWindow* source_window,
|
| + DragTargetConnection* source_connection,
|
| + int32_t drag_pointer,
|
| + mojo::Map<mojo::String, mojo::Array<uint8_t>> mime_data,
|
| + uint32_t drag_operations)
|
| + : source_(source),
|
| + drag_operations_(drag_operations),
|
| + drag_pointer_id_(drag_pointer),
|
| + source_window_(source_window),
|
| + source_connection_(source_connection),
|
| + mime_data_(std::move(mime_data)),
|
| + weak_factory_(this) {
|
| + EnsureWindowObserved(source_window_);
|
| +}
|
| +
|
| +DragController::~DragController() {
|
| + for (auto& pair : window_state_) {
|
| + if (pair.second.observed)
|
| + pair.first->RemoveObserver(this);
|
| + }
|
| +}
|
| +
|
| +void DragController::Cancel() {
|
| + MessageDragCompleted(false);
|
| + // |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);
|
| + }
|
| + }
|
| +
|
| + return true;
|
| +}
|
| +
|
| +void DragController::OnWillDestroyDragTargetConnection(
|
| + DragTargetConnection* connection) {
|
| + called_on_drag_mime_types_.erase(connection);
|
| +}
|
| +
|
| +void DragController::MessageDragCompleted(bool success) {
|
| + for (DragTargetConnection* connection : called_on_drag_mime_types_)
|
| + connection->PerformOnDragDropDone();
|
| + called_on_drag_mime_types_.clear();
|
| +
|
| + source_->OnDragCompleted(success);
|
| + // |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::SetCurrentTargetWindow(ServerWindow* current_target) {
|
| + current_target_window_ = current_target;
|
| +}
|
| +
|
| +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,
|
| + 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_.Clone());
|
| + 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,
|
| + uint32_t 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);
|
| +
|
| + // TODO(erg): |bitmask| is the allowed drag actions at the mouse location. We
|
| + // should use this data to change the cursor.
|
| +}
|
| +
|
| +void DragController::OnDragDropCompleted(const WindowId& id, uint32_t bitmask) {
|
| + 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);
|
| + return;
|
| + }
|
| +
|
| + OnRespondToOperation(window);
|
| + MessageDragCompleted(bitmask != 0u);
|
| +}
|
| +
|
| +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)
|
| + current_target_window_ = nullptr;
|
| +
|
| + if (source_window_ == window) {
|
| + source_window_ = nullptr;
|
| + // Our source window is being deleted, fail the drag.
|
| + MessageDragCompleted(false);
|
| + }
|
| +}
|
| +
|
| +} // namespace ws
|
| +} // namespace ui
|
|
|