Qt
Internal/Contributor docs for the Qt SDK. <b>Note:</b> These are NOT official API docs; those are found <a href='https://doc.qt.io/'>here</a>.
Loading...
Searching...
No Matches
qnsview_mouse.mm
Go to the documentation of this file.
1// Copyright (C) 2018 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
4// This file is included from qnsview.mm, and only used to organize the code
5
6using namespace Qt::StringLiterals;
7
9{
10 // macOS will in many cases not report a deviceID (0 value).
11 // We can't pass this on directly, as the QInputDevicePrivate
12 // constructor will treat this as a request to assign a new Id.
13 // Instead we use the default Id of the primary pointing device.
14 static const int kDefaultPrimaryPointingDeviceId = 1;
15 if (!deviceID)
16 deviceID = kDefaultPrimaryPointingDeviceId;
17
18 if (const auto *device = QPointingDevicePrivate::pointingDeviceById(deviceID))
19 return device; // All good, already have the device registered
20
21 const auto *primaryDevice = QPointingDevice::primaryPointingDevice();
22 if (primaryDevice->systemId() == kDefaultPrimaryPointingDeviceId) {
23 // Adopt existing primary device instead of creating a new one
24 QPointingDevicePrivate::get(const_cast<QPointingDevice *>(primaryDevice))->systemId = deviceID;
25 qCDebug(lcInputDevices) << "primaryPointingDevice is now" << primaryDevice;
26 return primaryDevice;
27 } else {
28 // Register a new device. Name and capabilities may need updating later.
29 const auto *device = new QPointingDevice("mouse"_L1, deviceID,
34 return device;
35 }
36}
37
38/*
39 The reason for using this helper is to ensure that QNSView doesn't implement
40 the NSResponder callbacks for mouseEntered, mouseExited, and mouseMoved.
41
42 If it did, we would get mouse events though the responder chain as well,
43 for example if a subview has a tracking area of its own and calls super
44 in the handler, which results in forwarding the event though the responder
45 chain. The same applies if NSWindow.acceptsMouseMovedEvents is YES.
46
47 By having a helper as the target for our tracking areas, we know for sure
48 that the events we are getting stem from our own tracking areas.
49
50 FIXME: Ideally we wouldn't need this workaround, and would correctly
51 interact with the responder chain by e.g. calling super if Qt does not
52 accept the mouse event
53*/
54@implementation QNSViewMouseMoveHelper {
56}
57
58- (instancetype)initWithView:(QNSView *)theView
59{
60 if ((self = [super init]))
61 view = theView;
62
63 return self;
64}
65
66- (void)mouseMoved:(NSEvent *)theEvent
67{
68 [view mouseMovedImpl:theEvent];
69}
70
71- (void)mouseEntered:(NSEvent *)theEvent
72{
73 [view mouseEnteredImpl:theEvent];
74}
75
76- (void)mouseExited:(NSEvent *)theEvent
77{
78 [view mouseExitedImpl:theEvent];
79}
80
81- (void)cursorUpdate:(NSEvent *)theEvent
82{
83 [view cursorUpdate:theEvent];
84}
85
86@end
87
88@implementation QNSView (MouseAPI)
89
90- (void)resetMouseButtons
91{
92 qCDebug(lcQpaMouse) << "Resetting mouse buttons";
95}
96
97- (void)handleMouseEvent:(NSEvent *)theEvent
98{
99 if (!m_platformWindow)
100 return;
101
102#ifndef QT_NO_TABLETEVENT
103 // Tablet events may come in via the mouse event handlers,
104 // check if this is a valid tablet event first.
105 if ([self handleTabletEvent: theEvent])
106 return;
107#endif
108
109 QPointF qtWindowPoint;
110 QPointF qtScreenPoint;
111 QNSView *targetView = self;
112 if (!targetView.platformWindow)
113 return;
114
115
116 [targetView convertFromScreen:[self screenMousePoint:theEvent] toWindowPoint:&qtWindowPoint andScreenPoint:&qtScreenPoint];
117 ulong timestamp = [theEvent timestamp] * 1000;
118
119 QCocoaDrag* nativeDrag = QCocoaIntegration::instance()->drag();
120 nativeDrag->setLastMouseEvent(theEvent, self);
121
122 const auto modifiers = QAppleKeyMapper::fromCocoaModifiers(theEvent.modifierFlags);
123 auto button = cocoaButton2QtButton(theEvent);
126 const auto eventType = cocoaEvent2QtMouseEvent(theEvent);
127
128 const QPointingDevice *device = pointingDeviceFor(theEvent.deviceID);
130
131 if (eventType == QEvent::MouseMove)
132 qCDebug(lcQpaMouse) << eventType << "at" << qtWindowPoint << "with" << m_buttons;
133 else
134 qCInfo(lcQpaMouse) << eventType << "of" << button << "at" << qtWindowPoint << "with" << m_buttons;
135
136 QWindowSystemInterface::handleMouseEvent(targetView->m_platformWindow->window(),
137 timestamp, qtWindowPoint, qtScreenPoint,
138 m_buttons, button, eventType, modifiers);
139}
140
141- (void)handleFrameStrutMouseEvent:(NSEvent *)theEvent
142{
143 if (!m_platformWindow)
144 return;
145
146 switch (theEvent.type) {
147 case NSEventTypeLeftMouseDown:
149 break;
150 case NSEventTypeLeftMouseUp:
151 m_frameStrutButtons &= ~Qt::LeftButton;
152 break;
153 case NSEventTypeRightMouseDown:
155 break;
156 case NSEventTypeRightMouseUp:
157 m_frameStrutButtons &= ~Qt::RightButton;
158 break;
159 case NSEventTypeOtherMouseDown:
160 m_frameStrutButtons |= cocoaButton2QtButton(theEvent.buttonNumber);
161 break;
162 case NSEventTypeOtherMouseUp:
163 m_frameStrutButtons &= ~cocoaButton2QtButton(theEvent.buttonNumber);
164 default:
165 break;
166 }
167
168 // m_buttons can sometimes get out of sync with the button state in AppKit
169 // E.g if the QNSView where a drag starts is reparented to another window
170 // while the drag is ongoing, it will not get the corresponding mouseUp
171 // call. This will result in m_buttons to be stuck on Qt::LeftButton.
172 // Since we know which buttons was pressed/released directly on the frame
173 // strut, we can rectify m_buttons here so that we at least don't return early
174 // from the drag test underneath because of the faulty m_buttons state.
175 // FIXME: get m_buttons in sync with AppKit/NSEvent all over in QNSView.
176 m_buttons &= ~m_frameStrutButtons;
177
178 if (m_buttons != Qt::NoButton) {
179 // Don't send frame strut events if we are in the middle of
180 // a mouse drag that didn't start on the frame strut.
181 return;
182 }
183
184 NSWindow *window = [self window];
185 NSPoint windowPoint = [theEvent locationInWindow];
186
187 int windowScreenY = [window frame].origin.y + [window frame].size.height;
188 NSPoint windowCoord = [self convertPoint:[self frame].origin toView:nil];
189 int viewScreenY = [window convertRectToScreen:NSMakeRect(windowCoord.x, windowCoord.y, 0, 0)].origin.y;
190 int titleBarHeight = windowScreenY - viewScreenY;
191
192 NSPoint nsViewPoint = [self convertPoint: windowPoint fromView: nil];
193 QPoint qtWindowPoint = QPoint(nsViewPoint.x, titleBarHeight + nsViewPoint.y);
194 NSPoint screenPoint = [window convertRectToScreen:NSMakeRect(windowPoint.x, windowPoint.y, 0, 0)].origin;
195 QPoint qtScreenPoint = QCocoaScreen::mapFromNative(screenPoint).toPoint();
196
197 ulong timestamp = [theEvent timestamp] * 1000;
198
199 const auto button = cocoaButton2QtButton(theEvent);
200 auto eventType = [&]() {
201 switch (theEvent.type) {
202 case NSEventTypeLeftMouseDown:
203 case NSEventTypeRightMouseDown:
204 case NSEventTypeOtherMouseDown:
206
207 case NSEventTypeLeftMouseUp:
208 case NSEventTypeRightMouseUp:
209 case NSEventTypeOtherMouseUp:
211
212 case NSEventTypeMouseMoved:
213 case NSEventTypeLeftMouseDragged:
214 case NSEventTypeRightMouseDragged:
215 case NSEventTypeOtherMouseDragged:
217
218 default:
219 Q_UNREACHABLE();
220 }
221 }();
222
223 qCInfo(lcQpaMouse) << eventType << "at" << qtWindowPoint << "with" << m_frameStrutButtons << "in" << self.window;
224 QWindowSystemInterface::handleMouseEvent(m_platformWindow->window(),
225 timestamp, qtWindowPoint, qtScreenPoint, m_frameStrutButtons, button, eventType);
226}
227@end
228
229@implementation QNSView (Mouse)
230
231- (void)initMouse
232{
236
237 m_scrolling = false;
238 self.cursor = nil;
239
240 m_sendUpAsRightButton = false;
241 m_dontOverrideCtrlLMB = qt_mac_resolveOption(false, m_platformWindow->window(),
242 "_q_platform_MacDontOverrideCtrlLMB", "QT_MAC_DONT_OVERRIDE_CTRL_LMB");
243
244 m_mouseMoveHelper = [[QNSViewMouseMoveHelper alloc] initWithView:self];
245
246 NSUInteger trackingOptions = NSTrackingCursorUpdate | NSTrackingMouseEnteredAndExited;
247
248 // Ideally we should have used NSTrackingActiveInActiveApp, but that
249 // fails when the application is deactivated from using e.g cmd+tab, and later
250 // reactivated again from a mouse click. So as a work-around we use NSTrackingActiveAlways
251 // instead, and simply ignore any related callbacks while the application is inactive.
252 trackingOptions |= NSTrackingActiveAlways;
253
254 // Ideally, NSTrackingMouseMoved should be turned on only if QWidget::mouseTracking
255 // is enabled, hover is on, or a tool tip is set. Unfortunately, Qt will send "tooltip"
256 // events on mouse moves, so we need to turn it on in ALL case. That means EVERY QWindow
257 // gets to pay the cost of mouse moves delivered to it (Apple recommends keeping it OFF
258 // because there is a performance hit).
259 trackingOptions |= NSTrackingMouseMoved;
260
261 // Using NSTrackingInVisibleRect means AppKit will automatically synchronize the
262 // tracking rect with changes in the view's visible area, so leave it undefined.
263 trackingOptions |= NSTrackingInVisibleRect;
264 static const NSRect trackingRect = NSZeroRect;
265
267 [self addTrackingArea:[[[NSTrackingArea alloc] initWithRect:trackingRect
268 options:trackingOptions owner:m_mouseMoveHelper userInfo:nil] autorelease]];
269}
270
271- (BOOL)acceptsFirstMouse:(NSEvent *)theEvent
272{
273 Q_UNUSED(theEvent);
274 if (!m_platformWindow)
275 return NO;
276 if ([self isTransparentForUserInput])
277 return NO;
278 QPointF windowPoint;
279 QPointF screenPoint;
280 [self convertFromScreen:[NSEvent mouseLocation] toWindowPoint: &windowPoint andScreenPoint: &screenPoint];
281 return YES;
282}
283
284- (NSPoint)screenMousePoint:(NSEvent *)theEvent
285{
286 NSPoint screenPoint;
287 if (theEvent) {
288 NSPoint windowPoint = [theEvent locationInWindow];
289 if (qIsNaN(windowPoint.x) || qIsNaN(windowPoint.y)) {
290 screenPoint = [NSEvent mouseLocation];
291 } else {
292 NSRect screenRect = [[theEvent window] convertRectToScreen:NSMakeRect(windowPoint.x, windowPoint.y, 1, 1)];
293 screenPoint = screenRect.origin;
294 }
295 } else {
296 screenPoint = [NSEvent mouseLocation];
297 }
298 return screenPoint;
299}
300
301- (bool)handleMouseDownEvent:(NSEvent *)theEvent
302{
303 if ([self isTransparentForUserInput])
304 return false;
305
306 const auto button = cocoaButton2QtButton(theEvent);
307
308 QPointF qtWindowPoint;
309 QPointF qtScreenPoint;
310 [self convertFromScreen:[self screenMousePoint:theEvent] toWindowPoint:&qtWindowPoint andScreenPoint:&qtScreenPoint];
311 Q_UNUSED(qtScreenPoint);
312
313 // Maintain masked state for the button for use by MouseDragged and MouseUp.
314 QRegion mask = QHighDpi::toNativeLocalPosition(m_platformWindow->window()->mask(), m_platformWindow->window());
315 const bool masked = !mask.isEmpty() && !mask.contains(qtWindowPoint.toPoint());
316 if (masked)
317 m_acceptedMouseDowns &= ~button;
318 else
320
321 // Forward masked out events to the next responder
322 if (masked)
323 return false;
324
325 m_buttons |= button;
326
327 [self handleMouseEvent:theEvent];
328 return true;
329}
330
331- (bool)handleMouseDraggedEvent:(NSEvent *)theEvent
332{
333 if ([self isTransparentForUserInput])
334 return false;
335
336 const auto button = cocoaButton2QtButton(theEvent);
337
338 // Forward the event to the next responder if Qt did not accept the
339 // corresponding mouse down for this button
341 return false;
342
343 [self handleMouseEvent:theEvent];
344 return true;
345}
346
347- (bool)handleMouseUpEvent:(NSEvent *)theEvent
348{
349 if ([self isTransparentForUserInput])
350 return false;
351
352 auto button = cocoaButton2QtButton(theEvent);
353
354 // Forward the event to the next responder if Qt did not accept the
355 // corresponding mouse down for this button
357 return false;
358
361
362 m_buttons &= ~button;
363
364 [self handleMouseEvent:theEvent];
365
366 if (button == Qt::RightButton)
367 m_sendUpAsRightButton = false;
368
369 return true;
370}
371
372- (void)mouseDown:(NSEvent *)theEvent
373{
374 if ([self isTransparentForUserInput])
375 return [super mouseDown:theEvent];
376 m_sendUpAsRightButton = false;
377
378 QPointF qtWindowPoint;
379 QPointF qtScreenPoint;
380 [self convertFromScreen:[self screenMousePoint:theEvent] toWindowPoint:&qtWindowPoint andScreenPoint:&qtScreenPoint];
381 Q_UNUSED(qtScreenPoint);
382
383 QRegion mask = QHighDpi::toNativeLocalPosition(m_platformWindow->window()->mask(), m_platformWindow->window());
384 const bool masked = !mask.isEmpty() && !mask.contains(qtWindowPoint.toPoint());
385 // Maintain masked state for the button for use by MouseDragged and Up.
386 if (masked)
387 m_acceptedMouseDowns &= ~Qt::LeftButton;
388 else
390
391 // Forward masked out events to the next responder
392 if (masked) {
393 [super mouseDown:theEvent];
394 return;
395 }
396
397 // FIXME: AppKit transfers first responder to the view before calling mouseDown,
398 // whereas we only transfer focus once the mouse press is delivered, which means
399 // on first click the focus item won't be the correct one when transferring focus.
400 auto *focusObject = m_platformWindow->window()->focusObject();
401 if (queryInputMethod(focusObject)) {
402 // Input method is enabled. Pass on to the input context if we
403 // are hitting the input item.
405 qCDebug(lcQpaInputMethods) << "Asking input context to handle mouse press"
406 << "for focus object" << focusObject;
407 if ([NSTextInputContext.currentInputContext handleEvent:theEvent]) {
408 // NSTextView bails out if the input context handled the event,
409 // which is e.g. the case for 2-Set Korean input. We follow suit,
410 // even if that means having to click twice to move the cursor
411 // for these input methods when they are composing.
412 qCDebug(lcQpaInputMethods) << "Input context handled event; bailing out.";
413 return;
414 }
415 }
416 }
417
418 if (!m_dontOverrideCtrlLMB && (theEvent.modifierFlags & NSEventModifierFlagControl)) {
421 } else {
423 }
424
425 [self handleMouseEvent:theEvent];
426}
427
428- (void)mouseDragged:(NSEvent *)theEvent
429{
430 const bool accepted = [self handleMouseDraggedEvent:theEvent];
431 if (!accepted)
432 [super mouseDragged:theEvent];
433}
434
435- (void)mouseUp:(NSEvent *)theEvent
436{
437 const bool accepted = [self handleMouseUpEvent:theEvent];
438 if (!accepted)
439 [super mouseUp:theEvent];
440}
441
442- (void)rightMouseDown:(NSEvent *)theEvent
443{
444 const bool accepted = [self handleMouseDownEvent:theEvent];
445 if (!accepted)
446 [super rightMouseDown:theEvent];
447}
448
449- (void)rightMouseDragged:(NSEvent *)theEvent
450{
451 const bool accepted = [self handleMouseDraggedEvent:theEvent];
452 if (!accepted)
453 [super rightMouseDragged:theEvent];
454}
455
456- (void)rightMouseUp:(NSEvent *)theEvent
457{
458 const bool accepted = [self handleMouseUpEvent:theEvent];
459 if (!accepted)
460 [super rightMouseUp:theEvent];
461}
462
463- (void)otherMouseDown:(NSEvent *)theEvent
464{
465 const bool accepted = [self handleMouseDownEvent:theEvent];
466 if (!accepted)
467 [super otherMouseDown:theEvent];
468}
469
470- (void)otherMouseDragged:(NSEvent *)theEvent
471{
472 const bool accepted = [self handleMouseDraggedEvent:theEvent];
473 if (!accepted)
474 [super otherMouseDragged:theEvent];
475}
476
477- (void)otherMouseUp:(NSEvent *)theEvent
478{
479 const bool accepted = [self handleMouseUpEvent:theEvent];
480 if (!accepted)
481 [super otherMouseUp:theEvent];
482}
483
484- (void)cursorUpdate:(NSEvent *)theEvent
485{
486 if (!NSApp.active)
487 return;
488
489 auto previousCursor = NSCursor.currentCursor;
490
491 if (self.cursor)
492 [self.cursor set];
493 else
494 [super cursorUpdate:theEvent];
495
496 if (NSCursor.currentCursor != previousCursor)
497 qCInfo(lcQpaMouse) << "Cursor update for" << self << "resulted in new cursor" << NSCursor.currentCursor;
498}
499
500- (void)mouseMovedImpl:(NSEvent *)theEvent
501{
502 if (!m_platformWindow)
503 return;
504
505 // Top-level windows generate enter-leave events for sub-windows.
506 // Qt wants to know which window (if any) will be entered at the
507 // the time of the leave. This is dificult to accomplish by
508 // handling mouseEnter and mouseLeave envents, since they are sent
509 // individually to different views.
510 QPointF windowPoint;
511 QPointF screenPoint;
512 QCocoaWindow *windowToLeave = nullptr;
513
514 if (m_platformWindow->isContentView()) {
515 [self convertFromScreen:[self screenMousePoint:theEvent] toWindowPoint:&windowPoint andScreenPoint:&screenPoint];
516 QWindow *childUnderMouse = m_platformWindow->childWindowAt(windowPoint.toPoint());
517 QCocoaWindow *childWindow = static_cast<QCocoaWindow *>(childUnderMouse->handle());
518 if (childWindow != QCocoaWindow::s_windowUnderMouse) {
520 windowToLeave = QCocoaWindow::s_windowUnderMouse;
522 }
523 }
524
525 if (!NSApp.active)
526 return;
527
528 if ([self isTransparentForUserInput])
529 return;
530
531 if (windowToLeave) {
532 qCInfo(lcQpaMouse) << "Detected new window under mouse at" << windowPoint << "; sending"
534 << QEvent::Leave << windowToLeave->window();
535 QWindowSystemInterface::handleEnterLeaveEvent(QCocoaWindow::s_windowUnderMouse->window(), windowToLeave->window(), windowPoint, screenPoint);
536 }
537
538 // Cocoa keeps firing mouse move events for obscured parent views. Qt should not
539 // send those events so filter them out here.
540 if (m_platformWindow != QCocoaWindow::s_windowUnderMouse)
541 return;
542
543 [self handleMouseEvent: theEvent];
544}
545
546- (BOOL)shouldPropagateMouseEnterExit
547{
548 Q_ASSERT(m_platformWindow);
549
550 // We send out enter and leave events mainly from mouse move events (mouseMovedImpl),
551 // but in some case (see mouseEnteredImpl:) we also want to propagate enter/leave
552 // events from the platform. We only do this for windows that themselves are not
553 // handled by another parent QWindow.
554
555 if (m_platformWindow->isContentView())
556 return true;
557
558 // Windows manually embedded into a native view does not have a QWindow parent
559 if (m_platformWindow->isEmbedded())
560 return true;
561
562 // Windows embedded via fromWinId do, but the parent isn't a QNSView
563 QPlatformWindow *parentWindow = m_platformWindow->QPlatformWindow::parent();
564 if (parentWindow && parentWindow->isForeignWindow())
565 return true;
566
567 return false;
568}
569
570- (void)mouseEnteredImpl:(NSEvent *)theEvent
571{
572 Q_UNUSED(theEvent);
573 if (!m_platformWindow)
574 return;
575
576 // We send out enter and leave events mainly from mouse move events (mouseMovedImpl).
577 // Therefore, in most cases, we should not send out enter/leave events from here, as
578 // this results in duplicated enter/leave events being delivered.
579 // This is especially important when working with NSTrackingArea, since AppKit documents that
580 // the order of enter/exit events when several NSTrackingAreas are in use is not guaranteed.
581 // So if we just forwarded enter/leave events from NSTrackingArea directly, it would not only
582 // result in duplicated events, but also sometimes events that would be out of sync.
583 // But not all enter events can be resolved from mouse move events. E.g if a window is raised
584 // in front of the mouse, or if the application is activated while the mouse is on top of a
585 // window, we need to send out enter events for those cases as well. And we do so from this
586 // function to support the former case. But only when we receive an enter event for the
587 // top-level window, when no child QWindows are being hovered from before.
588 // Since QWSI expects us to send both the window entered, and the window left, in the same
589 // callback, we manually keep track of which child QWindow is under the mouse at any point
590 // in time (s_windowUnderMouse). The latter is also used to also send out enter/leave
591 // events when the application is activated/deactivated.
592
593 if (![self shouldPropagateMouseEnterExit])
594 return;
595
596 QPointF windowPoint;
597 QPointF screenPoint;
598 [self convertFromScreen:[self screenMousePoint:theEvent] toWindowPoint:&windowPoint andScreenPoint:&screenPoint];
599 QWindow *childUnderMouse = m_platformWindow->childWindowAt(windowPoint.toPoint());
600 QCocoaWindow::s_windowUnderMouse = static_cast<QCocoaWindow *>(childUnderMouse->handle());
601
602 if ([self isTransparentForUserInput])
603 return;
604
605 if (!NSApp.active)
606 return;
607
608 qCInfo(lcQpaMouse) << "Mouse entered" << self << "at" << windowPoint << "with" << currentlyPressedMouseButtons()
609 << "; sending" << QEvent::Enter << "to" << QCocoaWindow::s_windowUnderMouse->window();
611}
612
613- (void)mouseExitedImpl:(NSEvent *)theEvent
614{
615 Q_UNUSED(theEvent);
616 if (!m_platformWindow)
617 return;
618
619 if (![self shouldPropagateMouseEnterExit])
620 return;
621
624
625 if ([self isTransparentForUserInput])
626 return;
627
628 if (!NSApp.active)
629 return;
630
631 if (!windowToLeave)
632 return;
633
634 qCInfo(lcQpaMouse) << "Mouse left" << self << "; sending" << QEvent::Leave << "to" << windowToLeave->window();
635 QWindowSystemInterface::handleLeaveEvent(windowToLeave->window());
636}
637
638#if QT_CONFIG(wheelevent)
639- (void)scrollWheel:(NSEvent *)theEvent
640{
641 if (!m_platformWindow)
642 return;
643
644 if ([self isTransparentForUserInput])
645 return [super scrollWheel:theEvent];
646
647 QPoint angleDelta;
649 if ([theEvent hasPreciseScrollingDeltas]) {
650 // The mouse device contains pixel scroll wheel support (Mighty Mouse, Trackpad).
651 // Since deviceDelta is delivered as pixels rather than degrees, we need to
652 // convert from pixels to degrees in a sensible manner.
653 // It looks like 1/4 degrees per pixel behaves most native.
654 // (NB: Qt expects the unit for delta to be 8 per degree):
655 const int pixelsToDegrees = 2; // 8 * 1/4
656 angleDelta.setX([theEvent scrollingDeltaX] * pixelsToDegrees);
657 angleDelta.setY([theEvent scrollingDeltaY] * pixelsToDegrees);
659 } else {
660 // Remove acceleration, and use either -120 or 120 as delta:
661 angleDelta.setX(qBound(-120, int([theEvent deltaX] * 10000), 120));
662 angleDelta.setY(qBound(-120, int([theEvent deltaY] * 10000), 120));
663 }
664
665 QPoint pixelDelta;
666 if ([theEvent hasPreciseScrollingDeltas]) {
667 pixelDelta.setX([theEvent scrollingDeltaX]);
668 pixelDelta.setY([theEvent scrollingDeltaY]);
669 } else {
670 // docs: "In the case of !hasPreciseScrollingDeltas, multiply the delta with the line width."
671 // scrollingDeltaX seems to return a minimum value of 0.1 in this case, map that to two pixels.
672 const CGFloat lineWithEstimate = 20.0;
673 pixelDelta.setX([theEvent scrollingDeltaX] * lineWithEstimate);
674 pixelDelta.setY([theEvent scrollingDeltaY] * lineWithEstimate);
675 }
676
677 QPointF qt_windowPoint;
678 QPointF qt_screenPoint;
679 [self convertFromScreen:[self screenMousePoint:theEvent] toWindowPoint:&qt_windowPoint andScreenPoint:&qt_screenPoint];
680 NSTimeInterval timestamp = [theEvent timestamp];
681 ulong qt_timestamp = timestamp * 1000;
682
684 if (theEvent.phase == NSEventPhaseMayBegin || theEvent.phase == NSEventPhaseBegan) {
685 // MayBegin is likely to happen. We treat it the same as an actual begin,
686 // and follow it with an update when the actual begin is delivered.
688 m_scrolling = true;
689 } else if (theEvent.phase == NSEventPhaseStationary || theEvent.phase == NSEventPhaseChanged) {
690 phase = Qt::ScrollUpdate;
691 } else if (theEvent.phase == NSEventPhaseEnded) {
692 // A scroll event phase may be followed by a momentum phase after the user releases
693 // the finger, and in that case we don't want to send a Qt::ScrollEnd until after
694 // the momentum phase has ended. Unfortunately there isn't any guaranteed way of
695 // knowing whether or not a NSEventPhaseEnded will be followed by a momentum phase.
696 // The best we can do is to look at the event queue and hope that the system has
697 // had time to emit a momentum phase event.
698 if ([NSApp nextEventMatchingMask:NSEventMaskScrollWheel untilDate:[NSDate distantPast]
699 inMode:@"QtMomementumEventSearchMode" dequeue:NO].momentumPhase == NSEventPhaseBegan) {
700 return; // Ignore, even if it has delta
701 } else {
702 phase = Qt::ScrollEnd;
703 m_scrolling = false;
704 }
705 } else if (theEvent.momentumPhase == NSEventPhaseBegan) {
706 Q_ASSERT(!pixelDelta.isNull() && !angleDelta.isNull());
707 // If we missed finding a momentum NSEventPhaseBegan when the non-momentum
708 // phase ended we need to treat this as a scroll begin, to not confuse client
709 // code. Otherwise we treat it as a continuation of the existing scroll.
711 m_scrolling = true;
712 } else if (theEvent.momentumPhase == NSEventPhaseChanged) {
713 phase = Qt::ScrollMomentum;
714 } else if (theEvent.phase == NSEventPhaseCancelled
715 || theEvent.momentumPhase == NSEventPhaseEnded
716 || theEvent.momentumPhase == NSEventPhaseCancelled) {
717 phase = Qt::ScrollEnd;
718 m_scrolling = false;
719 } else {
720 Q_ASSERT(theEvent.momentumPhase != NSEventPhaseStationary);
721 }
722
723 // Sanitize deltas for events that should not result in scrolling.
724 // On macOS 12.1 this phase has been observed to report deltas.
725 if (theEvent.phase == NSEventPhaseCancelled) {
726 if (!pixelDelta.isNull() || !angleDelta.isNull()) {
727 qCInfo(lcQpaMouse) << "Ignoring unexpected delta for" << theEvent;
728 pixelDelta = QPoint();
729 angleDelta = QPoint();
730 }
731 }
732
733 // Prevent keyboard modifier state from changing during scroll event streams.
734 // A two-finger trackpad flick generates a stream of scroll events. We want
735 // the keyboard modifier state to be the state at the beginning of the
736 // flick in order to avoid changing the interpretation of the events
737 // mid-stream. One example of this happening would be when pressing cmd
738 // after scrolling in Qt Creator: not taking the phase into account causes
739 // the end of the event stream to be interpreted as font size changes.
740 if (theEvent.momentumPhase == NSEventPhaseNone)
741 m_currentWheelModifiers = QAppleKeyMapper::fromCocoaModifiers(theEvent.modifierFlags);
742
743 // "isInverted": natural OS X scrolling, inverted from the Qt/other platform/Jens perspective.
744 bool isInverted = [theEvent isDirectionInvertedFromDevice];
745
746 qCInfo(lcQpaMouse).nospace() << phase << " at " << qt_windowPoint
747 << " pixelDelta=" << pixelDelta << " angleDelta=" << angleDelta
748 << (isInverted ? " inverted=true" : "");
749
750 const QPointingDevice *device = pointingDeviceFor(theEvent.deviceID);
752
753 if (theEvent.hasPreciseScrollingDeltas) {
754 auto *devicePriv = QPointingDevicePrivate::get(const_cast<QPointingDevice *>(device));
755 if (!devicePriv->capabilities.testFlag(QInputDevice::Capability::PixelScroll)) {
756 devicePriv->name = "trackpad or magic mouse"_L1;
757 devicePriv->deviceType = QInputDevice::DeviceType::TouchPad;
758 devicePriv->capabilities |= QInputDevice::Capability::PixelScroll;
759 qCDebug(lcInputDevices) << "mouse scrolling: updated capabilities" << device;
760 }
761 }
762
763 QWindowSystemInterface::handleWheelEvent(m_platformWindow->window(), qt_timestamp,
764 device, qt_windowPoint, qt_screenPoint, pixelDelta, angleDelta,
765 m_currentWheelModifiers, phase, source, isInverted);
766}
767#endif // QT_CONFIG(wheelevent)
768
769@end
IOBluetoothDevice * device
static QCocoaIntegration * instance()
static QPointF mapFromNative(CGPoint pos, QCocoaScreen *screen=QCocoaScreen::primaryScreen())
static QPointer< QCocoaWindow > s_windowUnderMouse
@ MouseMove
Definition qcoreevent.h:63
@ NonClientAreaMouseMove
Definition qcoreevent.h:212
@ NonClientAreaMouseButtonRelease
Definition qcoreevent.h:214
@ NonClientAreaMouseButtonPress
Definition qcoreevent.h:213
static QRectF inputItemClipRectangle()
QPlatformInputContext::inputItemClipRectangle.
The QPlatformWindow class provides an abstraction for top-level windows.
virtual bool isForeignWindow() const
\inmodule QtCore\reentrant
Definition qpoint.h:217
\inmodule QtCore\reentrant
Definition qpoint.h:25
constexpr bool isNull() const noexcept
Returns true if both the x and y coordinates are set to 0, otherwise returns false.
Definition qpoint.h:125
constexpr void setY(int y) noexcept
Sets the y coordinate of this point to the given y coordinate.
Definition qpoint.h:145
constexpr void setX(int x) noexcept
Sets the x coordinate of this point to the given x coordinate.
Definition qpoint.h:140
static const QPointingDevice * pointingDeviceById(qint64 systemId)
static QPointingDevicePrivate * get(QPointingDevice *q)
QPointingDeviceUniqueId identifies a unique object, such as a tagged token or stylus,...
The QPointingDevice class describes a device from which mouse, touch or tablet events originate.
static const QPointingDevice * primaryPointingDevice(const QString &seatName=QString())
Returns the primary pointing device (the core pointer, traditionally assumed to be a mouse) on the gi...
The QRegion class specifies a clip region for a painter.
Definition qregion.h:27
\macro QT_RESTRICTED_CAST_FROM_ASCII
Definition qstring.h:129
int y
the y coordinate of the widget relative to its parent and including any window frame
Definition qwidget.h:110
static void handleLeaveEvent(QWindow *window)
static bool handleMouseEvent(QWindow *window, const QPointF &local, const QPointF &global, Qt::MouseButtons state, Qt::MouseButton button, QEvent::Type type, Qt::KeyboardModifiers mods=Qt::NoModifier, Qt::MouseEventSource source=Qt::MouseEventNotSynthesized)
static void registerInputDevice(const QInputDevice *device)
static void handleEnterLeaveEvent(QWindow *enter, QWindow *leave, const QPointF &local=QPointF(), const QPointF &global=QPointF())
This method can be used to ensure leave and enter events are both in queue when moving from one QWind...
static void handleEnterEvent(QWindow *window, const QPointF &local=QPointF(), const QPointF &global=QPointF())
static bool handleWheelEvent(QWindow *window, const QPointF &local, const QPointF &global, QPoint pixelDelta, QPoint angleDelta, Qt::KeyboardModifiers mods=Qt::NoModifier, Qt::ScrollPhase phase=Qt::NoScrollPhase, Qt::MouseEventSource source=Qt::MouseEventNotSynthesized)
\inmodule QtGui
Definition qwindow.h:63
EGLImageKHR int int EGLuint64KHR * modifiers
QPushButton * button
[2]
T toNativeLocalPosition(const T &value, const C *context)
Definition qcompare.h:63
@ LeftButton
Definition qnamespace.h:58
@ RightButton
Definition qnamespace.h:59
@ NoButton
Definition qnamespace.h:57
MouseEventSource
@ MouseEventSynthesizedBySystem
@ MouseEventNotSynthesized
ScrollPhase
@ ScrollBegin
@ ScrollUpdate
@ ScrollMomentum
@ NoScrollPhase
@ ScrollEnd
QString self
Definition language.cpp:58
float CGFloat
QEvent::Type cocoaEvent2QtMouseEvent(NSEvent *event)
Returns the QEvent::Type that corresponds to an NSEvent.type.
Qt::MouseButton cocoaButton2QtButton(NSInteger buttonNum)
Returns the Qt::Button that corresponds to an NSEvent.buttonNumber.
InputMethodQueryResult queryInputMethod(QObject *object, Qt::InputMethodQueries queries)
Qt::MouseButtons currentlyPressedMouseButtons()
Returns the Qt::MouseButtons that corresponds to an NSEvent.pressedMouseButtons.
unsigned long NSUInteger
DBusConnection const char DBusError DBusBusType DBusError return DBusConnection DBusHandleMessageFunction void DBusFreeFunction return DBusConnection return DBusConnection return const char DBusError return DBusConnection DBusMessage dbus_uint32_t return DBusConnection dbus_bool_t DBusConnection DBusAddWatchFunction DBusRemoveWatchFunction DBusWatchToggledFunction void DBusFreeFunction return DBusConnection DBusDispatchStatusFunction void DBusFreeFunction DBusTimeout return DBusTimeout return DBusWatch return DBusWatch unsigned int return DBusError const DBusError return const DBusMessage return DBusMessage return DBusMessage return DBusMessage return DBusMessage return DBusMessage return DBusMessageIter int const void return DBusMessageIter DBusMessageIter return DBusMessageIter void DBusMessageIter void int return DBusMessage DBusMessageIter return DBusMessageIter return DBusMessageIter DBusMessageIter const char const char const char const char return DBusMessage return DBusMessage const char return DBusMessage dbus_bool_t return DBusMessage dbus_uint32_t return DBusMessage void
bool qIsNaN(qfloat16 f) noexcept
Definition qfloat16.h:284
#define qCInfo(category,...)
#define qCDebug(category,...)
constexpr const T & qBound(const T &min, const T &val, const T &max)
Definition qminmax.h:44
bool m_sendUpAsRightButton
Definition qnsview.mm:107
Qt::MouseButtons m_buttons
Definition qnsview.mm:102
bool m_dontOverrideCtrlLMB
Definition qnsview.mm:106
bool m_scrolling
Definition qnsview.mm:108
QNSViewMouseMoveHelper * m_mouseMoveHelper
Definition qnsview.mm:101
Qt::MouseButtons m_acceptedMouseDowns
Definition qnsview.mm:103
Qt::MouseButtons m_frameStrutButtons
Definition qnsview.mm:104
Qt::KeyboardModifiers m_currentWheelModifiers
Definition qnsview.mm:105
static const QPointingDevice * pointingDeviceFor(qint64 deviceID)
static bool contains(const QJsonArray &haystack, unsigned needle)
Definition qopengl.cpp:116
GLint GLsizei GLsizei height
GLenum GLuint GLintptr GLsizeiptr size
[1]
GLint GLint GLint GLint GLint GLint GLint GLbitfield mask
GLsizei GLsizei GLchar * source
#define Q_ASSERT(cond)
Definition qrandom.cpp:47
static QT_BEGIN_NAMESPACE void init(QTextBoundaryFinder::BoundaryType type, QStringView str, QCharAttributes *attributes)
#define Q_UNUSED(x)
unsigned long ulong
Definition qtypes.h:35
long long qint64
Definition qtypes.h:60
aWidget window() -> setWindowTitle("New Window Title")
[2]
QFrame frame
[0]
QQuickView * view
[0]