diff options
Diffstat (limited to 'src/plugins/platforms/cocoa/qcocoadrag.mm')
-rw-r--r-- | src/plugins/platforms/cocoa/qcocoadrag.mm | 207 |
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; } |