blob: b22753402dc00c4579143ef133d6d90109ca667c [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.
#import "ui/views/cocoa/drag_drop_client_mac.h"
#include "base/mac/mac_util.h"
#include "base/run_loop.h"
#include "base/strings/sys_string_conversions.h"
#import "ui/base/dragdrop/os_exchange_data_provider_mac.h"
#include "ui/gfx/image/image_skia_util_mac.h"
#include "ui/views/drag_utils.h"
#include "ui/views/widget/native_widget_mac.h"
#import "ui/views_bridge_mac/bridged_content_view.h"
#import "ui/views_bridge_mac/bridged_native_widget_impl.h"
@interface CocoaDragDropDataProvider ()
- (instancetype)initWithData:(const ui::OSExchangeData&)data;
- (instancetype)initWithPasteboard:(NSPasteboard*)pasteboard;
@end
@implementation CocoaDragDropDataProvider {
std::unique_ptr<ui::OSExchangeData> data_;
}
- (instancetype)initWithData:(const ui::OSExchangeData&)data {
if ((self = [super init])) {
data_.reset(new OSExchangeData(
std::unique_ptr<OSExchangeData::Provider>(data.provider().Clone())));
}
return self;
}
- (instancetype)initWithPasteboard:(NSPasteboard*)pasteboard {
if ((self = [super init])) {
data_ = ui::OSExchangeDataProviderMac::CreateDataFromPasteboard(pasteboard);
}
return self;
}
- (ui::OSExchangeData*)data {
return data_.get();
}
// NSPasteboardItemDataProvider protocol implementation.
- (void)pasteboard:(NSPasteboard*)sender
item:(NSPasteboardItem*)item
provideDataForType:(NSString*)type {
const ui::OSExchangeDataProviderMac& provider =
static_cast<const ui::OSExchangeDataProviderMac&>(data_->provider());
NSData* ns_data = provider.GetNSDataForType(type);
[sender setData:ns_data forType:type];
}
@end
namespace views {
DragDropClientMac::DragDropClientMac(BridgedNativeWidgetImpl* bridge,
View* root_view)
: drop_helper_(root_view),
operation_(0),
bridge_(bridge),
quit_closure_(base::Closure()),
is_drag_source_(false) {
DCHECK(bridge);
}
DragDropClientMac::~DragDropClientMac() {}
void DragDropClientMac::StartDragAndDrop(
View* view,
const ui::OSExchangeData& data,
int operation,
ui::DragDropTypes::DragEventSource source) {
data_source_.reset([[CocoaDragDropDataProvider alloc] initWithData:data]);
operation_ = operation;
is_drag_source_ = true;
const ui::OSExchangeDataProviderMac& provider =
static_cast<const ui::OSExchangeDataProviderMac&>(data.provider());
// Release capture before beginning the dragging session. Capture may have
// been acquired on the mouseDown, but capture is not required during the
// dragging session and the mouseUp that would release it will be suppressed.
bridge_->ReleaseCapture();
// Synthesize an event for dragging, since we can't be sure that
// [NSApp currentEvent] will return a valid dragging event.
NSWindow* window = bridge_->ns_window();
NSPoint position = [window mouseLocationOutsideOfEventStream];
NSTimeInterval event_time = [[NSApp currentEvent] timestamp];
NSEvent* event = [NSEvent mouseEventWithType:NSLeftMouseDragged
location:position
modifierFlags:NSLeftMouseDraggedMask
timestamp:event_time
windowNumber:[window windowNumber]
context:nil
eventNumber:0
clickCount:1
pressure:1.0];
NSImage* image = gfx::NSImageFromImageSkiaWithColorSpace(
provider.GetDragImage(), base::mac::GetSRGBColorSpace());
// TODO(crbug/876201): This shouldn't happen. When a repro for this
// is identified and the bug is fixed, change the early return to
// a DCHECK.
if (!image || NSEqualSizes([image size], NSZeroSize))
return;
base::scoped_nsobject<NSPasteboardItem> item([[NSPasteboardItem alloc] init]);
[item setDataProvider:data_source_.get()
forTypes:provider.GetAvailableTypes()];
base::scoped_nsobject<NSDraggingItem> drag_item(
[[NSDraggingItem alloc] initWithPasteboardWriter:item.get()]);
// Subtract the image's height from the y location so that the mouse will be
// at the upper left corner of the image.
NSRect dragging_frame =
NSMakeRect([event locationInWindow].x,
[event locationInWindow].y - [image size].height,
[image size].width, [image size].height);
[drag_item setDraggingFrame:dragging_frame contents:image];
[bridge_->ns_view() beginDraggingSessionWithItems:@[ drag_item.get() ]
event:event
source:bridge_->ns_view()];
// Since Drag and drop is asynchronous on Mac, we need to spin a nested run
// loop for consistency with other platforms.
base::RunLoop run_loop;
quit_closure_ = run_loop.QuitClosure();
run_loop.Run();
}
NSDragOperation DragDropClientMac::DragUpdate(id<NSDraggingInfo> sender) {
if (!data_source_.get()) {
data_source_.reset([[CocoaDragDropDataProvider alloc]
initWithPasteboard:[sender draggingPasteboard]]);
operation_ = ui::DragDropTypes::NSDragOperationToDragOperation(
[sender draggingSourceOperationMask]);
}
int drag_operation = drop_helper_.OnDragOver(
*[data_source_ data], LocationInView([sender draggingLocation]),
operation_);
return ui::DragDropTypes::DragOperationToNSDragOperation(drag_operation);
}
NSDragOperation DragDropClientMac::Drop(id<NSDraggingInfo> sender) {
// OnDrop may delete |this|, so clear |data_source_| first.
base::scoped_nsobject<CocoaDragDropDataProvider> data_source(
std::move(data_source_));
int drag_operation = drop_helper_.OnDrop(
*[data_source data], LocationInView([sender draggingLocation]),
operation_);
return ui::DragDropTypes::DragOperationToNSDragOperation(drag_operation);
}
void DragDropClientMac::EndDrag() {
data_source_.reset();
is_drag_source_ = false;
// Allow a test to invoke EndDrag() without spinning the nested run loop.
if (!quit_closure_.is_null()) {
quit_closure_.Run();
quit_closure_.Reset();
}
}
void DragDropClientMac::DragExit() {
drop_helper_.OnDragExit();
if (!is_drag_source_)
data_source_.reset();
}
gfx::Point DragDropClientMac::LocationInView(NSPoint point) const {
return gfx::Point(point.x, NSHeight([bridge_->ns_window() frame]) - point.y);
}
} // namespace views