Skip to content

Commit

Permalink
QCocoaDrag - avoid using the deprecated API if possible
Browse files Browse the repository at this point in the history
The -dragImage stopped to work in 10.14 first with change in behavior
documented and the new API (AppKit) proposed/advised. Then after
some update the old behavior was restored and D&D for multiple
URLs was working again without us having to change anything.
In 10.15 it's not working anymore and we have to properly fix
it and switch to the API advised. The new API is non-blocking,
but QCocoaDrag::drag is expected to be blocking so we have to
add a nested event loop to emulate the old behavior. Dragging
image is a bit problematic (the exception is thrown because
number of images (1) and number of items in NSPasteboard
are not equal): creating separate images for different
drag items looks really messy. So ... we use the same image
again and again with the same frame.

Fixes: QTBUG-71939
Change-Id: I02c2a10eab280cf4a55513adaf29c22fff934c01
Pick-to: 5.15
Reviewed-by: Tor Arne Vestbø <tor.arne.vestbo@qt.io>
  • Loading branch information
Timur Pocheptsov committed May 8, 2020
1 parent 7cb4c50 commit 8481a9f
Show file tree
Hide file tree
Showing 3 changed files with 104 additions and 1 deletion.
6 changes: 6 additions & 0 deletions src/plugins/platforms/cocoa/qcocoadrag.h
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@
#include <QtGui/private/qdnd_p.h>
#include <QtGui/private/qinternalmimedata_p.h>

#include <QtCore/qeventloop.h>

QT_BEGIN_NAMESPACE

class QCocoaDrag : public QPlatformDrag
Expand All @@ -69,11 +71,15 @@ class QCocoaDrag : public QPlatformDrag
void setLastMouseEvent(NSEvent *event, NSView *view);

void setAcceptedAction(Qt::DropAction act);
void exitDragLoop();
private:
QDrag *m_drag;
NSEvent *m_lastEvent;
NSView *m_lastView;
Qt::DropAction m_executed_drop_action;
QEventLoop internalDragLoop;

bool maybeDragMultipleItems();

QPixmap dragPixmap(QDrag *drag, QPoint &hotSpot) const;
};
Expand Down
90 changes: 90 additions & 0 deletions src/plugins/platforms/cocoa/qcocoadrag.mm
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,9 @@
#include "qmacclipboard.h"
#include "qcocoahelpers.h"
#include <QtGui/private/qcoregraphics_p.h>
#include <QtCore/qsysinfo.h>

#include <vector>

QT_BEGIN_NAMESPACE

Expand Down Expand Up @@ -125,6 +128,9 @@
m_drag = o;
m_executed_drop_action = Qt::IgnoreAction;

if (maybeDragMultipleItems())
return m_executed_drop_action;

QPoint hotSpot = m_drag->hotSpot();
QPixmap pm = dragPixmap(m_drag, hotSpot);
NSImage *dragImage = [NSImage imageFromQImage:pm.toImage()];
Expand Down Expand Up @@ -155,11 +161,95 @@
return m_executed_drop_action;
}

