summaryrefslogtreecommitdiffstats
path: root/src/plugins/platforms/cocoa/qcocoawindow.mm
diff options
context:
space:
mode:
authorTor Arne Vestbø <tor.arne.vestbo@qt.io>2016-10-13 14:49:01 +0200
committerTor Arne Vestbø <tor.arne.vestbo@qt.io>2016-10-27 13:08:24 +0000
commit7f507c16202003e834863b8f26213c5e1c06fd0e (patch)
tree8702dbe9524ada5b6617714e4f297fe77e25a129 /src/plugins/platforms/cocoa/qcocoawindow.mm
parentd04207342ea4d22e42257eb0adc1048fd2e068b4 (diff)
macOS: Decouple NSWindow notifications and delegate callbacks from QNSView
The logic for handling NSWindow events was split partly between QNSView observing notifications, and QNSWindowDelegate implementing direct delegate callbacks. The logic of how to handle the events was then split further by sometimes handling the event in the delegate callback or notification handler, and sometimes forwarding the event to QCocoaWindow. We now handle most events via notifications, and propagate these directly to QCocoaWindow, so that all the logic is in one place. This improves the situation for foreign windows, since we're not relying on having a QNSView, or being able to inject our QNSWindowDelegate. To keep code duplication to a minimum and risking missing a notification in the forwarding logic, the logic is based on QMetatType and QMetaMethod tags, so that the notifications are declared in the header file, along with the handler function. Change-Id: I2fb6372010048a8a1f6e4426b988a3f6f5abdbab Reviewed-by: Erik Verbruggen <erik.verbruggen@qt.io> Reviewed-by: Morten Johan Sørvig <morten.sorvig@qt.io>
Diffstat (limited to 'src/plugins/platforms/cocoa/qcocoawindow.mm')
-rw-r--r--src/plugins/platforms/cocoa/qcocoawindow.mm167
1 files changed, 148 insertions, 19 deletions
diff --git a/src/plugins/platforms/cocoa/qcocoawindow.mm b/src/plugins/platforms/cocoa/qcocoawindow.mm
index d10d50aca9..9e5b52d1f7 100644
--- a/src/plugins/platforms/cocoa/qcocoawindow.mm
+++ b/src/plugins/platforms/cocoa/qcocoawindow.mm
@@ -343,6 +343,49 @@ 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) {
+
+ NSWindow *window = notification.object;
+
+ // Only top level NSWindows should notify their QNSViews
+ if (window.parentWindow)
+ return;
+
+ QCocoaWindow *cocoaWindow = nullptr;
+ if (QNSView *view = qnsview_cast(window.contentView))
+ cocoaWindow = view.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)
@@ -1180,6 +1223,8 @@ void QCocoaWindow::setEmbeddedInForeignView(bool embedded)
m_nsWindow = 0;
}
+// ----------------------- NSWindow notifications -----------------------
+
void QCocoaWindow::windowWillMove()
{
// Close any open popups on window move
@@ -1214,6 +1259,108 @@ 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;
+
+ NSUInteger screenIndex = [[NSScreen screens] indexOfObject:m_view.window.screen];
+ if (screenIndex == NSNotFound)
+ return;
+
+ if (QCocoaScreen *cocoaScreen = QCocoaIntegration::instance()->screenAtIndex(screenIndex))
+ 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();
@@ -1227,12 +1374,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()
{
@@ -1287,11 +1429,6 @@ void QCocoaWindow::recreateWindow()
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())) {
[m_nsWindow closeAndRelease];
@@ -1305,14 +1442,6 @@ void QCocoaWindow::recreateWindow()
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);