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