diff options
Diffstat (limited to 'src/plugins/platforms/cocoa/qcocoawindow.mm')
-rw-r--r-- | src/plugins/platforms/cocoa/qcocoawindow.mm | 120 |
1 files changed, 103 insertions, 17 deletions
diff --git a/src/plugins/platforms/cocoa/qcocoawindow.mm b/src/plugins/platforms/cocoa/qcocoawindow.mm index ef94b49736..8a1aee3ad4 100644 --- a/src/plugins/platforms/cocoa/qcocoawindow.mm +++ b/src/plugins/platforms/cocoa/qcocoawindow.mm @@ -526,9 +526,11 @@ NSUInteger QCocoaWindow::windowStyleMask(Qt::WindowFlags flags) // working (for example minimizing frameless windows, or resizing // windows that don't have zoom or fullscreen titlebar buttons). styleMask |= NSWindowStyleMaskClosable - | NSWindowStyleMaskResizable | NSWindowStyleMaskMiniaturizable; + if (type != Qt::Popup) // We only care about popups exactly. + styleMask |= NSWindowStyleMaskResizable; + if (type == Qt::Tool) styleMask |= NSWindowStyleMaskUtilityWindow; @@ -658,7 +660,7 @@ void QCocoaWindow::applyWindowState(Qt::WindowStates requestedState) if (!isContentView()) return; - const Qt::WindowState currentState = windowState(); + const Qt::WindowState currentState = QWindowPrivate::effectiveState(windowState()); const Qt::WindowState newState = QWindowPrivate::effectiveState(requestedState); if (newState == currentState) @@ -690,9 +692,10 @@ void QCocoaWindow::applyWindowState(Qt::WindowStates requestedState) switch (currentState) { case Qt::WindowMinimized: [nsWindow deminiaturize:sender]; - Q_ASSERT_X(windowState() != Qt::WindowMinimized, "QCocoaWindow", - "[NSWindow deminiaturize:] is synchronous"); - break; + // Deminiaturizing is not synchronous, so we need to wait for the + // NSWindowDidMiniaturizeNotification before continuing to apply + // the new state. + return; case Qt::WindowFullScreen: { toggleFullScreen(); // Exiting fullscreen is not synchronous, so we need to wait for the @@ -726,23 +729,27 @@ void QCocoaWindow::applyWindowState(Qt::WindowStates requestedState) } } -Qt::WindowState QCocoaWindow::windowState() const +Qt::WindowStates QCocoaWindow::windowState() const { - // FIXME: Support compound states (Qt::WindowStates) - + Qt::WindowStates states = Qt::WindowNoState; NSWindow *window = m_view.window; + if (window.miniaturized) - return Qt::WindowMinimized; - if (window.qt_fullScreen) - return Qt::WindowFullScreen; - if ((window.zoomed && !isTransitioningToFullScreen()) - || (m_lastReportedWindowState == Qt::WindowMaximized && isTransitioningToFullScreen())) - return Qt::WindowMaximized; + states |= Qt::WindowMinimized; + + // Full screen and maximized are mutually exclusive, as macOS + // will report a full screen window as zoomed. + if (window.qt_fullScreen) { + states |= Qt::WindowFullScreen; + } else if ((window.zoomed && !isTransitioningToFullScreen()) + || (m_lastReportedWindowState == Qt::WindowMaximized && isTransitioningToFullScreen())) { + states |= Qt::WindowMaximized; + } // Note: We do not report Qt::WindowActive, even if isActive() // is true, as QtGui does not expect this window state to be set. - return Qt::WindowNoState; + return states; } void QCocoaWindow::toggleMaximized() @@ -858,12 +865,20 @@ void QCocoaWindow::windowDidDeminiaturize() if (!isContentView()) return; + Qt::WindowState requestedState = window()->windowState(); + handleWindowStateChanged(); + + if (requestedState != windowState() && requestedState != Qt::WindowMinimized) { + // We were only going out of minimized as an intermediate step before + // progressing into the final step, so re-sync the desired state. + applyWindowState(requestedState); + } } void QCocoaWindow::handleWindowStateChanged(HandleFlags flags) { - Qt::WindowState currentState = windowState(); + Qt::WindowStates currentState = windowState(); if (!(flags & HandleUnconditionally) && currentState == m_lastReportedWindowState) return; @@ -1226,7 +1241,7 @@ void QCocoaWindow::windowDidResignKey() // Make sure popups are closed before we deliver activation changes, which are // otherwise ignored by QApplication. - QGuiApplicationPrivate::instance()->closeAllPopups(); + closeAllPopups(); // The current key window will be non-nil if another window became key. If that // window is a Qt window, we delay the window activation event until the didBecomeKey @@ -1573,6 +1588,75 @@ void QCocoaWindow::requestActivateWindow() [m_view.window makeKeyWindow]; } +/* + Closes all popups, and removes observers and monitors. +*/ +void QCocoaWindow::closeAllPopups() +{ + QGuiApplicationPrivate::instance()->closeAllPopups(); + + removePopupMonitor(); +} + +void QCocoaWindow::removePopupMonitor() +{ + if (s_globalMouseMonitor) { + [NSEvent removeMonitor:s_globalMouseMonitor]; + s_globalMouseMonitor = nil; + } + if (s_applicationActivationObserver) { + [[[NSWorkspace sharedWorkspace] notificationCenter] removeObserver:s_applicationActivationObserver]; + s_applicationActivationObserver = nil; + } +} + +void QCocoaWindow::setupPopupMonitor() +{ + // we open a popup window while we are not active. None of our existing event + // handlers will get called if the user now clicks anywhere outside the application + // or activates another window. Use a global event monitor to watch for mouse + // presses, and close popups. We also want mouse tracking in the popup to work, so + // also watch for MouseMoved. + if (!s_globalMouseMonitor) { + // we only get LeftMouseDown events when we also set LeftMouseUp. + constexpr NSEventMask mouseButtonMask = NSEventTypeLeftMouseDown | NSEventTypeLeftMouseUp + | NSEventMaskRightMouseDown | NSEventMaskOtherMouseDown + | NSEventMaskMouseMoved; + s_globalMouseMonitor = [NSEvent addGlobalMonitorForEventsMatchingMask:mouseButtonMask + handler:^(NSEvent *e){ + if (!QGuiApplicationPrivate::instance()->popupActive()) { + removePopupMonitor(); + return; + } + const auto eventType = cocoaEvent2QtMouseEvent(e); + if (eventType == QEvent::MouseMove) { + if (s_windowUnderMouse) { + QWindow *window = s_windowUnderMouse->window(); + const auto button = cocoaButton2QtButton(e); + const auto buttons = currentlyPressedMouseButtons(); + const auto globalPoint = QCocoaScreen::mapFromNative(NSEvent.mouseLocation); + const auto localPoint = window->mapFromGlobal(globalPoint.toPoint()); + QWindowSystemInterface::handleMouseEvent(window, localPoint, globalPoint, + buttons, button, eventType); + } + } else { + closeAllPopups(); + } + }]; + } + // The activation observer also gets called when we become active because the user clicks + // into the popup. This should not close the popup, so QCocoaApplicationDelegate's + // applicationDidBecomeActive implementation removes this observer. + if (!s_applicationActivationObserver) { + s_applicationActivationObserver = [[[NSWorkspace sharedWorkspace] notificationCenter] + addObserverForName:NSWorkspaceDidActivateApplicationNotification + object:nil queue:nil + usingBlock:^(NSNotification *){ + closeAllPopups(); + }]; + } +} + QCocoaNSWindow *QCocoaWindow::createNSWindow(bool shouldBePanel) { QMacAutoReleasePool pool; @@ -1678,6 +1762,8 @@ QCocoaNSWindow *QCocoaWindow::createNSWindow(bool shouldBePanel) if ((type & Qt::Popup) == Qt::Popup) { nsWindow.hasShadow = YES; nsWindow.animationBehavior = NSWindowAnimationBehaviorUtilityWindow; + if (QGuiApplication::applicationState() != Qt::ApplicationActive) + setupPopupMonitor(); } } |