diff options
Diffstat (limited to 'src/plugins/platforms/cocoa')
27 files changed, 902 insertions, 631 deletions
diff --git a/src/plugins/platforms/cocoa/cocoa.pro b/src/plugins/platforms/cocoa/cocoa.pro index 0664841c2d..62935210be 100644 --- a/src/plugins/platforms/cocoa/cocoa.pro +++ b/src/plugins/platforms/cocoa/cocoa.pro @@ -86,6 +86,8 @@ QT += \ accessibility_support-private clipboard_support-private theme_support-private \ fontdatabase_support-private graphics_support-private cgl_support-private +CONFIG += no_app_extension_api_only + qtHaveModule(widgets) { OBJECTIVE_SOURCES += \ qpaintengine_mac.mm \ diff --git a/src/plugins/platforms/cocoa/images/copyarrowcursor.png b/src/plugins/platforms/cocoa/images/copyarrowcursor.png Binary files differdeleted file mode 100644 index 13dfca95bc..0000000000 --- a/src/plugins/platforms/cocoa/images/copyarrowcursor.png +++ /dev/null diff --git a/src/plugins/platforms/cocoa/images/forbiddencursor.png b/src/plugins/platforms/cocoa/images/forbiddencursor.png Binary files differdeleted file mode 100644 index a9f21b4a5e..0000000000 --- a/src/plugins/platforms/cocoa/images/forbiddencursor.png +++ /dev/null diff --git a/src/plugins/platforms/cocoa/images/leopard-unified-toolbar-on.png b/src/plugins/platforms/cocoa/images/leopard-unified-toolbar-on.png Binary files differdeleted file mode 100644 index 6716597046..0000000000 --- a/src/plugins/platforms/cocoa/images/leopard-unified-toolbar-on.png +++ /dev/null diff --git a/src/plugins/platforms/cocoa/qcocoaapplication.mm b/src/plugins/platforms/cocoa/qcocoaapplication.mm index c5ae4bc2bf..3b950efa55 100644 --- a/src/plugins/platforms/cocoa/qcocoaapplication.mm +++ b/src/plugins/platforms/cocoa/qcocoaapplication.mm @@ -76,6 +76,7 @@ #include "qcocoaintrospection.h" #include "qcocoaapplicationdelegate.h" #include "qcocoahelpers.h" +#include "qcocoawindow.h" #include <qguiapplication.h> #include <qdebug.h> @@ -148,6 +149,21 @@ static const QByteArray q_macLocalEventType = QByteArrayLiteral("mac_generic_NSE @end +static void qt_maybeSendKeyEquivalentUpEvent(NSEvent *event) +{ + // Cocoa is known for not sending key up events for key + // equivalents, regardless of whether it's an actual + // recognized key equivalent. We decide to force fate + // and forward the key event to the key (focus) window. + // However, non-Qt windows will not (and should not) get + // any special treatment, only QWindow-owned NSWindows. + if (event.type == NSKeyUp && (event.modifierFlags & NSCommandKeyMask)) { + NSWindow *targetWindow = event.window; + if ([targetWindow.class conformsToProtocol:@protocol(QNSWindowProtocol)]) + [targetWindow sendEvent:event]; + } +} + @implementation QT_MANGLE_NAMESPACE(QNSApplication) - (void)QT_MANGLE_NAMESPACE(qt_sendEvent_original):(NSEvent *)event @@ -164,16 +180,20 @@ static const QByteArray q_macLocalEventType = QByteArrayLiteral("mac_generic_NSE // be called instead of sendEvent if redirection occurs. // 'self' will then be an instance of NSApplication // (and not QNSApplication) - if (![NSApp QT_MANGLE_NAMESPACE(qt_filterEvent):event]) + if (![NSApp QT_MANGLE_NAMESPACE(qt_filterEvent):event]) { [self QT_MANGLE_NAMESPACE(qt_sendEvent_original):event]; + qt_maybeSendKeyEquivalentUpEvent(event); + } } - (void)sendEvent:(NSEvent *)event { // This method will be called if // no redirection occurs - if (![NSApp QT_MANGLE_NAMESPACE(qt_filterEvent):event]) + if (![NSApp QT_MANGLE_NAMESPACE(qt_filterEvent):event]) { [super sendEvent:event]; + qt_maybeSendKeyEquivalentUpEvent(event); + } } @end diff --git a/src/plugins/platforms/cocoa/qcocoabackingstore.mm b/src/plugins/platforms/cocoa/qcocoabackingstore.mm index a74995319b..1d7ad772dc 100644 --- a/src/plugins/platforms/cocoa/qcocoabackingstore.mm +++ b/src/plugins/platforms/cocoa/qcocoabackingstore.mm @@ -57,7 +57,8 @@ QCocoaBackingStore::~QCocoaBackingStore() QImage::Format QCocoaBackingStore::format() const { - if (static_cast<QCocoaWindow *>(window()->handle())->m_drawContentBorderGradient) + QCocoaWindow *cocoaWindow = static_cast<QCocoaWindow *>(window()->handle()); + if (cocoaWindow && cocoaWindow->m_drawContentBorderGradient) return QImage::Format_ARGB32_Premultiplied; return QRasterBackingStore::format(); diff --git a/src/plugins/platforms/cocoa/qcocoacolordialoghelper.mm b/src/plugins/platforms/cocoa/qcocoacolordialoghelper.mm index e53c085e41..a8974c4de5 100644 --- a/src/plugins/platforms/cocoa/qcocoacolordialoghelper.mm +++ b/src/plugins/platforms/cocoa/qcocoacolordialoghelper.mm @@ -80,7 +80,7 @@ QT_NAMESPACE_ALIAS_OBJC_CLASS(QNSColorPanelDelegate); mHelper = 0; mStolenContentView = 0; mPanelButtons = nil; - mResultCode = NSCancelButton; + mResultCode = NSModalResponseCancel; mDialogIsExecuting = false; mResultSet = false; mClosingDueToKnownButton = false; @@ -168,7 +168,7 @@ QT_NAMESPACE_ALIAS_OBJC_CLASS(QNSColorPanelDelegate); mClosingDueToKnownButton = true; [mColorPanel close]; [self updateQtColor]; - [self finishOffWithCode:NSOKButton]; + [self finishOffWithCode:NSModalResponseOK]; } - (void)onCancelClicked @@ -177,7 +177,7 @@ QT_NAMESPACE_ALIAS_OBJC_CLASS(QNSColorPanelDelegate); mClosingDueToKnownButton = true; [mColorPanel close]; mQtColor = QColor(); - [self finishOffWithCode:NSCancelButton]; + [self finishOffWithCode:NSModalResponseCancel]; } } @@ -238,12 +238,12 @@ QT_NAMESPACE_ALIAS_OBJC_CLASS(QNSColorPanelDelegate); [NSApp runModalForWindow:mColorPanel]; mDialogIsExecuting = false; - return (mResultCode == NSOKButton); + return (mResultCode == NSModalResponseOK); } - (QPlatformDialogHelper::DialogCode)dialogResultCode { - return (mResultCode == NSOKButton) ? QPlatformDialogHelper::Accepted : QPlatformDialogHelper::Rejected; + return (mResultCode == NSModalResponseOK) ? QPlatformDialogHelper::Accepted : QPlatformDialogHelper::Rejected; } - (BOOL)windowShouldClose:(id)window @@ -252,7 +252,7 @@ QT_NAMESPACE_ALIAS_OBJC_CLASS(QNSColorPanelDelegate); if (!mPanelButtons) [self updateQtColor]; if (mDialogIsExecuting) { - [self finishOffWithCode:NSCancelButton]; + [self finishOffWithCode:NSModalResponseCancel]; } else { mResultSet = true; if (mHelper) @@ -278,7 +278,7 @@ QT_NAMESPACE_ALIAS_OBJC_CLASS(QNSColorPanelDelegate); // This check will prevent any such recursion. if (!mResultSet) { mResultSet = true; - if (mResultCode == NSCancelButton) { + if (mResultCode == NSModalResponseCancel) { emit mHelper->reject(); } else { emit mHelper->accept(); diff --git a/src/plugins/platforms/cocoa/qcocoacursor.mm b/src/plugins/platforms/cocoa/qcocoacursor.mm index 3df2a7c962..99a136d384 100644 --- a/src/plugins/platforms/cocoa/qcocoacursor.mm +++ b/src/plugins/platforms/cocoa/qcocoacursor.mm @@ -97,6 +97,9 @@ NSCursor *QCocoaCursor::convertCursor(QCursor *cursor) case Qt::ArrowCursor: cocoaCursor= [NSCursor arrowCursor]; break; + case Qt::ForbiddenCursor: + cocoaCursor = [NSCursor operationNotAllowedCursor]; + break; case Qt::CrossCursor: cocoaCursor = [NSCursor crosshairCursor]; break; @@ -123,7 +126,7 @@ NSCursor *QCocoaCursor::convertCursor(QCursor *cursor) cocoaCursor = [NSCursor crosshairCursor]; break; case Qt::DragCopyCursor: - cocoaCursor = [NSCursor crosshairCursor]; + cocoaCursor = [NSCursor dragCopyCursor]; break; case Qt::DragLinkCursor: cocoaCursor = [NSCursor dragLinkCursor]; @@ -235,10 +238,6 @@ NSCursor *QCocoaCursor::createCursorData(QCursor *cursor) QPixmap pixmap = QPixmap(QLatin1String(":/qt-project.org/mac/cursors/images/waitcursor.png")); return createCursorFromPixmap(pixmap, hotspot); break; } - case Qt::ForbiddenCursor: { - QPixmap pixmap = QPixmap(QLatin1String(":/qt-project.org/mac/cursors/images/forbiddencursor.png")); - return createCursorFromPixmap(pixmap, hotspot); - break; } #define QT_USE_APPROXIMATE_CURSORS #ifdef QT_USE_APPROXIMATE_CURSORS case Qt::SizeVerCursor: diff --git a/src/plugins/platforms/cocoa/qcocoadrag.mm b/src/plugins/platforms/cocoa/qcocoadrag.mm index a0967750e7..c71e80d191 100644 --- a/src/plugins/platforms/cocoa/qcocoadrag.mm +++ b/src/plugins/platforms/cocoa/qcocoadrag.mm @@ -132,7 +132,7 @@ Qt::DropAction QCocoaDrag::drag(QDrag *o) QPixmap pm = dragPixmap(m_drag, hotSpot); QSize pmDeviceIndependentSize = pm.size() / pm.devicePixelRatio(); NSImage *nsimage = qt_mac_create_nsimage(pm); - [nsimage setSize:pmDeviceIndependentSize.toCGSize()]; + [nsimage setSize:NSSizeFromCGSize(pmDeviceIndependentSize.toCGSize())]; QMacPasteboard dragBoard((CFStringRef) NSDragPboard, QMacInternalPasteboardMime::MIME_DND); m_drag->mimeData()->setData(QLatin1String("application/x-qt-mime-type-name"), QByteArray("dummy")); diff --git a/src/plugins/platforms/cocoa/qcocoaeventdispatcher.mm b/src/plugins/platforms/cocoa/qcocoaeventdispatcher.mm index 72c7856c2d..d2f985ec87 100644 --- a/src/plugins/platforms/cocoa/qcocoaeventdispatcher.mm +++ b/src/plugins/platforms/cocoa/qcocoaeventdispatcher.mm @@ -401,7 +401,7 @@ bool QCocoaEventDispatcher::processEvents(QEventLoop::ProcessEventsFlags flags) // [NSApp run], which is the normal code path for cocoa applications. if (NSModalSession session = d->currentModalSession()) { QBoolBlocker execGuard(d->currentExecIsNSAppRun, false); - while ([NSApp runModalSession:session] == NSRunContinuesResponse && !d->interrupt) + while ([NSApp runModalSession:session] == NSModalResponseContinue && !d->interrupt) qt_mac_waitForMoreEvents(NSModalPanelRunLoopMode); if (!d->interrupt && session == d->currentModalSessionCached) { @@ -435,7 +435,7 @@ bool QCocoaEventDispatcher::processEvents(QEventLoop::ProcessEventsFlags flags) if (flags & QEventLoop::WaitForMoreEvents) qt_mac_waitForMoreEvents(NSModalPanelRunLoopMode); NSInteger status = [NSApp runModalSession:session]; - if (status != NSRunContinuesResponse && session == d->currentModalSessionCached) { + if (status != NSModalResponseContinue && session == d->currentModalSessionCached) { // INVARIANT: Someone called [NSApp stopModal:] from outside the event // dispatcher (e.g to stop a native dialog). But that call wrongly stopped // 'session' as well. As a result, we need to restart all internal sessions: diff --git a/src/plugins/platforms/cocoa/qcocoafiledialoghelper.mm b/src/plugins/platforms/cocoa/qcocoafiledialoghelper.mm index 234da57f59..41a809cdd2 100644 --- a/src/plugins/platforms/cocoa/qcocoafiledialoghelper.mm +++ b/src/plugins/platforms/cocoa/qcocoafiledialoghelper.mm @@ -63,6 +63,7 @@ #include <stdlib.h> #include <qabstracteventdispatcher.h> #include <qsysinfo.h> +#include <qoperatingsystemversion.h> #include <qglobal.h> #include <QDir> @@ -164,7 +165,7 @@ QT_NAMESPACE_ALIAS_OBJC_CLASS(QNSOpenSavePanelDelegate); [mSavePanel setDelegate:self]; #if QT_OSX_PLATFORM_SDK_EQUAL_OR_ABOVE(__MAC_10_11) - if (QSysInfo::MacintoshVersion >= QSysInfo::MV_10_11) + if (QOperatingSystemVersion::current() >= QOperatingSystemVersion::OSXElCapitan) mOpenPanel.accessoryViewDisclosed = YES; #endif diff --git a/src/plugins/platforms/cocoa/qcocoafontdialoghelper.mm b/src/plugins/platforms/cocoa/qcocoafontdialoghelper.mm index 33dd4260a5..e4b796dcde 100644 --- a/src/plugins/platforms/cocoa/qcocoafontdialoghelper.mm +++ b/src/plugins/platforms/cocoa/qcocoafontdialoghelper.mm @@ -106,7 +106,7 @@ QT_NAMESPACE_ALIAS_OBJC_CLASS(QNSFontPanelDelegate); mHelper = 0; mStolenContentView = 0; mPanelButtons = 0; - mResultCode = NSCancelButton; + mResultCode = NSModalResponseCancel; mDialogIsExecuting = false; mResultSet = false; @@ -171,7 +171,7 @@ QT_NAMESPACE_ALIAS_OBJC_CLASS(QNSFontPanelDelegate); - (void)onOkClicked { [mFontPanel close]; - [self finishOffWithCode:NSOKButton]; + [self finishOffWithCode:NSModalResponseOK]; } - (void)onCancelClicked @@ -179,7 +179,7 @@ QT_NAMESPACE_ALIAS_OBJC_CLASS(QNSFontPanelDelegate); if (mPanelButtons) { [mFontPanel close]; mQtFont = QFont(); - [self finishOffWithCode:NSCancelButton]; + [self finishOffWithCode:NSModalResponseCancel]; } } @@ -224,12 +224,12 @@ QT_NAMESPACE_ALIAS_OBJC_CLASS(QNSFontPanelDelegate); [NSApp runModalForWindow:mFontPanel]; mDialogIsExecuting = false; - return (mResultCode == NSOKButton); + return (mResultCode == NSModalResponseOK); } - (QPlatformDialogHelper::DialogCode)dialogResultCode { - return (mResultCode == NSOKButton) ? QPlatformDialogHelper::Accepted : QPlatformDialogHelper::Rejected; + return (mResultCode == NSModalResponseOK) ? QPlatformDialogHelper::Accepted : QPlatformDialogHelper::Rejected; } - (BOOL)windowShouldClose:(id)window @@ -238,7 +238,7 @@ QT_NAMESPACE_ALIAS_OBJC_CLASS(QNSFontPanelDelegate); if (!mPanelButtons) [self updateQtFont]; if (mDialogIsExecuting) { - [self finishOffWithCode:NSCancelButton]; + [self finishOffWithCode:NSModalResponseCancel]; } else { mResultSet = true; if (mHelper) @@ -264,7 +264,7 @@ QT_NAMESPACE_ALIAS_OBJC_CLASS(QNSFontPanelDelegate); // This check will prevent any such recursion. if (!mResultSet) { mResultSet = true; - if (mResultCode == NSCancelButton) { + if (mResultCode == NSModalResponseCancel) { emit mHelper->reject(); } else { emit mHelper->accept(); diff --git a/src/plugins/platforms/cocoa/qcocoaglcontext.mm b/src/plugins/platforms/cocoa/qcocoaglcontext.mm index a7cc19b3bf..9e688f4d1b 100644 --- a/src/plugins/platforms/cocoa/qcocoaglcontext.mm +++ b/src/plugins/platforms/cocoa/qcocoaglcontext.mm @@ -255,7 +255,8 @@ void QCocoaGLContext::setActiveWindow(QWindow *window) QCocoaWindow *cocoaWindow = static_cast<QCocoaWindow *>(window->handle()); cocoaWindow->setCurrentContext(this); - [(QNSView *) cocoaWindow->view() setQCocoaGLContext:this]; + Q_ASSERT(!cocoaWindow->isForeignWindow()); + [qnsview_cast(cocoaWindow->view()) setQCocoaGLContext:this]; } void QCocoaGLContext::updateSurfaceFormat() diff --git a/src/plugins/platforms/cocoa/qcocoahelpers.mm b/src/plugins/platforms/cocoa/qcocoahelpers.mm index 3ab6b641fa..232e40769b 100644 --- a/src/plugins/platforms/cocoa/qcocoahelpers.mm +++ b/src/plugins/platforms/cocoa/qcocoahelpers.mm @@ -151,7 +151,8 @@ Qt::DropActions qt_mac_mapNSDragOperations(NSDragOperation nsActions) a no-op. For extra verbosity and clearer code, please consider checking - that window()->type() != Qt::ForeignWindow before using this cast. + that the platform window is not a foreign window before using + this cast, via QPlatformWindow::isForeignWindow(). Do not use this method soley to check for foreign windows, as that will make the code harder to read for people not working @@ -160,10 +161,8 @@ Qt::DropActions qt_mac_mapNSDragOperations(NSDragOperation nsActions) */ QNSView *qnsview_cast(NSView *view) { - if (![view isKindOfClass:[QNSView class]]) { - qCWarning(lcQpaCocoaWindow) << "NSView is not QNSView, consider checking for Qt::ForeignWindow"; + if (![view isKindOfClass:[QNSView class]]) return nil; - } return static_cast<QNSView *>(view); } @@ -235,13 +234,13 @@ QString qt_mac_applicationName() return appName; } -int qt_mac_mainScreenHeight() +int qt_mac_primaryScreenHeight() { QMacAutoReleasePool pool; NSArray *screens = [NSScreen screens]; if ([screens count] > 0) { - // The first screen in the screens array is documented - // to have the (0,0) origin. + // The first screen in the screens array is documented to + // have the (0,0) origin and is designated the primary screen. NSRect screenFrame = [[screens objectAtIndex: 0] frame]; return screenFrame.size.height; } @@ -250,12 +249,12 @@ int qt_mac_mainScreenHeight() int qt_mac_flipYCoordinate(int y) { - return qt_mac_mainScreenHeight() - y; + return qt_mac_primaryScreenHeight() - y; } qreal qt_mac_flipYCoordinate(qreal y) { - return qt_mac_mainScreenHeight() - y; + return qt_mac_primaryScreenHeight() - y; } QPointF qt_mac_flipPoint(const NSPoint &p) diff --git a/src/plugins/platforms/cocoa/qcocoaintegration.h b/src/plugins/platforms/cocoa/qcocoaintegration.h index 32f6fe0af1..ecdd20c4dc 100644 --- a/src/plugins/platforms/cocoa/qcocoaintegration.h +++ b/src/plugins/platforms/cocoa/qcocoaintegration.h @@ -84,9 +84,18 @@ public: // ---------------------------------------------------- // Additional methods void setVirtualSiblings(const QList<QPlatformScreen *> &siblings) { m_siblings = siblings; } - NSScreen *osScreen() const; + NSScreen *nativeScreen() const; void updateGeometry(); + QPointF mapToNative(const QPointF &pos) const { return flipCoordinate(pos); } + QRectF mapToNative(const QRectF &rect) const { return flipCoordinate(rect); } + QPointF mapFromNative(const QPointF &pos) const { return flipCoordinate(pos); } + QRectF mapFromNative(const QRectF &rect) const { return flipCoordinate(rect); } + +private: + QPointF flipCoordinate(const QPointF &pos) const; + QRectF flipCoordinate(const QRectF &rect) const; + public: int m_screenIndex; QRect m_geometry; @@ -117,6 +126,7 @@ public: bool hasCapability(QPlatformIntegration::Capability cap) const Q_DECL_OVERRIDE; QPlatformWindow *createPlatformWindow(QWindow *window) const Q_DECL_OVERRIDE; + QPlatformWindow *createForeignWindow(QWindow *window, WId nativeHandle) const Q_DECL_OVERRIDE; #ifndef QT_NO_OPENGL QPlatformOpenGLContext *createPlatformOpenGLContext(QOpenGLContext *context) const Q_DECL_OVERRIDE; #endif @@ -144,7 +154,7 @@ public: QList<int> possibleKeys(const QKeyEvent *event) const Q_DECL_OVERRIDE; void updateScreens(); - QCocoaScreen *screenAtIndex(int index); + QCocoaScreen *screenForNSScreen(NSScreen *nsScreen); void setToolbar(QWindow *window, NSToolbar *toolbar); NSToolbar *toolbar(QWindow *window) const; diff --git a/src/plugins/platforms/cocoa/qcocoaintegration.mm b/src/plugins/platforms/cocoa/qcocoaintegration.mm index 18340f4ee1..91f408e5c2 100644 --- a/src/plugins/platforms/cocoa/qcocoaintegration.mm +++ b/src/plugins/platforms/cocoa/qcocoaintegration.mm @@ -69,8 +69,8 @@ static void initResources() QT_BEGIN_NAMESPACE -QCocoaScreen::QCocoaScreen(int screenIndex) : - QPlatformScreen(), m_screenIndex(screenIndex), m_refreshRate(60.0) +QCocoaScreen::QCocoaScreen(int screenIndex) + : QPlatformScreen(), m_screenIndex(screenIndex), m_refreshRate(60.0) { updateGeometry(); m_cursor = new QCocoaCursor; @@ -81,41 +81,65 @@ QCocoaScreen::~QCocoaScreen() delete m_cursor; } -NSScreen *QCocoaScreen::osScreen() const +NSScreen *QCocoaScreen::nativeScreen() const { NSArray *screens = [NSScreen screens]; - return ((NSUInteger)m_screenIndex < [screens count]) ? [screens objectAtIndex:m_screenIndex] : nil; + + // Stale reference, screen configuration has changed + if (m_screenIndex < 0 || (NSUInteger)m_screenIndex >= [screens count]) + return nil; + + return [screens objectAtIndex:m_screenIndex]; +} + +/*! + Flips the Y coordinate of the point between quadrant I and IV. + + The native coordinate system on macOS uses quadrant I, with origin + in bottom left, and Qt uses quadrant IV, with origin in top left. + + By flippig the Y coordinate, we can map the position between the + two coordinate systems. +*/ +QPointF QCocoaScreen::flipCoordinate(const QPointF &pos) const +{ + return QPointF(pos.x(), m_geometry.height() - pos.y()); +} + +/*! + Flips the Y coordinate of the rectangle between quadrant I and IV. + + The native coordinate system on macOS uses quadrant I, with origin + in bottom left, and Qt uses quadrant IV, with origin in top left. + + By flippig the Y coordinate, we can map the rectangle between the + two coordinate systems. +*/ +QRectF QCocoaScreen::flipCoordinate(const QRectF &rect) const +{ + return QRectF(flipCoordinate(rect.topLeft() + QPoint(0, rect.height())), rect.size()); } void QCocoaScreen::updateGeometry() { - NSScreen *nsScreen = osScreen(); + NSScreen *nsScreen = nativeScreen(); if (!nsScreen) return; - NSRect frameRect = [nsScreen frame]; + // At this point the geometry is in native coordinates, but the size + // is correct, which we take advantage of next when we map the native + // coordinates to the Qt coordinate system. + m_geometry = QRectF::fromCGRect(NSRectToCGRect(nsScreen.frame)).toRect(); + m_availableGeometry = QRectF::fromCGRect(NSRectToCGRect(nsScreen.visibleFrame)).toRect(); - if (m_screenIndex == 0) { - m_geometry = QRect(frameRect.origin.x, frameRect.origin.y, frameRect.size.width, frameRect.size.height); - // This is the primary screen, the one that contains the menubar. Its origin should be - // (0, 0), and it's the only one whose available geometry differs from its full geometry. - NSRect visibleRect = [nsScreen visibleFrame]; - m_availableGeometry = QRect(visibleRect.origin.x, - frameRect.size.height - (visibleRect.origin.y + visibleRect.size.height), // invert y - visibleRect.size.width, visibleRect.size.height); - } else { - // NSScreen origin is at the bottom-left corner, QScreen is at the top-left corner. - // When we get the NSScreen frame rect, we need to re-align its origin y coordinate - // w.r.t. the primary screen, whose origin is (0, 0). - NSRect r = [[[NSScreen screens] objectAtIndex:0] frame]; - QRect referenceScreenGeometry = QRect(r.origin.x, r.origin.y, r.size.width, r.size.height); - m_geometry = QRect(frameRect.origin.x, - referenceScreenGeometry.height() - (frameRect.origin.y + frameRect.size.height), - frameRect.size.width, frameRect.size.height); - - // Not primary screen. See above. - m_availableGeometry = m_geometry; - } + // The reference screen for the geometry is always the primary screen, but since + // we may be in the process of creating and registering the primary screen, we + // must special-case that and assign it direcly. + QCocoaScreen *primaryScreen = (nsScreen == [[NSScreen screens] firstObject]) ? + this : static_cast<QCocoaScreen*>(QGuiApplication::primaryScreen()->handle()); + + m_geometry = primaryScreen->mapFromNative(m_geometry).toRect(); + m_availableGeometry = primaryScreen->mapFromNative(m_availableGeometry).toRect(); m_format = QImage::Format_RGB32; m_depth = NSBitsPerPixelFromDepth([nsScreen depth]); @@ -147,8 +171,8 @@ void QCocoaScreen::updateGeometry() qreal QCocoaScreen::devicePixelRatio() const { QMacAutoReleasePool pool; - NSScreen * screen = osScreen(); - return qreal(screen ? [screen backingScaleFactor] : 1.0); + NSScreen *nsScreen = nativeScreen(); + return qreal(nsScreen ? [nsScreen backingScaleFactor] : 1.0); } QPlatformScreen::SubpixelAntialiasingType QCocoaScreen::subpixelAntialiasingTypeHint() const @@ -427,7 +451,7 @@ void QCocoaIntegration::updateScreens() // NSScreen documentation says do not cache the array returned from [NSScreen screens]. // However in practice, we can identify a screen by its pointer: if resolution changes, // the NSScreen object will be the same instance, just with different values. - if (existingScr->osScreen() == scr) { + if (existingScr->nativeScreen() == scr) { screen = existingScr; break; } @@ -451,20 +475,27 @@ void QCocoaIntegration::updateScreens() // Now the leftovers in remainingScreens are no longer current, so we can delete them. foreach (QCocoaScreen* screen, remainingScreens) { mScreens.removeOne(screen); + // Prevent stale references to NSScreen during destroy + screen->m_screenIndex = -1; destroyScreen(screen); } } -QCocoaScreen *QCocoaIntegration::screenAtIndex(int index) +QCocoaScreen *QCocoaIntegration::screenForNSScreen(NSScreen *nsScreen) { - if (index >= mScreens.count()) + NSUInteger index = [[NSScreen screens] indexOfObject:nsScreen]; + if (index == NSNotFound) + return 0; + + if (index >= unsigned(mScreens.count())) updateScreens(); - // It is possible that the screen got removed while updateScreens was called - // so we do a sanity check to be certain - if (index >= mScreens.count()) - return 0; - return mScreens.at(index); + for (QCocoaScreen *screen : mScreens) { + if (screen->nativeScreen() == nsScreen) + return screen; + } + + return 0; } bool QCocoaIntegration::hasCapability(QPlatformIntegration::Capability cap) const @@ -493,6 +524,11 @@ QPlatformWindow *QCocoaIntegration::createPlatformWindow(QWindow *window) const return new QCocoaWindow(window); } +QPlatformWindow *QCocoaIntegration::createForeignWindow(QWindow *window, WId nativeHandle) const +{ + return new QCocoaWindow(window, nativeHandle); +} + #ifndef QT_NO_OPENGL QPlatformOpenGLContext *QCocoaIntegration::createPlatformOpenGLContext(QOpenGLContext *context) const { diff --git a/src/plugins/platforms/cocoa/qcocoamenu.mm b/src/plugins/platforms/cocoa/qcocoamenu.mm index e177a24e73..8e47974d12 100644 --- a/src/plugins/platforms/cocoa/qcocoamenu.mm +++ b/src/plugins/platforms/cocoa/qcocoamenu.mm @@ -581,8 +581,8 @@ void QCocoaMenu::showPopup(const QWindow *parentWindow, const QRect &targetRect, // The calls above block, and also swallow any mouse release event, // so we need to clear any mouse button that triggered the menu popup. - if ([view isKindOfClass:[QNSView class]]) - [(QNSView *)view resetMouseButtons]; + if (!cocoaWindow->isForeignWindow()) + [qnsview_cast(view) resetMouseButtons]; } void QCocoaMenu::dismiss() diff --git a/src/plugins/platforms/cocoa/qcocoanativeinterface.mm b/src/plugins/platforms/cocoa/qcocoanativeinterface.mm index 972230349b..26ab07ffaf 100644 --- a/src/plugins/platforms/cocoa/qcocoanativeinterface.mm +++ b/src/plugins/platforms/cocoa/qcocoanativeinterface.mm @@ -172,11 +172,12 @@ void *QCocoaNativeInterface::NSPrintInfoForPrintEngine(QPrintEngine *printEngine QPixmap QCocoaNativeInterface::defaultBackgroundPixmapForQWizard() { - QCFType<CFURLRef> url; const int ExpectedImageWidth = 242; const int ExpectedImageHeight = 414; - if (LSFindApplicationForInfo(kLSUnknownCreator, CFSTR("com.apple.KeyboardSetupAssistant"), - 0, 0, &url) == noErr) { + QCFType<CFArrayRef> urls = LSCopyApplicationURLsForBundleIdentifier( + CFSTR("com.apple.KeyboardSetupAssistant"), nullptr); + if (urls && CFArrayGetCount(urls) > 0) { + CFURLRef url = (CFURLRef)CFArrayGetValueAtIndex(urls, 0); QCFType<CFBundleRef> bundle = CFBundleCreate(kCFAllocatorDefault, url); if (bundle) { url = CFBundleCopyResourceURL(bundle, CFSTR("Background"), CFSTR("png"), 0); diff --git a/src/plugins/platforms/cocoa/qcocoaresources.qrc b/src/plugins/platforms/cocoa/qcocoaresources.qrc index 4255bfba9d..1c4b941b9b 100644 --- a/src/plugins/platforms/cocoa/qcocoaresources.qrc +++ b/src/plugins/platforms/cocoa/qcocoaresources.qrc @@ -1,12 +1,7 @@ -<!DOCTYPE RCC><RCC version="1.0"> -<qresource prefix="/qt-project.org/mac/cursors"> -<file>images/copyarrowcursor.png</file> -<file>images/forbiddencursor.png</file> -<file>images/spincursor.png</file> -<file>images/waitcursor.png</file> -<file>images/sizeallcursor.png</file> -</qresource> -<qresource prefix="/qt-project.org/mac/style"> -<file>images/leopard-unified-toolbar-on.png</file> -</qresource> +<RCC> + <qresource prefix="/qt-project.org/mac/cursors"> + <file>images/spincursor.png</file> + <file>images/waitcursor.png</file> + <file>images/sizeallcursor.png</file> + </qresource> </RCC> diff --git a/src/plugins/platforms/cocoa/qcocoatheme.h b/src/plugins/platforms/cocoa/qcocoatheme.h index d47e620fbb..27c071a8cd 100644 --- a/src/plugins/platforms/cocoa/qcocoatheme.h +++ b/src/plugins/platforms/cocoa/qcocoatheme.h @@ -74,6 +74,7 @@ public: QVariant themeHint(ThemeHint hint) const Q_DECL_OVERRIDE; QString standardButtonText(int button) const Q_DECL_OVERRIDE; + QKeySequence standardButtonShortcut(int button) const Q_DECL_OVERRIDE; static const char *name; diff --git a/src/plugins/platforms/cocoa/qcocoatheme.mm b/src/plugins/platforms/cocoa/qcocoatheme.mm index 4d74c11581..d2345f9abc 100644 --- a/src/plugins/platforms/cocoa/qcocoatheme.mm +++ b/src/plugins/platforms/cocoa/qcocoatheme.mm @@ -344,6 +344,12 @@ QString QCocoaTheme::standardButtonText(int button) const return button == QPlatformDialogHelper::Discard ? msgDialogButtonDiscard() : QPlatformTheme::standardButtonText(button); } +QKeySequence QCocoaTheme::standardButtonShortcut(int button) const +{ + return button == QPlatformDialogHelper::Discard ? QKeySequence(Qt::CTRL | Qt::Key_Delete) + : QPlatformTheme::standardButtonShortcut(button); +} + QPlatformMenuItem *QCocoaTheme::createPlatformMenuItem() const { return new QCocoaMenuItem(); diff --git a/src/plugins/platforms/cocoa/qcocoawindow.h b/src/plugins/platforms/cocoa/qcocoawindow.h index 16639fd8b1..567eb7438b 100644 --- a/src/plugins/platforms/cocoa/qcocoawindow.h +++ b/src/plugins/platforms/cocoa/qcocoawindow.h @@ -96,6 +96,7 @@ QT_NAMESPACE_ALIAS_OBJC_CLASS(QNSWindowHelper); @property (nonatomic, readonly) QNSWindowHelper *helper; - (id)initWithContentRect:(NSRect)contentRect + screen:(NSScreen*)screen styleMask:(NSUInteger)windowStyle qPlatformWindow:(QCocoaWindow *)qpw; @@ -111,6 +112,7 @@ QT_NAMESPACE_ALIAS_OBJC_CLASS(QNSWindow); @property (nonatomic, readonly) QNSWindowHelper *helper; - (id)initWithContentRect:(NSRect)contentRect + screen:(NSScreen*)screen styleMask:(NSUInteger)windowStyle qPlatformWindow:(QCocoaWindow *)qpw; @@ -141,13 +143,20 @@ QT_BEGIN_NAMESPACE // See the qt_on_cocoa manual tests for a working example, located // in tests/manual/cocoa at the time of writing. +#ifdef Q_MOC_RUN +#define Q_NOTIFICATION_HANDLER(notification) Q_INVOKABLE Q_COCOA_NOTIFICATION_##notification +#else +#define Q_NOTIFICATION_HANDLER(notification) +#define Q_NOTIFICATION_PREFIX QT_STRINGIFY2(Q_COCOA_NOTIFICATION_) +#endif + class QCocoaMenuBar; class QCocoaWindow : public QObject, public QPlatformWindow { Q_OBJECT public: - QCocoaWindow(QWindow *tlw); + QCocoaWindow(QWindow *tlw, WId nativeHandle = 0); ~QCocoaWindow(); void setGeometry(const QRect &rect) Q_DECL_OVERRIDE; @@ -177,6 +186,8 @@ public: QMargins frameMargins() const Q_DECL_OVERRIDE; QSurfaceFormat format() const Q_DECL_OVERRIDE; + bool isForeignWindow() const Q_DECL_OVERRIDE; + void requestActivateWindow() Q_DECL_OVERRIDE; WId winId() const Q_DECL_OVERRIDE; @@ -187,15 +198,30 @@ public: void setEmbeddedInForeignView(bool subwindow); - void windowWillMove(); - void windowDidMove(); - void windowDidResize(); - void windowDidEndLiveResize(); + 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(); + Q_NOTIFICATION_HANDLER(NSWindowDidMiniaturizeNotification) void windowDidMiniaturize(); + Q_NOTIFICATION_HANDLER(NSWindowDidDeminiaturizeNotification) void windowDidDeminiaturize(); + Q_NOTIFICATION_HANDLER(NSWindowWillEnterFullScreenNotification) void windowWillEnterFullScreen(); + 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(NSWindowDidChangeOcclusionStateNotification) void windowDidChangeOcclusionState(); + Q_NOTIFICATION_HANDLER(NSWindowDidChangeScreenNotification) void windowDidChangeScreen(); + Q_NOTIFICATION_HANDLER(NSWindowWillCloseNotification) void windowWillClose(); + bool windowShouldClose(); - void windowWillClose(); bool windowIsPopupType(Qt::WindowType type = Qt::Widget) const; - void setSynchedWindowStateFromWindow(); + void reportCurrentWindowState(bool unconditionally = false); NSInteger windowLevel(Qt::WindowFlags flags); NSUInteger windowStyleMask(Qt::WindowFlags flags); @@ -239,19 +265,37 @@ public: static QPoint bottomLeftClippedByNSWindowOffsetStatic(QWindow *window); QPoint bottomLeftClippedByNSWindowOffset() const; + + enum RecreationReason { + RecreationNotNeeded = 0, + ParentChanged = 0x1, + MissingWindow = 0x2, + WindowModalityChanged = 0x4, + ChildNSWindowChanged = 0x8, + ContentViewChanged = 0x10, + PanelChanged = 0x20, + }; + Q_DECLARE_FLAGS(RecreationReasons, RecreationReason) + Q_FLAG(RecreationReasons) + protected: - void recreateWindow(const QPlatformWindow *parentWindow); - QCocoaNSWindow *createNSWindow(); - void setNSWindow(QCocoaNSWindow *window); + bool isChildNSWindow() const; + bool isContentView() const; + + void foreachChildNSWindow(void (^block)(QCocoaWindow *)); - bool shouldUseNSPanel(); + void recreateWindowIfNeeded(); + QCocoaNSWindow *createNSWindow(bool shouldBeChildNSWindow, bool shouldBePanel); QRect nativeWindowGeometry() const; - QCocoaWindow *parentCocoaWindow() const; - void syncWindowState(Qt::WindowState newState); void reinsertChildWindow(QCocoaWindow *child); void removeChildWindow(QCocoaWindow *child); - bool isNativeWindowTypeInconsistent(); + + Qt::WindowState windowState() const; + void applyWindowState(Qt::WindowState newState); + void toggleMaximized(); + void toggleFullScreen(); + bool isTransitioningToFullScreen() const; // private: public: // for QNSView @@ -268,13 +312,8 @@ public: // for QNSView bool m_viewIsEmbedded; // true if the m_view is actually embedded in a "foreign" NSView hiearchy bool m_viewIsToBeEmbedded; // true if the m_view is intended to be embedded in a "foreign" NSView hiearchy - QCocoaWindow *m_parentCocoaWindow; - bool m_isNSWindowChild; // this window is a non-top level QWindow with a NSWindow. - QList<QCocoaWindow *> m_childWindows; - Qt::WindowFlags m_windowFlags; - bool m_effectivelyMaximized; - Qt::WindowState m_synchedWindowState; + Qt::WindowState m_lastReportedWindowState; Qt::WindowModality m_windowModality; QPointer<QWindow> m_enterLeaveTargetWindow; bool m_windowUnderMouse; @@ -308,11 +347,6 @@ public: // for QNSView int m_topContentBorderThickness; int m_bottomContentBorderThickness; - // used by showFullScreen in fake mode - QRect m_normalGeometry; - Qt::WindowFlags m_oldWindowFlags; - NSApplicationPresentationOptions m_presentationOptions; - struct BorderRange { BorderRange(quintptr i, int u, int l) : identifier(i), upper(u), lower(l) { } quintptr identifier; 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()); } diff --git a/src/plugins/platforms/cocoa/qnsview.h b/src/plugins/platforms/cocoa/qnsview.h index f226547b90..75a508370f 100644 --- a/src/plugins/platforms/cocoa/qnsview.h +++ b/src/plugins/platforms/cocoa/qnsview.h @@ -74,7 +74,6 @@ Q_FORWARD_DECLARE_OBJC_CLASS(QT_MANGLE_NAMESPACE(QNSViewMouseMoveHelper)); QStringList *currentCustomDragTypes; bool m_sendUpAsRightButton; Qt::KeyboardModifiers currentWheelModifiers; - bool m_subscribesForGlobalFrameNotifications; #ifndef QT_NO_OPENGL QCocoaGLContext *m_glContext; bool m_shouldSetGLContextinDrawRect; @@ -101,9 +100,6 @@ Q_FORWARD_DECLARE_OBJC_CLASS(QT_MANGLE_NAMESPACE(QNSViewMouseMoveHelper)); - (void)drawRect:(NSRect)dirtyRect; - (void)drawBackingStoreUsingCoreGraphics:(NSRect)dirtyRect; - (void)updateGeometry; -- (void)notifyWindowStateChanged:(Qt::WindowState)newState; -- (void)windowNotification : (NSNotification *) windowNotification; -- (void)notifyWindowWillZoom:(BOOL)willZoom; - (void)textInputContextKeyboardSelectionDidChangeNotification : (NSNotification *) textInputContextKeyboardSelectionDidChangeNotification; - (void)viewDidHide; - (void)viewDidUnhide; @@ -152,6 +148,11 @@ Q_FORWARD_DECLARE_OBJC_CLASS(QT_MANGLE_NAMESPACE(QNSViewMouseMoveHelper)); @end +@interface QT_MANGLE_NAMESPACE(QNSView) (QtExtras) +@property (nonatomic, readonly) QCocoaWindow *platformWindow; +@property (nonatomic, readonly) BOOL isMenuView; +@end + QT_NAMESPACE_ALIAS_OBJC_CLASS(QNSView); #endif //QNSVIEW_H diff --git a/src/plugins/platforms/cocoa/qnsview.mm b/src/plugins/platforms/cocoa/qnsview.mm index 970ce9e785..fbf4f99f2e 100644 --- a/src/plugins/platforms/cocoa/qnsview.mm +++ b/src/plugins/platforms/cocoa/qnsview.mm @@ -149,7 +149,6 @@ static bool _q_dontOverrideCtrlLMB = false; m_acceptedMouseDowns = Qt::NoButton; m_frameStrutButtons = Qt::NoButton; m_sendKeyEvent = false; - m_subscribesForGlobalFrameNotifications = false; #ifndef QT_NO_OPENGL m_glContext = 0; m_shouldSetGLContextinDrawRect = false; @@ -181,7 +180,6 @@ static bool _q_dontOverrideCtrlLMB = false; CGImageRelease(m_maskImage); [m_trackingArea release]; m_maskImage = 0; - m_subscribesForGlobalFrameNotifications = false; [m_inputSource release]; [[NSNotificationCenter defaultCenter] removeObserver:self]; [m_mouseMoveHelper release]; @@ -217,11 +215,6 @@ static bool _q_dontOverrideCtrlLMB = false; #endif [self registerDragTypes]; - [self setPostsFrameChangedNotifications : YES]; - [[NSNotificationCenter defaultCenter] addObserver:self - selector:@selector(updateGeometry) - name:NSViewFrameDidChangeNotification - object:self]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(textInputContextKeyboardSelectionDidChangeNotification:) @@ -240,27 +233,9 @@ static bool _q_dontOverrideCtrlLMB = false; //was unable to set view m_shouldSetGLContextinDrawRect = true; } - - if (!m_subscribesForGlobalFrameNotifications) { - // NSOpenGLContext expects us to repaint (or update) the view when - // it changes position on screen. Since this happens unnoticed for - // the view when the parent view moves, we need to register a special - // notification that lets us handle this case: - m_subscribesForGlobalFrameNotifications = true; - [[NSNotificationCenter defaultCenter] addObserver:self - selector:@selector(globalFrameChanged:) - name:NSViewGlobalFrameDidChangeNotification - object:self]; - } } #endif -- (void) globalFrameChanged:(NSNotification*)notification -{ - Q_UNUSED(notification); - m_platformWindow->updateExposedGeometry(); -} - - (void)viewDidMoveToSuperview { if (!(m_platformWindow->m_viewIsToBeEmbedded)) @@ -281,22 +256,6 @@ static bool _q_dontOverrideCtrlLMB = false; m_backingStore = Q_NULLPTR; } -- (void)viewWillMoveToWindow:(NSWindow *)newWindow -{ - // ### Merge "normal" window code path with this one for 5.1. - if (!(m_platformWindow->window()->type() & Qt::SubWindow)) - return; - - if (newWindow) { - [[NSNotificationCenter defaultCenter] addObserver:self - selector:@selector(windowNotification:) - name:nil // Get all notifications - object:newWindow]; - } - if ([self window]) - [[NSNotificationCenter defaultCenter] removeObserver:self name:nil object:[self window]]; -} - - (QWindow *)topLevelWindow { QWindow *focusWindow = m_platformWindow->window(); @@ -316,7 +275,7 @@ static bool _q_dontOverrideCtrlLMB = false; { QRect geometry; - if (m_platformWindow->m_isNSWindowChild) { + if (self.window.parentWindow) { return; #if 0 //geometry = QRectF::fromCGRect([self frame]).toRect(); @@ -336,10 +295,10 @@ static bool _q_dontOverrideCtrlLMB = false; geometry = QRect(windowRect.origin.x, qt_mac_flipYCoordinate(windowRect.origin.y + rect.size.height), rect.size.width, rect.size.height); } else if (m_platformWindow->m_viewIsToBeEmbedded) { // embedded child window, use the frame rect ### merge with case below - geometry = QRectF::fromCGRect([self bounds]).toRect(); + geometry = QRectF::fromCGRect(NSRectToCGRect([self bounds])).toRect(); } else { // child window, use the frame rect - geometry = QRectF::fromCGRect([self frame]).toRect(); + geometry = QRectF::fromCGRect(NSRectToCGRect([self frame])).toRect(); } if (m_platformWindow->m_nsWindow && geometry == m_platformWindow->geometry()) @@ -380,79 +339,6 @@ static bool _q_dontOverrideCtrlLMB = false; } } -- (void)notifyWindowStateChanged:(Qt::WindowState)newState -{ - // If the window was maximized, then fullscreen, then tried to go directly to "normal" state, - // this notification will say that it is "normal", but it will still look maximized, and - // if you called performZoom it would actually take it back to "normal". - // So we should say that it is maximized because it actually is. - if (newState == Qt::WindowNoState && m_platformWindow->m_effectivelyMaximized) - newState = Qt::WindowMaximized; - QWindowSystemInterface::handleWindowStateChanged(m_platformWindow->window(), newState); - // We want to read the window state back from the window, - // but the event we just sent may be asynchronous. - QWindowSystemInterface::flushWindowSystemEvents(); - m_platformWindow->setSynchedWindowStateFromWindow(); -} - -- (void)windowNotification : (NSNotification *) windowNotification -{ - //qDebug() << "windowNotification" << QString::fromNSString([windowNotification name]); - - NSString *notificationName = [windowNotification name]; - if (notificationName == NSWindowDidBecomeKeyNotification) { - if (!m_platformWindow->windowIsPopupType() && !m_isMenuView) - QWindowSystemInterface::handleWindowActivated(m_platformWindow->window()); - } else if (notificationName == NSWindowDidResignKeyNotification) { - // key window will be non-nil if another window became key... 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 == windowNotification.object) { - // no new key window, go ahead and set the active window to zero - if (!m_platformWindow->windowIsPopupType() && !m_isMenuView) - QWindowSystemInterface::handleWindowActivated(0); - } - } else if (notificationName == NSWindowDidMiniaturizeNotification - || notificationName == NSWindowDidDeminiaturizeNotification) { - Qt::WindowState newState = notificationName == NSWindowDidMiniaturizeNotification ? - Qt::WindowMinimized : Qt::WindowNoState; - [self notifyWindowStateChanged:newState]; - } else if ([notificationName isEqualToString: @"NSWindowDidOrderOffScreenNotification"]) { - m_platformWindow->obscureWindow(); - } else if ([notificationName isEqualToString: @"NSWindowDidOrderOnScreenAndFinishAnimatingNotification"]) { - m_platformWindow->exposeWindow(); - } else if ([notificationName isEqualToString:NSWindowDidChangeOcclusionStateNotification]) { - // 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)[self.window occlusionState] & NSWindowOcclusionStateVisible) { - m_platformWindow->exposeWindow(); - } else { - // Send Obscure events on window occlusion to stop animations. - m_platformWindow->obscureWindow(); - } - } - } else if (notificationName == NSWindowDidChangeScreenNotification) { - if (m_platformWindow->window()) { - NSUInteger screenIndex = [[NSScreen screens] indexOfObject:self.window.screen]; - if (screenIndex != NSNotFound) { - QCocoaScreen *cocoaScreen = QCocoaIntegration::instance()->screenAtIndex(screenIndex); - if (cocoaScreen) - QWindowSystemInterface::handleWindowScreenChanged(m_platformWindow->window(), cocoaScreen->screen()); - m_platformWindow->updateExposedGeometry(); - } - } - } else if (notificationName == NSWindowDidEnterFullScreenNotification - || notificationName == NSWindowDidExitFullScreenNotification) { - Qt::WindowState newState = notificationName == NSWindowDidEnterFullScreenNotification ? - Qt::WindowFullScreen : Qt::WindowNoState; - [self notifyWindowStateChanged:newState]; - } -} - - (void)textInputContextKeyboardSelectionDidChangeNotification : (NSNotification *) textInputContextKeyboardSelectionDidChangeNotification { Q_UNUSED(textInputContextKeyboardSelectionDidChangeNotification) @@ -462,14 +348,6 @@ static bool _q_dontOverrideCtrlLMB = false; } } -- (void)notifyWindowWillZoom:(BOOL)willZoom -{ - Qt::WindowState newState = willZoom ? Qt::WindowMaximized : Qt::WindowNoState; - if (!willZoom) - m_platformWindow->m_effectivelyMaximized = false; - [self notifyWindowStateChanged:newState]; -} - - (void)viewDidHide { m_platformWindow->obscureWindow(); @@ -553,7 +431,7 @@ static bool _q_dontOverrideCtrlLMB = false; - (void) drawRect:(NSRect)dirtyRect { - qCDebug(lcQpaCocoaWindow) << "[QNSView drawRect:]" << m_platformWindow->window() << QRectF::fromCGRect(dirtyRect); + qCDebug(lcQpaCocoaWindow) << "[QNSView drawRect:]" << m_platformWindow->window() << QRectF::fromCGRect(NSRectToCGRect(dirtyRect)); #ifndef QT_NO_OPENGL if (m_glContext && m_shouldSetGLContextinDrawRect) { @@ -1333,7 +1211,7 @@ static QTabletEvent::TabletDevice wacomTabletDevice(NSEvent *theEvent) - (bool)handleGestureAsBeginEnd:(NSEvent *)event { - if (QSysInfo::QSysInfo::MacintoshVersion < QSysInfo::MV_10_11) + if (QOperatingSystemVersion::current() < QOperatingSystemVersion::OSXElCapitan) return false; if ([event phase] == NSEventPhaseBegan) { @@ -2199,3 +2077,17 @@ static QPoint mapWindowCoordinates(QWindow *source, QWindow *target, QPoint poin } @end + +@implementation QT_MANGLE_NAMESPACE(QNSView) (QtExtras) + +- (QCocoaWindow*)platformWindow +{ + return m_platformWindow.data();; +} + +- (BOOL)isMenuView +{ + return m_isMenuView; +} + +@end diff --git a/src/plugins/platforms/cocoa/qnswindowdelegate.h b/src/plugins/platforms/cocoa/qnswindowdelegate.h index f29aa97b68..d2078b5786 100644 --- a/src/plugins/platforms/cocoa/qnswindowdelegate.h +++ b/src/plugins/platforms/cocoa/qnswindowdelegate.h @@ -49,15 +49,9 @@ QCocoaWindow *m_cocoaWindow; } -- (id)initWithQCocoaWindow: (QCocoaWindow *) cocoaWindow; +- (id)initWithQCocoaWindow:(QCocoaWindow *)cocoaWindow; -- (void)windowDidBecomeKey:(NSNotification *)notification; -- (void)windowDidResize:(NSNotification *)notification; -- (void)windowDidMove:(NSNotification *)notification; -- (void)windowWillMove:(NSNotification *)notification; - (BOOL)windowShouldClose:(NSNotification *)notification; -- (BOOL)windowShouldZoom:(NSWindow *)window toFrame:(NSRect)newFrame; -- (void)windowWillClose:(NSNotification *)notification; - (BOOL)window:(NSWindow *)window shouldPopUpDocumentPathMenu:(NSMenu *)menu; - (BOOL)window:(NSWindow *)window shouldDragDocumentWithEvent:(NSEvent *)event from:(NSPoint)dragImageLocation withPasteboard:(NSPasteboard *)pasteboard; diff --git a/src/plugins/platforms/cocoa/qnswindowdelegate.mm b/src/plugins/platforms/cocoa/qnswindowdelegate.mm index 7f988ac963..ce74aa9973 100644 --- a/src/plugins/platforms/cocoa/qnswindowdelegate.mm +++ b/src/plugins/platforms/cocoa/qnswindowdelegate.mm @@ -41,61 +41,17 @@ #include "qcocoahelpers.h" #include <QDebug> +#include <qpa/qplatformscreen.h> #include <qpa/qwindowsysteminterface.h> @implementation QNSWindowDelegate -- (id) initWithQCocoaWindow: (QCocoaWindow *) cocoaWindow +- (id)initWithQCocoaWindow:(QCocoaWindow *)cocoaWindow { - self = [super init]; - - if (self) { + if (self = [super init]) m_cocoaWindow = cocoaWindow; - } - return self; -} - -- (void)windowDidBecomeKey:(NSNotification *)notification -{ - Q_UNUSED(notification); - if (m_cocoaWindow->m_windowUnderMouse) { - QPointF windowPoint; - QPointF screenPoint; - [qnsview_cast(m_cocoaWindow->view()) convertFromScreen:[NSEvent mouseLocation] toWindowPoint:&windowPoint andScreenPoint:&screenPoint]; - QWindowSystemInterface::handleEnterEvent(m_cocoaWindow->m_enterLeaveTargetWindow, windowPoint, screenPoint); - } -} -- (void)windowDidResize:(NSNotification *)notification -{ - Q_UNUSED(notification); - if (m_cocoaWindow) { - m_cocoaWindow->windowDidResize(); - } -} - -- (void)windowDidEndLiveResize:(NSNotification *)notification -{ - Q_UNUSED(notification); - if (m_cocoaWindow) { - m_cocoaWindow->windowDidEndLiveResize(); - } -} - -- (void)windowWillMove:(NSNotification *)notification -{ - Q_UNUSED(notification); - if (m_cocoaWindow) { - m_cocoaWindow->windowWillMove(); - } -} - -- (void)windowDidMove:(NSNotification *)notification -{ - Q_UNUSED(notification); - if (m_cocoaWindow) { - m_cocoaWindow->windowDidMove(); - } + return self; } - (BOOL)windowShouldClose:(NSNotification *)notification @@ -107,20 +63,19 @@ return YES; } - -- (BOOL)windowShouldZoom:(NSWindow *)window toFrame:(NSRect)newFrame +/*! + Overridden to ensure that the zoomed state always results in a maximized + window, which would otherwise not be the case for borderless windows. +*/ +- (NSRect)windowWillUseStandardFrame:(NSWindow *)window defaultFrame:(NSRect)newFrame { Q_UNUSED(newFrame); - if (m_cocoaWindow && m_cocoaWindow->window()->type() != Qt::ForeignWindow) - [qnsview_cast(m_cocoaWindow->view()) notifyWindowWillZoom:![window isZoomed]]; - return YES; -} -- (void)windowWillClose:(NSNotification *)notification -{ - Q_UNUSED(notification); - if (m_cocoaWindow) - m_cocoaWindow->windowWillClose(); + // We explicitly go through the QScreen API here instead of just using + // window.screen.visibleFrame directly, as that ensures we have the same + // behavior for both use-cases/APIs. + Q_ASSERT(window == m_cocoaWindow->nativeWindow()); + return m_cocoaWindow->screen()->availableGeometry().toCGRect(); } - (BOOL)window:(NSWindow *)window shouldPopUpDocumentPathMenu:(NSMenu *)menu |