diff options
Diffstat (limited to 'src/plugins/platforms/cocoa/qcocoawindow.mm')
-rw-r--r-- | src/plugins/platforms/cocoa/qcocoawindow.mm | 180 |
1 files changed, 116 insertions, 64 deletions
diff --git a/src/plugins/platforms/cocoa/qcocoawindow.mm b/src/plugins/platforms/cocoa/qcocoawindow.mm index 63ee8c10ac..8e5a9268ec 100644 --- a/src/plugins/platforms/cocoa/qcocoawindow.mm +++ b/src/plugins/platforms/cocoa/qcocoawindow.mm @@ -38,6 +38,7 @@ ****************************************************************************/ #include "qcocoawindow.h" #include "qcocoaintegration.h" +#include "qcocoascreen.h" #include "qnswindowdelegate.h" #include "qcocoaeventdispatcher.h" #ifndef QT_NO_OPENGL @@ -97,34 +98,30 @@ static void qRegisterNotificationCallbacks() [center addObserverForName:notificationName.toNSString() object:nil queue:nil usingBlock:^(NSNotification *notification) { - NSView *view = nullptr; + QVarLengthArray<QCocoaWindow *, 32> cocoaWindows; if ([notification.object isKindOfClass:[NSWindow class]]) { - NSWindow *window = notification.object; - if (!window.contentView) - return; - - view = window.contentView; + NSWindow *nsWindow = notification.object; + for (const QWindow *window : QGuiApplication::allWindows()) { + if (QCocoaWindow *cocoaWindow = static_cast<QCocoaWindow *>(window->handle())) + if (cocoaWindow->nativeWindow() == nsWindow) + cocoaWindows += cocoaWindow; + } } else if ([notification.object isKindOfClass:[NSView class]]) { - view = notification.object; + if (QNSView *qnsView = qnsview_cast(notification.object)) + cocoaWindows += qnsView.platformWindow; } else { qCWarning(lcQpaCocoaWindow) << "Unhandled notifcation" << notification.name << "for" << notification.object; return; } - Q_ASSERT(view); - - QCocoaWindow *cocoaWindow = nullptr; - if (QNSView *qnsView = qnsview_cast(view)) - cocoaWindow = qnsView.platformWindow; // FIXME: Could be a foreign window, look up by iterating top level QWindows - if (!cocoaWindow) - return; - - if (!method.invoke(cocoaWindow, Qt::DirectConnection)) { - qCWarning(lcQpaCocoaWindow) << "Failed to invoke NSNotification callback for" - << notification.name << "on" << cocoaWindow; + for (QCocoaWindow *cocoaWindow : cocoaWindows) { + if (!method.invoke(cocoaWindow, Qt::DirectConnection)) { + qCWarning(lcQpaCocoaWindow) << "Failed to invoke NSNotification callback for" + << notification.name << "on" << cocoaWindow; + } } }]; } @@ -161,7 +158,6 @@ QCocoaWindow::QCocoaWindow(QWindow *win, WId nativeHandle) , m_drawContentBorderGradient(false) , m_topContentBorderThickness(0) , m_bottomContentBorderThickness(0) - , m_hasWindowFilePath(false) { qCDebug(lcQpaCocoaWindow) << "QCocoaWindow::QCocoaWindow" << window(); @@ -598,6 +594,11 @@ void QCocoaWindow::setWindowTitle(const QString &title) QMacAutoReleasePool pool; m_view.window.title = title.toNSString(); + + if (title.isEmpty() && !window()->filePath().isEmpty()) { + // Clearing the title should restore the default filename + setWindowFilePath(window()->filePath()); + } } void QCocoaWindow::setWindowFilePath(const QString &filePath) @@ -606,9 +607,14 @@ void QCocoaWindow::setWindowFilePath(const QString &filePath) return; QMacAutoReleasePool pool; - QFileInfo fi(filePath); - [m_view.window setRepresentedFilename:fi.exists() ? filePath.toNSString() : @""]; - m_hasWindowFilePath = fi.exists(); + + if (window()->title().isNull()) + [m_view.window setTitleWithRepresentedFilename:filePath.toNSString()]; + else + m_view.window.representedFilename = filePath.toNSString(); + + // Changing the file path may affect icon visibility + setWindowIcon(window()->icon()); } void QCocoaWindow::setWindowIcon(const QIcon &icon) @@ -616,23 +622,21 @@ void QCocoaWindow::setWindowIcon(const QIcon &icon) if (!isContentView()) return; - QMacAutoReleasePool pool; - NSButton *iconButton = [m_view.window standardWindowButton:NSWindowDocumentIconButton]; - if (iconButton == nil) { - if (icon.isNull()) - return; - NSString *title = window()->title().toNSString(); - [m_view.window setRepresentedURL:[NSURL fileURLWithPath:title]]; - iconButton = [m_view.window standardWindowButton:NSWindowDocumentIconButton]; + if (!iconButton) { + // Window icons are only supported on macOS in combination with a document filePath + return; } + + QMacAutoReleasePool pool; + if (icon.isNull()) { - [iconButton setImage:nil]; + NSWorkspace *workspace = [NSWorkspace sharedWorkspace]; + [iconButton setImage:[workspace iconForFile:m_view.window.representedFilename]]; } else { QPixmap pixmap = icon.pixmap(QSize(22, 22)); NSImage *image = static_cast<NSImage *>(qt_mac_create_nsimage(pixmap)); - [iconButton setImage:image]; - [image release]; + [iconButton setImage:[image autorelease]]; } } @@ -844,8 +848,32 @@ void QCocoaWindow::setEmbeddedInForeignView(bool embedded) m_nsWindow = 0; } +// ----------------------- NSView notifications ----------------------- + +void QCocoaWindow::viewDidChangeFrame() +{ + handleGeometryChange(); +} + +/*! + 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() +{ + [m_view setNeedsDisplay:YES]; +} + // ----------------------- NSWindow notifications ----------------------- +// Note: The following notifications are delivered to every QCocoaWindow +// that is a child of the NSWindow that triggered the notification. Each +// callback should make sure to filter out notifications if they do not +// apply to that QCocoaWindow, e.g. if the window is not a content view. + void QCocoaWindow::windowWillMove() { // Close any open popups on window move @@ -854,6 +882,9 @@ void QCocoaWindow::windowWillMove() void QCocoaWindow::windowDidMove() { + if (!isContentView()) + return; + handleGeometryChange(); // Moving a window might bring it out of maximized state @@ -871,30 +902,19 @@ void QCocoaWindow::windowDidResize() handleWindowStateChanged(); } -void QCocoaWindow::viewDidChangeFrame() -{ - handleGeometryChange(); -} - -/*! - 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() -{ - [m_view setNeedsDisplay:YES]; -} - void QCocoaWindow::windowDidEndLiveResize() { + if (!isContentView()) + return; + handleWindowStateChanged(); } void QCocoaWindow::windowDidBecomeKey() { + if (!isContentView()) + return; + if (isForeignWindow()) return; @@ -911,6 +931,9 @@ void QCocoaWindow::windowDidBecomeKey() void QCocoaWindow::windowDidResignKey() { + if (!isContentView()) + return; + if (isForeignWindow()) return; @@ -927,16 +950,25 @@ void QCocoaWindow::windowDidResignKey() void QCocoaWindow::windowDidMiniaturize() { + if (!isContentView()) + return; + handleWindowStateChanged(); } void QCocoaWindow::windowDidDeminiaturize() { + if (!isContentView()) + return; + handleWindowStateChanged(); } void QCocoaWindow::windowWillEnterFullScreen() { + if (!isContentView()) + return; + // The NSWindow needs to be resizable, otherwise we'll end up with // the normal window geometry, centered in the middle of the screen // on a black background. The styleMask will be reset below. @@ -945,6 +977,9 @@ void QCocoaWindow::windowWillEnterFullScreen() void QCocoaWindow::windowDidEnterFullScreen() { + if (!isContentView()) + return; + Q_ASSERT_X(m_view.window.qt_fullScreen, "QCocoaWindow", "FullScreen category processes window notifications first"); @@ -956,6 +991,9 @@ void QCocoaWindow::windowDidEnterFullScreen() void QCocoaWindow::windowWillExitFullScreen() { + if (!isContentView()) + return; + // The NSWindow needs to be resizable, otherwise we'll end up with // a weird zoom animation. The styleMask will be reset below. m_view.window.styleMask |= NSResizableWindowMask; @@ -963,6 +1001,9 @@ void QCocoaWindow::windowWillExitFullScreen() void QCocoaWindow::windowDidExitFullScreen() { + if (!isContentView()) + return; + Q_ASSERT_X(!m_view.window.qt_fullScreen, "QCocoaWindow", "FullScreen category processes window notifications first"); @@ -981,14 +1022,14 @@ void QCocoaWindow::windowDidExitFullScreen() } } -void QCocoaWindow::windowDidOrderOffScreen() +void QCocoaWindow::windowDidOrderOnScreen() { - handleExposeEvent(QRegion()); + [m_view setNeedsDisplay:YES]; } -void QCocoaWindow::windowDidOrderOnScreen() +void QCocoaWindow::windowDidOrderOffScreen() { - [m_view setNeedsDisplay:YES]; + handleExposeEvent(QRegion()); } void QCocoaWindow::windowDidChangeOcclusionState() @@ -1079,6 +1120,8 @@ void QCocoaWindow::handleGeometryChange() void QCocoaWindow::handleExposeEvent(const QRegion ®ion) { + const bool wasExposed = isExposed(); + // Ideally we'd implement isExposed() in terms of these properties, // plus the occlusionState of the NSWindow, and let the expose event // pull the exposed state out when needed. However, when the window @@ -1096,13 +1139,21 @@ void QCocoaWindow::handleExposeEvent(const QRegion ®ion) && !region.isEmpty() && !m_view.hiddenOrHasHiddenAncestor; - QWindowPrivate *windowPrivate = qt_window_private(window()); - if (m_isExposed && windowPrivate->updateRequestPending) { - // FIXME: Should this logic for expose events be in QGuiApplication? - qCDebug(lcQpaCocoaWindow) << "QCocoaWindow::handleExposeEvent" << window() << region << "as update request"; - windowPrivate->deliverUpdateRequest(); - return; + if (windowPrivate->updateRequestPending) { + // We can only deliver update request events when the window is exposed, + // and we also have to make sure we deliver the first expose event after + // becoming exposed as a real expose event, otherwise the exposed state + // of the QWindow is never updated. + // FIXME: Should this logic live in QGuiApplication? + if (wasExposed && m_isExposed) { + qCDebug(lcQpaCocoaWindow) << "QCocoaWindow::handleExposeEvent" << window() << region << "as update request"; + windowPrivate->deliverUpdateRequest(); + return; + } + + // FIXME: Should we re-trigger setNeedsDisplay in case of !wasExposed && m_isExposed? + // Or possibly send the expose event first, and then the update request? } qCDebug(lcQpaCocoaWindow) << "QCocoaWindow::handleExposeEvent" << window() << region << "isExposed" << isExposed(); @@ -1189,7 +1240,7 @@ void QCocoaWindow::recreateWindowIfNeeded() if (m_windowModality != window()->modality()) recreateReason |= WindowModalityChanged; - const bool shouldBeContentView = !parentWindow && !m_viewIsEmbedded; + const bool shouldBeContentView = !parentWindow && !(m_viewIsToBeEmbedded || m_viewIsEmbedded); if (isContentView() != shouldBeContentView) recreateReason |= ContentViewChanged; @@ -1249,6 +1300,7 @@ void QCocoaWindow::recreateWindowIfNeeded() propagateSizeHints(); setWindowFlags(window()->flags()); setWindowTitle(window()->title()); + setWindowFilePath(window()->filePath()); setWindowState(window()->windowState()); } else { // Child windows have no NSWindow, link the NSViews instead. @@ -1412,15 +1464,15 @@ QRect QCocoaWindow::nativeWindowGeometry() const */ void QCocoaWindow::applyWindowState(Qt::WindowStates requestedState) { + if (!isContentView()) + return; + const Qt::WindowState currentState = windowState(); const Qt::WindowState newState = QWindowPrivate::effectiveState(requestedState); if (newState == currentState) return; - if (!isContentView()) - return; - const NSSize contentSize = m_view.frame.size; if (contentSize.width <= 0 || contentSize.height <= 0) { // If content view width or height is 0 then the window animations will crash so |