summaryrefslogtreecommitdiffstats
path: root/src/plugins/platforms/cocoa/qnswindow.mm
diff options
context:
space:
mode:
Diffstat (limited to 'src/plugins/platforms/cocoa/qnswindow.mm')
-rw-r--r--src/plugins/platforms/cocoa/qnswindow.mm209
1 files changed, 120 insertions, 89 deletions
diff --git a/src/plugins/platforms/cocoa/qnswindow.mm b/src/plugins/platforms/cocoa/qnswindow.mm
index e7c7f6dc5d..74ba6f65ac 100644
--- a/src/plugins/platforms/cocoa/qnswindow.mm
+++ b/src/plugins/platforms/cocoa/qnswindow.mm
@@ -1,41 +1,5 @@
-/****************************************************************************
-**
-** Copyright (C) 2017 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) 2017 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
#if !defined(QNSWINDOW_PROTOCOL_IMPLMENTATION)
@@ -45,6 +9,7 @@
#include "qcocoawindow.h"
#include "qcocoahelpers.h"
#include "qcocoaeventdispatcher.h"
+#include "qcocoaintegration.h"
#include <qpa/qwindowsysteminterface.h>
#include <qoperatingsystemversion.h>
@@ -93,6 +58,15 @@ static bool isMouseEvent(NSEvent *ev)
}
@end
+
+NSWindow<QNSWindowProtocol> *qnswindow_cast(NSWindow *window)
+{
+ if ([window conformsToProtocol:@protocol(QNSWindowProtocol)])
+ return static_cast<QCocoaNSWindow *>(window);
+ else
+ return nil;
+}
+
@implementation QNSWindow
#define QNSWINDOW_PROTOCOL_IMPLMENTATION 1
#include "qnswindow.mm"
@@ -106,7 +80,7 @@ static bool isMouseEvent(NSEvent *ev)
// Unfortunately there's no NSWindowListOrderedBackToFront,
// so we have to manually reverse the order using an array.
- NSMutableArray<NSWindow *> *windows = [NSMutableArray<NSWindow *> new];
+ NSMutableArray<NSWindow *> *windows = [[NSMutableArray<NSWindow *> new] autorelease];
[application enumerateWindowsWithOptions:NSWindowListOrderedFrontToBack
usingBlock:^(NSWindow *window, BOOL *) {
// For some reason AppKit will give us nil-windows, skip those
@@ -133,9 +107,10 @@ static bool isMouseEvent(NSEvent *ev)
continue;
if ([window conformsToProtocol:@protocol(QNSWindowProtocol)]) {
- QCocoaWindow *cocoaWindow = static_cast<QCocoaNSWindow *>(window).platformWindow;
- window.level = notification.name == NSApplicationWillResignActiveNotification ?
- NSNormalWindowLevel : cocoaWindow->windowLevel(cocoaWindow->window()->flags());
+ if (QCocoaWindow *cocoaWindow = static_cast<QCocoaNSWindow *>(window).platformWindow) {
+ window.level = notification.name == NSApplicationWillResignActiveNotification ?
+ NSNormalWindowLevel : cocoaWindow->windowLevel(cocoaWindow->window()->flags());
+ }
}
// The documentation says that "when a window enters a new level, it’s ordered
@@ -180,6 +155,14 @@ static bool isMouseEvent(NSEvent *ev)
if (!NSApp.modalWindow)
return NO;
+ // Special case popup windows (menus, completions, etc), as these usually
+ // don't have a transient parent set, and we don't want to block them. The
+ // assumption is that these windows are only opened intermittently, from
+ // within windows that can already be interacted with in this modal session.
+ Qt::WindowType type = m_platformWindow->window()->type();
+ if (type == Qt::Popup)
+ return YES;
+
// If the current modal window (top level modal session) is not a Qt window we
// have no way of knowing if this window is transient child of the modal window.
if (![NSApp.modalWindow conformsToProtocol:@protocol(QNSWindowProtocol)])
@@ -194,45 +177,6 @@ static bool isMouseEvent(NSEvent *ev)
}
@end
-#if !defined(QT_APPLE_NO_PRIVATE_APIS)
-// When creating an NSWindow the worksWhenModal function is queried,
-// and the resulting state is used to set the corresponding window tag,
-// which the window server uses to determine whether or not the window
-// should be allowed to activate via mouse clicks in the title-bar.
-// Unfortunately, prior to macOS 10.15, this window tag was never
-// updated after the initial assignment in [NSWindow _commonAwake],
-// which meant that windows that dynamically change their worksWhenModal
-// state will behave as if they were never allowed to work when modal.
-// We work around this by manually updating the window tag when needed.
-
-typedef uint32_t CGSConnectionID;
-typedef uint32_t CGSWindowID;
-
-extern "C" {
-CGSConnectionID CGSMainConnectionID() __attribute__((weak_import));
-OSStatus CGSSetWindowTags(const CGSConnectionID, const CGSWindowID, int *, int) __attribute__((weak_import));
-OSStatus CGSClearWindowTags(const CGSConnectionID, const CGSWindowID, int *, int) __attribute__((weak_import));
-}
-
-@interface QNSPanel (WorksWhenModalWindowTagWorkaround) @end
-@implementation QNSPanel (WorksWhenModalWindowTagWorkaround)
-- (void)setWorksWhenModal:(BOOL)worksWhenModal
-{
- [super setWorksWhenModal:worksWhenModal];
-
- if (QOperatingSystemVersion::current() < QOperatingSystemVersion::MacOSCatalina) {
- if (CGSMainConnectionID && CGSSetWindowTags && CGSClearWindowTags) {
- static int kWorksWhenModalWindowTag = 0x40;
- auto *function = worksWhenModal ? CGSSetWindowTags : CGSClearWindowTags;
- function(CGSMainConnectionID(), self.windowNumber, &kWorksWhenModalWindowTag, 64);
- } else {
- qWarning() << "Missing APIs for window tag handling, can not update worksWhenModal state";
- }
- }
-}
-@end
-#endif // QT_APPLE_NO_PRIVATE_APIS
-
#else // QNSWINDOW_PROTOCOL_IMPLMENTATION
// The following content is mixed in to the QNSWindow and QNSPanel classes via includes
@@ -240,6 +184,7 @@ OSStatus CGSClearWindowTags(const CGSConnectionID, const CGSWindowID, int *, int
{
// Member variables
QPointer<QCocoaWindow> m_platformWindow;
+ bool m_isMinimizing;
}
- (instancetype)initWithContentRect:(NSRect)contentRect styleMask:(NSWindowStyleMask)style
@@ -251,6 +196,8 @@ OSStatus CGSClearWindowTags(const CGSConnectionID, const CGSWindowID, int *, int
// we can properly reflect the window's state during initialization.
m_platformWindow = window;
+ m_isMinimizing = false;
+
return [super initWithContentRect:contentRect styleMask:style backing:backingStoreType defer:defer screen:screen];
}
@@ -259,6 +206,29 @@ OSStatus CGSClearWindowTags(const CGSConnectionID, const CGSWindowID, int *, int
return m_platformWindow;
}
+- (void)setContentView:(NSView*)view
+{
+ [super setContentView:view];
+
+ if (!qnsview_cast(self.contentView))
+ return;
+
+ // Now that we're the content view, we can apply the properties of
+ // the QWindow. We do this here, instead of in init, so that we can
+ // use the same code paths for setting these properties during
+ // NSWindow initialization as we do when setting them later on.
+ const QWindow *window = m_platformWindow->window();
+ qCDebug(lcQpaWindow) << "Reflecting" << window << "state to" << self;
+
+ m_platformWindow->propagateSizeHints();
+ m_platformWindow->setWindowFlags(window->flags());
+ m_platformWindow->setWindowTitle(window->title());
+ m_platformWindow->setWindowFilePath(window->filePath()); // Also sets window icon
+ m_platformWindow->setWindowState(window->windowState());
+ m_platformWindow->setOpacity(window->opacity());
+ m_platformWindow->setVisible(window->isVisible());
+}
+
- (NSString *)description
{
NSMutableString *description = [NSMutableString stringWithString:[super description]];
@@ -315,8 +285,23 @@ OSStatus CGSClearWindowTags(const CGSConnectionID, const CGSWindowID, int *, int
- (NSColor *)backgroundColor
{
- return self.styleMask == NSWindowStyleMaskBorderless ?
- [NSColor clearColor] : [super backgroundColor];
+ // FIXME: Plumb to a WA_NoSystemBackground-like window flag,
+ // or a QWindow::backgroundColor() property. In the meantime
+ // we assume that if you have translucent content, without a
+ // frame then you intend to do all background drawing yourself.
+ const QWindow *window = m_platformWindow ? m_platformWindow->window() : nullptr;
+ if (!self.opaque && window) {
+ // Qt::Popup also requires clearColor - in qmacstyle
+ // we fill background using a special path with rounded corners.
+ if (window->flags().testFlag(Qt::FramelessWindowHint)
+ || (window->flags() & Qt::WindowType_Mask) == Qt::Popup)
+ return [NSColor clearColor];
+ }
+
+ // This still allows you to have translucent content with a frame,
+ // where the system background (or color set via NSWindow) will
+ // shine through.
+ return [super backgroundColor];
}
- (void)sendEvent:(NSEvent*)theEvent
@@ -341,18 +326,64 @@ OSStatus CGSClearWindowTags(const CGSConnectionID, const CGSWindowID, int *, int
return;
}
+ const bool mouseEventInFrameStrut = [theEvent, self]{
+ if (isMouseEvent(theEvent)) {
+ const NSPoint loc = theEvent.locationInWindow;
+ const NSRect windowFrame = [self convertRectFromScreen:self.frame];
+ const NSRect contentFrame = self.contentView.frame;
+ if (NSMouseInRect(loc, windowFrame, NO) && !NSMouseInRect(loc, contentFrame, NO))
+ return true;
+ }
+ return false;
+ }();
+ // Any mouse-press in the frame of the window, including the title bar buttons, should
+ // close open popups. Presses within the window's content are handled to do that in the
+ // NSView::mouseDown implementation.
+ if (theEvent.type == NSEventTypeLeftMouseDown && mouseEventInFrameStrut)
+ QGuiApplicationPrivate::instance()->closeAllPopups();
+
[super sendEvent:theEvent];
if (!m_platformWindow)
return; // Platform window went away while processing event
- if (m_platformWindow->frameStrutEventsEnabled() && isMouseEvent(theEvent)) {
- NSPoint loc = [theEvent locationInWindow];
- NSRect windowFrame = [self convertRectFromScreen:self.frame];
- NSRect contentFrame = self.contentView.frame;
- if (NSMouseInRect(loc, windowFrame, NO) && !NSMouseInRect(loc, contentFrame, NO))
- [qnsview_cast(m_platformWindow->view()) handleFrameStrutMouseEvent:theEvent];
+ // Cocoa will not deliver mouse events to a window that is modally blocked (by Cocoa,
+ // not Qt). However, an active popup is expected to grab any mouse event within the
+ // application, so we need to handle those explicitly and trust Qt's isWindowBlocked
+ // implementation to eat events that shouldn't be delivered anyway.
+ if (isMouseEvent(theEvent) && QGuiApplicationPrivate::instance()->popupActive()
+ && QGuiApplicationPrivate::instance()->isWindowBlocked(m_platformWindow->window(), nullptr)) {
+ qCDebug(lcQpaWindow) << "Mouse event over modally blocked window" << m_platformWindow->window()
+ << "while popup is open - redirecting";
+ [qnsview_cast(m_platformWindow->view()) handleMouseEvent:theEvent];
}
+ if (m_platformWindow->frameStrutEventsEnabled() && mouseEventInFrameStrut)
+ [qnsview_cast(m_platformWindow->view()) handleFrameStrutMouseEvent:theEvent];
+}
+
+- (void)miniaturize:(id)sender
+{
+ QBoolBlocker miniaturizeTracker(m_isMinimizing, true);
+ [super miniaturize:sender];
+}
+
+- (NSButton *)standardWindowButton:(NSWindowButton)buttonType
+{
+ NSButton *button = [super standardWindowButton:buttonType];
+
+ // When an NSWindow is asked to minimize it will check the
+ // NSWindowMiniaturizeButton for enablement before continuing,
+ // even if the style mask includes NSWindowStyleMaskMiniaturizable.
+ // To ensure that a window can be minimized, even when the
+ // minimize button has been disabled in response to the user
+ // setting CustomizeWindowHint, we temporarily return a default
+ // minimize-button that we haven't modified in updateTitleBarButtons.
+ // This ensures the window can be minimized, without visually
+ // toggling the actual minimize-button on and off.
+ if (buttonType == NSWindowMiniaturizeButton && m_isMinimizing && !button.enabled)
+ return [NSWindow standardWindowButton:buttonType forStyleMask:self.styleMask];
+
+ return button;
}
- (void)closeAndRelease