bool QCocoaDrag::maybeDragMultipleItems()
{
Q_ASSERT(m_drag && m_drag->mimeData());
Q_ASSERT(m_executed_drop_action == Qt::IgnoreAction);

if (QOperatingSystemVersion::current() < QOperatingSystemVersion::MacOSMojave) {
// -dragImage: stopped working in 10.14 first.
return false;
}

const QMacAutoReleasePool pool;

NSWindow *theWindow = [m_lastEvent window];
Q_ASSERT(theWindow);

if (![theWindow.contentView respondsToSelector:@selector(draggingSession:sourceOperationMaskForDraggingContext:)])
return false;

auto *sourceView = static_cast<NSView<NSDraggingSource>*>(theWindow.contentView);

const auto &qtUrls = m_drag->mimeData()->urls();
NSPasteboard *dragBoard = [NSPasteboard pasteboardWithName:NSPasteboardNameDrag];

if (int(dragBoard.pasteboardItems.count) == 1 && qtUrls.size() <= 1) {
// Good old -dragImage: works perfectly for this ...
return false;
}

std::vector<NSPasteboardItem *> nonUrls;
for (NSPasteboardItem *item in dragBoard.pasteboardItems) {
bool isUrl = false;
for (NSPasteboardType type in item.types) {
using NSStringRef = NSString *;
if ([type isEqualToString:NSStringRef(kUTTypeFileURL)]) {
isUrl = true;
break;
}
}

if (!isUrl)
nonUrls.push_back(item);
}

QPoint hotSpot = m_drag->hotSpot();
const auto pixmap = dragPixmap(m_drag, hotSpot);
NSImage *dragImage = [NSImage imageFromQImage:pixmap.toImage()];
Q_ASSERT(dragImage);

NSMutableArray<NSDraggingItem *> *dragItems = [[[NSMutableArray alloc] init] autorelease];
const NSPoint itemLocation = m_drag->hotSpot().toCGPoint();
// 0. We start from URLs, which can be actually in a list (thus technically
// only ONE item in the pasteboard. The fact it's only one does not help, we are
// still getting an exception because of the number of items/images mismatch ...
for (const auto &qtUrl : qtUrls) {
NSURL *nsUrl = qtUrl.toNSURL();
auto *newItem = [[[NSDraggingItem alloc] initWithPasteboardWriter:nsUrl] autorelease];
const NSRect itemFrame = NSMakeRect(itemLocation.x, itemLocation.y,
dragImage.size.width,
dragImage.size.height);
[newItem setDraggingFrame:itemFrame contents:dragImage];
[dragItems addObject:newItem];
}
// 1. Repeat for non-url items, if any:
for (auto *pbItem : nonUrls) {
auto *newItem = [[[NSDraggingItem alloc] initWithPasteboardWriter:pbItem] autorelease];
const NSRect itemFrame = NSMakeRect(itemLocation.x, itemLocation.y,
dragImage.size.width,
dragImage.size.height);
[newItem setDraggingFrame:itemFrame contents:dragImage];
[dragItems addObject:newItem];
}

[sourceView beginDraggingSessionWithItems:dragItems event:m_lastEvent source:sourceView];
internalDragLoop.exec();
return true;
}

void QCocoaDrag::setAcceptedAction(Qt::DropAction act)
{
m_executed_drop_action = act;
}

void QCocoaDrag::exitDragLoop()
{
if (internalDragLoop.isRunning())
internalDragLoop.exit();
}


QPixmap QCocoaDrag::dragPixmap(QDrag *drag, QPoint &hotSpot) const
{
const QMimeData* data = drag->mimeData();
Expand Down
9 changes: 8 additions & 1 deletion src/plugins/platforms/cocoa/qnsview_dragging.mm
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,10 @@ - (void)draggingExited:(id<NSDraggingInfo>)sender
if (!target)
return;

auto *nativeDrag = QCocoaIntegration::instance()->drag();
Q_ASSERT(nativeDrag);
nativeDrag->exitDragLoop();

QPoint windowPoint = QPointF::fromCGPoint([self convertPoint:sender.draggingLocation fromView:nil]).toPoint();

qCDebug(lcQpaMouse) << QEvent::DragLeave << self << "at" << windowPoint;
Expand Down Expand Up @@ -289,7 +293,10 @@ - (void)draggingSession:(NSDraggingSession *)session endedAtPoint:(NSPoint)scree
if (!target)
return;

QCocoaIntegration::instance()->drag();
QCocoaDrag* nativeDrag = QCocoaIntegration::instance()->drag();
Q_ASSERT(nativeDrag);
nativeDrag->exitDragLoop();
nativeDrag->setAcceptedAction(qt_mac_mapNSDragOperation(operation));

// Qt starts drag-and-drop on a mouse button press event. Cococa in
// this case won't send the matching release event, so we have to
Expand Down

0 comments on commit 8481a9f

Please sign in to comment.