diff options
Diffstat (limited to 'src/plugins/platforms/cocoa/qcocoawindow.mm')
-rw-r--r-- | src/plugins/platforms/cocoa/qcocoawindow.mm | 786 |
1 files changed, 393 insertions, 393 deletions
diff --git a/src/plugins/platforms/cocoa/qcocoawindow.mm b/src/plugins/platforms/cocoa/qcocoawindow.mm index 9b10c8b053..b79804fd0b 100644 --- a/src/plugins/platforms/cocoa/qcocoawindow.mm +++ b/src/plugins/platforms/cocoa/qcocoawindow.mm @@ -41,9 +41,6 @@ #include "qcocoascreen.h" #include "qnswindowdelegate.h" #include "qcocoaeventdispatcher.h" -#ifndef QT_NO_OPENGL -#include "qcocoaglcontext.h" -#endif #include "qcocoahelpers.h" #include "qcocoanativeinterface.h" #include "qnsview.h" @@ -117,24 +114,19 @@ static void qRegisterNotificationCallbacks() return; } - if (lcCocoaNotifications().isDebugEnabled()) { - if (cocoaWindows.isEmpty()) { - qCDebug(lcCocoaNotifications) << "Could not find forwarding target for" << - qPrintable(notificationName) << "from" << notification.object; - } else { - QVector<QCocoaWindow *> debugWindows; - for (QCocoaWindow *cocoaWindow : cocoaWindows) - debugWindows += cocoaWindow; - qCDebug(lcCocoaNotifications) << "Forwarding" << qPrintable(notificationName) << - "to" << debugWindows; - } + if (lcCocoaNotifications().isDebugEnabled() && !cocoaWindows.isEmpty()) { + QVector<QCocoaWindow *> debugWindows; + for (QCocoaWindow *cocoaWindow : cocoaWindows) + debugWindows += cocoaWindow; + qCDebug(lcCocoaNotifications) << "Forwarding" << qPrintable(notificationName) << + "to" << debugWindows; } // FIXME: Could be a foreign window, look up by iterating top level QWindows for (QCocoaWindow *cocoaWindow : cocoaWindows) { if (!method.invoke(cocoaWindow, Qt::DirectConnection)) { - qCWarning(lcQpaCocoaWindow) << "Failed to invoke NSNotification callback for" + qCWarning(lcQpaWindow) << "Failed to invoke NSNotification callback for" << notification.name << "on" << cocoaWindow; } } @@ -148,9 +140,7 @@ const int QCocoaWindow::NoAlertRequest = -1; QCocoaWindow::QCocoaWindow(QWindow *win, WId nativeHandle) : QPlatformWindow(win) , m_view(nil) - , m_nsWindow(0) - , m_viewIsEmbedded(false) - , m_viewIsToBeEmbedded(false) + , m_nsWindow(nil) , m_lastReportedWindowState(Qt::WindowNoState) , m_windowModality(Qt::NonModal) , m_windowUnderMouse(false) @@ -158,10 +148,7 @@ QCocoaWindow::QCocoaWindow(QWindow *win, WId nativeHandle) , m_inSetVisible(false) , m_inSetGeometry(false) , m_inSetStyleMask(false) -#ifndef QT_NO_OPENGL - , m_glContext(0) -#endif - , m_menubar(0) + , m_menubar(nullptr) , m_needsInvalidateShadow(false) , m_hasModalSession(false) , m_frameStrutEventsEnabled(false) @@ -173,7 +160,7 @@ QCocoaWindow::QCocoaWindow(QWindow *win, WId nativeHandle) , m_topContentBorderThickness(0) , m_bottomContentBorderThickness(0) { - qCDebug(lcQpaCocoaWindow) << "QCocoaWindow::QCocoaWindow" << window(); + qCDebug(lcQpaWindow) << "QCocoaWindow::QCocoaWindow" << window(); if (nativeHandle) { m_view = reinterpret_cast<NSView *>(nativeHandle); @@ -183,41 +170,24 @@ QCocoaWindow::QCocoaWindow(QWindow *win, WId nativeHandle) void QCocoaWindow::initialize() { - qCDebug(lcQpaCocoaWindow) << "QCocoaWindow::initialize" << window(); + qCDebug(lcQpaWindow) << "QCocoaWindow::initialize" << window(); QMacAutoReleasePool pool; - if (!m_view) { + if (!m_view) m_view = [[QNSView alloc] initWithCocoaWindow:this]; - // Enable high-dpi OpenGL for retina displays. Enabling has the side - // effect that Cocoa will start calling glViewport(0, 0, width, height), - // overriding any glViewport calls in application code. This is usually not a - // problem, except if the appilcation wants to have a "custom" viewport. - // (like the hellogl example) - if (window()->supportsOpenGL()) { - BOOL enable = qt_mac_resolveOption(YES, window(), "_q_mac_wantsBestResolutionOpenGLSurface", - "QT_MAC_WANTS_BEST_RESOLUTION_OPENGL_SURFACE"); - [m_view setWantsBestResolutionOpenGLSurface:enable]; - // See also QCocoaGLContext::makeCurrent for software renderer workarounds. - } - BOOL enable = qt_mac_resolveOption(NO, window(), "_q_mac_wantsLayer", - "QT_MAC_WANTS_LAYER"); - [m_view setWantsLayer:enable]; - } setGeometry(initialGeometry(window(), windowGeometry(), defaultWindowWidth, defaultWindowHeight)); recreateWindowIfNeeded(); window()->setGeometry(geometry()); - if (window()->isTopLevel()) - setWindowIcon(window()->icon()); m_initialized = true; } QCocoaWindow::~QCocoaWindow() { - qCDebug(lcQpaCocoaWindow) << "QCocoaWindow::~QCocoaWindow" << window(); + qCDebug(lcQpaWindow) << "QCocoaWindow::~QCocoaWindow" << window(); QMacAutoReleasePool pool; [m_nsWindow makeFirstResponder:nil]; @@ -232,10 +202,16 @@ QCocoaWindow::~QCocoaWindow() if (!isForeignWindow()) [[NSNotificationCenter defaultCenter] removeObserver:m_view]; - // While it is unlikely that this window will be in the popup stack - // during deletetion we clear any pointers here to make sure. - if (QCocoaIntegration::instance()) { - QCocoaIntegration::instance()->popupWindowStack()->removeAll(this); + if (QCocoaIntegration *cocoaIntegration = QCocoaIntegration::instance()) { + // While it is unlikely that this window will be in the popup stack + // during deletetion we clear any pointers here to make sure. + cocoaIntegration->popupWindowStack()->removeAll(this); + +#if QT_CONFIG(vulkan) + auto vulcanInstance = cocoaIntegration->getCocoaVulkanInstance(); + if (vulcanInstance) + vulcanInstance->destroySurface(m_vulkanSurface); +#endif } [m_view release]; @@ -255,7 +231,7 @@ QSurfaceFormat QCocoaWindow::format() const void QCocoaWindow::setGeometry(const QRect &rectIn) { - qCDebug(lcQpaCocoaWindow) << "QCocoaWindow::setGeometry" << window() << rectIn; + qCDebug(lcQpaWindow) << "QCocoaWindow::setGeometry" << window() << rectIn; QBoolBlocker inSetGeometry(m_inSetGeometry, true); @@ -283,7 +259,7 @@ QRect QCocoaWindow::geometry() const // QWindows that are embedded in a NSView hiearchy may be considered // top-level from Qt's point of view but are not from Cocoa's point // of view. Embedded QWindows get global (screen) geometry. - if (m_viewIsEmbedded) { + if (isEmbedded()) { NSPoint windowPoint = [m_view convertPoint:NSMakePoint(0, 0) toView:nil]; NSRect screenRect = [[m_view window] convertRectToScreen:NSMakeRect(windowPoint.x, windowPoint.y, 1, 1)]; NSPoint screenPoint = screenRect.origin; @@ -297,12 +273,12 @@ QRect QCocoaWindow::geometry() const void QCocoaWindow::setCocoaGeometry(const QRect &rect) { - qCDebug(lcQpaCocoaWindow) << "QCocoaWindow::setCocoaGeometry" << window() << rect; + qCDebug(lcQpaWindow) << "QCocoaWindow::setCocoaGeometry" << window() << rect; QMacAutoReleasePool pool; QPlatformWindow::setGeometry(rect); - if (m_viewIsEmbedded) { + if (isEmbedded()) { if (!isForeignWindow()) { [m_view setFrame:NSMakeRect(0, 0, rect.width(), rect.height())]; } @@ -321,12 +297,12 @@ void QCocoaWindow::setCocoaGeometry(const QRect &rect) void QCocoaWindow::setVisible(bool visible) { - qCDebug(lcQpaCocoaWindow) << "QCocoaWindow::setVisible" << window() << visible; + qCDebug(lcQpaWindow) << "QCocoaWindow::setVisible" << window() << visible; m_inSetVisible = true; QMacAutoReleasePool pool; - QCocoaWindow *parentCocoaWindow = 0; + QCocoaWindow *parentCocoaWindow = nullptr; if (window()->transientParent()) parentCocoaWindow = static_cast<QCocoaWindow *>(window()->transientParent()->handle()); @@ -378,13 +354,13 @@ void QCocoaWindow::setVisible(bool visible) } else if (window()->modality() != Qt::NonModal) { // show the window as application modal QCocoaEventDispatcher *cocoaEventDispatcher = qobject_cast<QCocoaEventDispatcher *>(QGuiApplication::instance()->eventDispatcher()); - Q_ASSERT(cocoaEventDispatcher != 0); + Q_ASSERT(cocoaEventDispatcher); QCocoaEventDispatcherPrivate *cocoaEventDispatcherPrivate = static_cast<QCocoaEventDispatcherPrivate *>(QObjectPrivate::get(cocoaEventDispatcher)); cocoaEventDispatcherPrivate->beginModalSession(window()); m_hasModalSession = true; } else if ([m_view.window canBecomeKeyWindow]) { QCocoaEventDispatcher *cocoaEventDispatcher = qobject_cast<QCocoaEventDispatcher *>(QGuiApplication::instance()->eventDispatcher()); - QCocoaEventDispatcherPrivate *cocoaEventDispatcherPrivate = 0; + QCocoaEventDispatcherPrivate *cocoaEventDispatcherPrivate = nullptr; if (cocoaEventDispatcher) cocoaEventDispatcherPrivate = static_cast<QCocoaEventDispatcherPrivate *>(QObjectPrivate::get(cocoaEventDispatcher)); @@ -403,11 +379,12 @@ void QCocoaWindow::setVisible(bool visible) if (!(parentCocoaWindow && window()->transientParent()->isActive()) && window()->type() == Qt::Popup) { removeMonitor(); monitor = [NSEvent addGlobalMonitorForEventsMatchingMask:NSLeftMouseDownMask|NSRightMouseDownMask|NSOtherMouseDownMask|NSMouseMovedMask handler:^(NSEvent *e) { - QPointF localPoint = QCocoaScreen::mapFromNative([NSEvent mouseLocation]); - const auto button = e.type == NSEventTypeMouseMoved ? Qt::NoButton : cocoaButton2QtButton([e buttonNumber]); - const auto eventType = e.type == NSEventTypeMouseMoved ? QEvent::MouseMove : QEvent::MouseButtonPress; - QWindowSystemInterface::handleMouseEvent(window(), window()->mapFromGlobal(localPoint.toPoint()), localPoint, - Qt::MouseButtons(uint(NSEvent.pressedMouseButtons & 0xFFFF)), button, eventType); + const auto button = cocoaButton2QtButton(e); + const auto buttons = currentlyPressedMouseButtons(); + const auto eventType = cocoaEvent2QtMouseEvent(e); + const auto globalPoint = QCocoaScreen::mapFromNative(NSEvent.mouseLocation); + const auto localPoint = window()->mapFromGlobal(globalPoint.toPoint()); + QWindowSystemInterface::handleMouseEvent(window(), localPoint, globalPoint, buttons, button, eventType); }]; } } @@ -420,12 +397,8 @@ void QCocoaWindow::setVisible(bool visible) [m_view setHidden:NO]; } else { // qDebug() << "close" << this; -#ifndef QT_NO_OPENGL - if (m_glContext) - m_glContext->windowWasHidden(); -#endif QCocoaEventDispatcher *cocoaEventDispatcher = qobject_cast<QCocoaEventDispatcher *>(QGuiApplication::instance()->eventDispatcher()); - QCocoaEventDispatcherPrivate *cocoaEventDispatcherPrivate = 0; + QCocoaEventDispatcherPrivate *cocoaEventDispatcherPrivate = nullptr; if (cocoaEventDispatcher) cocoaEventDispatcherPrivate = static_cast<QCocoaEventDispatcherPrivate *>(QObjectPrivate::get(cocoaEventDispatcher)); if (isContentView()) { @@ -493,7 +466,8 @@ NSInteger QCocoaWindow::windowLevel(Qt::WindowFlags flags) // Any "special" window should be in at least the same level as its parent. if (type != Qt::Window) { const QWindow * const transientParent = window()->transientParent(); - const QCocoaWindow * const transientParentWindow = transientParent ? static_cast<QCocoaWindow *>(transientParent->handle()) : 0; + const QCocoaWindow * const transientParentWindow = transientParent ? + static_cast<QCocoaWindow *>(transientParent->handle()) : nullptr; if (transientParentWindow) windowLevel = qMax([transientParentWindow->nativeWindow() level], windowLevel); } @@ -562,54 +536,283 @@ void QCocoaWindow::setWindowZoomButton(Qt::WindowFlags flags) void QCocoaWindow::setWindowFlags(Qt::WindowFlags flags) { - if (isContentView()) { - // While setting style mask we can have handleGeometryChange calls on a content - // view with null geometry, reporting an invalid coordinates as a result. - m_inSetStyleMask = true; - m_view.window.styleMask = windowStyleMask(flags); - m_inSetStyleMask = false; - m_view.window.level = this->windowLevel(flags); - - m_view.window.hasShadow = !(flags & Qt::NoDropShadowWindowHint); - - if (!(flags & Qt::FramelessWindowHint)) - setWindowTitle(window()->title()); - - Qt::WindowType type = window()->type(); - if ((type & Qt::Popup) != Qt::Popup && (type & Qt::Dialog) != Qt::Dialog) { - NSWindowCollectionBehavior behavior = m_view.window.collectionBehavior; - if ((flags & Qt::WindowFullscreenButtonHint) || m_view.window.qt_fullScreen) { - behavior |= NSWindowCollectionBehaviorFullScreenPrimary; - behavior &= ~NSWindowCollectionBehaviorFullScreenAuxiliary; - } else { - behavior |= NSWindowCollectionBehaviorFullScreenAuxiliary; - behavior &= ~NSWindowCollectionBehaviorFullScreenPrimary; - } - m_view.window.collectionBehavior = behavior; + if (!isContentView()) + return; + + // While setting style mask we can have handleGeometryChange calls on a content + // view with null geometry, reporting an invalid coordinates as a result. + m_inSetStyleMask = true; + m_view.window.styleMask = windowStyleMask(flags); + m_inSetStyleMask = false; + + Qt::WindowType type = static_cast<Qt::WindowType>(int(flags & Qt::WindowType_Mask)); + if ((type & Qt::Popup) != Qt::Popup && (type & Qt::Dialog) != Qt::Dialog) { + NSWindowCollectionBehavior behavior = m_view.window.collectionBehavior; + if ((flags & Qt::WindowFullscreenButtonHint) || m_view.window.qt_fullScreen) { + behavior |= NSWindowCollectionBehaviorFullScreenPrimary; + behavior &= ~NSWindowCollectionBehaviorFullScreenAuxiliary; + } else { + behavior |= NSWindowCollectionBehaviorFullScreenAuxiliary; + behavior &= ~NSWindowCollectionBehaviorFullScreenPrimary; } - setWindowZoomButton(flags); - - // Make window ignore mouse events if WindowTransparentForInput is set. - // Note that ignoresMouseEvents has a special initial state where events - // are ignored (passed through) based on window transparency, and that - // setting the property to false does not return us to that state. Instead, - // this makes the window capture all mouse events. Take care to only - // set the property if needed. FIXME: recreate window if needed or find - // some other way to implement WindowTransparentForInput. - bool ignoreMouse = flags & Qt::WindowTransparentForInput; - if (m_view.window.ignoresMouseEvents != ignoreMouse) - m_view.window.ignoresMouseEvents = ignoreMouse; + m_view.window.collectionBehavior = behavior; } - m_windowFlags = flags; + // Set styleMask and collectionBehavior before applying window level, as + // the window level change will trigger verification of the two properties. + m_view.window.level = this->windowLevel(flags); + + m_view.window.hasShadow = !(flags & Qt::NoDropShadowWindowHint); + + if (!(flags & Qt::FramelessWindowHint)) + setWindowTitle(window()->title()); + + setWindowZoomButton(flags); + + // Make window ignore mouse events if WindowTransparentForInput is set. + // Note that ignoresMouseEvents has a special initial state where events + // are ignored (passed through) based on window transparency, and that + // setting the property to false does not return us to that state. Instead, + // this makes the window capture all mouse events. Take care to only + // set the property if needed. FIXME: recreate window if needed or find + // some other way to implement WindowTransparentForInput. + bool ignoreMouse = flags & Qt::WindowTransparentForInput; + if (m_view.window.ignoresMouseEvents != ignoreMouse) + m_view.window.ignoresMouseEvents = ignoreMouse; } +// ----------------------- Window state ----------------------- + +/*! + Changes the state of the NSWindow, going in/out of minimize/zoomed/fullscreen + + When this is called from QWindow::setWindowState(), the QWindow state has not been + updated yet, so window()->windowState() will reflect the previous state that was + reported to QtGui. +*/ void QCocoaWindow::setWindowState(Qt::WindowStates state) { if (window()->isVisible()) applyWindowState(state); // Window state set for hidden windows take effect when show() is called } +void QCocoaWindow::applyWindowState(Qt::WindowStates requestedState) +{ + if (!isContentView()) + return; + + const Qt::WindowState currentState = windowState(); + const Qt::WindowState newState = QWindowPrivate::effectiveState(requestedState); + + if (newState == currentState) + return; + + qCDebug(lcQpaWindow) << "Applying" << newState << "to" << window() << "in" << currentState; + + const NSSize contentSize = m_view.frame.size; + if (contentSize.width <= 0 || contentSize.height <= 0) { + // If content view width or height is 0 then the window animations will crash so + // do nothing. We report the current state back to reflect the failed operation. + qWarning("invalid window content view size, check your window geometry"); + handleWindowStateChanged(HandleUnconditionally); + return; + } + + const NSWindow *nsWindow = m_view.window; + + if (nsWindow.styleMask & NSUtilityWindowMask + && newState & (Qt::WindowMinimized | Qt::WindowFullScreen)) { + qWarning() << window()->type() << "windows can not be made" << newState; + handleWindowStateChanged(HandleUnconditionally); + return; + } + + const id sender = nsWindow; + + // First we need to exit states that can't transition directly to other states + switch (currentState) { + case Qt::WindowMinimized: + [nsWindow deminiaturize:sender]; + Q_ASSERT_X(windowState() != Qt::WindowMinimized, "QCocoaWindow", + "[NSWindow deminiaturize:] is synchronous"); + break; + case Qt::WindowFullScreen: { + toggleFullScreen(); + // Exiting fullscreen is not synchronous, so we need to wait for the + // NSWindowDidExitFullScreenNotification before continuing to apply + // the new state. + return; + } + default:; + } + + // Then we apply the new state if needed + if (newState == windowState()) + return; + + switch (newState) { + case Qt::WindowFullScreen: + toggleFullScreen(); + break; + case Qt::WindowMaximized: + toggleMaximized(); + break; + case Qt::WindowMinimized: + [nsWindow miniaturize:sender]; + break; + case Qt::WindowNoState: + if (windowState() == Qt::WindowMaximized) + toggleMaximized(); + break; + default: + Q_UNREACHABLE(); + } +} + +Qt::WindowState QCocoaWindow::windowState() const +{ + // FIXME: Support compound states (Qt::WindowStates) + + 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; + + // 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; +} + +void QCocoaWindow::toggleMaximized() +{ + const NSWindow *window = m_view.window; + + // The NSWindow needs to be resizable, otherwise the window will + // not be possible to zoom back to non-zoomed state. + const bool wasResizable = window.styleMask & NSResizableWindowMask; + window.styleMask |= NSResizableWindowMask; + + const id sender = window; + [window zoom:sender]; + + if (!wasResizable) + window.styleMask &= ~NSResizableWindowMask; +} + +void QCocoaWindow::toggleFullScreen() +{ + const NSWindow *window = m_view.window; + + // The window needs to have the correct collection behavior for the + // toggleFullScreen call to have an effect. The collection behavior + // will be reset in windowDidEnterFullScreen/windowDidLeaveFullScreen. + window.collectionBehavior |= NSWindowCollectionBehaviorFullScreenPrimary; + + const id sender = window; + [window toggleFullScreen:sender]; +} + +void QCocoaWindow::windowWillEnterFullScreen() +{ + if (!isContentView()) + return; + + // The NSWindow needs to be resizable, otherwise we'll end up with + // the normal window geometry, centered in the middle of the screen + // on a black background. The styleMask will be reset below. + m_view.window.styleMask |= NSResizableWindowMask; +} + +bool QCocoaWindow::isTransitioningToFullScreen() const +{ + NSWindow *window = m_view.window; + return window.styleMask & NSFullScreenWindowMask && !window.qt_fullScreen; +} + +void QCocoaWindow::windowDidEnterFullScreen() +{ + if (!isContentView()) + return; + + Q_ASSERT_X(m_view.window.qt_fullScreen, "QCocoaWindow", + "FullScreen category processes window notifications first"); + + // Reset to original styleMask + setWindowFlags(window()->flags()); + + handleWindowStateChanged(); +} + +void QCocoaWindow::windowWillExitFullScreen() +{ + if (!isContentView()) + return; + + // The NSWindow needs to be resizable, otherwise we'll end up with + // a weird zoom animation. The styleMask will be reset below. + m_view.window.styleMask |= NSResizableWindowMask; +} + +void QCocoaWindow::windowDidExitFullScreen() +{ + if (!isContentView()) + return; + + Q_ASSERT_X(!m_view.window.qt_fullScreen, "QCocoaWindow", + "FullScreen category processes window notifications first"); + + // Reset to original styleMask + setWindowFlags(window()->flags()); + + Qt::WindowState requestedState = window()->windowState(); + + // Deliver update of QWindow state + handleWindowStateChanged(); + + if (requestedState != windowState() && requestedState != Qt::WindowFullScreen) { + // We were only going out of full screen as an intermediate step before + // progressing into the final step, so re-sync the desired state. + applyWindowState(requestedState); + } +} + +void QCocoaWindow::windowDidMiniaturize() +{ + if (!isContentView()) + return; + + handleWindowStateChanged(); +} + +void QCocoaWindow::windowDidDeminiaturize() +{ + if (!isContentView()) + return; + + handleWindowStateChanged(); +} + +void QCocoaWindow::handleWindowStateChanged(HandleFlags flags) +{ + Qt::WindowState currentState = windowState(); + if (!(flags & HandleUnconditionally) && currentState == m_lastReportedWindowState) + return; + + qCDebug(lcQpaWindow) << "QCocoaWindow::handleWindowStateChanged" << + m_lastReportedWindowState << "-->" << currentState; + + QWindowSystemInterface::handleWindowStateChanged<QWindowSystemInterface::SynchronousDelivery>( + window(), currentState, m_lastReportedWindowState); + m_lastReportedWindowState = currentState; +} + +// ------------------------------------------------------------ + void QCocoaWindow::setWindowTitle(const QString &title) { if (!isContentView()) @@ -680,7 +883,7 @@ bool QCocoaWindow::isAlertState() const void QCocoaWindow::raise() { - qCDebug(lcQpaCocoaWindow) << "QCocoaWindow::raise" << window(); + qCDebug(lcQpaWindow) << "QCocoaWindow::raise" << window(); // ### handle spaces (see Qt 4 raise_sys in qwidget_mac.mm) if (!isContentView()) @@ -705,7 +908,7 @@ void QCocoaWindow::raise() void QCocoaWindow::lower() { - qCDebug(lcQpaCocoaWindow) << "QCocoaWindow::lower" << window(); + qCDebug(lcQpaWindow) << "QCocoaWindow::lower" << window(); if (!isContentView()) return; @@ -718,6 +921,22 @@ bool QCocoaWindow::isExposed() const return !m_exposedRect.isEmpty(); } +bool QCocoaWindow::isEmbedded() const +{ + // Child QWindows are not embedded + if (window()->parent()) + return false; + + // Top-level QWindows with non-Qt NSWindows are embedded + if (m_view.window) + return !([m_view.window isKindOfClass:[QNSWindow class]] || + [m_view.window isKindOfClass:[QNSPanel class]]); + + // The window has no QWindow parent but also no NSWindow, + // conservatively reuturn false. + return false; +} + bool QCocoaWindow::isOpaque() const { // OpenGL surfaces can be ordered either above(default) or below the NSWindow. @@ -737,7 +956,7 @@ void QCocoaWindow::propagateSizeHints() if (!isContentView()) return; - qCDebug(lcQpaCocoaWindow) << "QCocoaWindow::propagateSizeHints" << window() + qCDebug(lcQpaWindow) << "QCocoaWindow::propagateSizeHints" << window() << "min:" << windowMinimumSize() << "max:" << windowMaximumSize() << "increment:" << windowSizeIncrement() << "base:" << windowBaseSize(); @@ -754,7 +973,7 @@ void QCocoaWindow::propagateSizeHints() window.contentMaxSize = NSSizeFromCGSize(windowMaximumSize().toCGSize()); // The window may end up with a fixed size; in this case the zoom button should be disabled. - setWindowZoomButton(m_windowFlags); + setWindowZoomButton(this->window()->flags()); // sizeIncrement is observed to take values of (-1, -1) and (0, 0) for windows that should be // resizable and that have no specific size increment set. Cocoa expects (1.0, 1.0) in this case. @@ -771,7 +990,7 @@ void QCocoaWindow::propagateSizeHints() void QCocoaWindow::setOpacity(qreal level) { - qCDebug(lcQpaCocoaWindow) << "QCocoaWindow::setOpacity" << level; + qCDebug(lcQpaWindow) << "QCocoaWindow::setOpacity" << level; if (!isContentView()) return; @@ -780,7 +999,7 @@ void QCocoaWindow::setOpacity(qreal level) void QCocoaWindow::setMask(const QRegion ®ion) { - qCDebug(lcQpaCocoaWindow) << "QCocoaWindow::setMask" << window() << region; + qCDebug(lcQpaWindow) << "QCocoaWindow::setMask" << window() << region; if (m_view.layer) { if (!region.isEmpty()) { @@ -803,18 +1022,12 @@ void QCocoaWindow::setMask(const QRegion ®ion) // time, and so would not result in an updated backingstore. m_needsInvalidateShadow = true; [m_view setNeedsDisplay:YES]; - - // FIXME: [NSWindow invalidateShadow] has no effect when in layer-backed mode, - // so if the mask is changed after the initial mask is applied, it will not - // result in any visual change to the shadow. This is an Apple bug, and there - // may be ways to work around it, such as calling setFrame on the window to - // trigger some internal invalidation, but that needs more research. } } bool QCocoaWindow::setKeyboardGrabEnabled(bool grab) { - qCDebug(lcQpaCocoaWindow) << "QCocoaWindow::setKeyboardGrabEnabled" << window() << grab; + qCDebug(lcQpaWindow) << "QCocoaWindow::setKeyboardGrabEnabled" << window() << grab; if (!isContentView()) return false; @@ -826,7 +1039,7 @@ bool QCocoaWindow::setKeyboardGrabEnabled(bool grab) bool QCocoaWindow::setMouseGrabEnabled(bool grab) { - qCDebug(lcQpaCocoaWindow) << "QCocoaWindow::setMouseGrabEnabled" << window() << grab; + qCDebug(lcQpaWindow) << "QCocoaWindow::setMouseGrabEnabled" << window() << grab; if (!isContentView()) return false; @@ -843,10 +1056,10 @@ WId QCocoaWindow::winId() const void QCocoaWindow::setParent(const QPlatformWindow *parentWindow) { - qCDebug(lcQpaCocoaWindow) << "QCocoaWindow::setParent" << window() << (parentWindow ? parentWindow->window() : 0); + qCDebug(lcQpaWindow) << "QCocoaWindow::setParent" << window() << (parentWindow ? parentWindow->window() : 0); // recreate the window for compatibility - bool unhideAfterRecreate = parentWindow && !m_viewIsToBeEmbedded && ![m_view isHidden]; + bool unhideAfterRecreate = parentWindow && !isEmbedded() && ![m_view isHidden]; recreateWindowIfNeeded(); if (unhideAfterRecreate) [m_view setHidden:NO]; @@ -863,9 +1076,8 @@ NSWindow *QCocoaWindow::nativeWindow() const return m_view.window; } -void QCocoaWindow::setEmbeddedInForeignView(bool embedded) +void QCocoaWindow::setEmbeddedInForeignView() { - m_viewIsToBeEmbedded = embedded; // Release any previosly created NSWindow. [m_nsWindow closeAndRelease]; m_nsWindow = 0; @@ -948,8 +1160,8 @@ void QCocoaWindow::windowDidBecomeKey() QWindowSystemInterface::handleEnterEvent(m_enterLeaveTargetWindow, windowPoint, screenPoint); } - if (!windowIsPopupType() && !qnsview_cast(m_view).isMenuView) - QWindowSystemInterface::handleWindowActivated(window()); + if (!windowIsPopupType()) + QWindowSystemInterface::handleWindowActivated<QWindowSystemInterface::SynchronousDelivery>(window()); } void QCocoaWindow::windowDidResignKey() @@ -966,82 +1178,8 @@ void QCocoaWindow::windowDidResignKey() NSWindow *keyWindow = [NSApp keyWindow]; if (!keyWindow || keyWindow == m_view.window) { // No new key window, go ahead and set the active window to zero - if (!windowIsPopupType() && !qnsview_cast(m_view).isMenuView) - QWindowSystemInterface::handleWindowActivated(0); - } -} - -void QCocoaWindow::windowDidMiniaturize() -{ - if (!isContentView()) - return; - - handleWindowStateChanged(); -} - -void QCocoaWindow::windowDidDeminiaturize() -{ - if (!isContentView()) - return; - - handleWindowStateChanged(); -} - -void QCocoaWindow::windowWillEnterFullScreen() -{ - if (!isContentView()) - return; - - // The NSWindow needs to be resizable, otherwise we'll end up with - // the normal window geometry, centered in the middle of the screen - // on a black background. The styleMask will be reset below. - m_view.window.styleMask |= NSResizableWindowMask; -} - -void QCocoaWindow::windowDidEnterFullScreen() -{ - if (!isContentView()) - return; - - Q_ASSERT_X(m_view.window.qt_fullScreen, "QCocoaWindow", - "FullScreen category processes window notifications first"); - - // Reset to original styleMask - setWindowFlags(m_windowFlags); - - handleWindowStateChanged(); -} - -void QCocoaWindow::windowWillExitFullScreen() -{ - if (!isContentView()) - return; - - // The NSWindow needs to be resizable, otherwise we'll end up with - // a weird zoom animation. The styleMask will be reset below. - m_view.window.styleMask |= NSResizableWindowMask; -} - -void QCocoaWindow::windowDidExitFullScreen() -{ - if (!isContentView()) - return; - - Q_ASSERT_X(!m_view.window.qt_fullScreen, "QCocoaWindow", - "FullScreen category processes window notifications first"); - - // Reset to original styleMask - setWindowFlags(m_windowFlags); - - Qt::WindowState requestedState = window()->windowState(); - - // Deliver update of QWindow state - handleWindowStateChanged(); - - if (requestedState != windowState() && requestedState != Qt::WindowFullScreen) { - // We were only going out of full screen as an intermediate step before - // progressing into the final step, so re-sync the desired state. - applyWindowState(requestedState); + if (!windowIsPopupType()) + QWindowSystemInterface::handleWindowActivated<QWindowSystemInterface::SynchronousDelivery>(0); } } @@ -1068,8 +1206,24 @@ void QCocoaWindow::windowDidChangeScreen() if (!window()) return; - if (QCocoaScreen *cocoaScreen = QCocoaIntegration::instance()->screenForNSScreen(m_view.window.screen)) - QWindowSystemInterface::handleWindowScreenChanged(window(), cocoaScreen->screen()); + const bool wasRunningDisplayLink = static_cast<QCocoaScreen *>(screen())->isRunningDisplayLink(); + + if (QCocoaScreen *newScreen = QCocoaIntegration::instance()->screenForNSScreen(m_view.window.screen)) { + if (newScreen == screen()) { + // Screen properties have changed. Will be handled by + // NSApplicationDidChangeScreenParametersNotification + // in QCocoaIntegration::updateScreens(). + return; + } + + qCDebug(lcQpaWindow) << window() << "moved to" << newScreen; + QWindowSystemInterface::handleWindowScreenChanged<QWindowSystemInterface::SynchronousDelivery>(window(), newScreen->screen()); + + if (hasPendingUpdateRequest() && wasRunningDisplayLink) + requestUpdate(); // Restart display-link on new screen + } else { + qCWarning(lcQpaWindow) << "Failed to get QCocoaScreen for" << m_view.window.screen; + } } void QCocoaWindow::windowWillClose() @@ -1083,7 +1237,7 @@ void QCocoaWindow::windowWillClose() bool QCocoaWindow::windowShouldClose() { - qCDebug(lcQpaCocoaWindow) << "QCocoaWindow::windowShouldClose" << window(); + qCDebug(lcQpaWindow) << "QCocoaWindow::windowShouldClose" << window(); // This callback should technically only determine if the window // should (be allowed to) close, but since our QPA API to determine // that also involves actually closing the window we do both at the @@ -1104,22 +1258,14 @@ void QCocoaWindow::handleGeometryChange() if (!m_initialized) return; - // Don't send the geometry change if the QWindow is designated to be - // embedded in a foreign view hierarchy but has not actually been - // embedded yet - it's too early. - if (m_viewIsToBeEmbedded && !m_viewIsEmbedded) - return; - // It can happen that the current NSWindow is nil (if we are changing styleMask // from/to borderless, and the content view is being re-parented), which results // in invalid coordinates. if (m_inSetStyleMask && !m_view.window) return; - const bool isEmbedded = m_viewIsToBeEmbedded || m_viewIsEmbedded; - QRect newGeometry; - if (isContentView() && !isEmbedded) { + if (isContentView() && !isEmbedded()) { // Content views are positioned at (0, 0) in the window, so we resolve via the window CGRect contentRect = [m_view.window contentRectForFrameRect:m_view.window.frame]; @@ -1130,7 +1276,7 @@ void QCocoaWindow::handleGeometryChange() newGeometry = QRectF::fromCGRect(m_view.frame).toRect(); } - qCDebug(lcQpaCocoaWindow) << "QCocoaWindow::handleGeometryChange" << window() + qCDebug(lcQpaWindow) << "QCocoaWindow::handleGeometryChange" << window() << "current" << geometry() << "new" << newGeometry; QWindowSystemInterface::handleGeometryChange(window(), newGeometry); @@ -1162,24 +1308,10 @@ void QCocoaWindow::handleExposeEvent(const QRegion ®ion) m_exposedRect = QRect(); } - qCDebug(lcQpaCocoaDrawing) << "QCocoaWindow::handleExposeEvent" << window() << region << "isExposed" << isExposed(); + qCDebug(lcQpaDrawing) << "QCocoaWindow::handleExposeEvent" << window() << region << "isExposed" << isExposed(); QWindowSystemInterface::handleExposeEvent<QWindowSystemInterface::SynchronousDelivery>(window(), region); } -void QCocoaWindow::handleWindowStateChanged(HandleFlags flags) -{ - Qt::WindowState currentState = windowState(); - if (!(flags & HandleUnconditionally) && currentState == m_lastReportedWindowState) - return; - - qCDebug(lcQpaCocoaWindow) << "QCocoaWindow::handleWindowStateChanged" << - m_lastReportedWindowState << "-->" << currentState; - - QWindowSystemInterface::handleWindowStateChanged<QWindowSystemInterface::SynchronousDelivery>( - window(), currentState, m_lastReportedWindowState); - m_lastReportedWindowState = currentState; -} - // -------------------------------------------------------------------------- bool QCocoaWindow::windowIsPopupType(Qt::WindowType type) const @@ -1192,18 +1324,6 @@ bool QCocoaWindow::windowIsPopupType(Qt::WindowType type) const return ((type & Qt::Popup) == Qt::Popup); } -#ifndef QT_NO_OPENGL -void QCocoaWindow::setCurrentContext(QCocoaGLContext *context) -{ - m_glContext = context; -} - -QCocoaGLContext *QCocoaWindow::currentContext() const -{ - return m_glContext; -} -#endif - /*! Checks if the window is the content view of its immediate NSWindow. @@ -1230,6 +1350,7 @@ void QCocoaWindow::recreateWindowIfNeeded() QPlatformWindow *parentWindow = QPlatformWindow::parent(); + const bool isEmbeddedView = isEmbedded(); RecreationReasons recreateReason = RecreationNotNeeded; QCocoaWindow *oldParentCocoaWindow = nullptr; @@ -1246,7 +1367,7 @@ void QCocoaWindow::recreateWindowIfNeeded() if (m_windowModality != window()->modality()) recreateReason |= WindowModalityChanged; - const bool shouldBeContentView = !parentWindow && !(m_viewIsToBeEmbedded || m_viewIsEmbedded); + const bool shouldBeContentView = !parentWindow && !isEmbeddedView; if (isContentView() != shouldBeContentView) recreateReason |= ContentViewChanged; @@ -1258,7 +1379,7 @@ void QCocoaWindow::recreateWindowIfNeeded() if (isPanel != shouldBePanel) recreateReason |= PanelChanged; - qCDebug(lcQpaCocoaWindow) << "QCocoaWindow::recreateWindowIfNeeded" << window() << recreateReason; + qCDebug(lcQpaWindow) << "QCocoaWindow::recreateWindowIfNeeded" << window() << recreateReason; if (recreateReason == RecreationNotNeeded) return; @@ -1268,33 +1389,28 @@ void QCocoaWindow::recreateWindowIfNeeded() // Remove current window (if any) if ((isContentView() && !shouldBeContentView) || (recreateReason & PanelChanged)) { if (m_nsWindow) { - qCDebug(lcQpaCocoaWindow) << "Getting rid of existing window" << m_nsWindow; + qCDebug(lcQpaWindow) << "Getting rid of existing window" << m_nsWindow; if (m_nsWindow.observationInfo) { - qCCritical(lcQpaCocoaWindow) << m_nsWindow << "has active key-value observers (KVO)!" + qCCritical(lcQpaWindow) << m_nsWindow << "has active key-value observers (KVO)!" << "These will stop working now that the window is recreated, and will result in exceptions" << "when the observers are removed. Break in QCocoaWindow::recreateWindowIfNeeded to debug."; } [m_nsWindow closeAndRelease]; - if (isContentView()) { + if (isContentView() && !isEmbeddedView) { // We explicitly disassociate m_view from the window's contentView, // as AppKit does not automatically do this in response to removing // the view from the NSThemeFrame subview list, so we might end up // with a NSWindow contentView pointing to a deallocated NSView. m_view.window.contentView = nil; } - m_nsWindow = 0; + m_nsWindow = nil; } } - if (shouldBeContentView) { - bool noPreviousWindow = m_nsWindow == 0; - QCocoaNSWindow *newWindow = nullptr; - if (noPreviousWindow) - newWindow = createNSWindow(shouldBePanel); - + if (shouldBeContentView && !m_nsWindow) { // Move view to new NSWindow if needed - if (newWindow) { - qCDebug(lcQpaCocoaWindow) << "Ensuring that" << m_view << "is content view for" << newWindow; + if (auto *newWindow = createNSWindow(shouldBePanel)) { + qCDebug(lcQpaWindow) << "Ensuring that" << m_view << "is content view for" << newWindow; [m_view setPostsFrameChangedNotifications:NO]; [newWindow setContentView:m_view]; [m_view setPostsFrameChangedNotifications:YES]; @@ -1304,14 +1420,14 @@ void QCocoaWindow::recreateWindowIfNeeded() } } - if (m_viewIsToBeEmbedded) { + if (isEmbeddedView) { // An embedded window doesn't have its own NSWindow. } else if (!parentWindow) { // QPlatformWindow subclasses must sync up with QWindow on creation: propagateSizeHints(); setWindowFlags(window()->flags()); setWindowTitle(window()->title()); - setWindowFilePath(window()->filePath()); + setWindowFilePath(window()->filePath()); // Also sets window icon setWindowState(window()->windowState()); } else { // Child windows have no NSWindow, link the NSViews instead. @@ -1334,14 +1450,28 @@ void QCocoaWindow::recreateWindowIfNeeded() // top-level QWindows may have an attached NSToolBar, call // update function which will attach to the NSWindow. - if (!parentWindow) + if (!parentWindow && !isEmbeddedView) updateNSToolbar(); } void QCocoaWindow::requestUpdate() { - qCDebug(lcQpaCocoaDrawing) << "QCocoaWindow::requestUpdate" << window(); - QPlatformWindow::requestUpdate(); + const int swapInterval = format().swapInterval(); + qCDebug(lcQpaDrawing) << "QCocoaWindow::requestUpdate" << window() << "swapInterval" << swapInterval; + + if (swapInterval > 0) { + // Vsync is enabled, deliver via CVDisplayLink + static_cast<QCocoaScreen *>(screen())->requestUpdate(); + } else { + // Fall back to the un-throttled timer-based callback + QPlatformWindow::requestUpdate(); + } +} + +void QCocoaWindow::deliverUpdateRequest() +{ + qCDebug(lcQpaDrawing) << "Delivering update request to" << window(); + QPlatformWindow::deliverUpdateRequest(); } void QCocoaWindow::requestActivateWindow() @@ -1375,7 +1505,7 @@ QCocoaNSWindow *QCocoaWindow::createNSWindow(bool shouldBePanel) } if (!targetScreen) { - qCWarning(lcQpaCocoaWindow) << "Window position" << rect << "outside any known screen, using primary screen"; + qCWarning(lcQpaWindow) << "Window position" << rect << "outside any known screen, using primary screen"; targetScreen = QGuiApplication::primaryScreen(); // AppKit will only reposition a window that's outside the target screen area if // the window has a title bar. If left out, the window ends up with no screen. @@ -1399,8 +1529,10 @@ QCocoaNSWindow *QCocoaWindow::createNSWindow(bool shouldBePanel) Q_ASSERT_X(nsWindow.screen == cocoaScreen->nativeScreen(), "QCocoaWindow", "Resulting NSScreen should match the requested NSScreen"); - if (targetScreen != window()->screen()) - QWindowSystemInterface::handleWindowScreenChanged(window(), targetScreen); + if (targetScreen != window()->screen()) { + QWindowSystemInterface::handleWindowScreenChanged< + QWindowSystemInterface::SynchronousDelivery>(window(), targetScreen); + } nsWindow.delegate = [[QNSWindowDelegate alloc] initWithQCocoaWindow:this]; @@ -1473,138 +1605,6 @@ void QCocoaWindow::removeMonitor() monitor = nil; } -/*! - Applies the given state to the NSWindow, going in/out of minimize/zoomed/fullscreen - - When this is called from QWindow::setWindowState(), the QWindow state has not been - updated yet, so window()->windowState() will reflect the previous state that was - reported to QtGui. -*/ -void QCocoaWindow::applyWindowState(Qt::WindowStates requestedState) -{ - if (!isContentView()) - return; - - const Qt::WindowState currentState = windowState(); - const Qt::WindowState newState = QWindowPrivate::effectiveState(requestedState); - - if (newState == currentState) - return; - - const NSSize contentSize = m_view.frame.size; - if (contentSize.width <= 0 || contentSize.height <= 0) { - // If content view width or height is 0 then the window animations will crash so - // do nothing. We report the current state back to reflect the failed operation. - qWarning("invalid window content view size, check your window geometry"); - handleWindowStateChanged(HandleUnconditionally); - return; - } - - const NSWindow *nsWindow = m_view.window; - - if (nsWindow.styleMask & NSUtilityWindowMask - && newState & (Qt::WindowMinimized | Qt::WindowFullScreen)) { - qWarning() << window()->type() << "windows can not be made" << newState; - handleWindowStateChanged(HandleUnconditionally); - return; - } - - const id sender = nsWindow; - - // First we need to exit states that can't transition directly to other states - switch (currentState) { - case Qt::WindowMinimized: - [nsWindow deminiaturize:sender]; - Q_ASSERT_X(windowState() != Qt::WindowMinimized, "QCocoaWindow", - "[NSWindow deminiaturize:] is synchronous"); - break; - case Qt::WindowFullScreen: { - toggleFullScreen(); - // Exiting fullscreen is not synchronous, so we need to wait for the - // NSWindowDidExitFullScreenNotification before continuing to apply - // the new state. - return; - } - default:; - } - - // Then we apply the new state if needed - if (newState == windowState()) - return; - - switch (newState) { - case Qt::WindowFullScreen: - toggleFullScreen(); - break; - case Qt::WindowMaximized: - toggleMaximized(); - break; - case Qt::WindowMinimized: - [nsWindow miniaturize:sender]; - break; - case Qt::WindowNoState: - if (windowState() == Qt::WindowMaximized) - toggleMaximized(); - break; - default: - Q_UNREACHABLE(); - } -} - -void QCocoaWindow::toggleMaximized() -{ - const NSWindow *window = m_view.window; - - // The NSWindow needs to be resizable, otherwise the window will - // not be possible to zoom back to non-zoomed state. - const bool wasResizable = window.styleMask & NSResizableWindowMask; - window.styleMask |= NSResizableWindowMask; - - const id sender = window; - [window zoom:sender]; - - if (!wasResizable) - window.styleMask &= ~NSResizableWindowMask; -} - -void QCocoaWindow::toggleFullScreen() -{ - const NSWindow *window = m_view.window; - - // The window needs to have the correct collection behavior for the - // toggleFullScreen call to have an effect. The collection behavior - // will be reset in windowDidEnterFullScreen/windowDidLeaveFullScreen. - window.collectionBehavior |= NSWindowCollectionBehaviorFullScreenPrimary; - - const id sender = window; - [window toggleFullScreen:sender]; -} - -bool QCocoaWindow::isTransitioningToFullScreen() const -{ - NSWindow *window = m_view.window; - return window.styleMask & NSFullScreenWindowMask && !window.qt_fullScreen; -} - -Qt::WindowState QCocoaWindow::windowState() const -{ - // FIXME: Support compound states (Qt::WindowStates) - - 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; - - // 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; -} - bool QCocoaWindow::setWindowModified(bool modified) { if (!isContentView()) |