diff --git a/src/plugins/platforms/cocoa/qnsview.h b/src/plugins/platforms/cocoa/qnsview.h index a0710be564..00bf3627f3 100644 --- a/src/plugins/platforms/cocoa/qnsview.h +++ b/src/plugins/platforms/cocoa/qnsview.h @@ -52,7 +52,7 @@ QT_END_NAMESPACE Q_FORWARD_DECLARE_OBJC_CLASS(QT_MANGLE_NAMESPACE(QNSViewMouseMoveHelper)); -@interface QT_MANGLE_NAMESPACE(QNSView) : NSView +@interface QT_MANGLE_NAMESPACE(QNSView) : NSView @property (nonatomic, retain) NSCursor *cursor; @@ -63,18 +63,22 @@ Q_FORWARD_DECLARE_OBJC_CLASS(QT_MANGLE_NAMESPACE(QNSViewMouseMoveHelper)); #endif - (void)convertFromScreen:(NSPoint)mouseLocation toWindowPoint:(QPointF *)qtWindowPoint andScreenPoint:(QPointF *)qtScreenPoint; - - (void)requestUpdate; -- (void)mouseMovedImpl:(NSEvent *)theEvent; -- (void)mouseEnteredImpl:(NSEvent *)theEvent; -- (void)mouseExitedImpl:(NSEvent *)theEvent; +@end + +@interface QT_MANGLE_NAMESPACE(QNSView) (MouseAPI) - (void)handleFrameStrutMouseEvent:(NSEvent *)theEvent; - (void)resetMouseButtons; +@end +@interface QT_MANGLE_NAMESPACE(QNSView) (KeysAPI) + (Qt::KeyboardModifiers)convertKeyModifiers:(ulong)modifierFlags; -- (void)cancelComposingText; +@end +@interface QT_MANGLE_NAMESPACE(QNSView) (ComplexTextAPI) +- (void)unmarkText; +- (void)cancelComposingText; @end @interface QT_MANGLE_NAMESPACE(QNSView) (QtExtras) diff --git a/src/plugins/platforms/cocoa/qnsview.mm b/src/plugins/platforms/cocoa/qnsview.mm index c2408f729b..460d67c13b 100644 --- a/src/plugins/platforms/cocoa/qnsview.mm +++ b/src/plugins/platforms/cocoa/qnsview.mm @@ -69,60 +69,44 @@ #include #endif -Q_LOGGING_CATEGORY(lcQpaTouch, "qt.qpa.input.touch") -#ifndef QT_NO_GESTURES -Q_LOGGING_CATEGORY(lcQpaGestures, "qt.qpa.input.gestures") -#endif -Q_LOGGING_CATEGORY(lcQpaTablet, "qt.qpa.input.tablet") +// Private interface +@interface QT_MANGLE_NAMESPACE(QNSView) () +- (BOOL)isTransparentForUserInput; +@end @interface QT_MANGLE_NAMESPACE(QNSViewMouseMoveHelper) : NSObject - - (instancetype)initWithView:(QNSView *)theView; - - (void)mouseMoved:(NSEvent *)theEvent; - (void)mouseEntered:(NSEvent *)theEvent; - (void)mouseExited:(NSEvent *)theEvent; - (void)cursorUpdate:(NSEvent *)theEvent; - @end -@implementation QT_MANGLE_NAMESPACE(QNSViewMouseMoveHelper) { - QNSView *view; -} - -- (instancetype)initWithView:(QNSView *)theView -{ - if ((self = [super init])) - view = theView; - - return self; -} - -- (void)mouseMoved:(NSEvent *)theEvent -{ - [view mouseMovedImpl:theEvent]; -} - -- (void)mouseEntered:(NSEvent *)theEvent -{ - [view mouseEnteredImpl:theEvent]; -} - -- (void)mouseExited:(NSEvent *)theEvent -{ - [view mouseExitedImpl:theEvent]; -} - -- (void)cursorUpdate:(NSEvent *)theEvent -{ - [view cursorUpdate:theEvent]; -} - +@interface QT_MANGLE_NAMESPACE(QNSView) (Mouse) +- (NSPoint)screenMousePoint:(NSEvent *)theEvent; +- (void)mouseMovedImpl:(NSEvent *)theEvent; +- (void)mouseEnteredImpl:(NSEvent *)theEvent; +- (void)mouseExitedImpl:(NSEvent *)theEvent; @end -// Private interface -@interface QT_MANGLE_NAMESPACE(QNSView) () -- (BOOL)isTransparentForUserInput; +@interface QT_MANGLE_NAMESPACE(QNSView) (Touch) +@end + +@interface QT_MANGLE_NAMESPACE(QNSView) (Tablet) +- (bool)handleTabletEvent:(NSEvent *)theEvent; +@end + +@interface QT_MANGLE_NAMESPACE(QNSView) (Gestures) +@end + +@interface QT_MANGLE_NAMESPACE(QNSView) (Dragging) +-(void)registerDragTypes; +@end + +@interface QT_MANGLE_NAMESPACE(QNSView) (Keys) +@end + +@interface QT_MANGLE_NAMESPACE(QNSView) (ComplexText) - (void)textInputContextKeyboardSelectionDidChangeNotification:(NSNotification *)textInputContextKeyboardSelectionDidChangeNotification; @end @@ -445,16 +429,6 @@ Q_LOGGING_CATEGORY(lcQpaTablet, "qt.qpa.input.tablet") return YES; } -- (BOOL)acceptsFirstMouse:(NSEvent *)theEvent -{ - Q_UNUSED(theEvent) - if (!m_platformWindow) - return NO; - if ([self isTransparentForUserInput]) - return NO; - return YES; -} - - (NSView *)hitTest:(NSPoint)aPoint { NSView *candidate = [super hitTest:aPoint]; @@ -494,1612 +468,18 @@ Q_LOGGING_CATEGORY(lcQpaTablet, "qt.qpa.input.tablet") *qtScreenPoint = QCocoaScreen::mapFromNative(mouseLocation); } -- (void)resetMouseButtons -{ - m_buttons = Qt::NoButton; - m_frameStrutButtons = Qt::NoButton; -} - -- (NSPoint)screenMousePoint:(NSEvent *)theEvent -{ - NSPoint screenPoint; - if (theEvent) { - NSPoint windowPoint = [theEvent locationInWindow]; - NSRect screenRect = [[theEvent window] convertRectToScreen:NSMakeRect(windowPoint.x, windowPoint.y, 1, 1)]; - screenPoint = screenRect.origin; - } else { - screenPoint = [NSEvent mouseLocation]; - } - return screenPoint; -} - -- (void)handleMouseEvent:(NSEvent *)theEvent -{ - if (!m_platformWindow) - return; - -#ifndef QT_NO_TABLETEVENT - // Tablet events may come in via the mouse event handlers, - // check if this is a valid tablet event first. - if ([self handleTabletEvent: theEvent]) - return; -#endif - - QPointF qtWindowPoint; - QPointF qtScreenPoint; - QNSView *targetView = self; - if (!targetView.platformWindow) - return; - - // Popups implicitly grap mouse events; forward to the active popup if there is one - if (QCocoaWindow *popup = QCocoaIntegration::instance()->activePopupWindow()) { - // Tooltips must be transparent for mouse events - // The bug reference is QTBUG-46379 - if (!popup->window()->flags().testFlag(Qt::ToolTip)) { - if (QNSView *popupView = qnsview_cast(popup->view())) - targetView = popupView; - } - } - - [targetView convertFromScreen:[self screenMousePoint:theEvent] toWindowPoint:&qtWindowPoint andScreenPoint:&qtScreenPoint]; - ulong timestamp = [theEvent timestamp] * 1000; - - QCocoaDrag* nativeDrag = QCocoaIntegration::instance()->drag(); - nativeDrag->setLastMouseEvent(theEvent, self); - - Qt::KeyboardModifiers keyboardModifiers = [QNSView convertKeyModifiers:[theEvent modifierFlags]]; - QWindowSystemInterface::handleMouseEvent(targetView->m_platformWindow->window(), timestamp, qtWindowPoint, qtScreenPoint, - m_buttons, keyboardModifiers, Qt::MouseEventNotSynthesized); -} - -- (void)handleFrameStrutMouseEvent:(NSEvent *)theEvent -{ - if (!m_platformWindow) - return; - - // get m_buttons in sync - // Don't send frme strut events if we are in the middle of a mouse drag. - if (m_buttons != Qt::NoButton) - return; - - NSEventType ty = [theEvent type]; - switch (ty) { - case NSLeftMouseDown: - m_frameStrutButtons |= Qt::LeftButton; - break; - case NSLeftMouseUp: - m_frameStrutButtons &= ~Qt::LeftButton; - break; - case NSRightMouseDown: - m_frameStrutButtons |= Qt::RightButton; - break; - case NSLeftMouseDragged: - m_frameStrutButtons |= Qt::LeftButton; - break; - case NSRightMouseDragged: - m_frameStrutButtons |= Qt::RightButton; - break; - case NSRightMouseUp: - m_frameStrutButtons &= ~Qt::RightButton; - break; - case NSOtherMouseDown: - m_frameStrutButtons |= cocoaButton2QtButton([theEvent buttonNumber]); - break; - case NSOtherMouseUp: - m_frameStrutButtons &= ~cocoaButton2QtButton([theEvent buttonNumber]); - default: - break; - } - - NSWindow *window = [self window]; - NSPoint windowPoint = [theEvent locationInWindow]; - - int windowScreenY = [window frame].origin.y + [window frame].size.height; - NSPoint windowCoord = [self convertPoint:[self frame].origin toView:nil]; - int viewScreenY = [window convertRectToScreen:NSMakeRect(windowCoord.x, windowCoord.y, 0, 0)].origin.y; - int titleBarHeight = windowScreenY - viewScreenY; - - NSPoint nsViewPoint = [self convertPoint: windowPoint fromView: nil]; - QPoint qtWindowPoint = QPoint(nsViewPoint.x, titleBarHeight + nsViewPoint.y); - NSPoint screenPoint = [window convertRectToScreen:NSMakeRect(windowPoint.x, windowPoint.y, 0, 0)].origin; - QPoint qtScreenPoint = QCocoaScreen::mapFromNative(screenPoint).toPoint(); - - ulong timestamp = [theEvent timestamp] * 1000; - QWindowSystemInterface::handleFrameStrutMouseEvent(m_platformWindow->window(), timestamp, qtWindowPoint, qtScreenPoint, m_frameStrutButtons); -} - -- (bool)handleMouseDownEvent:(NSEvent *)theEvent withButton:(int)buttonNumber -{ - if ([self isTransparentForUserInput]) - return false; - - Qt::MouseButton button = cocoaButton2QtButton(buttonNumber); - - QPointF qtWindowPoint; - QPointF qtScreenPoint; - [self convertFromScreen:[self screenMousePoint:theEvent] toWindowPoint:&qtWindowPoint andScreenPoint:&qtScreenPoint]; - Q_UNUSED(qtScreenPoint); - - // Maintain masked state for the button for use by MouseDragged and MouseUp. - QRegion mask = m_platformWindow->window()->mask(); - const bool masked = !mask.isEmpty() && !mask.contains(qtWindowPoint.toPoint()); - if (masked) - m_acceptedMouseDowns &= ~button; - else - m_acceptedMouseDowns |= button; - - // Forward masked out events to the next responder - if (masked) - return false; - - m_buttons |= button; - - [self handleMouseEvent:theEvent]; - return true; -} - -- (bool)handleMouseDraggedEvent:(NSEvent *)theEvent withButton:(int)buttonNumber -{ - if ([self isTransparentForUserInput]) - return false; - - Qt::MouseButton button = cocoaButton2QtButton(buttonNumber); - - // Forward the event to the next responder if Qt did not accept the - // corresponding mouse down for this button - if (!(m_acceptedMouseDowns & button) == button) - return false; - - [self handleMouseEvent:theEvent]; - return true; -} - -- (bool)handleMouseUpEvent:(NSEvent *)theEvent withButton:(int)buttonNumber -{ - if ([self isTransparentForUserInput]) - return false; - - Qt::MouseButton button = cocoaButton2QtButton(buttonNumber); - - // Forward the event to the next responder if Qt did not accept the - // corresponding mouse down for this button - if (!(m_acceptedMouseDowns & button) == button) - return false; - - if (m_sendUpAsRightButton && button == Qt::LeftButton) - button = Qt::RightButton; - if (button == Qt::RightButton) - m_sendUpAsRightButton = false; - - m_buttons &= ~button; - - [self handleMouseEvent:theEvent]; - return true; -} - -- (void)mouseDown:(NSEvent *)theEvent -{ - if ([self isTransparentForUserInput]) - return [super mouseDown:theEvent]; - m_sendUpAsRightButton = false; - - // Handle any active poup windows; clicking outisde them should close them - // all. Don't do anything or clicks inside one of the menus, let Cocoa - // handle that case. Note that in practice many windows of the Qt::Popup type - // will actually close themselves in this case using logic implemented in - // that particular poup type (for example context menus). However, Qt expects - // that plain popup QWindows will also be closed, so we implement the logic - // here as well. - QList *popups = QCocoaIntegration::instance()->popupWindowStack(); - if (!popups->isEmpty()) { - // Check if the click is outside all popups. - bool inside = false; - QPointF qtScreenPoint = QCocoaScreen::mapFromNative([self screenMousePoint:theEvent]); - for (QList::const_iterator it = popups->begin(); it != popups->end(); ++it) { - if ((*it)->geometry().contains(qtScreenPoint.toPoint())) { - inside = true; - break; - } - } - // Close the popups if the click was outside. - if (!inside) { - Qt::WindowType type = QCocoaIntegration::instance()->activePopupWindow()->window()->type(); - while (QCocoaWindow *popup = QCocoaIntegration::instance()->popPopupWindow()) { - QWindowSystemInterface::handleCloseEvent(popup->window()); - QWindowSystemInterface::flushWindowSystemEvents(); - } - // Consume the mouse event when closing the popup, except for tool tips - // were it's expected that the event is processed normally. - if (type != Qt::ToolTip) - return; - } - } - - QPointF qtWindowPoint; - QPointF qtScreenPoint; - [self convertFromScreen:[self screenMousePoint:theEvent] toWindowPoint:&qtWindowPoint andScreenPoint:&qtScreenPoint]; - Q_UNUSED(qtScreenPoint); - - QRegion mask = m_platformWindow->window()->mask(); - const bool masked = !mask.isEmpty() && !mask.contains(qtWindowPoint.toPoint()); - // Maintain masked state for the button for use by MouseDragged and Up. - if (masked) - m_acceptedMouseDowns &= ~Qt::LeftButton; - else - m_acceptedMouseDowns |= Qt::LeftButton; - - // Forward masked out events to the next responder - if (masked) { - [super mouseDown:theEvent]; - return; - } - - if ([self hasMarkedText]) { - [[NSTextInputContext currentInputContext] handleEvent:theEvent]; - } else { - auto ctrlOrMetaModifier = qApp->testAttribute(Qt::AA_MacDontSwapCtrlAndMeta) ? Qt::ControlModifier : Qt::MetaModifier; - if (!m_dontOverrideCtrlLMB && [QNSView convertKeyModifiers:[theEvent modifierFlags]] & ctrlOrMetaModifier) { - m_buttons |= Qt::RightButton; - m_sendUpAsRightButton = true; - } else { - m_buttons |= Qt::LeftButton; - } - [self handleMouseEvent:theEvent]; - } -} - -- (void)mouseDragged:(NSEvent *)theEvent -{ - const bool accepted = [self handleMouseDraggedEvent:theEvent withButton:[theEvent buttonNumber]]; - if (!accepted) - [super mouseDragged:theEvent]; -} - -- (void)mouseUp:(NSEvent *)theEvent -{ - const bool accepted = [self handleMouseUpEvent:theEvent withButton:[theEvent buttonNumber]]; - if (!accepted) - [super mouseUp:theEvent]; -} - -- (void)rightMouseDown:(NSEvent *)theEvent -{ - // Wacom tablet might not return the correct button number for NSEvent buttonNumber - // on right clicks. Decide here that the button is the "right" button and forward - // the button number to the mouse (and tablet) handler. - const bool accepted = [self handleMouseDownEvent:theEvent withButton:1]; - if (!accepted) - [super rightMouseDown:theEvent]; -} - -- (void)rightMouseDragged:(NSEvent *)theEvent -{ - const bool accepted = [self handleMouseDraggedEvent:theEvent withButton:1]; - if (!accepted) - [super rightMouseDragged:theEvent]; -} - -- (void)rightMouseUp:(NSEvent *)theEvent -{ - const bool accepted = [self handleMouseUpEvent:theEvent withButton:1]; - if (!accepted) - [super rightMouseUp:theEvent]; -} - -- (void)otherMouseDown:(NSEvent *)theEvent -{ - const bool accepted = [self handleMouseDownEvent:theEvent withButton:[theEvent buttonNumber]]; - if (!accepted) - [super otherMouseDown:theEvent]; -} - -- (void)otherMouseDragged:(NSEvent *)theEvent -{ - const bool accepted = [self handleMouseDraggedEvent:theEvent withButton:[theEvent buttonNumber]]; - if (!accepted) - [super otherMouseDragged:theEvent]; -} - -- (void)otherMouseUp:(NSEvent *)theEvent -{ - const bool accepted = [self handleMouseUpEvent:theEvent withButton:[theEvent buttonNumber]]; - if (!accepted) - [super otherMouseUp:theEvent]; -} - -- (void)updateTrackingAreas -{ - [super updateTrackingAreas]; - - QMacAutoReleasePool pool; - - // NSTrackingInVisibleRect keeps care of updating once the tracking is set up, so bail out early - if (m_trackingArea && [[self trackingAreas] containsObject:m_trackingArea]) - return; - - // Ideally, we shouldn't have NSTrackingMouseMoved events included below, it should - // only be turned on if mouseTracking, hover is on or a tool tip is set. - // Unfortunately, Qt will send "tooltip" events on mouse moves, so we need to - // turn it on in ALL case. That means EVERY QWindow gets to pay the cost of - // mouse moves delivered to it (Apple recommends keeping it OFF because there - // is a performance hit). So it goes. - NSUInteger trackingOptions = NSTrackingMouseEnteredAndExited | NSTrackingActiveInActiveApp - | NSTrackingInVisibleRect | NSTrackingMouseMoved | NSTrackingCursorUpdate; - [m_trackingArea release]; - m_trackingArea = [[NSTrackingArea alloc] initWithRect:[self frame] - options:trackingOptions - owner:m_mouseMoveHelper - userInfo:nil]; - [self addTrackingArea:m_trackingArea]; -} - -- (void)cursorUpdate:(NSEvent *)theEvent -{ - qCDebug(lcQpaMouse) << "[QNSView cursorUpdate:]" << self.cursor; - - // Note: We do not get this callback when moving from a subview that - // uses the legacy cursorRect API, so the cursor is reset to the arrow - // cursor. See rdar://34183708 - - if (self.cursor) - [self.cursor set]; - else - [super cursorUpdate:theEvent]; -} - -- (void)mouseMovedImpl:(NSEvent *)theEvent -{ - if (!m_platformWindow) - return; - - if ([self isTransparentForUserInput]) - return; - - QPointF windowPoint; - QPointF screenPoint; - [self convertFromScreen:[self screenMousePoint:theEvent] toWindowPoint:&windowPoint andScreenPoint:&screenPoint]; - QWindow *childWindow = m_platformWindow->childWindowAt(windowPoint.toPoint()); - - // Top-level windows generate enter-leave events for sub-windows. - // Qt wants to know which window (if any) will be entered at the - // the time of the leave. This is dificult to accomplish by - // handling mouseEnter and mouseLeave envents, since they are sent - // individually to different views. - if (m_platformWindow->isContentView() && childWindow) { - if (childWindow != m_platformWindow->m_enterLeaveTargetWindow) { - QWindowSystemInterface::handleEnterLeaveEvent(childWindow, m_platformWindow->m_enterLeaveTargetWindow, windowPoint, screenPoint); - m_platformWindow->m_enterLeaveTargetWindow = childWindow; - } - } - - // Cocoa keeps firing mouse move events for obscured parent views. Qt should not - // send those events so filter them out here. - if (childWindow != m_platformWindow->window()) - return; - - [self handleMouseEvent: theEvent]; -} - -- (void)mouseEnteredImpl:(NSEvent *)theEvent -{ - Q_UNUSED(theEvent) - if (!m_platformWindow) - return; - - m_platformWindow->m_windowUnderMouse = true; - - if ([self isTransparentForUserInput]) - return; - - // Top-level windows generate enter events for sub-windows. - if (!m_platformWindow->isContentView()) - return; - - QPointF windowPoint; - QPointF screenPoint; - [self convertFromScreen:[self screenMousePoint:theEvent] toWindowPoint:&windowPoint andScreenPoint:&screenPoint]; - m_platformWindow->m_enterLeaveTargetWindow = m_platformWindow->childWindowAt(windowPoint.toPoint()); - QWindowSystemInterface::handleEnterEvent(m_platformWindow->m_enterLeaveTargetWindow, windowPoint, screenPoint); -} - -- (void)mouseExitedImpl:(NSEvent *)theEvent -{ - Q_UNUSED(theEvent); - if (!m_platformWindow) - return; - - m_platformWindow->m_windowUnderMouse = false; - - if ([self isTransparentForUserInput]) - return; - - // Top-level windows generate leave events for sub-windows. - if (!m_platformWindow->isContentView()) - return; - - QWindowSystemInterface::handleLeaveEvent(m_platformWindow->m_enterLeaveTargetWindow); - m_platformWindow->m_enterLeaveTargetWindow = 0; -} - -#ifndef QT_NO_TABLETEVENT -struct QCocoaTabletDeviceData -{ - QTabletEvent::TabletDevice device; - QTabletEvent::PointerType pointerType; - uint capabilityMask; - qint64 uid; -}; - -typedef QHash QCocoaTabletDeviceDataHash; -Q_GLOBAL_STATIC(QCocoaTabletDeviceDataHash, tabletDeviceDataHash) - -- (bool)handleTabletEvent:(NSEvent *)theEvent -{ - static bool ignoreButtonMapping = qEnvironmentVariableIsSet("QT_MAC_TABLET_IGNORE_BUTTON_MAPPING"); - - if (!m_platformWindow) - return false; - - NSEventType eventType = [theEvent type]; - if (eventType != NSTabletPoint && [theEvent subtype] != NSTabletPointEventSubtype) - return false; // Not a tablet event. - - ulong timestamp = [theEvent timestamp] * 1000; - - QPointF windowPoint; - QPointF screenPoint; - [self convertFromScreen:[self screenMousePoint:theEvent] toWindowPoint: &windowPoint andScreenPoint: &screenPoint]; - - uint deviceId = [theEvent deviceID]; - if (!tabletDeviceDataHash->contains(deviceId)) { - // Error: Unknown tablet device. Qt also gets into this state - // when running on a VM. This appears to be harmless; don't - // print a warning. - return false; - } - const QCocoaTabletDeviceData &deviceData = tabletDeviceDataHash->value(deviceId); - - bool down = (eventType != NSMouseMoved); - - qreal pressure; - if (down) { - pressure = [theEvent pressure]; - } else { - pressure = 0.0; - } - - NSPoint tilt = [theEvent tilt]; - int xTilt = qRound(tilt.x * 60.0); - int yTilt = qRound(tilt.y * -60.0); - qreal tangentialPressure = 0; - qreal rotation = 0; - int z = 0; - if (deviceData.capabilityMask & 0x0200) - z = [theEvent absoluteZ]; - - if (deviceData.capabilityMask & 0x0800) - tangentialPressure = ([theEvent tangentialPressure] * 2.0) - 1.0; - - rotation = 360.0 - [theEvent rotation]; - if (rotation > 180.0) - rotation -= 360.0; - - Qt::KeyboardModifiers keyboardModifiers = [QNSView convertKeyModifiers:[theEvent modifierFlags]]; - Qt::MouseButtons buttons = ignoreButtonMapping ? static_cast(static_cast([theEvent buttonMask])) : m_buttons; - - qCDebug(lcQpaTablet, "event on tablet %d with tool %d type %d unique ID %lld pos %6.1f, %6.1f root pos %6.1f, %6.1f buttons 0x%x pressure %4.2lf tilt %d, %d rotation %6.2lf", - deviceId, deviceData.device, deviceData.pointerType, deviceData.uid, - windowPoint.x(), windowPoint.y(), screenPoint.x(), screenPoint.y(), - static_cast(buttons), pressure, xTilt, yTilt, rotation); - - QWindowSystemInterface::handleTabletEvent(m_platformWindow->window(), timestamp, windowPoint, screenPoint, - deviceData.device, deviceData.pointerType, buttons, pressure, xTilt, yTilt, - tangentialPressure, rotation, z, deviceData.uid, - keyboardModifiers); - return true; -} - -- (void)tabletPoint:(NSEvent *)theEvent -{ - if ([self isTransparentForUserInput]) - return [super tabletPoint:theEvent]; - - [self handleTabletEvent: theEvent]; -} - -static QTabletEvent::TabletDevice wacomTabletDevice(NSEvent *theEvent) -{ - qint64 uid = [theEvent uniqueID]; - uint bits = [theEvent vendorPointingDeviceType]; - if (bits == 0 && uid != 0) { - // Fallback. It seems that the driver doesn't always include all the information. - // High-End Wacom devices store their "type" in the uper bits of the Unique ID. - // I'm not sure how to handle it for consumer devices, but I'll test that in a bit. - bits = uid >> 32; - } - - QTabletEvent::TabletDevice device; - // Defined in the "EN0056-NxtGenImpGuideX" - // on Wacom's Developer Website (www.wacomeng.com) - if (((bits & 0x0006) == 0x0002) && ((bits & 0x0F06) != 0x0902)) { - device = QTabletEvent::Stylus; - } else { - switch (bits & 0x0F06) { - case 0x0802: - device = QTabletEvent::Stylus; - break; - case 0x0902: - device = QTabletEvent::Airbrush; - break; - case 0x0004: - device = QTabletEvent::FourDMouse; - break; - case 0x0006: - device = QTabletEvent::Puck; - break; - case 0x0804: - device = QTabletEvent::RotationStylus; - break; - default: - device = QTabletEvent::NoDevice; - } - } - return device; -} - -- (void)tabletProximity:(NSEvent *)theEvent -{ - if ([self isTransparentForUserInput]) - return [super tabletProximity:theEvent]; - - ulong timestamp = [theEvent timestamp] * 1000; - - QCocoaTabletDeviceData deviceData; - deviceData.uid = [theEvent uniqueID]; - deviceData.capabilityMask = [theEvent capabilityMask]; - - switch ([theEvent pointingDeviceType]) { - case NSUnknownPointingDevice: - default: - deviceData.pointerType = QTabletEvent::UnknownPointer; - break; - case NSPenPointingDevice: - deviceData.pointerType = QTabletEvent::Pen; - break; - case NSCursorPointingDevice: - deviceData.pointerType = QTabletEvent::Cursor; - break; - case NSEraserPointingDevice: - deviceData.pointerType = QTabletEvent::Eraser; - break; - } - - deviceData.device = wacomTabletDevice(theEvent); - - // The deviceID is "unique" while in the proximity, it's a key that we can use for - // linking up QCocoaTabletDeviceData to an event (especially if there are two devices in action). - bool entering = [theEvent isEnteringProximity]; - uint deviceId = [theEvent deviceID]; - if (entering) { - tabletDeviceDataHash->insert(deviceId, deviceData); - } else { - tabletDeviceDataHash->remove(deviceId); - } - - qCDebug(lcQpaTablet, "proximity change on tablet %d: current tool %d type %d unique ID %lld", - deviceId, deviceData.device, deviceData.pointerType, deviceData.uid); - - if (entering) { - QWindowSystemInterface::handleTabletEnterProximityEvent(timestamp, deviceData.device, deviceData.pointerType, deviceData.uid); - } else { - QWindowSystemInterface::handleTabletLeaveProximityEvent(timestamp, deviceData.device, deviceData.pointerType, deviceData.uid); - } -} -#endif - -- (bool)shouldSendSingleTouch -{ - if (!m_platformWindow) - return true; - - // QtWidgets expects single-point touch events, QtDeclarative does not. - // Until there is an API we solve this by looking at the window class type. - return m_platformWindow->window()->inherits("QWidgetWindow"); -} - -- (void)touchesBeganWithEvent:(NSEvent *)event -{ - if (!m_platformWindow) - return; - - const NSTimeInterval timestamp = [event timestamp]; - const QList points = QCocoaTouch::getCurrentTouchPointList(event, [self shouldSendSingleTouch]); - qCDebug(lcQpaTouch) << "touchesBeganWithEvent" << points << "from device" << hex << [event deviceID]; - QWindowSystemInterface::handleTouchEvent(m_platformWindow->window(), timestamp * 1000, QCocoaTouch::getTouchDevice(QTouchDevice::TouchPad, [event deviceID]), points); -} - -- (void)touchesMovedWithEvent:(NSEvent *)event -{ - if (!m_platformWindow) - return; - - const NSTimeInterval timestamp = [event timestamp]; - const QList points = QCocoaTouch::getCurrentTouchPointList(event, [self shouldSendSingleTouch]); - qCDebug(lcQpaTouch) << "touchesMovedWithEvent" << points << "from device" << hex << [event deviceID]; - QWindowSystemInterface::handleTouchEvent(m_platformWindow->window(), timestamp * 1000, QCocoaTouch::getTouchDevice(QTouchDevice::TouchPad, [event deviceID]), points); -} - -- (void)touchesEndedWithEvent:(NSEvent *)event -{ - if (!m_platformWindow) - return; - - const NSTimeInterval timestamp = [event timestamp]; - const QList points = QCocoaTouch::getCurrentTouchPointList(event, [self shouldSendSingleTouch]); - qCDebug(lcQpaTouch) << "touchesEndedWithEvent" << points << "from device" << hex << [event deviceID]; - QWindowSystemInterface::handleTouchEvent(m_platformWindow->window(), timestamp * 1000, QCocoaTouch::getTouchDevice(QTouchDevice::TouchPad, [event deviceID]), points); -} - -- (void)touchesCancelledWithEvent:(NSEvent *)event -{ - if (!m_platformWindow) - return; - - const NSTimeInterval timestamp = [event timestamp]; - const QList points = QCocoaTouch::getCurrentTouchPointList(event, [self shouldSendSingleTouch]); - qCDebug(lcQpaTouch) << "touchesCancelledWithEvent" << points << "from device" << hex << [event deviceID]; - QWindowSystemInterface::handleTouchEvent(m_platformWindow->window(), timestamp * 1000, QCocoaTouch::getTouchDevice(QTouchDevice::TouchPad, [event deviceID]), points); -} - -#ifndef QT_NO_GESTURES - -- (bool)handleGestureAsBeginEnd:(NSEvent *)event -{ - if (QOperatingSystemVersion::current() < QOperatingSystemVersion::OSXElCapitan) - return false; - - if ([event phase] == NSEventPhaseBegan) { - [self beginGestureWithEvent:event]; - return true; - } - - if ([event phase] == NSEventPhaseEnded) { - [self endGestureWithEvent:event]; - return true; - } - - return false; -} -- (void)magnifyWithEvent:(NSEvent *)event -{ - if (!m_platformWindow) - return; - - if ([self handleGestureAsBeginEnd:event]) - return; - - qCDebug(lcQpaGestures) << "magnifyWithEvent" << [event magnification] << "from device" << hex << [event deviceID]; - const NSTimeInterval timestamp = [event timestamp]; - QPointF windowPoint; - QPointF screenPoint; - [self convertFromScreen:[self screenMousePoint:event] toWindowPoint:&windowPoint andScreenPoint:&screenPoint]; - QWindowSystemInterface::handleGestureEventWithRealValue(m_platformWindow->window(), QCocoaTouch::getTouchDevice(QTouchDevice::TouchPad, [event deviceID]), timestamp, Qt::ZoomNativeGesture, - [event magnification], windowPoint, screenPoint); -} - -- (void)smartMagnifyWithEvent:(NSEvent *)event -{ - if (!m_platformWindow) - return; - - static bool zoomIn = true; - qCDebug(lcQpaGestures) << "smartMagnifyWithEvent" << zoomIn << "from device" << hex << [event deviceID]; - const NSTimeInterval timestamp = [event timestamp]; - QPointF windowPoint; - QPointF screenPoint; - [self convertFromScreen:[self screenMousePoint:event] toWindowPoint:&windowPoint andScreenPoint:&screenPoint]; - QWindowSystemInterface::handleGestureEventWithRealValue(m_platformWindow->window(), QCocoaTouch::getTouchDevice(QTouchDevice::TouchPad, [event deviceID]), timestamp, Qt::SmartZoomNativeGesture, - zoomIn ? 1.0f : 0.0f, windowPoint, screenPoint); - zoomIn = !zoomIn; -} - -- (void)rotateWithEvent:(NSEvent *)event -{ - if (!m_platformWindow) - return; - - if ([self handleGestureAsBeginEnd:event]) - return; - - const NSTimeInterval timestamp = [event timestamp]; - QPointF windowPoint; - QPointF screenPoint; - [self convertFromScreen:[self screenMousePoint:event] toWindowPoint:&windowPoint andScreenPoint:&screenPoint]; - QWindowSystemInterface::handleGestureEventWithRealValue(m_platformWindow->window(), QCocoaTouch::getTouchDevice(QTouchDevice::TouchPad, [event deviceID]), timestamp, Qt::RotateNativeGesture, - -[event rotation], windowPoint, screenPoint); -} - -- (void)swipeWithEvent:(NSEvent *)event -{ - if (!m_platformWindow) - return; - - qCDebug(lcQpaGestures) << "swipeWithEvent" << [event deltaX] << [event deltaY] << "from device" << hex << [event deviceID]; - const NSTimeInterval timestamp = [event timestamp]; - QPointF windowPoint; - QPointF screenPoint; - [self convertFromScreen:[self screenMousePoint:event] toWindowPoint:&windowPoint andScreenPoint:&screenPoint]; - - qreal angle = 0.0f; - if ([event deltaX] == 1) - angle = 180.0f; - else if ([event deltaX] == -1) - angle = 0.0f; - else if ([event deltaY] == 1) - angle = 90.0f; - else if ([event deltaY] == -1) - angle = 270.0f; - - QWindowSystemInterface::handleGestureEventWithRealValue(m_platformWindow->window(), QCocoaTouch::getTouchDevice(QTouchDevice::TouchPad, [event deviceID]), timestamp, Qt::SwipeNativeGesture, - angle, windowPoint, screenPoint); -} - -- (void)beginGestureWithEvent:(NSEvent *)event -{ - if (!m_platformWindow) - return; - - const NSTimeInterval timestamp = [event timestamp]; - QPointF windowPoint; - QPointF screenPoint; - [self convertFromScreen:[self screenMousePoint:event] toWindowPoint:&windowPoint andScreenPoint:&screenPoint]; - qCDebug(lcQpaGestures) << "beginGestureWithEvent @" << windowPoint << "from device" << hex << [event deviceID]; - QWindowSystemInterface::handleGestureEvent(m_platformWindow->window(), QCocoaTouch::getTouchDevice(QTouchDevice::TouchPad, [event deviceID]), timestamp, Qt::BeginNativeGesture, - windowPoint, screenPoint); -} - -- (void)endGestureWithEvent:(NSEvent *)event -{ - if (!m_platformWindow) - return; - - qCDebug(lcQpaGestures) << "endGestureWithEvent" << "from device" << hex << [event deviceID]; - const NSTimeInterval timestamp = [event timestamp]; - QPointF windowPoint; - QPointF screenPoint; - [self convertFromScreen:[self screenMousePoint:event] toWindowPoint:&windowPoint andScreenPoint:&screenPoint]; - QWindowSystemInterface::handleGestureEvent(m_platformWindow->window(), QCocoaTouch::getTouchDevice(QTouchDevice::TouchPad, [event deviceID]), timestamp, Qt::EndNativeGesture, - windowPoint, screenPoint); -} -#endif // QT_NO_GESTURES - -#if QT_CONFIG(wheelevent) -- (void)scrollWheel:(NSEvent *)theEvent -{ - if (!m_platformWindow) - return; - - if ([self isTransparentForUserInput]) - return [super scrollWheel:theEvent]; - - QPoint angleDelta; - Qt::MouseEventSource source = Qt::MouseEventNotSynthesized; - if ([theEvent hasPreciseScrollingDeltas]) { - // The mouse device contains pixel scroll wheel support (Mighty Mouse, Trackpad). - // Since deviceDelta is delivered as pixels rather than degrees, we need to - // convert from pixels to degrees in a sensible manner. - // It looks like 1/4 degrees per pixel behaves most native. - // (NB: Qt expects the unit for delta to be 8 per degree): - const int pixelsToDegrees = 2; // 8 * 1/4 - angleDelta.setX([theEvent scrollingDeltaX] * pixelsToDegrees); - angleDelta.setY([theEvent scrollingDeltaY] * pixelsToDegrees); - source = Qt::MouseEventSynthesizedBySystem; - } else { - // Remove acceleration, and use either -120 or 120 as delta: - angleDelta.setX(qBound(-120, int([theEvent deltaX] * 10000), 120)); - angleDelta.setY(qBound(-120, int([theEvent deltaY] * 10000), 120)); - } - - QPoint pixelDelta; - if ([theEvent hasPreciseScrollingDeltas]) { - pixelDelta.setX([theEvent scrollingDeltaX]); - pixelDelta.setY([theEvent scrollingDeltaY]); - } else { - // docs: "In the case of !hasPreciseScrollingDeltas, multiply the delta with the line width." - // scrollingDeltaX seems to return a minimum value of 0.1 in this case, map that to two pixels. - const CGFloat lineWithEstimate = 20.0; - pixelDelta.setX([theEvent scrollingDeltaX] * lineWithEstimate); - pixelDelta.setY([theEvent scrollingDeltaY] * lineWithEstimate); - } - - QPointF qt_windowPoint; - QPointF qt_screenPoint; - [self convertFromScreen:[self screenMousePoint:theEvent] toWindowPoint:&qt_windowPoint andScreenPoint:&qt_screenPoint]; - NSTimeInterval timestamp = [theEvent timestamp]; - ulong qt_timestamp = timestamp * 1000; - - // Prevent keyboard modifier state from changing during scroll event streams. - // A two-finger trackpad flick generates a stream of scroll events. We want - // the keyboard modifier state to be the state at the beginning of the - // flick in order to avoid changing the interpretation of the events - // mid-stream. One example of this happening would be when pressing cmd - // after scrolling in Qt Creator: not taking the phase into account causes - // the end of the event stream to be interpreted as font size changes. - NSEventPhase momentumPhase = [theEvent momentumPhase]; - if (momentumPhase == NSEventPhaseNone) { - currentWheelModifiers = [QNSView convertKeyModifiers:[theEvent modifierFlags]]; - } - - NSEventPhase phase = [theEvent phase]; - Qt::ScrollPhase ph = Qt::ScrollUpdate; - - // MayBegin is likely to happen. We treat it the same as an actual begin. - if (phase == NSEventPhaseMayBegin) { - m_scrolling = true; - ph = Qt::ScrollBegin; - } else if (phase == NSEventPhaseBegan) { - // If MayBegin did not happen, Began is the actual beginning. - if (!m_scrolling) - ph = Qt::ScrollBegin; - m_scrolling = true; - } else if (phase == NSEventPhaseEnded || phase == NSEventPhaseCancelled || - momentumPhase == NSEventPhaseEnded || momentumPhase == NSEventPhaseCancelled) { - ph = Qt::ScrollEnd; - m_scrolling = false; - } else if (phase == NSEventPhaseNone && momentumPhase == NSEventPhaseNone) { - ph = Qt::NoScrollPhase; - } - // "isInverted": natural OS X scrolling, inverted from the Qt/other platform/Jens perspective. - bool isInverted = [theEvent isDirectionInvertedFromDevice]; - - qCDebug(lcQpaMouse) << "scroll wheel @ window pos" << qt_windowPoint << "delta px" << pixelDelta << "angle" << angleDelta << "phase" << ph << (isInverted ? "inverted" : ""); - QWindowSystemInterface::handleWheelEvent(m_platformWindow->window(), qt_timestamp, qt_windowPoint, qt_screenPoint, pixelDelta, angleDelta, currentWheelModifiers, ph, source, isInverted); -} -#endif // QT_CONFIG(wheelevent) - -- (int)convertKeyCode:(QChar)keyChar -{ - return qt_mac_cocoaKey2QtKey(keyChar); -} - -+ (Qt::KeyboardModifiers)convertKeyModifiers:(ulong)modifierFlags -{ - const bool dontSwapCtrlAndMeta = qApp->testAttribute(Qt::AA_MacDontSwapCtrlAndMeta); - Qt::KeyboardModifiers qtMods =Qt::NoModifier; - if (modifierFlags & NSShiftKeyMask) - qtMods |= Qt::ShiftModifier; - if (modifierFlags & NSControlKeyMask) - qtMods |= dontSwapCtrlAndMeta ? Qt::ControlModifier : Qt::MetaModifier; - if (modifierFlags & NSAlternateKeyMask) - qtMods |= Qt::AltModifier; - if (modifierFlags & NSCommandKeyMask) - qtMods |= dontSwapCtrlAndMeta ? Qt::MetaModifier : Qt::ControlModifier; - if (modifierFlags & NSNumericPadKeyMask) - qtMods |= Qt::KeypadModifier; - return qtMods; -} - -- (bool)handleKeyEvent:(NSEvent *)nsevent eventType:(int)eventType -{ - ulong timestamp = [nsevent timestamp] * 1000; - ulong nativeModifiers = [nsevent modifierFlags]; - Qt::KeyboardModifiers modifiers = [QNSView convertKeyModifiers: nativeModifiers]; - NSString *charactersIgnoringModifiers = [nsevent charactersIgnoringModifiers]; - NSString *characters = [nsevent characters]; - if (m_inputSource != characters) { - [m_inputSource release]; - m_inputSource = [characters retain]; - } - - // There is no way to get the scan code from carbon/cocoa. But we cannot - // use the value 0, since it indicates that the event originates from somewhere - // else than the keyboard. - quint32 nativeScanCode = 1; - quint32 nativeVirtualKey = [nsevent keyCode]; - - QChar ch = QChar::ReplacementCharacter; - int keyCode = Qt::Key_unknown; - - // If a dead key occurs as a result of pressing a key combination then - // characters will have 0 length, but charactersIgnoringModifiers will - // have a valid character in it. This enables key combinations such as - // ALT+E to be used as a shortcut with an English keyboard even though - // pressing ALT+E will give a dead key while doing normal text input. - if ([characters length] != 0 || [charactersIgnoringModifiers length] != 0) { - auto ctrlOrMetaModifier = qApp->testAttribute(Qt::AA_MacDontSwapCtrlAndMeta) ? Qt::ControlModifier : Qt::MetaModifier; - if (((modifiers & ctrlOrMetaModifier) || (modifiers & Qt::AltModifier)) && ([charactersIgnoringModifiers length] != 0)) - ch = QChar([charactersIgnoringModifiers characterAtIndex:0]); - else if ([characters length] != 0) - ch = QChar([characters characterAtIndex:0]); - keyCode = [self convertKeyCode:ch]; - } - - // we will send a key event unless the input method sets m_sendKeyEvent to false - m_sendKeyEvent = true; - QString text; - // ignore text for the U+F700-U+F8FF range. This is used by Cocoa when - // delivering function keys (e.g. arrow keys, backspace, F1-F35, etc.) - if (!(modifiers & (Qt::ControlModifier | Qt::MetaModifier)) && (ch.unicode() < 0xf700 || ch.unicode() > 0xf8ff)) - text = QString::fromNSString(characters); - - QWindow *window = [self topLevelWindow]; - - // Popups implicitly grab key events; forward to the active popup if there is one. - // This allows popups to e.g. intercept shortcuts and close the popup in response. - if (QCocoaWindow *popup = QCocoaIntegration::instance()->activePopupWindow()) { - if (!popup->window()->flags().testFlag(Qt::ToolTip)) - window = popup->window(); - } - - if (eventType == QEvent::KeyPress) { - - if (m_composingText.isEmpty()) { - m_sendKeyEvent = !QWindowSystemInterface::handleShortcutEvent(window, timestamp, keyCode, - modifiers, nativeScanCode, nativeVirtualKey, nativeModifiers, text, [nsevent isARepeat], 1); - - // Handling a shortcut may result in closing the window - if (!m_platformWindow) - return true; - } - - QObject *fo = m_platformWindow->window()->focusObject(); - if (m_sendKeyEvent && fo) { - QInputMethodQueryEvent queryEvent(Qt::ImEnabled | Qt::ImHints); - if (QCoreApplication::sendEvent(fo, &queryEvent)) { - bool imEnabled = queryEvent.value(Qt::ImEnabled).toBool(); - Qt::InputMethodHints hints = static_cast(queryEvent.value(Qt::ImHints).toUInt()); - if (imEnabled && !(hints & Qt::ImhDigitsOnly || hints & Qt::ImhFormattedNumbersOnly || hints & Qt::ImhHiddenText)) { - // pass the key event to the input method. note that m_sendKeyEvent may be set to false during this call - m_currentlyInterpretedKeyEvent = nsevent; - [self interpretKeyEvents:@[nsevent]]; - m_currentlyInterpretedKeyEvent = 0; - } - } - } - if (m_resendKeyEvent) - m_sendKeyEvent = true; - } - - bool accepted = true; - if (m_sendKeyEvent && m_composingText.isEmpty()) { - QWindowSystemInterface::handleExtendedKeyEvent(window, timestamp, QEvent::Type(eventType), keyCode, modifiers, - nativeScanCode, nativeVirtualKey, nativeModifiers, text, [nsevent isARepeat], 1, false); - accepted = QWindowSystemInterface::flushWindowSystemEvents(); - } - m_sendKeyEvent = false; - m_resendKeyEvent = false; - return accepted; -} - -- (void)keyDown:(NSEvent *)nsevent -{ - if ([self isTransparentForUserInput]) - return [super keyDown:nsevent]; - - const bool accepted = [self handleKeyEvent:nsevent eventType:int(QEvent::KeyPress)]; - - // When Qt is used to implement a plugin for a native application we - // want to propagate unhandled events to other native views. However, - // Qt does not always set the accepted state correctly (in particular - // for return key events), so do this for plugin applications only - // to prevent incorrect forwarding in the general case. - const bool shouldPropagate = QCoreApplication::testAttribute(Qt::AA_PluginApplication) && !accepted; - - // Track keyDown acceptance/forward state for later acceptance of the keyUp. - if (!shouldPropagate) - m_acceptedKeyDowns.insert([nsevent keyCode]); - - if (shouldPropagate) - [super keyDown:nsevent]; -} - -- (void)keyUp:(NSEvent *)nsevent -{ - if ([self isTransparentForUserInput]) - return [super keyUp:nsevent]; - - const bool keyUpAccepted = [self handleKeyEvent:nsevent eventType:int(QEvent::KeyRelease)]; - - // Propagate the keyUp if neither Qt accepted it nor the corresponding KeyDown was - // accepted. Qt text controls wil often not use and ignore keyUp events, but we - // want to avoid propagating unmatched keyUps. - const bool keyDownAccepted = m_acceptedKeyDowns.remove([nsevent keyCode]); - if (!keyUpAccepted && !keyDownAccepted) - [super keyUp:nsevent]; -} - -- (void)cancelOperation:(id)sender -{ - Q_UNUSED(sender); - - NSEvent *currentEvent = [NSApp currentEvent]; - if (!currentEvent || currentEvent.type != NSKeyDown) - return; - - // Handling the key event may recurse back here through interpretKeyEvents - // (when IM is enabled), so we need to guard against that. - if (currentEvent == m_currentlyInterpretedKeyEvent) - return; - - // Send Command+Key_Period and Escape as normal keypresses so that - // the key sequence is delivered through Qt. That way clients can - // intercept the shortcut and override its effect. - [self handleKeyEvent:currentEvent eventType:int(QEvent::KeyPress)]; -} - -- (void)flagsChanged:(NSEvent *)nsevent -{ - ulong timestamp = [nsevent timestamp] * 1000; - ulong modifiers = [nsevent modifierFlags]; - Qt::KeyboardModifiers qmodifiers = [QNSView convertKeyModifiers:modifiers]; - - // calculate the delta and remember the current modifiers for next time - static ulong m_lastKnownModifiers; - ulong lastKnownModifiers = m_lastKnownModifiers; - ulong delta = lastKnownModifiers ^ modifiers; - m_lastKnownModifiers = modifiers; - - struct qt_mac_enum_mapper - { - ulong mac_mask; - Qt::Key qt_code; - }; - static qt_mac_enum_mapper modifier_key_symbols[] = { - { NSShiftKeyMask, Qt::Key_Shift }, - { NSControlKeyMask, Qt::Key_Meta }, - { NSCommandKeyMask, Qt::Key_Control }, - { NSAlternateKeyMask, Qt::Key_Alt }, - { NSAlphaShiftKeyMask, Qt::Key_CapsLock }, - { 0ul, Qt::Key_unknown } }; - for (int i = 0; modifier_key_symbols[i].mac_mask != 0u; ++i) { - uint mac_mask = modifier_key_symbols[i].mac_mask; - if ((delta & mac_mask) == 0u) - continue; - - Qt::Key qtCode = modifier_key_symbols[i].qt_code; - if (qApp->testAttribute(Qt::AA_MacDontSwapCtrlAndMeta)) { - if (qtCode == Qt::Key_Meta) - qtCode = Qt::Key_Control; - else if (qtCode == Qt::Key_Control) - qtCode = Qt::Key_Meta; - } - QWindowSystemInterface::handleKeyEvent(m_platformWindow->window(), - timestamp, - (lastKnownModifiers & mac_mask) ? QEvent::KeyRelease : QEvent::KeyPress, - qtCode, - qmodifiers ^ [QNSView convertKeyModifiers:mac_mask]); - } -} - -- (void)insertNewline:(id)sender -{ - Q_UNUSED(sender); - m_resendKeyEvent = true; -} - -- (void)doCommandBySelector:(SEL)aSelector -{ - [self tryToPerform:aSelector with:self]; -} - -- (void)insertText:(id)aString replacementRange:(NSRange)replacementRange -{ - Q_UNUSED(replacementRange) - - if (m_sendKeyEvent && m_composingText.isEmpty() && [aString isEqualToString:m_inputSource]) { - // don't send input method events for simple text input (let handleKeyEvent send key events instead) - return; - } - - QString commitString; - if ([aString length]) { - if ([aString isKindOfClass:[NSAttributedString class]]) { - commitString = QString::fromCFString(reinterpret_cast([aString string])); - } else { - commitString = QString::fromCFString(reinterpret_cast(aString)); - }; - } - if (QObject *fo = m_platformWindow->window()->focusObject()) { - QInputMethodQueryEvent queryEvent(Qt::ImEnabled); - if (QCoreApplication::sendEvent(fo, &queryEvent)) { - if (queryEvent.value(Qt::ImEnabled).toBool()) { - QInputMethodEvent e; - e.setCommitString(commitString); - QCoreApplication::sendEvent(fo, &e); - // prevent handleKeyEvent from sending a key event - m_sendKeyEvent = false; - } - } - } - - m_composingText.clear(); - m_composingFocusObject = nullptr; -} - -- (void)setMarkedText:(id)aString selectedRange:(NSRange)selectedRange replacementRange:(NSRange)replacementRange -{ - Q_UNUSED(replacementRange) - QString preeditString; - - QList attrs; - attrs<([aString string])); - int composingLength = preeditString.length(); - int index = 0; - // Create attributes for individual sections of preedit text - while (index < composingLength) { - NSRange effectiveRange; - NSRange range = NSMakeRange(index, composingLength-index); - NSDictionary *attributes = [aString attributesAtIndex:index - longestEffectiveRange:&effectiveRange - inRange:range]; - NSNumber *underlineStyle = [attributes objectForKey:NSUnderlineStyleAttributeName]; - if (underlineStyle) { - QColor clr (Qt::black); - NSColor *color = [attributes objectForKey:NSUnderlineColorAttributeName]; - if (color) { - clr = qt_mac_toQColor(color); - } - QTextCharFormat format; - format.setFontUnderline(true); - format.setUnderlineColor(clr); - attrs<(aString)); - } - - if (attrs.isEmpty()) { - QTextCharFormat format; - format.setFontUnderline(true); - attrs<window()->focusObject()) { - m_composingFocusObject = fo; - QInputMethodQueryEvent queryEvent(Qt::ImEnabled); - if (QCoreApplication::sendEvent(fo, &queryEvent)) { - if (queryEvent.value(Qt::ImEnabled).toBool()) { - QInputMethodEvent e(preeditString, attrs); - QCoreApplication::sendEvent(fo, &e); - // prevent handleKeyEvent from sending a key event - m_sendKeyEvent = false; - } - } - } -} - -- (void)cancelComposingText -{ - if (m_composingText.isEmpty()) - return; - - if (m_composingFocusObject) { - QInputMethodQueryEvent queryEvent(Qt::ImEnabled); - if (QCoreApplication::sendEvent(m_composingFocusObject, &queryEvent)) { - if (queryEvent.value(Qt::ImEnabled).toBool()) { - QInputMethodEvent e; - QCoreApplication::sendEvent(m_composingFocusObject, &e); - } - } - } - - m_composingText.clear(); - m_composingFocusObject = nullptr; -} - -- (void)unmarkText -{ - if (!m_composingText.isEmpty()) { - if (QObject *fo = m_platformWindow->window()->focusObject()) { - QInputMethodQueryEvent queryEvent(Qt::ImEnabled); - if (QCoreApplication::sendEvent(fo, &queryEvent)) { - if (queryEvent.value(Qt::ImEnabled).toBool()) { - QInputMethodEvent e; - e.setCommitString(m_composingText); - QCoreApplication::sendEvent(fo, &e); - } - } - } - } - m_composingText.clear(); - m_composingFocusObject = nullptr; -} - -- (BOOL)hasMarkedText -{ - return (m_composingText.isEmpty() ? NO: YES); -} - -- (NSAttributedString *)attributedSubstringForProposedRange:(NSRange)aRange actualRange:(NSRangePointer)actualRange -{ - Q_UNUSED(actualRange) - QObject *fo = m_platformWindow->window()->focusObject(); - if (!fo) - return nil; - QInputMethodQueryEvent queryEvent(Qt::ImEnabled | Qt::ImCurrentSelection); - if (!QCoreApplication::sendEvent(fo, &queryEvent)) - return nil; - if (!queryEvent.value(Qt::ImEnabled).toBool()) - return nil; - - QString selectedText = queryEvent.value(Qt::ImCurrentSelection).toString(); - if (selectedText.isEmpty()) - return nil; - - QCFString string(selectedText.mid(aRange.location, aRange.length)); - const NSString *tmpString = reinterpret_cast((CFStringRef)string); - return [[[NSAttributedString alloc] initWithString:const_cast(tmpString)] autorelease]; -} - -- (NSRange)markedRange -{ - NSRange range; - if (!m_composingText.isEmpty()) { - range.location = 0; - range.length = m_composingText.length(); - } else { - range.location = NSNotFound; - range.length = 0; - } - return range; -} - -- (NSRange)selectedRange -{ - NSRange selectedRange = {0, 0}; - - QObject *fo = m_platformWindow->window()->focusObject(); - if (!fo) - return selectedRange; - QInputMethodQueryEvent queryEvent(Qt::ImEnabled | Qt::ImCurrentSelection); - if (!QCoreApplication::sendEvent(fo, &queryEvent)) - return selectedRange; - if (!queryEvent.value(Qt::ImEnabled).toBool()) - return selectedRange; - - QString selectedText = queryEvent.value(Qt::ImCurrentSelection).toString(); - - if (!selectedText.isEmpty()) { - selectedRange.location = 0; - selectedRange.length = selectedText.length(); - } - return selectedRange; -} - -- (NSRect)firstRectForCharacterRange:(NSRange)aRange actualRange:(NSRangePointer)actualRange -{ - Q_UNUSED(aRange) - Q_UNUSED(actualRange) - QObject *fo = m_platformWindow->window()->focusObject(); - if (!fo) - return NSZeroRect; - - QInputMethodQueryEvent queryEvent(Qt::ImEnabled); - if (!QCoreApplication::sendEvent(fo, &queryEvent)) - return NSZeroRect; - if (!queryEvent.value(Qt::ImEnabled).toBool()) - return NSZeroRect; - - if (!m_platformWindow->window()) - return NSZeroRect; - - // The returned rect is always based on the internal cursor. - QRect mr = qApp->inputMethod()->cursorRectangle().toRect(); - mr.moveBottomLeft(m_platformWindow->window()->mapToGlobal(mr.bottomLeft())); - return QCocoaScreen::mapToNative(mr); -} - -- (NSUInteger)characterIndexForPoint:(NSPoint)aPoint -{ - // We don't support cursor movements using mouse while composing. - Q_UNUSED(aPoint); - return NSNotFound; -} - -- (NSArray *)validAttributesForMarkedText -{ - if (!m_platformWindow) - return nil; - - if (m_platformWindow->window() != QGuiApplication::focusWindow()) - return nil; - - QObject *fo = m_platformWindow->window()->focusObject(); - if (!fo) - return nil; - - QInputMethodQueryEvent queryEvent(Qt::ImEnabled); - if (!QCoreApplication::sendEvent(fo, &queryEvent)) - return nil; - if (!queryEvent.value(Qt::ImEnabled).toBool()) - return nil; - - // Support only underline color/style. - return @[NSUnderlineColorAttributeName, NSUnderlineStyleAttributeName]; -} - -- (void)textInputContextKeyboardSelectionDidChangeNotification:(NSNotification *)textInputContextKeyboardSelectionDidChangeNotification -{ - Q_UNUSED(textInputContextKeyboardSelectionDidChangeNotification) - if (([NSApp keyWindow] == self.window) && self.window.firstResponder == self) { - QCocoaInputContext *ic = qobject_cast(QCocoaIntegration::instance()->inputContext()); - ic->updateLocale(); - } -} - --(void)registerDragTypes -{ - QMacAutoReleasePool pool; - QStringList customTypes = qt_mac_enabledDraggedTypes(); - if (currentCustomDragTypes == 0 || *currentCustomDragTypes != customTypes) { - if (currentCustomDragTypes == 0) - currentCustomDragTypes = new QStringList(); - *currentCustomDragTypes = customTypes; - NSString * const mimeTypeGeneric = @"com.trolltech.qt.MimeTypeName"; - NSMutableArray *supportedTypes = [NSMutableArray 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 (int i = 0; i < customTypes.size(); i++) { - [supportedTypes addObject:customTypes[i].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 forbiden cursor wanted on non droppable 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 - if (m_updatingDrag) - return; - - const QPoint mousePos(QCursor::pos()); - CGEventRef moveEvent(CGEventCreateMouseEvent( - NULL, kCGEventMouseMoved, - CGPointMake(mousePos.x(), mousePos.y()), - kCGMouseButtonLeft // ignored - )); - CGEventPost(kCGHIDEventTap, moveEvent); - CFRelease(moveEvent); -} - -- (NSDragOperation)draggingEntered:(id )sender -{ - return [self handleDrag : sender]; -} - -- (NSDragOperation)draggingUpdated:(id )sender -{ - m_updatingDrag = true; - const NSDragOperation ret([self handleDrag : sender]); - m_updatingDrag = false; - - return ret; -} - -// Sends drag update to Qt, return the action -- (NSDragOperation)handleDrag:(id )sender -{ - if (!m_platformWindow) - return NSDragOperationNone; - - NSPoint windowPoint = [self convertPoint: [sender draggingLocation] fromView: nil]; - QPoint qt_windowPoint(windowPoint.x, windowPoint.y); - Qt::DropActions qtAllowed = qt_mac_mapNSDragOperations([sender draggingSourceOperationMask]); - - QWindow *target = findEventTargetWindow(m_platformWindow->window()); - if (!target) - return NSDragOperationNone; - - // update these so selecting move/copy/link works - QGuiApplicationPrivate::modifier_buttons = [QNSView convertKeyModifiers: [[NSApp currentEvent] modifierFlags]]; - - 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(), mapWindowCoordinates(m_platformWindow->window(), target, qt_windowPoint), qtAllowed); - [self updateCursorFromDragResponse:response drag:nativeDrag]; - } else { - QCocoaDropData mimeData([sender draggingPasteboard]); - response = QWindowSystemInterface::handleDrag(target, &mimeData, mapWindowCoordinates(m_platformWindow->window(), target, qt_windowPoint), qtAllowed); - } - - return qt_mac_mapDropAction(response.acceptedAction()); -} - -- (void)draggingExited:(id )sender -{ - if (!m_platformWindow) - return; - - QWindow *target = findEventTargetWindow(m_platformWindow->window()); - if (!target) - return; - - NSPoint windowPoint = [self convertPoint: [sender draggingLocation] fromView: nil]; - QPoint qt_windowPoint(windowPoint.x, windowPoint.y); - - // Send 0 mime data to indicate drag exit - QWindowSystemInterface::handleDrag(target, 0, mapWindowCoordinates(m_platformWindow->window(), target, qt_windowPoint), Qt::IgnoreAction); -} - -// called on drop, send the drop to Qt and return if it was accepted. -- (BOOL)performDragOperation:(id )sender -{ - if (!m_platformWindow) - return false; - - QWindow *target = findEventTargetWindow(m_platformWindow->window()); - if (!target) - return false; - - NSPoint windowPoint = [self convertPoint: [sender draggingLocation] fromView: nil]; - QPoint qt_windowPoint(windowPoint.x, windowPoint.y); - Qt::DropActions qtAllowed = qt_mac_mapNSDragOperations([sender draggingSourceOperationMask]); - - QPlatformDropQtResponse response(false, Qt::IgnoreAction); - QCocoaDrag* nativeDrag = QCocoaIntegration::instance()->drag(); - if (nativeDrag->currentDrag()) { - // The drag was started from within the application - response = QWindowSystemInterface::handleDrop(target, nativeDrag->dragMimeData(), mapWindowCoordinates(m_platformWindow->window(), target, qt_windowPoint), qtAllowed); - } else { - QCocoaDropData mimeData([sender draggingPasteboard]); - response = QWindowSystemInterface::handleDrop(target, &mimeData, mapWindowCoordinates(m_platformWindow->window(), target, qt_windowPoint), qtAllowed); - } - return response.isAccepted(); -} - -- (void)draggingSession:(NSDraggingSession *)session - endedAtPoint:(NSPoint)screenPoint - operation:(NSDragOperation)operation -{ - Q_UNUSED(session); - Q_UNUSED(operation); - - if (!m_platformWindow) - return; - - QWindow *target = findEventTargetWindow(m_platformWindow->window()); - if (!target) - return; - - QCocoaDrag* nativeDrag = QCocoaIntegration::instance()->drag(); - nativeDrag->setAcceptedAction(qt_mac_mapNSDragOperation(operation)); - - // keep our state, and QGuiApplication state (buttons member) in-sync, - // or future mouse events will be processed incorrectly - NSUInteger pmb = [NSEvent pressedMouseButtons]; - for (int buttonNumber = 0; buttonNumber < 32; buttonNumber++) { // see cocoaButton2QtButton() for the 32 value - if (!(pmb & (1 << buttonNumber))) - m_buttons &= ~cocoaButton2QtButton(buttonNumber); - } - - NSPoint windowPoint = [self.window convertRectFromScreen:NSMakeRect(screenPoint.x, screenPoint.y, 1, 1)].origin; - NSPoint nsViewPoint = [self convertPoint: windowPoint fromView: nil]; // NSView/QWindow coordinates - QPoint qtWindowPoint(nsViewPoint.x, nsViewPoint.y); - QPoint qtScreenPoint = QCocoaScreen::mapFromNative(screenPoint).toPoint(); - - QWindowSystemInterface::handleMouseEvent(target, mapWindowCoordinates(m_platformWindow->window(), target, qtWindowPoint), qtScreenPoint, m_buttons); -} - @end +#include "qnsview_mouse.mm" +#include "qnsview_touch.mm" +#include "qnsview_gestures.mm" +#include "qnsview_tablet.mm" +#include "qnsview_dragging.mm" +#include "qnsview_keys.mm" +#include "qnsview_complextext.mm" + +// ----------------------------------------------------- + @implementation QT_MANGLE_NAMESPACE(QNSView) (QtExtras) - (QCocoaWindow*)platformWindow diff --git a/src/plugins/platforms/cocoa/qnsview_complextext.mm b/src/plugins/platforms/cocoa/qnsview_complextext.mm new file mode 100644 index 0000000000..f0d544c63d --- /dev/null +++ b/src/plugins/platforms/cocoa/qnsview_complextext.mm @@ -0,0 +1,317 @@ +/**************************************************************************** +** +** 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 QT_MANGLE_NAMESPACE(QNSView) (ComplexTextAPI) + +- (void)cancelComposingText +{ + if (m_composingText.isEmpty()) + return; + + if (m_composingFocusObject) { + QInputMethodQueryEvent queryEvent(Qt::ImEnabled); + if (QCoreApplication::sendEvent(m_composingFocusObject, &queryEvent)) { + if (queryEvent.value(Qt::ImEnabled).toBool()) { + QInputMethodEvent e; + QCoreApplication::sendEvent(m_composingFocusObject, &e); + } + } + } + + m_composingText.clear(); + m_composingFocusObject = nullptr; +} + +- (void)unmarkText +{ + if (!m_composingText.isEmpty()) { + if (QObject *fo = m_platformWindow->window()->focusObject()) { + QInputMethodQueryEvent queryEvent(Qt::ImEnabled); + if (QCoreApplication::sendEvent(fo, &queryEvent)) { + if (queryEvent.value(Qt::ImEnabled).toBool()) { + QInputMethodEvent e; + e.setCommitString(m_composingText); + QCoreApplication::sendEvent(fo, &e); + } + } + } + } + m_composingText.clear(); + m_composingFocusObject = nullptr; +} + +@end + +@implementation QT_MANGLE_NAMESPACE(QNSView) (ComplexText) + +- (void)insertNewline:(id)sender +{ + Q_UNUSED(sender); + m_resendKeyEvent = true; +} + +- (void)doCommandBySelector:(SEL)aSelector +{ + [self tryToPerform:aSelector with:self]; +} + +- (void)insertText:(id)aString replacementRange:(NSRange)replacementRange +{ + Q_UNUSED(replacementRange) + + if (m_sendKeyEvent && m_composingText.isEmpty() && [aString isEqualToString:m_inputSource]) { + // don't send input method events for simple text input (let handleKeyEvent send key events instead) + return; + } + + QString commitString; + if ([aString length]) { + if ([aString isKindOfClass:[NSAttributedString class]]) { + commitString = QString::fromCFString(reinterpret_cast([aString string])); + } else { + commitString = QString::fromCFString(reinterpret_cast(aString)); + }; + } + if (QObject *fo = m_platformWindow->window()->focusObject()) { + QInputMethodQueryEvent queryEvent(Qt::ImEnabled); + if (QCoreApplication::sendEvent(fo, &queryEvent)) { + if (queryEvent.value(Qt::ImEnabled).toBool()) { + QInputMethodEvent e; + e.setCommitString(commitString); + QCoreApplication::sendEvent(fo, &e); + // prevent handleKeyEvent from sending a key event + m_sendKeyEvent = false; + } + } + } + + m_composingText.clear(); + m_composingFocusObject = nullptr; +} + +- (void)setMarkedText:(id)aString selectedRange:(NSRange)selectedRange replacementRange:(NSRange)replacementRange +{ + Q_UNUSED(replacementRange) + QString preeditString; + + QList attrs; + attrs<([aString string])); + int composingLength = preeditString.length(); + int index = 0; + // Create attributes for individual sections of preedit text + while (index < composingLength) { + NSRange effectiveRange; + NSRange range = NSMakeRange(index, composingLength-index); + NSDictionary *attributes = [aString attributesAtIndex:index + longestEffectiveRange:&effectiveRange + inRange:range]; + NSNumber *underlineStyle = [attributes objectForKey:NSUnderlineStyleAttributeName]; + if (underlineStyle) { + QColor clr (Qt::black); + NSColor *color = [attributes objectForKey:NSUnderlineColorAttributeName]; + if (color) { + clr = qt_mac_toQColor(color); + } + QTextCharFormat format; + format.setFontUnderline(true); + format.setUnderlineColor(clr); + attrs<(aString)); + } + + if (attrs.isEmpty()) { + QTextCharFormat format; + format.setFontUnderline(true); + attrs<window()->focusObject()) { + m_composingFocusObject = fo; + QInputMethodQueryEvent queryEvent(Qt::ImEnabled); + if (QCoreApplication::sendEvent(fo, &queryEvent)) { + if (queryEvent.value(Qt::ImEnabled).toBool()) { + QInputMethodEvent e(preeditString, attrs); + QCoreApplication::sendEvent(fo, &e); + // prevent handleKeyEvent from sending a key event + m_sendKeyEvent = false; + } + } + } +} + +- (BOOL)hasMarkedText +{ + return (m_composingText.isEmpty() ? NO: YES); +} + +- (NSAttributedString *)attributedSubstringForProposedRange:(NSRange)aRange actualRange:(NSRangePointer)actualRange +{ + Q_UNUSED(actualRange) + QObject *fo = m_platformWindow->window()->focusObject(); + if (!fo) + return nil; + QInputMethodQueryEvent queryEvent(Qt::ImEnabled | Qt::ImCurrentSelection); + if (!QCoreApplication::sendEvent(fo, &queryEvent)) + return nil; + if (!queryEvent.value(Qt::ImEnabled).toBool()) + return nil; + + QString selectedText = queryEvent.value(Qt::ImCurrentSelection).toString(); + if (selectedText.isEmpty()) + return nil; + + QCFString string(selectedText.mid(aRange.location, aRange.length)); + const NSString *tmpString = reinterpret_cast((CFStringRef)string); + return [[[NSAttributedString alloc] initWithString:const_cast(tmpString)] autorelease]; +} + +- (NSRange)markedRange +{ + NSRange range; + if (!m_composingText.isEmpty()) { + range.location = 0; + range.length = m_composingText.length(); + } else { + range.location = NSNotFound; + range.length = 0; + } + return range; +} + +- (NSRange)selectedRange +{ + NSRange selectedRange = {0, 0}; + + QObject *fo = m_platformWindow->window()->focusObject(); + if (!fo) + return selectedRange; + QInputMethodQueryEvent queryEvent(Qt::ImEnabled | Qt::ImCurrentSelection); + if (!QCoreApplication::sendEvent(fo, &queryEvent)) + return selectedRange; + if (!queryEvent.value(Qt::ImEnabled).toBool()) + return selectedRange; + + QString selectedText = queryEvent.value(Qt::ImCurrentSelection).toString(); + + if (!selectedText.isEmpty()) { + selectedRange.location = 0; + selectedRange.length = selectedText.length(); + } + return selectedRange; +} + +- (NSRect)firstRectForCharacterRange:(NSRange)aRange actualRange:(NSRangePointer)actualRange +{ + Q_UNUSED(aRange) + Q_UNUSED(actualRange) + QObject *fo = m_platformWindow->window()->focusObject(); + if (!fo) + return NSZeroRect; + + QInputMethodQueryEvent queryEvent(Qt::ImEnabled); + if (!QCoreApplication::sendEvent(fo, &queryEvent)) + return NSZeroRect; + if (!queryEvent.value(Qt::ImEnabled).toBool()) + return NSZeroRect; + + if (!m_platformWindow->window()) + return NSZeroRect; + + // The returned rect is always based on the internal cursor. + QRect mr = qApp->inputMethod()->cursorRectangle().toRect(); + mr.moveBottomLeft(m_platformWindow->window()->mapToGlobal(mr.bottomLeft())); + return QCocoaScreen::mapToNative(mr); +} + +- (NSUInteger)characterIndexForPoint:(NSPoint)aPoint +{ + // We don't support cursor movements using mouse while composing. + Q_UNUSED(aPoint); + return NSNotFound; +} + +- (NSArray *)validAttributesForMarkedText +{ + if (!m_platformWindow) + return nil; + + if (m_platformWindow->window() != QGuiApplication::focusWindow()) + return nil; + + QObject *fo = m_platformWindow->window()->focusObject(); + if (!fo) + return nil; + + QInputMethodQueryEvent queryEvent(Qt::ImEnabled); + if (!QCoreApplication::sendEvent(fo, &queryEvent)) + return nil; + if (!queryEvent.value(Qt::ImEnabled).toBool()) + return nil; + + // Support only underline color/style. + return @[NSUnderlineColorAttributeName, NSUnderlineStyleAttributeName]; +} + +- (void)textInputContextKeyboardSelectionDidChangeNotification:(NSNotification *)textInputContextKeyboardSelectionDidChangeNotification +{ + Q_UNUSED(textInputContextKeyboardSelectionDidChangeNotification) + if (([NSApp keyWindow] == self.window) && self.window.firstResponder == self) { + QCocoaInputContext *ic = qobject_cast(QCocoaIntegration::instance()->inputContext()); + ic->updateLocale(); + } +} + +@end diff --git a/src/plugins/platforms/cocoa/qnsview_dragging.mm b/src/plugins/platforms/cocoa/qnsview_dragging.mm new file mode 100644 index 0000000000..e77a26eed0 --- /dev/null +++ b/src/plugins/platforms/cocoa/qnsview_dragging.mm @@ -0,0 +1,302 @@ +/**************************************************************************** +** +** 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 QT_MANGLE_NAMESPACE(QNSView) (Dragging) + +-(void)registerDragTypes +{ + QMacAutoReleasePool pool; + QStringList customTypes = qt_mac_enabledDraggedTypes(); + if (currentCustomDragTypes == 0 || *currentCustomDragTypes != customTypes) { + if (currentCustomDragTypes == 0) + currentCustomDragTypes = new QStringList(); + *currentCustomDragTypes = customTypes; + NSString * const mimeTypeGeneric = @"com.trolltech.qt.MimeTypeName"; + NSMutableArray *supportedTypes = [NSMutableArray 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 (int i = 0; i < customTypes.size(); i++) { + [supportedTypes addObject:customTypes[i].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 forbiden cursor wanted on non droppable 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 + if (m_updatingDrag) + return; + + const QPoint mousePos(QCursor::pos()); + CGEventRef moveEvent(CGEventCreateMouseEvent( + NULL, kCGEventMouseMoved, + CGPointMake(mousePos.x(), mousePos.y()), + kCGMouseButtonLeft // ignored + )); + CGEventPost(kCGHIDEventTap, moveEvent); + CFRelease(moveEvent); +} + +- (NSDragOperation)draggingEntered:(id )sender +{ + return [self handleDrag : sender]; +} + +- (NSDragOperation)draggingUpdated:(id )sender +{ + m_updatingDrag = true; + const NSDragOperation ret([self handleDrag : sender]); + m_updatingDrag = false; + + return ret; +} + +// Sends drag update to Qt, return the action +- (NSDragOperation)handleDrag:(id )sender +{ + if (!m_platformWindow) + return NSDragOperationNone; + + NSPoint windowPoint = [self convertPoint: [sender draggingLocation] fromView: nil]; + QPoint qt_windowPoint(windowPoint.x, windowPoint.y); + Qt::DropActions qtAllowed = qt_mac_mapNSDragOperations([sender draggingSourceOperationMask]); + + QWindow *target = findEventTargetWindow(m_platformWindow->window()); + if (!target) + return NSDragOperationNone; + + // update these so selecting move/copy/link works + QGuiApplicationPrivate::modifier_buttons = [QNSView convertKeyModifiers: [[NSApp currentEvent] modifierFlags]]; + + 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(), mapWindowCoordinates(m_platformWindow->window(), target, qt_windowPoint), qtAllowed); + [self updateCursorFromDragResponse:response drag:nativeDrag]; + } else { + QCocoaDropData mimeData([sender draggingPasteboard]); + response = QWindowSystemInterface::handleDrag(target, &mimeData, mapWindowCoordinates(m_platformWindow->window(), target, qt_windowPoint), qtAllowed); + } + + return qt_mac_mapDropAction(response.acceptedAction()); +} + +- (void)draggingExited:(id )sender +{ + if (!m_platformWindow) + return; + + QWindow *target = findEventTargetWindow(m_platformWindow->window()); + if (!target) + return; + + NSPoint windowPoint = [self convertPoint: [sender draggingLocation] fromView: nil]; + QPoint qt_windowPoint(windowPoint.x, windowPoint.y); + + // Send 0 mime data to indicate drag exit + QWindowSystemInterface::handleDrag(target, 0, mapWindowCoordinates(m_platformWindow->window(), target, qt_windowPoint), Qt::IgnoreAction); +} + +// called on drop, send the drop to Qt and return if it was accepted. +- (BOOL)performDragOperation:(id )sender +{ + if (!m_platformWindow) + return false; + + QWindow *target = findEventTargetWindow(m_platformWindow->window()); + if (!target) + return false; + + NSPoint windowPoint = [self convertPoint: [sender draggingLocation] fromView: nil]; + QPoint qt_windowPoint(windowPoint.x, windowPoint.y); + Qt::DropActions qtAllowed = qt_mac_mapNSDragOperations([sender draggingSourceOperationMask]); + + QPlatformDropQtResponse response(false, Qt::IgnoreAction); + QCocoaDrag* nativeDrag = QCocoaIntegration::instance()->drag(); + if (nativeDrag->currentDrag()) { + // The drag was started from within the application + response = QWindowSystemInterface::handleDrop(target, nativeDrag->dragMimeData(), mapWindowCoordinates(m_platformWindow->window(), target, qt_windowPoint), qtAllowed); + } else { + QCocoaDropData mimeData([sender draggingPasteboard]); + response = QWindowSystemInterface::handleDrop(target, &mimeData, mapWindowCoordinates(m_platformWindow->window(), target, qt_windowPoint), qtAllowed); + } + return response.isAccepted(); +} + +- (void)draggingSession:(NSDraggingSession *)session + endedAtPoint:(NSPoint)screenPoint + operation:(NSDragOperation)operation +{ + Q_UNUSED(session); + Q_UNUSED(operation); + + if (!m_platformWindow) + return; + + QWindow *target = findEventTargetWindow(m_platformWindow->window()); + if (!target) + return; + + QCocoaDrag* nativeDrag = QCocoaIntegration::instance()->drag(); + nativeDrag->setAcceptedAction(qt_mac_mapNSDragOperation(operation)); + + // keep our state, and QGuiApplication state (buttons member) in-sync, + // or future mouse events will be processed incorrectly + NSUInteger pmb = [NSEvent pressedMouseButtons]; + for (int buttonNumber = 0; buttonNumber < 32; buttonNumber++) { // see cocoaButton2QtButton() for the 32 value + if (!(pmb & (1 << buttonNumber))) + m_buttons &= ~cocoaButton2QtButton(buttonNumber); + } + + NSPoint windowPoint = [self.window convertRectFromScreen:NSMakeRect(screenPoint.x, screenPoint.y, 1, 1)].origin; + NSPoint nsViewPoint = [self convertPoint: windowPoint fromView: nil]; // NSView/QWindow coordinates + QPoint qtWindowPoint(nsViewPoint.x, nsViewPoint.y); + QPoint qtScreenPoint = QCocoaScreen::mapFromNative(screenPoint).toPoint(); + + QWindowSystemInterface::handleMouseEvent(target, mapWindowCoordinates(m_platformWindow->window(), target, qtWindowPoint), qtScreenPoint, m_buttons); +} + +@end diff --git a/src/plugins/platforms/cocoa/qnsview_gestures.mm b/src/plugins/platforms/cocoa/qnsview_gestures.mm new file mode 100644 index 0000000000..61d551ee0e --- /dev/null +++ b/src/plugins/platforms/cocoa/qnsview_gestures.mm @@ -0,0 +1,169 @@ +/**************************************************************************** +** +** 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 + +#ifndef QT_NO_GESTURES + +Q_LOGGING_CATEGORY(lcQpaGestures, "qt.qpa.input.gestures") + +@implementation QT_MANGLE_NAMESPACE(QNSView) (Gestures) + +- (bool)handleGestureAsBeginEnd:(NSEvent *)event +{ + if (QOperatingSystemVersion::current() < QOperatingSystemVersion::OSXElCapitan) + return false; + + if ([event phase] == NSEventPhaseBegan) { + [self beginGestureWithEvent:event]; + return true; + } + + if ([event phase] == NSEventPhaseEnded) { + [self endGestureWithEvent:event]; + return true; + } + + return false; +} +- (void)magnifyWithEvent:(NSEvent *)event +{ + if (!m_platformWindow) + return; + + if ([self handleGestureAsBeginEnd:event]) + return; + + qCDebug(lcQpaGestures) << "magnifyWithEvent" << [event magnification] << "from device" << hex << [event deviceID]; + const NSTimeInterval timestamp = [event timestamp]; + QPointF windowPoint; + QPointF screenPoint; + [self convertFromScreen:[self screenMousePoint:event] toWindowPoint:&windowPoint andScreenPoint:&screenPoint]; + QWindowSystemInterface::handleGestureEventWithRealValue(m_platformWindow->window(), QCocoaTouch::getTouchDevice(QTouchDevice::TouchPad, [event deviceID]), timestamp, Qt::ZoomNativeGesture, + [event magnification], windowPoint, screenPoint); +} + +- (void)smartMagnifyWithEvent:(NSEvent *)event +{ + if (!m_platformWindow) + return; + + static bool zoomIn = true; + qCDebug(lcQpaGestures) << "smartMagnifyWithEvent" << zoomIn << "from device" << hex << [event deviceID]; + const NSTimeInterval timestamp = [event timestamp]; + QPointF windowPoint; + QPointF screenPoint; + [self convertFromScreen:[self screenMousePoint:event] toWindowPoint:&windowPoint andScreenPoint:&screenPoint]; + QWindowSystemInterface::handleGestureEventWithRealValue(m_platformWindow->window(), QCocoaTouch::getTouchDevice(QTouchDevice::TouchPad, [event deviceID]), timestamp, Qt::SmartZoomNativeGesture, + zoomIn ? 1.0f : 0.0f, windowPoint, screenPoint); + zoomIn = !zoomIn; +} + +- (void)rotateWithEvent:(NSEvent *)event +{ + if (!m_platformWindow) + return; + + if ([self handleGestureAsBeginEnd:event]) + return; + + const NSTimeInterval timestamp = [event timestamp]; + QPointF windowPoint; + QPointF screenPoint; + [self convertFromScreen:[self screenMousePoint:event] toWindowPoint:&windowPoint andScreenPoint:&screenPoint]; + QWindowSystemInterface::handleGestureEventWithRealValue(m_platformWindow->window(), QCocoaTouch::getTouchDevice(QTouchDevice::TouchPad, [event deviceID]), timestamp, Qt::RotateNativeGesture, + -[event rotation], windowPoint, screenPoint); +} + +- (void)swipeWithEvent:(NSEvent *)event +{ + if (!m_platformWindow) + return; + + qCDebug(lcQpaGestures) << "swipeWithEvent" << [event deltaX] << [event deltaY] << "from device" << hex << [event deviceID]; + const NSTimeInterval timestamp = [event timestamp]; + QPointF windowPoint; + QPointF screenPoint; + [self convertFromScreen:[self screenMousePoint:event] toWindowPoint:&windowPoint andScreenPoint:&screenPoint]; + + qreal angle = 0.0f; + if ([event deltaX] == 1) + angle = 180.0f; + else if ([event deltaX] == -1) + angle = 0.0f; + else if ([event deltaY] == 1) + angle = 90.0f; + else if ([event deltaY] == -1) + angle = 270.0f; + + QWindowSystemInterface::handleGestureEventWithRealValue(m_platformWindow->window(), QCocoaTouch::getTouchDevice(QTouchDevice::TouchPad, [event deviceID]), timestamp, Qt::SwipeNativeGesture, + angle, windowPoint, screenPoint); +} + +- (void)beginGestureWithEvent:(NSEvent *)event +{ + if (!m_platformWindow) + return; + + const NSTimeInterval timestamp = [event timestamp]; + QPointF windowPoint; + QPointF screenPoint; + [self convertFromScreen:[self screenMousePoint:event] toWindowPoint:&windowPoint andScreenPoint:&screenPoint]; + qCDebug(lcQpaGestures) << "beginGestureWithEvent @" << windowPoint << "from device" << hex << [event deviceID]; + QWindowSystemInterface::handleGestureEvent(m_platformWindow->window(), QCocoaTouch::getTouchDevice(QTouchDevice::TouchPad, [event deviceID]), timestamp, Qt::BeginNativeGesture, + windowPoint, screenPoint); +} + +- (void)endGestureWithEvent:(NSEvent *)event +{ + if (!m_platformWindow) + return; + + qCDebug(lcQpaGestures) << "endGestureWithEvent" << "from device" << hex << [event deviceID]; + const NSTimeInterval timestamp = [event timestamp]; + QPointF windowPoint; + QPointF screenPoint; + [self convertFromScreen:[self screenMousePoint:event] toWindowPoint:&windowPoint andScreenPoint:&screenPoint]; + QWindowSystemInterface::handleGestureEvent(m_platformWindow->window(), QCocoaTouch::getTouchDevice(QTouchDevice::TouchPad, [event deviceID]), timestamp, Qt::EndNativeGesture, + windowPoint, screenPoint); +} + +@end + +#endif // QT_NO_GESTURES diff --git a/src/plugins/platforms/cocoa/qnsview_keys.mm b/src/plugins/platforms/cocoa/qnsview_keys.mm new file mode 100644 index 0000000000..57ce6a009f --- /dev/null +++ b/src/plugins/platforms/cocoa/qnsview_keys.mm @@ -0,0 +1,262 @@ +/**************************************************************************** +** +** 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 QT_MANGLE_NAMESPACE(QNSView) (KeysAPI) + ++ (Qt::KeyboardModifiers)convertKeyModifiers:(ulong)modifierFlags +{ + const bool dontSwapCtrlAndMeta = qApp->testAttribute(Qt::AA_MacDontSwapCtrlAndMeta); + Qt::KeyboardModifiers qtMods =Qt::NoModifier; + if (modifierFlags & NSShiftKeyMask) + qtMods |= Qt::ShiftModifier; + if (modifierFlags & NSControlKeyMask) + qtMods |= dontSwapCtrlAndMeta ? Qt::ControlModifier : Qt::MetaModifier; + if (modifierFlags & NSAlternateKeyMask) + qtMods |= Qt::AltModifier; + if (modifierFlags & NSCommandKeyMask) + qtMods |= dontSwapCtrlAndMeta ? Qt::MetaModifier : Qt::ControlModifier; + if (modifierFlags & NSNumericPadKeyMask) + qtMods |= Qt::KeypadModifier; + return qtMods; +} + +@end + +@implementation QT_MANGLE_NAMESPACE(QNSView) (Keys) + +- (int)convertKeyCode:(QChar)keyChar +{ + return qt_mac_cocoaKey2QtKey(keyChar); +} + +- (bool)handleKeyEvent:(NSEvent *)nsevent eventType:(int)eventType +{ + ulong timestamp = [nsevent timestamp] * 1000; + ulong nativeModifiers = [nsevent modifierFlags]; + Qt::KeyboardModifiers modifiers = [QNSView convertKeyModifiers: nativeModifiers]; + NSString *charactersIgnoringModifiers = [nsevent charactersIgnoringModifiers]; + NSString *characters = [nsevent characters]; + if (m_inputSource != characters) { + [m_inputSource release]; + m_inputSource = [characters retain]; + } + + // There is no way to get the scan code from carbon/cocoa. But we cannot + // use the value 0, since it indicates that the event originates from somewhere + // else than the keyboard. + quint32 nativeScanCode = 1; + quint32 nativeVirtualKey = [nsevent keyCode]; + + QChar ch = QChar::ReplacementCharacter; + int keyCode = Qt::Key_unknown; + + // If a dead key occurs as a result of pressing a key combination then + // characters will have 0 length, but charactersIgnoringModifiers will + // have a valid character in it. This enables key combinations such as + // ALT+E to be used as a shortcut with an English keyboard even though + // pressing ALT+E will give a dead key while doing normal text input. + if ([characters length] != 0 || [charactersIgnoringModifiers length] != 0) { + auto ctrlOrMetaModifier = qApp->testAttribute(Qt::AA_MacDontSwapCtrlAndMeta) ? Qt::ControlModifier : Qt::MetaModifier; + if (((modifiers & ctrlOrMetaModifier) || (modifiers & Qt::AltModifier)) && ([charactersIgnoringModifiers length] != 0)) + ch = QChar([charactersIgnoringModifiers characterAtIndex:0]); + else if ([characters length] != 0) + ch = QChar([characters characterAtIndex:0]); + keyCode = [self convertKeyCode:ch]; + } + + // we will send a key event unless the input method sets m_sendKeyEvent to false + m_sendKeyEvent = true; + QString text; + // ignore text for the U+F700-U+F8FF range. This is used by Cocoa when + // delivering function keys (e.g. arrow keys, backspace, F1-F35, etc.) + if (!(modifiers & (Qt::ControlModifier | Qt::MetaModifier)) && (ch.unicode() < 0xf700 || ch.unicode() > 0xf8ff)) + text = QString::fromNSString(characters); + + QWindow *window = [self topLevelWindow]; + + // Popups implicitly grab key events; forward to the active popup if there is one. + // This allows popups to e.g. intercept shortcuts and close the popup in response. + if (QCocoaWindow *popup = QCocoaIntegration::instance()->activePopupWindow()) { + if (!popup->window()->flags().testFlag(Qt::ToolTip)) + window = popup->window(); + } + + if (eventType == QEvent::KeyPress) { + + if (m_composingText.isEmpty()) { + m_sendKeyEvent = !QWindowSystemInterface::handleShortcutEvent(window, timestamp, keyCode, + modifiers, nativeScanCode, nativeVirtualKey, nativeModifiers, text, [nsevent isARepeat], 1); + + // Handling a shortcut may result in closing the window + if (!m_platformWindow) + return true; + } + + QObject *fo = m_platformWindow->window()->focusObject(); + if (m_sendKeyEvent && fo) { + QInputMethodQueryEvent queryEvent(Qt::ImEnabled | Qt::ImHints); + if (QCoreApplication::sendEvent(fo, &queryEvent)) { + bool imEnabled = queryEvent.value(Qt::ImEnabled).toBool(); + Qt::InputMethodHints hints = static_cast(queryEvent.value(Qt::ImHints).toUInt()); + if (imEnabled && !(hints & Qt::ImhDigitsOnly || hints & Qt::ImhFormattedNumbersOnly || hints & Qt::ImhHiddenText)) { + // pass the key event to the input method. note that m_sendKeyEvent may be set to false during this call + m_currentlyInterpretedKeyEvent = nsevent; + [self interpretKeyEvents:@[nsevent]]; + m_currentlyInterpretedKeyEvent = 0; + } + } + } + if (m_resendKeyEvent) + m_sendKeyEvent = true; + } + + bool accepted = true; + if (m_sendKeyEvent && m_composingText.isEmpty()) { + QWindowSystemInterface::handleExtendedKeyEvent(window, timestamp, QEvent::Type(eventType), keyCode, modifiers, + nativeScanCode, nativeVirtualKey, nativeModifiers, text, [nsevent isARepeat], 1, false); + accepted = QWindowSystemInterface::flushWindowSystemEvents(); + } + m_sendKeyEvent = false; + m_resendKeyEvent = false; + return accepted; +} + +- (void)keyDown:(NSEvent *)nsevent +{ + if ([self isTransparentForUserInput]) + return [super keyDown:nsevent]; + + const bool accepted = [self handleKeyEvent:nsevent eventType:int(QEvent::KeyPress)]; + + // When Qt is used to implement a plugin for a native application we + // want to propagate unhandled events to other native views. However, + // Qt does not always set the accepted state correctly (in particular + // for return key events), so do this for plugin applications only + // to prevent incorrect forwarding in the general case. + const bool shouldPropagate = QCoreApplication::testAttribute(Qt::AA_PluginApplication) && !accepted; + + // Track keyDown acceptance/forward state for later acceptance of the keyUp. + if (!shouldPropagate) + m_acceptedKeyDowns.insert([nsevent keyCode]); + + if (shouldPropagate) + [super keyDown:nsevent]; +} + +- (void)keyUp:(NSEvent *)nsevent +{ + if ([self isTransparentForUserInput]) + return [super keyUp:nsevent]; + + const bool keyUpAccepted = [self handleKeyEvent:nsevent eventType:int(QEvent::KeyRelease)]; + + // Propagate the keyUp if neither Qt accepted it nor the corresponding KeyDown was + // accepted. Qt text controls wil often not use and ignore keyUp events, but we + // want to avoid propagating unmatched keyUps. + const bool keyDownAccepted = m_acceptedKeyDowns.remove([nsevent keyCode]); + if (!keyUpAccepted && !keyDownAccepted) + [super keyUp:nsevent]; +} + +- (void)cancelOperation:(id)sender +{ + Q_UNUSED(sender); + + NSEvent *currentEvent = [NSApp currentEvent]; + if (!currentEvent || currentEvent.type != NSKeyDown) + return; + + // Handling the key event may recurse back here through interpretKeyEvents + // (when IM is enabled), so we need to guard against that. + if (currentEvent == m_currentlyInterpretedKeyEvent) + return; + + // Send Command+Key_Period and Escape as normal keypresses so that + // the key sequence is delivered through Qt. That way clients can + // intercept the shortcut and override its effect. + [self handleKeyEvent:currentEvent eventType:int(QEvent::KeyPress)]; +} + +- (void)flagsChanged:(NSEvent *)nsevent +{ + ulong timestamp = [nsevent timestamp] * 1000; + ulong modifiers = [nsevent modifierFlags]; + Qt::KeyboardModifiers qmodifiers = [QNSView convertKeyModifiers:modifiers]; + + // calculate the delta and remember the current modifiers for next time + static ulong m_lastKnownModifiers; + ulong lastKnownModifiers = m_lastKnownModifiers; + ulong delta = lastKnownModifiers ^ modifiers; + m_lastKnownModifiers = modifiers; + + struct qt_mac_enum_mapper + { + ulong mac_mask; + Qt::Key qt_code; + }; + static qt_mac_enum_mapper modifier_key_symbols[] = { + { NSShiftKeyMask, Qt::Key_Shift }, + { NSControlKeyMask, Qt::Key_Meta }, + { NSCommandKeyMask, Qt::Key_Control }, + { NSAlternateKeyMask, Qt::Key_Alt }, + { NSAlphaShiftKeyMask, Qt::Key_CapsLock }, + { 0ul, Qt::Key_unknown } }; + for (int i = 0; modifier_key_symbols[i].mac_mask != 0u; ++i) { + uint mac_mask = modifier_key_symbols[i].mac_mask; + if ((delta & mac_mask) == 0u) + continue; + + Qt::Key qtCode = modifier_key_symbols[i].qt_code; + if (qApp->testAttribute(Qt::AA_MacDontSwapCtrlAndMeta)) { + if (qtCode == Qt::Key_Meta) + qtCode = Qt::Key_Control; + else if (qtCode == Qt::Key_Control) + qtCode = Qt::Key_Meta; + } + QWindowSystemInterface::handleKeyEvent(m_platformWindow->window(), + timestamp, + (lastKnownModifiers & mac_mask) ? QEvent::KeyRelease : QEvent::KeyPress, + qtCode, + qmodifiers ^ [QNSView convertKeyModifiers:mac_mask]); + } +} + +@end diff --git a/src/plugins/platforms/cocoa/qnsview_mouse.mm b/src/plugins/platforms/cocoa/qnsview_mouse.mm new file mode 100644 index 0000000000..77af50119d --- /dev/null +++ b/src/plugins/platforms/cocoa/qnsview_mouse.mm @@ -0,0 +1,602 @@ +/**************************************************************************** +** +** 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 QT_MANGLE_NAMESPACE(QNSViewMouseMoveHelper) { + QNSView *view; +} + +- (instancetype)initWithView:(QNSView *)theView +{ + if ((self = [super init])) + view = theView; + + return self; +} + +- (void)mouseMoved:(NSEvent *)theEvent +{ + [view mouseMovedImpl:theEvent]; +} + +- (void)mouseEntered:(NSEvent *)theEvent +{ + [view mouseEnteredImpl:theEvent]; +} + +- (void)mouseExited:(NSEvent *)theEvent +{ + [view mouseExitedImpl:theEvent]; +} + +- (void)cursorUpdate:(NSEvent *)theEvent +{ + [view cursorUpdate:theEvent]; +} + +@end + +@implementation QT_MANGLE_NAMESPACE(QNSView) (MouseAPI) + +- (void)resetMouseButtons +{ + m_buttons = Qt::NoButton; + m_frameStrutButtons = Qt::NoButton; +} + +- (void)handleFrameStrutMouseEvent:(NSEvent *)theEvent +{ + if (!m_platformWindow) + return; + + // get m_buttons in sync + // Don't send frme strut events if we are in the middle of a mouse drag. + if (m_buttons != Qt::NoButton) + return; + + NSEventType ty = [theEvent type]; + switch (ty) { + case NSLeftMouseDown: + m_frameStrutButtons |= Qt::LeftButton; + break; + case NSLeftMouseUp: + m_frameStrutButtons &= ~Qt::LeftButton; + break; + case NSRightMouseDown: + m_frameStrutButtons |= Qt::RightButton; + break; + case NSLeftMouseDragged: + m_frameStrutButtons |= Qt::LeftButton; + break; + case NSRightMouseDragged: + m_frameStrutButtons |= Qt::RightButton; + break; + case NSRightMouseUp: + m_frameStrutButtons &= ~Qt::RightButton; + break; + case NSOtherMouseDown: + m_frameStrutButtons |= cocoaButton2QtButton([theEvent buttonNumber]); + break; + case NSOtherMouseUp: + m_frameStrutButtons &= ~cocoaButton2QtButton([theEvent buttonNumber]); + default: + break; + } + + NSWindow *window = [self window]; + NSPoint windowPoint = [theEvent locationInWindow]; + + int windowScreenY = [window frame].origin.y + [window frame].size.height; + NSPoint windowCoord = [self convertPoint:[self frame].origin toView:nil]; + int viewScreenY = [window convertRectToScreen:NSMakeRect(windowCoord.x, windowCoord.y, 0, 0)].origin.y; + int titleBarHeight = windowScreenY - viewScreenY; + + NSPoint nsViewPoint = [self convertPoint: windowPoint fromView: nil]; + QPoint qtWindowPoint = QPoint(nsViewPoint.x, titleBarHeight + nsViewPoint.y); + NSPoint screenPoint = [window convertRectToScreen:NSMakeRect(windowPoint.x, windowPoint.y, 0, 0)].origin; + QPoint qtScreenPoint = QCocoaScreen::mapFromNative(screenPoint).toPoint(); + + ulong timestamp = [theEvent timestamp] * 1000; + QWindowSystemInterface::handleFrameStrutMouseEvent(m_platformWindow->window(), timestamp, qtWindowPoint, qtScreenPoint, m_frameStrutButtons); +} +@end + +@implementation QT_MANGLE_NAMESPACE(QNSView) (Mouse) + +- (BOOL)acceptsFirstMouse:(NSEvent *)theEvent +{ + Q_UNUSED(theEvent) + if (!m_platformWindow) + return NO; + if ([self isTransparentForUserInput]) + return NO; + return YES; +} + +- (NSPoint)screenMousePoint:(NSEvent *)theEvent +{ + NSPoint screenPoint; + if (theEvent) { + NSPoint windowPoint = [theEvent locationInWindow]; + NSRect screenRect = [[theEvent window] convertRectToScreen:NSMakeRect(windowPoint.x, windowPoint.y, 1, 1)]; + screenPoint = screenRect.origin; + } else { + screenPoint = [NSEvent mouseLocation]; + } + return screenPoint; +} + +- (void)handleMouseEvent:(NSEvent *)theEvent +{ + if (!m_platformWindow) + return; + +#ifndef QT_NO_TABLETEVENT + // Tablet events may come in via the mouse event handlers, + // check if this is a valid tablet event first. + if ([self handleTabletEvent: theEvent]) + return; +#endif + + QPointF qtWindowPoint; + QPointF qtScreenPoint; + QNSView *targetView = self; + if (!targetView.platformWindow) + return; + + // Popups implicitly grap mouse events; forward to the active popup if there is one + if (QCocoaWindow *popup = QCocoaIntegration::instance()->activePopupWindow()) { + // Tooltips must be transparent for mouse events + // The bug reference is QTBUG-46379 + if (!popup->window()->flags().testFlag(Qt::ToolTip)) { + if (QNSView *popupView = qnsview_cast(popup->view())) + targetView = popupView; + } + } + + [targetView convertFromScreen:[self screenMousePoint:theEvent] toWindowPoint:&qtWindowPoint andScreenPoint:&qtScreenPoint]; + ulong timestamp = [theEvent timestamp] * 1000; + + QCocoaDrag* nativeDrag = QCocoaIntegration::instance()->drag(); + nativeDrag->setLastMouseEvent(theEvent, self); + + Qt::KeyboardModifiers keyboardModifiers = [QNSView convertKeyModifiers:[theEvent modifierFlags]]; + QWindowSystemInterface::handleMouseEvent(targetView->m_platformWindow->window(), timestamp, qtWindowPoint, qtScreenPoint, + m_buttons, keyboardModifiers, Qt::MouseEventNotSynthesized); +} + +- (bool)handleMouseDownEvent:(NSEvent *)theEvent withButton:(int)buttonNumber +{ + if ([self isTransparentForUserInput]) + return false; + + Qt::MouseButton button = cocoaButton2QtButton(buttonNumber); + + QPointF qtWindowPoint; + QPointF qtScreenPoint; + [self convertFromScreen:[self screenMousePoint:theEvent] toWindowPoint:&qtWindowPoint andScreenPoint:&qtScreenPoint]; + Q_UNUSED(qtScreenPoint); + + // Maintain masked state for the button for use by MouseDragged and MouseUp. + QRegion mask = m_platformWindow->window()->mask(); + const bool masked = !mask.isEmpty() && !mask.contains(qtWindowPoint.toPoint()); + if (masked) + m_acceptedMouseDowns &= ~button; + else + m_acceptedMouseDowns |= button; + + // Forward masked out events to the next responder + if (masked) + return false; + + m_buttons |= button; + + [self handleMouseEvent:theEvent]; + return true; +} + +- (bool)handleMouseDraggedEvent:(NSEvent *)theEvent withButton:(int)buttonNumber +{ + if ([self isTransparentForUserInput]) + return false; + + Qt::MouseButton button = cocoaButton2QtButton(buttonNumber); + + // Forward the event to the next responder if Qt did not accept the + // corresponding mouse down for this button + if (!(m_acceptedMouseDowns & button) == button) + return false; + + [self handleMouseEvent:theEvent]; + return true; +} + +- (bool)handleMouseUpEvent:(NSEvent *)theEvent withButton:(int)buttonNumber +{ + if ([self isTransparentForUserInput]) + return false; + + Qt::MouseButton button = cocoaButton2QtButton(buttonNumber); + + // Forward the event to the next responder if Qt did not accept the + // corresponding mouse down for this button + if (!(m_acceptedMouseDowns & button) == button) + return false; + + if (m_sendUpAsRightButton && button == Qt::LeftButton) + button = Qt::RightButton; + if (button == Qt::RightButton) + m_sendUpAsRightButton = false; + + m_buttons &= ~button; + + [self handleMouseEvent:theEvent]; + return true; +} + +- (void)mouseDown:(NSEvent *)theEvent +{ + if ([self isTransparentForUserInput]) + return [super mouseDown:theEvent]; + m_sendUpAsRightButton = false; + + // Handle any active poup windows; clicking outisde them should close them + // all. Don't do anything or clicks inside one of the menus, let Cocoa + // handle that case. Note that in practice many windows of the Qt::Popup type + // will actually close themselves in this case using logic implemented in + // that particular poup type (for example context menus). However, Qt expects + // that plain popup QWindows will also be closed, so we implement the logic + // here as well. + QList *popups = QCocoaIntegration::instance()->popupWindowStack(); + if (!popups->isEmpty()) { + // Check if the click is outside all popups. + bool inside = false; + QPointF qtScreenPoint = QCocoaScreen::mapFromNative([self screenMousePoint:theEvent]); + for (QList::const_iterator it = popups->begin(); it != popups->end(); ++it) { + if ((*it)->geometry().contains(qtScreenPoint.toPoint())) { + inside = true; + break; + } + } + // Close the popups if the click was outside. + if (!inside) { + Qt::WindowType type = QCocoaIntegration::instance()->activePopupWindow()->window()->type(); + while (QCocoaWindow *popup = QCocoaIntegration::instance()->popPopupWindow()) { + QWindowSystemInterface::handleCloseEvent(popup->window()); + QWindowSystemInterface::flushWindowSystemEvents(); + } + // Consume the mouse event when closing the popup, except for tool tips + // were it's expected that the event is processed normally. + if (type != Qt::ToolTip) + return; + } + } + + QPointF qtWindowPoint; + QPointF qtScreenPoint; + [self convertFromScreen:[self screenMousePoint:theEvent] toWindowPoint:&qtWindowPoint andScreenPoint:&qtScreenPoint]; + Q_UNUSED(qtScreenPoint); + + QRegion mask = m_platformWindow->window()->mask(); + const bool masked = !mask.isEmpty() && !mask.contains(qtWindowPoint.toPoint()); + // Maintain masked state for the button for use by MouseDragged and Up. + if (masked) + m_acceptedMouseDowns &= ~Qt::LeftButton; + else + m_acceptedMouseDowns |= Qt::LeftButton; + + // Forward masked out events to the next responder + if (masked) { + [super mouseDown:theEvent]; + return; + } + + if ([self hasMarkedText]) { + [[NSTextInputContext currentInputContext] handleEvent:theEvent]; + } else { + auto ctrlOrMetaModifier = qApp->testAttribute(Qt::AA_MacDontSwapCtrlAndMeta) ? Qt::ControlModifier : Qt::MetaModifier; + if (!m_dontOverrideCtrlLMB && [QNSView convertKeyModifiers:[theEvent modifierFlags]] & ctrlOrMetaModifier) { + m_buttons |= Qt::RightButton; + m_sendUpAsRightButton = true; + } else { + m_buttons |= Qt::LeftButton; + } + [self handleMouseEvent:theEvent]; + } +} + +- (void)mouseDragged:(NSEvent *)theEvent +{ + const bool accepted = [self handleMouseDraggedEvent:theEvent withButton:[theEvent buttonNumber]]; + if (!accepted) + [super mouseDragged:theEvent]; +} + +- (void)mouseUp:(NSEvent *)theEvent +{ + const bool accepted = [self handleMouseUpEvent:theEvent withButton:[theEvent buttonNumber]]; + if (!accepted) + [super mouseUp:theEvent]; +} + +- (void)rightMouseDown:(NSEvent *)theEvent +{ + // Wacom tablet might not return the correct button number for NSEvent buttonNumber + // on right clicks. Decide here that the button is the "right" button and forward + // the button number to the mouse (and tablet) handler. + const bool accepted = [self handleMouseDownEvent:theEvent withButton:1]; + if (!accepted) + [super rightMouseDown:theEvent]; +} + +- (void)rightMouseDragged:(NSEvent *)theEvent +{ + const bool accepted = [self handleMouseDraggedEvent:theEvent withButton:1]; + if (!accepted) + [super rightMouseDragged:theEvent]; +} + +- (void)rightMouseUp:(NSEvent *)theEvent +{ + const bool accepted = [self handleMouseUpEvent:theEvent withButton:1]; + if (!accepted) + [super rightMouseUp:theEvent]; +} + +- (void)otherMouseDown:(NSEvent *)theEvent +{ + const bool accepted = [self handleMouseDownEvent:theEvent withButton:[theEvent buttonNumber]]; + if (!accepted) + [super otherMouseDown:theEvent]; +} + +- (void)otherMouseDragged:(NSEvent *)theEvent +{ + const bool accepted = [self handleMouseDraggedEvent:theEvent withButton:[theEvent buttonNumber]]; + if (!accepted) + [super otherMouseDragged:theEvent]; +} + +- (void)otherMouseUp:(NSEvent *)theEvent +{ + const bool accepted = [self handleMouseUpEvent:theEvent withButton:[theEvent buttonNumber]]; + if (!accepted) + [super otherMouseUp:theEvent]; +} + +- (void)updateTrackingAreas +{ + [super updateTrackingAreas]; + + QMacAutoReleasePool pool; + + // NSTrackingInVisibleRect keeps care of updating once the tracking is set up, so bail out early + if (m_trackingArea && [[self trackingAreas] containsObject:m_trackingArea]) + return; + + // Ideally, we shouldn't have NSTrackingMouseMoved events included below, it should + // only be turned on if mouseTracking, hover is on or a tool tip is set. + // Unfortunately, Qt will send "tooltip" events on mouse moves, so we need to + // turn it on in ALL case. That means EVERY QWindow gets to pay the cost of + // mouse moves delivered to it (Apple recommends keeping it OFF because there + // is a performance hit). So it goes. + NSUInteger trackingOptions = NSTrackingMouseEnteredAndExited | NSTrackingActiveInActiveApp + | NSTrackingInVisibleRect | NSTrackingMouseMoved | NSTrackingCursorUpdate; + [m_trackingArea release]; + m_trackingArea = [[NSTrackingArea alloc] initWithRect:[self frame] + options:trackingOptions + owner:m_mouseMoveHelper + userInfo:nil]; + [self addTrackingArea:m_trackingArea]; +} + +- (void)cursorUpdate:(NSEvent *)theEvent +{ + qCDebug(lcQpaMouse) << "[QNSView cursorUpdate:]" << self.cursor; + + // Note: We do not get this callback when moving from a subview that + // uses the legacy cursorRect API, so the cursor is reset to the arrow + // cursor. See rdar://34183708 + + if (self.cursor) + [self.cursor set]; + else + [super cursorUpdate:theEvent]; +} + +- (void)mouseMovedImpl:(NSEvent *)theEvent +{ + if (!m_platformWindow) + return; + + if ([self isTransparentForUserInput]) + return; + + QPointF windowPoint; + QPointF screenPoint; + [self convertFromScreen:[self screenMousePoint:theEvent] toWindowPoint:&windowPoint andScreenPoint:&screenPoint]; + QWindow *childWindow = m_platformWindow->childWindowAt(windowPoint.toPoint()); + + // Top-level windows generate enter-leave events for sub-windows. + // Qt wants to know which window (if any) will be entered at the + // the time of the leave. This is dificult to accomplish by + // handling mouseEnter and mouseLeave envents, since they are sent + // individually to different views. + if (m_platformWindow->isContentView() && childWindow) { + if (childWindow != m_platformWindow->m_enterLeaveTargetWindow) { + QWindowSystemInterface::handleEnterLeaveEvent(childWindow, m_platformWindow->m_enterLeaveTargetWindow, windowPoint, screenPoint); + m_platformWindow->m_enterLeaveTargetWindow = childWindow; + } + } + + // Cocoa keeps firing mouse move events for obscured parent views. Qt should not + // send those events so filter them out here. + if (childWindow != m_platformWindow->window()) + return; + + [self handleMouseEvent: theEvent]; +} + +- (void)mouseEnteredImpl:(NSEvent *)theEvent +{ + Q_UNUSED(theEvent) + if (!m_platformWindow) + return; + + m_platformWindow->m_windowUnderMouse = true; + + if ([self isTransparentForUserInput]) + return; + + // Top-level windows generate enter events for sub-windows. + if (!m_platformWindow->isContentView()) + return; + + QPointF windowPoint; + QPointF screenPoint; + [self convertFromScreen:[self screenMousePoint:theEvent] toWindowPoint:&windowPoint andScreenPoint:&screenPoint]; + m_platformWindow->m_enterLeaveTargetWindow = m_platformWindow->childWindowAt(windowPoint.toPoint()); + QWindowSystemInterface::handleEnterEvent(m_platformWindow->m_enterLeaveTargetWindow, windowPoint, screenPoint); +} + +- (void)mouseExitedImpl:(NSEvent *)theEvent +{ + Q_UNUSED(theEvent); + if (!m_platformWindow) + return; + + m_platformWindow->m_windowUnderMouse = false; + + if ([self isTransparentForUserInput]) + return; + + // Top-level windows generate leave events for sub-windows. + if (!m_platformWindow->isContentView()) + return; + + QWindowSystemInterface::handleLeaveEvent(m_platformWindow->m_enterLeaveTargetWindow); + m_platformWindow->m_enterLeaveTargetWindow = 0; +} + +#if QT_CONFIG(wheelevent) +- (void)scrollWheel:(NSEvent *)theEvent +{ + if (!m_platformWindow) + return; + + if ([self isTransparentForUserInput]) + return [super scrollWheel:theEvent]; + + QPoint angleDelta; + Qt::MouseEventSource source = Qt::MouseEventNotSynthesized; + if ([theEvent hasPreciseScrollingDeltas]) { + // The mouse device contains pixel scroll wheel support (Mighty Mouse, Trackpad). + // Since deviceDelta is delivered as pixels rather than degrees, we need to + // convert from pixels to degrees in a sensible manner. + // It looks like 1/4 degrees per pixel behaves most native. + // (NB: Qt expects the unit for delta to be 8 per degree): + const int pixelsToDegrees = 2; // 8 * 1/4 + angleDelta.setX([theEvent scrollingDeltaX] * pixelsToDegrees); + angleDelta.setY([theEvent scrollingDeltaY] * pixelsToDegrees); + source = Qt::MouseEventSynthesizedBySystem; + } else { + // Remove acceleration, and use either -120 or 120 as delta: + angleDelta.setX(qBound(-120, int([theEvent deltaX] * 10000), 120)); + angleDelta.setY(qBound(-120, int([theEvent deltaY] * 10000), 120)); + } + + QPoint pixelDelta; + if ([theEvent hasPreciseScrollingDeltas]) { + pixelDelta.setX([theEvent scrollingDeltaX]); + pixelDelta.setY([theEvent scrollingDeltaY]); + } else { + // docs: "In the case of !hasPreciseScrollingDeltas, multiply the delta with the line width." + // scrollingDeltaX seems to return a minimum value of 0.1 in this case, map that to two pixels. + const CGFloat lineWithEstimate = 20.0; + pixelDelta.setX([theEvent scrollingDeltaX] * lineWithEstimate); + pixelDelta.setY([theEvent scrollingDeltaY] * lineWithEstimate); + } + + QPointF qt_windowPoint; + QPointF qt_screenPoint; + [self convertFromScreen:[self screenMousePoint:theEvent] toWindowPoint:&qt_windowPoint andScreenPoint:&qt_screenPoint]; + NSTimeInterval timestamp = [theEvent timestamp]; + ulong qt_timestamp = timestamp * 1000; + + // Prevent keyboard modifier state from changing during scroll event streams. + // A two-finger trackpad flick generates a stream of scroll events. We want + // the keyboard modifier state to be the state at the beginning of the + // flick in order to avoid changing the interpretation of the events + // mid-stream. One example of this happening would be when pressing cmd + // after scrolling in Qt Creator: not taking the phase into account causes + // the end of the event stream to be interpreted as font size changes. + NSEventPhase momentumPhase = [theEvent momentumPhase]; + if (momentumPhase == NSEventPhaseNone) { + currentWheelModifiers = [QNSView convertKeyModifiers:[theEvent modifierFlags]]; + } + + NSEventPhase phase = [theEvent phase]; + Qt::ScrollPhase ph = Qt::ScrollUpdate; + + // MayBegin is likely to happen. We treat it the same as an actual begin. + if (phase == NSEventPhaseMayBegin) { + m_scrolling = true; + ph = Qt::ScrollBegin; + } else if (phase == NSEventPhaseBegan) { + // If MayBegin did not happen, Began is the actual beginning. + if (!m_scrolling) + ph = Qt::ScrollBegin; + m_scrolling = true; + } else if (phase == NSEventPhaseEnded || phase == NSEventPhaseCancelled || + momentumPhase == NSEventPhaseEnded || momentumPhase == NSEventPhaseCancelled) { + ph = Qt::ScrollEnd; + m_scrolling = false; + } else if (phase == NSEventPhaseNone && momentumPhase == NSEventPhaseNone) { + ph = Qt::NoScrollPhase; + } + // "isInverted": natural OS X scrolling, inverted from the Qt/other platform/Jens perspective. + bool isInverted = [theEvent isDirectionInvertedFromDevice]; + + qCDebug(lcQpaMouse) << "scroll wheel @ window pos" << qt_windowPoint << "delta px" << pixelDelta << "angle" << angleDelta << "phase" << ph << (isInverted ? "inverted" : ""); + QWindowSystemInterface::handleWheelEvent(m_platformWindow->window(), qt_timestamp, qt_windowPoint, qt_screenPoint, pixelDelta, angleDelta, currentWheelModifiers, ph, source, isInverted); +} +#endif // QT_CONFIG(wheelevent) + +@end diff --git a/src/plugins/platforms/cocoa/qnsview_tablet.mm b/src/plugins/platforms/cocoa/qnsview_tablet.mm new file mode 100644 index 0000000000..0b56123955 --- /dev/null +++ b/src/plugins/platforms/cocoa/qnsview_tablet.mm @@ -0,0 +1,223 @@ +/**************************************************************************** +** +** 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 + +#ifndef QT_NO_TABLETEVENT + +Q_LOGGING_CATEGORY(lcQpaTablet, "qt.qpa.input.tablet") + +struct QCocoaTabletDeviceData +{ + QTabletEvent::TabletDevice device; + QTabletEvent::PointerType pointerType; + uint capabilityMask; + qint64 uid; +}; + +typedef QHash QCocoaTabletDeviceDataHash; +Q_GLOBAL_STATIC(QCocoaTabletDeviceDataHash, tabletDeviceDataHash) + +@implementation QT_MANGLE_NAMESPACE(QNSView) (Tablet) + +- (bool)handleTabletEvent:(NSEvent *)theEvent +{ + static bool ignoreButtonMapping = qEnvironmentVariableIsSet("QT_MAC_TABLET_IGNORE_BUTTON_MAPPING"); + + if (!m_platformWindow) + return false; + + NSEventType eventType = [theEvent type]; + if (eventType != NSTabletPoint && [theEvent subtype] != NSTabletPointEventSubtype) + return false; // Not a tablet event. + + ulong timestamp = [theEvent timestamp] * 1000; + + QPointF windowPoint; + QPointF screenPoint; + [self convertFromScreen:[self screenMousePoint:theEvent] toWindowPoint: &windowPoint andScreenPoint: &screenPoint]; + + uint deviceId = [theEvent deviceID]; + if (!tabletDeviceDataHash->contains(deviceId)) { + // Error: Unknown tablet device. Qt also gets into this state + // when running on a VM. This appears to be harmless; don't + // print a warning. + return false; + } + const QCocoaTabletDeviceData &deviceData = tabletDeviceDataHash->value(deviceId); + + bool down = (eventType != NSMouseMoved); + + qreal pressure; + if (down) { + pressure = [theEvent pressure]; + } else { + pressure = 0.0; + } + + NSPoint tilt = [theEvent tilt]; + int xTilt = qRound(tilt.x * 60.0); + int yTilt = qRound(tilt.y * -60.0); + qreal tangentialPressure = 0; + qreal rotation = 0; + int z = 0; + if (deviceData.capabilityMask & 0x0200) + z = [theEvent absoluteZ]; + + if (deviceData.capabilityMask & 0x0800) + tangentialPressure = ([theEvent tangentialPressure] * 2.0) - 1.0; + + rotation = 360.0 - [theEvent rotation]; + if (rotation > 180.0) + rotation -= 360.0; + + Qt::KeyboardModifiers keyboardModifiers = [QNSView convertKeyModifiers:[theEvent modifierFlags]]; + Qt::MouseButtons buttons = ignoreButtonMapping ? static_cast(static_cast([theEvent buttonMask])) : m_buttons; + + qCDebug(lcQpaTablet, "event on tablet %d with tool %d type %d unique ID %lld pos %6.1f, %6.1f root pos %6.1f, %6.1f buttons 0x%x pressure %4.2lf tilt %d, %d rotation %6.2lf", + deviceId, deviceData.device, deviceData.pointerType, deviceData.uid, + windowPoint.x(), windowPoint.y(), screenPoint.x(), screenPoint.y(), + static_cast(buttons), pressure, xTilt, yTilt, rotation); + + QWindowSystemInterface::handleTabletEvent(m_platformWindow->window(), timestamp, windowPoint, screenPoint, + deviceData.device, deviceData.pointerType, buttons, pressure, xTilt, yTilt, + tangentialPressure, rotation, z, deviceData.uid, + keyboardModifiers); + return true; +} + +- (void)tabletPoint:(NSEvent *)theEvent +{ + if ([self isTransparentForUserInput]) + return [super tabletPoint:theEvent]; + + [self handleTabletEvent: theEvent]; +} + +static QTabletEvent::TabletDevice wacomTabletDevice(NSEvent *theEvent) +{ + qint64 uid = [theEvent uniqueID]; + uint bits = [theEvent vendorPointingDeviceType]; + if (bits == 0 && uid != 0) { + // Fallback. It seems that the driver doesn't always include all the information. + // High-End Wacom devices store their "type" in the uper bits of the Unique ID. + // I'm not sure how to handle it for consumer devices, but I'll test that in a bit. + bits = uid >> 32; + } + + QTabletEvent::TabletDevice device; + // Defined in the "EN0056-NxtGenImpGuideX" + // on Wacom's Developer Website (www.wacomeng.com) + if (((bits & 0x0006) == 0x0002) && ((bits & 0x0F06) != 0x0902)) { + device = QTabletEvent::Stylus; + } else { + switch (bits & 0x0F06) { + case 0x0802: + device = QTabletEvent::Stylus; + break; + case 0x0902: + device = QTabletEvent::Airbrush; + break; + case 0x0004: + device = QTabletEvent::FourDMouse; + break; + case 0x0006: + device = QTabletEvent::Puck; + break; + case 0x0804: + device = QTabletEvent::RotationStylus; + break; + default: + device = QTabletEvent::NoDevice; + } + } + return device; +} + +- (void)tabletProximity:(NSEvent *)theEvent +{ + if ([self isTransparentForUserInput]) + return [super tabletProximity:theEvent]; + + ulong timestamp = [theEvent timestamp] * 1000; + + QCocoaTabletDeviceData deviceData; + deviceData.uid = [theEvent uniqueID]; + deviceData.capabilityMask = [theEvent capabilityMask]; + + switch ([theEvent pointingDeviceType]) { + case NSUnknownPointingDevice: + default: + deviceData.pointerType = QTabletEvent::UnknownPointer; + break; + case NSPenPointingDevice: + deviceData.pointerType = QTabletEvent::Pen; + break; + case NSCursorPointingDevice: + deviceData.pointerType = QTabletEvent::Cursor; + break; + case NSEraserPointingDevice: + deviceData.pointerType = QTabletEvent::Eraser; + break; + } + + deviceData.device = wacomTabletDevice(theEvent); + + // The deviceID is "unique" while in the proximity, it's a key that we can use for + // linking up QCocoaTabletDeviceData to an event (especially if there are two devices in action). + bool entering = [theEvent isEnteringProximity]; + uint deviceId = [theEvent deviceID]; + if (entering) { + tabletDeviceDataHash->insert(deviceId, deviceData); + } else { + tabletDeviceDataHash->remove(deviceId); + } + + qCDebug(lcQpaTablet, "proximity change on tablet %d: current tool %d type %d unique ID %lld", + deviceId, deviceData.device, deviceData.pointerType, deviceData.uid); + + if (entering) { + QWindowSystemInterface::handleTabletEnterProximityEvent(timestamp, deviceData.device, deviceData.pointerType, deviceData.uid); + } else { + QWindowSystemInterface::handleTabletLeaveProximityEvent(timestamp, deviceData.device, deviceData.pointerType, deviceData.uid); + } +} +@end + +#endif diff --git a/src/plugins/platforms/cocoa/qnsview_touch.mm b/src/plugins/platforms/cocoa/qnsview_touch.mm new file mode 100644 index 0000000000..e789213f70 --- /dev/null +++ b/src/plugins/platforms/cocoa/qnsview_touch.mm @@ -0,0 +1,100 @@ +/**************************************************************************** +** +** 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 + +Q_LOGGING_CATEGORY(lcQpaTouch, "qt.qpa.input.touch") + +@implementation QT_MANGLE_NAMESPACE(QNSView) (Touch) + +- (bool)shouldSendSingleTouch +{ + if (!m_platformWindow) + return true; + + // QtWidgets expects single-point touch events, QtDeclarative does not. + // Until there is an API we solve this by looking at the window class type. + return m_platformWindow->window()->inherits("QWidgetWindow"); +} + +- (void)touchesBeganWithEvent:(NSEvent *)event +{ + if (!m_platformWindow) + return; + + const NSTimeInterval timestamp = [event timestamp]; + const QList points = QCocoaTouch::getCurrentTouchPointList(event, [self shouldSendSingleTouch]); + qCDebug(lcQpaTouch) << "touchesBeganWithEvent" << points << "from device" << hex << [event deviceID]; + QWindowSystemInterface::handleTouchEvent(m_platformWindow->window(), timestamp * 1000, QCocoaTouch::getTouchDevice(QTouchDevice::TouchPad, [event deviceID]), points); +} + +- (void)touchesMovedWithEvent:(NSEvent *)event +{ + if (!m_platformWindow) + return; + + const NSTimeInterval timestamp = [event timestamp]; + const QList points = QCocoaTouch::getCurrentTouchPointList(event, [self shouldSendSingleTouch]); + qCDebug(lcQpaTouch) << "touchesMovedWithEvent" << points << "from device" << hex << [event deviceID]; + QWindowSystemInterface::handleTouchEvent(m_platformWindow->window(), timestamp * 1000, QCocoaTouch::getTouchDevice(QTouchDevice::TouchPad, [event deviceID]), points); +} + +- (void)touchesEndedWithEvent:(NSEvent *)event +{ + if (!m_platformWindow) + return; + + const NSTimeInterval timestamp = [event timestamp]; + const QList points = QCocoaTouch::getCurrentTouchPointList(event, [self shouldSendSingleTouch]); + qCDebug(lcQpaTouch) << "touchesEndedWithEvent" << points << "from device" << hex << [event deviceID]; + QWindowSystemInterface::handleTouchEvent(m_platformWindow->window(), timestamp * 1000, QCocoaTouch::getTouchDevice(QTouchDevice::TouchPad, [event deviceID]), points); +} + +- (void)touchesCancelledWithEvent:(NSEvent *)event +{ + if (!m_platformWindow) + return; + + const NSTimeInterval timestamp = [event timestamp]; + const QList points = QCocoaTouch::getCurrentTouchPointList(event, [self shouldSendSingleTouch]); + qCDebug(lcQpaTouch) << "touchesCancelledWithEvent" << points << "from device" << hex << [event deviceID]; + QWindowSystemInterface::handleTouchEvent(m_platformWindow->window(), timestamp * 1000, QCocoaTouch::getTouchDevice(QTouchDevice::TouchPad, [event deviceID]), points); +} + +@end