diff options
Diffstat (limited to 'src/plugins/platforms/cocoa/qcocoawindow.mm')
-rw-r--r-- | src/plugins/platforms/cocoa/qcocoawindow.mm | 959 |
1 files changed, 641 insertions, 318 deletions
diff --git a/src/plugins/platforms/cocoa/qcocoawindow.mm b/src/plugins/platforms/cocoa/qcocoawindow.mm index 40666772c5..75c13d6bbc 100644 --- a/src/plugins/platforms/cocoa/qcocoawindow.mm +++ b/src/plugins/platforms/cocoa/qcocoawindow.mm @@ -87,6 +87,36 @@ static void qt_closePopups() } } +@interface NSWindow (FullScreenProperty) +@property(readonly) BOOL qt_fullScreen; +@end + +@implementation NSWindow (FullScreenProperty) + ++ (void)load +{ + NSNotificationCenter *center = [NSNotificationCenter defaultCenter]; + [center addObserverForName:NSWindowDidEnterFullScreenNotification object:nil queue:nil + usingBlock:^(NSNotification *notification) { + objc_setAssociatedObject(notification.object, @selector(qt_fullScreen), + [NSNumber numberWithBool:YES], OBJC_ASSOCIATION_RETAIN); + } + ]; + [center addObserverForName:NSWindowDidExitFullScreenNotification object:nil queue:nil + usingBlock:^(NSNotification *notification) { + objc_setAssociatedObject(notification.object, @selector(qt_fullScreen), + nil, OBJC_ASSOCIATION_RETAIN); + } + ]; +} + +- (BOOL)qt_fullScreen +{ + NSNumber *number = objc_getAssociatedObject(self, @selector(qt_fullScreen)); + return [number boolValue]; +} +@end + @implementation QNSWindowHelper @synthesize window = _window; @@ -130,8 +160,7 @@ static void qt_closePopups() [forwardView mouseDragged:theEvent]; } } - - if (!pw->m_isNSWindowChild && theEvent.type == NSLeftMouseDown) { + if (pw->window()->isTopLevel() && theEvent.type == NSLeftMouseDown) { pw->m_forwardWindow.clear(); } } @@ -202,13 +231,14 @@ static void qt_closePopups() @synthesize helper = _helper; - (id)initWithContentRect:(NSRect)contentRect + screen:(NSScreen*)screen styleMask:(NSUInteger)windowStyle qPlatformWindow:(QCocoaWindow *)qpw { self = [super initWithContentRect:contentRect styleMask:windowStyle backing:NSBackingStoreBuffered - defer:NO]; // Deferring window creation breaks OpenGL (the GL context is + defer:NO screen:screen]; // Deferring window creation breaks OpenGL (the GL context is // set up before the window is shown and needs a proper window) if (self) { @@ -222,7 +252,7 @@ static void qt_closePopups() // Prevent child NSWindows from becoming the key window in // order keep the active apperance of the top-level window. QCocoaWindow *pw = self.helper.platformWindow; - if (!pw || pw->m_isNSWindowChild) + if (!pw || !pw->window()->isTopLevel()) return NO; if (pw->shouldRefuseKeyWindowAndFirstResponder()) @@ -241,7 +271,7 @@ static void qt_closePopups() // Windows with a transient parent (such as combobox popup windows) // cannot become the main window: QCocoaWindow *pw = self.helper.platformWindow; - if (!pw || pw->m_isNSWindowChild || pw->window()->transientParent()) + if (!pw || !pw->window()->isTopLevel() || pw->window()->transientParent()) canBecomeMain = NO; return canBecomeMain; @@ -284,13 +314,14 @@ static void qt_closePopups() @synthesize helper = _helper; - (id)initWithContentRect:(NSRect)contentRect + screen:(NSScreen*)screen styleMask:(NSUInteger)windowStyle qPlatformWindow:(QCocoaWindow *)qpw { self = [super initWithContentRect:contentRect styleMask:windowStyle backing:NSBackingStoreBuffered - defer:NO]; // Deferring window creation breaks OpenGL (the GL context is + defer:NO screen:screen]; // Deferring window creation breaks OpenGL (the GL context is // set up before the window is shown and needs a proper window) if (self) { @@ -343,18 +374,72 @@ static void qt_closePopups() @end +static void qRegisterNotificationCallbacks() +{ + static const QLatin1String notificationHandlerPrefix(Q_NOTIFICATION_PREFIX); + + NSNotificationCenter *center = [NSNotificationCenter defaultCenter]; + + const QMetaObject *metaObject = QMetaType::metaObjectForType(qRegisterMetaType<QCocoaWindow*>()); + Q_ASSERT(metaObject); + + for (int i = 0; i < metaObject->methodCount(); ++i) { + QMetaMethod method = metaObject->method(i); + const QString methodTag = QString::fromLatin1(method.tag()); + if (!methodTag.startsWith(notificationHandlerPrefix)) + continue; + + const QString notificationName = methodTag.mid(notificationHandlerPrefix.size()); + [center addObserverForName:notificationName.toNSString() object:nil queue:nil + usingBlock:^(NSNotification *notification) { + + NSView *view = nullptr; + if ([notification.object isKindOfClass:[NSWindow class]]) { + NSWindow *window = notification.object; + // Only top level NSWindows should notify their QNSViews + if (window.parentWindow) + return; + + if (!window.contentView) + return; + + view = window.contentView; + } else if ([notification.object isKindOfClass:[NSView class]]) { + view = notification.object; + } else { + qCWarning(lcQpaCocoaWindow) << "Unhandled notifcation" + << notification.name << "for" << notification.object; + return; + } + Q_ASSERT(view); + + QCocoaWindow *cocoaWindow = nullptr; + if (QNSView *qnsView = qnsview_cast(view)) + cocoaWindow = qnsView.platformWindow; + + // FIXME: Could be a foreign window, look up by iterating top level QWindows + + if (!cocoaWindow) + return; + + if (!method.invoke(cocoaWindow, Qt::DirectConnection)) { + qCWarning(lcQpaCocoaWindow) << "Failed to invoke NSNotification callback for" + << notification.name << "on" << cocoaWindow; + } + }]; + } +} +Q_CONSTRUCTOR_FUNCTION(qRegisterNotificationCallbacks) + const int QCocoaWindow::NoAlertRequest = -1; -QCocoaWindow::QCocoaWindow(QWindow *tlw) +QCocoaWindow::QCocoaWindow(QWindow *tlw, WId nativeHandle) : QPlatformWindow(tlw) , m_view(nil) , m_nsWindow(0) , m_viewIsEmbedded(false) , m_viewIsToBeEmbedded(false) - , m_parentCocoaWindow(0) - , m_isNSWindowChild(false) - , m_effectivelyMaximized(false) - , m_synchedWindowState(Qt::WindowActive) + , m_lastReportedWindowState(Qt::WindowNoState) , m_windowModality(Qt::NonModal) , m_windowUnderMouse(false) , m_inConstructor(true) @@ -379,15 +464,15 @@ QCocoaWindow::QCocoaWindow(QWindow *tlw) , m_drawContentBorderGradient(false) , m_topContentBorderThickness(0) , m_bottomContentBorderThickness(0) - , m_normalGeometry(QRect(0,0,-1,-1)) , m_hasWindowFilePath(false) { qCDebug(lcQpaCocoaWindow) << "QCocoaWindow::QCocoaWindow" << window(); QMacAutoReleasePool pool; - if (tlw->type() == Qt::ForeignWindow) { - m_view = (NSView *)WId(tlw->property("_q_foreignWinId").value<WId>()); + if (nativeHandle) { + m_view = reinterpret_cast<NSView *>(nativeHandle); + [m_view retain]; } else { m_view = [[QNSView alloc] initWithCocoaWindow:this]; // Enable high-dpi OpenGL for retina displays. Enabling has the side @@ -405,7 +490,7 @@ QCocoaWindow::QCocoaWindow(QWindow *tlw) [m_view setWantsLayer:enable]; } setGeometry(tlw->geometry()); - recreateWindow(QPlatformWindow::parent()); + recreateWindowIfNeeded(); tlw->setGeometry(geometry()); if (tlw->isTopLevel()) setWindowIcon(tlw->icon()); @@ -420,18 +505,16 @@ QCocoaWindow::~QCocoaWindow() [m_nsWindow makeFirstResponder:nil]; [m_nsWindow setContentView:nil]; [m_nsWindow.helper detachFromPlatformWindow]; - if (m_isNSWindowChild) { - if (m_parentCocoaWindow) - m_parentCocoaWindow->removeChildWindow(this); - } else if ([m_view superview]) { + if (m_view.window.parentWindow) + [m_view.window.parentWindow removeChildWindow:m_view.window]; + else if ([m_view superview]) [m_view removeFromSuperview]; - } removeMonitor(); // Make sure to disconnect observer in all case if view is valid // to avoid notifications received when deleting when using Qt::AA_NativeWindows attribute - if (window()->type() != Qt::ForeignWindow) + if (!isForeignWindow()) [[NSNotificationCenter defaultCenter] removeObserver:m_view]; // While it is unlikely that this window will be in the popup stack @@ -440,10 +523,9 @@ QCocoaWindow::~QCocoaWindow() QCocoaIntegration::instance()->popupWindowStack()->removeAll(this); } - foreach (QCocoaWindow *child, m_childWindows) { - [m_nsWindow removeChildWindow:child->m_nsWindow]; - child->m_parentCocoaWindow = 0; - } + foreachChildNSWindow(^(QCocoaWindow *childWindow) { + [m_nsWindow removeChildWindow:childWindow->m_nsWindow]; + }); [m_view release]; [m_nsWindow release]; @@ -481,6 +563,11 @@ void QCocoaWindow::setGeometry(const QRect &rectIn) setCocoaGeometry(rect); } +bool QCocoaWindow::isForeignWindow() const +{ + return ![m_view isKindOfClass:[QNSView class]]; +} + QRect QCocoaWindow::geometry() const { // QWindows that are embedded in a NSView hiearchy may be considered @@ -491,7 +578,7 @@ QRect QCocoaWindow::geometry() const NSRect screenRect = [[m_view window] convertRectToScreen:NSMakeRect(windowPoint.x, windowPoint.y, 1, 1)]; NSPoint screenPoint = screenRect.origin; QPoint position = qt_mac_flipPoint(screenPoint).toPoint(); - QSize size = QRectF::fromCGRect([m_view bounds]).toRect().size(); + QSize size = QRectF::fromCGRect(NSRectToCGRect([m_view bounds])).toRect().size(); return QRect(position, size); } @@ -504,7 +591,7 @@ void QCocoaWindow::setCocoaGeometry(const QRect &rect) QMacAutoReleasePool pool; if (m_viewIsEmbedded) { - if (window()->type() != Qt::ForeignWindow) { + if (!isForeignWindow()) { [m_view setFrame:NSMakeRect(0, 0, rect.width(), rect.height())]; } else { QPlatformWindow::setGeometry(rect); @@ -512,9 +599,9 @@ void QCocoaWindow::setCocoaGeometry(const QRect &rect) return; } - if (m_isNSWindowChild) { + if (isChildNSWindow()) { QPlatformWindow::setGeometry(rect); - NSWindow *parentNSWindow = m_parentCocoaWindow->m_nsWindow; + NSWindow *parentNSWindow = m_view.window.parentWindow; NSRect parentWindowFrame = [parentNSWindow contentRectForFrameRect:parentNSWindow.frame]; clipWindow(parentWindowFrame); @@ -528,7 +615,7 @@ void QCocoaWindow::setCocoaGeometry(const QRect &rect) [m_view setFrame:NSMakeRect(rect.x(), rect.y(), rect.width(), rect.height())]; } - if (window()->type() == Qt::ForeignWindow) + if (isForeignWindow()) QPlatformWindow::setGeometry(rect); // will call QPlatformWindow::setGeometry(rect) during resize confirmation (see qnsview.mm) @@ -536,14 +623,14 @@ void QCocoaWindow::setCocoaGeometry(const QRect &rect) void QCocoaWindow::clipChildWindows() { - foreach (QCocoaWindow *childWindow, m_childWindows) { + foreachChildNSWindow(^(QCocoaWindow *childWindow) { childWindow->clipWindow(m_nsWindow.frame); - } + }); } void QCocoaWindow::clipWindow(const NSRect &clipRect) { - if (!m_isNSWindowChild) + if (!isChildNSWindow()) return; NSRect clippedWindowRect = NSZeroRect; @@ -568,15 +655,15 @@ void QCocoaWindow::clipWindow(const NSRect &clipRect) m_hiddenByClipping = false; if (!m_hiddenByAncestor) { [m_nsWindow orderFront:nil]; - m_parentCocoaWindow->reinsertChildWindow(this); + static_cast<QCocoaWindow *>(QPlatformWindow::parent())->reinsertChildWindow(this); } } } // recurse - foreach (QCocoaWindow *childWindow, m_childWindows) { + foreachChildNSWindow(^(QCocoaWindow *childWindow) { childWindow->clipWindow(clippedWindowRect); - } + }); } void QCocoaWindow::hide(bool becauseOfAncestor) @@ -593,8 +680,9 @@ void QCocoaWindow::hide(bool becauseOfAncestor) if (!visible) // Could have been clipped before return; - foreach (QCocoaWindow *childWindow, m_childWindows) + foreachChildNSWindow(^(QCocoaWindow *childWindow) { childWindow->hide(true); + }); [m_nsWindow orderOut:nil]; } @@ -604,20 +692,21 @@ void QCocoaWindow::show(bool becauseOfAncestor) if ([m_nsWindow isVisible]) return; - if (m_parentCocoaWindow && ![m_parentCocoaWindow->m_nsWindow isVisible]) { + if (m_view.window.parentWindow && !m_view.window.parentWindow.visible) { m_hiddenByAncestor = true; // Parent still hidden, don't show now } else if ((becauseOfAncestor == m_hiddenByAncestor) // Was NEITHER explicitly hidden && !m_hiddenByClipping) { // ... NOR clipped - if (m_isNSWindowChild) { + if (isChildNSWindow()) { m_hiddenByAncestor = false; setCocoaGeometry(windowGeometry()); } if (!m_hiddenByClipping) { // setCocoaGeometry() can change the clipping status [m_nsWindow orderFront:nil]; - if (m_isNSWindowChild) - m_parentCocoaWindow->reinsertChildWindow(this); - foreach (QCocoaWindow *childWindow, m_childWindows) + if (isChildNSWindow()) + static_cast<QCocoaWindow *>(QPlatformWindow::parent())->reinsertChildWindow(this); + foreachChildNSWindow(^(QCocoaWindow *childWindow) { childWindow->show(true); + }); } } } @@ -626,7 +715,7 @@ void QCocoaWindow::setVisible(bool visible) { qCDebug(lcQpaCocoaWindow) << "QCocoaWindow::setVisible" << window() << visible; - if (m_isNSWindowChild && m_hiddenByClipping) + if (isChildNSWindow() && m_hiddenByClipping) return; m_inSetVisible = true; @@ -638,8 +727,7 @@ void QCocoaWindow::setVisible(bool visible) if (visible) { // We need to recreate if the modality has changed as the style mask will need updating - if (m_windowModality != window()->modality() || isNativeWindowTypeInconsistent()) - recreateWindow(QPlatformWindow::parent()); + recreateWindowIfNeeded(); // Register popup windows. The Cocoa platform plugin will forward mouse events // to them and close them when needed. @@ -674,14 +762,14 @@ void QCocoaWindow::setVisible(bool visible) // setWindowState might have been called while the window was hidden and // will not change the NSWindow state in that case. Sync up here: - syncWindowState(window()->windowState()); + applyWindowState(window()->windowState()); if (window()->windowState() != Qt::WindowMinimized) { if ((window()->modality() == Qt::WindowModal || window()->type() == Qt::Sheet) && parentCocoaWindow) { // show the window as a sheet - [NSApp beginSheet:m_nsWindow modalForWindow:parentCocoaWindow->m_nsWindow modalDelegate:nil didEndSelector:nil contextInfo:nil]; + [parentCocoaWindow->m_nsWindow beginSheet:m_nsWindow completionHandler:nil]; } else if (window()->modality() != Qt::NonModal) { // show the window as application modal QCocoaEventDispatcher *cocoaEventDispatcher = qobject_cast<QCocoaEventDispatcher *>(QGuiApplication::instance()->eventDispatcher()); @@ -700,8 +788,9 @@ void QCocoaWindow::setVisible(bool visible) else [m_nsWindow orderFront:nil]; - foreach (QCocoaWindow *childWindow, m_childWindows) + foreachChildNSWindow(^(QCocoaWindow *childWindow) { childWindow->show(true); + }); } else { show(); } @@ -742,8 +831,10 @@ void QCocoaWindow::setVisible(bool visible) cocoaEventDispatcherPrivate->endModalSession(window()); m_hasModalSession = false; } else { - if ([m_nsWindow isSheet]) - [NSApp endSheet:m_nsWindow]; + if ([m_nsWindow isSheet]) { + Q_ASSERT_X(parentCocoaWindow, "QCocoaWindow", "Window modal dialog has no transient parent."); + [parentCocoaWindow->m_nsWindow endSheet:m_nsWindow]; + } } hide(); @@ -856,6 +947,10 @@ NSUInteger QCocoaWindow::windowStyleMask(Qt::WindowFlags flags) if (m_drawContentBorderGradient) styleMask |= NSTexturedBackgroundWindowMask; + // Don't wipe fullscreen state + if (m_nsWindow.styleMask & NSFullScreenWindowMask) + styleMask |= NSFullScreenWindowMask; + return styleMask; } @@ -873,13 +968,14 @@ void QCocoaWindow::setWindowZoomButton(Qt::WindowFlags flags) // in line with the platform style guidelines. bool fixedSizeNoZoom = (windowMinimumSize().isValid() && windowMaximumSize().isValid() && windowMinimumSize() == windowMaximumSize()); - bool customizeNoZoom = ((flags & Qt::CustomizeWindowHint) && !(flags & Qt::WindowMaximizeButtonHint)); + bool customizeNoZoom = ((flags & Qt::CustomizeWindowHint) + && !(flags & (Qt::WindowMaximizeButtonHint | Qt::WindowFullscreenButtonHint))); [[m_nsWindow standardWindowButton:NSWindowZoomButton] setEnabled:!(fixedSizeNoZoom || customizeNoZoom)]; } void QCocoaWindow::setWindowFlags(Qt::WindowFlags flags) { - if (m_nsWindow && !m_isNSWindowChild) { + if (m_nsWindow && !isChildNSWindow()) { NSUInteger styleMask = windowStyleMask(flags); NSInteger level = this->windowLevel(flags); // While setting style mask we can have -updateGeometry calls on a content @@ -908,13 +1004,16 @@ void QCocoaWindow::setWindowFlags(Qt::WindowFlags flags) setWindowZoomButton(flags); } + if (m_nsWindow) + m_nsWindow.ignoresMouseEvents = flags & Qt::WindowTransparentForInput; + m_windowFlags = flags; } void QCocoaWindow::setWindowState(Qt::WindowState state) { if (window()->isVisible()) - syncWindowState(state); // Window state set for hidden windows take effect when show() is called. + applyWindowState(state); // Window state set for hidden windows take effect when show() is called } void QCocoaWindow::setWindowTitle(const QString &title) @@ -983,19 +1082,16 @@ void QCocoaWindow::raise() // ### handle spaces (see Qt 4 raise_sys in qwidget_mac.mm) if (!m_nsWindow) return; - if (m_isNSWindowChild) { - QList<QCocoaWindow *> &siblings = m_parentCocoaWindow->m_childWindows; - siblings.removeOne(this); - siblings.append(this); + if (isChildNSWindow()) { if (m_hiddenByClipping) return; } if ([m_nsWindow isVisible]) { - if (m_isNSWindowChild) { + if (isChildNSWindow()) { // -[NSWindow orderFront:] doesn't work with attached windows. // The only solution is to remove and add the child window. // This will place it on top of all the other NSWindows. - NSWindow *parentNSWindow = m_parentCocoaWindow->m_nsWindow; + NSWindow *parentNSWindow = m_view.window.parentWindow; [parentNSWindow removeChildWindow:m_nsWindow]; [parentNSWindow addChildWindow:m_nsWindow ordered:NSWindowAbove]; } else { @@ -1021,20 +1117,17 @@ void QCocoaWindow::lower() qCDebug(lcQpaCocoaWindow) << "QCocoaWindow::lower" << window(); if (!m_nsWindow) return; - if (m_isNSWindowChild) { - QList<QCocoaWindow *> &siblings = m_parentCocoaWindow->m_childWindows; - siblings.removeOne(this); - siblings.prepend(this); + if (isChildNSWindow()) { if (m_hiddenByClipping) return; } if ([m_nsWindow isVisible]) { - if (m_isNSWindowChild) { + if (isChildNSWindow()) { // -[NSWindow orderBack:] doesn't work with attached windows. // The only solution is to remove and add all the child windows except this one. // This will keep the current window at the bottom while adding the others on top of it, // hopefully in the same order (this is not documented anywhere in the Cocoa documentation). - NSWindow *parentNSWindow = m_parentCocoaWindow->m_nsWindow; + NSWindow *parentNSWindow = m_view.window.parentWindow; NSArray *children = [parentNSWindow.childWindows copy]; for (NSWindow *child in children) if (m_nsWindow != child) { @@ -1095,7 +1188,7 @@ void QCocoaWindow::propagateSizeHints() QSize sizeIncrement = windowSizeIncrement(); if (sizeIncrement.isEmpty()) sizeIncrement = QSize(1, 1); - [m_nsWindow setResizeIncrements:sizeIncrement.toCGSize()]; + [m_nsWindow setResizeIncrements:NSSizeFromCGSize(sizeIncrement.toCGSize())]; QRect rect = geometry(); QSize baseSize = windowBaseSize(); @@ -1158,7 +1251,7 @@ void QCocoaWindow::setParent(const QPlatformWindow *parentWindow) // recreate the window for compatibility bool unhideAfterRecreate = parentWindow && !m_viewIsToBeEmbedded && ![m_view isHidden]; - recreateWindow(parentWindow); + recreateWindowIfNeeded(); if (unhideAfterRecreate) [m_view setHidden:NO]; setCocoaGeometry(geometry()); @@ -1182,6 +1275,8 @@ void QCocoaWindow::setEmbeddedInForeignView(bool embedded) m_nsWindow = 0; } +// ----------------------- NSWindow notifications ----------------------- + void QCocoaWindow::windowWillMove() { // Close any open popups on window move @@ -1190,10 +1285,13 @@ void QCocoaWindow::windowWillMove() void QCocoaWindow::windowDidMove() { - if (m_isNSWindowChild) + if (isChildNSWindow()) return; [qnsview_cast(m_view) updateGeometry]; + + // Moving a window might bring it out of maximized state + reportCurrentWindowState(); } void QCocoaWindow::windowDidResize() @@ -1201,21 +1299,172 @@ void QCocoaWindow::windowDidResize() if (!m_nsWindow) return; - if (m_isNSWindowChild) + if (isChildNSWindow()) return; clipChildWindows(); [qnsview_cast(m_view) updateGeometry]; + + if (!m_view.inLiveResize) + reportCurrentWindowState(); +} + +void QCocoaWindow::viewDidChangeFrame() +{ + [qnsview_cast(m_view) updateGeometry]; +} + +/*! + Callback for NSViewGlobalFrameDidChangeNotification. + + Posted whenever an NSView object that has attached surfaces (that is, + NSOpenGLContext objects) moves to a different screen, or other cases + where the NSOpenGLContext object needs to be updated. +*/ +void QCocoaWindow::viewDidChangeGlobalFrame() +{ + updateExposedGeometry(); } void QCocoaWindow::windowDidEndLiveResize() { - if (m_synchedWindowState == Qt::WindowMaximized && ![m_nsWindow isZoomed]) { - m_effectivelyMaximized = false; - [qnsview_cast(m_view) notifyWindowStateChanged:Qt::WindowNoState]; + reportCurrentWindowState(); +} + +void QCocoaWindow::windowDidBecomeKey() +{ + if (isForeignWindow()) + return; + + if (m_windowUnderMouse) { + QPointF windowPoint; + QPointF screenPoint; + [qnsview_cast(m_view) convertFromScreen:[NSEvent mouseLocation] toWindowPoint:&windowPoint andScreenPoint:&screenPoint]; + QWindowSystemInterface::handleEnterEvent(m_enterLeaveTargetWindow, windowPoint, screenPoint); } + + if (!windowIsPopupType() && !qnsview_cast(m_view).isMenuView) + QWindowSystemInterface::handleWindowActivated(window()); } +void QCocoaWindow::windowDidResignKey() +{ + if (isForeignWindow()) + return; + + // Key window will be non-nil if another window became key, so do not + // set the active window to zero here -- the new key window's + // NSWindowDidBecomeKeyNotification hander will change the active window. + 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() +{ + reportCurrentWindowState(); +} + +void QCocoaWindow::windowDidDeminiaturize() +{ + reportCurrentWindowState(); +} + +void QCocoaWindow::windowWillEnterFullScreen() +{ + // 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_nsWindow.styleMask |= NSResizableWindowMask; +} + +void QCocoaWindow::windowDidEnterFullScreen() +{ + Q_ASSERT_X(m_nsWindow.qt_fullScreen, "QCocoaWindow", + "FullScreen category processes window notifications first"); + + // Reset to original styleMask + setWindowFlags(m_windowFlags); + + reportCurrentWindowState(); +} + +void QCocoaWindow::windowWillExitFullScreen() +{ + // The NSWindow needs to be resizable, otherwise we'll end up with + // a weird zoom animation. The styleMask will be reset below. + m_nsWindow.styleMask |= NSResizableWindowMask; +} + +void QCocoaWindow::windowDidExitFullScreen() +{ + Q_ASSERT_X(!m_nsWindow.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 + reportCurrentWindowState(); + + 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::windowDidOrderOffScreen() +{ + obscureWindow(); +} + +void QCocoaWindow::windowDidOrderOnScreen() +{ + exposeWindow(); +} + +void QCocoaWindow::windowDidChangeOcclusionState() +{ + // Several unit tests expect paint and/or expose events for windows that are + // sometimes (unpredictably) occluded and some unit tests depend on QWindow::isExposed. + // Don't send Expose/Obscure events when running under QTestLib. + static const bool onTestLib = qt_mac_resolveOption(false, "QT_QTESTLIB_RUNNING"); + if (!onTestLib) { + if ((NSUInteger)[m_view.window occlusionState] & NSWindowOcclusionStateVisible) { + exposeWindow(); + } else { + // Send Obscure events on window occlusion to stop animations. + obscureWindow(); + } + } +} + +void QCocoaWindow::windowDidChangeScreen() +{ + if (!window()) + return; + + if (QCocoaScreen *cocoaScreen = QCocoaIntegration::instance()->screenForNSScreen(m_view.window.screen)) + QWindowSystemInterface::handleWindowScreenChanged(window(), cocoaScreen->screen()); + + updateExposedGeometry(); +} + +void QCocoaWindow::windowWillClose() +{ + // Close any open popups on window closing. + if (window() && !windowIsPopupType(window()->type())) + qt_closePopups(); +} + +// ----------------------- NSWindowDelegate callbacks ----------------------- + bool QCocoaWindow::windowShouldClose() { qCDebug(lcQpaCocoaWindow) << "QCocoaWindow::windowShouldClose" << window(); @@ -1229,18 +1478,7 @@ bool QCocoaWindow::windowShouldClose() return accepted; } -void QCocoaWindow::windowWillClose() -{ - // Close any open popups on window closing. - if (window() && !windowIsPopupType(window()->type())) - qt_closePopups(); -} - -void QCocoaWindow::setSynchedWindowStateFromWindow() -{ - if (QWindow *w = window()) - m_synchedWindowState = w->windowState(); -} +// -------------------------------------------------------------------------- bool QCocoaWindow::windowIsPopupType(Qt::WindowType type) const { @@ -1264,62 +1502,141 @@ QCocoaGLContext *QCocoaWindow::currentContext() const } #endif -void QCocoaWindow::recreateWindow(const QPlatformWindow *parentWindow) +/*! + Checks if the window is a non-top level QWindow with a NSWindow. + + \sa _q_platform_MacUseNSWindow, QT_MAC_USE_NSWINDOW +*/ +bool QCocoaWindow::isChildNSWindow() const +{ + return m_view.window.parentWindow != nil; +} + +/*! + Checks if the window is the content view of its immediate NSWindow. + + Being the content view of a NSWindow means the QWindow is + the highest accessible NSView object in the window's view + hierarchy. + + This can only happen in two cases, either if the QWindow is + itself a top level window, or if it's a child NSWindow. + + \sa isChildNSWindow +*/ +bool QCocoaWindow::isContentView() const +{ + return m_view.window.contentView == m_view; +} + +/*! + Iterates child NSWindows that have a corresponding QCocoaWindow. +*/ +void QCocoaWindow::foreachChildNSWindow(void (^block)(QCocoaWindow *)) { - qCDebug(lcQpaCocoaWindow) << "QCocoaWindow::recreateWindow" << window() + NSArray *windows = m_view.window.childWindows; + [windows enumerateObjectsUsingBlock:^(NSWindow *window, NSUInteger index, BOOL *stop) { + Q_UNUSED(index); + Q_UNUSED(stop); + if (QNSView *view = qnsview_cast(window.contentView)) + block(view.platformWindow); + }]; +} + +/*! + Recreates (or removes) the NSWindow for this QWindow, if needed. + + A QWindow may need a corresponding NSWindow, depending on whether + or not it's a top level or not (or explicitly set to be a child + NSWindow), whether it is a NSPanel or not, etc. +*/ +void QCocoaWindow::recreateWindowIfNeeded() +{ + QPlatformWindow *parentWindow = QPlatformWindow::parent(); + qCDebug(lcQpaCocoaWindow) << "QCocoaWindow::recreateWindowIfNeeded" << window() << "parent" << (parentWindow ? parentWindow->window() : 0); - bool wasNSWindowChild = m_isNSWindowChild; - BOOL requestNSWindowChild = qt_mac_resolveOption(NO, window(), "_q_platform_MacUseNSWindow", - "QT_MAC_USE_NSWINDOW"); - m_isNSWindowChild = parentWindow && requestNSWindowChild; - bool needsNSWindow = m_isNSWindowChild || !parentWindow; + RecreationReasons recreateReason = RecreationNotNeeded; + + QCocoaWindow *oldParentCocoaWindow = nullptr; + if (QNSView *qnsView = qnsview_cast(m_view.superview)) + oldParentCocoaWindow = qnsView.platformWindow; + + if (parentWindow != oldParentCocoaWindow) + recreateReason |= ParentChanged; + + if (!m_view.window) + recreateReason |= MissingWindow; + + // If the modality has changed the style mask will need updating + if (m_windowModality != window()->modality()) + recreateReason |= WindowModalityChanged; + + const bool shouldBeChildNSWindow = parentWindow && qt_mac_resolveOption(NO, + window(), "_q_platform_MacUseNSWindow", "QT_MAC_USE_NSWINDOW"); + + if (isChildNSWindow() != shouldBeChildNSWindow) + recreateReason |= ChildNSWindowChanged; + + const bool shouldBeContentView = !parentWindow || shouldBeChildNSWindow; + if (isContentView() != shouldBeContentView) + recreateReason |= ContentViewChanged; + + Qt::WindowType type = window()->type(); + const bool isPanel = isContentView() && [m_view.window isKindOfClass:[QNSPanel class]]; + const bool shouldBePanel = shouldBeContentView && !shouldBeChildNSWindow && + ((type & Qt::Popup) == Qt::Popup || (type & Qt::Dialog) == Qt::Dialog); + + if (isPanel != shouldBePanel) + recreateReason |= PanelChanged; + + if (recreateReason == RecreationNotNeeded) { + qCDebug(lcQpaCocoaWindow) << "No need to recreate NSWindow"; + return; + } + + qCDebug(lcQpaCocoaWindow) << "Recreating NSWindow due to" << recreateReason; + + QCocoaWindow *parentCocoaWindow = static_cast<QCocoaWindow *>(parentWindow); - QCocoaWindow *oldParentCocoaWindow = m_parentCocoaWindow; - m_parentCocoaWindow = const_cast<QCocoaWindow *>(static_cast<const QCocoaWindow *>(parentWindow)); - if (m_parentCocoaWindow && m_isNSWindowChild) { - QWindow *parentQWindow = m_parentCocoaWindow->window(); + if (shouldBeChildNSWindow) { + QWindow *parentQWindow = parentWindow->window(); + // Ensure that all parents in the hierarchy are also child NSWindows if (!parentQWindow->property("_q_platform_MacUseNSWindow").toBool()) { parentQWindow->setProperty("_q_platform_MacUseNSWindow", QVariant(true)); - m_parentCocoaWindow->recreateWindow(m_parentCocoaWindow->m_parentCocoaWindow); + parentCocoaWindow->recreateWindowIfNeeded(); } } - bool usesNSPanel = [m_nsWindow isKindOfClass:[QNSPanel class]]; - - // No child QNSWindow should notify its QNSView - if (m_nsWindow && (window()->type() != Qt::ForeignWindow) && m_parentCocoaWindow && !oldParentCocoaWindow) - [[NSNotificationCenter defaultCenter] removeObserver:m_view - name:nil object:m_nsWindow]; - // Remove current window (if any) - if ((m_nsWindow && !needsNSWindow) || (usesNSPanel != shouldUseNSPanel())) { + if ((isContentView() && !shouldBeContentView) || (recreateReason & PanelChanged)) { [m_nsWindow closeAndRelease]; - if (wasNSWindowChild && oldParentCocoaWindow) - oldParentCocoaWindow->removeChildWindow(this); + if (isChildNSWindow()) + [m_view.window.parentWindow removeChildWindow:m_view.window]; m_nsWindow = 0; } - if (needsNSWindow) { + if (shouldBeContentView) { bool noPreviousWindow = m_nsWindow == 0; if (noPreviousWindow) - m_nsWindow = createNSWindow(); - - // Only non-child QNSWindows should notify their QNSViews - // (but don't register more than once). - if ((window()->type() != Qt::ForeignWindow) && (noPreviousWindow || (wasNSWindowChild && !m_isNSWindowChild))) - [[NSNotificationCenter defaultCenter] addObserver:m_view - selector:@selector(windowNotification:) - name:nil // Get all notifications - object:m_nsWindow]; - - if (oldParentCocoaWindow) { - if (!m_isNSWindowChild || oldParentCocoaWindow != m_parentCocoaWindow) - oldParentCocoaWindow->removeChildWindow(this); + m_nsWindow = createNSWindow(shouldBeChildNSWindow, shouldBePanel); + + if (m_view.window.parentWindow) { + if (!shouldBeChildNSWindow || (recreateReason & ParentChanged)) + [m_view.window.parentWindow removeChildWindow:m_view.window]; m_forwardWindow = oldParentCocoaWindow; } - setNSWindow(m_nsWindow); + // Move view to new NSWindow if needed + if (m_nsWindow.contentView != m_view) { + [m_view setPostsFrameChangedNotifications:NO]; + [m_view retain]; + if (m_view.superview) // m_view comes from another NSWindow + [m_view removeFromSuperview]; + [m_nsWindow setContentView:m_view]; + [m_view release]; + [m_view setPostsFrameChangedNotifications:YES]; + } } if (m_viewIsToBeEmbedded) { @@ -1330,32 +1647,21 @@ void QCocoaWindow::recreateWindow(const QPlatformWindow *parentWindow) setWindowFlags(window()->flags()); setWindowTitle(window()->title()); setWindowState(window()->windowState()); - } else if (m_isNSWindowChild) { - m_nsWindow.styleMask = NSBorderlessWindowMask; - m_nsWindow.hasShadow = NO; - m_nsWindow.level = NSNormalWindowLevel; - NSWindowCollectionBehavior collectionBehavior = - NSWindowCollectionBehaviorManaged | NSWindowCollectionBehaviorIgnoresCycle - | NSWindowCollectionBehaviorFullScreenAuxiliary; - m_nsWindow.animationBehavior = NSWindowAnimationBehaviorNone; - m_nsWindow.collectionBehavior = collectionBehavior; - setCocoaGeometry(windowGeometry()); - - QList<QCocoaWindow *> &siblings = m_parentCocoaWindow->m_childWindows; - if (siblings.contains(this)) { - if (!m_hiddenByClipping) - m_parentCocoaWindow->reinsertChildWindow(this); - } else { - if (!m_hiddenByClipping) - [m_parentCocoaWindow->m_nsWindow addChildWindow:m_nsWindow ordered:NSWindowAbove]; - siblings.append(this); + } else if (shouldBeChildNSWindow) { + if (!m_hiddenByClipping) { + [parentCocoaWindow->m_nsWindow addChildWindow:m_nsWindow ordered:NSWindowAbove]; + parentCocoaWindow->reinsertChildWindow(this); } + + // Set properties after the window has been made a child NSWindow + setCocoaGeometry(windowGeometry()); + setWindowFlags(window()->flags()); } else { // Child windows have no NSWindow, link the NSViews instead. if ([m_view superview]) [m_view removeFromSuperview]; - [m_parentCocoaWindow->m_view addSubview:m_view]; + [parentCocoaWindow->m_view addSubview:m_view]; QRect rect = windowGeometry(); // Prevent setting a (0,0) window size; causes opengl context // "Invalid Drawable" warnings. @@ -1366,9 +1672,6 @@ void QCocoaWindow::recreateWindow(const QPlatformWindow *parentWindow) [m_view setHidden:!window()->isVisible()]; } - m_nsWindow.ignoresMouseEvents = - (window()->flags() & Qt::WindowTransparentForInput) == Qt::WindowTransparentForInput; - const qreal opacity = qt_window_private(window())->opacity; if (!qFuzzyCompare(opacity, qreal(1.0))) setOpacity(opacity); @@ -1381,11 +1684,17 @@ void QCocoaWindow::recreateWindow(const QPlatformWindow *parentWindow) void QCocoaWindow::reinsertChildWindow(QCocoaWindow *child) { - int childIndex = m_childWindows.indexOf(child); + const QObjectList &childWindows = window()->children(); + int childIndex = childWindows.indexOf(child->window()); Q_ASSERT(childIndex != -1); - for (int i = childIndex; i < m_childWindows.size(); i++) { - NSWindow *nsChild = m_childWindows[i]->m_nsWindow; + for (int i = childIndex; i < childWindows.size(); ++i) { + QWindow *window = static_cast<QWindow *>(childWindows.at(i)); + QCocoaWindow *cocoaWindow = static_cast<QCocoaWindow *>(window->handle()); + if (!cocoaWindow) + continue; + + NSWindow *nsChild = cocoaWindow->m_nsWindow; if (i != childIndex) [m_nsWindow removeChildWindow:nsChild]; [m_nsWindow addChildWindow:nsChild ordered:NSWindowAbove]; @@ -1399,114 +1708,80 @@ void QCocoaWindow::requestActivateWindow() [window makeKeyWindow]; } -bool QCocoaWindow::shouldUseNSPanel() -{ - Qt::WindowType type = window()->type(); - - return !m_isNSWindowChild && - ((type & Qt::Popup) == Qt::Popup || (type & Qt::Dialog) == Qt::Dialog); -} - -QCocoaNSWindow * QCocoaWindow::createNSWindow() +QCocoaNSWindow *QCocoaWindow::createNSWindow(bool shouldBeChildNSWindow, bool shouldBePanel) { QMacAutoReleasePool pool; QRect rect = initialGeometry(window(), windowGeometry(), defaultWindowWidth, defaultWindowHeight); - NSRect frame = qt_mac_flipRect(rect); - - Qt::WindowType type = window()->type(); - Qt::WindowFlags flags = window()->flags(); - NSUInteger styleMask; - if (m_isNSWindowChild) { - styleMask = NSBorderlessWindowMask; - } else { - styleMask = windowStyleMask(flags); + QScreen *targetScreen = nullptr; + for (QScreen *screen : QGuiApplication::screens()) { + if (screen->geometry().contains(rect.topLeft())) { + targetScreen = screen; + break; + } } - QCocoaNSWindow *createdWindow = 0; - - // Use NSPanel for popup-type windows. (Popup, Tool, ToolTip, SplashScreen) - // and dialogs - if (shouldUseNSPanel()) { - QNSPanel *window; - window = [[QNSPanel alloc] initWithContentRect:frame - styleMask: styleMask - qPlatformWindow:this]; - if ((type & Qt::Popup) == Qt::Popup) - [window setHasShadow:YES]; - - // Qt::Tool windows hide on app deactivation, unless Qt::WA_MacAlwaysShowToolWindow is set. - QVariant showWithoutActivating = QPlatformWindow::window()->property("_q_macAlwaysShowToolWindow"); - bool shouldHideOnDeactivate = ((type & Qt::Tool) == Qt::Tool) && - !(showWithoutActivating.isValid() && showWithoutActivating.toBool()); - [window setHidesOnDeactivate: shouldHideOnDeactivate]; - - // Make popup windows show on the same desktop as the parent full-screen window. - [window setCollectionBehavior:NSWindowCollectionBehaviorFullScreenAuxiliary]; - if ((type & Qt::Popup) == Qt::Popup) - [window setAnimationBehavior:NSWindowAnimationBehaviorUtilityWindow]; - - createdWindow = window; - } else { - QNSWindow *window; - window = [[QNSWindow alloc] initWithContentRect:frame - styleMask: styleMask - qPlatformWindow:this]; - createdWindow = window; + + if (!targetScreen) { + qCWarning(lcQpaCocoaWindow) << "Window position outside any known screen, using primary screen"; + targetScreen = QGuiApplication::primaryScreen(); } - if ([createdWindow respondsToSelector:@selector(setRestorable:)]) - [createdWindow setRestorable: NO]; + rect.translate(-targetScreen->geometry().topLeft()); + QCocoaScreen *cocoaScreen = static_cast<QCocoaScreen *>(targetScreen->handle()); + NSRect frame = NSRectFromCGRect(cocoaScreen->mapToNative(rect).toCGRect()); - NSInteger level = windowLevel(flags); - [createdWindow setLevel:level]; + // Note: The macOS window manager has a bug, where if a screen is rotated, it will not allow + // a window to be created within the area of the screen that has a Y coordinate (I quadrant) + // higher than the height of the screen in its non-rotated state, unless the window is + // created with the NSWindowStyleMaskBorderless style mask. - // OpenGL surfaces can be ordered either above(default) or below the NSWindow. - // When ordering below the window must be tranclucent and have a clear background color. - static GLint openglSourfaceOrder = qt_mac_resolveOption(1, "QT_MAC_OPENGL_SURFACE_ORDER"); + Qt::WindowType type = window()->type(); + Qt::WindowFlags flags = window()->flags(); + + // Create NSWindow + Class windowClass = shouldBePanel ? [QNSPanel class] : [QNSWindow class]; + NSUInteger styleMask = shouldBeChildNSWindow ? NSBorderlessWindowMask : windowStyleMask(flags); + QCocoaNSWindow *window = [[windowClass alloc] initWithContentRect:frame + screen:cocoaScreen->nativeScreen() styleMask:styleMask qPlatformWindow:this]; + + window.restorable = NO; + window.level = shouldBeChildNSWindow ? NSNormalWindowLevel : windowLevel(flags); - bool isTranslucent = window()->format().alphaBufferSize() > 0 - || (surface()->supportsOpenGL() && openglSourfaceOrder == -1); - if (isTranslucent) { - [createdWindow setBackgroundColor:[NSColor clearColor]]; - [createdWindow setOpaque:NO]; + if (!isOpaque()) { + window.backgroundColor = [NSColor clearColor]; + window.opaque = NO; } - m_windowModality = window()->modality(); + Q_ASSERT(!(shouldBePanel && shouldBeChildNSWindow)); - applyContentBorderThickness(createdWindow); + if (shouldBePanel) { + // Qt::Tool windows hide on app deactivation, unless Qt::WA_MacAlwaysShowToolWindow is set + window.hidesOnDeactivate = ((type & Qt::Tool) == Qt::Tool) && + !qt_mac_resolveOption(false, QPlatformWindow::window(), "_q_macAlwaysShowToolWindow", ""); - return createdWindow; -} + // Make popup windows show on the same desktop as the parent full-screen window + window.collectionBehavior = NSWindowCollectionBehaviorFullScreenAuxiliary; -void QCocoaWindow::setNSWindow(QCocoaNSWindow *window) -{ - if (window.contentView != m_view) { - [m_view setPostsFrameChangedNotifications:NO]; - [m_view retain]; - if (m_view.superview) // m_view comes from another NSWindow - [m_view removeFromSuperview]; - [window setContentView:m_view]; - [m_view release]; - [m_view setPostsFrameChangedNotifications:YES]; + if ((type & Qt::Popup) == Qt::Popup) { + window.hasShadow = YES; + window.animationBehavior = NSWindowAnimationBehaviorUtilityWindow; + } + } else if (shouldBeChildNSWindow) { + window.collectionBehavior = + NSWindowCollectionBehaviorManaged + | NSWindowCollectionBehaviorIgnoresCycle + | NSWindowCollectionBehaviorFullScreenAuxiliary; + window.hasShadow = NO; + window.animationBehavior = NSWindowAnimationBehaviorNone; } -} - -void QCocoaWindow::removeChildWindow(QCocoaWindow *child) -{ - m_childWindows.removeOne(child); - [m_nsWindow removeChildWindow:child->m_nsWindow]; -} -bool QCocoaWindow::isNativeWindowTypeInconsistent() -{ - if (!m_nsWindow) - return false; + // Persist modality so we can detect changes later on + m_windowModality = QPlatformWindow::window()->modality(); - const bool isPanel = [m_nsWindow isKindOfClass:[QNSPanel class]]; - const bool usePanel = shouldUseNSPanel(); + applyContentBorderThickness(window); - return isPanel != usePanel; + return window; } void QCocoaWindow::removeMonitor() @@ -1520,7 +1795,7 @@ void QCocoaWindow::removeMonitor() // Returns the current global screen geometry for the nswindow associated with this window. QRect QCocoaWindow::nativeWindowGeometry() const { - if (!m_nsWindow || m_isNSWindowChild) + if (!m_nsWindow || isChildNSWindow()) return geometry(); NSRect rect = [m_nsWindow frame]; @@ -1530,94 +1805,144 @@ QRect QCocoaWindow::nativeWindowGeometry() const return qRect; } -// Returns a pointer to the parent QCocoaWindow for this window, or 0 if there is none. -QCocoaWindow *QCocoaWindow::parentCocoaWindow() const -{ - if (window() && window()->transientParent()) { - return static_cast<QCocoaWindow*>(window()->transientParent()->handle()); - } - return 0; -} +/*! + Applies the given state to the NSWindow, going in/out of minimize/zoomed/fullscreen -// Syncs the NSWindow minimize/maximize/fullscreen state with the current QWindow state -void QCocoaWindow::syncWindowState(Qt::WindowState newState) + 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::WindowState newState) { + const Qt::WindowState currentState = windowState(); + if (newState == currentState) + return; + if (!m_nsWindow) return; - // if content view width or height is 0 then the window animations will crash so - // do nothing except set the new state - NSRect contentRect = m_view.frame; - if (contentRect.size.width <= 0 || contentRect.size.height <= 0) { + + 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"); - m_synchedWindowState = newState; + reportCurrentWindowState(true); return; } - Qt::WindowState predictedState = newState; - if ((m_synchedWindowState & Qt::WindowMaximized) != (newState & Qt::WindowMaximized)) { - const int styleMask = [m_nsWindow styleMask]; - const bool usePerform = styleMask & NSResizableWindowMask; - [m_nsWindow setStyleMask:styleMask | NSResizableWindowMask]; - if (usePerform) - [m_nsWindow performZoom : m_nsWindow]; // toggles - else - [m_nsWindow zoom : m_nsWindow]; // toggles - [m_nsWindow setStyleMask:styleMask]; + if (m_nsWindow.styleMask & NSUtilityWindowMask) { + // Utility panels cannot be fullscreen + qWarning() << window()->type() << "windows can not be made full screen"; + reportCurrentWindowState(true); + return; } - if ((m_synchedWindowState & Qt::WindowMinimized) != (newState & Qt::WindowMinimized)) { - if (newState & Qt::WindowMinimized) { - if ([m_nsWindow styleMask] & NSMiniaturizableWindowMask) - [m_nsWindow performMiniaturize : m_nsWindow]; - else - [m_nsWindow miniaturize : m_nsWindow]; - } else { - [m_nsWindow deminiaturize : m_nsWindow]; - } + const id sender = m_nsWindow; + + // First we need to exit states that can't transition directly to other states + switch (currentState) { + case Qt::WindowMinimized: + [m_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; } - - const bool effMax = m_effectivelyMaximized; - if ((m_synchedWindowState & Qt::WindowMaximized) != (newState & Qt::WindowMaximized) || (m_effectivelyMaximized && newState == Qt::WindowNoState)) { - if ((m_synchedWindowState & Qt::WindowFullScreen) == (newState & Qt::WindowFullScreen)) { - [m_nsWindow zoom : m_nsWindow]; // toggles - m_effectivelyMaximized = !effMax; - } else if (!(newState & Qt::WindowMaximized)) { - // it would be nice to change the target geometry that toggleFullScreen will animate toward - // but there is no known way, so the maximized state is not possible at this time - predictedState = static_cast<Qt::WindowState>(static_cast<int>(newState) | Qt::WindowMaximized); - m_effectivelyMaximized = true; - } + default: + Q_FALLTHROUGH(); } - if ((m_synchedWindowState & Qt::WindowFullScreen) != (newState & Qt::WindowFullScreen)) { - if (window()->flags() & Qt::WindowFullscreenButtonHint) { - if (m_effectivelyMaximized && m_synchedWindowState == Qt::WindowFullScreen) - predictedState = Qt::WindowMaximized; - [m_nsWindow toggleFullScreen : m_nsWindow]; - } else { - if (newState & Qt::WindowFullScreen) { - QScreen *screen = window()->screen(); - if (screen) { - if (m_normalGeometry.width() < 0) { - m_oldWindowFlags = m_windowFlags; - window()->setFlags(window()->flags() | Qt::FramelessWindowHint); - m_normalGeometry = nativeWindowGeometry(); - setGeometry(screen->geometry()); - m_presentationOptions = [NSApp presentationOptions]; - [NSApp setPresentationOptions : m_presentationOptions | NSApplicationPresentationAutoHideMenuBar | NSApplicationPresentationAutoHideDock]; - } - } - } else { - window()->setFlags(m_oldWindowFlags); - setGeometry(m_normalGeometry); - m_normalGeometry.setRect(0, 0, -1, -1); - [NSApp setPresentationOptions : m_presentationOptions]; - } + // 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: + [m_nsWindow miniaturize:sender]; + break; + case Qt::WindowNoState: + switch (windowState()) { + case Qt::WindowMaximized: + toggleMaximized(); + default: + Q_FALLTHROUGH(); } + break; + default: + Q_UNREACHABLE(); } +} + +void QCocoaWindow::toggleMaximized() +{ + // The NSWindow needs to be resizable, otherwise the window will + // not be possible to zoom back to non-zoomed state. + const bool wasResizable = m_nsWindow.styleMask & NSResizableWindowMask; + m_nsWindow.styleMask |= NSResizableWindowMask; + + const id sender = m_nsWindow; + [m_nsWindow zoom:sender]; + + if (!wasResizable) + m_nsWindow.styleMask &= ~NSResizableWindowMask; +} + +void QCocoaWindow::toggleFullScreen() +{ + // 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. + m_nsWindow.collectionBehavior |= NSWindowCollectionBehaviorFullScreenPrimary; + + const id sender = m_nsWindow; + [m_nsWindow 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; +} + +void QCocoaWindow::reportCurrentWindowState(bool unconditionally) +{ + Qt::WindowState currentState = windowState(); + if (!unconditionally && currentState == m_lastReportedWindowState) + return; - // New state is now the current synched state - m_synchedWindowState = predictedState; + QWindowSystemInterface::handleWindowStateChanged<QWindowSystemInterface::SynchronousDelivery>( + window(), currentState, m_lastReportedWindowState); + m_lastReportedWindowState = currentState; } bool QCocoaWindow::setWindowModified(bool modified) @@ -1671,7 +1996,7 @@ void QCocoaWindow::setWindowCursor(NSCursor *cursor) return; // Setting a cursor in a foregin view is not supported. - if (window()->type() == Qt::ForeignWindow) + if (isForeignWindow()) return; [m_windowCursor release]; @@ -1814,15 +2139,13 @@ void QCocoaWindow::exposeWindow() if (!isWindowExposable()) return; - // Update the QWindow's screen property. This property is set - // to QGuiApplication::primaryScreen() at QWindow construciton - // time, and we won't get a NSWindowDidChangeScreenNotification - // on show. The case where the window is initially displayed - // on a non-primary screen needs special handling here. - NSUInteger screenIndex = [[NSScreen screens] indexOfObject:m_nsWindow.screen]; - if (screenIndex != NSNotFound) { - QCocoaScreen *cocoaScreen = QCocoaIntegration::instance()->screenAtIndex(screenIndex); - if (cocoaScreen) + if (window()->isTopLevel()) { + // Update the QWindow's screen property. This property is set + // to QGuiApplication::primaryScreen() at QWindow construciton + // time, and we won't get a NSWindowDidChangeScreenNotification + // on show. The case where the window is initially displayed + // on a non-primary screen needs special handling here. + if (QCocoaScreen *cocoaScreen = QCocoaIntegration::instance()->screenForNSScreen(m_nsWindow.screen)) window()->setScreen(cocoaScreen->screen()); } |