Qt 6.x
The Qt SDK
Loading...
Searching...
No Matches
qcocoadrag.mm
Go to the documentation of this file.
1// Copyright (C) 2016 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
4#include <AppKit/AppKit.h>
5
6#include "qcocoadrag.h"
7#include "qmacclipboard.h"
8#include "qcocoahelpers.h"
9#include <QtGui/private/qcoregraphics_p.h>
10#include <QtGui/qutimimeconverter.h>
11#include <QtCore/qsysinfo.h>
12#include <QtCore/private/qcore_mac_p.h>
13
14#include <vector>
15
17
18using namespace Qt::StringLiterals;
19
20static const int dragImageMaxChars = 26;
21
23 m_drag(nullptr)
24{
25 m_lastEvent = nil;
26 m_lastView = nil;
27}
28
30{
31 [m_lastEvent release];
32}
33
35{
36 [m_lastEvent release];
37 m_lastEvent = [event copy];
38 m_lastView = view;
39}
40
42{
43 if (m_drag)
44 return m_drag->mimeData();
45
46 return nullptr;
47}
48
49Qt::DropAction QCocoaDrag::defaultAction(Qt::DropActions possibleActions,
50 Qt::KeyboardModifiers modifiers) const
51{
52 Qt::DropAction default_action = Qt::IgnoreAction;
53
54 if (currentDrag()) {
55 default_action = currentDrag()->defaultAction();
56 possibleActions = currentDrag()->supportedActions();
57 }
58
59 if (default_action == Qt::IgnoreAction) {
60 //This means that the drag was initiated by QDrag::start and we need to
61 //preserve the old behavior
62 default_action = Qt::CopyAction;
63 }
64
66 default_action = Qt::LinkAction;
67 else if (modifiers & Qt::AltModifier)
68 default_action = Qt::CopyAction;
70 default_action = Qt::MoveAction;
71
72#ifdef QDND_DEBUG
73 qDebug("possible actions : %s", dragActionsToString(possibleActions).latin1());
74#endif
75
76 // Check if the action determined is allowed
77 if (!(possibleActions & default_action)) {
78 if (possibleActions & Qt::CopyAction)
79 default_action = Qt::CopyAction;
80 else if (possibleActions & Qt::MoveAction)
81 default_action = Qt::MoveAction;
82 else if (possibleActions & Qt::LinkAction)
83 default_action = Qt::LinkAction;
84 else
85 default_action = Qt::IgnoreAction;
86 }
87
88#ifdef QDND_DEBUG
89 qDebug("default action : %s", dragActionsToString(default_action).latin1());
90#endif
91
92 return default_action;
93}
94
95
97{
98 m_drag = o;
99 m_executed_drop_action = Qt::IgnoreAction;
100
101 QMacPasteboard dragBoard(CFStringRef(NSPasteboardNameDrag), QUtiMimeConverter::HandlerScopeFlag::DnD);
102 m_drag->mimeData()->setData("application/x-qt-mime-type-name"_L1, QByteArray("dummy"));
104
105 if (maybeDragMultipleItems())
106 return m_executed_drop_action;
107
108 QPoint hotSpot = m_drag->hotSpot();
109 QPixmap pm = dragPixmap(m_drag, hotSpot);
110 NSImage *dragImage = [NSImage imageFromQImage:pm.toImage()];
111 Q_ASSERT(dragImage);
112
113 NSPoint event_location = [m_lastEvent locationInWindow];
114 NSWindow *theWindow = [m_lastEvent window];
115 Q_ASSERT(theWindow);
116 event_location.x -= hotSpot.x();
117 CGFloat flippedY = dragImage.size.height - hotSpot.y();
118 event_location.y -= flippedY;
119 NSSize mouseOffset_unused = NSMakeSize(0.0, 0.0);
120 NSPasteboard *pboard = [NSPasteboard pasteboardWithName:NSPasteboardNameDrag];
121
122 [theWindow dragImage:dragImage
123 at:event_location
124 offset:mouseOffset_unused
125 event:m_lastEvent
126 pasteboard:pboard
127 source:m_lastView
128 slideBack:YES];
129
130 m_drag = nullptr;
131 return m_executed_drop_action;
132}
133
134bool QCocoaDrag::maybeDragMultipleItems()
135{
136 Q_ASSERT(m_drag && m_drag->mimeData());
137 Q_ASSERT(m_executed_drop_action == Qt::IgnoreAction);
138
140 // -dragImage: stopped working in 10.14 first.
141 return false;
142 }
143
145
146 NSView *view = m_lastView ? m_lastView : m_lastEvent.window.contentView;
147 if (![view respondsToSelector:@selector(draggingSession:sourceOperationMaskForDraggingContext:)])
149
150 auto *sourceView = static_cast<NSView<NSDraggingSource>*>(view);
151
152 const auto &qtUrls = m_drag->mimeData()->urls();
153 NSPasteboard *dragBoard = [NSPasteboard pasteboardWithName:NSPasteboardNameDrag];
154
155 if (qtUrls.size() <= 1) {
156 // Good old -dragImage: works perfectly for this ...
157 return false;
158 }
159
160 std::vector<NSPasteboardItem *> nonUrls;
161 for (NSPasteboardItem *item in dragBoard.pasteboardItems) {
162 bool isUrl = false;
163 for (NSPasteboardType type in item.types) {
164 using NSStringRef = NSString *;
165 if ([type isEqualToString:NSStringRef(kUTTypeFileURL)]) {
166 isUrl = true;
167 break;
168 }
169 }
170
171 if (!isUrl)
172 nonUrls.push_back(item);
173 }
174
175 QPoint hotSpot = m_drag->hotSpot();
176 const auto pixmap = dragPixmap(m_drag, hotSpot);
177 NSImage *dragImage = [NSImage imageFromQImage:pixmap.toImage()];
178 Q_ASSERT(dragImage);
179
180 NSMutableArray<NSDraggingItem *> *dragItems = [[[NSMutableArray alloc] init] autorelease];
181 const NSPoint itemLocation = m_drag->hotSpot().toCGPoint();
182 // 0. We start from URLs, which can be actually in a list (thus technically
183 // only ONE item in the pasteboard. The fact it's only one does not help, we are
184 // still getting an exception because of the number of items/images mismatch ...
185 // We only set the image for the first item and nil for the rest, the image already
186 // contains a combined picture for all urls we drag.
187 auto imageOrNil = dragImage;
188 for (const auto &qtUrl : qtUrls) {
189 if (!qtUrl.isValid())
190 continue;
191
192 if (qtUrl.isRelative()) // NSPasteboardWriting rejects such items.
193 continue;
194
195 NSURL *nsUrl = qtUrl.toNSURL();
196 auto *newItem = [[[NSDraggingItem alloc] initWithPasteboardWriter:nsUrl] autorelease];
197 const NSRect itemFrame = NSMakeRect(itemLocation.x, itemLocation.y,
198 dragImage.size.width,
199 dragImage.size.height);
200
201 [newItem setDraggingFrame:itemFrame contents:imageOrNil];
202 imageOrNil = nil;
203 [dragItems addObject:newItem];
204 }
205 // 1. Repeat for non-url items, if any:
206 for (auto *pbItem : nonUrls) {
207 auto *newItem = [[[NSDraggingItem alloc] initWithPasteboardWriter:pbItem] autorelease];
208 const NSRect itemFrame = NSMakeRect(itemLocation.x, itemLocation.y,
209 dragImage.size.width,
210 dragImage.size.height);
211 [newItem setDraggingFrame:itemFrame contents:imageOrNil];
212 [dragItems addObject:newItem];
213 }
214
215 [sourceView beginDraggingSessionWithItems:dragItems event:m_lastEvent source:sourceView];
216 QEventLoop eventLoop;
217 QScopedValueRollback updateGuard(m_internalDragLoop, &eventLoop);
218 eventLoop.exec();
219 return true;
220}
221
223{
224 m_executed_drop_action = act;
225}
226
228{
229 if (m_internalDragLoop) {
230 Q_ASSERT(m_internalDragLoop->isRunning());
231 m_internalDragLoop->exit();
232 }
233}
234
235
236QPixmap QCocoaDrag::dragPixmap(QDrag *drag, QPoint &hotSpot) const
237{
238 const QMimeData* data = drag->mimeData();
239 QPixmap pm = drag->pixmap();
240
241 if (pm.isNull()) {
242 QFont f(qApp->font());
243 f.setPointSize(12);
244 QFontMetrics fm(f);
245
246 if (data->hasImage()) {
247 const QImage img = data->imageData().value<QImage>();
248 if (!img.isNull()) {
249 pm = QPixmap::fromImage(img).scaledToWidth(dragImageMaxChars *fm.averageCharWidth());
250 }
251 }
252
253 if (pm.isNull() && (data->hasText() || data->hasUrls()) ) {
254 QString s = data->hasText() ? data->text() : data->urls().first().toString();
255 if (s.length() > dragImageMaxChars)
256 s = s.left(dragImageMaxChars -3) + QChar(0x2026);
257 if (!s.isEmpty()) {
258 const int width = fm.horizontalAdvance(s);
259 const int height = fm.height();
260 if (width > 0 && height > 0) {
261 qreal dpr = 1.0;
263 if (!window && drag->source()->metaObject()->indexOfMethod("_q_closestWindowHandle()") != -1) {
264 QMetaObject::invokeMethod(drag->source(), "_q_closestWindowHandle",
266 }
267 if (!window)
268 window = qApp->focusWindow();
269
270 if (window)
271 dpr = window->devicePixelRatio();
272
273 pm = QPixmap(width * dpr, height * dpr);
275 QPainter p(&pm);
276 p.fillRect(0, 0, pm.width(), pm.height(), Qt::color0);
277 p.setPen(Qt::color1);
278 p.setFont(f);
279 p.drawText(0, fm.ascent(), s);
280 p.end();
281 hotSpot = QPoint(pm.width() / 2, pm.height() / 2);
282 }
283 }
284 }
285 }
286
287 if (pm.isNull())
288 pm = defaultPixmap();
289
290 return pm;
291}
292
293QCocoaDropData::QCocoaDropData(NSPasteboard *pasteboard)
294{
295 dropPasteboard = reinterpret_cast<CFStringRef>(const_cast<const NSString *>([pasteboard name]));
296 CFRetain(dropPasteboard);
297}
298
300{
301 CFRelease(dropPasteboard);
302}
303
305{
307 PasteboardRef board;
308 if (PasteboardCreate(dropPasteboard, &board) != noErr) {
309 qDebug("DnD: Cannot get PasteBoard!");
310 return formats;
311 }
313 return formats;
314}
315
317{
319 PasteboardRef board;
320 if (PasteboardCreate(dropPasteboard, &board) != noErr) {
321 qDebug("DnD: Cannot get PasteBoard!");
322 return data;
323 }
325 CFRelease(board);
326 return data;
327}
328
330{
331 bool has = false;
332 PasteboardRef board;
333 if (PasteboardCreate(dropPasteboard, &board) != noErr) {
334 qDebug("DnD: Cannot get PasteBoard!");
335 return has;
336 }
338 CFRelease(board);
339 return has;
340}
341
342
344
\inmodule QtCore
Definition qbytearray.h:57
\inmodule QtCore
Definition qchar.h:48
void setLastMouseEvent(NSEvent *event, NSView *view)
Definition qcocoadrag.mm:34
Qt::DropAction defaultAction(Qt::DropActions possibleActions, Qt::KeyboardModifiers modifiers) const override
Definition qcocoadrag.mm:49
Qt::DropAction drag(QDrag *m_drag) override
Definition qcocoadrag.mm:96
void setAcceptedAction(Qt::DropAction act)
QMimeData * dragMimeData()
Definition qcocoadrag.mm:41
void exitDragLoop()
QVariant retrieveData_sys(const QString &mimeType, QMetaType type) const
QStringList formats_sys() const
QCocoaDropData(NSPasteboard *pasteboard)
bool hasFormat_sys(const QString &mimeType) const
CFStringRef dropPasteboard
Definition qcocoadrag.h:64
\inmodule QtGui
Definition qdrag.h:22
QMimeData * mimeData() const
Returns the MIME data that is encapsulated by the drag object.
Definition qdrag.cpp:112
Qt::DropAction defaultAction() const
Returns the default proposed drop action for this drag operation.
Definition qdrag.cpp:320
QPoint hotSpot() const
Returns the position of the hot spot relative to the top-left corner of the cursor.
Definition qdrag.cpp:156
Qt::DropActions supportedActions() const
Returns the set of possible drop actions for this drag operation.
Definition qdrag.cpp:308
\inmodule QtCore
Definition qeventloop.h:16
int exec(ProcessEventsFlags flags=AllEvents)
Enters the main event loop and waits until exit() is called.
void exit(int returnCode=0)
Tells the event loop to exit with a return code.
bool isRunning() const
Returns true if the event loop is running; otherwise returns false.
\reentrant \inmodule QtGui
\reentrant
Definition qfont.h:20
\inmodule QtGui
Definition qimage.h:37
QStringList formats() const override
Returns a list of formats supported by the object.
void push_back(parameter_type t)
Definition qlist.h:672
QStringList formats() const
void setMimeData(QMimeData *mime, DataRequestType dataRequestType=EagerRequest)
QVariant retrieveData(const QString &format) const
bool hasFormat(const QString &format) const
\inmodule QtCore
Definition qmetatype.h:320
\inmodule QtCore
Definition qmimedata.h:16
void setData(const QString &mimetype, const QByteArray &data)
Sets the data associated with the MIME type given by mimeType to the specified data.
QList< QUrl > urls() const
Returns a list of URLs contained within the MIME data object.
static constexpr QOperatingSystemVersionBase MacOSMojave
\variable QOperatingSystemVersion::MacOSMojave
static QOperatingSystemVersion current()
[0]
The QPainter class performs low-level painting on widgets and other paint devices.
Definition qpainter.h:46
Returns a copy of the pixmap that is transformed using the given transformation transform and transfo...
Definition qpixmap.h:27
int height() const
Returns the height of the pixmap.
Definition qpixmap.cpp:484
QImage toImage() const
Converts the pixmap to a QImage.
Definition qpixmap.cpp:412
QPixmap scaledToWidth(int w, Qt::TransformationMode mode=Qt::FastTransformation) const
Returns a scaled copy of the image.
Definition qpixmap.cpp:1038
bool isNull() const
Returns true if this is a null pixmap; otherwise returns false.
Definition qpixmap.cpp:460
int width() const
Returns the width of the pixmap.
Definition qpixmap.cpp:472
void setDevicePixelRatio(qreal scaleFactor)
Sets the device pixel ratio for the pixmap.
Definition qpixmap.cpp:608
static QPixmap fromImage(const QImage &image, Qt::ImageConversionFlags flags=Qt::AutoColor)
Converts the given image to a pixmap using the specified flags to control the conversion.
Definition qpixmap.cpp:1445
QDrag * currentDrag() const
static QPixmap defaultPixmap()
\inmodule QtCore\reentrant
Definition qpoint.h:23
constexpr int x() const noexcept
Returns the x coordinate of this point.
Definition qpoint.h:127
constexpr int y() const noexcept
Returns the y coordinate of this point.
Definition qpoint.h:132
\inmodule QtCore
\macro QT_RESTRICTED_CAST_FROM_ASCII
Definition qstring.h:127
QString first(qsizetype n) const
Definition qstring.h:337
QString left(qsizetype n) const
Returns a substring that contains the n leftmost characters of the string.
Definition qstring.cpp:5161
\inmodule QtCore
Definition qvariant.h:64
\inmodule QtGui
Definition qwindow.h:63
EGLImageKHR int int EGLuint64KHR * modifiers
EGLint EGLint * formats
Combined button and popup list for selecting options.
@ color1
Definition qnamespace.h:28
@ color0
Definition qnamespace.h:27
@ ControlModifier
@ AltModifier
DropAction
@ CopyAction
@ IgnoreAction
@ MoveAction
@ LinkAction
static jboolean copy(JNIEnv *, jobject)
static const int dragImageMaxChars
Definition qcocoadrag.mm:20
float CGFloat
#define qApp
const char * mimeType
#define qDebug
[1]
Definition qlogging.h:160
#define Q_RETURN_ARG(Type, data)
Definition qobjectdefs.h:63
GLint GLsizei GLsizei height
GLfloat GLfloat f
GLint GLsizei width
GLenum type
GLint GLsizei GLsizei GLenum GLenum GLsizei void * data
GLenum GLuint GLintptr offset
GLuint name
GLsizei GLsizei GLchar * source
struct _cl_event * event
GLint void * img
Definition qopenglext.h:233
GLuint in
GLdouble s
[6]
Definition qopenglext.h:235
GLfloat GLfloat p
[1]
static QT_BEGIN_NAMESPACE qreal dpr(const QWindow *w)
#define Q_ASSERT(cond)
Definition qrandom.cpp:47
static QT_BEGIN_NAMESPACE void init(QTextBoundaryFinder::BoundaryType type, QStringView str, QCharAttributes *attributes)
double qreal
Definition qtypes.h:92
QWindow * qobject_cast< QWindow * >(QObject *o)
Definition qwindow.h:367
QFileSelector selector
[1]
QObject::connect nullptr
sem release()
QGraphicsItem * item
widget render & pixmap
aWidget window() -> setWindowTitle("New Window Title")
[2]
QAction * at
QQuickView * view
[0]
static bool invokeMethod(QObject *obj, const char *member, Qt::ConnectionType, QGenericReturnArgument ret, QGenericArgument val0=QGenericArgument(nullptr), QGenericArgument val1=QGenericArgument(), QGenericArgument val2=QGenericArgument(), QGenericArgument val3=QGenericArgument(), QGenericArgument val4=QGenericArgument(), QGenericArgument val5=QGenericArgument(), QGenericArgument val6=QGenericArgument(), QGenericArgument val7=QGenericArgument(), QGenericArgument val8=QGenericArgument(), QGenericArgument val9=QGenericArgument())
\threadsafe This is an overloaded member function, provided for convenience. It differs from the abov...