Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
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// Qt-Security score:significant reason:default
4
5#include <AppKit/AppKit.h>
6#include <UniformTypeIdentifiers/UTCoreTypes.h>
7
8#include "qcocoadrag.h"
10#include "qcocoahelpers.h"
11
12#include <QtGui/qfont.h>
13#include <QtGui/qfontmetrics.h>
14#include <QtGui/qpainter.h>
15#include <QtGui/qutimimeconverter.h>
16#include <QtGui/private/qcoregraphics_p.h>
17#include <QtGui/private/qdnd_p.h>
18
19#include <QtCore/qeventloop.h>
20#include <QtCore/private/qcore_mac_p.h>
21
22#include <vector>
23
24QT_BEGIN_NAMESPACE
25
26using namespace Qt::StringLiterals;
27
28static const int dragImageMaxChars = 26;
29
31 m_drag(nullptr)
32{
33 m_lastEvent = nil;
34 m_lastView = nil;
35}
36
38{
39 [m_lastEvent release];
40}
41
42void QCocoaDrag::setLastInputEvent(NSEvent *event, NSView *view)
43{
44 [m_lastEvent release];
45 m_lastEvent = [event copy];
46 if (view != m_lastView)
47 m_lastView = view;
48}
49
51{
52 if (m_drag)
53 return m_drag->mimeData();
54
55 return nullptr;
56}
57
58Qt::DropAction QCocoaDrag::defaultAction(Qt::DropActions possibleActions,
59 Qt::KeyboardModifiers modifiers) const
60{
61 Qt::DropAction default_action = Qt::IgnoreAction;
62
63 if (currentDrag()) {
64 default_action = currentDrag()->defaultAction();
65 possibleActions = currentDrag()->supportedActions();
66 }
67
68 if (default_action == Qt::IgnoreAction) {
69 //This means that the drag was initiated by QDrag::start and we need to
70 //preserve the old behavior
71 default_action = Qt::CopyAction;
72 }
73
74 if (modifiers & Qt::ControlModifier && modifiers & Qt::AltModifier)
75 default_action = Qt::LinkAction;
76 else if (modifiers & Qt::AltModifier)
77 default_action = Qt::CopyAction;
78 else if (modifiers & Qt::ControlModifier)
79 default_action = Qt::MoveAction;
80
81#ifdef QDND_DEBUG
82 qDebug("possible actions : %s", dragActionsToString(possibleActions).latin1());
83#endif
84
85 // Check if the action determined is allowed
86 if (!(possibleActions & default_action)) {
87 if (possibleActions & Qt::CopyAction)
88 default_action = Qt::CopyAction;
89 else if (possibleActions & Qt::MoveAction)
90 default_action = Qt::MoveAction;
91 else if (possibleActions & Qt::LinkAction)
92 default_action = Qt::LinkAction;
93 else
94 default_action = Qt::IgnoreAction;
95 }
96
97#ifdef QDND_DEBUG
98 qDebug("default action : %s", dragActionsToString(default_action).latin1());
99#endif
100
101 return default_action;
102}
103
104
106{
107 m_executed_drop_action = Qt::IgnoreAction;
108 if (!m_lastView) {
109 [m_lastEvent release];
110 m_lastEvent = nil;
111 return m_executed_drop_action;
112 }
113
114 m_drag = o;
115 QMacPasteboard dragBoard(CFStringRef(NSPasteboardNameDrag), QUtiMimeConverter::HandlerScopeFlag::DnD);
116 m_drag->mimeData()->setData("application/x-qt-mime-type-name"_L1, QByteArray("dummy"));
117 dragBoard.setMimeData(m_drag->mimeData(), QMacPasteboard::LazyRequest);
118
119 if (maybeDragMultipleItems())
120 return m_executed_drop_action;
121
122 QPoint hotSpot = m_drag->hotSpot();
123 QPixmap pm = dragPixmap(m_drag, hotSpot);
124 NSImage *dragImage = [NSImage imageFromQImage:pm.toImage()];
125 Q_ASSERT(dragImage);
126
127 NSPoint event_location = [m_lastEvent locationInWindow];
128 NSWindow *theWindow = [m_lastEvent window];
129 Q_ASSERT(theWindow);
130 event_location.x -= hotSpot.x();
131 CGFloat flippedY = dragImage.size.height - hotSpot.y();
132 event_location.y -= flippedY;
133 NSSize mouseOffset_unused = NSMakeSize(0.0, 0.0);
134 NSPasteboard *pboard = [NSPasteboard pasteboardWithName:NSPasteboardNameDrag];
135
136 [theWindow dragImage:dragImage
137 at:event_location
138 offset:mouseOffset_unused
139 event:m_lastEvent
140 pasteboard:pboard
141 source:m_lastView
142 slideBack:YES];
143
144 m_drag = nullptr;
145 return m_executed_drop_action;
146}
147
148bool QCocoaDrag::maybeDragMultipleItems()
149{
150 Q_ASSERT(m_drag && m_drag->mimeData());
151 Q_ASSERT(m_executed_drop_action == Qt::IgnoreAction);
152
153 const QMacAutoReleasePool pool;
154
155 NSView *view = m_lastView ? static_cast<NSView*>(m_lastView) : m_lastEvent.window.contentView;
156 if (![view respondsToSelector:@selector(draggingSession:sourceOperationMaskForDraggingContext:)])
157 return false;
158
159 auto *sourceView = static_cast<NSView<NSDraggingSource>*>(view);
160
161 const auto &qtUrls = m_drag->mimeData()->urls();
162 NSPasteboard *dragBoard = [NSPasteboard pasteboardWithName:NSPasteboardNameDrag];
163
164 if (qtUrls.size() <= 1) {
165 // Good old -dragImage: works perfectly for this ...
166 return false;
167 }
168
169 std::vector<NSPasteboardItem *> nonUrls;
170 for (NSPasteboardItem *item in dragBoard.pasteboardItems) {
171 bool isUrl = false;
172 for (NSPasteboardType type in item.types) {
173 if ([type isEqualToString:UTTypeFileURL.identifier]) {
174 isUrl = true;
175 break;
176 }
177 }
178
179 if (!isUrl)
180 nonUrls.push_back(item);
181 }
182
183 QPoint hotSpot = m_drag->hotSpot();
184 const auto pixmap = dragPixmap(m_drag, hotSpot);
185 NSImage *dragImage = [NSImage imageFromQImage:pixmap.toImage()];
186 Q_ASSERT(dragImage);
187
188 NSMutableArray<NSDraggingItem *> *dragItems = [[[NSMutableArray alloc] init] autorelease];
189 const NSPoint itemLocation = m_drag->hotSpot().toCGPoint();
190 // 0. We start from URLs, which can be actually in a list (thus technically
191 // only ONE item in the pasteboard. The fact it's only one does not help, we are
192 // still getting an exception because of the number of items/images mismatch ...
193 // We only set the image for the first item and nil for the rest, the image already
194 // contains a combined picture for all urls we drag.
195 auto imageOrNil = dragImage;
196 for (const auto &qtUrl : qtUrls) {
197 if (!qtUrl.isValid())
198 continue;
199
200 if (qtUrl.isRelative()) // NSPasteboardWriting rejects such items.
201 continue;
202
203 NSURL *nsUrl = qtUrl.toNSURL();
204 auto *newItem = [[[NSDraggingItem alloc] initWithPasteboardWriter:nsUrl] autorelease];
205 const NSRect itemFrame = NSMakeRect(itemLocation.x, itemLocation.y,
206 dragImage.size.width,
207 dragImage.size.height);
208
209 [newItem setDraggingFrame:itemFrame contents:imageOrNil];
210 imageOrNil = nil;
211 [dragItems addObject:newItem];
212 }
213 // 1. Repeat for non-url items, if any:
214 for (auto *pbItem : nonUrls) {
215 auto *newItem = [[[NSDraggingItem alloc] initWithPasteboardWriter:pbItem] autorelease];
216 const NSRect itemFrame = NSMakeRect(itemLocation.x, itemLocation.y,
217 dragImage.size.width,
218 dragImage.size.height);
219 [newItem setDraggingFrame:itemFrame contents:imageOrNil];
220 [dragItems addObject:newItem];
221 }
222
223 [sourceView beginDraggingSessionWithItems:dragItems event:m_lastEvent source:sourceView];
224 QEventLoop eventLoop;
225 QScopedValueRollback updateGuard(m_internalDragLoop, &eventLoop);
226 eventLoop.exec();
227 return true;
228}
229
230void QCocoaDrag::setAcceptedAction(Qt::DropAction act)
231{
232 m_executed_drop_action = act;
233}
234
236{
237 if (m_internalDragLoop) {
238 Q_ASSERT(m_internalDragLoop->isRunning());
239 m_internalDragLoop->exit();
240 }
241}
242
243
244QPixmap QCocoaDrag::dragPixmap(QDrag *drag, QPoint &hotSpot) const
245{
246 const QMimeData* data = drag->mimeData();
247 QPixmap pm = drag->pixmap();
248
249 if (pm.isNull()) {
250 QFont f(qApp->font());
251 f.setPointSize(12);
252 QFontMetrics fm(f);
253
254 if (data->hasImage()) {
255 QImage img = data->imageData().value<QImage>();
256 if (!img.isNull()) {
257 pm = QPixmap::fromImage(std::move(img)).scaledToWidth(dragImageMaxChars *fm.averageCharWidth());
258 }
259 }
260
261 if (pm.isNull() && (data->hasText() || data->hasUrls()) ) {
262 QString s = data->hasText() ? data->text() : data->urls().constFirst().toString();
263 if (s.length() > dragImageMaxChars)
264 s = s.left(dragImageMaxChars -3) + QChar(0x2026);
265 if (!s.isEmpty()) {
266 const int width = fm.horizontalAdvance(s);
267 const int height = fm.height();
268 if (width > 0 && height > 0) {
269 qreal dpr = 1.0;
270 QWindow *window = qobject_cast<QWindow *>(drag->source());
271 if (!window && drag->source()->metaObject()->indexOfMethod("_q_closestWindowHandle()") != -1) {
272 QMetaObject::invokeMethod(drag->source(), "_q_closestWindowHandle",
273 Q_RETURN_ARG(QWindow *, window));
274 }
275 if (!window)
276 window = qApp->focusWindow();
277
278 if (window)
279 dpr = window->devicePixelRatio();
280
281 pm = QPixmap(width * dpr, height * dpr);
282 pm.setDevicePixelRatio(dpr);
283 QPainter p(&pm);
284 p.fillRect(0, 0, pm.width(), pm.height(), Qt::color0);
285 p.setPen(Qt::color1);
286 p.setFont(f);
287 p.drawText(0, fm.ascent(), s);
288 p.end();
289 hotSpot = QPoint(pm.width() / 2, pm.height() / 2);
290 }
291 }
292 }
293 }
294
295 if (pm.isNull())
296 pm = defaultPixmap();
297
298 return pm;
299}
300
301QCocoaDropData::QCocoaDropData(NSPasteboard *pasteboard)
302{
303 dropPasteboard = reinterpret_cast<CFStringRef>(const_cast<const NSString *>([pasteboard name]));
304 CFRetain(dropPasteboard);
305}
306
308{
309 CFRelease(dropPasteboard);
310}
311
313{
314 QStringList formats;
315 PasteboardRef board;
316 if (PasteboardCreate(dropPasteboard, &board) != noErr) {
317 qDebug("DnD: Cannot get PasteBoard!");
318 return formats;
319 }
320 formats = QMacPasteboard(board, QUtiMimeConverter::HandlerScopeFlag::DnD).formats();
321 return formats;
322}
323
324QVariant QCocoaDropData::retrieveData_sys(const QString &mimeType, QMetaType) const
325{
326 QVariant data;
327 PasteboardRef board;
328 if (PasteboardCreate(dropPasteboard, &board) != noErr) {
329 qDebug("DnD: Cannot get PasteBoard!");
330 return data;
331 }
332 data = QMacPasteboard(board, QUtiMimeConverter::HandlerScopeFlag::DnD).retrieveData(mimeType);
333 CFRelease(board);
334 return data;
335}
336
337bool QCocoaDropData::hasFormat_sys(const QString &mimeType) const
338{
339 bool has = false;
340 PasteboardRef board;
341 if (PasteboardCreate(dropPasteboard, &board) != noErr) {
342 qDebug("DnD: Cannot get PasteBoard!");
343 return has;
344 }
345 has = QMacPasteboard(board, QUtiMimeConverter::HandlerScopeFlag::DnD).hasFormat(mimeType);
346 CFRelease(board);
347 return has;
348}
349
350
351QT_END_NAMESPACE
Qt::DropAction defaultAction(Qt::DropActions possibleActions, Qt::KeyboardModifiers modifiers) const override
Definition qcocoadrag.mm:58
Qt::DropAction drag(QDrag *m_drag) override
void setAcceptedAction(Qt::DropAction act)
QMimeData * dragMimeData()
Definition qcocoadrag.mm:50
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
void setMimeData(QMimeData *mime, DataRequestType dataRequestType=EagerRequest)
static const int dragImageMaxChars
Definition qcocoadrag.mm:28