1/**************************************************************************** 2** 3** Copyright (C) 2018 The Qt Company Ltd. 4** Contact: https://www.qt.io/licensing/ 5** 6** This file is part of the plugins of the Qt Toolkit. 7** 8** $QT_BEGIN_LICENSE:LGPL$ 9** Commercial License Usage 10** Licensees holding valid commercial Qt licenses may use this file in 11** accordance with the commercial license agreement provided with the 12** Software or, alternatively, in accordance with the terms contained in 13** a written agreement between you and The Qt Company. For licensing terms 14** and conditions see https://www.qt.io/terms-conditions. For further 15** information use the contact form at https://www.qt.io/contact-us. 16** 17** GNU Lesser General Public License Usage 18** Alternatively, this file may be used under the terms of the GNU Lesser 19** General Public License version 3 as published by the Free Software 20** Foundation and appearing in the file LICENSE.LGPL3 included in the 21** packaging of this file. Please review the following information to 22** ensure the GNU Lesser General Public License version 3 requirements 23** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. 24** 25** GNU General Public License Usage 26** Alternatively, this file may be used under the terms of the GNU 27** General Public License version 2.0 or (at your option) the GNU General 28** Public license version 3 or any later version approved by the KDE Free 29** Qt Foundation. The licenses are as published by the Free Software 30** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 31** included in the packaging of this file. Please review the following 32** information to ensure the GNU General Public License requirements will 33** be met: https://www.gnu.org/licenses/gpl-2.0.html and 34** https://www.gnu.org/licenses/gpl-3.0.html. 35** 36** $QT_END_LICENSE$ 37** 38****************************************************************************/ 39 40// This file is included from qnsview.mm, and only used to organize the code 41 42/* 43 The reason for using this helper is to ensure that QNSView doesn't implement 44 the NSResponder callbacks for mouseEntered, mouseExited, and mouseMoved. 45 46 If it did, we would get mouse events though the responder chain as well, 47 for example if a subview has a tracking area of its own and calls super 48 in the handler, which results in forwarding the event though the responder 49 chain. The same applies if NSWindow.acceptsMouseMovedEvents is YES. 50 51 By having a helper as the target for our tracking areas, we know for sure 52 that the events we are getting stem from our own tracking areas. 53 54 FIXME: Ideally we wouldn't need this workaround, and would correctly 55 interact with the responder chain by e.g. calling super if Qt does not 56 accept the mouse event 57*/ 58@implementation QNSViewMouseMoveHelper { 59 QNSView *view; 60} 61 62- (instancetype)initWithView:(QNSView *)theView 63{ 64 if ((self = [super init])) 65 view = theView; 66 67 return self; 68} 69 70- (void)mouseMoved:(NSEvent *)theEvent 71{ 72 [view mouseMovedImpl:theEvent]; 73} 74 75- (void)mouseEntered:(NSEvent *)theEvent 76{ 77 [view mouseEnteredImpl:theEvent]; 78} 79 80- (void)mouseExited:(NSEvent *)theEvent 81{ 82 [view mouseExitedImpl:theEvent]; 83} 84 85- (void)cursorUpdate:(NSEvent *)theEvent 86{ 87 [view cursorUpdate:theEvent]; 88} 89 90@end 91 92@implementation QNSView (MouseAPI) 93 94- (void)resetMouseButtons 95{ 96 qCDebug(lcQpaMouse) << "Reseting mouse buttons"; 97 m_buttons = Qt::NoButton; 98 m_frameStrutButtons = Qt::NoButton; 99} 100 101- (void)handleFrameStrutMouseEvent:(NSEvent *)theEvent 102{ 103 if (!m_platformWindow) 104 return; 105 106 // get m_buttons in sync 107 // Don't send frme strut events if we are in the middle of a mouse drag. 108 if (m_buttons != Qt::NoButton) 109 return; 110 111 switch (theEvent.type) { 112 case NSEventTypeLeftMouseDown: 113 case NSEventTypeLeftMouseDragged: 114 m_frameStrutButtons |= Qt::LeftButton; 115 break; 116 case NSEventTypeLeftMouseUp: 117 m_frameStrutButtons &= ~Qt::LeftButton; 118 break; 119 case NSEventTypeRightMouseDown: 120 case NSEventTypeRightMouseDragged: 121 m_frameStrutButtons |= Qt::RightButton; 122 break; 123 case NSEventTypeRightMouseUp: 124 m_frameStrutButtons &= ~Qt::RightButton; 125 break; 126 case NSEventTypeOtherMouseDown: 127 m_frameStrutButtons |= cocoaButton2QtButton(theEvent.buttonNumber); 128 break; 129 case NSEventTypeOtherMouseUp: 130 m_frameStrutButtons &= ~cocoaButton2QtButton(theEvent.buttonNumber); 131 default: 132 break; 133 } 134 135 NSWindow *window = [self window]; 136 NSPoint windowPoint = [theEvent locationInWindow]; 137 138 int windowScreenY = [window frame].origin.y + [window frame].size.height; 139 NSPoint windowCoord = [self convertPoint:[self frame].origin toView:nil]; 140 int viewScreenY = [window convertRectToScreen:NSMakeRect(windowCoord.x, windowCoord.y, 0, 0)].origin.y; 141 int titleBarHeight = windowScreenY - viewScreenY; 142 143 NSPoint nsViewPoint = [self convertPoint: windowPoint fromView: nil]; 144 QPoint qtWindowPoint = QPoint(nsViewPoint.x, titleBarHeight + nsViewPoint.y); 145 NSPoint screenPoint = [window convertRectToScreen:NSMakeRect(windowPoint.x, windowPoint.y, 0, 0)].origin; 146 QPoint qtScreenPoint = QCocoaScreen::mapFromNative(screenPoint).toPoint(); 147 148 ulong timestamp = [theEvent timestamp] * 1000; 149 150 const auto button = cocoaButton2QtButton(theEvent); 151 auto eventType = [&]() { 152 switch (theEvent.type) { 153 case NSEventTypeLeftMouseDown: 154 case NSEventTypeRightMouseDown: 155 case NSEventTypeOtherMouseDown: 156 return QEvent::NonClientAreaMouseButtonPress; 157 158 case NSEventTypeLeftMouseUp: 159 case NSEventTypeRightMouseUp: 160 case NSEventTypeOtherMouseUp: 161 return QEvent::NonClientAreaMouseButtonRelease; 162 163 case NSEventTypeLeftMouseDragged: 164 case NSEventTypeRightMouseDragged: 165 case NSEventTypeOtherMouseDragged: 166 return QEvent::NonClientAreaMouseMove; 167 168 default: 169 break; 170 } 171 172 return QEvent::None; 173 }(); 174 175 qCInfo(lcQpaMouse) << eventType << "at" << qtWindowPoint << "with" << m_frameStrutButtons << "in" << self.window; 176 QWindowSystemInterface::handleFrameStrutMouseEvent(m_platformWindow->window(), 177 timestamp, qtWindowPoint, qtScreenPoint, m_frameStrutButtons, button, eventType); 178} 179@end 180 181@implementation QNSView (Mouse) 182 183- (void)initMouse 184{ 185 m_buttons = Qt::NoButton; 186 m_acceptedMouseDowns = Qt::NoButton; 187 m_frameStrutButtons = Qt::NoButton; 188 189 m_scrolling = false; 190 self.cursor = nil; 191 192 m_sendUpAsRightButton = false; 193 m_dontOverrideCtrlLMB = qt_mac_resolveOption(false, m_platformWindow->window(), 194 "_q_platform_MacDontOverrideCtrlLMB", "QT_MAC_DONT_OVERRIDE_CTRL_LMB"); 195 196 m_mouseMoveHelper = [[QNSViewMouseMoveHelper alloc] initWithView:self]; 197 198 NSUInteger trackingOptions = NSTrackingActiveInActiveApp 199 | NSTrackingMouseEnteredAndExited | NSTrackingCursorUpdate; 200 201 // Ideally, NSTrackingMouseMoved should be turned on only if QWidget::mouseTracking 202 // is enabled, hover is on, or a tool tip is set. Unfortunately, Qt will send "tooltip" 203 // events on mouse moves, so we need to turn it on in ALL case. That means EVERY QWindow 204 // gets to pay the cost of mouse moves delivered to it (Apple recommends keeping it OFF 205 // because there is a performance hit). 206 trackingOptions |= NSTrackingMouseMoved; 207 208 // Using NSTrackingInVisibleRect means AppKit will automatically synchronize the 209 // tracking rect with changes in the view's visible area, so leave it undefined. 210 trackingOptions |= NSTrackingInVisibleRect; 211 static const NSRect trackingRect = NSZeroRect; 212 213 QMacAutoReleasePool pool; 214 [self addTrackingArea:[[[NSTrackingArea alloc] initWithRect:trackingRect 215 options:trackingOptions owner:m_mouseMoveHelper userInfo:nil] autorelease]]; 216} 217 218- (BOOL)acceptsFirstMouse:(NSEvent *)theEvent 219{ 220 Q_UNUSED(theEvent) 221 if (!m_platformWindow) 222 return NO; 223 if ([self isTransparentForUserInput]) 224 return NO; 225 QPointF windowPoint; 226 QPointF screenPoint; 227 [self convertFromScreen:[NSEvent mouseLocation] toWindowPoint: &windowPoint andScreenPoint: &screenPoint]; 228 if (!qt_window_private(m_platformWindow->window())->allowClickThrough(screenPoint.toPoint())) 229 return NO; 230 return YES; 231} 232 233- (NSPoint)screenMousePoint:(NSEvent *)theEvent 234{ 235 NSPoint screenPoint; 236 if (theEvent) { 237 NSPoint windowPoint = [theEvent locationInWindow]; 238 if (qIsNaN(windowPoint.x) || qIsNaN(windowPoint.y)) { 239 screenPoint = [NSEvent mouseLocation]; 240 } else { 241 NSRect screenRect = [[theEvent window] convertRectToScreen:NSMakeRect(windowPoint.x, windowPoint.y, 1, 1)]; 242 screenPoint = screenRect.origin; 243 } 244 } else { 245 screenPoint = [NSEvent mouseLocation]; 246 } 247 return screenPoint; 248} 249 250- (void)handleMouseEvent:(NSEvent *)theEvent 251{ 252 if (!m_platformWindow) 253 return; 254 255#ifndef QT_NO_TABLETEVENT 256 // Tablet events may come in via the mouse event handlers, 257 // check if this is a valid tablet event first. 258 if ([self handleTabletEvent: theEvent]) 259 return; 260#endif 261 262 QPointF qtWindowPoint; 263 QPointF qtScreenPoint; 264 QNSView *targetView = self; 265 if (!targetView.platformWindow) 266 return; 267 268 // Popups implicitly grap mouse events; forward to the active popup if there is one 269 if (QCocoaWindow *popup = QCocoaIntegration::instance()->activePopupWindow()) { 270 // Tooltips must be transparent for mouse events 271 // The bug reference is QTBUG-46379 272 if (!popup->window()->flags().testFlag(Qt::ToolTip)) { 273 if (QNSView *popupView = qnsview_cast(popup->view())) 274 targetView = popupView; 275 } 276 } 277 278 [targetView convertFromScreen:[self screenMousePoint:theEvent] toWindowPoint:&qtWindowPoint andScreenPoint:&qtScreenPoint]; 279 ulong timestamp = [theEvent timestamp] * 1000; 280 281 QCocoaDrag* nativeDrag = QCocoaIntegration::instance()->drag(); 282 nativeDrag->setLastMouseEvent(theEvent, self); 283 284 const auto modifiers = [QNSView convertKeyModifiers:theEvent.modifierFlags]; 285 auto button = cocoaButton2QtButton(theEvent); 286 if (button == Qt::LeftButton && m_sendUpAsRightButton) 287 button = Qt::RightButton; 288 const auto eventType = cocoaEvent2QtMouseEvent(theEvent); 289 290 if (eventType == QEvent::MouseMove) 291 qCDebug(lcQpaMouse) << eventType << "at" << qtWindowPoint << "with" << m_buttons; 292 else 293 qCInfo(lcQpaMouse) << eventType << "of" << button << "at" << qtWindowPoint << "with" << m_buttons; 294 295 QWindowSystemInterface::handleMouseEvent(targetView->m_platformWindow->window(), 296 timestamp, qtWindowPoint, qtScreenPoint, 297 m_buttons, button, eventType, modifiers); 298} 299 300- (bool)handleMouseDownEvent:(NSEvent *)theEvent 301{ 302 if ([self isTransparentForUserInput]) 303 return false; 304 305 const auto button = cocoaButton2QtButton(theEvent); 306 307 QPointF qtWindowPoint; 308 QPointF qtScreenPoint; 309 [self convertFromScreen:[self screenMousePoint:theEvent] toWindowPoint:&qtWindowPoint andScreenPoint:&qtScreenPoint]; 310 Q_UNUSED(qtScreenPoint); 311 312 // Maintain masked state for the button for use by MouseDragged and MouseUp. 313 QRegion mask = m_platformWindow->window()->mask(); 314 const bool masked = !mask.isEmpty() && !mask.contains(qtWindowPoint.toPoint()); 315 if (masked) 316 m_acceptedMouseDowns &= ~button; 317 else 318 m_acceptedMouseDowns |= button; 319 320 // Forward masked out events to the next responder 321 if (masked) 322 return false; 323 324 m_buttons |= button; 325 326 [self handleMouseEvent:theEvent]; 327 return true; 328} 329 330- (bool)handleMouseDraggedEvent:(NSEvent *)theEvent 331{ 332 if ([self isTransparentForUserInput]) 333 return false; 334 335 const auto button = cocoaButton2QtButton(theEvent); 336 337 // Forward the event to the next responder if Qt did not accept the 338 // corresponding mouse down for this button 339 if (!(m_acceptedMouseDowns & button) == button) 340 return false; 341 342 [self handleMouseEvent:theEvent]; 343 return true; 344} 345 346- (bool)handleMouseUpEvent:(NSEvent *)theEvent 347{ 348 if ([self isTransparentForUserInput]) 349 return false; 350 351 auto button = cocoaButton2QtButton(theEvent); 352 353 // Forward the event to the next responder if Qt did not accept the 354 // corresponding mouse down for this button 355 if (!(m_acceptedMouseDowns & button) == button) 356 return false; 357 358 if (m_sendUpAsRightButton && button == Qt::LeftButton) 359 button = Qt::RightButton; 360 361 m_buttons &= ~button; 362 363 [self handleMouseEvent:theEvent]; 364 365 if (button == Qt::RightButton) 366 m_sendUpAsRightButton = false; 367 368 return true; 369} 370 371- (void)mouseDown:(NSEvent *)theEvent 372{ 373 if ([self isTransparentForUserInput]) 374 return [super mouseDown:theEvent]; 375 m_sendUpAsRightButton = false; 376 377 // Handle any active poup windows; clicking outisde them should close them 378 // all. Don't do anything or clicks inside one of the menus, let Cocoa 379 // handle that case. Note that in practice many windows of the Qt::Popup type 380 // will actually close themselves in this case using logic implemented in 381 // that particular poup type (for example context menus). However, Qt expects 382 // that plain popup QWindows will also be closed, so we implement the logic 383 // here as well. 384 QList<QCocoaWindow *> *popups = QCocoaIntegration::instance()->popupWindowStack(); 385 if (!popups->isEmpty()) { 386 // Check if the click is outside all popups. 387 bool inside = false; 388 QPointF qtScreenPoint = QCocoaScreen::mapFromNative([self screenMousePoint:theEvent]); 389 for (QList<QCocoaWindow *>::const_iterator it = popups->begin(); it != popups->end(); ++it) { 390 if ((*it)->geometry().contains(qtScreenPoint.toPoint())) { 391 inside = true; 392 break; 393 } 394 } 395 // Close the popups if the click was outside. 396 if (!inside) { 397 bool selfClosed = false; 398 Qt::WindowType type = QCocoaIntegration::instance()->activePopupWindow()->window()->type(); 399 while (QCocoaWindow *popup = QCocoaIntegration::instance()->popPopupWindow()) { 400 selfClosed = self == popup->view(); 401 QWindowSystemInterface::handleCloseEvent(popup->window()); 402 QWindowSystemInterface::flushWindowSystemEvents(); 403 if (!m_platformWindow) 404 return; // Bail out if window was destroyed 405 } 406 // Consume the mouse event when closing the popup, except for tool tips 407 // were it's expected that the event is processed normally. 408 if (type != Qt::ToolTip || selfClosed) 409 return; 410 } 411 } 412 413 QPointF qtWindowPoint; 414 QPointF qtScreenPoint; 415 [self convertFromScreen:[self screenMousePoint:theEvent] toWindowPoint:&qtWindowPoint andScreenPoint:&qtScreenPoint]; 416 Q_UNUSED(qtScreenPoint); 417 418 QRegion mask = m_platformWindow->window()->mask(); 419 const bool masked = !mask.isEmpty() && !mask.contains(qtWindowPoint.toPoint()); 420 // Maintain masked state for the button for use by MouseDragged and Up. 421 if (masked) 422 m_acceptedMouseDowns &= ~Qt::LeftButton; 423 else 424 m_acceptedMouseDowns |= Qt::LeftButton; 425 426 // Forward masked out events to the next responder 427 if (masked) { 428 [super mouseDown:theEvent]; 429 return; 430 } 431 432 if ([self hasMarkedText]) { 433 [[NSTextInputContext currentInputContext] handleEvent:theEvent]; 434 } else { 435 auto ctrlOrMetaModifier = qApp->testAttribute(Qt::AA_MacDontSwapCtrlAndMeta) ? Qt::ControlModifier : Qt::MetaModifier; 436 if (!m_dontOverrideCtrlLMB && [QNSView convertKeyModifiers:[theEvent modifierFlags]] & ctrlOrMetaModifier) { 437 m_buttons |= Qt::RightButton; 438 m_sendUpAsRightButton = true; 439 } else { 440 m_buttons |= Qt::LeftButton; 441 } 442 [self handleMouseEvent:theEvent]; 443 } 444} 445 446- (void)mouseDragged:(NSEvent *)theEvent 447{ 448 const bool accepted = [self handleMouseDraggedEvent:theEvent]; 449 if (!accepted) 450 [super mouseDragged:theEvent]; 451} 452 453- (void)mouseUp:(NSEvent *)theEvent 454{ 455 const bool accepted = [self handleMouseUpEvent:theEvent]; 456 if (!accepted) 457 [super mouseUp:theEvent]; 458} 459 460- (void)rightMouseDown:(NSEvent *)theEvent 461{ 462 const bool accepted = [self handleMouseDownEvent:theEvent]; 463 if (!accepted) 464 [super rightMouseDown:theEvent]; 465} 466 467- (void)rightMouseDragged:(NSEvent *)theEvent 468{ 469 const bool accepted = [self handleMouseDraggedEvent:theEvent]; 470 if (!accepted) 471 [super rightMouseDragged:theEvent]; 472} 473 474- (void)rightMouseUp:(NSEvent *)theEvent 475{ 476 const bool accepted = [self handleMouseUpEvent:theEvent]; 477 if (!accepted) 478 [super rightMouseUp:theEvent]; 479} 480 481- (void)otherMouseDown:(NSEvent *)theEvent 482{ 483 const bool accepted = [self handleMouseDownEvent:theEvent]; 484 if (!accepted) 485 [super otherMouseDown:theEvent]; 486} 487 488- (void)otherMouseDragged:(NSEvent *)theEvent 489{ 490 const bool accepted = [self handleMouseDraggedEvent:theEvent]; 491 if (!accepted) 492 [super otherMouseDragged:theEvent]; 493} 494 495- (void)otherMouseUp:(NSEvent *)theEvent 496{ 497 const bool accepted = [self handleMouseUpEvent:theEvent]; 498 if (!accepted) 499 [super otherMouseUp:theEvent]; 500} 501 502- (void)cursorUpdate:(NSEvent *)theEvent 503{ 504 // Note: We do not get this callback when moving from a subview that 505 // uses the legacy cursorRect API, so the cursor is reset to the arrow 506 // cursor. See rdar://34183708 507 508 auto previousCursor = NSCursor.currentCursor; 509 510 if (self.cursor) 511 [self.cursor set]; 512 else 513 [super cursorUpdate:theEvent]; 514 515 if (NSCursor.currentCursor != previousCursor) 516 qCInfo(lcQpaMouse) << "Cursor update for" << self << "resulted in new cursor" << NSCursor.currentCursor; 517} 518 519- (void)mouseMovedImpl:(NSEvent *)theEvent 520{ 521 if (!m_platformWindow) 522 return; 523 524 if ([self isTransparentForUserInput]) 525 return; 526 527 QPointF windowPoint; 528 QPointF screenPoint; 529 [self convertFromScreen:[self screenMousePoint:theEvent] toWindowPoint:&windowPoint andScreenPoint:&screenPoint]; 530 QWindow *childWindow = m_platformWindow->childWindowAt(windowPoint.toPoint()); 531 532 // Top-level windows generate enter-leave events for sub-windows. 533 // Qt wants to know which window (if any) will be entered at the 534 // the time of the leave. This is dificult to accomplish by 535 // handling mouseEnter and mouseLeave envents, since they are sent 536 // individually to different views. 537 if (m_platformWindow->isContentView() && childWindow) { 538 if (childWindow != m_platformWindow->m_enterLeaveTargetWindow) { 539 QWindowSystemInterface::handleEnterLeaveEvent(childWindow, m_platformWindow->m_enterLeaveTargetWindow, windowPoint, screenPoint); 540 m_platformWindow->m_enterLeaveTargetWindow = childWindow; 541 } 542 } 543 544 // Cocoa keeps firing mouse move events for obscured parent views. Qt should not 545 // send those events so filter them out here. 546 if (childWindow != m_platformWindow->window()) 547 return; 548 549 [self handleMouseEvent: theEvent]; 550} 551 552- (void)mouseEnteredImpl:(NSEvent *)theEvent 553{ 554 Q_UNUSED(theEvent) 555 if (!m_platformWindow) 556 return; 557 558 m_platformWindow->m_windowUnderMouse = true; 559 560 if ([self isTransparentForUserInput]) 561 return; 562 563 // Top-level windows generate enter events for sub-windows. 564 if (!m_platformWindow->isContentView()) 565 return; 566 567 QPointF windowPoint; 568 QPointF screenPoint; 569 [self convertFromScreen:[self screenMousePoint:theEvent] toWindowPoint:&windowPoint andScreenPoint:&screenPoint]; 570 m_platformWindow->m_enterLeaveTargetWindow = m_platformWindow->childWindowAt(windowPoint.toPoint()); 571 572 qCInfo(lcQpaMouse) << QEvent::Enter << self << "at" << windowPoint << "with" << currentlyPressedMouseButtons(); 573 QWindowSystemInterface::handleEnterEvent(m_platformWindow->m_enterLeaveTargetWindow, windowPoint, screenPoint); 574} 575 576- (void)mouseExitedImpl:(NSEvent *)theEvent 577{ 578 Q_UNUSED(theEvent); 579 if (!m_platformWindow) 580 return; 581 582 m_platformWindow->m_windowUnderMouse = false; 583 584 if ([self isTransparentForUserInput]) 585 return; 586 587 // Top-level windows generate leave events for sub-windows. 588 if (!m_platformWindow->isContentView()) 589 return; 590 591 qCInfo(lcQpaMouse) << QEvent::Leave << self; 592 QWindowSystemInterface::handleLeaveEvent(m_platformWindow->m_enterLeaveTargetWindow); 593 m_platformWindow->m_enterLeaveTargetWindow = 0; 594} 595 596#if QT_CONFIG(wheelevent) 597- (void)scrollWheel:(NSEvent *)theEvent 598{ 599 if (!m_platformWindow) 600 return; 601 602 if ([self isTransparentForUserInput]) 603 return [super scrollWheel:theEvent]; 604 605 QPoint angleDelta; 606 Qt::MouseEventSource source = Qt::MouseEventNotSynthesized; 607 if ([theEvent hasPreciseScrollingDeltas]) { 608 // The mouse device contains pixel scroll wheel support (Mighty Mouse, Trackpad). 609 // Since deviceDelta is delivered as pixels rather than degrees, we need to 610 // convert from pixels to degrees in a sensible manner. 611 // It looks like 1/4 degrees per pixel behaves most native. 612 // (NB: Qt expects the unit for delta to be 8 per degree): 613 const int pixelsToDegrees = 2; // 8 * 1/4 614 angleDelta.setX([theEvent scrollingDeltaX] * pixelsToDegrees); 615 angleDelta.setY([theEvent scrollingDeltaY] * pixelsToDegrees); 616 source = Qt::MouseEventSynthesizedBySystem; 617 } else { 618 // Remove acceleration, and use either -120 or 120 as delta: 619 angleDelta.setX(qBound(-120, int([theEvent deltaX] * 10000), 120)); 620 angleDelta.setY(qBound(-120, int([theEvent deltaY] * 10000), 120)); 621 } 622 623 QPoint pixelDelta; 624 if ([theEvent hasPreciseScrollingDeltas]) { 625 pixelDelta.setX([theEvent scrollingDeltaX]); 626 pixelDelta.setY([theEvent scrollingDeltaY]); 627 } else { 628 // docs: "In the case of !hasPreciseScrollingDeltas, multiply the delta with the line width." 629 // scrollingDeltaX seems to return a minimum value of 0.1 in this case, map that to two pixels. 630 const CGFloat lineWithEstimate = 20.0; 631 pixelDelta.setX([theEvent scrollingDeltaX] * lineWithEstimate); 632 pixelDelta.setY([theEvent scrollingDeltaY] * lineWithEstimate); 633 } 634 635 QPointF qt_windowPoint; 636 QPointF qt_screenPoint; 637 [self convertFromScreen:[self screenMousePoint:theEvent] toWindowPoint:&qt_windowPoint andScreenPoint:&qt_screenPoint]; 638 NSTimeInterval timestamp = [theEvent timestamp]; 639 ulong qt_timestamp = timestamp * 1000; 640 641 Qt::ScrollPhase phase = Qt::NoScrollPhase; 642 if (theEvent.phase == NSEventPhaseMayBegin || theEvent.phase == NSEventPhaseBegan) { 643 // MayBegin is likely to happen. We treat it the same as an actual begin, 644 // and follow it with an update when the actual begin is delivered. 645 phase = m_scrolling ? Qt::ScrollUpdate : Qt::ScrollBegin; 646 m_scrolling = true; 647 } else if (theEvent.phase == NSEventPhaseStationary || theEvent.phase == NSEventPhaseChanged) { 648 phase = Qt::ScrollUpdate; 649 } else if (theEvent.phase == NSEventPhaseEnded) { 650 // A scroll event phase may be followed by a momentum phase after the user releases 651 // the finger, and in that case we don't want to send a Qt::ScrollEnd until after 652 // the momentum phase has ended. Unfortunately there isn't any guaranteed way of 653 // knowing whether or not a NSEventPhaseEnded will be followed by a momentum phase. 654 // The best we can do is to look at the event queue and hope that the system has 655 // had time to emit a momentum phase event. 656 if ([NSApp nextEventMatchingMask:NSEventMaskScrollWheel untilDate:[NSDate distantPast] 657 inMode:@"QtMomementumEventSearchMode" dequeue:NO].momentumPhase == NSEventPhaseBegan) { 658 Q_ASSERT(pixelDelta.isNull() && angleDelta.isNull()); 659 return; // Ignore this event, as it has a delta of 0,0 660 } 661 phase = Qt::ScrollEnd; 662 m_scrolling = false; 663 } else if (theEvent.momentumPhase == NSEventPhaseBegan) { 664 Q_ASSERT(!pixelDelta.isNull() && !angleDelta.isNull()); 665 phase = Qt::ScrollUpdate; // Send as update, it has a delta 666 } else if (theEvent.momentumPhase == NSEventPhaseChanged) { 667 phase = Qt::ScrollMomentum; 668 } else if (theEvent.phase == NSEventPhaseCancelled 669 || theEvent.momentumPhase == NSEventPhaseEnded 670 || theEvent.momentumPhase == NSEventPhaseCancelled) { 671 phase = Qt::ScrollEnd; 672 m_scrolling = false; 673 } else { 674 Q_ASSERT(theEvent.momentumPhase != NSEventPhaseStationary); 675 } 676 677 // Prevent keyboard modifier state from changing during scroll event streams. 678 // A two-finger trackpad flick generates a stream of scroll events. We want 679 // the keyboard modifier state to be the state at the beginning of the 680 // flick in order to avoid changing the interpretation of the events 681 // mid-stream. One example of this happening would be when pressing cmd 682 // after scrolling in Qt Creator: not taking the phase into account causes 683 // the end of the event stream to be interpreted as font size changes. 684 if (theEvent.momentumPhase == NSEventPhaseNone) 685 m_currentWheelModifiers = [QNSView convertKeyModifiers:[theEvent modifierFlags]]; 686 687 // "isInverted": natural OS X scrolling, inverted from the Qt/other platform/Jens perspective. 688 bool isInverted = [theEvent isDirectionInvertedFromDevice]; 689 690 qCInfo(lcQpaMouse).nospace() << phase << " at " << qt_windowPoint 691 << " pixelDelta=" << pixelDelta << " angleDelta=" << angleDelta 692 << (isInverted ? " inverted=true" : ""); 693 694 QWindowSystemInterface::handleWheelEvent(m_platformWindow->window(), qt_timestamp, qt_windowPoint, 695 qt_screenPoint, pixelDelta, angleDelta, m_currentWheelModifiers, phase, source, isInverted); 696} 697#endif // QT_CONFIG(wheelevent) 698 699@end 700