summaryrefslogtreecommitdiffstats
path: root/src/plugins/platforms/cocoa/qcocoadrag.mm
diff options
context:
space:
mode:
Diffstat (limited to 'src/plugins/platforms/cocoa/qcocoadrag.mm')
-rw-r--r--src/plugins/platforms/cocoa/qcocoadrag.mm207
1 files changed, 136 insertions, 71 deletions
diff --git a/src/plugins/platforms/cocoa/qcocoadrag.mm b/src/plugins/platforms/cocoa/qcocoadrag.mm
index 95808b8a11..a8404889e9 100644
--- a/src/plugins/platforms/cocoa/qcocoadrag.mm
+++ b/src/plugins/platforms/cocoa/qcocoadrag.mm
@@ -1,52 +1,23 @@
-/****************************************************************************
-**
-** Copyright (C) 2016 The Qt Company Ltd.
-** Contact: https://www.qt.io/licensing/
-**
-** This file is part of the plugins of the Qt Toolkit.
-**
-** $QT_BEGIN_LICENSE:LGPL$
-** Commercial License Usage
-** Licensees holding valid commercial Qt licenses may use this file in
-** accordance with the commercial license agreement provided with the
-** Software or, alternatively, in accordance with the terms contained in
-** a written agreement between you and The Qt Company. For licensing terms
-** and conditions see https://www.qt.io/terms-conditions. For further
-** information use the contact form at https://www.qt.io/contact-us.
-**
-** GNU Lesser General Public License Usage
-** Alternatively, this file may be used under the terms of the GNU Lesser
-** General Public License version 3 as published by the Free Software
-** Foundation and appearing in the file LICENSE.LGPL3 included in the
-** packaging of this file. Please review the following information to
-** ensure the GNU Lesser General Public License version 3 requirements
-** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
-**
-** GNU General Public License Usage
-** Alternatively, this file may be used under the terms of the GNU
-** General Public License version 2.0 or (at your option) the GNU General
-** Public license version 3 or any later version approved by the KDE Free
-** Qt Foundation. The licenses are as published by the Free Software
-** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
-** included in the packaging of this file. Please review the following
-** information to ensure the GNU General Public License requirements will
-** be met: https://www.gnu.org/licenses/gpl-2.0.html and
-** https://www.gnu.org/licenses/gpl-3.0.html.
-**
-** $QT_END_LICENSE$
-**
-****************************************************************************/
+// Copyright (C) 2016 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#include <AppKit/AppKit.h>
+#include <UniformTypeIdentifiers/UTCoreTypes.h>
#include "qcocoadrag.h"
#include "qmacclipboard.h"
#include "qcocoahelpers.h"
-#ifndef QT_NO_WIDGETS
-#include <QtWidgets/qwidget.h>
-#endif
#include <QtGui/private/qcoregraphics_p.h>
+#include <QtGui/qutimimeconverter.h>
+#include <QtCore/qsysinfo.h>
+#include <QtCore/private/qcore_mac_p.h>
+
+#include <vector>
QT_BEGIN_NAMESPACE
+using namespace Qt::StringLiterals;
+
static const int dragImageMaxChars = 26;
QCocoaDrag::QCocoaDrag() :
@@ -128,26 +99,28 @@ Qt::DropAction QCocoaDrag::drag(QDrag *o)
m_drag = o;
m_executed_drop_action = Qt::IgnoreAction;
+ QMacPasteboard dragBoard(CFStringRef(NSPasteboardNameDrag), QUtiMimeConverter::HandlerScopeFlag::DnD);
+ m_drag->mimeData()->setData("application/x-qt-mime-type-name"_L1, QByteArray("dummy"));
+ dragBoard.setMimeData(m_drag->mimeData(), QMacPasteboard::LazyRequest);
+
+ if (maybeDragMultipleItems())
+ return m_executed_drop_action;
+
QPoint hotSpot = m_drag->hotSpot();
QPixmap pm = dragPixmap(m_drag, hotSpot);
- QSize pmDeviceIndependentSize = pm.size() / pm.devicePixelRatio();
- NSImage *nsimage = qt_mac_create_nsimage(pm);
- [nsimage setSize:NSSizeFromCGSize(pmDeviceIndependentSize.toCGSize())];
-
- QMacPasteboard dragBoard(CFStringRef(NSPasteboardNameDrag), QMacInternalPasteboardMime::MIME_DND);
- m_drag->mimeData()->setData(QLatin1String("application/x-qt-mime-type-name"), QByteArray("dummy"));
- dragBoard.setMimeData(m_drag->mimeData(), QMacPasteboard::LazyRequest);
+ NSImage *dragImage = [NSImage imageFromQImage:pm.toImage()];
+ Q_ASSERT(dragImage);
NSPoint event_location = [m_lastEvent locationInWindow];
NSWindow *theWindow = [m_lastEvent window];
Q_ASSERT(theWindow);
event_location.x -= hotSpot.x();
- CGFloat flippedY = pmDeviceIndependentSize.height() - hotSpot.y();
+ CGFloat flippedY = dragImage.size.height - hotSpot.y();
event_location.y -= flippedY;
NSSize mouseOffset_unused = NSMakeSize(0.0, 0.0);
NSPasteboard *pboard = [NSPasteboard pasteboardWithName:NSPasteboardNameDrag];
- [theWindow dragImage:nsimage
+ [theWindow dragImage:dragImage
at:event_location
offset:mouseOffset_unused
event:m_lastEvent
@@ -155,17 +128,111 @@ Qt::DropAction QCocoaDrag::drag(QDrag *o)
source:m_lastView
slideBack:YES];
- [nsimage release];
-
m_drag = nullptr;
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;
+
+ NSView *view = m_lastView ? m_lastView : m_lastEvent.window.contentView;
+ if (![view respondsToSelector:@selector(draggingSession:sourceOperationMaskForDraggingContext:)])
+ return false;
+
+ auto *sourceView = static_cast<NSView<NSDraggingSource>*>(view);
+
+ const auto &qtUrls = m_drag->mimeData()->urls();
+ NSPasteboard *dragBoard = [NSPasteboard pasteboardWithName:NSPasteboardNameDrag];
+
+ if (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) {
+ if ([type isEqualToString:UTTypeFileURL.identifier]) {
+ 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 ...
+ // We only set the image for the first item and nil for the rest, the image already
+ // contains a combined picture for all urls we drag.
+ auto imageOrNil = dragImage;
+ for (const auto &qtUrl : qtUrls) {
+ if (!qtUrl.isValid())
+ continue;
+
+ if (qtUrl.isRelative()) // NSPasteboardWriting rejects such items.
+ continue;
+
+ 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:imageOrNil];
+ imageOrNil = nil;
+ [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:imageOrNil];
+ [dragItems addObject:newItem];
+ }
+
+ [sourceView beginDraggingSessionWithItems:dragItems event:m_lastEvent source:sourceView];
+ QEventLoop eventLoop;
+ QScopedValueRollback updateGuard(m_internalDragLoop, &eventLoop);
+ eventLoop.exec();
+ return true;
+}
+
void QCocoaDrag::setAcceptedAction(Qt::DropAction act)
{
m_executed_drop_action = act;
}
+void QCocoaDrag::exitDragLoop()
+{
+ if (m_internalDragLoop) {
+ Q_ASSERT(m_internalDragLoop->isRunning());
+ m_internalDragLoop->exit();
+ }
+}
+
+
QPixmap QCocoaDrag::dragPixmap(QDrag *drag, QPoint &hotSpot) const
{
const QMimeData* data = drag->mimeData();
@@ -177,14 +244,14 @@ QPixmap QCocoaDrag::dragPixmap(QDrag *drag, QPoint &hotSpot) const
QFontMetrics fm(f);
if (data->hasImage()) {
- const QImage img = data->imageData().value<QImage>();
+ QImage img = data->imageData().value<QImage>();
if (!img.isNull()) {
- pm = QPixmap::fromImage(img).scaledToWidth(dragImageMaxChars *fm.averageCharWidth());
+ pm = QPixmap::fromImage(std::move(img)).scaledToWidth(dragImageMaxChars *fm.averageCharWidth());
}
}
if (pm.isNull() && (data->hasText() || data->hasUrls()) ) {
- QString s = data->hasText() ? data->text() : data->urls().first().toString();
+ QString s = data->hasText() ? data->text() : data->urls().constFirst().toString();
if (s.length() > dragImageMaxChars)
s = s.left(dragImageMaxChars -3) + QChar(0x2026);
if (!s.isEmpty()) {
@@ -192,19 +259,17 @@ QPixmap QCocoaDrag::dragPixmap(QDrag *drag, QPoint &hotSpot) const
const int height = fm.height();
if (width > 0 && height > 0) {
qreal dpr = 1.0;
- if (const QWindow *sourceWindow = qobject_cast<QWindow *>(drag->source())) {
- dpr = sourceWindow->devicePixelRatio();
- }
-#ifndef QT_NO_WIDGETS
- else if (const QWidget *sourceWidget = qobject_cast<QWidget *>(drag->source())) {
- if (const QWindow *sourceWindow = sourceWidget->window()->windowHandle())
- dpr = sourceWindow->devicePixelRatio();
- }
-#endif
- else {
- if (const QWindow *focusWindow = qApp->focusWindow())
- dpr = focusWindow->devicePixelRatio();
+ QWindow *window = qobject_cast<QWindow *>(drag->source());
+ if (!window && drag->source()->metaObject()->indexOfMethod("_q_closestWindowHandle()") != -1) {
+ QMetaObject::invokeMethod(drag->source(), "_q_closestWindowHandle",
+ Q_RETURN_ARG(QWindow *, window));
}
+ if (!window)
+ window = qApp->focusWindow();
+
+ if (window)
+ dpr = window->devicePixelRatio();
+
pm = QPixmap(width * dpr, height * dpr);
pm.setDevicePixelRatio(dpr);
QPainter p(&pm);
@@ -244,11 +309,11 @@ QStringList QCocoaDropData::formats_sys() const
qDebug("DnD: Cannot get PasteBoard!");
return formats;
}
- formats = QMacPasteboard(board, QMacInternalPasteboardMime::MIME_DND).formats();
+ formats = QMacPasteboard(board, QUtiMimeConverter::HandlerScopeFlag::DnD).formats();
return formats;
}
-QVariant QCocoaDropData::retrieveData_sys(const QString &mimeType, QVariant::Type type) const
+QVariant QCocoaDropData::retrieveData_sys(const QString &mimeType, QMetaType) const
{
QVariant data;
PasteboardRef board;
@@ -256,7 +321,7 @@ QVariant QCocoaDropData::retrieveData_sys(const QString &mimeType, QVariant::Typ
qDebug("DnD: Cannot get PasteBoard!");
return data;
}
- data = QMacPasteboard(board, QMacInternalPasteboardMime::MIME_DND).retrieveData(mimeType, type);
+ data = QMacPasteboard(board, QUtiMimeConverter::HandlerScopeFlag::DnD).retrieveData(mimeType);
CFRelease(board);
return data;
}
@@ -269,7 +334,7 @@ bool QCocoaDropData::hasFormat_sys(const QString &mimeType) const
qDebug("DnD: Cannot get PasteBoard!");
return has;
}
- has = QMacPasteboard(board, QMacInternalPasteboardMime::MIME_DND).hasFormat(mimeType);
+ has = QMacPasteboard(board, QUtiMimeConverter::HandlerScopeFlag::DnD).hasFormat(mimeType);
CFRelease(board);
return has;
}