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
qnsview_dragging.mm
Go to the documentation of this file.
1// Copyright (C) 2018 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// This file is included from qnsview.mm, and only used to organize the code
6
7#include <QtGui/qdrag.h>
8
9@implementation QNSView (Dragging)
10
11-(void)registerDragTypes
12{
13 QMacAutoReleasePool pool;
14
15 NSString * const mimeTypeGeneric = @"com.trolltech.qt.MimeTypeName";
16 NSMutableArray<NSString *> *supportedTypes = [NSMutableArray<NSString *> arrayWithArray:@[
17 NSPasteboardTypeColor, NSPasteboardTypeString,
18 NSPasteboardTypeFileURL, @"com.adobe.encapsulated-postscript", NSPasteboardTypeTIFF,
19 NSPasteboardTypeRTF, NSPasteboardTypeTabularText, NSPasteboardTypeFont,
20 NSPasteboardTypeRuler, NSFileContentsPboardType,
21 NSPasteboardTypeRTFD , NSPasteboardTypeHTML,
22 NSPasteboardTypeURL, NSPasteboardTypePDF, UTTypeVCard.identifier,
23 (NSString *)kPasteboardTypeFileURLPromise,
24 NSPasteboardTypeMultipleTextSelection, mimeTypeGeneric]];
25
26 // Add custom types supported by the application
27 for (const QString &customType : QMacMimeRegistry::enabledDraggedTypes())
28 [supportedTypes addObject:customType.toNSString()];
29
30 [self registerForDraggedTypes:supportedTypes];
31}
32
33static QWindow *findEventTargetWindow(QWindow *candidate)
34{
35 while (candidate) {
36 if (!(candidate->flags() & Qt::WindowTransparentForInput))
37 return candidate;
38 candidate = candidate->parent();
39 }
40 return candidate;
41}
42
43static QPoint mapWindowCoordinates(QWindow *source, QWindow *target, QPoint point)
44{
45 return target->mapFromGlobal(source->mapToGlobal(point));
46}
47
48- (NSDragOperation)draggingSession:(NSDraggingSession *)session sourceOperationMaskForDraggingContext:(NSDraggingContext)context
49{
50 Q_UNUSED(session);
51 Q_UNUSED(context);
52
53 QCocoaDrag* nativeDrag = QCocoaIntegration::instance()->drag();
54 return qt_mac_mapDropActions(nativeDrag->currentDrag()->supportedActions());
55}
56
57- (BOOL)ignoreModifierKeysForDraggingSession:(NSDraggingSession *)session
58{
59 Q_UNUSED(session);
60 // According to the "Dragging Sources" chapter on Cocoa DnD Programming
61 // (https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/DragandDrop/Concepts/dragsource.html),
62 // if the control, option, or command key is pressed, the source’s
63 // operation mask is filtered to only contain a reduced set of operations.
64 //
65 // Since Qt already takes care of tracking the keyboard modifiers, we
66 // don't need (or want) Cocoa to filter anything. Instead, we'll let
67 // the application do the actual filtering.
68 return YES;
69}
70
71- (BOOL)wantsPeriodicDraggingUpdates
72{
73 // From the documentation:
74 //
75 // "If the destination returns NO, these messages are sent only when the mouse moves
76 // or a modifier flag changes. Otherwise the destination gets the default behavior,
77 // where it receives periodic dragging-updated messages even if nothing changes."
78 //
79 // We do not want these constant drag update events while mouse is stationary,
80 // since we do all animations (autoscroll) with timers.
81 return NO;
82}
83
84
85- (BOOL)wantsPeriodicDraggingUpdates:(void *)dummy
86{
87 // This method never gets called. It's a workaround for Apple's
88 // bug: they first respondsToSelector : @selector(wantsPeriodicDraggingUpdates:)
89 // (note ':') and then call -wantsPeriodicDraggingUpdate (without colon).
90 // So, let's make them happy.
91 Q_UNUSED(dummy);
92
93 return NO;
94}
95
96- (void)updateCursorFromDragResponse:(QPlatformDragQtResponse)response drag:(QCocoaDrag *)drag
97{
98 const QPixmap pixmapCursor = drag->currentDrag()->dragCursor(response.acceptedAction());
99 NSCursor *nativeCursor = nil;
100
101 if (pixmapCursor.isNull()) {
102 switch (response.acceptedAction()) {
103 case Qt::CopyAction:
104 nativeCursor = [NSCursor dragCopyCursor];
105 break;
106 case Qt::LinkAction:
107 nativeCursor = [NSCursor dragLinkCursor];
108 break;
109 case Qt::IgnoreAction:
110 // Uncomment the next lines if forbidden cursor is wanted on undroppable targets.
111 /*nativeCursor = [NSCursor operationNotAllowedCursor];
112 break;*/
113 case Qt::MoveAction:
114 default:
115 nativeCursor = [NSCursor arrowCursor];
116 break;
117 }
118 } else {
119 auto *nsimage = [NSImage imageFromQImage:pixmapCursor.toImage()];
120 nativeCursor = [[NSCursor alloc] initWithImage:nsimage hotSpot:NSZeroPoint];
121 }
122
123 // Change the cursor
124 [nativeCursor set];
125
126 // Make sure the cursor is updated correctly if the mouse does not move and window is under cursor
127 // by creating a fake move event, unless on 10.14 and later where doing so will trigger a security
128 // warning dialog. FIXME: Find a way to update the cursor without fake mouse events.
129 if (QOperatingSystemVersion::current() >= QOperatingSystemVersion::MacOSMojave)
130 return;
131
132 if (m_updatingDrag)
133 return;
134
135 QCFType<CGEventRef> moveEvent = CGEventCreateMouseEvent(
136 nullptr, kCGEventMouseMoved, QCursor::pos().toCGPoint(),
137 kCGMouseButtonLeft // ignored
138 );
139 CGEventPost(kCGHIDEventTap, moveEvent);
140}
141
142- (NSDragOperation)draggingEntered:(id<NSDraggingInfo>)sender
143{
144 return [self handleDrag:(QEvent::DragEnter) sender:sender];
145}
146
147- (NSDragOperation)draggingUpdated:(id<NSDraggingInfo>)sender
148{
149 QScopedValueRollback<bool> rollback(m_updatingDrag, true);
150 return [self handleDrag:(QEvent::DragMove) sender:sender];
151}
152
153// Sends drag update to Qt, return the action
154- (NSDragOperation)handleDrag:(QEvent::Type)dragType sender:(id<NSDraggingInfo>)sender
155{
156 if (!m_platformWindow)
157 return NSDragOperationNone;
158
159 QPoint windowPoint = QPointF::fromCGPoint([self convertPoint:sender.draggingLocation fromView:nil]).toPoint();
160
161 Qt::DropActions qtAllowed = qt_mac_mapNSDragOperations(sender.draggingSourceOperationMask);
162
163 QWindow *target = findEventTargetWindow(m_platformWindow->window());
164 if (!target)
165 return NSDragOperationNone;
166
167 const auto modifiers = QAppleKeyMapper::fromCocoaModifiers(NSApp.currentEvent.modifierFlags);
168 const auto buttons = currentlyPressedMouseButtons();
169 const auto point = mapWindowCoordinates(m_platformWindow->window(), target, windowPoint);
170
171 if (dragType == QEvent::DragEnter)
172 qCDebug(lcQpaMouse) << dragType << self << "at" << windowPoint;
173 else
174 qCDebug(lcQpaMouse) << dragType << "at" << windowPoint << "with" << buttons;
175
176 QPlatformDragQtResponse response(false, Qt::IgnoreAction, QRect());
177 QCocoaDrag* nativeDrag = QCocoaIntegration::instance()->drag();
178 if (nativeDrag->currentDrag()) {
179 // The drag was started from within the application
180 response = QWindowSystemInterface::handleDrag(target, nativeDrag->dragMimeData(),
181 point, qtAllowed, buttons, modifiers);
182 [self updateCursorFromDragResponse:response drag:nativeDrag];
183 } else {
184 QCocoaDropData mimeData(sender.draggingPasteboard);
185 response = QWindowSystemInterface::handleDrag(target, &mimeData,
186 point, qtAllowed, buttons, modifiers);
187 }
188
189 return qt_mac_mapDropAction(response.acceptedAction());
190}
191
192- (void)draggingExited:(id<NSDraggingInfo>)sender
193{
194 if (!m_platformWindow)
195 return;
196
197 QWindow *target = findEventTargetWindow(m_platformWindow->window());
198 if (!target)
199 return;
200
201 auto *nativeDrag = QCocoaIntegration::instance()->drag();
202 Q_ASSERT(nativeDrag);
203 nativeDrag->exitDragLoop();
204
205 QPoint windowPoint = QPointF::fromCGPoint([self convertPoint:sender.draggingLocation fromView:nil]).toPoint();
206
207 qCDebug(lcQpaMouse) << QEvent::DragLeave << self << "at" << windowPoint;
208
209 // Send 0 mime data to indicate drag exit
210 QWindowSystemInterface::handleDrag(target, nullptr,
211 mapWindowCoordinates(m_platformWindow->window(), target, windowPoint),
212 Qt::IgnoreAction, Qt::NoButton, Qt::NoModifier);
213}
214
215// Called on drop, send the drop to Qt and return if it was accepted
216- (BOOL)performDragOperation:(id<NSDraggingInfo>)sender
217{
218 if (!m_platformWindow)
219 return false;
220
221 QWindow *target = findEventTargetWindow(m_platformWindow->window());
222 if (!target)
223 return false;
224
225 QPoint windowPoint = QPointF::fromCGPoint([self convertPoint:sender.draggingLocation fromView:nil]).toPoint();
226
227 Qt::DropActions qtAllowed = qt_mac_mapNSDragOperations(sender.draggingSourceOperationMask);
228
229 QPlatformDropQtResponse response(false, Qt::IgnoreAction);
230 QCocoaDrag* nativeDrag = QCocoaIntegration::instance()->drag();
231 const auto modifiers = QAppleKeyMapper::fromCocoaModifiers(NSApp.currentEvent.modifierFlags);
232 const auto buttons = currentlyPressedMouseButtons();
233 const auto point = mapWindowCoordinates(m_platformWindow->window(), target, windowPoint);
234
235 qCDebug(lcQpaMouse) << QEvent::Drop << "at" << windowPoint << "with" << buttons;
236
237 if (nativeDrag->currentDrag()) {
238 // The drag was started from within the application
239 response = QWindowSystemInterface::handleDrop(target, nativeDrag->dragMimeData(),
240 point, qtAllowed, buttons, modifiers);
241 nativeDrag->setAcceptedAction(response.acceptedAction());
242 } else {
243 QCocoaDropData mimeData(sender.draggingPasteboard);
244 response = QWindowSystemInterface::handleDrop(target, &mimeData,
245 point, qtAllowed, buttons, modifiers);
246 }
247 return response.isAccepted();
248}
249
250- (void)draggingSession:(NSDraggingSession *)session endedAtPoint:(NSPoint)screenPoint operation:(NSDragOperation)operation
251{
252 Q_UNUSED(session);
253 Q_UNUSED(screenPoint);
254 Q_UNUSED(operation);
255
256 if (!m_platformWindow)
257 return;
258
259 QWindow *target = findEventTargetWindow(m_platformWindow->window());
260 if (!target)
261 return;
262
263 QCocoaDrag* nativeDrag = QCocoaIntegration::instance()->drag();
264 Q_ASSERT(nativeDrag);
265 nativeDrag->exitDragLoop();
266 // for internal drag'n'drop, don't override the action the drop event accepted
267 if (!nativeDrag->currentDrag())
268 nativeDrag->setAcceptedAction(qt_mac_mapNSDragOperation(operation));
269
270 // Qt starts drag-and-drop on a mouse button press event. Cococa in
271 // this case won't send the matching release event, so we have to
272 // synthesize it here.
273 m_buttons = currentlyPressedMouseButtons();
274 const auto modifiers = QAppleKeyMapper::fromCocoaModifiers(NSApp.currentEvent.modifierFlags);
275
276 NSPoint windowPoint = [self.window convertRectFromScreen:NSMakeRect(screenPoint.x, screenPoint.y, 1, 1)].origin;
277 NSPoint nsViewPoint = [self convertPoint: windowPoint fromView: nil];
278
279 QPoint qtWindowPoint(nsViewPoint.x, nsViewPoint.y);
280 QPoint qtScreenPoint = QCocoaScreen::mapFromNative(screenPoint).toPoint();
281
282 QWindowSystemInterface::handleMouseEvent(
283 target,
284 mapWindowCoordinates(m_platformWindow->window(), target, qtWindowPoint),
285 qtScreenPoint,
286 m_buttons,
287 Qt::NoButton,
288 QEvent::MouseButtonRelease,
289 modifiers);
290
291 qCDebug(lcQpaMouse) << "Drag session" << session << "ended, with" << m_buttons;
292}
293
294@end