diff options
Diffstat (limited to 'src/plugins/platforms/cocoa')
-rw-r--r-- | src/plugins/platforms/cocoa/qcocoabackingstore.mm | 3 | ||||
-rw-r--r-- | src/plugins/platforms/cocoa/qcocoadrag.mm | 2 | ||||
-rw-r--r-- | src/plugins/platforms/cocoa/qcocoafiledialoghelper.mm | 3 | ||||
-rw-r--r-- | src/plugins/platforms/cocoa/qcocoahelpers.mm | 14 | ||||
-rw-r--r-- | src/plugins/platforms/cocoa/qcocoaintegration.h | 17 | ||||
-rw-r--r-- | src/plugins/platforms/cocoa/qcocoaintegration.mm | 105 | ||||
-rw-r--r-- | src/plugins/platforms/cocoa/qcocoawindow.h | 60 | ||||
-rw-r--r-- | src/plugins/platforms/cocoa/qcocoawindow.mm | 567 | ||||
-rw-r--r-- | src/plugins/platforms/cocoa/qnsview.h | 7 | ||||
-rw-r--r-- | src/plugins/platforms/cocoa/qnsview.mm | 123 | ||||
-rw-r--r-- | src/plugins/platforms/cocoa/qnswindowdelegate.h | 7 | ||||
-rw-r--r-- | src/plugins/platforms/cocoa/qnswindowdelegate.mm | 58 |
12 files changed, 553 insertions, 413 deletions
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/qcocoadrag.mm b/src/plugins/platforms/cocoa/qcocoadrag.mm index 7d11023b78..de4fa95530 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/qcocoafiledialoghelper.mm b/src/plugins/platforms/cocoa/qcocoafiledialoghelper.mm index ef102990b4..7706f39198 100644 --- a/src/plugins/platforms/cocoa/qcocoafiledialoghelper.mm +++ b/src/plugins/platforms/cocoa/qcocoafiledialoghelper.mm @@ -62,6 +62,7 @@ #include <stdlib.h> #include <qabstracteventdispatcher.h> #include <qsysinfo.h> +#include <qoperatingsystemversion.h> #include <qglobal.h> #include <QDir> @@ -163,7 +164,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/qcocoahelpers.mm b/src/plugins/platforms/cocoa/qcocoahelpers.mm index c57567bdd6..b092d013c5 100644 --- a/src/plugins/platforms/cocoa/qcocoahelpers.mm +++ b/src/plugins/platforms/cocoa/qcocoahelpers.mm @@ -160,10 +160,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 +233,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 +248,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..5cf8e7d237 100644 --- a/src/plugins/platforms/cocoa/qcocoaintegration.h +++ b/src/plugins/platforms/cocoa/qcocoaintegration.h @@ -61,7 +61,7 @@ QT_BEGIN_NAMESPACE class QCocoaScreen : public QPlatformScreen { public: - QCocoaScreen(int screenIndex); + QCocoaScreen(NSScreen *screen); ~QCocoaScreen(); // ---------------------------------------------------- @@ -84,11 +84,20 @@ public: // ---------------------------------------------------- // Additional methods void setVirtualSiblings(const QList<QPlatformScreen *> &siblings) { m_siblings = siblings; } - NSScreen *osScreen() const; + NSScreen *nsScreen() 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; + NSScreen *m_nsScreen; QRect m_geometry; QRect m_availableGeometry; QDpi m_logicalDpi; @@ -144,7 +153,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 92fffb4d15..d48722b82f 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(NSScreen *screen) + : QPlatformScreen(), m_nsScreen(screen), m_refreshRate(60.0) { updateGeometry(); m_cursor = new QCocoaCursor; @@ -81,46 +81,63 @@ QCocoaScreen::~QCocoaScreen() delete m_cursor; } -NSScreen *QCocoaScreen::osScreen() const +NSScreen *QCocoaScreen::nsScreen() const { - NSArray *screens = [NSScreen screens]; - return ((NSUInteger)m_screenIndex < [screens count]) ? [screens objectAtIndex:m_screenIndex] : nil; + return m_nsScreen; +} + +/*! + 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(); - if (!nsScreen) + if (!m_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(m_nsScreen.frame).toRect(); + m_availableGeometry = QRectF::fromCGRect(m_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 = (m_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]); + m_depth = NSBitsPerPixelFromDepth([m_nsScreen depth]); - NSDictionary *devDesc = [nsScreen deviceDescription]; + NSDictionary *devDesc = [m_nsScreen deviceDescription]; CGDirectDisplayID dpy = [[devDesc objectForKey:@"NSScreenNumber"] unsignedIntValue]; CGSize size = CGDisplayScreenSize(dpy); m_physicalSize = QSizeF(size.width, size.height); @@ -147,8 +164,7 @@ void QCocoaScreen::updateGeometry() qreal QCocoaScreen::devicePixelRatio() const { QMacAutoReleasePool pool; - NSScreen * screen = osScreen(); - return qreal(screen ? [screen backingScaleFactor] : 1.0); + return qreal(m_nsScreen ? [m_nsScreen backingScaleFactor] : 1.0); } QPlatformScreen::SubpixelAntialiasingType QCocoaScreen::subpixelAntialiasingTypeHint() const @@ -423,7 +439,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->nsScreen() == scr) { screen = existingScr; break; } @@ -431,7 +447,7 @@ void QCocoaIntegration::updateScreens() remainingScreens.remove(screen); screen->updateGeometry(); } else { - screen = new QCocoaScreen(i); + screen = new QCocoaScreen(scr); mScreens.append(screen); screenAdded(screen); } @@ -451,16 +467,21 @@ void QCocoaIntegration::updateScreens() } } -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->nsScreen() == nsScreen) + return screen; + } + + return 0; } bool QCocoaIntegration::hasCapability(QPlatformIntegration::Capability cap) const diff --git a/src/plugins/platforms/cocoa/qcocoawindow.h b/src/plugins/platforms/cocoa/qcocoawindow.h index 2e9de03dfc..1cd4c1905a 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,6 +143,13 @@ 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 @@ -187,12 +196,25 @@ 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(NSWindowDidEnterFullScreenNotification) void windowDidEnterFullScreen(); + 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(); @@ -237,19 +259,33 @@ 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; - bool shouldUseNSPanel(); + void foreachChildNSWindow(void (^block)(QCocoaWindow *)); + + 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(); // private: public: // for QNSView @@ -266,10 +302,6 @@ 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; diff --git a/src/plugins/platforms/cocoa/qcocoawindow.mm b/src/plugins/platforms/cocoa/qcocoawindow.mm index e377d8f83d..dfaed9923e 100644 --- a/src/plugins/platforms/cocoa/qcocoawindow.mm +++ b/src/plugins/platforms/cocoa/qcocoawindow.mm @@ -130,8 +130,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 +201,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 +222,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 +241,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 +284,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,6 +344,63 @@ 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) @@ -351,8 +409,6 @@ QCocoaWindow::QCocoaWindow(QWindow *tlw) , m_nsWindow(0) , m_viewIsEmbedded(false) , m_viewIsToBeEmbedded(false) - , m_parentCocoaWindow(0) - , m_isNSWindowChild(false) , m_effectivelyMaximized(false) , m_synchedWindowState(Qt::WindowActive) , m_windowModality(Qt::NonModal) @@ -405,7 +461,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,12 +476,10 @@ 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(); @@ -440,10 +494,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]; @@ -491,7 +544,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); } @@ -512,9 +565,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); @@ -536,14 +589,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 +621,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 +646,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 +658,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 +681,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 +693,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. @@ -700,8 +754,9 @@ void QCocoaWindow::setVisible(bool visible) else [m_nsWindow orderFront:nil]; - foreach (QCocoaWindow *childWindow, m_childWindows) + foreachChildNSWindow(^(QCocoaWindow *childWindow) { childWindow->show(true); + }); } else { show(); } @@ -879,7 +934,7 @@ void QCocoaWindow::setWindowZoomButton(Qt::WindowFlags flags) 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 @@ -983,19 +1038,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 +1073,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 +1144,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 +1207,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 +1231,8 @@ void QCocoaWindow::setEmbeddedInForeignView(bool embedded) m_nsWindow = 0; } +// ----------------------- NSWindow notifications ----------------------- + void QCocoaWindow::windowWillMove() { // Close any open popups on window move @@ -1190,7 +1241,7 @@ void QCocoaWindow::windowWillMove() void QCocoaWindow::windowDidMove() { - if (m_isNSWindowChild) + if (isChildNSWindow()) return; [qnsview_cast(m_view) updateGeometry]; @@ -1201,13 +1252,30 @@ void QCocoaWindow::windowDidResize() if (!m_nsWindow) return; - if (m_isNSWindowChild) + if (isChildNSWindow()) return; clipChildWindows(); [qnsview_cast(m_view) updateGeometry]; } +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]) { @@ -1216,6 +1284,104 @@ void QCocoaWindow::windowDidEndLiveResize() } } +void QCocoaWindow::windowDidBecomeKey() +{ + if (window()->type() == Qt::ForeignWindow) + 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 (window()->type() == Qt::ForeignWindow) + 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() +{ + [qnsview_cast(m_view) notifyWindowStateChanged:Qt::WindowMinimized]; +} + +void QCocoaWindow::windowDidDeminiaturize() +{ + [qnsview_cast(m_view) notifyWindowStateChanged:Qt::WindowNoState]; +} + +void QCocoaWindow::windowDidEnterFullScreen() +{ + [qnsview_cast(m_view) notifyWindowStateChanged:Qt::WindowFullScreen]; +} + +void QCocoaWindow::windowDidExitFullScreen() +{ + [qnsview_cast(m_view) notifyWindowStateChanged:Qt::WindowNoState]; +} + +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,12 +1395,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() { @@ -1264,62 +1425,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 { - qCDebug(lcQpaCocoaWindow) << "QCocoaWindow::recreateWindow" << window() + 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 *)) +{ + 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; - QCocoaWindow *oldParentCocoaWindow = m_parentCocoaWindow; - m_parentCocoaWindow = const_cast<QCocoaWindow *>(static_cast<const QCocoaWindow *>(parentWindow)); - if (m_parentCocoaWindow && m_isNSWindowChild) { - QWindow *parentQWindow = m_parentCocoaWindow->window(); + 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); + + 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,7 +1570,13 @@ void QCocoaWindow::recreateWindow(const QPlatformWindow *parentWindow) setWindowFlags(window()->flags()); setWindowTitle(window()->title()); setWindowState(window()->windowState()); - } else if (m_isNSWindowChild) { + } 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 m_nsWindow.styleMask = NSBorderlessWindowMask; m_nsWindow.hasShadow = NO; m_nsWindow.level = NSNormalWindowLevel; @@ -1340,22 +1586,12 @@ void QCocoaWindow::recreateWindow(const QPlatformWindow *parentWindow) 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 { // 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. @@ -1381,11 +1617,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,26 +1641,39 @@ 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); + + QScreen *targetScreen = nullptr; + for (QScreen *screen : QGuiApplication::screens()) { + if (screen->geometry().contains(rect.topLeft())) { + targetScreen = screen; + break; + } + } + + if (!targetScreen) { + qCWarning(lcQpaCocoaWindow) << "Window position outside any known screen, using primary screen"; + targetScreen = QGuiApplication::primaryScreen(); + } + + rect.translate(-targetScreen->geometry().topLeft()); + QCocoaScreen *cocoaScreen = static_cast<QCocoaScreen *>(targetScreen->handle()); + NSRect frame = cocoaScreen->mapToNative(rect).toCGRect(); + + // 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. Qt::WindowType type = window()->type(); Qt::WindowFlags flags = window()->flags(); NSUInteger styleMask; - if (m_isNSWindowChild) { + if (shouldBeChildNSWindow) { styleMask = NSBorderlessWindowMask; } else { styleMask = windowStyleMask(flags); @@ -1427,32 +1682,28 @@ QCocoaNSWindow * QCocoaWindow::createNSWindow() // 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 (shouldBePanel) { + QNSPanel *panel = [[QNSPanel alloc] initWithContentRect:frame screen:cocoaScreen->nsScreen() + styleMask: styleMask qPlatformWindow:this]; + if ((type & Qt::Popup) == Qt::Popup) - [window setHasShadow:YES]; + [panel 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]; + [panel setHidesOnDeactivate: shouldHideOnDeactivate]; // Make popup windows show on the same desktop as the parent full-screen window. - [window setCollectionBehavior:NSWindowCollectionBehaviorFullScreenAuxiliary]; + [panel setCollectionBehavior:NSWindowCollectionBehaviorFullScreenAuxiliary]; if ((type & Qt::Popup) == Qt::Popup) - [window setAnimationBehavior:NSWindowAnimationBehaviorUtilityWindow]; + [panel setAnimationBehavior:NSWindowAnimationBehaviorUtilityWindow]; - createdWindow = window; + createdWindow = panel; } else { - QNSWindow *window; - window = [[QNSWindow alloc] initWithContentRect:frame - styleMask: styleMask - qPlatformWindow:this]; - createdWindow = window; + createdWindow = [[QNSWindow alloc] initWithContentRect:frame screen:cocoaScreen->nsScreen() + styleMask: styleMask qPlatformWindow:this]; } if ([createdWindow respondsToSelector:@selector(setRestorable:)]) @@ -1479,36 +1730,6 @@ QCocoaNSWindow * QCocoaWindow::createNSWindow() return createdWindow; } -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]; - } -} - -void QCocoaWindow::removeChildWindow(QCocoaWindow *child) -{ - m_childWindows.removeOne(child); - [m_nsWindow removeChildWindow:child->m_nsWindow]; -} - -bool QCocoaWindow::isNativeWindowTypeInconsistent() -{ - if (!m_nsWindow) - return false; - - const bool isPanel = [m_nsWindow isKindOfClass:[QNSPanel class]]; - const bool usePanel = shouldUseNSPanel(); - - return isPanel != usePanel; -} - void QCocoaWindow::removeMonitor() { if (!monitor) @@ -1520,7 +1741,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]; @@ -1797,12 +2018,8 @@ void QCocoaWindow::exposeWindow() // 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) - window()->setScreen(cocoaScreen->screen()); - } + if (QCocoaScreen *cocoaScreen = QCocoaIntegration::instance()->screenForNSScreen(m_nsWindow.screen)) + window()->setScreen(cocoaScreen->screen()); if (!m_isExposed) { m_isExposed = true; diff --git a/src/plugins/platforms/cocoa/qnsview.h b/src/plugins/platforms/cocoa/qnsview.h index abaff39e81..d911b804cc 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; @@ -102,7 +101,6 @@ Q_FORWARD_DECLARE_OBJC_CLASS(QT_MANGLE_NAMESPACE(QNSViewMouseMoveHelper)); - (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; @@ -153,6 +151,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 6181997923..73fe5d8526 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()) @@ -395,64 +354,6 @@ static bool _q_dontOverrideCtrlLMB = false; 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) @@ -553,7 +454,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) { @@ -1347,7 +1248,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) { @@ -2214,3 +2115,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..a465b249c5 100644 --- a/src/plugins/platforms/cocoa/qnswindowdelegate.h +++ b/src/plugins/platforms/cocoa/qnswindowdelegate.h @@ -49,15 +49,10 @@ 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..3781a4cc65 100644 --- a/src/plugins/platforms/cocoa/qnswindowdelegate.mm +++ b/src/plugins/platforms/cocoa/qnswindowdelegate.mm @@ -45,57 +45,12 @@ @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 @@ -116,13 +71,6 @@ return YES; } -- (void)windowWillClose:(NSNotification *)notification -{ - Q_UNUSED(notification); - if (m_cocoaWindow) - m_cocoaWindow->windowWillClose(); -} - - (BOOL)window:(NSWindow *)window shouldPopUpDocumentPathMenu:(NSMenu *)menu { Q_UNUSED(window); |