Chromium Code Reviews| 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..cbd650cc598866bdc264fd291b7f4df8b7f8f23a |
| --- /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; |
| +}; |
| + |
| +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) { |
| + source_window_->AddObserver(this); |
| +} |
| + |
| +DragController::~DragController() { |
| + // Only remove observer one time. |
| + std::set<ServerWindow*> observed_windows; |
| + if (source_window_) |
| + observed_windows.insert(source_window_); |
| + if (current_target_window_) |
| + observed_windows.insert(current_target_window_); |
| + for (auto& w : window_operations_) |
|
sky
2016/09/13 18:15:31
optional: 'w' is mildly misleading as I would tend
|
| + observed_windows.insert(w.first); |
| + |
| + for (ServerWindow* w : observed_windows) |
| + w->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 && current_target == current_target_window_ && |
| + event.type() != ET_POINTER_UP) { |
| + // We're continuing a drag inside the bounds of |
| + // |current_target_window_|. Send a continue message. |
| + auto& window_ops = window_operations_[current_target]; |
| + if (window_ops.size() > 1 && |
| + window_ops.back().type == OperationType::OVER) { |
| + // If we have a queued DragOver which isn't in progress, then just |
| + // modify it's data. |
| + auto& back = window_ops.back(); |
| + back.event_flags = event_flags; |
| + back.screen_position = screen_position; |
| + } else { |
| + QueueOperation(current_target, OperationType::OVER, event_flags, |
| + screen_position); |
| + } |
| + } else if (current_target != current_target_window_) { |
| + if (current_target_window_) { |
| + auto& window_ops = window_operations_[current_target_window_]; |
| + if (window_ops.size() > 1 && |
| + window_ops.back().type == OperationType::OVER) { |
| + // If we have queued DragOver events, we want to replace them. |
| + auto& back = window_ops.back(); |
| + back.type = OperationType::LEAVE; |
| + back.event_flags = event_flags; |
| + back.screen_position = screen_position; |
| + } else { |
| + QueueOperation(current_target_window_, OperationType::LEAVE, |
| + event_flags, screen_position); |
| + } |
| + } |
| + |
| + if (current_target) |
| + 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->PerformOnDragFinish(); |
| + called_on_drag_mime_types_.clear(); |
| + |
| + source_->OnDragCompleted(success); |
| + // |this| may be deleted now. |
| +} |
| + |
| +size_t DragController::GetSizeOfQueueForWindow(ServerWindow* window) { |
| + auto it = window_operations_.find(window); |
| + if (it == window_operations_.end()) |
| + return 0u; |
| + return it->second.size(); |
| +} |
| + |
| +void DragController::SetCurrentTargetWindow(ServerWindow* current_target) { |
| + // Only add or remove observers when the window isn't in |window_operations_|. |
| + if (current_target_window_ && current_target_window_ != source_window_ && |
| + !base::ContainsKey(window_operations_, current_target_window_)) { |
| + current_target_window_->RemoveObserver(this); |
| + } |
| + |
| + current_target_window_ = current_target; |
| + |
| + if (current_target_window_ && current_target_window_ != source_window_ && |
| + !base::ContainsKey(window_operations_, current_target_window_)) { |
| + current_target_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_->GetDragTargetWithRoot(window); |
| + if (connection != source_connection_ && |
| + !base::ContainsKey(called_on_drag_mime_types_, connection)) { |
| + connection->PerformOnDragMimeTypes(mime_data_.Clone()); |
| + called_on_drag_mime_types_.insert(connection); |
| + } |
| + |
| + auto& window_ops = window_operations_[window]; |
| + if (window_ops.empty() && window != source_window_ && |
| + window != current_target_window_) { |
| + window->AddObserver(this); |
| + } |
| + window_ops.push_back({type, event_flags, screen_position}); |
| + if (window_ops.size() == 1) { |
| + // The first item in the queue is the item we're waiting for a response |
| + // on. If we're the only item, send it. |
| + DispatchFrontOfWindowQueue(window, &window_ops); |
| + } |
| +} |
| + |
| +void DragController::RemoveQueueFront(ServerWindow* window) { |
| + auto& window_ops = window_operations_[window]; |
| + if (window_ops.size() == 1 && window != source_window_ && |
| + window != current_target_window_) { |
| + // This is the last event in queue; stop watching the queue. |
|
sky
2016/09/13 18:15:31
If you do this, and the window is destroyed OnWind
Elliot Glaysher
2016/09/14 22:19:16
I'm confused.
If we're here, we're removing the l
sky
2016/09/14 22:52:02
Isn't the reference still held in the map? e.g. wi
Elliot Glaysher
2016/09/15 17:12:40
This (and related) chunks have been removed. This
|
| + window->RemoveObserver(this); |
| + } |
| + |
| + DCHECK(!window_ops.empty()); |
| + window_ops.pop_front(); |
| + |
| + if (!window_ops.empty()) |
| + DispatchFrontOfWindowQueue(window, &window_ops); |
| +} |
| + |
| +void DragController::DispatchFrontOfWindowQueue(ServerWindow* target, |
| + std::deque<Operation>* queue) { |
| + DragTargetConnection* connection = source_->GetDragTargetWithRoot(target); |
| + |
| + DCHECK(!queue->empty()); |
| + const Operation& op = queue->front(); |
| + switch (op.type) { |
| + case OperationType::ENTER: { |
| + connection->PerformOnDragEnter( |
| + target, op.event_flags, op.screen_position, drag_operations_, |
| + base::Bind(&DragController::OnDragStatusCompleted, |
| + weak_factory_.GetWeakPtr(), target->id())); |
| + break; |
| + } |
| + case OperationType::OVER: { |
| + connection->PerformOnDragOver( |
| + target, op.event_flags, op.screen_position, drag_operations_, |
| + base::Bind(&DragController::OnDragStatusCompleted, |
| + weak_factory_.GetWeakPtr(), target->id())); |
| + break; |
| + } |
| + case OperationType::LEAVE: { |
| + connection->PerformOnDragLeave(target); |
| + RemoveQueueFront(target); |
| + break; |
| + } |
| + case OperationType::DROP: { |
| + connection->PerformOnCompleteDrop( |
| + target, op.event_flags, op.screen_position, drag_operations_, |
| + base::Bind(&DragController::OnDragDropCompleted, |
| + weak_factory_.GetWeakPtr(), target->id())); |
| + break; |
| + } |
| + } |
| +} |
| + |
| +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. |
| + RemoveQueueFront(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; |
| + } |
| + |
| + // TODO(erg): What happens if there are events behind the drag drop!? |
| + RemoveQueueFront(window); |
| + MessageDragCompleted(bitmask != 0u); |
| +} |
| + |
| +void DragController::OnWindowDestroying(ServerWindow* window) { |
| + if (current_target_window_ == window) |
| + SetCurrentTargetWindow(nullptr); |
| + |
| + window_operations_.erase(window); |
| + |
| + if (source_window_ == window) { |
| + source_window_->RemoveObserver(this); |
| + source_window_ = nullptr; |
| + // Our source window is being deleted, fail the drag. |
| + MessageDragCompleted(false); |
| + } |
| +} |
| + |
| +} // namespace ws |
| +} // namespace ui |