blob: 650612e7ffa6a9ef2ed26bb18fbf2ba41b8368fa [file] [log] [blame]
/****************************************************************************
**
** Copyright (C) 2018 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$
**
****************************************************************************/
// This file is included from qnsview.mm, and only used to organize the code
@implementation QNSView (Dragging)
-(void)registerDragTypes
{
QMacAutoReleasePool pool;
NSString * const mimeTypeGeneric = @"com.trolltech.qt.MimeTypeName";
NSMutableArray<NSString *> *supportedTypes = [NSMutableArray<NSString *> arrayWithArray:@[
NSColorPboardType,
NSFilenamesPboardType, NSStringPboardType,
NSFilenamesPboardType, NSPostScriptPboardType, NSTIFFPboardType,
NSRTFPboardType, NSTabularTextPboardType, NSFontPboardType,
NSRulerPboardType, NSFileContentsPboardType, NSColorPboardType,
NSRTFDPboardType, NSHTMLPboardType,
NSURLPboardType, NSPDFPboardType, NSVCardPboardType,
NSFilesPromisePboardType, NSInkTextPboardType,
NSMultipleTextSelectionPboardType, mimeTypeGeneric]];
// Add custom types supported by the application
for (const QString &customType : qt_mac_enabledDraggedTypes())
[supportedTypes addObject:customType.toNSString()];
[self registerForDraggedTypes:supportedTypes];
}
static QWindow *findEventTargetWindow(QWindow *candidate)
{
while (candidate) {
if (!(candidate->flags() & Qt::WindowTransparentForInput))
return candidate;
candidate = candidate->parent();
}
return candidate;
}
static QPoint mapWindowCoordinates(QWindow *source, QWindow *target, QPoint point)
{
return target->mapFromGlobal(source->mapToGlobal(point));
}
- (NSDragOperation)draggingSession:(NSDraggingSession *)session sourceOperationMaskForDraggingContext:(NSDraggingContext)context
{
Q_UNUSED(session);
Q_UNUSED(context);
QCocoaDrag* nativeDrag = QCocoaIntegration::instance()->drag();
return qt_mac_mapDropActions(nativeDrag->currentDrag()->supportedActions());
}
- (BOOL)ignoreModifierKeysForDraggingSession:(NSDraggingSession *)session
{
Q_UNUSED(session);
// According to the "Dragging Sources" chapter on Cocoa DnD Programming
// (https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/DragandDrop/Concepts/dragsource.html),
// if the control, option, or command key is pressed, the source’s
// operation mask is filtered to only contain a reduced set of operations.
//
// Since Qt already takes care of tracking the keyboard modifiers, we
// don't need (or want) Cocoa to filter anything. Instead, we'll let
// the application do the actual filtering.
return YES;
}
- (BOOL)wantsPeriodicDraggingUpdates
{
// From the documentation:
//
// "If the destination returns NO, these messages are sent only when the mouse moves
// or a modifier flag changes. Otherwise the destination gets the default behavior,
// where it receives periodic dragging-updated messages even if nothing changes."
//
// We do not want these constant drag update events while mouse is stationary,
// since we do all animations (autoscroll) with timers.
return NO;
}
- (BOOL)wantsPeriodicDraggingUpdates:(void *)dummy
{
// This method never gets called. It's a workaround for Apple's
// bug: they first respondsToSelector : @selector(wantsPeriodicDraggingUpdates:)
// (note ':') and then call -wantsPeriodicDraggingUpdate (without colon).
// So, let's make them happy.
Q_UNUSED(dummy);
return NO;
}
- (void)updateCursorFromDragResponse:(QPlatformDragQtResponse)response drag:(QCocoaDrag *)drag
{
const QPixmap pixmapCursor = drag->currentDrag()->dragCursor(response.acceptedAction());
NSCursor *nativeCursor = nil;
if (pixmapCursor.isNull()) {
switch (response.acceptedAction()) {
case Qt::CopyAction:
nativeCursor = [NSCursor dragCopyCursor];
break;
case Qt::LinkAction:
nativeCursor = [NSCursor dragLinkCursor];
break;
case Qt::IgnoreAction:
// Uncomment the next lines if forbidden cursor is wanted on undroppable targets.
/*nativeCursor = [NSCursor operationNotAllowedCursor];
break;*/
case Qt::MoveAction:
default:
nativeCursor = [NSCursor arrowCursor];
break;
}
} else {
NSImage *nsimage = qt_mac_create_nsimage(pixmapCursor);
nsimage.size = NSSizeFromCGSize((pixmapCursor.size() / pixmapCursor.devicePixelRatioF()).toCGSize());
nativeCursor = [[NSCursor alloc] initWithImage:nsimage hotSpot:NSZeroPoint];
[nsimage release];
}
// Change the cursor
[nativeCursor set];
// Make sure the cursor is updated correctly if the mouse does not move and window is under cursor
// by creating a fake move event, unless on 10.14 and later where doing so will trigger a security
// warning dialog. FIXME: Find a way to update the cursor without fake mouse events.
if (QOperatingSystemVersion::current() >= QOperatingSystemVersion::MacOSMojave)
return;
if (m_updatingDrag)
return;
QCFType<CGEventRef> moveEvent = CGEventCreateMouseEvent(
nullptr, kCGEventMouseMoved, QCursor::pos().toCGPoint(),
kCGMouseButtonLeft // ignored
);
CGEventPost(kCGHIDEventTap, moveEvent);
}
- (NSDragOperation)draggingEntered:(id<NSDraggingInfo>)sender
{
return [self handleDrag:(QEvent::DragEnter) sender:sender];
}
- (NSDragOperation)draggingUpdated:(id<NSDraggingInfo>)sender
{
QScopedValueRollback<bool> rollback(m_updatingDrag, true);
return [self handleDrag:(QEvent::DragMove) sender:sender];
}
// Sends drag update to Qt, return the action
- (NSDragOperation)handleDrag:(QEvent::Type)dragType sender:(id<NSDraggingInfo>)sender
{
if (!m_platformWindow)
return NSDragOperationNone;
QPoint windowPoint = QPointF::fromCGPoint([self convertPoint:sender.draggingLocation fromView:nil]).toPoint();
Qt::DropActions qtAllowed = qt_mac_mapNSDragOperations(sender.draggingSourceOperationMask);
QWindow *target = findEventTargetWindow(m_platformWindow->window());
if (!target)
return NSDragOperationNone;
const auto modifiers = [QNSView convertKeyModifiers:NSApp.currentEvent.modifierFlags];
const auto buttons = currentlyPressedMouseButtons();
const auto point = mapWindowCoordinates(m_platformWindow->window(), target, windowPoint);
if (dragType == QEvent::DragEnter)
qCDebug(lcQpaMouse) << dragType << self << "at" << windowPoint;
else
qCDebug(lcQpaMouse) << dragType << "at" << windowPoint << "with" << buttons;
QPlatformDragQtResponse response(false, Qt::IgnoreAction, QRect());
QCocoaDrag* nativeDrag = QCocoaIntegration::instance()->drag();
if (nativeDrag->currentDrag()) {
// The drag was started from within the application
response = QWindowSystemInterface::handleDrag(target, nativeDrag->dragMimeData(),
point, qtAllowed, buttons, modifiers);
[self updateCursorFromDragResponse:response drag:nativeDrag];
} else {
QCocoaDropData mimeData(sender.draggingPasteboard);
response = QWindowSystemInterface::handleDrag(target, &mimeData,
point, qtAllowed, buttons, modifiers);
}
return qt_mac_mapDropAction(response.acceptedAction());
}
- (void)draggingExited:(id<NSDraggingInfo>)sender
{
if (!m_platformWindow)
return;
QWindow *target = findEventTargetWindow(m_platformWindow->window());
if (!target)
return;
QPoint windowPoint = QPointF::fromCGPoint([self convertPoint:sender.draggingLocation fromView:nil]).toPoint();
qCDebug(lcQpaMouse) << QEvent::DragLeave << self << "at" << windowPoint;
// Send 0 mime data to indicate drag exit
QWindowSystemInterface::handleDrag(target, nullptr,
mapWindowCoordinates(m_platformWindow->window(), target, windowPoint),
Qt::IgnoreAction, Qt::NoButton, Qt::NoModifier);
}
// Called on drop, send the drop to Qt and return if it was accepted
- (BOOL)performDragOperation:(id<NSDraggingInfo>)sender
{
if (!m_platformWindow)
return false;
QWindow *target = findEventTargetWindow(m_platformWindow->window());
if (!target)
return false;
QPoint windowPoint = QPointF::fromCGPoint([self convertPoint:sender.draggingLocation fromView:nil]).toPoint();
Qt::DropActions qtAllowed = qt_mac_mapNSDragOperations(sender.draggingSourceOperationMask);
QPlatformDropQtResponse response(false, Qt::IgnoreAction);
QCocoaDrag* nativeDrag = QCocoaIntegration::instance()->drag();
const auto modifiers = [QNSView convertKeyModifiers:NSApp.currentEvent.modifierFlags];
const auto buttons = currentlyPressedMouseButtons();
const auto point = mapWindowCoordinates(m_platformWindow->window(), target, windowPoint);
qCDebug(lcQpaMouse) << QEvent::Drop << "at" << windowPoint << "with" << buttons;
if (nativeDrag->currentDrag()) {
// The drag was started from within the application
response = QWindowSystemInterface::handleDrop(target, nativeDrag->dragMimeData(),
point, qtAllowed, buttons, modifiers);
nativeDrag->setAcceptedAction(response.acceptedAction());
} else {
QCocoaDropData mimeData(sender.draggingPasteboard);
response = QWindowSystemInterface::handleDrop(target, &mimeData,
point, qtAllowed, buttons, modifiers);
}
return response.isAccepted();
}
- (void)draggingSession:(NSDraggingSession *)session endedAtPoint:(NSPoint)screenPoint operation:(NSDragOperation)operation
{
Q_UNUSED(session);
Q_UNUSED(screenPoint);
Q_UNUSED(operation);
if (!m_platformWindow)
return;
QWindow *target = findEventTargetWindow(m_platformWindow->window());
if (!target)
return;
QCocoaIntegration::instance()->drag();
// Qt starts drag-and-drop on a mouse button press event. Cococa in
// this case won't send the matching release event, so we have to
// synthesize it here.
m_buttons = currentlyPressedMouseButtons();
const auto modifiers = [QNSView convertKeyModifiers:NSApp.currentEvent.modifierFlags];
NSPoint windowPoint = [self.window convertRectFromScreen:NSMakeRect(screenPoint.x, screenPoint.y, 1, 1)].origin;
NSPoint nsViewPoint = [self convertPoint: windowPoint fromView: nil];
QPoint qtWindowPoint(nsViewPoint.x, nsViewPoint.y);
QPoint qtScreenPoint = QCocoaScreen::mapFromNative(screenPoint).toPoint();
QWindowSystemInterface::handleMouseEvent(
target,
mapWindowCoordinates(m_platformWindow->window(), target, qtWindowPoint),
qtScreenPoint,
m_buttons,
Qt::NoButton,
QEvent::MouseButtonRelease,
modifiers);
qCDebug(lcQpaMouse) << "Drag session" << session << "ended, with" << m_buttons;
}
@end