diff --git a/src/plugins/platforms/cocoa/qcocoawindow.h b/src/plugins/platforms/cocoa/qcocoawindow.h index c650c863797..a93a391358f 100644 --- a/src/plugins/platforms/cocoa/qcocoawindow.h +++ b/src/plugins/platforms/cocoa/qcocoawindow.h @@ -134,11 +134,12 @@ public: void setEmbeddedInForeignView(bool subwindow); + Q_NOTIFICATION_HANDLER(NSViewFrameDidChangeNotification) void viewDidChangeFrame(); + Q_NOTIFICATION_HANDLER(NSViewGlobalFrameDidChangeNotification) void viewDidChangeGlobalFrame(); + Q_NOTIFICATION_HANDLER(NSWindowWillMoveNotification) void windowWillMove(); Q_NOTIFICATION_HANDLER(NSWindowDidMoveNotification) void windowDidMove(); Q_NOTIFICATION_HANDLER(NSWindowDidResizeNotification) void windowDidResize(); - Q_NOTIFICATION_HANDLER(NSViewFrameDidChangeNotification) void viewDidChangeFrame(); - Q_NOTIFICATION_HANDLER(NSViewGlobalFrameDidChangeNotification) void viewDidChangeGlobalFrame(); Q_NOTIFICATION_HANDLER(NSWindowDidEndLiveResizeNotification) void windowDidEndLiveResize(); Q_NOTIFICATION_HANDLER(NSWindowDidBecomeKeyNotification) void windowDidBecomeKey(); Q_NOTIFICATION_HANDLER(NSWindowDidResignKeyNotification) void windowDidResignKey(); @@ -148,8 +149,8 @@ public: Q_NOTIFICATION_HANDLER(NSWindowDidEnterFullScreenNotification) void windowDidEnterFullScreen(); Q_NOTIFICATION_HANDLER(NSWindowWillExitFullScreenNotification) void windowWillExitFullScreen(); Q_NOTIFICATION_HANDLER(NSWindowDidExitFullScreenNotification) void windowDidExitFullScreen(); - Q_NOTIFICATION_HANDLER(NSWindowDidOrderOffScreenNotification) void windowDidOrderOffScreen(); Q_NOTIFICATION_HANDLER(NSWindowDidOrderOnScreenAndFinishAnimatingNotification) void windowDidOrderOnScreen(); + Q_NOTIFICATION_HANDLER(NSWindowDidOrderOffScreenNotification) void windowDidOrderOffScreen(); Q_NOTIFICATION_HANDLER(NSWindowDidChangeOcclusionStateNotification) void windowDidChangeOcclusionState(); Q_NOTIFICATION_HANDLER(NSWindowDidChangeScreenNotification) void windowDidChangeScreen(); Q_NOTIFICATION_HANDLER(NSWindowWillCloseNotification) void windowWillClose(); diff --git a/src/plugins/platforms/cocoa/qcocoawindow.mm b/src/plugins/platforms/cocoa/qcocoawindow.mm index 1ef02f52743..a88ec2b0db3 100644 --- a/src/plugins/platforms/cocoa/qcocoawindow.mm +++ b/src/plugins/platforms/cocoa/qcocoawindow.mm @@ -97,34 +97,30 @@ static void qRegisterNotificationCallbacks() [center addObserverForName:notificationName.toNSString() object:nil queue:nil usingBlock:^(NSNotification *notification) { - NSView *view = nullptr; + QVarLengthArray cocoaWindows; if ([notification.object isKindOfClass:[NSWindow class]]) { - NSWindow *window = notification.object; - if (!window.contentView) - return; - - view = window.contentView; + NSWindow *nsWindow = notification.object; + for (const QWindow *window : QGuiApplication::allWindows()) { + if (QCocoaWindow *cocoaWindow = static_cast(window->handle())) + if (cocoaWindow->nativeWindow() == nsWindow) + cocoaWindows += cocoaWindow; + } } else if ([notification.object isKindOfClass:[NSView class]]) { - view = notification.object; + if (QNSView *qnsView = qnsview_cast(notification.object)) + cocoaWindows += qnsView.platformWindow; } 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; + for (QCocoaWindow *cocoaWindow : cocoaWindows) { + if (!method.invoke(cocoaWindow, Qt::DirectConnection)) { + qCWarning(lcQpaCocoaWindow) << "Failed to invoke NSNotification callback for" + << notification.name << "on" << cocoaWindow; + } } }]; } @@ -844,32 +840,7 @@ void QCocoaWindow::setEmbeddedInForeignView(bool embedded) m_nsWindow = 0; } -// ----------------------- NSWindow notifications ----------------------- - -void QCocoaWindow::windowWillMove() -{ - // Close any open popups on window move - qt_closePopups(); -} - -void QCocoaWindow::windowDidMove() -{ - handleGeometryChange(); - - // Moving a window might bring it out of maximized state - handleWindowStateChanged(); -} - -void QCocoaWindow::windowDidResize() -{ - if (!isContentView()) - return; - - handleGeometryChange(); - - if (!m_view.inLiveResize) - handleWindowStateChanged(); -} +// ----------------------- NSView notifications ----------------------- void QCocoaWindow::viewDidChangeFrame() { @@ -888,13 +859,54 @@ void QCocoaWindow::viewDidChangeGlobalFrame() [m_view setNeedsDisplay:YES]; } +// ----------------------- NSWindow notifications ----------------------- + +// Note: The following notifications are delivered to every QCocoaWindow +// that is a child of the NSWindow that triggered the notification. Each +// callback should make sure to filter out notifications if they do not +// apply to that QCocoaWindow, e.g. if the window is not a content view. + +void QCocoaWindow::windowWillMove() +{ + // Close any open popups on window move + qt_closePopups(); +} + +void QCocoaWindow::windowDidMove() +{ + if (!isContentView()) + return; + + handleGeometryChange(); + + // Moving a window might bring it out of maximized state + handleWindowStateChanged(); +} + +void QCocoaWindow::windowDidResize() +{ + if (!isContentView()) + return; + + handleGeometryChange(); + + if (!m_view.inLiveResize) + handleWindowStateChanged(); +} + void QCocoaWindow::windowDidEndLiveResize() { + if (!isContentView()) + return; + handleWindowStateChanged(); } void QCocoaWindow::windowDidBecomeKey() { + if (!isContentView()) + return; + if (isForeignWindow()) return; @@ -911,6 +923,9 @@ void QCocoaWindow::windowDidBecomeKey() void QCocoaWindow::windowDidResignKey() { + if (!isContentView()) + return; + if (isForeignWindow()) return; @@ -927,16 +942,25 @@ void QCocoaWindow::windowDidResignKey() 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. @@ -945,6 +969,9 @@ void QCocoaWindow::windowWillEnterFullScreen() void QCocoaWindow::windowDidEnterFullScreen() { + if (!isContentView()) + return; + Q_ASSERT_X(m_view.window.qt_fullScreen, "QCocoaWindow", "FullScreen category processes window notifications first"); @@ -956,6 +983,9 @@ void QCocoaWindow::windowDidEnterFullScreen() 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; @@ -963,6 +993,9 @@ void QCocoaWindow::windowWillExitFullScreen() void QCocoaWindow::windowDidExitFullScreen() { + if (!isContentView()) + return; + Q_ASSERT_X(!m_view.window.qt_fullScreen, "QCocoaWindow", "FullScreen category processes window notifications first"); @@ -981,16 +1014,16 @@ void QCocoaWindow::windowDidExitFullScreen() } } -void QCocoaWindow::windowDidOrderOffScreen() -{ - handleExposeEvent(QRegion()); -} - void QCocoaWindow::windowDidOrderOnScreen() { [m_view setNeedsDisplay:YES]; } +void QCocoaWindow::windowDidOrderOffScreen() +{ + handleExposeEvent(QRegion()); +} + void QCocoaWindow::windowDidChangeOcclusionState() { if (m_view.window.occlusionState & NSWindowOcclusionStateVisible) @@ -1422,15 +1455,15 @@ QRect QCocoaWindow::nativeWindowGeometry() const */ 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; - if (!isContentView()) - 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 diff --git a/tests/manual/cocoa/qt_on_cocoa/main.mm b/tests/manual/cocoa/qt_on_cocoa/main.mm index 5e3b8fcd39c..805ef0d7c25 100644 --- a/tests/manual/cocoa/qt_on_cocoa/main.mm +++ b/tests/manual/cocoa/qt_on_cocoa/main.mm @@ -87,8 +87,6 @@ options:NSTrackingActiveInActiveApp | NSTrackingInVisibleRect | NSTrackingCursorUpdate owner:contentView userInfo:nil]]; - window.contentView = contentView; - // Create the QWindow, add its NSView to the content view m_window = new RasterWindow; m_window->setObjectName("RasterWindow"); @@ -104,10 +102,15 @@ NSTextField *textField = [[NSTextField alloc] initWithFrame:NSMakeRect(10, 10, 80, 25)]; [(NSView*)childWindow->winId() addSubview:textField]; - [window.contentView addSubview:reinterpret_cast(m_window->winId())]; + [contentView addSubview:reinterpret_cast(m_window->winId())]; - // Show the NSWindow - [window makeKeyAndOrderFront:NSApp]; + window.contentView = contentView; + + // Show the NSWindow delayed, so that we can verify that Qt picks up the right + // notifications to expose the window when it does become visible. + dispatch_async(dispatch_get_main_queue(), ^{ + [window makeKeyAndOrderFront:NSApp]; + }); } - (void)applicationWillTerminate:(NSNotification *)notification diff --git a/tests/manual/cocoa/qt_on_cocoa/rasterwindow.cpp b/tests/manual/cocoa/qt_on_cocoa/rasterwindow.cpp index dca39839dda..6d7cb3e3054 100644 --- a/tests/manual/cocoa/qt_on_cocoa/rasterwindow.cpp +++ b/tests/manual/cocoa/qt_on_cocoa/rasterwindow.cpp @@ -148,6 +148,11 @@ bool RasterWindow::event(QEvent *e) void RasterWindow::render() { + if (!isExposed()) { + qDebug() << "Skipping render, not exposed"; + return; + } + QRect rect(QPoint(), geometry().size()); m_backingStore->resize(rect.size());