1// Copyright 2016 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#import "ui/views/cocoa/drag_drop_client_mac.h"
6
7#include "base/mac/mac_util.h"
8#include "base/run_loop.h"
9#include "base/strings/sys_string_conversions.h"
10#import "components/remote_cocoa/app_shim/bridged_content_view.h"
11#import "components/remote_cocoa/app_shim/native_widget_ns_window_bridge.h"
12#import "ui/base/dragdrop/os_exchange_data_provider_mac.h"
13#include "ui/gfx/image/image_skia_util_mac.h"
14#include "ui/views/drag_utils.h"
15#include "ui/views/widget/native_widget_mac.h"
16
17namespace views {
18
19DragDropClientMac::DragDropClientMac(
20    remote_cocoa::NativeWidgetNSWindowBridge* bridge,
21    View* root_view)
22    : drop_helper_(root_view), bridge_(bridge) {
23  DCHECK(bridge);
24}
25
26DragDropClientMac::~DragDropClientMac() {}
27
28void DragDropClientMac::StartDragAndDrop(
29    View* view,
30    std::unique_ptr<ui::OSExchangeData> data,
31    int operation,
32    ui::DragDropTypes::DragEventSource source) {
33  exchange_data_ = std::move(data);
34  source_operation_ = operation;
35  is_drag_source_ = true;
36
37  const ui::OSExchangeDataProviderMac& provider_mac =
38      static_cast<const ui::OSExchangeDataProviderMac&>(
39          exchange_data_->provider());
40
41  // Release capture before beginning the dragging session. Capture may have
42  // been acquired on the mouseDown, but capture is not required during the
43  // dragging session and the mouseUp that would release it will be suppressed.
44  bridge_->ReleaseCapture();
45
46  // Synthesize an event for dragging, since we can't be sure that
47  // [NSApp currentEvent] will return a valid dragging event.
48  NSWindow* window = bridge_->ns_window();
49  NSPoint position = [window mouseLocationOutsideOfEventStream];
50  NSTimeInterval event_time = [[NSApp currentEvent] timestamp];
51  NSEvent* event = [NSEvent mouseEventWithType:NSLeftMouseDragged
52                                      location:position
53                                 modifierFlags:NSLeftMouseDraggedMask
54                                     timestamp:event_time
55                                  windowNumber:[window windowNumber]
56                                       context:nil
57                                   eventNumber:0
58                                    clickCount:1
59                                      pressure:1.0];
60
61  NSImage* image = gfx::NSImageFromImageSkiaWithColorSpace(
62      provider_mac.GetDragImage(), base::mac::GetSRGBColorSpace());
63
64  DCHECK(!NSEqualSizes([image size], NSZeroSize));
65  NSDraggingItem* drag_item = provider_mac.GetDraggingItem();
66
67  // Subtract the image's height from the y location so that the mouse will be
68  // at the upper left corner of the image.
69  NSRect dragging_frame =
70      NSMakeRect([event locationInWindow].x,
71                 [event locationInWindow].y - [image size].height,
72                 [image size].width, [image size].height);
73  [drag_item setDraggingFrame:dragging_frame contents:image];
74
75  [bridge_->ns_view() beginDraggingSessionWithItems:@[ drag_item ]
76                                              event:event
77                                             source:bridge_->ns_view()];
78
79  // Since Drag and drop is asynchronous on Mac, we need to spin a nested run
80  // loop for consistency with other platforms.
81  base::RunLoop run_loop(base::RunLoop::Type::kNestableTasksAllowed);
82  quit_closure_ = run_loop.QuitClosure();
83  run_loop.Run();
84}
85
86NSDragOperation DragDropClientMac::DragUpdate(id<NSDraggingInfo> sender) {
87  if (!exchange_data_) {
88    exchange_data_ = std::make_unique<OSExchangeData>(
89        ui::OSExchangeDataProviderMac::CreateProviderWrappingPasteboard(
90            [sender draggingPasteboard]));
91    source_operation_ = ui::DragDropTypes::NSDragOperationToDragOperation(
92        [sender draggingSourceOperationMask]);
93  }
94
95  last_operation_ = drop_helper_.OnDragOver(
96      *exchange_data_, LocationInView([sender draggingLocation]),
97      source_operation_);
98  return ui::DragDropTypes::DragOperationToNSDragOperation(last_operation_);
99}
100
101NSDragOperation DragDropClientMac::Drop(id<NSDraggingInfo> sender) {
102  // OnDrop may delete |this|, so clear |exchange_data_| first.
103  std::unique_ptr<ui::OSExchangeData> exchange_data = std::move(exchange_data_);
104
105  int drag_operation = drop_helper_.OnDrop(
106      *exchange_data, LocationInView([sender draggingLocation]),
107      last_operation_);
108  return ui::DragDropTypes::DragOperationToNSDragOperation(drag_operation);
109}
110
111void DragDropClientMac::EndDrag() {
112  exchange_data_.reset();
113  is_drag_source_ = false;
114
115  // Allow a test to invoke EndDrag() without spinning the nested run loop.
116  if (!quit_closure_.is_null())
117    std::move(quit_closure_).Run();
118}
119
120void DragDropClientMac::DragExit() {
121  drop_helper_.OnDragExit();
122  if (!is_drag_source_)
123    exchange_data_.reset();
124}
125
126gfx::Point DragDropClientMac::LocationInView(NSPoint point) const {
127  NSRect content_rect = [bridge_->ns_window()
128      contentRectForFrameRect:[bridge_->ns_window() frame]];
129  return gfx::Point(point.x, NSHeight(content_rect) - point.y);
130}
131
132}  // namespace views
133