summaryrefslogtreecommitdiffstats
path: root/src/plugins/platforms/cocoa/qcocoawindow.mm
diff options
context:
space:
mode:
Diffstat (limited to 'src/plugins/platforms/cocoa/qcocoawindow.mm')
-rw-r--r--src/plugins/platforms/cocoa/qcocoawindow.mm797
1 files changed, 483 insertions, 314 deletions
diff --git a/src/plugins/platforms/cocoa/qcocoawindow.mm b/src/plugins/platforms/cocoa/qcocoawindow.mm
index ee1689c2f4..4a245a0f8a 100644
--- a/src/plugins/platforms/cocoa/qcocoawindow.mm
+++ b/src/plugins/platforms/cocoa/qcocoawindow.mm
@@ -1,41 +1,5 @@
-/****************************************************************************
-**
-** Copyright (C) 2016 The Qt Company Ltd.
-** Contact: https://www.qt.io/licensing/
-**
-** This file is part of the plugins of the Qt Toolkit.
-**
-** $QT_BEGIN_LICENSE:LGPL$
-** Commercial License Usage
-** Licensees holding valid commercial Qt licenses may use this file in
-** accordance with the commercial license agreement provided with the
-** Software or, alternatively, in accordance with the terms contained in
-** a written agreement between you and The Qt Company. For licensing terms
-** and conditions see https://www.qt.io/terms-conditions. For further
-** information use the contact form at https://www.qt.io/contact-us.
-**
-** GNU Lesser General Public License Usage
-** Alternatively, this file may be used under the terms of the GNU Lesser
-** General Public License version 3 as published by the Free Software
-** Foundation and appearing in the file LICENSE.LGPL3 included in the
-** packaging of this file. Please review the following information to
-** ensure the GNU Lesser General Public License version 3 requirements
-** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
-**
-** GNU General Public License Usage
-** Alternatively, this file may be used under the terms of the GNU
-** General Public License version 2.0 or (at your option) the GNU General
-** Public license version 3 or any later version approved by the KDE Free
-** Qt Foundation. The licenses are as published by the Free Software
-** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
-** included in the packaging of this file. Please review the following
-** information to ensure the GNU General Public License requirements will
-** be met: https://www.gnu.org/licenses/gpl-2.0.html and
-** https://www.gnu.org/licenses/gpl-3.0.html.
-**
-** $QT_END_LICENSE$
-**
-****************************************************************************/
+// Copyright (C) 2016 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
#include <AppKit/AppKit.h>
#include <QuartzCore/QuartzCore.h>
@@ -76,7 +40,7 @@ Q_LOGGING_CATEGORY(lcCocoaNotifications, "qt.qpa.cocoa.notifications");
static void qRegisterNotificationCallbacks()
{
- static const QLatin1String notificationHandlerPrefix(Q_NOTIFICATION_PREFIX);
+ static const QLatin1StringView notificationHandlerPrefix(Q_NOTIFICATION_PREFIX);
NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
@@ -105,7 +69,7 @@ static void qRegisterNotificationCallbacks()
if (QNSView *qnsView = qnsview_cast(notification.object))
cocoaWindows += qnsView.platformWindow;
} else {
- qCWarning(lcCocoaNotifications) << "Unhandled notifcation"
+ qCWarning(lcCocoaNotifications) << "Unhandled notification"
<< notification.name << "for" << notification.object;
return;
}
@@ -132,27 +96,9 @@ static void qRegisterNotificationCallbacks()
Q_CONSTRUCTOR_FUNCTION(qRegisterNotificationCallbacks)
const int QCocoaWindow::NoAlertRequest = -1;
+QPointer<QCocoaWindow> QCocoaWindow::s_windowUnderMouse;
-QCocoaWindow::QCocoaWindow(QWindow *win, WId nativeHandle)
- : QPlatformWindow(win)
- , m_view(nil)
- , m_nsWindow(nil)
- , m_lastReportedWindowState(Qt::WindowNoState)
- , m_windowModality(Qt::NonModal)
- , m_windowUnderMouse(false)
- , m_initialized(false)
- , m_inSetVisible(false)
- , m_inSetGeometry(false)
- , m_inSetStyleMask(false)
- , m_menubar(nullptr)
- , m_frameStrutEventsEnabled(false)
- , m_registerTouchCount(0)
- , m_resizableTransientParent(false)
- , m_alertRequest(NoAlertRequest)
- , m_monitor(nil)
- , m_drawContentBorderGradient(false)
- , m_topContentBorderThickness(0)
- , m_bottomContentBorderThickness(0)
+QCocoaWindow::QCocoaWindow(QWindow *win, WId nativeHandle) : QPlatformWindow(win)
{
qCDebug(lcQpaWindow) << "QCocoaWindow::QCocoaWindow" << window();
@@ -171,22 +117,32 @@ void QCocoaWindow::initialize()
if (!m_view)
m_view = [[QNSView alloc] initWithCocoaWindow:this];
- // Compute the initial geometry based on the geometry set on the
- // QWindow. This geometry has already been reflected to the
- // QPlatformWindow in the constructor, so to ensure that the
- // resulting setGeometry call does not think the geometry has
- // already been applied, we reset the QPlatformWindow's view
- // of the geometry first.
- auto initialGeometry = QPlatformWindow::initialGeometry(window(),
- windowGeometry(), defaultWindowWidth, defaultWindowHeight);
- QPlatformWindow::d_ptr->rect = QRect();
- setGeometry(initialGeometry);
+ if (!isForeignWindow()) {
+ // Compute the initial geometry based on the geometry set on the
+ // QWindow. This geometry has already been reflected to the
+ // QPlatformWindow in the constructor, so to ensure that the
+ // resulting setGeometry call does not think the geometry has
+ // already been applied, we reset the QPlatformWindow's view
+ // of the geometry first.
+ auto initialGeometry = QPlatformWindow::initialGeometry(window(),
+ windowGeometry(), defaultWindowWidth, defaultWindowHeight);
+ QPlatformWindow::d_ptr->rect = QRect();
+ setGeometry(initialGeometry);
+
+ setMask(QHighDpi::toNativeLocalRegion(window()->mask(), window()));
+
+ } else {
+ // Pick up essential foreign window state
+ QPlatformWindow::setGeometry(QRectF::fromCGRect(m_view.frame).toRect());
+ }
recreateWindowIfNeeded();
m_initialized = true;
}
+const NSNotificationName QCocoaWindowWillReleaseQNSViewNotification = @"QCocoaWindowWillReleaseQNSViewNotification";
+
QCocoaWindow::~QCocoaWindow()
{
qCDebug(lcQpaWindow) << "QCocoaWindow::~QCocoaWindow" << window();
@@ -194,31 +150,34 @@ QCocoaWindow::~QCocoaWindow()
QMacAutoReleasePool pool;
[m_nsWindow makeFirstResponder:nil];
[m_nsWindow setContentView:nil];
- if ([m_view superview])
- [m_view removeFromSuperview];
- removeMonitor();
+ // Remove from superview only if we have a Qt window parent,
+ // as we don't want to affect window container foreign windows.
+ if (QPlatformWindow::parent())
+ [m_view removeFromSuperview];
// 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 (!isForeignWindow())
[[NSNotificationCenter defaultCenter] removeObserver:m_view];
- if (QCocoaIntegration *cocoaIntegration = QCocoaIntegration::instance()) {
- // While it is unlikely that this window will be in the popup stack
- // during deletetion we clear any pointers here to make sure.
- cocoaIntegration->popupWindowStack()->removeAll(this);
-
#if QT_CONFIG(vulkan)
+ if (QCocoaIntegration *cocoaIntegration = QCocoaIntegration::instance()) {
auto vulcanInstance = cocoaIntegration->getCocoaVulkanInstance();
if (vulcanInstance)
vulcanInstance->destroySurface(m_vulkanSurface);
-#endif
}
+#endif
+
+ // Must send notification before calling release, as doing it from
+ // [QNSView dealloc] would mean that any weak references to the view
+ // would already return nil.
+ [NSNotificationCenter.defaultCenter
+ postNotificationName:QCocoaWindowWillReleaseQNSViewNotification
+ object:m_view];
[m_view release];
- [m_nsWindow close];
- [m_nsWindow release];
+ [m_nsWindow closeAndRelease];
}
QSurfaceFormat QCocoaWindow::format() const
@@ -253,7 +212,7 @@ bool QCocoaWindow::isForeignWindow() const
QRect QCocoaWindow::geometry() const
{
- // QWindows that are embedded in a NSView hiearchy may be considered
+ // QWindows that are embedded in a NSView hierarchy may be considered
// top-level from Qt's point of view but are not from Cocoa's point
// of view. Embedded QWindows get global (screen) geometry.
if (isEmbedded()) {
@@ -268,6 +227,40 @@ QRect QCocoaWindow::geometry() const
return QPlatformWindow::geometry();
}
+/*!
+ \brief the geometry of the window as it will appear when shown as
+ a normal (not maximized or full screen) top-level window.
+
+ For child windows this property always holds an empty rectangle.
+
+ \sa QWidget::normalGeometry()
+*/
+QRect QCocoaWindow::normalGeometry() const
+{
+ if (!isContentView())
+ return QRect();
+
+ // We only persist the normal the geometry when going into
+ // fullscreen and maximized states. For all other cases we
+ // can just report the geometry as is.
+
+ if (!(windowState() & (Qt::WindowFullScreen | Qt::WindowMaximized)))
+ return geometry();
+
+ return m_normalGeometry;
+}
+
+void QCocoaWindow::updateNormalGeometry()
+{
+ if (!isContentView())
+ return;
+
+ if (windowState() != Qt::WindowNoState)
+ return;
+
+ m_normalGeometry = geometry();
+}
+
void QCocoaWindow::setCocoaGeometry(const QRect &rect)
{
qCDebug(lcQpaWindow) << "QCocoaWindow::setCocoaGeometry" << window() << rect;
@@ -315,6 +308,31 @@ void QCocoaWindow::setVisible(bool visible)
{
qCDebug(lcQpaWindow) << "QCocoaWindow::setVisible" << window() << visible;
+ // Our implementation of setVisible below is not idempotent, as for
+ // modal windows it calls beginSheet/endSheet or starts/ends modal
+ // sessions. However we can't simply guard for m_view.hidden already
+ // having the right state, as the behavior of this function differs
+ // based on whether the window has been initialized or not, as
+ // handleGeometryChange will bail out if the window is still
+ // initializing. Since we know we'll get a second setVisible
+ // call after creation, we can check for that case specifically,
+ // which means we can then safely guard on m_view.hidden changing.
+
+ if (!m_initialized) {
+ qCDebug(lcQpaWindow) << "Window still initializing, skipping setting visibility";
+ return; // We'll get another setVisible call after create is done
+ }
+
+ if (visible == !m_view.hidden && (!isContentView() || visible == m_view.window.visible)) {
+ qCDebug(lcQpaWindow) << "No change in visible status. Ignoring.";
+ return;
+ }
+
+ if (m_inSetVisible) {
+ qCWarning(lcQpaWindow) << "Already setting window visible!";
+ return;
+ }
+
QScopedValueRollback<bool> rollback(m_inSetVisible, true);
QMacAutoReleasePool pool;
@@ -336,11 +354,6 @@ void QCocoaWindow::setVisible(bool visible)
// so we can send the geometry change. FIXME: Get rid of this workaround.
handleGeometryChange();
- // Register popup windows. The Cocoa platform plugin will forward mouse events
- // to them and close them when needed.
- if (window()->type() == Qt::Popup || window()->type() == Qt::ToolTip)
- QCocoaIntegration::instance()->pushPopupWindow(this);
-
if (parentCocoaWindow) {
// The parent window might have moved while this window was hidden,
// update the window geometry if there is a parent.
@@ -358,6 +371,9 @@ void QCocoaWindow::setVisible(bool visible)
}
+ // Make the NSView visible first, before showing the NSWindow (in case of top level windows)
+ m_view.hidden = NO;
+
if (isContentView()) {
QWindowSystemInterface::flushWindowSystemEvents(QEventLoop::ExcludeUserInputEvents);
@@ -368,12 +384,18 @@ void QCocoaWindow::setVisible(bool visible)
if (window()->windowState() != Qt::WindowMinimized) {
if (parentCocoaWindow && (window()->modality() == Qt::WindowModal || window()->type() == Qt::Sheet)) {
// Show the window as a sheet
- [parentCocoaWindow->nativeWindow() beginSheet:m_view.window completionHandler:nil];
+ NSWindow *nativeParentWindow = parentCocoaWindow->nativeWindow();
+ if (!nativeParentWindow.attachedSheet)
+ [nativeParentWindow beginSheet:m_view.window completionHandler:nil];
+ else
+ [nativeParentWindow beginCriticalSheet:m_view.window completionHandler:nil];
} else if (window()->modality() == Qt::ApplicationModal) {
// Show the window as application modal
eventDispatcher()->beginModalSession(window());
} else if (m_view.window.canBecomeKeyWindow) {
- bool shouldBecomeKeyNow = !NSApp.modalWindow || m_view.window.worksWhenModal;
+ bool shouldBecomeKeyNow = !NSApp.modalWindow
+ || m_view.window.worksWhenModal
+ || !NSApp.modalWindow.visible;
// Panels with becomesKeyOnlyIfNeeded set should not activate until a view
// with needsPanelToBecomeKey, for example a line edit, is clicked.
@@ -387,30 +409,8 @@ void QCocoaWindow::setVisible(bool visible)
} else {
[m_view.window orderFront:nil];
}
-
- // Close popup when clicking outside it
- if (window()->type() == Qt::Popup && !(parentCocoaWindow && window()->transientParent()->isActive())) {
- removeMonitor();
- NSEventMask eventMask = NSEventMaskLeftMouseDown | NSEventMaskRightMouseDown
- | NSEventMaskOtherMouseDown | NSEventMaskMouseMoved;
- m_monitor = [NSEvent addGlobalMonitorForEventsMatchingMask:eventMask handler:^(NSEvent *e) {
- const auto button = cocoaButton2QtButton(e);
- const auto buttons = currentlyPressedMouseButtons();
- const auto eventType = cocoaEvent2QtMouseEvent(e);
- const auto globalPoint = QCocoaScreen::mapFromNative(NSEvent.mouseLocation);
- const auto localPoint = window()->mapFromGlobal(globalPoint.toPoint());
- QWindowSystemInterface::handleMouseEvent(window(), localPoint, globalPoint, buttons, button, eventType);
- }];
- }
}
}
-
- // In some cases, e.g. QDockWidget, the content view is hidden before moving to its own
- // Cocoa window, and then shown again. Therefore, we test for the view being hidden even
- // if it's attached to an NSWindow.
- if ([m_view isHidden])
- [m_view setHidden:NO];
-
} else {
// Window not visible, hide it
if (isContentView()) {
@@ -439,14 +439,27 @@ void QCocoaWindow::setVisible(bool visible)
if (mainWindow && [mainWindow canBecomeKeyWindow])
[mainWindow makeKeyWindow];
}
- } else {
- [m_view setHidden:YES];
}
- removeMonitor();
+ // AppKit will in some cases set up the key view loop for child views, even if we
+ // don't set autorecalculatesKeyViewLoop, nor call recalculateKeyViewLoop ourselves.
+ // When a child window is promoted to a top level, AppKit will maintain the key view
+ // loop between the views, even if these views now cross NSWindows, even after we
+ // explicitly call recalculateKeyViewLoop. When the top level is then hidden, AppKit
+ // will complain when -[NSView _setHidden:setNeedsDisplay:] tries to transfer first
+ // responder by reading the nextValidKeyView, and it turns out to live in a different
+ // window. We mitigate this by a last second reset of the first responder, which is
+ // what AppKit also falls back to. It's unclear if the original situation of views
+ // having their nextKeyView pointing to views in other windows is kosher or not.
+ if (m_view.window.firstResponder == m_view && m_view.nextValidKeyView
+ && m_view.nextValidKeyView.window != m_view.window) {
+ qCDebug(lcQpaWindow) << "Detected nextValidKeyView" << m_view.nextValidKeyView
+ << "in different window" << m_view.nextValidKeyView.window
+ << "Resetting" << m_view.window << "first responder to nil.";
+ [m_view.window makeFirstResponder:nil];
+ }
- if (window()->type() == Qt::Popup || window()->type() == Qt::ToolTip)
- QCocoaIntegration::instance()->popupWindowStack()->removeAll(this);
+ m_view.hidden = YES;
if (parentCocoaWindow && window()->type() == Qt::Popup) {
NSWindow *nativeParentWindow = parentCocoaWindow->nativeWindow();
@@ -513,65 +526,110 @@ NSInteger QCocoaWindow::windowLevel(Qt::WindowFlags flags)
NSUInteger QCocoaWindow::windowStyleMask(Qt::WindowFlags flags)
{
const Qt::WindowType type = static_cast<Qt::WindowType>(int(flags & Qt::WindowType_Mask));
- const bool frameless = (flags & Qt::FramelessWindowHint) || windowIsPopupType(type);
-
- // Remove zoom button by disabling resize for CustomizeWindowHint windows, except for
- // Qt::Tool windows (e.g. dock windows) which should always be resizable.
- const bool resizable = !(flags & Qt::CustomizeWindowHint) || (type == Qt::Tool);
-
- // Select base window type. Note that the value of NSBorderlessWindowMask is 0.
- NSUInteger styleMask = (frameless || !resizable) ? NSWindowStyleMaskBorderless : NSWindowStyleMaskResizable;
-
- if (frameless) {
- // Frameless windows do not display the traffic lights buttons for
- // e.g. minimize, however StyleMaskMiniaturizable is required to allow
- // programatic minimize. However, for framless tool windows (e.g. dock windows)
- // we don't want that, as it breaks translucency.
- if (type != Qt::Tool)
- styleMask |= NSWindowStyleMaskMiniaturizable;
- } else if (flags & Qt::CustomizeWindowHint) {
- if (flags & Qt::WindowTitleHint)
- styleMask |= NSWindowStyleMaskTitled;
- if (flags & Qt::WindowCloseButtonHint)
- styleMask |= NSWindowStyleMaskClosable;
- if (flags & Qt::WindowMinimizeButtonHint)
- styleMask |= NSWindowStyleMaskMiniaturizable;
- if (flags & Qt::WindowMaximizeButtonHint)
- styleMask |= NSWindowStyleMaskResizable;
- } else {
- styleMask |= NSWindowStyleMaskClosable | NSWindowStyleMaskTitled;
- if (type != Qt::Dialog)
- styleMask |= NSWindowStyleMaskMiniaturizable;
- }
+ // Determine initial style mask based on whether the window should
+ // have a frame and title or not. The NSWindowStyleMaskBorderless
+ // and NSWindowStyleMaskTitled styles are mutually exclusive, with
+ // values of 0 and 1 correspondingly.
+ NSUInteger styleMask = [&]{
+ // Honor explicit requests for borderless windows
+ if (flags & Qt::FramelessWindowHint)
+ return NSWindowStyleMaskBorderless;
+
+ // Popup windows should always be borderless
+ if (windowIsPopupType(type))
+ return NSWindowStyleMaskBorderless;
+
+ if (flags & Qt::CustomizeWindowHint) {
+ // CustomizeWindowHint turns off the default window title hints,
+ // so the choice is then up to the user via Qt::WindowTitleHint.
+ return flags & Qt::WindowTitleHint
+ ? NSWindowStyleMaskTitled
+ : NSWindowStyleMaskBorderless;
+ } else {
+ // Otherwise, default to using titled windows
+ return NSWindowStyleMaskTitled;
+ }
+ }();
+
+ // We determine which buttons to show in updateTitleBarButtons,
+ // so we can enable all the relevant style masks here to ensure
+ // that behaviors that don't involve the title bar buttons are
+ // working (for example minimizing frameless windows, or resizing
+ // windows that don't have zoom or fullscreen titlebar buttons).
+ styleMask |= NSWindowStyleMaskClosable
+ | NSWindowStyleMaskMiniaturizable;
+
+ if (type != Qt::Popup) // We only care about popups exactly.
+ styleMask |= NSWindowStyleMaskResizable;
if (type == Qt::Tool)
styleMask |= NSWindowStyleMaskUtilityWindow;
+ // FIXME: Remove use of deprecated style mask
if (m_drawContentBorderGradient)
styleMask |= NSWindowStyleMaskTexturedBackground;
- // Don't wipe fullscreen state
+ // Don't wipe existing states
if (m_view.window.styleMask & NSWindowStyleMaskFullScreen)
styleMask |= NSWindowStyleMaskFullScreen;
+ if (m_view.window.styleMask & NSWindowStyleMaskFullSizeContentView)
+ styleMask |= NSWindowStyleMaskFullSizeContentView;
return styleMask;
}
-void QCocoaWindow::setWindowZoomButton(Qt::WindowFlags flags)
+bool QCocoaWindow::isFixedSize() const
+{
+ return windowMinimumSize().isValid() && windowMaximumSize().isValid()
+ && windowMinimumSize() == windowMaximumSize();
+}
+
+void QCocoaWindow::updateTitleBarButtons(Qt::WindowFlags windowFlags)
{
if (!isContentView())
return;
- // Disable the zoom (maximize) button for fixed-sized windows and customized
- // no-WindowMaximizeButtonHint windows. From a Qt perspective it migth be expected
- // that the button would be removed in the latter case, but disabling it is more
- // in line with the platform style guidelines.
- bool fixedSizeNoZoom = (windowMinimumSize().isValid() && windowMaximumSize().isValid()
- && windowMinimumSize() == windowMaximumSize());
- bool customizeNoZoom = ((flags & Qt::CustomizeWindowHint)
- && !(flags & (Qt::WindowMaximizeButtonHint | Qt::WindowFullscreenButtonHint)));
- [[m_view.window standardWindowButton:NSWindowZoomButton] setEnabled:!(fixedSizeNoZoom || customizeNoZoom)];
+ static constexpr std::pair<NSWindowButton, Qt::WindowFlags> buttons[] = {
+ { NSWindowCloseButton, Qt::WindowCloseButtonHint },
+ { NSWindowMiniaturizeButton, Qt::WindowMinimizeButtonHint},
+ { NSWindowZoomButton, Qt::WindowMaximizeButtonHint | Qt::WindowFullscreenButtonHint }
+ };
+
+ bool hideButtons = true;
+ for (const auto &[button, buttonHint] : buttons) {
+ // Set up Qt defaults based on window type
+ bool enabled = true;
+ if (button == NSWindowMiniaturizeButton)
+ enabled = window()->type() != Qt::Dialog;
+
+ // Let users override via CustomizeWindowHint
+ if (windowFlags & Qt::CustomizeWindowHint)
+ enabled = windowFlags & buttonHint;
+
+ // Then do some final sanitizations
+
+ if (button == NSWindowZoomButton && isFixedSize())
+ enabled = false;
+
+ // Mimic what macOS natively does for parent windows of modal
+ // sheets, which is to disable the close button, but leave the
+ // other buttons as they were.
+ if (button == NSWindowCloseButton && enabled
+ && QWindowPrivate::get(window())->blockedByModalWindow) {
+ enabled = false;
+ // If we end up having no enabled buttons, our workaround
+ // should not be a reason for hiding all of them.
+ hideButtons = false;
+ }
+
+ [m_view.window standardWindowButton:button].enabled = enabled;
+ hideButtons &= !enabled;
+ }
+
+ // Hide buttons in case we disabled all of them
+ for (const auto &[button, buttonHint] : buttons)
+ [m_view.window standardWindowButton:button].hidden = hideButtons;
}
void QCocoaWindow::setWindowFlags(Qt::WindowFlags flags)
@@ -616,7 +674,7 @@ void QCocoaWindow::setWindowFlags(Qt::WindowFlags flags)
if (!(flags & Qt::FramelessWindowHint))
setWindowTitle(window()->title());
- setWindowZoomButton(flags);
+ updateTitleBarButtons(flags);
// Make window ignore mouse events if WindowTransparentForInput is set.
// Note that ignoresMouseEvents has a special initial state where events
@@ -650,7 +708,7 @@ void QCocoaWindow::applyWindowState(Qt::WindowStates requestedState)
if (!isContentView())
return;
- const Qt::WindowState currentState = windowState();
+ const Qt::WindowState currentState = QWindowPrivate::effectiveState(windowState());
const Qt::WindowState newState = QWindowPrivate::effectiveState(requestedState);
if (newState == currentState)
@@ -682,9 +740,10 @@ void QCocoaWindow::applyWindowState(Qt::WindowStates requestedState)
switch (currentState) {
case Qt::WindowMinimized:
[nsWindow deminiaturize:sender];
- Q_ASSERT_X(windowState() != Qt::WindowMinimized, "QCocoaWindow",
- "[NSWindow deminiaturize:] is synchronous");
- break;
+ // Deminiaturizing is not synchronous, so we need to wait for the
+ // NSWindowDidMiniaturizeNotification before continuing to apply
+ // the new state.
+ return;
case Qt::WindowFullScreen: {
toggleFullScreen();
// Exiting fullscreen is not synchronous, so we need to wait for the
@@ -718,23 +777,27 @@ void QCocoaWindow::applyWindowState(Qt::WindowStates requestedState)
}
}
-Qt::WindowState QCocoaWindow::windowState() const
+Qt::WindowStates QCocoaWindow::windowState() const
{
- // FIXME: Support compound states (Qt::WindowStates)
-
+ Qt::WindowStates states = Qt::WindowNoState;
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;
+ states |= Qt::WindowMinimized;
+
+ // Full screen and maximized are mutually exclusive, as macOS
+ // will report a full screen window as zoomed.
+ if (window.qt_fullScreen) {
+ states |= Qt::WindowFullScreen;
+ } else if ((window.zoomed && !isTransitioningToFullScreen())
+ || (m_lastReportedWindowState == Qt::WindowMaximized && isTransitioningToFullScreen())) {
+ states |= 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;
+ return states;
}
void QCocoaWindow::toggleMaximized()
@@ -753,6 +816,11 @@ void QCocoaWindow::toggleMaximized()
window.styleMask &= ~NSWindowStyleMaskResizable;
}
+void QCocoaWindow::windowWillZoom()
+{
+ updateNormalGeometry();
+}
+
void QCocoaWindow::toggleFullScreen()
{
const NSWindow *window = m_view.window;
@@ -771,6 +839,8 @@ void QCocoaWindow::windowWillEnterFullScreen()
if (!isContentView())
return;
+ updateNormalGeometry();
+
// 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.
@@ -830,11 +900,6 @@ void QCocoaWindow::windowDidExitFullScreen()
}
}
-void QCocoaWindow::windowWillMiniaturize()
-{
- QCocoaIntegration::instance()->closePopups(window());
-}
-
void QCocoaWindow::windowDidMiniaturize()
{
if (!isContentView())
@@ -848,12 +913,20 @@ void QCocoaWindow::windowDidDeminiaturize()
if (!isContentView())
return;
+ Qt::WindowState requestedState = window()->windowState();
+
handleWindowStateChanged();
+
+ if (requestedState != windowState() && requestedState != Qt::WindowMinimized) {
+ // We were only going out of minimized as an intermediate step before
+ // progressing into the final step, so re-sync the desired state.
+ applyWindowState(requestedState);
+ }
}
void QCocoaWindow::handleWindowStateChanged(HandleFlags flags)
{
- Qt::WindowState currentState = windowState();
+ Qt::WindowStates currentState = windowState();
if (!(flags & HandleUnconditionally) && currentState == m_lastReportedWindowState)
return;
@@ -1028,7 +1101,7 @@ void QCocoaWindow::propagateSizeHints()
window.contentMaxSize = NSSizeFromCGSize(windowMaximumSize().toCGSize());
// The window may end up with a fixed size; in this case the zoom button should be disabled.
- setWindowZoomButton(this->window()->flags());
+ updateTitleBarButtons(this->window()->flags());
// sizeIncrement is observed to take values of (-1, -1) and (0, 0) for windows that should be
// resizable and that have no specific size increment set. Cocoa expects (1.0, 1.0) in this case.
@@ -1068,28 +1141,14 @@ void QCocoaWindow::setMask(const QRegion &region)
}
}
-bool QCocoaWindow::setKeyboardGrabEnabled(bool grab)
+bool QCocoaWindow::setKeyboardGrabEnabled(bool)
{
- qCDebug(lcQpaWindow) << "QCocoaWindow::setKeyboardGrabEnabled" << window() << grab;
- if (!isContentView())
- return false;
-
- if (grab && ![m_view.window isKeyWindow])
- [m_view.window makeKeyWindow];
-
- return true;
+ return false; // FIXME (QTBUG-106597)
}
-bool QCocoaWindow::setMouseGrabEnabled(bool grab)
+bool QCocoaWindow::setMouseGrabEnabled(bool)
{
- qCDebug(lcQpaWindow) << "QCocoaWindow::setMouseGrabEnabled" << window() << grab;
- if (!isContentView())
- return false;
-
- if (grab && ![m_view.window isKeyWindow])
- [m_view.window makeKeyWindow];
-
- return true;
+ return false; // FIXME (QTBUG-106597)
}
WId QCocoaWindow::winId() const
@@ -1119,7 +1178,7 @@ NSWindow *QCocoaWindow::nativeWindow() const
void QCocoaWindow::setEmbeddedInForeignView()
{
- // Release any previosly created NSWindow.
+ // Release any previously created NSWindow.
[m_nsWindow closeAndRelease];
m_nsWindow = 0;
}
@@ -1155,12 +1214,6 @@ void QCocoaWindow::viewDidChangeGlobalFrame()
// 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
- QCocoaIntegration::instance()->closePopups();
-}
-
void QCocoaWindow::windowDidMove()
{
if (!isContentView())
@@ -1193,69 +1246,96 @@ void QCocoaWindow::windowDidEndLiveResize()
void QCocoaWindow::windowDidBecomeKey()
{
- if (!isContentView())
- return;
-
- if (isForeignWindow())
+ // The NSWindow we're part of become key. Check if we're the first
+ // responder, and if so, deliver focus window change to our window.
+ if (m_view.window.firstResponder != m_view)
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);
- }
-
- QNSView *firstResponderView = qt_objc_cast<QNSView *>(m_view.window.firstResponder);
- if (!firstResponderView)
- return;
+ qCDebug(lcQpaWindow) << m_view.window << "became key window."
+ << "Updating focus window to" << this << "with view" << m_view;
- const QCocoaWindow *focusCocoaWindow = firstResponderView.platformWindow;
- if (focusCocoaWindow->windowIsPopupType())
+ if (windowIsPopupType()) {
+ qCDebug(lcQpaWindow) << "Window is popup. Skipping focus window change.";
return;
+ }
// See also [QNSView becomeFirstResponder]
- QWindowSystemInterface::handleWindowActivated<QWindowSystemInterface::SynchronousDelivery>(
- focusCocoaWindow->window(), Qt::ActiveWindowFocusReason);
+ QWindowSystemInterface::handleFocusWindowChanged<QWindowSystemInterface::SynchronousDelivery>(
+ window(), Qt::ActiveWindowFocusReason);
}
void QCocoaWindow::windowDidResignKey()
{
- if (!isContentView())
+ // The NSWindow we're part of lost key. Check if we're the first
+ // responder, and if so, deliver window deactivation to our window.
+ if (m_view.window.firstResponder != m_view)
return;
- if (isForeignWindow())
- return;
+ qCDebug(lcQpaWindow) << m_view.window << "resigned key window."
+ << "Clearing focus window" << this << "with view" << m_view;
+
+ // Make sure popups are closed before we deliver activation changes, which are
+ // otherwise ignored by QApplication.
+ closeAllPopups();
// The current key window will be non-nil if another window became key. If that
// window is a Qt window, we delay the window activation event until the didBecomeKey
// notification is delivered to the active window, to ensure an atomic update.
NSWindow *newKeyWindow = [NSApp keyWindow];
if (newKeyWindow && newKeyWindow != m_view.window
- && [newKeyWindow conformsToProtocol:@protocol(QNSWindowProtocol)])
+ && [newKeyWindow conformsToProtocol:@protocol(QNSWindowProtocol)]) {
+ qCDebug(lcQpaWindow) << "New key window" << newKeyWindow
+ << "is Qt window. Deferring focus window change.";
return;
+ }
// Lost key window, go ahead and set the active window to zero
if (!windowIsPopupType()) {
- QWindowSystemInterface::handleWindowActivated<QWindowSystemInterface::SynchronousDelivery>(
+ QWindowSystemInterface::handleFocusWindowChanged<QWindowSystemInterface::SynchronousDelivery>(
nullptr, Qt::ActiveWindowFocusReason);
}
}
void QCocoaWindow::windowDidOrderOnScreen()
{
+ // The current mouse window needs to get a leave event when a popup window opens.
+ // For modal dialogs, QGuiApplicationPrivate::showModalWindow takes care of this.
+ if (QWindowPrivate::get(window())->isPopup()) {
+ QWindowSystemInterface::handleLeaveEvent<QWindowSystemInterface::SynchronousDelivery>
+ (QGuiApplicationPrivate::currentMouseWindow);
+ }
+
[m_view setNeedsDisplay:YES];
}
void QCocoaWindow::windowDidOrderOffScreen()
{
handleExposeEvent(QRegion());
+ // We are closing a window, so the window that is now under the mouse
+ // might need to get an Enter event if it isn't already the mouse window.
+ if (window()->type() & Qt::Window) {
+ const QPointF screenPoint = QCocoaScreen::mapFromNative([NSEvent mouseLocation]);
+ if (QWindow *windowUnderMouse = QGuiApplication::topLevelAt(screenPoint.toPoint())) {
+ if (windowUnderMouse != QGuiApplicationPrivate::instance()->currentMouseWindow) {
+ const auto windowPoint = windowUnderMouse->mapFromGlobal(screenPoint);
+ // asynchronous delivery on purpose
+ QWindowSystemInterface::handleEnterEvent<QWindowSystemInterface::AsynchronousDelivery>
+ (windowUnderMouse, windowPoint, screenPoint);
+ }
+ }
+ }
}
void QCocoaWindow::windowDidChangeOcclusionState()
{
+ // Note, we don't take the view's hiddenOrHasHiddenAncestor state into
+ // account here, but instead leave that up to handleExposeEvent, just
+ // like all the other signals that could potentially change the exposed
+ // state of the window.
bool visible = m_view.window.occlusionState & NSWindowOcclusionStateVisible;
- qCDebug(lcQpaWindow) << "QCocoaWindow::windowDidChangeOcclusionState" << window() << "is now" << (visible ? "visible" : "occluded");
+ qCDebug(lcQpaWindow) << "Occlusion state of" << m_view.window << "for"
+ << window() << "changed to" << (visible ? "visible" : "occluded");
+
if (visible)
[m_view setNeedsDisplay:YES];
else
@@ -1268,11 +1348,15 @@ void QCocoaWindow::windowDidChangeScreen()
return;
// Note: When a window is resized to 0x0 Cocoa will report the window's screen as nil
- auto *currentScreen = QCocoaScreen::get(m_view.window.screen);
+ NSScreen *nsScreen = m_view.window.screen;
+
+ qCDebug(lcQpaWindow) << window() << "did change" << nsScreen;
+ QCocoaScreen::updateScreens();
+
auto *previousScreen = static_cast<QCocoaScreen*>(screen());
+ auto *currentScreen = QCocoaScreen::get(nsScreen);
- Q_ASSERT_X(!m_view.window.screen || currentScreen,
- "QCocoaWindow", "Failed to get QCocoaScreen for NSScreen");
+ qCDebug(lcQpaWindow) << "Screen changed for" << window() << "from" << previousScreen << "to" << currentScreen;
// Note: The previous screen may be the same as the current screen, either because
// a) the screen was just reconfigured, which still results in AppKit sending an
@@ -1285,7 +1369,6 @@ void QCocoaWindow::windowDidChangeScreen()
// device-pixel ratio may have changed, and needs to be delivered to all
// windows, both top level and child windows.
- qCDebug(lcQpaWindow) << "Screen changed for" << window() << "from" << previousScreen << "to" << currentScreen;
QWindowSystemInterface::handleWindowScreenChanged<QWindowSystemInterface::SynchronousDelivery>(
window(), currentScreen ? currentScreen->screen() : nullptr);
@@ -1298,13 +1381,6 @@ void QCocoaWindow::windowDidChangeScreen()
}
}
-void QCocoaWindow::windowWillClose()
-{
- // Close any open popups on window closing.
- if (window() && !windowIsPopupType(window()->type()))
- QCocoaIntegration::instance()->closePopups();
-}
-
// ----------------------- NSWindowDelegate callbacks -----------------------
bool QCocoaWindow::windowShouldClose()
@@ -1427,14 +1503,30 @@ void QCocoaWindow::recreateWindowIfNeeded()
QMacAutoReleasePool pool;
QPlatformWindow *parentWindow = QPlatformWindow::parent();
-
- const bool isEmbeddedView = isEmbedded();
- RecreationReasons recreateReason = RecreationNotNeeded;
+ auto *parentCocoaWindow = static_cast<QCocoaWindow *>(parentWindow);
QCocoaWindow *oldParentCocoaWindow = nullptr;
if (QNSView *qnsView = qnsview_cast(m_view.superview))
oldParentCocoaWindow = qnsView.platformWindow;
+ if (isForeignWindow()) {
+ // A foreign window is created as such, and can never move between being
+ // foreign and not, so we don't need to get rid of any existing NSWindows,
+ // nor create new ones, as a foreign window is a single simple NSView.
+ qCDebug(lcQpaWindow) << "Skipping NSWindow management for foreign window" << this;
+
+ // We do however need to manage the parent relationship
+ if (parentCocoaWindow)
+ [parentCocoaWindow->m_view addSubview:m_view];
+ else if (oldParentCocoaWindow)
+ [m_view removeFromSuperview];
+
+ return;
+ }
+
+ const bool isEmbeddedView = isEmbedded();
+ RecreationReasons recreateReason = RecreationNotNeeded;
+
if (parentWindow != oldParentCocoaWindow)
recreateReason |= ParentChanged;
@@ -1465,8 +1557,6 @@ void QCocoaWindow::recreateWindowIfNeeded()
if (recreateReason == RecreationNotNeeded)
return;
- QCocoaWindow *parentCocoaWindow = static_cast<QCocoaWindow *>(parentWindow);
-
// Remove current window (if any)
if ((isContentView() && !shouldBeContentView) || (recreateReason & PanelChanged)) {
if (m_nsWindow) {
@@ -1496,31 +1586,10 @@ void QCocoaWindow::recreateWindowIfNeeded()
}
}
- if (isEmbeddedView) {
- // An embedded window doesn't have its own NSWindow.
- } else if (!parentWindow) {
- // QPlatformWindow subclasses must sync up with QWindow on creation:
- propagateSizeHints();
- setWindowFlags(window()->flags());
- setWindowTitle(window()->title());
- setWindowFilePath(window()->filePath()); // Also sets window icon
- setWindowState(window()->windowState());
- } else {
+ if (parentCocoaWindow) {
// Child windows have no NSWindow, re-parent to superview instead
[parentCocoaWindow->m_view addSubview:m_view];
- [m_view setHidden:!window()->isVisible()];
}
-
- const qreal opacity = qt_window_private(window())->opacity;
- if (!qFuzzyCompare(opacity, qreal(1.0)))
- setOpacity(opacity);
-
- setMask(QHighDpi::toNativeLocalRegion(window()->mask(), window()));
-
- // top-level QWindows may have an attached NSToolBar, call
- // update function which will attach to the NSWindow.
- if (!parentWindow && !isEmbeddedView)
- updateNSToolbar();
}
void QCocoaWindow::requestUpdate()
@@ -1529,7 +1598,10 @@ void QCocoaWindow::requestUpdate()
<< "using" << (updatesWithDisplayLink() ? "display-link" : "timer");
if (updatesWithDisplayLink()) {
- static_cast<QCocoaScreen *>(screen())->requestUpdate();
+ if (!static_cast<QCocoaScreen *>(screen())->requestUpdate()) {
+ qCDebug(lcQpaDrawing) << "Falling back to timer-based update request";
+ QPlatformWindow::requestUpdate();
+ }
} else {
// Fall back to the un-throttled timer-based callback
QPlatformWindow::requestUpdate();
@@ -1555,6 +1627,75 @@ void QCocoaWindow::requestActivateWindow()
[m_view.window makeKeyWindow];
}
+/*
+ Closes all popups, and removes observers and monitors.
+*/
+void QCocoaWindow::closeAllPopups()
+{
+ QGuiApplicationPrivate::instance()->closeAllPopups();
+
+ removePopupMonitor();
+}
+
+void QCocoaWindow::removePopupMonitor()
+{
+ if (s_globalMouseMonitor) {
+ [NSEvent removeMonitor:s_globalMouseMonitor];
+ s_globalMouseMonitor = nil;
+ }
+ if (s_applicationActivationObserver) {
+ [[[NSWorkspace sharedWorkspace] notificationCenter] removeObserver:s_applicationActivationObserver];
+ s_applicationActivationObserver = nil;
+ }
+}
+
+void QCocoaWindow::setupPopupMonitor()
+{
+ // we open a popup window while we are not active. None of our existing event
+ // handlers will get called if the user now clicks anywhere outside the application
+ // or activates another window. Use a global event monitor to watch for mouse
+ // presses, and close popups. We also want mouse tracking in the popup to work, so
+ // also watch for MouseMoved.
+ if (!s_globalMouseMonitor) {
+ // we only get LeftMouseDown events when we also set LeftMouseUp.
+ constexpr NSEventMask mouseButtonMask = NSEventTypeLeftMouseDown | NSEventTypeLeftMouseUp
+ | NSEventMaskRightMouseDown | NSEventMaskOtherMouseDown
+ | NSEventMaskMouseMoved;
+ s_globalMouseMonitor = [NSEvent addGlobalMonitorForEventsMatchingMask:mouseButtonMask
+ handler:^(NSEvent *e){
+ if (!QGuiApplicationPrivate::instance()->popupActive()) {
+ removePopupMonitor();
+ return;
+ }
+ const auto eventType = cocoaEvent2QtMouseEvent(e);
+ if (eventType == QEvent::MouseMove) {
+ if (s_windowUnderMouse) {
+ QWindow *window = s_windowUnderMouse->window();
+ const auto button = cocoaButton2QtButton(e);
+ const auto buttons = currentlyPressedMouseButtons();
+ const auto globalPoint = QCocoaScreen::mapFromNative(NSEvent.mouseLocation);
+ const auto localPoint = window->mapFromGlobal(globalPoint.toPoint());
+ QWindowSystemInterface::handleMouseEvent(window, localPoint, globalPoint,
+ buttons, button, eventType);
+ }
+ } else {
+ closeAllPopups();
+ }
+ }];
+ }
+ // The activation observer also gets called when we become active because the user clicks
+ // into the popup. This should not close the popup, so QCocoaApplicationDelegate's
+ // applicationDidBecomeActive implementation removes this observer.
+ if (!s_applicationActivationObserver) {
+ s_applicationActivationObserver = [[[NSWorkspace sharedWorkspace] notificationCenter]
+ addObserverForName:NSWorkspaceDidActivateApplicationNotification
+ object:nil queue:nil
+ usingBlock:^(NSNotification *){
+ closeAllPopups();
+ }];
+ }
+}
+
QCocoaNSWindow *QCocoaWindow::createNSWindow(bool shouldBePanel)
{
QMacAutoReleasePool pool;
@@ -1654,12 +1795,15 @@ QCocoaNSWindow *QCocoaWindow::createNSWindow(bool shouldBePanel)
// Qt::Tool windows hide on app deactivation, unless Qt::WA_MacAlwaysShowToolWindow is set
nsWindow.hidesOnDeactivate = ((type & Qt::Tool) == Qt::Tool) && !alwaysShowToolWindow();
- // Make popup windows show on the same desktop as the parent full-screen window
- nsWindow.collectionBehavior = NSWindowCollectionBehaviorFullScreenAuxiliary;
+ // Make popup windows show on the same desktop as the parent window
+ nsWindow.collectionBehavior = NSWindowCollectionBehaviorFullScreenAuxiliary
+ | NSWindowCollectionBehaviorMoveToActiveSpace;
if ((type & Qt::Popup) == Qt::Popup) {
nsWindow.hasShadow = YES;
nsWindow.animationBehavior = NSWindowAnimationBehaviorUtilityWindow;
+ if (QGuiApplication::applicationState() != Qt::ApplicationActive)
+ setupPopupMonitor();
}
}
@@ -1668,11 +1812,15 @@ QCocoaNSWindow *QCocoaWindow::createNSWindow(bool shouldBePanel)
applyContentBorderThickness(nsWindow);
- if (QColorSpace colorSpace = format().colorSpace(); colorSpace.isValid()) {
- NSData *iccData = colorSpace.iccProfile().toNSData();
- nsWindow.colorSpace = [[[NSColorSpace alloc] initWithICCProfileData:iccData] autorelease];
- qCDebug(lcQpaDrawing) << "Set" << this << "color space to" << nsWindow.colorSpace;
- }
+ // We propagate the view's color space granulary to both the IOSurfaces
+ // used for QSurface::RasterSurface, as well as the CAMetalLayer used for
+ // QSurface::MetalSurface, but for QSurface::OpenGLSurface we don't have
+ // that option as we use NSOpenGLContext instead of CAOpenGLLayer. As a
+ // workaround we set the NSWindow's color space, which affects GL drawing
+ // with NSOpenGLContext as well. This does not conflict with the granular
+ // modifications we do to each surface for raster or Metal.
+ if (auto *qtView = qnsview_cast(m_view))
+ nsWindow.colorSpace = qtView.colorSpace;
return nsWindow;
}
@@ -1682,14 +1830,6 @@ bool QCocoaWindow::alwaysShowToolWindow() const
return qt_mac_resolveOption(false, window(), "_q_macAlwaysShowToolWindow", "");
}
-void QCocoaWindow::removeMonitor()
-{
- if (!m_monitor)
- return;
- [NSEvent removeMonitor:m_monitor];
- m_monitor = nil;
-}
-
bool QCocoaWindow::setWindowModified(bool modified)
{
if (!isContentView())
@@ -1715,13 +1855,37 @@ void QCocoaWindow::setWindowCursor(NSCursor *cursor)
if (isForeignWindow())
return;
+ qCInfo(lcQpaMouse) << "Setting" << this << "cursor to" << cursor;
+
QNSView *view = qnsview_cast(m_view);
if (cursor == view.cursor)
return;
view.cursor = cursor;
+ // We're not using the the legacy cursor rects API to manage our
+ // cursor, but calling this function also invalidates AppKit's
+ // view of whether or not we need a cursorUpdate callback for
+ // our tracking area.
[m_view.window invalidateCursorRectsForView:m_view];
+
+ // We've informed AppKit that we need a cursorUpdate, but cursor
+ // updates for tracking areas are deferred in some cases, such as
+ // when the mouse is down, whereas we want a synchronous update.
+ // To ensure an updated cursor we synthesize a cursor update event
+ // now if the window is otherwise allowed to change the cursor.
+ auto locationInWindow = m_view.window.mouseLocationOutsideOfEventStream;
+ auto locationInSuperview = [m_view.superview convertPoint:locationInWindow fromView:nil];
+ bool mouseIsOverView = [m_view hitTest:locationInSuperview] == m_view;
+ auto utilityMask = NSWindowStyleMaskUtilityWindow | NSWindowStyleMaskTitled;
+ bool isUtilityWindow = (m_view.window.styleMask & utilityMask) == utilityMask;
+ if (mouseIsOverView && (m_view.window.keyWindow || isUtilityWindow)) {
+ qCDebug(lcQpaMouse) << "Synthesizing cursor update";
+ [m_view cursorUpdate:[NSEvent enterExitEventWithType:NSEventTypeCursorUpdate
+ location:locationInWindow modifierFlags:0 timestamp:0
+ windowNumber:m_view.window.windowNumber context:nil
+ eventNumber:0 trackingNumber:0 userData:0]];
+ }
}
void QCocoaWindow::registerTouch(bool enable)
@@ -1733,16 +1897,6 @@ void QCocoaWindow::registerTouch(bool enable)
m_view.allowedTouchTypes &= ~NSTouchTypeMaskIndirect;
}
-void QCocoaWindow::setContentBorderThickness(int topThickness, int bottomThickness)
-{
- m_topContentBorderThickness = topThickness;
- m_bottomContentBorderThickness = bottomThickness;
- bool enable = (topThickness > 0 || bottomThickness > 0);
- m_drawContentBorderGradient = enable;
-
- applyContentBorderThickness();
-}
-
void QCocoaWindow::registerContentBorderArea(quintptr identifier, int upper, int lower)
{
m_contentBorderAreas.insert(identifier, BorderRange(identifier, upper, lower));
@@ -1779,13 +1933,13 @@ void QCocoaWindow::applyContentBorderThickness(NSWindow *window)
// Find consecutive registered border areas, starting from the top.
std::vector<BorderRange> ranges(m_contentBorderAreas.cbegin(), m_contentBorderAreas.cend());
std::sort(ranges.begin(), ranges.end());
- int effectiveTopContentBorderThickness = m_topContentBorderThickness;
+ int effectiveTopContentBorderThickness = 0;
for (BorderRange range : ranges) {
// Skip disiabled ranges (typically hidden tool bars)
if (!m_enabledContentBorderAreas.value(range.identifier, false))
continue;
- // Is this sub-range adjacent to or overlaping the
+ // Is this sub-range adjacent to or overlapping the
// existing total border area range? If so merge
// it into the total range,
if (range.upper <= (effectiveTopContentBorderThickness + 1))
@@ -1794,7 +1948,7 @@ void QCocoaWindow::applyContentBorderThickness(NSWindow *window)
break;
}
- int effectiveBottomContentBorderThickness = m_bottomContentBorderThickness;
+ int effectiveBottomContentBorderThickness = 0;
[window setStyleMask:[window styleMask] | NSWindowStyleMaskTexturedBackground];
window.titlebarAppearsTransparent = YES;
@@ -1816,27 +1970,12 @@ void QCocoaWindow::applyContentBorderThickness(NSWindow *window)
[[[window contentView] superview] setNeedsDisplay:YES];
}
-void QCocoaWindow::updateNSToolbar()
-{
- if (!isContentView())
- return;
-
- NSToolbar *toolbar = QCocoaIntegration::instance()->toolbar(window());
- const NSWindow *window = m_view.window;
-
- if (window.toolbar == toolbar)
- return;
-
- window.toolbar = toolbar;
- window.showsToolbarButton = YES;
-}
-
bool QCocoaWindow::testContentBorderAreaPosition(int position) const
{
if (!m_drawContentBorderGradient || !isContentView())
return false;
- // Determine if the given y postion (relative to the content area) is inside the
+ // Determine if the given y position (relative to the content area) is inside the
// unified toolbar area. Note that the value returned by contentBorderThicknessForEdge
// includes the title bar height; subtract it.
const int contentBorderThickness = [m_view.window contentBorderThicknessForEdge:NSMaxYEdge];
@@ -1850,7 +1989,7 @@ qreal QCocoaWindow::devicePixelRatio() const
{
// The documented way to observe the relationship between device-independent
// and device pixels is to use one for the convertToBacking functions. Other
- // methods such as [NSWindow backingScaleFacor] might not give the correct
+ // methods such as [NSWindow backingScaleFactor] might not give the correct
// result, for example if setWantsBestResolutionOpenGLSurface is not set or
// or ignored by the OpenGL driver.
NSSize backingSize = [m_view convertSizeToBacking:NSMakeSize(1.0, 1.0)];
@@ -1877,6 +2016,22 @@ bool QCocoaWindow::shouldRefuseKeyWindowAndFirstResponder()
if (window()->flags() & (Qt::WindowDoesNotAcceptFocus | Qt::WindowTransparentForInput))
return true;
+ // For application modal windows, as well as direct parent windows
+ // of window modal windows, AppKit takes care of blocking interaction.
+ // The Qt expectation however, is that all transient parents of a
+ // window modal window is blocked, as reflected by QGuiApplication.
+ // We reflect this by returning false from this function for transient
+ // parents blocked by a modal window, but limit it to the cases not
+ // covered by AppKit to avoid potential unwanted side effects.
+ QWindow *modalWindow = nullptr;
+ if (QGuiApplicationPrivate::instance()->isWindowBlocked(window(), &modalWindow)) {
+ if (modalWindow->modality() == Qt::WindowModal && modalWindow->transientParent() != window()) {
+ qCDebug(lcQpaWindow) << "Refusing key window for" << this << "due to being"
+ << "blocked by" << modalWindow;
+ return true;
+ }
+ }
+
if (m_inSetVisible) {
QVariant showWithoutActivating = window()->property("_q_showWithoutActivating");
if (showWithoutActivating.isValid() && showWithoutActivating.toBool())
@@ -1886,6 +2041,20 @@ bool QCocoaWindow::shouldRefuseKeyWindowAndFirstResponder()
return false;
}
+bool QCocoaWindow::windowEvent(QEvent *event)
+{
+ switch (event->type()) {
+ case QEvent::WindowBlocked:
+ case QEvent::WindowUnblocked:
+ updateTitleBarButtons(window()->flags());
+ break;
+ default:
+ break;
+ }
+
+ return QPlatformWindow::windowEvent(event);
+}
+
QPoint QCocoaWindow::bottomLeftClippedByNSWindowOffset() const
{
if (!m_view)
@@ -1929,6 +2098,6 @@ QDebug operator<<(QDebug debug, const QCocoaWindow *window)
}
#endif // !QT_NO_DEBUG_STREAM
-#include "moc_qcocoawindow.cpp"
-
QT_END_NAMESPACE
+
+#include "moc_qcocoawindow.cpp"