diff options
Diffstat (limited to 'src/plugins/platforms/cocoa')
90 files changed, 3485 insertions, 4803 deletions
diff --git a/src/plugins/platforms/cocoa/CMakeLists.txt b/src/plugins/platforms/cocoa/CMakeLists.txt index 21f87dae83..92e681d8fb 100644 --- a/src/plugins/platforms/cocoa/CMakeLists.txt +++ b/src/plugins/platforms/cocoa/CMakeLists.txt @@ -1,6 +1,5 @@ -# Generated from cocoa.pro. - -# special case: +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause ##################################################################### ## QCocoaIntegrationPlugin Plugin: @@ -8,7 +7,7 @@ qt_internal_add_plugin(QCocoaIntegrationPlugin OUTPUT_NAME qcocoa - DEFAULT_IF ${QT_QPA_DEFAULT_PLATFORM} MATCHES cocoa # special case + DEFAULT_IF ${QT_QPA_DEFAULT_PLATFORM} MATCHES cocoa PLUGIN_TYPE platforms SOURCES main.mm @@ -16,12 +15,9 @@ qt_internal_add_plugin(QCocoaIntegrationPlugin qcocoaapplicationdelegate.h qcocoaapplicationdelegate.mm qcocoabackingstore.h qcocoabackingstore.mm qcocoaclipboard.h qcocoaclipboard.mm - qcocoacolordialoghelper.h qcocoacolordialoghelper.mm qcocoacursor.h qcocoacursor.mm qcocoadrag.h qcocoadrag.mm qcocoaeventdispatcher.h qcocoaeventdispatcher.mm - qcocoafiledialoghelper.h qcocoafiledialoghelper.mm - qcocoafontdialoghelper.h qcocoafontdialoghelper.mm qcocoahelpers.h qcocoahelpers.mm qcocoainputcontext.h qcocoainputcontext.mm qcocoaintegration.h qcocoaintegration.mm @@ -48,6 +44,7 @@ qt_internal_add_plugin(QCocoaIntegrationPlugin qcocoacolordialoghelper.h qcocoacolordialoghelper.mm qcocoafiledialoghelper.h qcocoafiledialoghelper.mm qcocoafontdialoghelper.h qcocoafontdialoghelper.mm + qcocoamessagedialog.h qcocoamessagedialog.mm DEFINES QT_NO_FOREACH LIBRARIES @@ -59,15 +56,14 @@ qt_internal_add_plugin(QCocoaIntegrationPlugin ${FWIOSurface} ${FWMetal} ${FWQuartzCore} + ${FWUniformTypeIdentifiers} Qt::Core Qt::CorePrivate Qt::Gui Qt::GuiPrivate ) -# special case begin qt_disable_apple_app_extension_api_only(QCocoaIntegrationPlugin) -# special case end # Resources: set(qcocoaresources_resource_files @@ -83,10 +79,6 @@ qt_internal_add_resource(QCocoaIntegrationPlugin "qcocoaresources" ${qcocoaresources_resource_files} ) - -#### Keys ignored in scope 1:.:.:cocoa.pro:<TRUE>: -# OTHER_FILES = "cocoa.json" - ## Scopes: ##################################################################### @@ -110,8 +102,3 @@ qt_internal_extend_target(QCocoaIntegrationPlugin CONDITION QT_FEATURE_sessionma SOURCES qcocoasessionmanager.cpp qcocoasessionmanager.h ) - -#### Keys ignored in scope 7:.:.:cocoa.pro:TARGET Qt::Widgets: -# QT_FOR_CONFIG = "widgets" -#### Keys ignored in scope 12:.:.:cocoa.pro:NOT TARGET___equals____ss_QT_DEFAULT_QPA_PLUGIN: -# PLUGIN_EXTENDS = "-" diff --git a/src/plugins/platforms/cocoa/main.mm b/src/plugins/platforms/cocoa/main.mm index 89ecdf46f9..7fbf26cb75 100644 --- a/src/plugins/platforms/cocoa/main.mm +++ b/src/plugins/platforms/cocoa/main.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> @@ -44,8 +8,12 @@ #include "qcocoaintegration.h" #include "qcocoatheme.h" +#include <QtCore/private/qcore_mac_p.h> + QT_BEGIN_NAMESPACE +using namespace Qt::StringLiterals; + class QCocoaIntegrationPlugin : public QPlatformIntegrationPlugin { Q_OBJECT @@ -57,7 +25,7 @@ public: QPlatformIntegration * QCocoaIntegrationPlugin::create(const QString& system, const QStringList& paramList) { QMacAutoReleasePool pool; - if (system.compare(QLatin1String("cocoa"), Qt::CaseInsensitive) == 0) + if (system.compare("cocoa"_L1, Qt::CaseInsensitive) == 0) return new QCocoaIntegration(paramList); return nullptr; diff --git a/src/plugins/platforms/cocoa/qcocoaaccessibility.h b/src/plugins/platforms/cocoa/qcocoaaccessibility.h index 5f89a41e65..cdd9aafc80 100644 --- a/src/plugins/platforms/cocoa/qcocoaaccessibility.h +++ b/src/plugins/platforms/cocoa/qcocoaaccessibility.h @@ -1,46 +1,12 @@ -/**************************************************************************** -** -** 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 #ifndef QCOCOAACCESIBILITY_H #define QCOCOAACCESIBILITY_H -#ifndef QT_NO_ACCESSIBILITY +#include <QtGui/qtguiglobal.h> + +#if QT_CONFIG(accessibility) #include <qpa/qplatformaccessibility.h> @@ -94,6 +60,6 @@ id getValueAttribute(QAccessibleInterface *interface); QT_END_NAMESPACE -#endif // QT_NO_ACCESSIBILITY +#endif // QT_CONFIG(accessibility) #endif // QCOCOAACCESIBILITY_H diff --git a/src/plugins/platforms/cocoa/qcocoaaccessibility.mm b/src/plugins/platforms/cocoa/qcocoaaccessibility.mm index 0f5c638f7c..c5e40a4087 100644 --- a/src/plugins/platforms/cocoa/qcocoaaccessibility.mm +++ b/src/plugins/platforms/cocoa/qcocoaaccessibility.mm @@ -1,52 +1,19 @@ -/**************************************************************************** -** -** 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 "qcocoaaccessibility.h" #include "qcocoaaccessibilityelement.h" #include <QtGui/qaccessible.h> +#include <QtCore/qmap.h> #include <private/qcore_mac_p.h> QT_BEGIN_NAMESPACE -#ifndef QT_NO_ACCESSIBILITY +using namespace Qt::StringLiterals; + +#if QT_CONFIG(accessibility) QCocoaAccessibility::QCocoaAccessibility() { @@ -87,6 +54,10 @@ void QCocoaAccessibility::notifyAccessibilityUpdate(QAccessibleEvent *event) case QAccessible::NameChanged: NSAccessibilityPostNotification(element, NSAccessibilityTitleChangedNotification); break; + case QAccessible::TableModelChanged: + // ### Could NSAccessibilityRowCountChangedNotification be relevant here? + [element updateTableModel]; + break; default: break; } @@ -146,7 +117,6 @@ static void populateRoleMap() roleMap[QAccessible::ColumnHeader] = NSAccessibilityColumnRole; roleMap[QAccessible::Row] = NSAccessibilityRowRole; roleMap[QAccessible::RowHeader] = NSAccessibilityRowRole; - roleMap[QAccessible::Cell] = NSAccessibilityTextFieldRole; roleMap[QAccessible::Button] = NSAccessibilityButtonRole; roleMap[QAccessible::EditableText] = NSAccessibilityTextFieldRole; roleMap[QAccessible::Link] = NSAccessibilityLinkRole; @@ -154,7 +124,7 @@ static void populateRoleMap() roleMap[QAccessible::Splitter] = NSAccessibilitySplitGroupRole; roleMap[QAccessible::List] = NSAccessibilityListRole; roleMap[QAccessible::ListItem] = NSAccessibilityStaticTextRole; - roleMap[QAccessible::Cell] = NSAccessibilityStaticTextRole; + roleMap[QAccessible::Cell] = NSAccessibilityCellRole; roleMap[QAccessible::Client] = NSAccessibilityGroupRole; roleMap[QAccessible::Paragraph] = NSAccessibilityGroupRole; roleMap[QAccessible::Section] = NSAccessibilityGroupRole; @@ -166,6 +136,7 @@ static void populateRoleMap() roleMap[QAccessible::Note] = NSAccessibilityGroupRole; roleMap[QAccessible::ComplementaryContent] = NSAccessibilityGroupRole; roleMap[QAccessible::Graphic] = NSAccessibilityImageRole; + roleMap[QAccessible::Tree] = NSAccessibilityOutlineRole; } /* @@ -184,6 +155,8 @@ NSString *macRole(QAccessibleInterface *interface) if (roleMap.contains(qtRole)) { // MAC_ACCESSIBILTY_DEBUG() << "return" << roleMap[qtRole]; + if (roleMap[qtRole] == NSAccessibilityComboBoxRole && !interface->state().editable) + return NSAccessibilityMenuButtonRole; if (roleMap[qtRole] == NSAccessibilityTextFieldRole && interface->state().multiLine) return NSAccessibilityTextAreaRole; return roleMap[qtRole]; @@ -247,12 +220,12 @@ bool shouldBeIgnored(QAccessibleInterface *interface) return true; if (QObject * const object = interface->object()) { - const QString className = QLatin1String(object->metaObject()->className()); + const QString className = QLatin1StringView(object->metaObject()->className()); // VoiceOver focusing on tool tips can be confusing. The contents of the // tool tip is available through the description attribute anyway, so // we disable accessibility for tool tips. - if (className == QLatin1String("QTipLabel")) + if (className == "QTipLabel"_L1) return true; } @@ -399,7 +372,7 @@ id getValueAttribute(QAccessibleInterface *interface) } // namespace QCocoaAccessible -#endif // QT_NO_ACCESSIBILITY +#endif // QT_CONFIG(accessibility) QT_END_NAMESPACE diff --git a/src/plugins/platforms/cocoa/qcocoaaccessibilityelement.h b/src/plugins/platforms/cocoa/qcocoaaccessibilityelement.h index 93158e562f..a96ab55735 100644 --- a/src/plugins/platforms/cocoa/qcocoaaccessibilityelement.h +++ b/src/plugins/platforms/cocoa/qcocoaaccessibilityelement.h @@ -1,57 +1,25 @@ -/**************************************************************************** -** -** 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 #ifndef QCOCOAACCESIBILITYELEMENT_H #define QCOCOAACCESIBILITYELEMENT_H -#ifndef QT_NO_ACCESSIBILITY +#include <QtGui/qtguiglobal.h> -#include <QtCore/qglobal.h> +#if QT_CONFIG(accessibility) #include <QtCore/private/qcore_mac_p.h> #include <QtGui/qaccessible.h> QT_DECLARE_NAMESPACED_OBJC_INTERFACE(QMacAccessibilityElement, NSObject <NSAccessibilityElement> - (instancetype)initWithId:(QAccessible::Id)anId; +- (instancetype)initWithId:(QAccessible::Id)anId role:(NSAccessibilityRole)role; + (instancetype)elementWithId:(QAccessible::Id)anId; ++ (instancetype)elementWithInterface:(QAccessibleInterface *)iface; +- (void)updateTableModel; +- (QAccessibleInterface *)qtInterface; ) -#endif // QT_NO_ACCESSIBILITY +#endif // QT_CONFIG(accessibility) #endif // QCOCOAACCESIBILITYELEMENT_H diff --git a/src/plugins/platforms/cocoa/qcocoaaccessibilityelement.mm b/src/plugins/platforms/cocoa/qcocoaaccessibilityelement.mm index 9e26f93b9d..8d4d6d683d 100644 --- a/src/plugins/platforms/cocoa/qcocoaaccessibilityelement.mm +++ b/src/plugins/platforms/cocoa/qcocoaaccessibilityelement.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> @@ -45,13 +9,18 @@ #include "qcocoawindow.h" #include "qcocoascreen.h" +#include <QtCore/qlogging.h> #include <QtGui/private/qaccessiblecache_p.h> #include <QtGui/private/qaccessiblebridgeutils_p.h> #include <QtGui/qaccessible.h> QT_USE_NAMESPACE -#ifndef QT_NO_ACCESSIBILITY +Q_LOGGING_CATEGORY(lcAccessibilityTable, "qt.accessibility.table") + +using namespace Qt::Literals::StringLiterals; + +#if QT_CONFIG(accessibility) /** * Converts between absolute character offsets and line numbers of a @@ -116,14 +85,92 @@ static void convertLineOffset(QAccessibleTextInterface *text, int *line, int *of @implementation QMacAccessibilityElement { QAccessible::Id axid; + int m_rowIndex; + int m_columnIndex; + + // used by NSAccessibilityTable + NSMutableArray<QMacAccessibilityElement *> *rows; // corresponds to accessibilityRows + NSMutableArray<QMacAccessibilityElement *> *columns; // corresponds to accessibilityColumns + + // If synthesizedRole is set, this means that this objects does not have a corresponding + // QAccessibleInterface, but it is synthesized by the cocoa plugin in order to meet the + // NSAccessibility requirements. + // The ownership is controlled by the parent object identified with the axid member variable. + // (Therefore, if this member is set, this objects axid member is the same as the parents axid + // member) + NSString *synthesizedRole; } - (instancetype)initWithId:(QAccessible::Id)anId { + return [self initWithId:anId role:nil]; +} + +- (instancetype)initWithId:(QAccessible::Id)anId role:(NSAccessibilityRole)role +{ Q_ASSERT((int)anId < 0); self = [super init]; if (self) { axid = anId; + m_rowIndex = -1; + m_columnIndex = -1; + rows = nil; + columns = nil; + synthesizedRole = role; + // table: if this is not created as an element managed by the table, then + // it's either the table itself, or an element created for an already existing + // cell interface (or an element that's not at all related to a table). + if (!synthesizedRole) { + if (QAccessibleInterface *iface = QAccessible::accessibleInterface(axid)) { + if (iface->tableInterface()) { + [self updateTableModel]; + } else if (const auto *cell = iface->tableCellInterface()) { + // If we create an element for a table cell, initialize it with row/column + // and insert it into the corresponding row's columns array. + m_rowIndex = cell->rowIndex(); + m_columnIndex = cell->columnIndex(); + QAccessibleInterface *table = cell->table(); + Q_ASSERT(table); + QAccessibleTableInterface *tableInterface = table->tableInterface(); + if (tableInterface) { + auto *tableElement = [QMacAccessibilityElement elementWithInterface:table]; + Q_ASSERT(tableElement); + if (!tableElement->rows + || int(tableElement->rows.count) <= m_rowIndex + || int(tableElement->rows.count) != tableInterface->rowCount()) { + qCWarning(lcAccessibilityTable) + << "Cell requested for row" << m_rowIndex << "is out of" + << "bounds for table with" << (tableElement->rows ? + tableElement->rows.count : tableInterface->rowCount()) + << "rows! Resizing table model."; + [tableElement updateTableModel]; + } + + Q_ASSERT(tableElement->rows); + Q_ASSERT(int(tableElement->rows.count) > m_rowIndex); + + auto *rowElement = tableElement->rows[m_rowIndex]; + if (!rowElement->columns || int(rowElement->columns.count) != tableInterface->columnCount()) { + if (rowElement->columns) { + qCWarning(lcAccessibilityTable) + << "Table representation column count is out of sync:" + << rowElement->columns.count << "!=" << tableInterface->columnCount(); + } + rowElement->columns = [rowElement populateTableRow:rowElement->columns + count:tableInterface->columnCount()]; + } + + qCDebug(lcAccessibilityTable) << "Creating cell representation for" + << m_rowIndex << m_columnIndex + << "in table with" + << tableElement->rows.count << "rows and" + << rowElement->columns.count << "columns"; + + rowElement->columns[m_columnIndex] = self; + } + } + } + } } return self; @@ -139,30 +186,45 @@ static void convertLineOffset(QAccessibleTextInterface *text, int *line, int *of QMacAccessibilityElement *element = cache->elementForId(anId); if (!element) { - QAccessibleInterface *iface = QAccessible::accessibleInterface(anId); - Q_ASSERT(iface); - if (!iface || !iface->isValid()) - return nil; + Q_ASSERT(QAccessible::accessibleInterface(anId)); element = [[self alloc] initWithId:anId]; cache->insertElement(anId, element); } return element; } ++ (instancetype)elementWithInterface:(QAccessibleInterface *)iface +{ + Q_ASSERT(iface); + if (!iface) + return nil; + + const QAccessible::Id anId = QAccessible::uniqueId(iface); + return [self elementWithId:anId]; +} + - (void)invalidate { axid = 0; + rows = nil; + columns = nil; + synthesizedRole = nil; + NSAccessibilityPostNotification(self, NSAccessibilityUIElementDestroyedNotification); [self release]; } - (void)dealloc { + if (rows) + [rows release]; // will also release all entries first + if (columns) + [columns release]; // will also release all entries first [super dealloc]; } - (BOOL)isEqual:(id)object { if ([object isKindOfClass:[QMacAccessibilityElement class]]) { QMacAccessibilityElement *other = object; - return other->axid == axid; + return other->axid == axid && other->synthesizedRole == synthesizedRole; } else { return NO; } @@ -172,16 +234,137 @@ static void convertLineOffset(QAccessibleTextInterface *text, int *line, int *of return axid; } +- (BOOL)isManagedByParent { + return synthesizedRole != nil; +} + +- (NSMutableArray *)populateTableArray:(NSMutableArray *)array role:(NSAccessibilityRole)role count:(int)count +{ + if (QAccessibleInterface *iface = self.qtInterface) { + if (array && int(array.count) != count) { + [array release]; + array = nil; + } + if (!array) { + array = [NSMutableArray<QMacAccessibilityElement *> arrayWithCapacity:count]; + [array retain]; + } else { + [array removeAllObjects]; + } + Q_ASSERT(array); + for (int n = 0; n < count; ++n) { + // columns will have same axid as table (but not inserted in cache) + QMacAccessibilityElement *element = + [[QMacAccessibilityElement alloc] initWithId:axid role:role]; + if (element) { + if (role == NSAccessibilityRowRole) + element->m_rowIndex = n; + else if (role == NSAccessibilityColumnRole) + element->m_columnIndex = n; + [array addObject:element]; + [element release]; + } else { + qWarning("QCocoaAccessibility: invalid child"); + } + } + return array; + } + return nil; +} + +- (NSMutableArray *)populateTableRow:(NSMutableArray *)array count:(int)count +{ + Q_ASSERT(synthesizedRole == NSAccessibilityRowRole); + if (array && int(array.count) != count) { + [array release]; + array = nil; + } + + if (!array) { + array = [NSMutableArray<QMacAccessibilityElement *> arrayWithCapacity:count]; + [array retain]; + // When macOS asks for the children of a row, then we populate the row's column + // array with synthetic elements as place holders. This way, we don't have to + // create QAccessibleInterfaces for every cell before they are really needed. + // We don't add those synthetic elements into the cache, and we give them the + // same axid as the table. This way, we can get easily to the table, and from + // there to the QAccessibleInterface for the cell, when we have to eventually + // associate such an interface with the element (at which point it is no longer + // a placeholder). + for (int n = 0; n < count; ++n) { + // columns will have same axid as table (but not inserted in cache) + QMacAccessibilityElement *cell = + [[QMacAccessibilityElement alloc] initWithId:axid role:NSAccessibilityCellRole]; + if (cell) { + cell->m_rowIndex = m_rowIndex; + cell->m_columnIndex = n; + [array addObject:cell]; + } + } + } + Q_ASSERT(array); + return array; +} + +- (void)updateTableModel +{ + if (QAccessibleInterface *iface = self.qtInterface) { + if (QAccessibleTableInterface *table = iface->tableInterface()) { + Q_ASSERT(!self.isManagedByParent); + qCDebug(lcAccessibilityTable) << "Updating table representation with" + << table->rowCount() << table->columnCount(); + rows = [self populateTableArray:rows role:NSAccessibilityRowRole count:table->rowCount()]; + columns = [self populateTableArray:columns role:NSAccessibilityColumnRole count:table->columnCount()]; + } + } +} + +- (QAccessibleInterface *)qtInterface +{ + QAccessibleInterface *iface = QAccessible::accessibleInterface(axid); + if (!iface || !iface->isValid()) + return nullptr; + + // If this is a placeholder element for a table cell, associate it with the + // cell interface (which will be created now, if needed). The current axid is + // for the table to which the cell belongs, so iface is pointing at the table. + if (synthesizedRole == NSAccessibilityCellRole) { + // get the cell interface - there must be a valid one + QAccessibleTableInterface *table = iface->tableInterface(); + Q_ASSERT(table); + QAccessibleInterface *cell = table->cellAt(m_rowIndex, m_columnIndex); + if (!cell) + return nullptr; + Q_ASSERT(cell->isValid()); + iface = cell; + + // no longer a placeholder + axid = QAccessible::uniqueId(cell); + synthesizedRole = nil; + + QAccessibleCache *cache = QAccessibleCache::instance(); + if (QMacAccessibilityElement *cellElement = cache->elementForId(axid)) { + // there already is another, non-placeholder element in the cache + Q_ASSERT(cellElement->synthesizedRole == nil); + // we have to release it if it's not us + if (cellElement != self) { + // for the same cell position + Q_ASSERT(cellElement->m_rowIndex == m_rowIndex && cellElement->m_columnIndex == m_columnIndex); + [cellElement release]; + } + } + + cache->insertElement(axid, self); + } + return iface; +} + // // accessibility protocol // - (BOOL)isAccessibilityFocused { - QAccessibleInterface *iface = QAccessible::accessibleInterface(axid); - if (!iface || !iface->isValid()) { - return false; - } // Just check if the app thinks we're focused. id focusedElement = NSApp.accessibilityApplicationFocusedUIElement; return [focusedElement isEqual:self]; @@ -192,7 +375,7 @@ static void convertLineOffset(QAccessibleTextInterface *text, int *line, int *of + (id) lineNumberForIndex: (int)index forText:(const QString &)text { auto textBefore = QStringView(text).left(index); - int newlines = textBefore.count(QLatin1Char('\n')); + qsizetype newlines = textBefore.count(u'\n'); return @(newlines); } @@ -201,31 +384,119 @@ static void convertLineOffset(QAccessibleTextInterface *text, int *line, int *of } - (NSString *) accessibilityRole { - QAccessibleInterface *iface = QAccessible::accessibleInterface(axid); - if (!iface || !iface->isValid()) - return NSAccessibilityUnknownRole; - return QCocoaAccessible::macRole(iface); + // shortcut for cells, rows, and columns in a table + if (synthesizedRole) + return synthesizedRole; + if (QAccessibleInterface *iface = self.qtInterface) + return QCocoaAccessible::macRole(iface); + return NSAccessibilityUnknownRole; } - (NSString *) accessibilitySubRole { - QAccessibleInterface *iface = QAccessible::accessibleInterface(axid); - if (!iface || !iface->isValid()) - return NSAccessibilityUnknownRole; - return QCocoaAccessible::macSubrole(iface); + if (QAccessibleInterface *iface = self.qtInterface) + return QCocoaAccessible::macSubrole(iface); + return NSAccessibilityUnknownRole; } - (NSString *) accessibilityRoleDescription { - QAccessibleInterface *iface = QAccessible::accessibleInterface(axid); - if (!iface || !iface->isValid()) - return NSAccessibilityUnknownRole; - return NSAccessibilityRoleDescription(self.accessibilityRole, self.accessibilitySubRole); + if (QAccessibleInterface *iface = self.qtInterface) + return NSAccessibilityRoleDescription(self.accessibilityRole, self.accessibilitySubRole); + return NSAccessibilityUnknownRole; } - (NSArray *) accessibilityChildren { + // shortcut for cells + if (synthesizedRole == NSAccessibilityCellRole) + return nil; + + QAccessibleInterface *iface = self.qtInterface; + if (!iface) + return nil; + if (QAccessibleTableInterface *table = iface->tableInterface()) { + // either a table or table rows/columns + if (!synthesizedRole) { + // This is the table element, parent of all rows and columns + /* + * Typical 2x2 table hierarchy as can be observed in a table found under + * Apple -> System Settings -> General -> Login Items (macOS 13) + * + * (AXTable) + * | Columns: NSArray* (2 items) + * | Rows: NSArray* (2 items) + * | Visible Columns: NSArray* (2 items) + * | Visible Rows: NSArray* (2 items) + * | Children: NSArray* (5 items) + +----<--| Header: (AXGroup) + | * +-- (AXRow) + | * | +-- (AXText) + | * | +-- (AXTextField) + | * +-- (AXRow) + | * | +-- (AXText) + | * | +-- (AXTextField) + | * +-- (AXColumn) + | * | Header: "Item" (sort button) + | * | Index: 0 + | * | Rows: NSArray* (2 items) + | * | Visible Rows: NSArray* (2 items) + | * +-- (AXColumn) + | * | Header: "Kind" (sort button) + | * | Index: 1 + | * | Rows: NSArray* (2 items) + | * | Visible Rows: NSArray* (2 items) + +----> +-- (AXGroup) + * +-- (AXButton/AXSortButton) Item [NSAccessibilityTableHeaderCellProxy] + * +-- (AXButton/AXSortButton) Kind [NSAccessibilityTableHeaderCellProxy] + */ + NSArray *rs = [self accessibilityRows]; + NSArray *cs = [self accessibilityColumns]; + const int rCount = int([rs count]); + const int cCount = int([cs count]); + int childCount = rCount + cCount; + NSMutableArray<QMacAccessibilityElement *> *tableChildren = + [NSMutableArray<QMacAccessibilityElement *> arrayWithCapacity:childCount]; + for (int i = 0; i < rCount; ++i) { + [tableChildren addObject:[rs objectAtIndex:i]]; + } + for (int i = 0; i < cCount; ++i) { + [tableChildren addObject:[cs objectAtIndex:i]]; + } + return NSAccessibilityUnignoredChildren(tableChildren); + } else if (synthesizedRole == NSAccessibilityColumnRole) { + return nil; + } else if (synthesizedRole == NSAccessibilityRowRole) { + // axid matches the parent table axid so that we can easily find the parent table + // children of row are cell/any items + Q_ASSERT(m_rowIndex >= 0); + const int numColumns = table->columnCount(); + columns = [self populateTableRow:columns count:numColumns]; + return NSAccessibilityUnignoredChildren(columns); + } + } + return QCocoaAccessible::unignoredChildren(iface); +} + +- (NSArray *) accessibilitySelectedChildren { QAccessibleInterface *iface = QAccessible::accessibleInterface(axid); if (!iface || !iface->isValid()) return nil; - return QCocoaAccessible::unignoredChildren(iface); + + QAccessibleSelectionInterface *selection = iface->selectionInterface(); + if (!selection) + return nil; + + const QList<QAccessibleInterface *> selectedList = selection->selectedItems(); + const qsizetype numSelected = selectedList.size(); + NSMutableArray<QMacAccessibilityElement *> *selectedChildren = + [NSMutableArray<QMacAccessibilityElement *> arrayWithCapacity:numSelected]; + for (QAccessibleInterface *selectedChild : selectedList) { + if (selectedChild && selectedChild->isValid()) { + QAccessible::Id id = QAccessible::uniqueId(selectedChild); + QMacAccessibilityElement *element = [QMacAccessibilityElement elementWithId:id]; + if (element) + [selectedChildren addObject:element]; + } + } + return NSAccessibilityUnignoredChildren(selectedChildren); } - (id) accessibilityWindow { @@ -239,36 +510,66 @@ static void convertLineOffset(QAccessibleTextInterface *text, int *line, int *of } - (NSString *) accessibilityTitle { - QAccessibleInterface *iface = QAccessible::accessibleInterface(axid); - if (!iface || !iface->isValid()) - return nil; - if (iface->role() == QAccessible::StaticText) - return nil; - return iface->text(QAccessible::Name).toNSString(); + if (QAccessibleInterface *iface = self.qtInterface) { + if (iface->role() == QAccessible::StaticText) + return nil; + if (self.isManagedByParent) + return nil; + return iface->text(QAccessible::Name).toNSString(); + } + return nil; } - (BOOL) isAccessibilityEnabled { - QAccessibleInterface *iface = QAccessible::accessibleInterface(axid); - if (!iface || !iface->isValid()) - return false; - return !iface->state().disabled; + if (QAccessibleInterface *iface = self.qtInterface) + return !iface->state().disabled; + return false; } - (id)accessibilityParent { - QAccessibleInterface *iface = QAccessible::accessibleInterface(axid); - if (!iface || !iface->isValid()) + if (synthesizedRole == NSAccessibilityCellRole) { + // a synthetic cell without interface - shortcut to the row + QMacAccessibilityElement *tableElement = + [QMacAccessibilityElement elementWithId:axid]; + Q_ASSERT(tableElement && tableElement->rows); + Q_ASSERT(int(tableElement->rows.count) > m_rowIndex); + QMacAccessibilityElement *rowElement = tableElement->rows[m_rowIndex]; + return rowElement; + } + + QAccessibleInterface *iface = self.qtInterface; + if (!iface) return nil; + if (self.isManagedByParent) { + // axid is the same for the parent element + return NSAccessibilityUnignoredAncestor([QMacAccessibilityElement elementWithId:axid]); + } + // macOS expects that the hierarchy is: // App -> Window -> Children // We don't actually have the window reflected properly in QAccessibility. // Check if the parent is the application and then instead return the native window. if (QAccessibleInterface *parent = iface->parent()) { - if (parent->role() != QAccessible::Application) { - QAccessible::Id parentId = QAccessible::uniqueId(parent); - return NSAccessibilityUnignoredAncestor([QMacAccessibilityElement elementWithId: parentId]); + if (parent->tableInterface()) { + QMacAccessibilityElement *tableElement = + [QMacAccessibilityElement elementWithInterface:parent]; + + // parent of cell should be row + int rowIndex = -1; + if (m_rowIndex >= 0 && m_columnIndex >= 0) + rowIndex = m_rowIndex; + else if (QAccessibleTableCellInterface *cell = iface->tableCellInterface()) + rowIndex = cell->rowIndex(); + Q_ASSERT(tableElement->rows); + if (rowIndex > int([tableElement->rows count]) || rowIndex == -1) + return nil; + QMacAccessibilityElement *rowElement = tableElement->rows[rowIndex]; + return NSAccessibilityUnignoredAncestor(rowElement); } + if (parent->role() != QAccessible::Application) + return NSAccessibilityUnignoredAncestor([QMacAccessibilityElement elementWithInterface: parent]); } if (QWindow *window = iface->window()) { @@ -282,78 +583,91 @@ static void convertLineOffset(QAccessibleTextInterface *text, int *line, int *of } - (NSRect)accessibilityFrame { - QAccessibleInterface *iface = QAccessible::accessibleInterface(axid); - if (!iface || !iface->isValid()) + QAccessibleInterface *iface = self.qtInterface; + if (!iface) return NSZeroRect; - return QCocoaScreen::mapToNative(iface->rect()); -} -- (NSString*)accessibilityLabel { - QAccessibleInterface *iface = QAccessible::accessibleInterface(axid); - if (!iface || !iface->isValid()) { - qWarning() << "Called accessibilityLabel on invalid object: " << axid; - return nil; + QRect rect; + if (self.isManagedByParent) { + if (QAccessibleTableInterface *table = iface->tableInterface()) { + // Construct the geometry of the Row/Column by looking at the individual table cells + // ### Assumes that cells logical coordinates have spatial ordering (e.g finds the + // rows width by taking the union between the leftmost item and the rightmost item in + // a row). + // Otherwise, we have to iterate over *all* cells in a row/columns to + // find out the Row/Column geometry + const bool isRow = synthesizedRole == NSAccessibilityRowRole; + QPoint cellPos; + int &row = isRow ? cellPos.ry() : cellPos.rx(); + int &col = isRow ? cellPos.rx() : cellPos.ry(); + + NSUInteger trackIndex = self.accessibilityIndex; + if (trackIndex != NSNotFound) { + row = int(trackIndex); + if (QAccessibleInterface *firstCell = table->cellAt(cellPos.y(), cellPos.x())) { + rect = firstCell->rect(); + col = isRow ? table->columnCount() : table->rowCount(); + if (col > 1) { + --col; + if (QAccessibleInterface *lastCell = + table->cellAt(cellPos.y(), cellPos.x())) + rect = rect.united(lastCell->rect()); + } + } + } + } + } else { + rect = iface->rect(); } - return iface->text(QAccessible::Description).toNSString(); -} -- (void)setAccessibilityLabel:(NSString*)label{ - QAccessibleInterface *iface = QAccessible::accessibleInterface(axid); - if (!iface || !iface->isValid()) - return; - iface->setText(QAccessible::Description, QString::fromNSString(label)); + return QCocoaScreen::mapToNative(rect); } -- (id) accessibilityMinValue:(QAccessibleInterface*)iface { - if (QAccessibleValueInterface *val = iface->valueInterface()) - return @(val->minimumValue().toDouble()); +- (NSString*)accessibilityLabel { + if (QAccessibleInterface *iface = self.qtInterface) + return iface->text(QAccessible::Description).toNSString(); + qWarning() << "Called accessibilityLabel on invalid object: " << axid; return nil; } -- (id) accessibilityMaxValue:(QAccessibleInterface*)iface { - if (QAccessibleValueInterface *val = iface->valueInterface()) - return @(val->maximumValue().toDouble()); - return nil; +- (void)setAccessibilityLabel:(NSString*)label{ + if (QAccessibleInterface *iface = self.qtInterface) + iface->setText(QAccessible::Description, QString::fromNSString(label)); } - (id) accessibilityValue { - QAccessibleInterface *iface = QAccessible::accessibleInterface(axid); - if (!iface || !iface->isValid()) - return nil; - - // VoiceOver asks for the value attribute for all elements. Return nil - // if we don't want the element to have a value attribute. - if (!QCocoaAccessible::hasValueAttribute(iface)) - return nil; - - return QCocoaAccessible::getValueAttribute(iface); + if (QAccessibleInterface *iface = self.qtInterface) { + // VoiceOver asks for the value attribute for all elements. Return nil + // if we don't want the element to have a value attribute. + if (QCocoaAccessible::hasValueAttribute(iface)) + return QCocoaAccessible::getValueAttribute(iface); + } + return nil; } - (NSInteger) accessibilityNumberOfCharacters { - QAccessibleInterface *iface = QAccessible::accessibleInterface(axid); - if (!iface || !iface->isValid()) - return 0; - if (QAccessibleTextInterface *text = iface->textInterface()) - return text->characterCount(); + if (QAccessibleInterface *iface = self.qtInterface) { + if (QAccessibleTextInterface *text = iface->textInterface()) + return text->characterCount(); + } return 0; } - (NSString *) accessibilitySelectedText { - QAccessibleInterface *iface = QAccessible::accessibleInterface(axid); - if (!iface || !iface->isValid()) - return nil; - if (QAccessibleTextInterface *text = iface->textInterface()) { - int start = 0; - int end = 0; - text->selection(0, &start, &end); - return text->text(start, end).toNSString(); + if (QAccessibleInterface *iface = self.qtInterface) { + if (QAccessibleTextInterface *text = iface->textInterface()) { + int start = 0; + int end = 0; + text->selection(0, &start, &end); + return text->text(start, end).toNSString(); + } } return nil; } - (NSRange) accessibilitySelectedTextRange { - QAccessibleInterface *iface = QAccessible::accessibleInterface(axid); - if (!iface || !iface->isValid()) + QAccessibleInterface *iface = self.qtInterface; + if (!iface) return NSRange(); if (QAccessibleTextInterface *text = iface->textInterface()) { int start = 0; @@ -370,8 +684,8 @@ static void convertLineOffset(QAccessibleTextInterface *text, int *line, int *of } - (NSInteger)accessibilityLineForIndex:(NSInteger)index { - QAccessibleInterface *iface = QAccessible::accessibleInterface(axid); - if (!iface || !iface->isValid()) + QAccessibleInterface *iface = self.qtInterface; + if (!iface) return 0; if (QAccessibleTextInterface *text = iface->textInterface()) { QString textToPos = text->text(0, index); @@ -381,8 +695,8 @@ static void convertLineOffset(QAccessibleTextInterface *text, int *line, int *of } - (NSRange)accessibilityVisibleCharacterRange { - QAccessibleInterface *iface = QAccessible::accessibleInterface(axid); - if (!iface || !iface->isValid()) + QAccessibleInterface *iface = self.qtInterface; + if (!iface) return NSRange(); // FIXME This is not correct and may impact performance for big texts if (QAccessibleTextInterface *text = iface->textInterface()) @@ -391,8 +705,8 @@ static void convertLineOffset(QAccessibleTextInterface *text, int *line, int *of } - (NSInteger) accessibilityInsertionPointLineNumber { - QAccessibleInterface *iface = QAccessible::accessibleInterface(axid); - if (!iface || !iface->isValid()) + QAccessibleInterface *iface = self.qtInterface; + if (!iface) return 0; if (QAccessibleTextInterface *text = iface->textInterface()) { int position = text->cursorPosition(); @@ -403,8 +717,8 @@ static void convertLineOffset(QAccessibleTextInterface *text, int *line, int *of - (NSArray *)accessibilityParameterizedAttributeNames { - QAccessibleInterface *iface = QAccessible::accessibleInterface(axid); - if (!iface || !iface->isValid()) { + QAccessibleInterface *iface = self.qtInterface; + if (!iface) { qWarning() << "Called attribute on invalid object: " << axid; return nil; } @@ -427,8 +741,8 @@ static void convertLineOffset(QAccessibleTextInterface *text, int *line, int *of } - (id)accessibilityAttributeValue:(NSString *)attribute forParameter:(id)parameter { - QAccessibleInterface *iface = QAccessible::accessibleInterface(axid); - if (!iface || !iface->isValid()) { + QAccessibleInterface *iface = self.qtInterface; + if (!iface) { qWarning() << "Called attribute on invalid object: " << axid; return nil; } @@ -468,7 +782,7 @@ static void convertLineOffset(QAccessibleTextInterface *text, int *line, int *of QRectF rect; if (range.length > 0) { NSUInteger position = range.location + range.length - 1; - if (position > range.location && iface->textInterface()->text(position, position + 1) == QStringLiteral("\n")) + if (position > range.location && iface->textInterface()->text(position, position + 1) == "\n"_L1) --position; QRect lastRect = iface->textInterface()->characterRect(position); rect = firstRect.united(lastRect); @@ -496,8 +810,8 @@ static void convertLineOffset(QAccessibleTextInterface *text, int *line, int *of } - (BOOL)accessibilityIsAttributeSettable:(NSString *)attribute { - QAccessibleInterface *iface = QAccessible::accessibleInterface(axid); - if (!iface || !iface->isValid()) + QAccessibleInterface *iface = self.qtInterface; + if (!iface) return NO; if ([attribute isEqualToString:NSAccessibilityFocusedAttribute]) { @@ -515,8 +829,8 @@ static void convertLineOffset(QAccessibleTextInterface *text, int *line, int *of } - (void)accessibilitySetValue:(id)value forAttribute:(NSString *)attribute { - QAccessibleInterface *iface = QAccessible::accessibleInterface(axid); - if (!iface || !iface->isValid()) + QAccessibleInterface *iface = self.qtInterface; + if (!iface) return; if ([attribute isEqualToString:NSAccessibilityFocusedAttribute]) { if (QAccessibleActionInterface *action = iface->actionInterface()) @@ -544,8 +858,8 @@ static void convertLineOffset(QAccessibleTextInterface *text, int *line, int *of - (NSArray *)accessibilityActionNames { NSMutableArray *nsActions = [[NSMutableArray new] autorelease]; - QAccessibleInterface *iface = QAccessible::accessibleInterface(axid); - if (!iface || !iface->isValid()) + QAccessibleInterface *iface = self.qtInterface; + if (!iface) return nsActions; const QStringList &supportedActionNames = QAccessibleBridgeUtils::effectiveActionNames(iface); @@ -559,8 +873,8 @@ static void convertLineOffset(QAccessibleTextInterface *text, int *line, int *of } - (NSString *)accessibilityActionDescription:(NSString *)action { - QAccessibleInterface *iface = QAccessible::accessibleInterface(axid); - if (!iface || !iface->isValid()) + QAccessibleInterface *iface = self.qtInterface; + if (!iface) return nil; // FIXME is that the right return type?? QString qtAction = QCocoaAccessible::translateAction(action, iface); QString description; @@ -577,8 +891,7 @@ static void convertLineOffset(QAccessibleTextInterface *text, int *line, int *of } - (void)accessibilityPerformAction:(NSString *)action { - QAccessibleInterface *iface = QAccessible::accessibleInterface(axid); - if (iface && iface->isValid()) { + if (QAccessibleInterface *iface = self.qtInterface) { const QString qtAction = QCocoaAccessible::translateAction(action, iface); QAccessibleBridgeUtils::performEffectiveAction(iface, qtAction); } @@ -587,15 +900,20 @@ static void convertLineOffset(QAccessibleTextInterface *text, int *line, int *of // misc - (BOOL)accessibilityIsIgnored { - QAccessibleInterface *iface = QAccessible::accessibleInterface(axid); - if (!iface || !iface->isValid()) - return true; - return QCocoaAccessible::shouldBeIgnored(iface); + // Short-cut for placeholders and synthesized elements. Working around a bug + // that corrups lists returned by NSAccessibilityUnignoredChildren, otherwise + // we could ignore rows and columns that are outside the table. + if (self.isManagedByParent) + return false; + + if (QAccessibleInterface *iface = self.qtInterface) + return QCocoaAccessible::shouldBeIgnored(iface); + return true; } - (id)accessibilityHitTest:(NSPoint)point { - QAccessibleInterface *iface = QAccessible::accessibleInterface(axid); - if (!iface || !iface->isValid()) { + QAccessibleInterface *iface = self.qtInterface; + if (!iface) { // qDebug("Hit test: INVALID"); return NSAccessibilityUnignoredAncestor(self); } @@ -614,26 +932,23 @@ static void convertLineOffset(QAccessibleTextInterface *text, int *line, int *of childInterface = childOfChildInterface; } while (childOfChildInterface && childOfChildInterface->isValid()); - QAccessible::Id childId = QAccessible::uniqueId(childInterface); // hit a child, forward to child accessible interface. - QMacAccessibilityElement *accessibleElement = [QMacAccessibilityElement elementWithId:childId]; + QMacAccessibilityElement *accessibleElement = [QMacAccessibilityElement elementWithInterface:childInterface]; if (accessibleElement) return NSAccessibilityUnignoredAncestor(accessibleElement); return NSAccessibilityUnignoredAncestor(self); } - (id)accessibilityFocusedUIElement { - QAccessibleInterface *iface = QAccessible::accessibleInterface(axid); - - if (!iface || !iface->isValid()) { + QAccessibleInterface *iface = self.qtInterface; + if (!iface) { qWarning("FocusedUIElement for INVALID"); return nil; } QAccessibleInterface *childInterface = iface->focusChild(); if (childInterface && childInterface->isValid()) { - QAccessible::Id childAxid = QAccessible::uniqueId(childInterface); - QMacAccessibilityElement *accessibleElement = [QMacAccessibilityElement elementWithId:childAxid]; + QMacAccessibilityElement *accessibleElement = [QMacAccessibilityElement elementWithInterface:childInterface]; return NSAccessibilityUnignoredAncestor(accessibleElement); } @@ -641,8 +956,7 @@ static void convertLineOffset(QAccessibleTextInterface *text, int *line, int *of } - (NSString *) accessibilityHelp { - QAccessibleInterface *iface = QAccessible::accessibleInterface(axid); - if (iface && iface->isValid()) { + if (QAccessibleInterface *iface = self.qtInterface) { const QString helpText = iface->text(QAccessible::Help); if (!helpText.isEmpty()) return helpText.toNSString(); @@ -650,6 +964,47 @@ static void convertLineOffset(QAccessibleTextInterface *text, int *line, int *of return nil; } +/* + * Support for table + */ +- (NSInteger) accessibilityIndex { + NSInteger index = 0; + if (synthesizedRole == NSAccessibilityCellRole) + return m_columnIndex; + if (QAccessibleInterface *iface = self.qtInterface) { + if (self.isManagedByParent) { + // axid matches the parent table axid so that we can easily find the parent table + // children of row are cell/any items + if (QAccessibleTableInterface *table = iface->tableInterface()) { + if (m_rowIndex >= 0) + index = NSInteger(m_rowIndex); + else if (m_columnIndex >= 0) + index = NSInteger(m_columnIndex); + } + } + } + return index; +} + +- (NSArray *) accessibilityRows { + if (!synthesizedRole && rows) { + QAccessibleInterface *iface = self.qtInterface; + if (iface && iface->tableInterface()) + return NSAccessibilityUnignoredChildren(rows); + } + return nil; +} + +- (NSArray *) accessibilityColumns { + if (!synthesizedRole && columns) { + QAccessibleInterface *iface = self.qtInterface; + if (iface && iface->tableInterface()) + return NSAccessibilityUnignoredChildren(columns); + } + return nil; +} + @end -#endif // QT_NO_ACCESSIBILITY +#endif // QT_CONFIG(accessibility) + diff --git a/src/plugins/platforms/cocoa/qcocoaapplication.h b/src/plugins/platforms/cocoa/qcocoaapplication.h index ec21b3cb9f..706923c337 100644 --- a/src/plugins/platforms/cocoa/qcocoaapplication.h +++ b/src/plugins/platforms/cocoa/qcocoaapplication.h @@ -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 /**************************************************************************** ** diff --git a/src/plugins/platforms/cocoa/qcocoaapplication.mm b/src/plugins/platforms/cocoa/qcocoaapplication.mm index 43e3a99a22..a68f993acf 100644 --- a/src/plugins/platforms/cocoa/qcocoaapplication.mm +++ b/src/plugins/platforms/cocoa/qcocoaapplication.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 /**************************************************************************** ** diff --git a/src/plugins/platforms/cocoa/qcocoaapplicationdelegate.h b/src/plugins/platforms/cocoa/qcocoaapplicationdelegate.h index 5ceaf2152b..67634dd26d 100644 --- a/src/plugins/platforms/cocoa/qcocoaapplicationdelegate.h +++ b/src/plugins/platforms/cocoa/qcocoaapplicationdelegate.h @@ -1,41 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2018 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) 2018 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 /**************************************************************************** diff --git a/src/plugins/platforms/cocoa/qcocoaapplicationdelegate.mm b/src/plugins/platforms/cocoa/qcocoaapplicationdelegate.mm index 8e5ecd0f69..d642115926 100644 --- a/src/plugins/platforms/cocoa/qcocoaapplicationdelegate.mm +++ b/src/plugins/platforms/cocoa/qcocoaapplicationdelegate.mm @@ -1,41 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2018 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) 2018 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 /**************************************************************************** ** @@ -184,7 +148,8 @@ QT_USE_NAMESPACE - (void)applicationWillFinishLaunching:(NSNotification *)notification { - Q_UNUSED(notification); + if ([reflectionDelegate respondsToSelector:_cmd]) + [reflectionDelegate applicationWillFinishLaunching:notification]; /* From the Cocoa documentation: "A good place to install event handlers @@ -221,15 +186,34 @@ QT_USE_NAMESPACE - (void)applicationDidFinishLaunching:(NSNotification *)aNotification { - Q_UNUSED(aNotification); + if ([reflectionDelegate respondsToSelector:_cmd]) + [reflectionDelegate applicationDidFinishLaunching:aNotification]; + inLaunch = false; if (qEnvironmentVariableIsEmpty("QT_MAC_DISABLE_FOREGROUND_APPLICATION_TRANSFORM")) { - // Move the application window to front to avoid launching behind the terminal. - // Ignoring other apps is necessary (we must ignore the terminal), but makes - // Qt apps play slightly less nice with other apps when lanching from Finder - // (See the activateIgnoringOtherApps docs.) - [[NSApplication sharedApplication] activateIgnoringOtherApps:YES]; + auto frontmostApplication = NSWorkspace.sharedWorkspace.frontmostApplication; + auto currentApplication = NSRunningApplication.currentApplication; + if (frontmostApplication != currentApplication) { + // Move the application to front to avoid launching behind the terminal. + // Ignoring other apps is necessary (we must ignore the terminal), but makes + // Qt apps play slightly less nice with other apps when launching from Finder + // (see the activateIgnoringOtherApps docs). FIXME: Try to distinguish between + // being non-active here because another application stole activation in the + // time it took us to launch from Finder, and being non-active because we were + // launched from Terminal or something that doesn't activate us at all. + qCDebug(lcQpaApplication) << "Launched with" << frontmostApplication + << "as frontmost application. Activating" << currentApplication << "instead."; + [NSApplication.sharedApplication activateIgnoringOtherApps:YES]; + } + + // Qt windows are typically shown in main(), at which point the application + // is not active yet. When the application is activated, either externally + // or via the override above, it will only bring the main and key windows + // forward, which differs from the behavior if these windows had been shown + // once the application was already active. To work around this, we explicitly + // activate the current application again, bringing all windows to the front. + [currentApplication activateWithOptions:NSApplicationActivateAllWindows]; } QCocoaMenuBar::insertWindowMenu(); @@ -268,6 +252,11 @@ QT_USE_NAMESPACE - (void)applicationDidBecomeActive:(NSNotification *)notification { + if (QCocoaWindow::s_applicationActivationObserver) { + [[[NSWorkspace sharedWorkspace] notificationCenter] removeObserver:QCocoaWindow::s_applicationActivationObserver]; + QCocoaWindow::s_applicationActivationObserver = nil; + } + if ([reflectionDelegate respondsToSelector:_cmd]) [reflectionDelegate applicationDidBecomeActive:notification]; @@ -345,18 +334,80 @@ QT_USE_NAMESPACE [self doesNotRecognizeSelector:invocationSelector]; } +- (BOOL)application:(NSApplication *)application continueUserActivity:(NSUserActivity *)userActivity + restorationHandler:(void(^)(NSArray<id<NSUserActivityRestoring>> *restorableObjects))restorationHandler +{ + // Check if eg. user has installed an app delegate capable of handling this + if ([reflectionDelegate respondsToSelector:_cmd] + && [reflectionDelegate application:application continueUserActivity:userActivity + restorationHandler:restorationHandler] == YES) { + return YES; + } + + if (!QGuiApplication::instance()) + return NO; + + if ([userActivity.activityType isEqualToString:NSUserActivityTypeBrowsingWeb]) { + QCocoaIntegration *cocoaIntegration = QCocoaIntegration::instance(); + Q_ASSERT(cocoaIntegration); + return cocoaIntegration->services()->handleUrl(QUrl::fromNSURL(userActivity.webpageURL)); + } + + return NO; +} + - (void)getUrl:(NSAppleEventDescriptor *)event withReplyEvent:(NSAppleEventDescriptor *)replyEvent { Q_UNUSED(replyEvent); + NSString *urlString = [[event paramDescriptorForKeyword:keyDirectObject] stringValue]; - QWindowSystemInterface::handleFileOpenEvent(QUrl(QString::fromNSString(urlString))); + const QString qurlString = QString::fromNSString(urlString); + + if (event.eventClass == kInternetEventClass && event.eventID == kAEGetURL) { + // 'GURL' (Get URL) event this application should handle + if (!QGuiApplication::instance()) + return; + QCocoaIntegration *cocoaIntegration = QCocoaIntegration::instance(); + Q_ASSERT(cocoaIntegration); + cocoaIntegration->services()->handleUrl(QUrl(qurlString)); + return; + } + + // The string we get from the requesting application might not necessarily meet + // QUrl's requirement for a IDN-compliant host. So if we can't parse into a QUrl, + // then we pass the string on to the application as the name of a file (and + // QFileOpenEvent::file is not guaranteed to be the path to a local, open'able + // file anyway). + if (const QUrl url(qurlString); url.isValid()) + QWindowSystemInterface::handleFileOpenEvent(url); + else + QWindowSystemInterface::handleFileOpenEvent(qurlString); } + +- (BOOL)applicationSupportsSecureRestorableState:(NSApplication *)application +{ + if (@available(macOS 12, *)) { + if ([reflectionDelegate respondsToSelector:_cmd]) + return [reflectionDelegate applicationSupportsSecureRestorableState:application]; + } + + // We don't support or implement state restorations via the AppKit + // state restoration APIs, but if we did, we would/should support + // secure state restoration. This is the default for apps linked + // against the macOS 14 SDK, but as we target versions below that + // as well we need to return YES here explicitly to silence a runtime + // warning. + return YES; +} + @end @implementation QCocoaApplicationDelegate (Menus) - (BOOL)validateMenuItem:(NSMenuItem*)item { + qCDebug(lcQpaMenus) << "Validating" << item << "for" << self; + auto *nativeItem = qt_objc_cast<QCocoaNSMenuItem *>(item); if (!nativeItem) return item.enabled; // FIXME Test with with Qt as plugin or embedded QWindow. @@ -378,6 +429,8 @@ QT_USE_NAMESPACE - (void)qt_itemFired:(QCocoaNSMenuItem *)item { + qCDebug(lcQpaMenus) << "Activating" << item; + if (item.hasSubmenu) return; @@ -389,7 +442,6 @@ QT_USE_NAMESPACE if (!platformItem || platformItem->menu()) return; - QScopedScopeLevelCounter scopeLevelCounter(QGuiApplicationPrivate::instance()->threadData.loadRelaxed()); QGuiApplicationPrivate::modifier_buttons = QAppleKeyMapper::fromCocoaModifiers([NSEvent modifierFlags]); static QMetaMethod activatedSignal = QMetaMethod::fromSignal(&QCocoaMenuItem::activated); diff --git a/src/plugins/platforms/cocoa/qcocoabackingstore.h b/src/plugins/platforms/cocoa/qcocoabackingstore.h index a91407da5d..71b6015a54 100644 --- a/src/plugins/platforms/cocoa/qcocoabackingstore.h +++ b/src/plugins/platforms/cocoa/qcocoabackingstore.h @@ -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 #ifndef QBACKINGSTORE_COCOA_H #define QBACKINGSTORE_COCOA_H @@ -74,10 +38,13 @@ public: bool scroll(const QRegion ®ion, int dx, int dy) override; void flush(QWindow *, const QRegion &, const QPoint &) override; -#ifndef QT_NO_OPENGL - void composeAndFlush(QWindow *window, const QRegion ®ion, const QPoint &offset, - QPlatformTextureList *textures, bool translucentBackground) override; -#endif + + FlushResult rhiFlush(QWindow *window, + qreal sourceDevicePixelRatio, + const QRegion ®ion, + const QPoint &offset, + QPlatformTextureList *textures, + bool translucentBackground) override; QImage toImage() const override; QPlatformGraphicsBuffer *graphicsBuffer() const override; @@ -87,6 +54,7 @@ private: bool eventFilter(QObject *watched, QEvent *event) override; QSize m_requestedSize; + QRegion m_staticContents; class GraphicsBuffer : public QIOSurfaceGraphicsBuffer { @@ -111,7 +79,8 @@ private: bool recreateBackBufferIfNeeded(); void finalizeBackBuffer(); - void preserveFromFrontBuffer(const QRegion ®ion, const QPoint &offset = QPoint()); + void blitBuffer(GraphicsBuffer *sourceBuffer, const QRegion &sourceRegion, + GraphicsBuffer *destinationBuffer, const QPoint &destinationOffset = QPoint()); void backingPropertiesChanged(); QMacNotificationObserver m_backingPropertiesObserver; diff --git a/src/plugins/platforms/cocoa/qcocoabackingstore.mm b/src/plugins/platforms/cocoa/qcocoabackingstore.mm index a09176cfaa..b211b5d02d 100644 --- a/src/plugins/platforms/cocoa/qcocoabackingstore.mm +++ b/src/plugins/platforms/cocoa/qcocoabackingstore.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> @@ -45,6 +9,7 @@ #include "qcocoahelpers.h" #include <QtCore/qmath.h> +#include <QtCore/private/qcore_mac_p.h> #include <QtGui/qpainter.h> #include <QuartzCore/CATransaction.h> @@ -58,8 +23,9 @@ QCocoaBackingStore::QCocoaBackingStore(QWindow *window) QCFType<CGColorSpaceRef> QCocoaBackingStore::colorSpace() const { - NSView *view = static_cast<QCocoaWindow *>(window()->handle())->view(); - return QCFType<CGColorSpaceRef>::constructFromGet(view.window.colorSpace.CGColorSpace); + const auto *platformWindow = static_cast<QCocoaWindow *>(window()->handle()); + const QNSView *view = qnsview_cast(platformWindow->view()); + return QCFType<CGColorSpaceRef>::constructFromGet(view.colorSpace.CGColorSpace); } // ---------------------------------------------------------------------------- @@ -107,12 +73,11 @@ bool QCALayerBackingStore::eventFilter(QObject *watched, QEvent *event) void QCALayerBackingStore::resize(const QSize &size, const QRegion &staticContents) { - qCDebug(lcQpaBackingStore) << "Resize requested to" << size; - - if (!staticContents.isNull()) - qCWarning(lcQpaBackingStore) << "QCALayerBackingStore does not support static contents"; + qCDebug(lcQpaBackingStore) << "Resize requested to" << size + << "with static contents" << staticContents; m_requestedSize = size; + m_staticContents = staticContents; } void QCALayerBackingStore::beginPaint(const QRegion ®ion) @@ -224,11 +189,50 @@ bool QCALayerBackingStore::recreateBackBufferIfNeeded() } #endif - qCInfo(lcQpaBackingStore) << "Creating surface of" << requestedBufferSize - << "based on requested" << m_requestedSize << "and dpr =" << devicePixelRatio; + qCInfo(lcQpaBackingStore)<< "Creating surface of" << requestedBufferSize + << "for" << window() << "based on requested" << m_requestedSize + << "dpr =" << devicePixelRatio << "and color space" << colorSpace(); static auto pixelFormat = QImage::toPixelFormat(QImage::Format_ARGB32_Premultiplied); - m_buffers.back().reset(new GraphicsBuffer(requestedBufferSize, devicePixelRatio, pixelFormat, colorSpace())); + auto *newBackBuffer = new GraphicsBuffer(requestedBufferSize, devicePixelRatio, pixelFormat, colorSpace()); + + if (!m_staticContents.isEmpty() && m_buffers.back()) { + // We implicitly support static backingstore content as a result of + // finalizing the back buffer on flush, where we copy any non-painted + // areas from the front buffer. But there is no guarantee that a resize + // will always come after a flush, where we have a pristine front buffer + // to copy from. It may come after a few begin/endPaints, where the back + // buffer then contains (part of) the latest state. We also have the case + // of single-buffered backingstore, where the front and back buffer is + // the same, which means we must do the copy from the old back buffer + // to the newly resized buffer now, before we replace it below. + + // If the back buffer has been partially filled already, we need to + // copy parts of the static content from that. The rest we copy from + // the front buffer. + const QRegion backBufferRegion = m_staticContents - m_buffers.back()->dirtyRegion; + const QRegion frontBufferRegion = m_staticContents - backBufferRegion; + + qCInfo(lcQpaBackingStore) << "Preserving static content" << backBufferRegion + << "from back buffer, and" << frontBufferRegion << "from front buffer"; + + newBackBuffer->lock(QPlatformGraphicsBuffer::SWWriteAccess); + blitBuffer(m_buffers.back().get(), backBufferRegion, newBackBuffer); + Q_ASSERT(frontBufferRegion.isEmpty() || m_buffers.front()); + blitBuffer(m_buffers.front().get(), frontBufferRegion, newBackBuffer); + newBackBuffer->unlock(); + + // The new back buffer now is valid for the static contents region. + // We don't need to maintain the static contents region for resizes + // of any other buffers in the swap chain, as these will finalize + // their content on flush from the buffer we just filled, and we + // don't need to mark them dirty for the area we just filled, as + // new buffers are fully dirty when created. + newBackBuffer->dirtyRegion -= m_staticContents; + m_staticContents = {}; + } + + m_buffers.back().reset(newBackBuffer); return true; } @@ -271,25 +275,27 @@ bool QCALayerBackingStore::scroll(const QRegion ®ion, int dx, int dy) m_buffers.back()->lock(QPlatformGraphicsBuffer::SWWriteAccess); if (!inPlaceRegion.isEmpty()) { - qCDebug(lcQpaBackingStore) << "Scrolling" << inPlaceRegion << "in place"; + // We have to scroll everything in one go, instead of scrolling the + // individual rects of the region, as otherwise we may end up reading + // already overwritten (scrolled) pixels. + const QRect inPlaceBoundingRect = inPlaceRegion.boundingRect(); + + qCDebug(lcQpaBackingStore) << "Scrolling" << inPlaceBoundingRect << "in place"; QImage *backBufferImage = m_buffers.back()->asImage(); const qreal devicePixelRatio = backBufferImage->devicePixelRatio(); const QPoint devicePixelDelta = scrollDelta * devicePixelRatio; extern void qt_scrollRectInImage(QImage &, const QRect &, const QPoint &); - for (const QRect &rect : inPlaceRegion) { - qt_scrollRectInImage(*backBufferImage, - QRect(rect.topLeft() * devicePixelRatio, - rect.size() * devicePixelRatio), - devicePixelDelta); - } - + qt_scrollRectInImage(*backBufferImage, + QRect(inPlaceBoundingRect.topLeft() * devicePixelRatio, + inPlaceBoundingRect.size() * devicePixelRatio), + devicePixelDelta); } if (!frontBufferRegion.isEmpty()) { qCDebug(lcQpaBackingStore) << "Scrolling" << frontBufferRegion << "by copying from front buffer"; - preserveFromFrontBuffer(frontBufferRegion, scrollDelta); + blitBuffer(m_buffers.front().get(), frontBufferRegion, m_buffers.back().get(), scrollDelta); } m_buffers.back()->unlock(); @@ -428,20 +434,22 @@ void QCALayerBackingStore::windowDestroyed(QObject *object) m_subWindowBackingstores.erase(window); } -#ifndef QT_NO_OPENGL -void QCALayerBackingStore::composeAndFlush(QWindow *window, const QRegion ®ion, const QPoint &offset, - QPlatformTextureList *textures, bool translucentBackground) +QPlatformBackingStore::FlushResult QCALayerBackingStore::rhiFlush(QWindow *window, + qreal sourceDevicePixelRatio, + const QRegion ®ion, + const QPoint &offset, + QPlatformTextureList *textures, + bool translucentBackground) { if (!m_buffers.back()) { qCWarning(lcQpaBackingStore) << "Tried to flush backingstore without painting to it first"; - return; + return FlushFailed; } finalizeBackBuffer(); - QPlatformBackingStore::composeAndFlush(window, region, offset, textures, translucentBackground); + return QPlatformBackingStore::rhiFlush(window, sourceDevicePixelRatio, region, offset, textures, translucentBackground); } -#endif QImage QCALayerBackingStore::toImage() const { @@ -471,10 +479,11 @@ void QCALayerBackingStore::backingPropertiesChanged() qCDebug(lcQpaBackingStore) << "Backing properties for" << window() << "did change"; - qCDebug(lcQpaBackingStore) << "Updating color space of existing buffers"; + const auto newColorSpace = colorSpace(); + qCDebug(lcQpaBackingStore) << "Updating color space of existing buffers to" << newColorSpace; for (auto &buffer : m_buffers) { if (buffer) - buffer->setColorSpace(colorSpace()); + buffer->setColorSpace(newColorSpace); } } @@ -506,54 +515,76 @@ void QCALayerBackingStore::finalizeBackBuffer() if (!m_buffers.back()->isDirty()) return; - m_buffers.back()->lock(QPlatformGraphicsBuffer::SWWriteAccess); - preserveFromFrontBuffer(m_buffers.back()->dirtyRegion); - m_buffers.back()->unlock(); + qCDebug(lcQpaBackingStore) << "Finalizing back buffer with dirty region" << m_buffers.back()->dirtyRegion; + + if (m_buffers.back() != m_buffers.front()) { + m_buffers.back()->lock(QPlatformGraphicsBuffer::SWWriteAccess); + blitBuffer(m_buffers.front().get(), m_buffers.back()->dirtyRegion, m_buffers.back().get()); + m_buffers.back()->unlock(); + } else { + qCDebug(lcQpaBackingStore) << "Front and back buffer is the same. Can not finalize back buffer."; + } // The back buffer is now completely in sync, ready to be presented m_buffers.back()->dirtyRegion = QRegion(); } -void QCALayerBackingStore::preserveFromFrontBuffer(const QRegion ®ion, const QPoint &offset) +/* + \internal + + Blits \a sourceRegion from \a sourceBuffer to \a destinationBuffer, + at offset \a destinationOffset. + + The source buffer is automatically locked for read only access + during the blit. + + The destination buffer has to be locked for write access by the + caller. +*/ + +void QCALayerBackingStore::blitBuffer(GraphicsBuffer *sourceBuffer, const QRegion &sourceRegion, + GraphicsBuffer *destinationBuffer, const QPoint &destinationOffset) { + Q_ASSERT(sourceBuffer && destinationBuffer); + Q_ASSERT(sourceBuffer != destinationBuffer); - if (m_buffers.front() == m_buffers.back()) - return; // Nothing to preserve from + if (sourceRegion.isEmpty()) + return; - qCDebug(lcQpaBackingStore) << "Preserving" << region << "of front buffer to" - << region.translated(offset) << "of back buffer"; + qCDebug(lcQpaBackingStore) << "Blitting" << sourceRegion << "of" << sourceBuffer + << "to" << sourceRegion.translated(destinationOffset) << "of" << destinationBuffer; - Q_ASSERT(m_buffers.back()->isLocked() == QPlatformGraphicsBuffer::SWWriteAccess); + Q_ASSERT(destinationBuffer->isLocked() == QPlatformGraphicsBuffer::SWWriteAccess); - m_buffers.front()->lock(QPlatformGraphicsBuffer::SWReadAccess); - const QImage *frontBuffer = m_buffers.front()->asImage(); + sourceBuffer->lock(QPlatformGraphicsBuffer::SWReadAccess); + const QImage *sourceImage = sourceBuffer->asImage(); - const QRect frontSurfaceBounds(QPoint(0, 0), m_buffers.front()->size()); - const qreal sourceDevicePixelRatio = frontBuffer->devicePixelRatio(); + const QRect sourceBufferBounds(QPoint(0, 0), sourceBuffer->size()); + const qreal sourceDevicePixelRatio = sourceImage->devicePixelRatio(); - QPainter painter(m_buffers.back()->asImage()); + QPainter painter(destinationBuffer->asImage()); painter.setCompositionMode(QPainter::CompositionMode_Source); // Let painter operate in device pixels, to make it easier to compare coordinates - const qreal targetDevicePixelRatio = painter.device()->devicePixelRatio(); - painter.scale(1.0 / targetDevicePixelRatio, 1.0 / targetDevicePixelRatio); + const qreal destinationDevicePixelRatio = painter.device()->devicePixelRatio(); + painter.scale(1.0 / destinationDevicePixelRatio, 1.0 / destinationDevicePixelRatio); - for (const QRect &rect : region) { + for (const QRect &rect : sourceRegion) { QRect sourceRect(rect.topLeft() * sourceDevicePixelRatio, rect.size() * sourceDevicePixelRatio); - QRect targetRect((rect.topLeft() + offset) * targetDevicePixelRatio, - rect.size() * targetDevicePixelRatio); + QRect destinationRect((rect.topLeft() + destinationOffset) * destinationDevicePixelRatio, + rect.size() * destinationDevicePixelRatio); #ifdef QT_DEBUG - if (Q_UNLIKELY(!frontSurfaceBounds.contains(sourceRect.bottomRight()))) { - qCWarning(lcQpaBackingStore) << "Front buffer too small to preserve" - << QRegion(sourceRect).subtracted(frontSurfaceBounds); + if (Q_UNLIKELY(!sourceBufferBounds.contains(sourceRect.bottomRight()))) { + qCWarning(lcQpaBackingStore) << "Source buffer of size" << sourceBuffer->size() + << "is too small to blit" << sourceRect; } #endif - painter.drawImage(targetRect, *frontBuffer, sourceRect); + painter.drawImage(destinationRect, *sourceImage, sourceRect); } - m_buffers.front()->unlock(); + sourceBuffer->unlock(); } // ---------------------------------------------------------------------------- @@ -591,6 +622,6 @@ QImage *QCALayerBackingStore::GraphicsBuffer::asImage() return &m_image; } -#include "moc_qcocoabackingstore.cpp" - QT_END_NAMESPACE + +#include "moc_qcocoabackingstore.cpp" diff --git a/src/plugins/platforms/cocoa/qcocoaclipboard.h b/src/plugins/platforms/cocoa/qcocoaclipboard.h index a78d8f4187..11912d6122 100644 --- a/src/plugins/platforms/cocoa/qcocoaclipboard.h +++ b/src/plugins/platforms/cocoa/qcocoaclipboard.h @@ -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 #ifndef QCOCOACLIPBOARD_H #define QCOCOACLIPBOARD_H diff --git a/src/plugins/platforms/cocoa/qcocoaclipboard.mm b/src/plugins/platforms/cocoa/qcocoaclipboard.mm index 141940cc50..241faadbec 100644 --- a/src/plugins/platforms/cocoa/qcocoaclipboard.mm +++ b/src/plugins/platforms/cocoa/qcocoaclipboard.mm @@ -1,51 +1,17 @@ -/**************************************************************************** -** -** 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 "qcocoaclipboard.h" +#include <QtGui/qutimimeconverter.h> + #ifndef QT_NO_CLIPBOARD QT_BEGIN_NAMESPACE QCocoaClipboard::QCocoaClipboard() - :m_clipboard(new QMacPasteboard(kPasteboardClipboard, QMacInternalPasteboardMime::MIME_CLIP)) - ,m_find(new QMacPasteboard(kPasteboardFind, QMacInternalPasteboardMime::MIME_CLIP)) + :m_clipboard(new QMacPasteboard(kPasteboardClipboard, QUtiMimeConverter::HandlerScopeFlag::Clipboard)) + ,m_find(new QMacPasteboard(kPasteboardFind, QUtiMimeConverter::HandlerScopeFlag::Clipboard)) { connect(qGuiApp, &QGuiApplication::applicationStateChanged, this, &QCocoaClipboard::handleApplicationStateChanged); } @@ -104,8 +70,8 @@ void QCocoaClipboard::handleApplicationStateChanged(Qt::ApplicationState state) emitChanged(QClipboard::FindBuffer); } -#include "moc_qcocoaclipboard.cpp" - QT_END_NAMESPACE +#include "moc_qcocoaclipboard.cpp" + #endif // QT_NO_CLIPBOARD diff --git a/src/plugins/platforms/cocoa/qcocoacolordialoghelper.h b/src/plugins/platforms/cocoa/qcocoacolordialoghelper.h index c3e4e8da04..c9be2a588f 100644 --- a/src/plugins/platforms/cocoa/qcocoacolordialoghelper.h +++ b/src/plugins/platforms/cocoa/qcocoacolordialoghelper.h @@ -1,41 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the QtGui module 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 #ifndef QCOCOACOLORDIALOGHELPER_H #define QCOCOACOLORDIALOGHELPER_H diff --git a/src/plugins/platforms/cocoa/qcocoacolordialoghelper.mm b/src/plugins/platforms/cocoa/qcocoacolordialoghelper.mm index b21b7c2cc9..b58f58caeb 100644 --- a/src/plugins/platforms/cocoa/qcocoacolordialoghelper.mm +++ b/src/plugins/platforms/cocoa/qcocoacolordialoghelper.mm @@ -1,41 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the QtGui module 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> @@ -218,7 +182,17 @@ QT_NAMESPACE_ALIAS_OBJC_CLASS(QNSColorPanelDelegate); mDialogIsExecuting = false; mResultSet = false; mClosingDueToKnownButton = false; - [mColorPanel makeKeyAndOrderFront:mColorPanel]; + // Make this an asynchronous call, so the panel is made key only + // in the next event loop run. This is to make sure that by + // the time the modal loop is run in runModalForWindow below, + // which internally also sets the panel to key window, + // the panel is not yet key, and the NSApp still has the right + // reference to the _previousKeyWindow. Otherwise both NSApp.key + // and NSApp._previousKeyWindow would wrongly point to the panel, + // loosing any reference to the window that was key before. + dispatch_async(dispatch_get_main_queue(), ^{ + [mColorPanel makeKeyAndOrderFront:mColorPanel]; + }); } - (BOOL)runApplicationModalPanel diff --git a/src/plugins/platforms/cocoa/qcocoacursor.h b/src/plugins/platforms/cocoa/qcocoacursor.h index c1aac306aa..82c0357376 100644 --- a/src/plugins/platforms/cocoa/qcocoacursor.h +++ b/src/plugins/platforms/cocoa/qcocoacursor.h @@ -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 #ifndef QWINDOWSCURSOR_H #define QWINDOWSCURSOR_H diff --git a/src/plugins/platforms/cocoa/qcocoacursor.mm b/src/plugins/platforms/cocoa/qcocoacursor.mm index 9e3bdb7b60..aaa35a91ef 100644 --- a/src/plugins/platforms/cocoa/qcocoacursor.mm +++ b/src/plugins/platforms/cocoa/qcocoacursor.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> @@ -47,8 +11,19 @@ #include <QtGui/QBitmap> +#if !defined(QT_APPLE_NO_PRIVATE_APIS) +@interface NSCursor() ++ (id)_windowResizeNorthWestSouthEastCursor; ++ (id)_windowResizeNorthEastSouthWestCursor; ++ (id)_windowResizeNorthSouthCursor; ++ (id)_windowResizeEastWestCursor; +@end +#endif // QT_APPLE_NO_PRIVATE_APIS + QT_BEGIN_NAMESPACE +using namespace Qt::StringLiterals; + QCocoaCursor::QCocoaCursor() { } @@ -118,7 +93,7 @@ NSCursor *QCocoaCursor::convertCursor(QCursor *cursor) return nil; const Qt::CursorShape newShape = cursor->shape(); - NSCursor *cocoaCursor; + NSCursor *cocoaCursor = nil; // Check for a suitable built-in NSCursor first: switch (newShape) { @@ -159,7 +134,29 @@ NSCursor *QCocoaCursor::convertCursor(QCursor *cursor) case Qt::DragLinkCursor: cocoaCursor = [NSCursor dragLinkCursor]; break; - default : { +#if !defined(QT_APPLE_NO_PRIVATE_APIS) + case Qt::SizeVerCursor: + if ([NSCursor respondsToSelector:@selector(_windowResizeNorthSouthCursor)]) + cocoaCursor = [NSCursor _windowResizeNorthSouthCursor]; + break; + case Qt::SizeHorCursor: + if ([NSCursor respondsToSelector:@selector(_windowResizeEastWestCursor)]) + cocoaCursor = [NSCursor _windowResizeEastWestCursor]; + break; + case Qt::SizeBDiagCursor: + if ([NSCursor respondsToSelector:@selector(_windowResizeNorthEastSouthWestCursor)]) + cocoaCursor = [NSCursor _windowResizeNorthEastSouthWestCursor]; + break; + case Qt::SizeFDiagCursor: + if ([NSCursor respondsToSelector:@selector(_windowResizeNorthWestSouthEastCursor)]) + cocoaCursor = [NSCursor _windowResizeNorthWestSouthEastCursor]; + break; +#endif // QT_APPLE_NO_PRIVATE_APIS + default: + break; + } + + if (!cocoaCursor) { // No suitable OS cursor exist, use cursors provided // by Qt for the rest. Check for a cached cursor: cocoaCursor = m_cursors.value(newShape); @@ -174,8 +171,6 @@ NSCursor *QCocoaCursor::convertCursor(QCursor *cursor) m_cursors.insert(newShape, cocoaCursor); } - - break; } } return cocoaCursor; } @@ -245,7 +240,7 @@ NSCursor *QCocoaCursor::createCursorData(QCursor *cursor) switch (cursor->shape()) { case Qt::BitmapCursor: { if (cursor->pixmap().isNull()) - return createCursorFromBitmap(cursor->bitmap(Qt::ReturnByValue), cursor->mask(Qt::ReturnByValue), hotspot); + return createCursorFromBitmap(cursor->bitmap(), cursor->mask(), hotspot); else return createCursorFromPixmap(cursor->pixmap(), hotspot); break; } @@ -255,15 +250,15 @@ NSCursor *QCocoaCursor::createCursorData(QCursor *cursor) return createCursorFromPixmap(pixmap); break; } case Qt::WaitCursor: { - QPixmap pixmap = QPixmap(QLatin1String(":/qt-project.org/mac/cursors/images/spincursor.png")); + QPixmap pixmap = QPixmap(":/qt-project.org/mac/cursors/images/spincursor.png"_L1); return createCursorFromPixmap(pixmap, hotspot); break; } case Qt::SizeAllCursor: { - QPixmap pixmap = QPixmap(QLatin1String(":/qt-project.org/mac/cursors/images/sizeallcursor.png")); + QPixmap pixmap = QPixmap(":/qt-project.org/mac/cursors/images/sizeallcursor.png"_L1); return createCursorFromPixmap(pixmap, QPoint(8, 8)); break; } case Qt::BusyCursor: { - QPixmap pixmap = QPixmap(QLatin1String(":/qt-project.org/mac/cursors/images/waitcursor.png")); + QPixmap pixmap = QPixmap(":/qt-project.org/mac/cursors/images/waitcursor.png"_L1); return createCursorFromPixmap(pixmap, hotspot); break; } #define QT_USE_APPROXIMATE_CURSORS diff --git a/src/plugins/platforms/cocoa/qcocoadrag.h b/src/plugins/platforms/cocoa/qcocoadrag.h index eee2692666..dedf8a7fd9 100644 --- a/src/plugins/platforms/cocoa/qcocoadrag.h +++ b/src/plugins/platforms/cocoa/qcocoadrag.h @@ -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 #ifndef QCOCOADRAG_H #define QCOCOADRAG_H @@ -80,7 +44,7 @@ private: NSEvent *m_lastEvent; NSView *m_lastView; Qt::DropAction m_executed_drop_action; - QEventLoop internalDragLoop; + QEventLoop *m_internalDragLoop = nullptr; bool maybeDragMultipleItems(); diff --git a/src/plugins/platforms/cocoa/qcocoadrag.mm b/src/plugins/platforms/cocoa/qcocoadrag.mm index f681981d34..a8404889e9 100644 --- a/src/plugins/platforms/cocoa/qcocoadrag.mm +++ b/src/plugins/platforms/cocoa/qcocoadrag.mm @@ -1,54 +1,23 @@ -/**************************************************************************** -** -** 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 <UniformTypeIdentifiers/UTCoreTypes.h> #include "qcocoadrag.h" #include "qmacclipboard.h" #include "qcocoahelpers.h" #include <QtGui/private/qcoregraphics_p.h> +#include <QtGui/qutimimeconverter.h> #include <QtCore/qsysinfo.h> +#include <QtCore/private/qcore_mac_p.h> #include <vector> QT_BEGIN_NAMESPACE +using namespace Qt::StringLiterals; + static const int dragImageMaxChars = 26; QCocoaDrag::QCocoaDrag() : @@ -130,8 +99,8 @@ Qt::DropAction QCocoaDrag::drag(QDrag *o) m_drag = o; m_executed_drop_action = Qt::IgnoreAction; - QMacPasteboard dragBoard(CFStringRef(NSPasteboardNameDrag), QMacInternalPasteboardMime::MIME_DND); - m_drag->mimeData()->setData(QLatin1String("application/x-qt-mime-type-name"), QByteArray("dummy")); + QMacPasteboard dragBoard(CFStringRef(NSPasteboardNameDrag), QUtiMimeConverter::HandlerScopeFlag::DnD); + m_drag->mimeData()->setData("application/x-qt-mime-type-name"_L1, QByteArray("dummy")); dragBoard.setMimeData(m_drag->mimeData(), QMacPasteboard::LazyRequest); if (maybeDragMultipleItems()) @@ -175,13 +144,11 @@ bool QCocoaDrag::maybeDragMultipleItems() const QMacAutoReleasePool pool; - NSWindow *theWindow = [m_lastEvent window]; - Q_ASSERT(theWindow); - - if (![theWindow.contentView respondsToSelector:@selector(draggingSession:sourceOperationMaskForDraggingContext:)]) + NSView *view = m_lastView ? m_lastView : m_lastEvent.window.contentView; + if (![view respondsToSelector:@selector(draggingSession:sourceOperationMaskForDraggingContext:)]) return false; - auto *sourceView = static_cast<NSView<NSDraggingSource>*>(theWindow.contentView); + auto *sourceView = static_cast<NSView<NSDraggingSource>*>(view); const auto &qtUrls = m_drag->mimeData()->urls(); NSPasteboard *dragBoard = [NSPasteboard pasteboardWithName:NSPasteboardNameDrag]; @@ -195,8 +162,7 @@ bool QCocoaDrag::maybeDragMultipleItems() for (NSPasteboardItem *item in dragBoard.pasteboardItems) { bool isUrl = false; for (NSPasteboardType type in item.types) { - using NSStringRef = NSString *; - if ([type isEqualToString:NSStringRef(kUTTypeFileURL)]) { + if ([type isEqualToString:UTTypeFileURL.identifier]) { isUrl = true; break; } @@ -220,6 +186,12 @@ bool QCocoaDrag::maybeDragMultipleItems() // contains a combined picture for all urls we drag. auto imageOrNil = dragImage; for (const auto &qtUrl : qtUrls) { + if (!qtUrl.isValid()) + continue; + + if (qtUrl.isRelative()) // NSPasteboardWriting rejects such items. + continue; + NSURL *nsUrl = qtUrl.toNSURL(); auto *newItem = [[[NSDraggingItem alloc] initWithPasteboardWriter:nsUrl] autorelease]; const NSRect itemFrame = NSMakeRect(itemLocation.x, itemLocation.y, @@ -241,7 +213,9 @@ bool QCocoaDrag::maybeDragMultipleItems() } [sourceView beginDraggingSessionWithItems:dragItems event:m_lastEvent source:sourceView]; - internalDragLoop.exec(); + QEventLoop eventLoop; + QScopedValueRollback updateGuard(m_internalDragLoop, &eventLoop); + eventLoop.exec(); return true; } @@ -252,8 +226,10 @@ void QCocoaDrag::setAcceptedAction(Qt::DropAction act) void QCocoaDrag::exitDragLoop() { - if (internalDragLoop.isRunning()) - internalDragLoop.exit(); + if (m_internalDragLoop) { + Q_ASSERT(m_internalDragLoop->isRunning()); + m_internalDragLoop->exit(); + } } @@ -268,14 +244,14 @@ QPixmap QCocoaDrag::dragPixmap(QDrag *drag, QPoint &hotSpot) const QFontMetrics fm(f); if (data->hasImage()) { - const QImage img = data->imageData().value<QImage>(); + QImage img = data->imageData().value<QImage>(); if (!img.isNull()) { - pm = QPixmap::fromImage(img).scaledToWidth(dragImageMaxChars *fm.averageCharWidth()); + pm = QPixmap::fromImage(std::move(img)).scaledToWidth(dragImageMaxChars *fm.averageCharWidth()); } } if (pm.isNull() && (data->hasText() || data->hasUrls()) ) { - QString s = data->hasText() ? data->text() : data->urls().first().toString(); + QString s = data->hasText() ? data->text() : data->urls().constFirst().toString(); if (s.length() > dragImageMaxChars) s = s.left(dragImageMaxChars -3) + QChar(0x2026); if (!s.isEmpty()) { @@ -333,11 +309,11 @@ QStringList QCocoaDropData::formats_sys() const qDebug("DnD: Cannot get PasteBoard!"); return formats; } - formats = QMacPasteboard(board, QMacInternalPasteboardMime::MIME_DND).formats(); + formats = QMacPasteboard(board, QUtiMimeConverter::HandlerScopeFlag::DnD).formats(); return formats; } -QVariant QCocoaDropData::retrieveData_sys(const QString &mimeType, QMetaType type) const +QVariant QCocoaDropData::retrieveData_sys(const QString &mimeType, QMetaType) const { QVariant data; PasteboardRef board; @@ -345,7 +321,7 @@ QVariant QCocoaDropData::retrieveData_sys(const QString &mimeType, QMetaType typ qDebug("DnD: Cannot get PasteBoard!"); return data; } - data = QMacPasteboard(board, QMacInternalPasteboardMime::MIME_DND).retrieveData(mimeType, type); + data = QMacPasteboard(board, QUtiMimeConverter::HandlerScopeFlag::DnD).retrieveData(mimeType); CFRelease(board); return data; } @@ -358,7 +334,7 @@ bool QCocoaDropData::hasFormat_sys(const QString &mimeType) const qDebug("DnD: Cannot get PasteBoard!"); return has; } - has = QMacPasteboard(board, QMacInternalPasteboardMime::MIME_DND).hasFormat(mimeType); + has = QMacPasteboard(board, QUtiMimeConverter::HandlerScopeFlag::DnD).hasFormat(mimeType); CFRelease(board); return has; } diff --git a/src/plugins/platforms/cocoa/qcocoaeventdispatcher.h b/src/plugins/platforms/cocoa/qcocoaeventdispatcher.h index 823633ffb9..96eb70dabc 100644 --- a/src/plugins/platforms/cocoa/qcocoaeventdispatcher.h +++ b/src/plugins/platforms/cocoa/qcocoaeventdispatcher.h @@ -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 /**************************************************************************** ** @@ -91,20 +55,26 @@ #include <QtCore/private/qabstracteventdispatcher_p.h> #include <QtCore/private/qcfsocketnotifier_p.h> #include <QtCore/private/qtimerinfo_unix_p.h> +#include <QtCore/qloggingcategory.h> +#include <QtCore/qpointer.h> #include <CoreFoundation/CoreFoundation.h> +Q_FORWARD_DECLARE_OBJC_CLASS(NSWindow); + QT_BEGIN_NAMESPACE +Q_DECLARE_LOGGING_CATEGORY(lcEventDispatcher); + typedef struct _NSModalSession *NSModalSession; typedef struct _QCocoaModalSessionInfo { QPointer<QWindow> window; NSModalSession session; - void *nswindow; + NSWindow *nswindow; } QCocoaModalSessionInfo; class QCocoaEventDispatcherPrivate; -class QCocoaEventDispatcher : public QAbstractEventDispatcher +class QCocoaEventDispatcher : public QAbstractEventDispatcherV2 { Q_OBJECT Q_DECLARE_PRIVATE(QCocoaEventDispatcher) @@ -119,12 +89,12 @@ public: void registerSocketNotifier(QSocketNotifier *notifier); void unregisterSocketNotifier(QSocketNotifier *notifier); - void registerTimer(int timerId, qint64 interval, Qt::TimerType timerType, QObject *object); - bool unregisterTimer(int timerId); - bool unregisterTimers(QObject *object); - QList<TimerInfo> registeredTimers(QObject *object) const; - - int remainingTime(int timerId); + void registerTimer(Qt::TimerId timerId, Duration interval, Qt::TimerType timerType, + QObject *object) final; + bool unregisterTimer(Qt::TimerId timerId) final; + bool unregisterTimers(QObject *object) final; + QList<TimerInfoV2> timersForObject(QObject *object) const final; + Duration remainingTime(Qt::TimerId timerId) const final; void wakeUp(); void interrupt(); @@ -162,6 +132,7 @@ public: QStack<QCocoaModalSessionInfo> cocoaModalSessionStack; bool currentExecIsNSAppRun; bool nsAppRunCalledByQt; + bool initializingNSApplication = false; bool cleanupModalSessionsNeeded; uint processEventsCalled; NSModalSession currentModalSessionCached; @@ -180,7 +151,6 @@ public: QList<void *> queuedUserInputEvents; // NSEvent * CFRunLoopSourceRef postedEventsSource; CFRunLoopObserverRef waitingObserver; - CFRunLoopObserverRef firstTimeObserver; QAtomicInt serialNumber; int lastSerial; bool interrupt; @@ -189,7 +159,6 @@ public: static void postedEventsSourceCallback(void *info); static void waitingObserverCallback(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info); - static void firstLoopEntry(CFRunLoopObserverRef ref, CFRunLoopActivity activity, void *info); bool sendQueuedUserInputEvents(); void processPostedEvents(); }; diff --git a/src/plugins/platforms/cocoa/qcocoaeventdispatcher.mm b/src/plugins/platforms/cocoa/qcocoaeventdispatcher.mm index 423a8a1ccd..739fbda4f5 100644 --- a/src/plugins/platforms/cocoa/qcocoaeventdispatcher.mm +++ b/src/plugins/platforms/cocoa/qcocoaeventdispatcher.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 /**************************************************************************** ** @@ -85,6 +49,7 @@ #include <QtCore/qscopeguard.h> #include <QtCore/qsocketnotifier.h> #include <QtCore/private/qthread_p.h> +#include <QtCore/private/qcore_mac_p.h> #include <qpa/qplatformwindow.h> #include <qpa/qplatformnativeinterface.h> @@ -93,6 +58,8 @@ QT_BEGIN_NAMESPACE +Q_LOGGING_CATEGORY(lcEventDispatcher, "qt.eventdispatcher"); + static inline CFRunLoopRef mainRunLoop() { return CFRunLoopGetMain(); @@ -122,6 +89,13 @@ void QCocoaEventDispatcherPrivate::runLoopTimerCallback(CFRunLoopTimerRef, void void QCocoaEventDispatcherPrivate::activateTimersSourceCallback(void *info) { QCocoaEventDispatcherPrivate *d = static_cast<QCocoaEventDispatcherPrivate *>(info); + if (d->initializingNSApplication) { + qCDebug(lcEventDispatcher) << "Deferring" << __FUNCTION__ << "due to NSApp initialization"; + // We don't want to process any sources during explicit NSApplication + // initialization, so defer the source until the actual event processing. + CFRunLoopSourceSignal(d->activateTimersSourceRef); + return; + } d->processTimers(); d->maybeCancelWaitForMoreEvents(); } @@ -141,6 +115,7 @@ void QCocoaEventDispatcherPrivate::maybeStartCFRunLoopTimer() return; } + using DoubleSeconds = std::chrono::duration<double, std::ratio<1>>; if (!runLoopTimerRef) { // start the CFRunLoopTimer CFAbsoluteTime ttf = CFAbsoluteTimeGetCurrent(); @@ -148,10 +123,10 @@ void QCocoaEventDispatcherPrivate::maybeStartCFRunLoopTimer() CFTimeInterval oneyear = CFTimeInterval(3600. * 24. * 365.); // Q: when should the CFRunLoopTimer fire for the first time? - struct timespec tv; - if (timerInfoList.timerWait(tv)) { + if (auto opt = timerInfoList.timerWait()) { // A: when we have timers to fire, of course - interval = qMax(tv.tv_sec + tv.tv_nsec / 1000000000., 0.0000001); + DoubleSeconds secs{*opt}; + interval = qMax(secs.count(), 0.0000001); } else { // this shouldn't really happen, but in case it does, set the timer to fire a some point in the distant future interval = oneyear; @@ -171,10 +146,10 @@ void QCocoaEventDispatcherPrivate::maybeStartCFRunLoopTimer() CFTimeInterval interval; // Q: when should the timer first next? - struct timespec tv; - if (timerInfoList.timerWait(tv)) { + if (auto opt = timerInfoList.timerWait()) { // A: when we have timers to fire, of course - interval = qMax(tv.tv_sec + tv.tv_nsec / 1000000000., 0.0000001); + DoubleSeconds secs{*opt}; + interval = qMax(secs.count(), 0.0000001); } else { // no timers can fire, but we cannot stop the CFRunLoopTimer, set the timer to fire at some // point in the distant future (the timer interval is one year) @@ -196,10 +171,11 @@ void QCocoaEventDispatcherPrivate::maybeStopCFRunLoopTimer() runLoopTimerRef = nullptr; } -void QCocoaEventDispatcher::registerTimer(int timerId, qint64 interval, Qt::TimerType timerType, QObject *obj) +void QCocoaEventDispatcher::registerTimer(Qt::TimerId timerId, Duration interval, + Qt::TimerType timerType, QObject *obj) { #ifndef QT_NO_DEBUG - if (timerId < 1 || interval < 0 || !obj) { + if (qToUnderlying(timerId) < 1 || interval.count() < 0 || !obj) { qWarning("QCocoaEventDispatcher::registerTimer: invalid arguments"); return; } else if (obj->thread() != thread() || thread() != QThread::currentThread()) { @@ -213,10 +189,10 @@ void QCocoaEventDispatcher::registerTimer(int timerId, qint64 interval, Qt::Time d->maybeStartCFRunLoopTimer(); } -bool QCocoaEventDispatcher::unregisterTimer(int timerId) +bool QCocoaEventDispatcher::unregisterTimer(Qt::TimerId timerId) { #ifndef QT_NO_DEBUG - if (timerId < 1) { + if (qToUnderlying(timerId) < 1) { qWarning("QCocoaEventDispatcher::unregisterTimer: invalid argument"); return false; } else if (thread() != QThread::currentThread()) { @@ -255,13 +231,13 @@ bool QCocoaEventDispatcher::unregisterTimers(QObject *obj) return returnValue; } -QList<QCocoaEventDispatcher::TimerInfo> -QCocoaEventDispatcher::registeredTimers(QObject *object) const +QList<QCocoaEventDispatcher::TimerInfoV2> +QCocoaEventDispatcher::timersForObject(QObject *object) const { #ifndef QT_NO_DEBUG if (!object) { qWarning("QCocoaEventDispatcher:registeredTimers: invalid argument"); - return QList<TimerInfo>(); + return {}; } #endif @@ -400,6 +376,7 @@ bool QCocoaEventDispatcher::processEvents(QEventLoop::ProcessEventsFlags flags) // [NSApp run], which is the normal code path for cocoa applications. if (NSModalSession session = d->currentModalSession()) { QBoolBlocker execGuard(d->currentExecIsNSAppRun, false); + qCDebug(lcEventDispatcher) << "Running modal session" << session; while ([NSApp runModalSession:session] == NSModalResponseContinue && !d->interrupt) { qt_mac_waitForMoreEvents(NSModalPanelRunLoopMode); if (session != d->currentModalSessionCached) { @@ -443,6 +420,7 @@ bool QCocoaEventDispatcher::processEvents(QEventLoop::ProcessEventsFlags flags) // to use cocoa's native way of running modal sessions: if (flags & QEventLoop::WaitForMoreEvents) qt_mac_waitForMoreEvents(NSModalPanelRunLoopMode); + qCDebug(lcEventDispatcher) << "Running modal session" << session; NSInteger status = [NSApp runModalSession:session]; if (status != NSModalResponseContinue && session == d->currentModalSessionCached) { // INVARIANT: Someone called [NSApp stopModal:] from outside the event @@ -563,17 +541,17 @@ bool QCocoaEventDispatcher::processEvents(QEventLoop::ProcessEventsFlags flags) return retVal; } -int QCocoaEventDispatcher::remainingTime(int timerId) +auto QCocoaEventDispatcher::remainingTime(Qt::TimerId timerId) const -> Duration { #ifndef QT_NO_DEBUG - if (timerId < 1) { + if (qToUnderlying(timerId) < 1) { qWarning("QCocoaEventDispatcher::remainingTime: invalid argument"); - return -1; + return Duration::min(); } #endif - Q_D(QCocoaEventDispatcher); - return d->timerInfoList.timerRemainingTime(timerId); + Q_D(const QCocoaEventDispatcher); + return d->timerInfoList.remainingDuration(timerId); } void QCocoaEventDispatcher::wakeUp() @@ -590,22 +568,42 @@ void QCocoaEventDispatcher::wakeUp() void QCocoaEventDispatcherPrivate::ensureNSAppInitialized() { - // Some elements in Cocoa require NSApplication to be running before - // they get fully initialized, in particular the menu bar. This - // function is intended for cases where a dialog is told to execute before - // QGuiApplication::exec is called, or the application spins the events loop - // manually rather than calling QGuiApplication:exec. - // The function makes sure that NSApplication starts running, but stops - // it again as soon as the send posted events callback is called. That way - // we let Cocoa finish the initialization it seems to need. We'll only - // apply this trick at most once for any application, and we avoid doing it - // for the common case where main just starts QGuiApplication::exec. + // Some elements in Cocoa require NSApplication to be initialized before + // use, for example the menu bar. Under normal circumstances this happens + // as part of [NSApp run], as a result of a call to QGuiApplication:exec(), + // but in the cases where a dialog is asked to execute before that happens, + // or the application spins the event loop manually via processEvents(), + // we need to explicitly ensure NSApplication initialization. + + // We can unfortunately not do this via NSApplicationLoad(), as the function + // bails out early if there's already an NSApplication instance, which is + // the case if any code has called [NSApplication sharedApplication], + // or its short form 'NSApp'. + + // Instead we do an actual [NSApp run], but stop the application as soon + // as possible, ensuring that AppKit will do the required initialization, + // including calling [NSApplication finishLaunching]. + + // We only apply this trick at most once for any application, and we avoid + // doing it for the common case where main just starts QGuiApplication::exec. if (nsAppRunCalledByQt || [NSApp isRunning]) return; + + qCDebug(lcEventDispatcher) << "Ensuring NSApplication is initialized"; nsAppRunCalledByQt = true; - QBoolBlocker block1(interrupt, true); - QBoolBlocker block2(currentExecIsNSAppRun, true); + + // Stopping the application will still process runloop sources before + // actually stopping, so we need to explicitly guard our sources from + // doing anything, deferring their actions until later. + QBoolBlocker initializationGuard(initializingNSApplication, true); + + CFRunLoopPerformBlock(mainRunLoop(), kCFRunLoopCommonModes, ^{ + qCDebug(lcEventDispatcher) << "NSApplication has been initialized; Stopping NSApp"; + [NSApp stop:NSApp]; + cancelWaitForMoreEvents(); // Post event that wakes up the runloop + }); [NSApp run]; + qCDebug(lcEventDispatcher) << "Finished ensuring NSApplication is initialized"; } void QCocoaEventDispatcherPrivate::temporarilyStopAllModalSessions() @@ -623,6 +621,8 @@ void QCocoaEventDispatcherPrivate::temporarilyStopAllModalSessions() for (int i=0; i<stackSize; ++i) { QCocoaModalSessionInfo &info = cocoaModalSessionStack[i]; if (info.session) { + qCDebug(lcEventDispatcher) << "Temporarily ending modal session" << info.session + << "for" << info.nswindow; [NSApp endModalSession:info.session]; info.session = nullptr; [(NSWindow*) info.nswindow release]; @@ -662,6 +662,8 @@ NSModalSession QCocoaEventDispatcherPrivate::currentModalSession() [(NSWindow*) info.nswindow retain]; QRect rect = cocoaWindow->geometry(); info.session = [NSApp beginModalSessionForWindow:nswindow]; + qCDebug(lcEventDispatcher) << "Begun modal session" << info.session + << "for" << nswindow; // The call to beginModalSessionForWindow above processes events and may // have deleted or destroyed the window. Check if it's still valid. @@ -710,6 +712,8 @@ void QCocoaEventDispatcherPrivate::cleanupModalSessions() currentModalSessionCached = nullptr; if (info.session) { Q_ASSERT(info.nswindow); + qCDebug(lcEventDispatcher) << "Ending modal session" << info.session + << "for" << info.nswindow; [NSApp endModalSession:info.session]; [(NSWindow *)info.nswindow release]; } @@ -722,6 +726,14 @@ void QCocoaEventDispatcherPrivate::cleanupModalSessions() void QCocoaEventDispatcherPrivate::beginModalSession(QWindow *window) { + qCDebug(lcEventDispatcher) << "Adding modal session for" << window; + + if (std::any_of(cocoaModalSessionStack.constBegin(), cocoaModalSessionStack.constEnd(), + [&](const auto &sessionInfo) { return sessionInfo.window == window; })) { + qCWarning(lcEventDispatcher) << "Modal session for" << window << "already exists!"; + return; + } + // We need to start spinning the modal session. Usually this is done with // QDialog::exec() for Qt Widgets based applications, but for others that // just call show(), we need to interrupt(). @@ -742,6 +754,8 @@ void QCocoaEventDispatcherPrivate::beginModalSession(QWindow *window) void QCocoaEventDispatcherPrivate::endModalSession(QWindow *window) { + qCDebug(lcEventDispatcher) << "Removing modal session for" << window; + Q_Q(QCocoaEventDispatcher); // Mark all sessions attached to window as pending to be stopped. We do this @@ -788,7 +802,7 @@ void qt_mac_maybeCancelWaitForMoreEventsForwarder(QAbstractEventDispatcher *even } QCocoaEventDispatcher::QCocoaEventDispatcher(QObject *parent) - : QAbstractEventDispatcher(*new QCocoaEventDispatcherPrivate, parent) + : QAbstractEventDispatcherV2(*new QCocoaEventDispatcherPrivate, parent) { Q_D(QCocoaEventDispatcher); @@ -825,21 +839,6 @@ QCocoaEventDispatcher::QCocoaEventDispatcher(QObject *parent) QCocoaEventDispatcherPrivate::waitingObserverCallback, &observerContext); CFRunLoopAddObserver(mainRunLoop(), d->waitingObserver, kCFRunLoopCommonModes); - - /* The first cycle in the loop adds the source and the events of the source - are not processed. - We use an observer to process the posted events for the first - execution of the loop. */ - CFRunLoopObserverContext firstTimeObserverContext; - bzero(&firstTimeObserverContext, sizeof(CFRunLoopObserverContext)); - firstTimeObserverContext.info = d; - d->firstTimeObserver = CFRunLoopObserverCreate(kCFAllocatorDefault, - kCFRunLoopEntry, - /* repeats = */ false, - 0, - QCocoaEventDispatcherPrivate::firstLoopEntry, - &firstTimeObserverContext); - CFRunLoopAddObserver(mainRunLoop(), d->firstTimeObserver, kCFRunLoopCommonModes); } void QCocoaEventDispatcherPrivate::waitingObserverCallback(CFRunLoopObserverRef, @@ -904,18 +903,17 @@ void QCocoaEventDispatcherPrivate::processPostedEvents() } } -void QCocoaEventDispatcherPrivate::firstLoopEntry(CFRunLoopObserverRef ref, - CFRunLoopActivity activity, - void *info) -{ - Q_UNUSED(ref); - Q_UNUSED(activity); - static_cast<QCocoaEventDispatcherPrivate *>(info)->processPostedEvents(); -} - void QCocoaEventDispatcherPrivate::postedEventsSourceCallback(void *info) { QCocoaEventDispatcherPrivate *d = static_cast<QCocoaEventDispatcherPrivate *>(info); + if (d->initializingNSApplication) { + qCDebug(lcEventDispatcher) << "Deferring" << __FUNCTION__ << "due to NSApp initialization"; + // We don't want to process any sources during explicit NSApplication + // initialization, so defer the source until the actual event processing. + CFRunLoopSourceSignal(d->postedEventsSource); + return; + } + if (d->processEventsCalled && (d->processEventsFlags & QEventLoop::EventLoopExec) == 0) { // processEvents() was called "manually," ignore this source for now d->maybeCancelWaitForMoreEvents(); @@ -979,7 +977,7 @@ QCocoaEventDispatcher::~QCocoaEventDispatcher() { Q_D(QCocoaEventDispatcher); - qDeleteAll(d->timerInfoList); + d->timerInfoList.clearTimers(); d->maybeStopCFRunLoopTimer(); CFRunLoopRemoveSource(mainRunLoop(), d->activateTimersSourceRef, kCFRunLoopCommonModes); CFRelease(d->activateTimersSourceRef); @@ -988,6 +986,8 @@ QCocoaEventDispatcher::~QCocoaEventDispatcher() for (int i = 0; i < d->cocoaModalSessionStack.count(); ++i) { QCocoaModalSessionInfo &info = d->cocoaModalSessionStack[i]; if (info.session) { + qCDebug(lcEventDispatcher) << "Ending modal session" << info.session + << "for" << info.nswindow << "during shutdown"; [NSApp endModalSession:info.session]; [(NSWindow *)info.nswindow release]; } @@ -1006,9 +1006,6 @@ QCocoaEventDispatcher::~QCocoaEventDispatcher() CFRunLoopObserverInvalidate(d->waitingObserver); CFRelease(d->waitingObserver); - - CFRunLoopObserverInvalidate(d->firstTimeObserver); - CFRelease(d->firstTimeObserver); } QtCocoaInterruptDispatcher* QtCocoaInterruptDispatcher::instance = nullptr; diff --git a/src/plugins/platforms/cocoa/qcocoafiledialoghelper.h b/src/plugins/platforms/cocoa/qcocoafiledialoghelper.h index ad61c2d584..3ffccb10fd 100644 --- a/src/plugins/platforms/cocoa/qcocoafiledialoghelper.h +++ b/src/plugins/platforms/cocoa/qcocoafiledialoghelper.h @@ -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 #ifndef QCOCOAFILEDIALOGHELPER_H #define QCOCOAFILEDIALOGHELPER_H @@ -74,6 +38,7 @@ public: public: // for QNSOpenSavePanelDelegate void panelClosed(NSInteger result); + void panelDirectoryDidChange(NSString *path); private: void createNSOpenSavePanelDelegate(); diff --git a/src/plugins/platforms/cocoa/qcocoafiledialoghelper.mm b/src/plugins/platforms/cocoa/qcocoafiledialoghelper.mm index 8bd649e461..044a282686 100644 --- a/src/plugins/platforms/cocoa/qcocoafiledialoghelper.mm +++ b/src/plugins/platforms/cocoa/qcocoafiledialoghelper.mm @@ -1,41 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the QtGui module 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 <QtCore/qglobal.h> @@ -54,6 +18,8 @@ #include <QtCore/qoperatingsystemversion.h> #include <QtCore/qdir.h> #include <QtCore/qregularexpression.h> +#include <QtCore/qpointer.h> +#include <QtCore/private/qcore_mac_p.h> #include <QtGui/qguiapplication.h> #include <QtGui/private/qguiapplication_p.h> @@ -61,11 +27,15 @@ #include <qpa/qplatformtheme.h> #include <qpa/qplatformnativeinterface.h> +#include <UniformTypeIdentifiers/UniformTypeIdentifiers.h> + QT_USE_NAMESPACE +using namespace Qt::StringLiterals; + static NSString *strippedText(QString s) { - s.remove(QLatin1String("...")); + s.remove("..."_L1); return QPlatformTheme::removeMnemonics(s).trimmed().toNSString(); } @@ -86,13 +56,12 @@ typedef QSharedPointer<QFileDialogOptions> SharedPointerFileDialogOptions; NSView *m_accessoryView; NSPopUpButton *m_popupButton; NSTextField *m_textField; - QCocoaFileDialogHelper *m_helper; - NSString *m_currentDirectory; + QPointer<QCocoaFileDialogHelper> m_helper; SharedPointerFileDialogOptions m_options; - QString *m_currentSelection; - QStringList *m_nameFilterDropDownList; - QStringList *m_selectedNameFilter; + QString m_currentSelection; + QStringList m_nameFilterDropDownList; + QStringList m_selectedNameFilter; } - (instancetype)initWithAcceptMode:(const QString &)selectFile @@ -112,26 +81,56 @@ typedef QSharedPointer<QFileDialogOptions> SharedPointerFileDialogOptions; m_helper = helper; - m_nameFilterDropDownList = new QStringList(m_options->nameFilters()); + m_nameFilterDropDownList = m_options->nameFilters(); QString selectedVisualNameFilter = m_options->initiallySelectedNameFilter(); - m_selectedNameFilter = new QStringList([self findStrippedFilterWithVisualFilterName:selectedVisualNameFilter]); - - QFileInfo sel(selectFile); + m_selectedNameFilter = [self findStrippedFilterWithVisualFilterName:selectedVisualNameFilter]; + + m_panel.extensionHidden = [&]{ + for (const auto &nameFilter : m_nameFilterDropDownList) { + const auto extensions = QPlatformFileDialogHelper::cleanFilterList(nameFilter); + for (const auto &extension : extensions) { + // Explicitly show extensions if we detect a filter + // of "all files", as clicking a single file with + // extensions hidden will then populate the name + // field with only the file name, without any + // extension. + if (extension == "*"_L1 || extension == "*.*"_L1) + return false; + + // Explicitly show extensions if we detect a filter + // that has a multi-part extension. This prevents + // confusing situations where the user clicks e.g. + // 'foo.tar.gz' and 'foo.tar' is populated in the + // file name box, but when then clicking save macOS + // will warn that the file needs to end in .gz, + // due to thinking the user tried to save the file + // as a 'tar' file instead. Unfortunately this + // property can only be set before the panel is + // shown, so we can't toggle it on and off based + // on the active filter. + if (extension.count('.') > 1) + return false; + } + } + return true; + }(); + + const QFileInfo sel(selectFile); if (sel.isDir() && !sel.isBundle()){ - m_currentDirectory = [sel.absoluteFilePath().toNSString() retain]; - m_currentSelection = new QString; + m_panel.directoryURL = [NSURL fileURLWithPath:sel.absoluteFilePath().toNSString()]; + m_currentSelection.clear(); } else { - m_currentDirectory = [sel.absolutePath().toNSString() retain]; - m_currentSelection = new QString(sel.absoluteFilePath()); + m_panel.directoryURL = [NSURL fileURLWithPath:sel.absolutePath().toNSString()]; + m_currentSelection = sel.absoluteFilePath(); } [self createPopUpButton:selectedVisualNameFilter hideDetails:options->testOption(QFileDialogOptions::HideNameFilterDetails)]; [self createTextField]; [self createAccessory]; - m_panel.accessoryView = m_nameFilterDropDownList->size() > 1 ? m_accessoryView : nil; + m_panel.accessoryView = m_nameFilterDropDownList.size() > 1 ? m_accessoryView : nil; // -setAccessoryView: can result in -panel:directoryDidChange: - // resetting our m_currentDirectory, set the delegate + // resetting our current directory. Set the delegate // here to make sure it gets the correct value. m_panel.delegate = self; @@ -145,10 +144,6 @@ typedef QSharedPointer<QFileDialogOptions> SharedPointerFileDialogOptions; - (void)dealloc { - delete m_nameFilterDropDownList; - delete m_selectedNameFilter; - delete m_currentSelection; - [m_panel orderOut:m_panel]; m_panel.accessoryView = nil; [m_popupButton release]; @@ -156,19 +151,17 @@ typedef QSharedPointer<QFileDialogOptions> SharedPointerFileDialogOptions; [m_accessoryView release]; m_panel.delegate = nil; [m_panel release]; - [m_currentDirectory release]; [super dealloc]; } - (bool)showPanel:(Qt::WindowModality) windowModality withParent:(QWindow *)parent { - QFileInfo info(*m_currentSelection); + const QFileInfo info(m_currentSelection); NSString *filepath = info.filePath().toNSString(); NSURL *url = [NSURL fileURLWithPath:filepath isDirectory:info.isDir()]; bool selectable = (m_options->acceptMode() == QFileDialogOptions::AcceptSave) || [self panel:m_panel shouldEnableURL:url]; - m_panel.directoryURL = [NSURL fileURLWithPath:m_currentDirectory]; m_panel.nameFieldStringValue = selectable ? info.fileName().toNSString() : @""; [self updateProperties]; @@ -193,6 +186,8 @@ typedef QSharedPointer<QFileDialogOptions> SharedPointerFileDialogOptions; // QEventLoop has been interrupted, and the second-most event loop has not // yet been reactivated (regardless if [NSApp run] is still on the stack)), // showing a native modal dialog will fail. + if (!m_helper) + return; QMacAutoReleasePool pool; @@ -214,7 +209,7 @@ typedef QSharedPointer<QFileDialogOptions> SharedPointerFileDialogOptions; - (void)closePanel { - *m_currentSelection = QString::fromNSString(m_panel.URL.path).normalized(QString::NormalizationForm_C); + m_currentSelection = QString::fromNSString(m_panel.URL.path).normalized(QString::NormalizationForm_C); if (m_panel.sheet) [NSApp endSheet:m_panel]; @@ -224,19 +219,6 @@ typedef QSharedPointer<QFileDialogOptions> SharedPointerFileDialogOptions; [m_panel close]; } -- (BOOL)isHiddenFileAtURL:(NSURL *)url -{ - BOOL hidden = NO; - if (url) { - CFBooleanRef isHiddenProperty; - if (CFURLCopyResourcePropertyForKey((__bridge CFURLRef)url, kCFURLIsHiddenKey, &isHiddenProperty, nullptr)) { - hidden = CFBooleanGetValue(isHiddenProperty); - CFRelease(isHiddenProperty); - } - } - return hidden; -} - - (BOOL)panel:(id)sender shouldEnableURL:(NSURL *)url { Q_UNUSED(sender); @@ -245,60 +227,141 @@ typedef QSharedPointer<QFileDialogOptions> SharedPointerFileDialogOptions; if (!filename.length) return NO; - // Always accept directories regardless of their names (unless it is a bundle): - NSFileManager *fm = NSFileManager.defaultManager; - NSDictionary *fileAttrs = [fm attributesOfItemAtPath:filename error:nil]; - if (!fileAttrs) - return NO; // Error accessing the file means 'no'. - NSString *fileType = fileAttrs.fileType; - bool isDir = [fileType isEqualToString:NSFileTypeDirectory]; - if (isDir) { - if (!m_panel.treatsFilePackagesAsDirectories) { - if ([NSWorkspace.sharedWorkspace isFilePackageAtPath:filename] == NO) - return YES; - } + const QFileInfo fileInfo(QString::fromNSString(filename)); + + // Always accept directories regardless of their names. + // This also includes symlinks and aliases to directories. + if (fileInfo.isDir()) { + // Unless it's a bundle, and we should treat bundles as files. + // FIXME: We'd like to use QFileInfo::isBundle() here, but the + // detection in QFileInfo goes deeper than NSWorkspace does + // (likely a bug), and as a result causes TCC permission + // dialogs to pop up when used. + bool treatBundlesAsFiles = !m_panel.treatsFilePackagesAsDirectories; + if (!(treatBundlesAsFiles && [NSWorkspace.sharedWorkspace isFilePackageAtPath:filename])) + return YES; } - QString qtFileName = QFileInfo(QString::fromNSString(filename)).fileName(); - // No filter means accept everything - bool nameMatches = m_selectedNameFilter->isEmpty(); - // Check if the current file name filter accepts the file: - for (int i = 0; !nameMatches && i < m_selectedNameFilter->size(); ++i) { - if (QDir::match(m_selectedNameFilter->at(i), qtFileName)) - nameMatches = true; - } - if (!nameMatches) + if (![self fileInfoMatchesCurrentNameFilter:fileInfo]) return NO; QDir::Filters filter = m_options->filter(); - if ((!(filter & (QDir::Dirs | QDir::AllDirs)) && isDir) - || (!(filter & QDir::Files) && [fileType isEqualToString:NSFileTypeRegular]) - || ((filter & QDir::NoSymLinks) && [fileType isEqualToString:NSFileTypeSymbolicLink])) + if ((!(filter & (QDir::Dirs | QDir::AllDirs)) && fileInfo.isDir()) + || (!(filter & QDir::Files) && (fileInfo.isFile() && !fileInfo.isSymLink())) + || ((filter & QDir::NoSymLinks) && fileInfo.isSymLink())) return NO; bool filterPermissions = ((filter & QDir::PermissionMask) && (filter & QDir::PermissionMask) != QDir::PermissionMask); if (filterPermissions) { - if ((!(filter & QDir::Readable) && [fm isReadableFileAtPath:filename]) - || (!(filter & QDir::Writable) && [fm isWritableFileAtPath:filename]) - || (!(filter & QDir::Executable) && [fm isExecutableFileAtPath:filename])) + if ((!(filter & QDir::Readable) && fileInfo.isReadable()) + || (!(filter & QDir::Writable) && fileInfo.isWritable()) + || (!(filter & QDir::Executable) && fileInfo.isExecutable())) return NO; } - if (!(filter & QDir::Hidden) - && (qtFileName.startsWith(QLatin1Char('.')) || [self isHiddenFileAtURL:url])) + + // We control the visibility of hidden files via the showsHiddenFiles + // property on the panel, based on QDir::Hidden being set. But the user + // can also toggle this via the Command+Shift+. keyboard shortcut, + // in which case they have explicitly requested to show hidden files, + // and we should enable them even if QDir::Hidden was not set. In + // effect, we don't need to filter on QDir::Hidden here. + + return YES; +} + +- (BOOL)panel:(id)sender validateURL:(NSURL *)url error:(NSError * _Nullable *)outError +{ + Q_ASSERT(sender == m_panel); + + if (!m_panel.allowedFileTypes && !m_selectedNameFilter.isEmpty()) { + // The save panel hasn't done filtering on our behalf, + // either because we couldn't represent the filter via + // allowedFileTypes, or we opted out due to a multi part + // extension, so do the filtering/validation ourselves. + QFileInfo fileInfo(QString::fromNSString(url.path).normalized(QString::NormalizationForm_C)); + + if ([self fileInfoMatchesCurrentNameFilter:fileInfo]) + return YES; + + if (fileInfo.suffix().isEmpty()) { + // The filter requires a file name with an extension. + // We're going to add a default file name in selectedFiles, + // to match the native behavior. Check now that we can + // overwrite the file, if is already exists. + fileInfo = [self applyDefaultSuffixFromCurrentNameFilter:fileInfo]; + + if (!fileInfo.exists() || m_options->testOption(QFileDialogOptions::DontConfirmOverwrite)) + return YES; + + QMacAutoReleasePool pool; + auto *alert = [[NSAlert new] autorelease]; + alert.alertStyle = NSAlertStyleCritical; + + alert.messageText = [NSString stringWithFormat:qt_mac_AppKitString(@"SavePanel", + @"\\U201c%@\\U201d already exists. Do you want to replace it?"), + fileInfo.fileName().toNSString()]; + alert.informativeText = [NSString stringWithFormat:qt_mac_AppKitString(@"SavePanel", + @"A file or folder with the same name already exists in the folder %@. " + "Replacing it will overwrite its current contents."), + fileInfo.absoluteDir().dirName().toNSString()]; + + auto *replaceButton = [alert addButtonWithTitle:qt_mac_AppKitString(@"SavePanel", @"Replace")]; + replaceButton.hasDestructiveAction = YES; + replaceButton.tag = 1337; + [alert addButtonWithTitle:qt_mac_AppKitString(@"Common", @"Cancel")]; + + [alert beginSheetModalForWindow:m_panel + completionHandler:^(NSModalResponse returnCode) { + [NSApp stopModalWithCode:returnCode]; + }]; + return [NSApp runModalForWindow:alert.window] == replaceButton.tag; + } else { + QFileInfo firstFilter(m_selectedNameFilter.first()); + auto *domain = qGuiApp->organizationDomain().toNSString(); + *outError = [NSError errorWithDomain:domain code:0 userInfo:@{ + NSLocalizedDescriptionKey:[NSString stringWithFormat:qt_mac_AppKitString(@"SavePanel", + @"You cannot save this document with extension \\U201c.%1$@\\U201d at the end " + "of the name. The required extension is \\U201c.%2$@\\U201d."), + fileInfo.completeSuffix().toNSString(), firstFilter.completeSuffix().toNSString()] + }]; return NO; + } + } return YES; } +- (QFileInfo)applyDefaultSuffixFromCurrentNameFilter:(const QFileInfo &)fileInfo +{ + QFileInfo filterInfo(m_selectedNameFilter.first()); + return QFileInfo(fileInfo.absolutePath(), + fileInfo.baseName() + '.' + filterInfo.completeSuffix()); +} + +- (bool)fileInfoMatchesCurrentNameFilter:(const QFileInfo &)fileInfo +{ + // No filter means accept everything + if (m_selectedNameFilter.isEmpty()) + return true; + + // Check if the current file name filter accepts the file + for (const auto &filter : m_selectedNameFilter) { + if (QDir::match(filter, fileInfo.fileName())) + return true; + } + + return false; +} + - (void)setNameFilters:(const QStringList &)filters hideDetails:(BOOL)hideDetails { [m_popupButton removeAllItems]; - *m_nameFilterDropDownList = filters; + m_nameFilterDropDownList = filters; if (filters.size() > 0){ for (int i = 0; i < filters.size(); ++i) { - QString filter = hideDetails ? [self removeExtensions:filters.at(i)] : filters.at(i); - [m_popupButton addItemWithTitle:filter.toNSString()]; + const QString filter = hideDetails ? [self removeExtensions:filters.at(i)] : filters.at(i); + [m_popupButton.menu addItemWithTitle:filter.toNSString() action:nil keyEquivalent:@""]; } [m_popupButton selectItemAtIndex:0]; m_panel.accessoryView = m_accessoryView; @@ -313,8 +376,10 @@ typedef QSharedPointer<QFileDialogOptions> SharedPointerFileDialogOptions; { // This m_delegate function is called when the _name_ filter changes. Q_UNUSED(sender); - QString selection = m_nameFilterDropDownList->value([m_popupButton indexOfSelectedItem]); - *m_selectedNameFilter = [self findStrippedFilterWithVisualFilterName:selection]; + if (!m_helper) + return; + const QString selection = m_nameFilterDropDownList.value([m_popupButton indexOfSelectedItem]); + m_selectedNameFilter = [self findStrippedFilterWithVisualFilterName:selection]; [m_panel validateVisibleColumns]; [self updateProperties]; @@ -333,18 +398,25 @@ typedef QSharedPointer<QFileDialogOptions> SharedPointerFileDialogOptions; } return result; } else { - QList<QUrl> result; QString filename = QString::fromNSString(m_panel.URL.path).normalized(QString::NormalizationForm_C); - const QString defaultSuffix = m_options->defaultSuffix(); - const QFileInfo fileInfo(filename); + QFileInfo fileInfo(filename); + + if (fileInfo.suffix().isEmpty() && ![self fileInfoMatchesCurrentNameFilter:fileInfo]) { + // We end up in this situation if we accept a file name without extension + // in panel:validateURL:error. If so, we match the behavior of the native + // save dialog and add the first of the accepted extension from the filter. + fileInfo = [self applyDefaultSuffixFromCurrentNameFilter:fileInfo]; + } // If neither the user or the NSSavePanel have provided a suffix, use // the default suffix (if it exists). - if (fileInfo.suffix().isEmpty() && !defaultSuffix.isEmpty()) - filename.append('.').append(defaultSuffix); + const QString defaultSuffix = m_options->defaultSuffix(); + if (fileInfo.suffix().isEmpty() && !defaultSuffix.isEmpty()) { + fileInfo.setFile(fileInfo.absolutePath(), + fileInfo.baseName() + '.' + defaultSuffix); + } - result << QUrl::fromLocalFile(filename); - return result; + return { QUrl::fromLocalFile(fileInfo.filePath()) }; } } @@ -376,19 +448,25 @@ typedef QSharedPointer<QFileDialogOptions> SharedPointerFileDialogOptions; m_panel.allowedFileTypes = [self computeAllowedFileTypes]; - // Explicitly show extensions if we detect a filter - // that has a multi-part extension. This prevents - // confusing situations where the user clicks e.g. - // 'foo.tar.gz' and 'foo.tar' is populated in the - // file name box, but when then clicking save macOS - // will warn that the file needs to end in .gz, - // due to thinking the user tried to save the file - // as a 'tar' file instead. Unfortunately this - // property can only be set before the panel is - // shown, so it will not have any effect when - // switching filters in an already opened dialog. - if (m_panel.allowedFileTypes.count > 2) - m_panel.extensionHidden = NO; + // Setting allowedFileTypes to nil is not enough to reset any + // automatically added extension based on a previous filter. + // This is problematic because extensions can in some cases + // be hidden from the user, resulting in confusion when the + // resulting file name doesn't match the current empty filter. + // We work around this by temporarily resetting the allowed + // content type to one without an extension, which forces + // the save panel to update and remove the extension. + const bool nameFieldHasExtension = m_panel.nameFieldStringValue.pathExtension.length > 0; + if (!m_panel.allowedFileTypes && !nameFieldHasExtension && !openpanel_cast(m_panel)) { + if (!UTTypeDirectory.preferredFilenameExtension) { + m_panel.allowedContentTypes = @[ UTTypeDirectory ]; + m_panel.allowedFileTypes = nil; + } else { + qWarning() << "UTTypeDirectory unexpectedly reported an extension"; + } + } + + m_panel.showsHiddenFiles = m_options->filter().testFlag(QDir::Hidden); if (m_panel.visible) [m_panel validateVisibleColumns]; @@ -397,10 +475,22 @@ typedef QSharedPointer<QFileDialogOptions> SharedPointerFileDialogOptions; - (void)panelSelectionDidChange:(id)sender { Q_UNUSED(sender); + + if (!m_helper) + return; + + // Save panels only allow you to select directories, which + // means currentChanged will only be emitted when selecting + // a directory, and if so, with the latest chosen file name, + // which is confusing and inconsistent. We choose to bail + // out entirely for save panels, to give consistent behavior. + if (!openpanel_cast(m_panel)) + return; + if (m_panel.visible) { - QString selection = QString::fromNSString(m_panel.URL.path); - if (selection != *m_currentSelection) { - *m_currentSelection = selection; + const QString selection = QString::fromNSString(m_panel.URL.path); + if (selection != m_currentSelection) { + m_currentSelection = selection; emit m_helper->currentChanged(QUrl::fromLocalFile(selection)); } } @@ -410,14 +500,10 @@ typedef QSharedPointer<QFileDialogOptions> SharedPointerFileDialogOptions; { Q_UNUSED(sender); - if (!(path && path.length) || [path isEqualToString:m_currentDirectory]) + if (!m_helper) return; - [m_currentDirectory release]; - m_currentDirectory = [path retain]; - - // ### fixme: priv->setLastVisitedDirectory(newDir); - emit m_helper->directoryEntered(QUrl::fromLocalFile(QString::fromNSString(m_currentDirectory))); + m_helper->panelDirectoryDidChange(path); } /* @@ -425,11 +511,9 @@ typedef QSharedPointer<QFileDialogOptions> SharedPointerFileDialogOptions; for the current name filter, and updates the save panel. If a filter do not conform to the format *.xyz or * or *.*, - all files types are allowed. - - Extensions with more than one part (e.g. "tar.gz") are - reduced to their final part, as NSSavePanel does not deal - well with multi-part extensions. + or contains an extensions with more than one part (e.g. "tar.gz") + we treat that as allowing all file types, and do our own + validation in panel:validateURL:error. */ - (NSArray<NSString*>*)computeAllowedFileTypes { @@ -437,17 +521,20 @@ typedef QSharedPointer<QFileDialogOptions> SharedPointerFileDialogOptions; return nil; // panel:shouldEnableURL: does the file filtering for NSOpenPanel QStringList fileTypes; - for (const QString &filter : *m_selectedNameFilter) { - if (!filter.startsWith(QLatin1String("*."))) + for (const QString &filter : std::as_const(m_selectedNameFilter)) { + if (!filter.startsWith("*."_L1)) continue; - if (filter.contains(QLatin1Char('?'))) + if (filter.contains(u'?')) continue; - if (filter.count(QLatin1Char('*')) != 1) + if (filter.count(u'*') != 1) continue; auto extensions = filter.split('.', Qt::SkipEmptyParts); + if (extensions.count() > 2) + return nil; + fileTypes += extensions.last(); } @@ -484,15 +571,15 @@ typedef QSharedPointer<QFileDialogOptions> SharedPointerFileDialogOptions; m_popupButton.target = self; m_popupButton.action = @selector(filterChanged:); - if (m_nameFilterDropDownList->size() > 0) { + if (!m_nameFilterDropDownList.isEmpty()) { int filterToUse = -1; - for (int i = 0; i < m_nameFilterDropDownList->size(); ++i) { - QString currentFilter = m_nameFilterDropDownList->at(i); + for (int i = 0; i < m_nameFilterDropDownList.size(); ++i) { + const QString currentFilter = m_nameFilterDropDownList.at(i); if (selectedFilter == currentFilter || (filterToUse == -1 && currentFilter.startsWith(selectedFilter))) filterToUse = i; QString filter = hideDetails ? [self removeExtensions:currentFilter] : currentFilter; - [m_popupButton addItemWithTitle:filter.toNSString()]; + [m_popupButton.menu addItemWithTitle:filter.toNSString() action:nil keyEquivalent:@""]; } if (filterToUse != -1) [m_popupButton selectItemAtIndex:filterToUse]; @@ -501,9 +588,9 @@ typedef QSharedPointer<QFileDialogOptions> SharedPointerFileDialogOptions; - (QStringList) findStrippedFilterWithVisualFilterName:(QString)name { - for (int i = 0; i < m_nameFilterDropDownList->size(); ++i) { - if (m_nameFilterDropDownList->at(i).startsWith(name)) - return QPlatformFileDialogHelper::cleanFilterList(m_nameFilterDropDownList->at(i)); + for (const QString ¤tFilter : std::as_const(m_nameFilterDropDownList)) { + if (currentFilter.startsWith(name)) + return QPlatformFileDialogHelper::cleanFilterList(currentFilter); } return QStringList(); } @@ -544,21 +631,32 @@ void QCocoaFileDialogHelper::panelClosed(NSInteger result) void QCocoaFileDialogHelper::setDirectory(const QUrl &directory) { + m_directory = directory; + if (m_delegate) m_delegate->m_panel.directoryURL = [NSURL fileURLWithPath:directory.toLocalFile().toNSString()]; - else - m_directory = directory; } QUrl QCocoaFileDialogHelper::directory() const { - if (m_delegate) { - QString path = QString::fromNSString(m_delegate->m_panel.directoryURL.path).normalized(QString::NormalizationForm_C); - return QUrl::fromLocalFile(path); - } return m_directory; } +void QCocoaFileDialogHelper::panelDirectoryDidChange(NSString *path) +{ + if (!path || [path isEqual:NSNull.null] || !path.length) + return; + + const auto oldDirectory = m_directory; + m_directory = QUrl::fromLocalFile( + QString::fromNSString(path).normalized(QString::NormalizationForm_C)); + + if (m_directory != oldDirectory) { + // FIXME: Plumb old directory back to QFileDialog's lastVisitedDir? + emit directoryEntered(m_directory); + } +} + void QCocoaFileDialogHelper::selectFile(const QUrl &filename) { QString filePath = filename.toLocalFile(); diff --git a/src/plugins/platforms/cocoa/qcocoafontdialoghelper.h b/src/plugins/platforms/cocoa/qcocoafontdialoghelper.h index 81ba3d6c41..03824c76ce 100644 --- a/src/plugins/platforms/cocoa/qcocoafontdialoghelper.h +++ b/src/plugins/platforms/cocoa/qcocoafontdialoghelper.h @@ -1,41 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the QtGui module 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 #ifndef QCOCOAFONTDIALOGHELPER_H #define QCOCOAFONTDIALOGHELPER_H diff --git a/src/plugins/platforms/cocoa/qcocoafontdialoghelper.mm b/src/plugins/platforms/cocoa/qcocoafontdialoghelper.mm index 355d658242..5cdf6bf02c 100644 --- a/src/plugins/platforms/cocoa/qcocoafontdialoghelper.mm +++ b/src/plugins/platforms/cocoa/qcocoafontdialoghelper.mm @@ -1,41 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the QtGui module 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> @@ -202,7 +166,17 @@ QT_NAMESPACE_ALIAS_OBJC_CLASS(QNSFontPanelDelegate); { mDialogIsExecuting = false; mResultSet = false; - [mFontPanel makeKeyAndOrderFront:mFontPanel]; + // Make this an asynchronous call, so the panel is made key only + // in the next event loop run. This is to make sure that by + // the time the modal loop is run in runModalForWindow below, + // which internally also sets the panel to key window, + // the panel is not yet key, and the NSApp still has the right + // reference to the _previousKeyWindow. Otherwise both NSApp.key + // and NSApp._previousKeyWindow would wrongly point to the panel, + // loosing any reference to the window that was key before. + dispatch_async(dispatch_get_main_queue(), ^{ + [mFontPanel makeKeyAndOrderFront:mFontPanel]; + }); } - (BOOL)runApplicationModalPanel diff --git a/src/plugins/platforms/cocoa/qcocoaglcontext.h b/src/plugins/platforms/cocoa/qcocoaglcontext.h index 633b9256c8..919f7c240b 100644 --- a/src/plugins/platforms/cocoa/qcocoaglcontext.h +++ b/src/plugins/platforms/cocoa/qcocoaglcontext.h @@ -1,46 +1,11 @@ -/**************************************************************************** -** -** 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 #ifndef QCOCOAGLCONTEXT_H #define QCOCOAGLCONTEXT_H #include <QtCore/QPointer> +#include <QtCore/QVarLengthArray> #include <QtCore/private/qcore_mac_p.h> #include <qpa/qplatformopenglcontext.h> #include <QtGui/qopenglcontext.h> diff --git a/src/plugins/platforms/cocoa/qcocoaglcontext.mm b/src/plugins/platforms/cocoa/qcocoaglcontext.mm index 026a99a152..a65311175f 100644 --- a/src/plugins/platforms/cocoa/qcocoaglcontext.mm +++ b/src/plugins/platforms/cocoa/qcocoaglcontext.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> @@ -44,6 +8,8 @@ #include "qcocoahelpers.h" #include "qcocoascreen.h" +#include <QtCore/private/qcore_mac_p.h> + #include <qdebug.h> #include <dlfcn.h> @@ -451,7 +417,7 @@ bool QCocoaGLContext::setDrawable(QPlatformSurface *surface) // NSOpenGLContext is not re-entrant. Even when using separate contexts per thread, // view, and window, calls into the API will still deadlock. For more information // see https://openradar.appspot.com/37064579 -static QMutex s_reentrancyMutex; +Q_CONSTINIT static QMutex s_reentrancyMutex; void QCocoaGLContext::update() { diff --git a/src/plugins/platforms/cocoa/qcocoahelpers.h b/src/plugins/platforms/cocoa/qcocoahelpers.h index 0fa868c73f..c6862a9e65 100644 --- a/src/plugins/platforms/cocoa/qcocoahelpers.h +++ b/src/plugins/platforms/cocoa/qcocoahelpers.h @@ -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 #ifndef QCOCOAHELPERS_H #define QCOCOAHELPERS_H @@ -77,6 +41,8 @@ Q_DECLARE_LOGGING_CATEGORY(lcQpaScreen) Q_DECLARE_LOGGING_CATEGORY(lcQpaApplication) Q_DECLARE_LOGGING_CATEGORY(lcQpaClipboard) Q_DECLARE_LOGGING_CATEGORY(lcInputDevices) +Q_DECLARE_LOGGING_CATEGORY(lcQpaDialogs) +Q_DECLARE_LOGGING_CATEGORY(lcQpaMenus) class QPixmap; class QString; @@ -90,16 +56,6 @@ NSDragOperation qt_mac_mapDropActions(Qt::DropActions actions); Qt::DropAction qt_mac_mapNSDragOperation(NSDragOperation nsActions); Qt::DropActions qt_mac_mapNSDragOperations(NSDragOperation nsActions); -template <typename T> -typename std::enable_if<std::is_pointer<T>::value, T>::type -qt_objc_cast(id object) -{ - if ([object isKindOfClass:[typename std::remove_pointer<T>::type class]]) - return static_cast<T>(object); - - return nil; -} - QT_MANGLE_NAMESPACE(QNSView) *qnsview_cast(NSView *view); // Misc @@ -122,6 +78,9 @@ Qt::MouseButtons currentlyPressedMouseButtons(); // accelerators. QString qt_mac_removeAmpersandEscapes(QString s); +// Similar to __NXKitString for localized AppKit strings +NSString *qt_mac_AppKitString(NSString *table, NSString *key); + enum { QtCocoaEventSubTypeWakeup = SHRT_MAX, QtCocoaEventSubTypePostMessage = SHRT_MAX-1 @@ -226,19 +185,6 @@ QT_NAMESPACE_ALIAS_OBJC_CLASS(QNSPanelContentsWrapper); // ------------------------------------------------------------------------- -// QAppleRefCounted expects the retain function to return the object -io_object_t q_IOObjectRetain(io_object_t obj); -// QAppleRefCounted expects the release function to return void -void q_IOObjectRelease(io_object_t obj); - -template <typename T> -class QIOType : public QAppleRefCounted<T, io_object_t, q_IOObjectRetain, q_IOObjectRelease> -{ - using QAppleRefCounted<T, io_object_t, q_IOObjectRetain, q_IOObjectRelease>::QAppleRefCounted; -}; - -// ------------------------------------------------------------------------- - // Depending on the ABI of the platform, we may need to use objc_msgSendSuper_stret: // - http://www.sealiesoftware.com/blog/archive/2008/10/30/objc_explain_objc_msgSend_stret.html // - https://lists.apple.com/archives/cocoa-dev/2008/Feb/msg02338.html @@ -362,7 +308,7 @@ QSendSuperHelper<Args...> qt_objcDynamicSuperHelper(id receiver, SEL selector, A // ------------------------------------------------------------------------- -struct InputMethodQueryResult : public QHash<Qt::InputMethodQuery, QVariant> +struct InputMethodQueryResult : public QHash<int, QVariant> { operator bool() { return !isEmpty(); } }; diff --git a/src/plugins/platforms/cocoa/qcocoahelpers.mm b/src/plugins/platforms/cocoa/qcocoahelpers.mm index 2578fb2140..1eba88d5e3 100644 --- a/src/plugins/platforms/cocoa/qcocoahelpers.mm +++ b/src/plugins/platforms/cocoa/qcocoahelpers.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> @@ -64,6 +28,8 @@ Q_LOGGING_CATEGORY(lcQpaScreen, "qt.qpa.screen", QtCriticalMsg); Q_LOGGING_CATEGORY(lcQpaApplication, "qt.qpa.application"); Q_LOGGING_CATEGORY(lcQpaClipboard, "qt.qpa.clipboard") Q_LOGGING_CATEGORY(lcInputDevices, "qt.qpa.input.devices") +Q_LOGGING_CATEGORY(lcQpaDialogs, "qt.qpa.dialogs") +Q_LOGGING_CATEGORY(lcQpaMenus, "qt.qpa.menus") // // Conversion Functions @@ -225,7 +191,7 @@ QString qt_mac_applicationName() if (appName.isEmpty()) { QString arg0 = QGuiApplicationPrivate::instance()->appName(); if (arg0.contains("/")) { - QStringList parts = arg0.split(QLatin1Char('/')); + QStringList parts = arg0.split(u'/'); appName = parts.at(parts.count() - 1); } else { appName = arg0; @@ -370,6 +336,15 @@ QString qt_mac_removeAmpersandEscapes(QString s) return QPlatformTheme::removeMnemonics(s).trimmed(); } +NSString *qt_mac_AppKitString(NSString *table, NSString *key) +{ + static const NSBundle *appKit = [NSBundle bundleForClass:NSApplication.class]; + if (!appKit) + return key; + + return [appKit localizedStringForKey:key value:nil table:table]; +} + QT_END_NAMESPACE /*! \internal @@ -499,21 +474,6 @@ QT_END_NAMESPACE // ------------------------------------------------------------------------- -io_object_t q_IOObjectRetain(io_object_t obj) -{ - kern_return_t ret = IOObjectRetain(obj); - Q_ASSERT(!ret); - return obj; -} - -void q_IOObjectRelease(io_object_t obj) -{ - kern_return_t ret = IOObjectRelease(obj); - Q_ASSERT(!ret); -} - -// ------------------------------------------------------------------------- - InputMethodQueryResult queryInputMethod(QObject *object, Qt::InputMethodQueries queries) { if (object) { diff --git a/src/plugins/platforms/cocoa/qcocoainputcontext.h b/src/plugins/platforms/cocoa/qcocoainputcontext.h index 3a8322461c..58d1d7ec86 100644 --- a/src/plugins/platforms/cocoa/qcocoainputcontext.h +++ b/src/plugins/platforms/cocoa/qcocoainputcontext.h @@ -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 #ifndef QCOCOAINPUTCONTEXT_H #define QCOCOAINPUTCONTEXT_H diff --git a/src/plugins/platforms/cocoa/qcocoainputcontext.mm b/src/plugins/platforms/cocoa/qcocoainputcontext.mm index 63d23b0f43..70461376e2 100644 --- a/src/plugins/platforms/cocoa/qcocoainputcontext.mm +++ b/src/plugins/platforms/cocoa/qcocoainputcontext.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> @@ -116,9 +80,16 @@ void QCocoaInputContext::commit() if (!view) return; - QMacAutoReleasePool pool; [view unmarkText]; + [view.inputContext discardMarkedText]; + if (view.inputContext != NSTextInputContext.currentInputContext) { + // discardMarkedText will activate the TSM document of the given input context, + // which may not match the current input context. To ensure the current input + // context is not left in an inconsistent state with a deactivated document + // we need to manually activate it here. + [NSTextInputContext.currentInputContext activate]; + } } @@ -129,8 +100,6 @@ void QCocoaInputContext::reset() { qCDebug(lcQpaInputMethods) << "Resetting input method"; - QPlatformInputContext::reset(); - if (!m_focusWindow) return; @@ -139,7 +108,6 @@ void QCocoaInputContext::reset() if (!view) return; - QMacAutoReleasePool pool; if (NSTextInputContext *ctxt = [NSTextInputContext currentInputContext]) { [ctxt discardMarkedText]; [view unmarkText]; @@ -182,11 +150,18 @@ void QCocoaInputContext::updateLocale() QString language = QString::fromNSString(languages.firstObject); QLocale locale(language); - if (m_locale != locale) { + + bool localeUpdated = m_locale != locale; + static bool firstUpdate = true; + + m_locale = locale; + + if (localeUpdated && !firstUpdate) { qCDebug(lcQpaInputMethods) << "Reporting new locale" << locale; - m_locale = locale; emitLocaleChanged(); } + + firstUpdate = false; } QT_END_NAMESPACE diff --git a/src/plugins/platforms/cocoa/qcocoaintegration.h b/src/plugins/platforms/cocoa/qcocoaintegration.h index fc9580b1b8..664700cf51 100644 --- a/src/plugins/platforms/cocoa/qcocoaintegration.h +++ b/src/plugins/platforms/cocoa/qcocoaintegration.h @@ -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 #ifndef QPLATFORMINTEGRATION_COCOA_H #define QPLATFORMINTEGRATION_COCOA_H @@ -51,11 +15,14 @@ #if QT_CONFIG(vulkan) #include "qcocoavulkaninstance.h" #endif +#include "qcocoawindowmanager.h" #include <QtCore/QScopedPointer> #include <qpa/qplatformintegration.h> #include <QtGui/private/qcoretextfontdatabase_p.h> -#include <QtGui/private/qopenglcontext_p.h> +#ifndef QT_NO_OPENGL +# include <QtGui/private/qopenglcontext_p.h> +#endif #include <QtGui/private/qapplekeymapper_p.h> Q_FORWARD_DECLARE_OBJC_CLASS(NSToolbar); @@ -104,7 +71,7 @@ public: QCoreTextFontDatabase *fontDatabase() const override; QCocoaNativeInterface *nativeInterface() const override; QPlatformInputContext *inputContext() const override; -#ifndef QT_NO_ACCESSIBILITY +#if QT_CONFIG(accessibility) QCocoaAccessibility *accessibility() const override; #endif #ifndef QT_NO_CLIPBOARD @@ -117,14 +84,10 @@ public: QCocoaServices *services() const override; QVariant styleHint(StyleHint hint) const override; - Qt::KeyboardModifiers queryKeyboardModifiers() const override; - QList<int> possibleKeys(const QKeyEvent *event) const override; - - void setToolbar(QWindow *window, NSToolbar *toolbar); - NSToolbar *toolbar(QWindow *window) const; - void clearToolbars(); + QPlatformKeyMapper *keyMapper() const override; void setApplicationIcon(const QIcon &icon) const override; + void setApplicationBadge(qint64 number) override; void beep() const override; void quit() const override; @@ -139,7 +102,7 @@ private: QScopedPointer<QCoreTextFontDatabase> mFontDb; QScopedPointer<QPlatformInputContext> mInputContext; -#ifndef QT_NO_ACCESSIBILITY +#if QT_CONFIG(accessibility) QScopedPointer<QCocoaAccessibility> mAccessibility; #endif QScopedPointer<QPlatformTheme> mPlatformTheme; @@ -154,7 +117,8 @@ private: #if QT_CONFIG(vulkan) mutable QCocoaVulkanInstance *mCocoaVulkanInstance = nullptr; #endif - QHash<QWindow *, NSToolbar *> mToolbars; + + QCocoaWindowManager m_windowManager; }; Q_DECLARE_OPERATORS_FOR_FLAGS(QCocoaIntegration::Options) diff --git a/src/plugins/platforms/cocoa/qcocoaintegration.mm b/src/plugins/platforms/cocoa/qcocoaintegration.mm index 92edeac558..2ce39ff897 100644 --- a/src/plugins/platforms/cocoa/qcocoaintegration.mm +++ b/src/plugins/platforms/cocoa/qcocoaintegration.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> @@ -66,12 +30,19 @@ #include <QtCore/qcoreapplication.h> #include <QtGui/qpointingdevice.h> +#include <QtCore/private/qcore_mac_p.h> #include <QtGui/private/qcoregraphics_p.h> -#include <QtGui/private/qopenglcontext_p.h> - +#include <QtGui/private/qmacmimeregistry_p.h> +#ifndef QT_NO_OPENGL +# include <QtGui/private/qopenglcontext_p.h> +#endif +#include <QtGui/private/qrhibackingstore_p.h> #include <QtGui/private/qfontengine_coretext_p.h> #include <IOKit/graphics/IOGraphicsLib.h> +#include <UniformTypeIdentifiers/UTCoreTypes.h> + +#include <inttypes.h> static void initResources() { @@ -80,12 +51,9 @@ static void initResources() QT_BEGIN_NAMESPACE -Q_LOGGING_CATEGORY(lcQpa, "qt.qpa", QtWarningMsg); +using namespace Qt::StringLiterals; -// Lives here so that the linker is forced to include the QCocoaWindowManager -// object file also in static builds. -static void initializeWindowManager() { Q_UNUSED(QCocoaWindowManager::instance()); } -Q_CONSTRUCTOR_FUNCTION(initializeWindowManager) +Q_LOGGING_CATEGORY(lcQpa, "qt.qpa", QtWarningMsg); static void logVersionInformation() { @@ -119,7 +87,7 @@ static QCocoaIntegration::Options parseOptions(const QStringList ¶mList) QCocoaIntegration::Options options; for (const QString ¶m : paramList) { #ifndef QT_NO_FREETYPE - if (param == QLatin1String("fontengine=freetype")) + if (param == "fontengine=freetype"_L1) options |= QCocoaIntegration::UseFreeTypeFontEngine; else #endif @@ -133,7 +101,7 @@ QCocoaIntegration *QCocoaIntegration::mInstance = nullptr; QCocoaIntegration::QCocoaIntegration(const QStringList ¶mList) : mOptions(parseOptions(paramList)) , mFontDb(nullptr) -#ifndef QT_NO_ACCESSIBILITY +#if QT_CONFIG(accessibility) , mAccessibility(new QCocoaAccessibility) #endif #ifndef QT_NO_CLIPBOARD @@ -157,9 +125,9 @@ QCocoaIntegration::QCocoaIntegration(const QStringList ¶mList) #endif mFontDb.reset(new QCoreTextFontDatabaseEngineFactory<QCoreTextFontEngine>); - QString icStr = QPlatformInputContextFactory::requested(); - icStr.isNull() ? mInputContext.reset(new QCocoaInputContext) - : mInputContext.reset(QPlatformInputContextFactory::create(icStr)); + auto icStrs = QPlatformInputContextFactory::requested(); + icStrs.isEmpty() ? mInputContext.reset(new QCocoaInputContext) + : mInputContext.reset(QPlatformInputContextFactory::create(icStrs)); initResources(); QMacAutoReleasePool pool; @@ -174,16 +142,6 @@ QCocoaIntegration::QCocoaIntegration(const QStringList ¶mList) // wants to be foreground applications so change the process type. (But // see the function implementation for exceptions.) qt_mac_transformProccessToForegroundApplication(); - - // Move the application window to front to make it take focus, also when launching - // from the terminal. On 10.12+ this call has been moved to applicationDidFinishLauching - // to work around issues with loss of focus at startup. - if (QOperatingSystemVersion::current() < QOperatingSystemVersion::MacOSSierra) { - // Ignoring other apps is necessary (we must ignore the terminal), but makes - // Qt apps play slightly less nice with other apps when lanching from Finder - // (See the activateIgnoringOtherApps docs.) - [cocoaApplication activateIgnoringOtherApps : YES]; - } } // Qt 4 also does not set the application delegate, so that behavior @@ -202,7 +160,7 @@ QCocoaIntegration::QCocoaIntegration(const QStringList ¶mList) QCocoaScreen::initializeScreens(); - QMacInternalPasteboardMime::initializeMimeTypes(); + QMacMimeRegistry::initializeMimeTypes(); QCocoaMimeTypes::initializeMimeTypes(); QWindowSystemInterfacePrivate::TabletEvent::setPlatformSynthesizesMouse(false); QWindowSystemInterface::registerInputDevice(new QInputDevice(QString("keyboard"), 0, @@ -227,17 +185,18 @@ QCocoaIntegration::~QCocoaIntegration() [[NSApplication sharedApplication] setDelegate:nil]; } + // Stop global mouse event and app activation monitoring + QCocoaWindow::removePopupMonitor(); + #ifndef QT_NO_CLIPBOARD // Delete the clipboard integration and destroy mime type converters. // Deleting the clipboard integration flushes promised pastes using // the mime converters - the ordering here is important. delete mCocoaClipboard; - QMacInternalPasteboardMime::destroyMimeTypes(); + QMacMimeRegistry::destroyMimeTypes(); #endif QCocoaScreen::cleanupScreens(); - - clearToolbars(); } QCocoaIntegration *QCocoaIntegration::instance() @@ -277,6 +236,7 @@ bool QCocoaIntegration::hasCapability(QPlatformIntegration::Capability cap) cons case RasterGLSurface: case ApplicationState: case ApplicationIcon: + case BackingStoreStaticContents: return true; default: return QPlatformIntegration::hasCapability(cap); @@ -339,7 +299,27 @@ QPlatformBackingStore *QCocoaIntegration::createPlatformBackingStore(QWindow *wi return nullptr; } - return new QCALayerBackingStore(window); + switch (window->surfaceType()) { + case QSurface::RasterSurface: + return new QCALayerBackingStore(window); + case QSurface::MetalSurface: + case QSurface::OpenGLSurface: + case QSurface::VulkanSurface: + // If the window is a widget window, we know that the QWidgetRepaintManager + // will explicitly use rhiFlush() for the window owning the backingstore, + // and any child window with the same surface format. This means we can + // safely return a QCALayerBackingStore here, to ensure that any plain + // flush() for child windows that don't have a matching surface format + // will still work, by setting the layer's contents property. + if (window->inherits("QWidgetWindow")) + return new QCALayerBackingStore(window); + + // Otherwise we return a QRhiBackingStore, that implements flush() in + // terms of rhiFlush(). + return new QRhiBackingStore(window); + default: + return nullptr; + } } QAbstractEventDispatcher *QCocoaIntegration::createEventDispatcher() const @@ -375,7 +355,7 @@ QPlatformInputContext *QCocoaIntegration::inputContext() const return mInputContext.data(); } -#ifndef QT_NO_ACCESSIBILITY +#if QT_CONFIG(accessibility) QCocoaAccessibility *QCocoaIntegration::accessibility() const { return mAccessibility.data(); @@ -396,12 +376,12 @@ QCocoaDrag *QCocoaIntegration::drag() const QStringList QCocoaIntegration::themeNames() const { - return QStringList(QLatin1String(QCocoaTheme::name)); + return QStringList(QLatin1StringView(QCocoaTheme::name)); } QPlatformTheme *QCocoaIntegration::createPlatformTheme(const QString &name) const { - if (name == QLatin1String(QCocoaTheme::name)) + if (name == QLatin1StringView(QCocoaTheme::name)) return new QCocoaTheme; return QPlatformIntegration::createPlatformTheme(name); } @@ -426,38 +406,9 @@ QVariant QCocoaIntegration::styleHint(StyleHint hint) const return QPlatformIntegration::styleHint(hint); } -Qt::KeyboardModifiers QCocoaIntegration::queryKeyboardModifiers() const -{ - return QAppleKeyMapper::queryKeyboardModifiers(); -} - -QList<int> QCocoaIntegration::possibleKeys(const QKeyEvent *event) const +QPlatformKeyMapper *QCocoaIntegration::keyMapper() const { - return mKeyboardMapper->possibleKeys(event); -} - -void QCocoaIntegration::setToolbar(QWindow *window, NSToolbar *toolbar) -{ - if (NSToolbar *prevToolbar = mToolbars.value(window)) - [prevToolbar release]; - - [toolbar retain]; - mToolbars.insert(window, toolbar); -} - -NSToolbar *QCocoaIntegration::toolbar(QWindow *window) const -{ - return mToolbars.value(window); -} - -void QCocoaIntegration::clearToolbars() -{ - QHash<QWindow *, NSToolbar *>::const_iterator it = mToolbars.constBegin(); - while (it != mToolbars.constEnd()) { - [it.value() release]; - ++it; - } - mToolbars.clear(); + return mKeyboardMapper.data(); } void QCocoaIntegration::setApplicationIcon(const QIcon &icon) const @@ -467,6 +418,11 @@ void QCocoaIntegration::setApplicationIcon(const QIcon &icon) const NSApp.applicationIconImage = [NSImage imageFromQIcon:icon withSize:fallbackSize]; } +void QCocoaIntegration::setApplicationBadge(qint64 number) +{ + NSApp.dockTile.badgeLabel = number ? [NSString stringWithFormat:@"%" PRId64, number] : nil; +} + void QCocoaIntegration::beep() const { NSBeep(); @@ -485,8 +441,8 @@ void QCocoaIntegration::focusWindowChanged(QWindow *focusWindow) return; static bool hasDefaultApplicationIcon = [](){ - NSImage *genericApplicationIcon = [[NSWorkspace sharedWorkspace] - iconForFileType:NSFileTypeForHFSTypeCode(kGenericApplicationIcon)]; + NSImage *genericApplicationIcon = [NSWorkspace.sharedWorkspace + iconForContentType:UTTypeApplicationBundle]; NSImage *applicationIcon = [NSImage imageNamed:NSImageNameApplicationIcon]; NSRect rect = NSMakeRect(0, 0, 32, 32); diff --git a/src/plugins/platforms/cocoa/qcocoaintrospection.h b/src/plugins/platforms/cocoa/qcocoaintrospection.h index 21b55a1711..c4956a9f62 100644 --- a/src/plugins/platforms/cocoa/qcocoaintrospection.h +++ b/src/plugins/platforms/cocoa/qcocoaintrospection.h @@ -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 /**************************************************************************** ** diff --git a/src/plugins/platforms/cocoa/qcocoaintrospection.mm b/src/plugins/platforms/cocoa/qcocoaintrospection.mm index 9d1e987176..2d769d4c6d 100644 --- a/src/plugins/platforms/cocoa/qcocoaintrospection.mm +++ b/src/plugins/platforms/cocoa/qcocoaintrospection.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 /**************************************************************************** ** diff --git a/src/plugins/platforms/cocoa/qcocoamenu.h b/src/plugins/platforms/cocoa/qcocoamenu.h index 1bf9cface0..f53f1c3e74 100644 --- a/src/plugins/platforms/cocoa/qcocoamenu.h +++ b/src/plugins/platforms/cocoa/qcocoamenu.h @@ -1,42 +1,6 @@ -/**************************************************************************** -** -** Copyright (C) 2018 The Qt Company Ltd. -** Copyright (C) 2012 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com, author James Turner <james.turner@kdab.com> -** 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) 2018 The Qt Company Ltd. +// Copyright (C) 2012 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com, author James Turner <james.turner@kdab.com> +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only #ifndef QCOCOAMENU_H #define QCOCOAMENU_H diff --git a/src/plugins/platforms/cocoa/qcocoamenu.mm b/src/plugins/platforms/cocoa/qcocoamenu.mm index 5023afa3a6..fa88a19d45 100644 --- a/src/plugins/platforms/cocoa/qcocoamenu.mm +++ b/src/plugins/platforms/cocoa/qcocoamenu.mm @@ -1,42 +1,6 @@ -/**************************************************************************** -** -** Copyright (C) 2018 The Qt Company Ltd. -** Copyright (C) 2012 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com, author James Turner <james.turner@kdab.com> -** 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) 2018 The Qt Company Ltd. +// Copyright (C) 2012 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com, author James Turner <james.turner@kdab.com> +// 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> @@ -54,6 +18,9 @@ #include "qcocoascreen.h" #include "qcocoaapplicationdelegate.h" +#include <QtCore/private/qcore_mac_p.h> +#include <QtCore/qpointer.h> + QT_BEGIN_NAMESPACE QCocoaMenu::QCocoaMenu() : @@ -71,11 +38,13 @@ QCocoaMenu::QCocoaMenu() : QCocoaMenu::~QCocoaMenu() { - for (auto *item : qAsConst(m_menuItems)) { + for (auto *item : std::as_const(m_menuItems)) { if (item->menuParent() == this) item->setMenuParent(nullptr); } + if (isOpen()) + dismiss(); [m_nativeMenu release]; } @@ -94,7 +63,7 @@ void QCocoaMenu::setMinimumWidth(int width) void QCocoaMenu::setFont(const QFont &font) { if (font.resolveMask()) { - NSFont *customMenuFont = [NSFont fontWithName:font.families().first().toNSString() + NSFont *customMenuFont = [NSFont fontWithName:font.families().constFirst().toNSString() size:font.pointSize()]; m_nativeMenu.font = customMenuFont; } @@ -122,7 +91,7 @@ void QCocoaMenu::insertMenuItem(QPlatformMenuItem *menuItem, QPlatformMenuItem * int index = m_menuItems.indexOf(beforeItem); // if a before item is supplied, it should be in the menu if (index < 0) { - qWarning("Before menu item not found"); + qCWarning(lcQpaMenus) << beforeItem << "not in" << m_menuItems; return; } m_menuItems.insert(index, cocoaItem); @@ -160,13 +129,13 @@ void QCocoaMenu::insertNative(QCocoaMenuItem *item, QCocoaMenuItem *beforeItem) } if (nativeItem.menu) { - qWarning() << "Menu item" << item->text() << "already in menu" << QString::fromNSString(nativeItem.menu.title); + qCWarning(lcQpaMenus) << "Menu item" << item->text() << "already in menu" << QString::fromNSString(nativeItem.menu.title); return; } if (beforeItem) { if (beforeItem->isMerged()) { - qWarning("No non-merged before menu item found"); + qCWarning(lcQpaMenus, "No non-merged before menu item found"); return; } const NSInteger nativeIndex = [m_nativeMenu indexOfItem:beforeItem->nsItem()]; @@ -202,7 +171,7 @@ void QCocoaMenu::removeMenuItem(QPlatformMenuItem *menuItem) QMacAutoReleasePool pool; QCocoaMenuItem *cocoaItem = static_cast<QCocoaMenuItem *>(menuItem); if (!m_menuItems.contains(cocoaItem)) { - qWarning("Menu does not contain the item to be removed"); + qCWarning(lcQpaMenus) << m_menuItems << "does not contain" << cocoaItem; return; } @@ -215,7 +184,7 @@ void QCocoaMenu::removeMenuItem(QPlatformMenuItem *menuItem) m_menuItems.removeOne(cocoaItem); if (!cocoaItem->isMerged()) { if (m_nativeMenu != cocoaItem->nsItem().menu) { - qWarning("Item to remove does not belong to this menu"); + qCWarning(lcQpaMenus) << cocoaItem << "does not belong to" << m_nativeMenu; return; } [m_nativeMenu removeItem:cocoaItem->nsItem()]; @@ -255,7 +224,7 @@ void QCocoaMenu::syncMenuItem_helper(QPlatformMenuItem *menuItem, bool menubarUp QMacAutoReleasePool pool; QCocoaMenuItem *cocoaItem = static_cast<QCocoaMenuItem *>(menuItem); if (!m_menuItems.contains(cocoaItem)) { - qWarning("Item does not belong to this menu"); + qCWarning(lcQpaMenus) << cocoaItem << "does not belong to" << this; return; } @@ -320,7 +289,7 @@ void QCocoaMenu::syncSeparatorsCollapsible(bool enable) if (lastVisibleItem && lastVisibleItem.separatorItem) lastVisibleItem.hidden = YES; } else { - for (auto *item : qAsConst(m_menuItems)) { + for (auto *item : std::as_const(m_menuItems)) { if (!item->isSeparator()) continue; @@ -354,8 +323,12 @@ void QCocoaMenu::showPopup(const QWindow *parentWindow, const QRect &targetRect, { QMacAutoReleasePool pool; + QPointer<QCocoaMenu> guard = this; + QPoint pos = QPoint(targetRect.left(), targetRect.top() + targetRect.height()); - QCocoaWindow *cocoaWindow = parentWindow ? static_cast<QCocoaWindow *>(parentWindow->handle()) : nullptr; + // If the app quits while the menu is open (e.g. through a timer that starts before the menu was opened), + // then the window will have been destroyed before this function finishes executing. Account for that with QPointer. + QPointer<QCocoaWindow> cocoaWindow = parentWindow ? static_cast<QCocoaWindow *>(parentWindow->handle()) : nullptr; NSView *view = cocoaWindow ? cocoaWindow->view() : nil; NSMenuItem *nsItem = item ? ((QCocoaMenuItem *)item)->nsItem() : nil; @@ -438,6 +411,11 @@ void QCocoaMenu::showPopup(const QWindow *parentWindow, const QRect &targetRect, } } + if (!guard) { + menuParentGuard.dismiss(); + return; + } + // The calls above block, and also swallow any mouse release event, // so we need to clear any mouse button that triggered the menu popup. if (cocoaWindow && !cocoaWindow->isForeignWindow()) @@ -459,7 +437,7 @@ QPlatformMenuItem *QCocoaMenu::menuItemAt(int position) const QPlatformMenuItem *QCocoaMenu::menuItemForTag(quintptr tag) const { - for (auto *item : qAsConst(m_menuItems)) { + for (auto *item : std::as_const(m_menuItems)) { if (item->tag() == tag) return item; } @@ -475,7 +453,7 @@ QList<QCocoaMenuItem *> QCocoaMenu::items() const QList<QCocoaMenuItem *> QCocoaMenu::merged() const { QList<QCocoaMenuItem *> result; - for (auto *item : qAsConst(m_menuItems)) { + for (auto *item : std::as_const(m_menuItems)) { if (item->menu()) { // recurse into submenus result.append(item->menu()->merged()); continue; @@ -496,7 +474,7 @@ void QCocoaMenu::propagateEnabledState(bool enabled) if (!m_enabled && enabled) // Some ancestor was enabled, but this menu is not return; - for (auto *item : qAsConst(m_menuItems)) { + for (auto *item : std::as_const(m_menuItems)) { if (QCocoaMenu *menu = item->menu()) menu->propagateEnabledState(enabled); else @@ -517,6 +495,10 @@ void QCocoaMenu::setAttachedItem(NSMenuItem *item) if (m_attachedItem) m_attachedItem.submenu = m_nativeMenu; + // NSMenuItems with a submenu and submenuAction: as the item's action + // will not take part in NSMenuValidation, so explicitly enable/disable + // the item here. See also QCocoaMenuItem::resolveTargetAction() + m_attachedItem.enabled = m_attachedItem.hasSubmenu; } NSMenuItem *QCocoaMenu::attachedItem() const diff --git a/src/plugins/platforms/cocoa/qcocoamenubar.h b/src/plugins/platforms/cocoa/qcocoamenubar.h index 6f8409434c..785de9c0f6 100644 --- a/src/plugins/platforms/cocoa/qcocoamenubar.h +++ b/src/plugins/platforms/cocoa/qcocoamenubar.h @@ -1,42 +1,6 @@ -/**************************************************************************** -** -** Copyright (C) 2018 The Qt Company Ltd. -** Copyright (C) 2012 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com, author James Turner <james.turner@kdab.com> -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the plugins module 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) 2018 The Qt Company Ltd. +// Copyright (C) 2012 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com, author James Turner <james.turner@kdab.com> +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only #ifndef QCOCOAMENUBAR_H #define QCOCOAMENUBAR_H @@ -45,6 +9,8 @@ #include <qpa/qplatformmenu.h> #include "qcocoamenu.h" +#include <QtCore/qpointer.h> + QT_BEGIN_NAMESPACE class QCocoaWindow; @@ -81,14 +47,12 @@ private: bool needsImmediateUpdate(); bool shouldDisable(QCocoaWindow *active) const; - void insertDefaultEditItems(QCocoaMenu *menu); NSMenuItem *nativeItemForMenu(QCocoaMenu *menu) const; QList<QPointer<QCocoaMenu> > m_menus; NSMenu *m_nativeMenu; QPointer<QCocoaWindow> m_window; - QList<QPointer<QCocoaMenuItem>> m_defaultEditMenuItems; }; QT_END_NAMESPACE diff --git a/src/plugins/platforms/cocoa/qcocoamenubar.mm b/src/plugins/platforms/cocoa/qcocoamenubar.mm index 365346ac8c..2493d90724 100644 --- a/src/plugins/platforms/cocoa/qcocoamenubar.mm +++ b/src/plugins/platforms/cocoa/qcocoamenubar.mm @@ -1,42 +1,6 @@ -/**************************************************************************** -** -** Copyright (C) 2018 The Qt Company Ltd. -** Copyright (C) 2012 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com, author James Turner <james.turner@kdab.com> -** 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) 2018 The Qt Company Ltd. +// Copyright (C) 2012 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com, author James Turner <james.turner@kdab.com> +// 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> @@ -45,10 +9,14 @@ #include "qcocoamenuloader.h" #include "qcocoaapplication.h" // for custom application category #include "qcocoaapplicationdelegate.h" +#include "qcocoahelpers.h" #include <QtGui/QGuiApplication> #include <QtCore/QDebug> +#include <QtCore/private/qcore_mac_p.h> +#include <QtGui/private/qguiapplication_p.h> + QT_BEGIN_NAMESPACE static QList<QCocoaMenuBar*> static_menubars; @@ -57,18 +25,19 @@ QCocoaMenuBar::QCocoaMenuBar() { static_menubars.append(this); + // clicks into the menu bar should close all popup windows + static QMacNotificationObserver menuBarClickObserver(nil, NSMenuDidBeginTrackingNotification, ^{ + QGuiApplicationPrivate::instance()->closeAllPopups(); + }); + m_nativeMenu = [[NSMenu alloc] init]; -#ifdef QT_COCOA_ENABLE_MENU_DEBUG - qDebug() << "Construct QCocoaMenuBar" << this << m_nativeMenu; -#endif + qCDebug(lcQpaMenus) << "Constructed" << this << "with" << m_nativeMenu; } QCocoaMenuBar::~QCocoaMenuBar() { -#ifdef QT_COCOA_ENABLE_MENU_DEBUG - qDebug() << "~QCocoaMenuBar" << this; -#endif - for (auto menu : qAsConst(m_menus)) { + qCDebug(lcQpaMenus) << "Destructing" << this << "with" << m_nativeMenu; + for (auto menu : std::as_const(m_menus)) { if (!menu) continue; NSMenuItem *item = nativeItemForMenu(menu); @@ -121,17 +90,16 @@ void QCocoaMenuBar::insertMenu(QPlatformMenu *platformMenu, QPlatformMenu *befor { QCocoaMenu *menu = static_cast<QCocoaMenu *>(platformMenu); QCocoaMenu *beforeMenu = static_cast<QCocoaMenu *>(before); -#ifdef QT_COCOA_ENABLE_MENU_DEBUG - qDebug() << "QCocoaMenuBar" << this << "insertMenu" << menu << "before" << before; -#endif + + qCDebug(lcQpaMenus) << "Inserting" << menu << "before" << before << "into" << this; if (m_menus.contains(QPointer<QCocoaMenu>(menu))) { - qWarning("This menu already belongs to the menubar, remove it first"); + qCWarning(lcQpaMenus, "This menu already belongs to the menubar, remove it first"); return; } if (beforeMenu && !m_menus.contains(QPointer<QCocoaMenu>(beforeMenu))) { - qWarning("The before menu does not belong to the menubar"); + qCWarning(lcQpaMenus, "The before menu does not belong to the menubar"); return; } @@ -165,7 +133,7 @@ void QCocoaMenuBar::removeMenu(QPlatformMenu *platformMenu) { QCocoaMenu *menu = static_cast<QCocoaMenu *>(platformMenu); if (!m_menus.contains(menu)) { - qWarning("Trying to remove a menu that does not belong to the menubar"); + qCWarning(lcQpaMenus) << "Trying to remove" << menu << "that does not belong to" << this; return; } @@ -194,11 +162,6 @@ void QCocoaMenuBar::syncMenu_helper(QPlatformMenu *menu, bool menubarUpdate) for (QCocoaMenuItem *item : cocoaMenu->items()) cocoaMenu->syncMenuItem_helper(item, menubarUpdate); - const QString captionNoAmpersand = QString::fromNSString(cocoaMenu->nsMenu().title) - .remove(QLatin1Char('&')); - if (captionNoAmpersand == QCoreApplication::translate("QCocoaMenu", "Edit")) - insertDefaultEditItems(cocoaMenu); - BOOL shouldHide = YES; if (cocoaMenu->isVisible()) { // If the NSMenu has no visible items, or only separators, we should hide it @@ -211,10 +174,44 @@ void QCocoaMenuBar::syncMenu_helper(QPlatformMenu *menu, bool menubarUpdate) } } - if (NSMenuItem *attachedItem = cocoaMenu->attachedItem()) { - // Non-nil attached item means the item's submenu is set - attachedItem.title = cocoaMenu->nsMenu().title; - attachedItem.hidden = shouldHide; + if (NSMenuItem *menuItem = cocoaMenu->attachedItem()) { + // Non-nil menu item means the item's sub menu is set + + NSString *menuTitle = cocoaMenu->nsMenu().title; + + // The NSMenu's title is what's visible to the user, and AppKit uses this + // for some of its heuristics of when to add special items to the menus, + // such as 'Enter Full Screen' in the View menu, the search bare in the + // Help menu, and the "Send App feedback to Apple" in the Help menu. + // This relies on the title matching AppKit's localized value from the + // MenuCommands table, which in turn depends on the preferredLocalizations + // of the AppKit bundle. We don't do any automatic translation of menu + // titles visible to the user, so this relies on the application developer + // having chosen translated titles that match AppKit's, and that the Qt + // preferred UI languages match AppKit's preferredLocalizations. + + // In the case of the Edit menu, AppKit uses the NSMenuItem's title + // for its heuristics of when to add the dictation and emoji entries, + // and this title is not visible to the user. But like above, the + // heuristics are based on the localized title of the menu, so we need + // to ensure the title matches AppKit's localization. + + // Unfortunately, the title we have at this point may have gone through + // Qt's i18n machinery already, via e.g. tr("Edit") in the application, + // in which case we don't know the context of the translation, and can't + // do a reverse lookup to go back to the untranslated title to pass to + // AppKit. As a workaround we translate the title via a our context, + // and document that the user needs to ensure their application matches + // this translation. + if ([menuTitle isEqual:@"Edit"] || [menuTitle isEqual:tr("Edit").toNSString()]) { + menuItem.title = qt_mac_AppKitString(@"InputManager", @"Edit"); + } else { + // The Edit menu is the only case we know of so far, but to be on + // the safe side we always sync the menu title. + menuItem.title = menuTitle; + } + + menuItem.hidden = shouldHide; } } @@ -228,9 +225,7 @@ NSMenuItem *QCocoaMenuBar::nativeItemForMenu(QCocoaMenu *menu) const void QCocoaMenuBar::handleReparent(QWindow *newParentWindow) { -#ifdef QT_COCOA_ENABLE_MENU_DEBUG - qDebug() << "QCocoaMenuBar" << this << "handleReparent" << newParentWindow; -#endif + qCDebug(lcQpaMenus) << "Reparenting" << this << "to" << newParentWindow; if (!m_window.isNull()) m_window->setMenubar(nullptr); @@ -262,7 +257,7 @@ QCocoaWindow *QCocoaMenuBar::findWindowForMenubar() QCocoaMenuBar *QCocoaMenuBar::findGlobalMenubar() { - for (auto *menubar : qAsConst(static_menubars)) { + for (auto *menubar : std::as_const(static_menubars)) { if (menubar->m_window.isNull()) return menubar; } @@ -298,12 +293,11 @@ void QCocoaMenuBar::updateMenuBarImmediately() if (!mb) return; -#ifdef QT_COCOA_ENABLE_MENU_DEBUG - qDebug() << "QCocoaMenuBar" << "updateMenuBarImmediately" << cw; -#endif + qCDebug(lcQpaMenus) << "Updating" << mb << "immediately for" << cw; + bool disableForModal = mb->shouldDisable(cw); - for (auto menu : qAsConst(mb->m_menus)) { + for (auto menu : std::as_const(mb->m_menus)) { if (!menu) continue; NSMenuItem *item = mb->nativeItemForMenu(menu); @@ -332,7 +326,21 @@ void QCocoaMenuBar::updateMenuBarImmediately() } [mergedItems release]; - [NSApp setMainMenu:mb->nsMenu()]; + + NSMenu *newMainMenu = mb->nsMenu(); + if (NSApp.mainMenu == newMainMenu) { + // NSApplication triggers _customizeMainMenu when the menu + // changes, which takes care of adding text input items to + // the edit menu e.g., but this doesn't happen if the menu + // is the same. In our case we might be re-using an existing + // menu, but the menu might have new sub menus that need to + // be customized. To ensure NSApplication does the right + // thing we reset the main menu first. + qCDebug(lcQpaMenus) << "Clearing main menu temporarily"; + NSApp.mainMenu = nil; + } + NSApp.mainMenu = newMainMenu; + insertWindowMenu(); [loader qtTranslateApplicationMenu]; } @@ -353,21 +361,39 @@ void QCocoaMenuBar::insertWindowMenu() winMenuItem.hidden = YES; winMenuItem.submenu = [[[NSMenu alloc] initWithTitle:@"QtWindowMenu"] autorelease]; + + // AppKit has a bug in [NSApplication setWindowsMenu:] where it will resolve + // the last item of the window menu's itemArray, but not account for the array + // being empty, resulting in a lookup of itemAtIndex:-1. To work around this, + // we insert a hidden dummy item into the menu. See FB13369198. + auto *dummyItem = [[NSMenuItem alloc] initWithTitle:@"" action:nil keyEquivalent:@""]; + dummyItem.hidden = YES; + [winMenuItem.submenu addItem:[dummyItem autorelease]]; + [mainMenu insertItem:winMenuItem atIndex:mainMenu.itemArray.count]; app.windowsMenu = winMenuItem.submenu; - // Windows, created and 'ordered front' before, will not be in this menu: + // Windows that have already been ordered in at this point have already been + // evaluated by AppKit via _addToWindowsMenuIfNecessary and added to the menu, + // but since the menu didn't exist at that point the addition was a noop. + // Instead of trying to duplicate the logic AppKit uses for deciding if + // a window should be part of the Window menu we toggle one of the settings + // that definitely will affect this, which results in AppKit reevaluating the + // situation and adding the window to the menu if necessary. for (NSWindow *win in app.windows) { - if (win.title && ![win.title isEqualToString:@""]) - [app addWindowsItem:win title:win.title filename:NO]; + win.excludedFromWindowsMenu = !win.excludedFromWindowsMenu; + win.excludedFromWindowsMenu = !win.excludedFromWindowsMenu; } } QList<QCocoaMenuItem*> QCocoaMenuBar::merged() const { QList<QCocoaMenuItem*> r; - for (auto menu : qAsConst(m_menus)) + for (auto menu : std::as_const(m_menus)) { + if (!menu) + continue; r.append(menu->merged()); + } return r; } @@ -386,10 +412,10 @@ bool QCocoaMenuBar::shouldDisable(QCocoaWindow *active) const // When there is an application modal window on screen, the entries of // the menubar should be disabled. The exception in Qt is that if the // modal window is the only window on screen, then we enable the menu bar. - for (auto *window : qAsConst(topWindows)) { + for (auto *window : std::as_const(topWindows)) { if (window->isVisible() && window->modality() == Qt::ApplicationModal) { // check for other visible windows - for (auto *other : qAsConst(topWindows)) { + for (auto *other : std::as_const(topWindows)) { if ((window != other) && (other->isVisible())) { // INVARIANT: we found another visible window // on screen other than our modalWidget. We therefore @@ -410,8 +436,8 @@ bool QCocoaMenuBar::shouldDisable(QCocoaWindow *active) const QPlatformMenu *QCocoaMenuBar::menuForTag(quintptr tag) const { - for (auto menu : qAsConst(m_menus)) - if (menu->tag() == tag) + for (auto menu : std::as_const(m_menus)) + if (menu && menu->tag() == tag) return menu; return nullptr; @@ -419,10 +445,13 @@ QPlatformMenu *QCocoaMenuBar::menuForTag(quintptr tag) const NSMenuItem *QCocoaMenuBar::itemForRole(QPlatformMenuItem::MenuRole role) { - for (auto menu : qAsConst(m_menus)) - for (auto *item : menu->items()) - if (item->effectiveRole() == role) - return item->nsItem(); + for (auto menu : std::as_const(m_menus)) { + if (menu) { + for (auto *item : menu->items()) + if (item->effectiveRole() == role) + return item->nsItem(); + } + } return nil; } @@ -432,48 +461,6 @@ QCocoaWindow *QCocoaMenuBar::cocoaWindow() const return m_window.data(); } -void QCocoaMenuBar::insertDefaultEditItems(QCocoaMenu *menu) -{ - if (menu->items().isEmpty()) - return; - - NSMenu *nsEditMenu = menu->nsMenu(); - if ([nsEditMenu itemAtIndex:nsEditMenu.numberOfItems - 1].action - == @selector(orderFrontCharacterPalette:)) { - for (auto defaultEditMenuItem : qAsConst(m_defaultEditMenuItems)) { - if (menu->items().contains(defaultEditMenuItem)) - menu->removeMenuItem(defaultEditMenuItem); - } - qDeleteAll(m_defaultEditMenuItems); - m_defaultEditMenuItems.clear(); - } else { - if (m_defaultEditMenuItems.isEmpty()) { - QCocoaMenuItem *separator = new QCocoaMenuItem; - separator->setIsSeparator(true); - - QCocoaMenuItem *dictationItem = new QCocoaMenuItem; - dictationItem->setText(QCoreApplication::translate("QCocoaMenuItem", "Start Dictation...")); - QObject::connect(dictationItem, &QPlatformMenuItem::activated, this, []{ - [NSApplication.sharedApplication performSelector:@selector(startDictation:)]; - }); - - QCocoaMenuItem *emojiItem = new QCocoaMenuItem; - emojiItem->setText(QCoreApplication::translate("QCocoaMenuItem", "Emoji && Symbols")); - emojiItem->setShortcut(QKeyCombination(Qt::MetaModifier|Qt::ControlModifier, Qt::Key_Space)); - QObject::connect(emojiItem, &QPlatformMenuItem::activated, this, []{ - [NSApplication.sharedApplication orderFrontCharacterPalette:nil]; - }); - - m_defaultEditMenuItems << separator << dictationItem << emojiItem; - } - for (auto defaultEditMenuItem : qAsConst(m_defaultEditMenuItems)) { - if (menu->items().contains(defaultEditMenuItem)) - menu->removeMenuItem(defaultEditMenuItem); - menu->insertMenuItem(defaultEditMenuItem, nullptr); - } - } -} - QT_END_NAMESPACE #include "moc_qcocoamenubar.cpp" diff --git a/src/plugins/platforms/cocoa/qcocoamenuitem.h b/src/plugins/platforms/cocoa/qcocoamenuitem.h index 2962bdb26e..f677ffb7a7 100644 --- a/src/plugins/platforms/cocoa/qcocoamenuitem.h +++ b/src/plugins/platforms/cocoa/qcocoamenuitem.h @@ -1,42 +1,6 @@ -/**************************************************************************** -** -** Copyright (C) 2018 The Qt Company Ltd. -** Copyright (C) 2012 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com, author James Turner <james.turner@kdab.com> -** 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) 2018 The Qt Company Ltd. +// Copyright (C) 2012 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com, author James Turner <james.turner@kdab.com> +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only #ifndef QCOCOAMENUITEM_H #define QCOCOAMENUITEM_H @@ -44,7 +8,7 @@ #include <qpa/qplatformmenu.h> #include <QtGui/QImage> -//#define QT_COCOA_ENABLE_MENU_DEBUG +#include <QtCore/qpointer.h> Q_FORWARD_DECLARE_OBJC_CLASS(NSMenuItem); Q_FORWARD_DECLARE_OBJC_CLASS(NSMenu); diff --git a/src/plugins/platforms/cocoa/qcocoamenuitem.mm b/src/plugins/platforms/cocoa/qcocoamenuitem.mm index 022972518a..3a0f71bc50 100644 --- a/src/plugins/platforms/cocoa/qcocoamenuitem.mm +++ b/src/plugins/platforms/cocoa/qcocoamenuitem.mm @@ -1,42 +1,6 @@ -/**************************************************************************** -** -** Copyright (C) 2018 The Qt Company Ltd. -** Copyright (C) 2012 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com, author James Turner <james.turner@kdab.com> -** 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) 2018 The Qt Company Ltd. +// Copyright (C) 2012 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com, author James Turner <james.turner@kdab.com> +// 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> @@ -52,12 +16,15 @@ #include "qcocoamenuloader.h" #include <QtGui/private/qcoregraphics_p.h> #include <QtCore/qregularexpression.h> +#include <QtCore/private/qcore_mac_p.h> #include <QtGui/private/qapplekeymapper_p.h> #include <QtCore/QDebug> QT_BEGIN_NAMESPACE +using namespace Qt::StringLiterals; + static const char *application_menu_strings[] = { QT_TRANSLATE_NOOP("MAC_APPLICATION_MENU","About %1"), @@ -213,38 +180,71 @@ void QCocoaMenuItem::setNativeContents(WId item) m_itemView.needsDisplay = YES; } -static QPlatformMenuItem::MenuRole detectMenuRole(const QString &caption) +static QPlatformMenuItem::MenuRole detectMenuRole(const QString &captionWithPossibleMnemonic) { - QString captionNoAmpersand(caption); - captionNoAmpersand.remove(QLatin1Char('&')); - const QString aboutString = QCoreApplication::translate("QCocoaMenuItem", "About"); - if (captionNoAmpersand.startsWith(aboutString, Qt::CaseInsensitive) - || captionNoAmpersand.endsWith(aboutString, Qt::CaseInsensitive)) { - static const QRegularExpression qtRegExp(QLatin1String("qt$"), QRegularExpression::CaseInsensitiveOption); - if (captionNoAmpersand.contains(qtRegExp)) - return QPlatformMenuItem::AboutQtRole; - return QPlatformMenuItem::AboutRole; - } - if (captionNoAmpersand.startsWith(QCoreApplication::translate("QCocoaMenuItem", "Config"), Qt::CaseInsensitive) - || captionNoAmpersand.startsWith(QCoreApplication::translate("QCocoaMenuItem", "Preference"), Qt::CaseInsensitive) - || captionNoAmpersand.startsWith(QCoreApplication::translate("QCocoaMenuItem", "Options"), Qt::CaseInsensitive) - || captionNoAmpersand.startsWith(QCoreApplication::translate("QCocoaMenuItem", "Setting"), Qt::CaseInsensitive) - || captionNoAmpersand.startsWith(QCoreApplication::translate("QCocoaMenuItem", "Setup"), Qt::CaseInsensitive)) { - return QPlatformMenuItem::PreferencesRole; - } - if (captionNoAmpersand.startsWith(QCoreApplication::translate("QCocoaMenuItem", "Quit"), Qt::CaseInsensitive) - || captionNoAmpersand.startsWith(QCoreApplication::translate("QCocoaMenuItem", "Exit"), Qt::CaseInsensitive)) { - return QPlatformMenuItem::QuitRole; + QString itemCaption(captionWithPossibleMnemonic); + itemCaption.remove(u'&'); + + static const std::tuple<QPlatformMenuItem::MenuRole, std::vector<std::tuple<Qt::MatchFlags, const char *>>> roleMap[] = { + { QPlatformMenuItem::AboutRole, { + { Qt::MatchStartsWith | Qt::MatchEndsWith, QT_TRANSLATE_NOOP("QCocoaMenuItem", "About") } + }}, + { QPlatformMenuItem::PreferencesRole, { + { Qt::MatchStartsWith, QT_TRANSLATE_NOOP("QCocoaMenuItem", "Config") }, + { Qt::MatchStartsWith, QT_TRANSLATE_NOOP("QCocoaMenuItem", "Preference") }, + { Qt::MatchStartsWith, QT_TRANSLATE_NOOP("QCocoaMenuItem", "Options") }, + { Qt::MatchStartsWith, QT_TRANSLATE_NOOP("QCocoaMenuItem", "Setting") }, + { Qt::MatchStartsWith, QT_TRANSLATE_NOOP("QCocoaMenuItem", "Setup") }, + }}, + { QPlatformMenuItem::QuitRole, { + { Qt::MatchStartsWith, QT_TRANSLATE_NOOP("QCocoaMenuItem", "Quit") }, + { Qt::MatchStartsWith, QT_TRANSLATE_NOOP("QCocoaMenuItem", "Exit") }, + }}, + { QPlatformMenuItem::CutRole, { + { Qt::MatchExactly, QT_TRANSLATE_NOOP("QCocoaMenuItem", "Cut") } + }}, + { QPlatformMenuItem::CopyRole, { + { Qt::MatchExactly, QT_TRANSLATE_NOOP("QCocoaMenuItem", "Copy") } + }}, + { QPlatformMenuItem::PasteRole, { + { Qt::MatchExactly, QT_TRANSLATE_NOOP("QCocoaMenuItem", "Paste") } + }}, + { QPlatformMenuItem::SelectAllRole, { + { Qt::MatchExactly, QT_TRANSLATE_NOOP("QCocoaMenuItem", "Select All") } + }}, + }; + + auto match = [](const QString &caption, const QString &itemCaption, Qt::MatchFlags matchFlags) { + if (matchFlags.testFlag(Qt::MatchExactly)) + return !itemCaption.compare(caption, Qt::CaseInsensitive); + if (matchFlags.testFlag(Qt::MatchStartsWith) && itemCaption.startsWith(caption, Qt::CaseInsensitive)) + return true; + if (matchFlags.testFlag(Qt::MatchEndsWith) && itemCaption.endsWith(caption, Qt::CaseInsensitive)) + return true; + return false; + }; + + QPlatformMenuItem::MenuRole detectedRole = [&]{ + for (const auto &[role, captions] : roleMap) { + for (const auto &[matchFlags, caption] : captions) { + // Check for untranslated match + if (match(caption, itemCaption, matchFlags)) + return role; + // Then translated with the current Qt translation + if (match(QCoreApplication::translate("QCocoaMenuItem", caption), itemCaption, matchFlags)) + return role; + } + } + return QPlatformMenuItem::NoRole; + }(); + + if (detectedRole == QPlatformMenuItem::AboutRole) { + static const QRegularExpression qtRegExp("qt$"_L1, QRegularExpression::CaseInsensitiveOption); + if (itemCaption.contains(qtRegExp)) + detectedRole = QPlatformMenuItem::AboutQtRole; } - if (!captionNoAmpersand.compare(QCoreApplication::translate("QCocoaMenuItem", "Cut"), Qt::CaseInsensitive)) - return QPlatformMenuItem::CutRole; - if (!captionNoAmpersand.compare(QCoreApplication::translate("QCocoaMenuItem", "Copy"), Qt::CaseInsensitive)) - return QPlatformMenuItem::CopyRole; - if (!captionNoAmpersand.compare(QCoreApplication::translate("QCocoaMenuItem", "Paste"), Qt::CaseInsensitive)) - return QPlatformMenuItem::PasteRole; - if (!captionNoAmpersand.compare(QCoreApplication::translate("QCocoaMenuItem", "Select All"), Qt::CaseInsensitive)) - return QPlatformMenuItem::SelectAllRole; - return QPlatformMenuItem::NoRole; + + return detectedRole; } NSMenuItem *QCocoaMenuItem::sync() @@ -345,7 +345,7 @@ NSMenuItem *QCocoaMenuItem::sync() // Show multiple key sequences as part of the menu text. if (accel.count() > 1) - text += QLatin1String(" (") + accel.toString(QKeySequence::NativeText) + QLatin1String(")"); + text += " ("_L1 + accel.toString(QKeySequence::NativeText) + ")"_L1; #endif m_native.title = QPlatformTheme::removeMnemonics(text).toNSString(); @@ -416,7 +416,7 @@ QKeySequence QCocoaMenuItem::mergeAccel() void QCocoaMenuItem::syncMerged() { if (!m_merged) { - qWarning("Trying to sync a non-merged item"); + qCWarning(lcQpaMenus) << "Trying to sync non-merged" << this; return; } @@ -473,7 +473,20 @@ void QCocoaMenuItem::resolveTargetAction() roleAction = @selector(selectAll:); break; default: - roleAction = @selector(qt_itemFired:); + if (m_menu) { + // Menu items that represent sub menus should have submenuAction: as their + // action, so that clicking the menu item opens the sub menu without closing + // the entire menu hierarchy. A menu item with this action and a valid submenu + // will disable NSMenuValidation for the item, which is normally not an issue + // as NSMenuItems are enabled by default. But in our case, we haven't attached + // the submenu yet, which results in AppKit concluding that there's no validator + // for the item (the target is nil, and nothing responds to submenuAction:), and + // will in response disable the menu item. To work around this we explicitly + // enable the menu item in QCocoaMenu::setAttachedItem() once we have a submenu. + roleAction = @selector(submenuAction:); + } else { + roleAction = @selector(qt_itemFired:); + } } m_native.action = roleAction; diff --git a/src/plugins/platforms/cocoa/qcocoamenuloader.h b/src/plugins/platforms/cocoa/qcocoamenuloader.h index 797d6b46d2..9c2fc84239 100644 --- a/src/plugins/platforms/cocoa/qcocoamenuloader.h +++ b/src/plugins/platforms/cocoa/qcocoamenuloader.h @@ -1,41 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2018 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) 2018 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 #ifndef QCOCOAMENULOADER_P_H #define QCOCOAMENULOADER_P_H diff --git a/src/plugins/platforms/cocoa/qcocoamenuloader.mm b/src/plugins/platforms/cocoa/qcocoamenuloader.mm index 50072b4e51..b5ee3479aa 100644 --- a/src/plugins/platforms/cocoa/qcocoamenuloader.mm +++ b/src/plugins/platforms/cocoa/qcocoamenuloader.mm @@ -1,41 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2018 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) 2018 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> @@ -92,7 +56,7 @@ NSMenuItem *appItem = [[[NSMenuItem alloc] init] autorelease]; appItem.title = appName; [theMenu addItem:appItem]; - appMenu = [[NSMenu alloc] initWithTitle:appName]; + appMenu = [[QCocoaNSMenu alloc] initWithoutPlatformMenu:appName]; appItem.submenu = appMenu; // About Application @@ -222,8 +186,8 @@ if (appMenu.supermenu) unparentAppMenu(appMenu.supermenu); - NSMenuItem *appMenuItem = [[NSMenuItem alloc] initWithTitle:@"Apple" - action:nil keyEquivalent:@""]; + NSMenuItem *appMenuItem = [[[NSMenuItem alloc] initWithTitle:@"Apple" + action:nil keyEquivalent:@""] autorelease]; appMenuItem.submenu = appMenu; [menu insertItem:appMenuItem atIndex:0]; } diff --git a/src/plugins/platforms/cocoa/qcocoamessagedialog.h b/src/plugins/platforms/cocoa/qcocoamessagedialog.h new file mode 100644 index 0000000000..b8c273469a --- /dev/null +++ b/src/plugins/platforms/cocoa/qcocoamessagedialog.h @@ -0,0 +1,38 @@ +// Copyright (C) 2022 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 + +#ifndef QCOCOAMESSAGEDIALOG_H +#define QCOCOAMESSAGEDIALOG_H + +#include <qpa/qplatformdialoghelper.h> + +Q_FORWARD_DECLARE_OBJC_CLASS(NSAlert); +typedef long NSInteger; +typedef NSInteger NSModalResponse; + +QT_BEGIN_NAMESPACE + +class QEventLoop; + +class QCocoaMessageDialog : public QPlatformMessageDialogHelper +{ +public: + QCocoaMessageDialog() = default; + ~QCocoaMessageDialog(); + + void exec() override; + bool show(Qt::WindowFlags windowFlags, Qt::WindowModality windowModality, QWindow *parent) override; + void hide() override; + +private: + Qt::WindowModality modality() const; + NSAlert *m_alert = nullptr; + QEventLoop *m_eventLoop = nullptr; + NSModalResponse runModal() const; + void processResponse(NSModalResponse response); +}; + +QT_END_NAMESPACE + +#endif // QCOCOAMESSAGEDIALOG_H + diff --git a/src/plugins/platforms/cocoa/qcocoamessagedialog.mm b/src/plugins/platforms/cocoa/qcocoamessagedialog.mm new file mode 100644 index 0000000000..84525099c9 --- /dev/null +++ b/src/plugins/platforms/cocoa/qcocoamessagedialog.mm @@ -0,0 +1,425 @@ +// Copyright (C) 2022 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 "qcocoamessagedialog.h" + +#include "qcocoawindow.h" +#include "qcocoahelpers.h" +#include "qcocoaeventdispatcher.h" + +#include <QtCore/qmetaobject.h> +#include <QtCore/qscopedvaluerollback.h> +#include <QtCore/qtimer.h> + +#include <QtGui/qtextdocument.h> +#include <QtGui/private/qguiapplication_p.h> +#include <QtGui/private/qcoregraphics_p.h> +#include <QtGui/qpa/qplatformtheme.h> + +#include <AppKit/NSAlert.h> +#include <AppKit/NSButton.h> + +QT_USE_NAMESPACE + +using namespace Qt::StringLiterals; + +QT_BEGIN_NAMESPACE + +QCocoaMessageDialog::~QCocoaMessageDialog() +{ + hide(); + [m_alert release]; +} + +static QString toPlainText(const QString &text) +{ + // FIXME: QMessageDialog supports Qt::TextFormat, which + // nowadays includes Qt::MarkdownText, but we don't have + // the machinery to deal with that yet. We should as a + // start plumb the dialog's text format to the platform + // via the dialog options. + + if (!Qt::mightBeRichText(text)) + return text; + + QTextDocument textDocument; + textDocument.setHtml(text); + return textDocument.toPlainText(); +} + +static NSControlStateValue controlStateFor(Qt::CheckState state) +{ + switch (state) { + case Qt::Checked: return NSControlStateValueOn; + case Qt::Unchecked: return NSControlStateValueOff; + case Qt::PartiallyChecked: return NSControlStateValueMixed; + } + Q_UNREACHABLE(); +} + +/* + Called from QDialogPrivate::setNativeDialogVisible() when the message box + is ready to be shown. + + At this point the options() will reflect the specific dialog shown. + + Returns true if the helper could successfully show the dialog, or + false if the cross platform fallback dialog should be used instead. +*/ +bool QCocoaMessageDialog::show(Qt::WindowFlags windowFlags, Qt::WindowModality windowModality, QWindow *parent) +{ + Q_UNUSED(windowFlags); + + qCDebug(lcQpaDialogs) << "Asked to show" << windowModality << "dialog with parent" << parent; + + if (m_alert.window.visible) { + qCDebug(lcQpaDialogs) << "Dialog already visible, ignoring request to show"; + return true; // But we don't want to show the fallback dialog instead + } + + // We can only do application and window modal dialogs + if (windowModality == Qt::NonModal) + return false; + + // And only window modal if we have a parent + if (windowModality == Qt::WindowModal && (!parent || !parent->handle())) { + qCWarning(lcQpaDialogs, "Cannot run window modal dialog without parent window"); + return false; + } + + // And without options we don't know what to show + if (!options()) + return false; + + // NSAlert doesn't have a section for detailed text + if (!options()->detailedText().isEmpty()) { + qCWarning(lcQpaDialogs, "Message box contains detailed text"); + return false; + } + + if (Qt::mightBeRichText(options()->text()) || + Qt::mightBeRichText(options()->informativeText())) { + // Let's fallback to non-native message box, + // we only have plain NSString/text in NSAlert. + qCDebug(lcQpaDialogs, "Message box contains text in rich text format"); + return false; + } + + Q_ASSERT(!m_alert); + m_alert = [NSAlert new]; + m_alert.window.title = options()->windowTitle().toNSString(); + + const QString text = toPlainText(options()->text()); + m_alert.messageText = text.toNSString(); + m_alert.informativeText = toPlainText(options()->informativeText()).toNSString(); + + switch (options()->standardIcon()) { + case QMessageDialogOptions::NoIcon: { + // We only reflect the pixmap icon if the standard icon is unset, + // as setting a standard icon will also set a corresponding pixmap + // icon, which we don't want since it conflicts with the platform. + // If the user has set an explicit pixmap icon however, the standard + // icon will be NoIcon, so we're good. + QPixmap iconPixmap = options()->iconPixmap(); + if (!iconPixmap.isNull()) + m_alert.icon = [NSImage imageFromQImage:iconPixmap.toImage()]; + break; + } + case QMessageDialogOptions::Information: + case QMessageDialogOptions::Question: + [m_alert setAlertStyle:NSAlertStyleInformational]; + break; + case QMessageDialogOptions::Warning: + [m_alert setAlertStyle:NSAlertStyleWarning]; + break; + case QMessageDialogOptions::Critical: + [m_alert setAlertStyle:NSAlertStyleCritical]; + break; + } + + auto defaultButton = options()->defaultButton(); + auto escapeButton = options()->escapeButton(); + + const auto addButton = [&](auto title, auto tag, auto role) { + title = QPlatformTheme::removeMnemonics(title); + NSButton *button = [m_alert addButtonWithTitle:title.toNSString()]; + + // Calling addButtonWithTitle places buttons starting at the right side/top of the alert + // and going toward the left/bottom. By default, the first button has a key equivalent of + // Return, any button with a title of "Cancel" has a key equivalent of Escape, and any button + // with the title "Don't Save" has a key equivalent of Command-D (but only if it's not the first + // button). If an explicit default or escape button has been set, we respect these, + // and otherwise we fall back to role-based default and escape buttons. + + qCDebug(lcQpaDialogs).verbosity(0) << "Adding button" << title << "with" << role; + + if (!defaultButton && role == AcceptRole) + defaultButton = tag; + + if (tag == defaultButton) + button.keyEquivalent = @"\r"; + else if ([button.keyEquivalent isEqualToString:@"\r"]) + button.keyEquivalent = @""; + + if (!escapeButton && role == RejectRole) + escapeButton = tag; + + // Don't override default button with escape button, to match AppKit default + if (tag == escapeButton && ![button.keyEquivalent isEqualToString:@"\r"]) + button.keyEquivalent = @"\e"; + else if ([button.keyEquivalent isEqualToString:@"\e"]) + button.keyEquivalent = @""; + + if (@available(macOS 11, *)) + button.hasDestructiveAction = role == DestructiveRole; + + // The NSModalResponse of showing an NSAlert normally depends on the order of the + // button that was clicked, starting from the right with NSAlertFirstButtonReturn (1000), + // NSAlertSecondButtonReturn (1001), NSAlertThirdButtonReturn (1002), and after that + // NSAlertThirdButtonReturn + n. The response can also be customized per button via its + // tag, which, following the above logic, can include any positive value from 1000 and up. + // In addition the system reserves the values from -1000 and down for its own modal responses, + // such as NSModalResponseStop, NSModalResponseAbort, and NSModalResponseContinue. + // Luckily for us, the QPlatformDialogHelper::StandardButton enum values all fall within + // the positive range, so we can use the standard button value as the tag directly. + // The same applies to the custom button IDs, as these are generated in sequence after + // the QPlatformDialogHelper::LastButton. + Q_ASSERT(tag >= NSAlertFirstButtonReturn); + button.tag = tag; + }; + + // Resolve all dialog buttons from the options, both standard and custom + + struct Button { QString title; int identifier; ButtonRole role; }; + std::vector<Button> buttons; + + const auto *platformTheme = QGuiApplicationPrivate::platformTheme(); + if (auto standardButtons = options()->standardButtons()) { + for (int standardButton = FirstButton; standardButton <= LastButton; standardButton <<= 1) { + if (standardButtons & standardButton) { + auto title = platformTheme->standardButtonText(standardButton); + buttons.push_back({ + title, standardButton, buttonRole(StandardButton(standardButton)) + }); + } + } + } + const auto customButtons = options()->customButtons(); + for (auto customButton : customButtons) + buttons.push_back({customButton.label, customButton.id, customButton.role}); + + // Sort them according to the QPlatformDialogHelper::ButtonLayout for macOS + + // The ButtonLayout adds one additional role, AlternateRole, which is used + // for any AcceptRole beyond the first one, and should be ordered before the + // AcceptRole. Set this up by fixing the roles up front. + bool seenAccept = false; + for (auto &button : buttons) { + if (button.role == AcceptRole) { + if (!seenAccept) + seenAccept = true; + else + button.role = AlternateRole; + } + } + + std::vector<Button> orderedButtons; + const int *layoutEntry = buttonLayout(Qt::Horizontal, ButtonLayout::MacLayout); + while (*layoutEntry != QPlatformDialogHelper::EOL) { + const auto role = ButtonRole(*layoutEntry & ~ButtonRole::Reverse); + const bool reverse = *layoutEntry & ButtonRole::Reverse; + + auto addButton = [&](const Button &button) { + if (button.role == role) + orderedButtons.push_back(button); + }; + + if (reverse) + std::for_each(std::crbegin(buttons), std::crend(buttons), addButton); + else + std::for_each(std::cbegin(buttons), std::cend(buttons), addButton); + + ++layoutEntry; + } + + // Add them to the alert in reverse order, since buttons are added right to left + for (auto button = orderedButtons.crbegin(); button != orderedButtons.crend(); ++button) + addButton(button->title, button->identifier, button->role); + + // If we didn't find a an explicit or implicit default button above + // we restore the AppKit behavior of making the first button default. + if (!defaultButton) + m_alert.buttons.firstObject.keyEquivalent = @"\r"; + + if (auto checkBoxLabel = options()->checkBoxLabel(); !checkBoxLabel.isNull()) { + checkBoxLabel = QPlatformTheme::removeMnemonics(checkBoxLabel); + m_alert.suppressionButton.title = checkBoxLabel.toNSString(); + auto state = options()->checkBoxState(); + m_alert.suppressionButton.allowsMixedState = state == Qt::PartiallyChecked; + m_alert.suppressionButton.state = controlStateFor(state); + m_alert.showsSuppressionButton = YES; + } + + qCDebug(lcQpaDialogs) << "Showing" << m_alert; + + if (windowModality == Qt::WindowModal) { + auto *cocoaWindow = static_cast<QCocoaWindow*>(parent->handle()); + [m_alert beginSheetModalForWindow:cocoaWindow->nativeWindow() + completionHandler:^(NSModalResponse response) { + processResponse(response); + } + ]; + } else { + // The dialog is application modal, so we need to call runModal, + // but we can't call it here as the nativeDialogInUse state of QDialog + // depends on the result of show(), and we can't rely on doing it + // in exec(), as we can't guarantee that the user will call exec() + // after showing the dialog. As a workaround, we call it from exec(), + // but also make sure that if the user returns to the main runloop + // we'll run the modal dialog from there. + QTimer::singleShot(0, this, [this]{ + if (m_alert && !m_alert.window.visible) { + qCDebug(lcQpaDialogs) << "Running deferred modal" << m_alert; + QCocoaEventDispatcher::clearCurrentThreadCocoaEventDispatcherInterruptFlag(); + processResponse(runModal()); + } + }); + } + + return true; +} + +// We shouldn't get NSModalResponseContinue as a response from NSAlert::runModal, +// and processResponse must not be called with that value (if we are there, it's +// too late to do anything about it. +// However, as QTBUG-114546 shows, there are scenarios where we might get that +// response anyway. We interpret it to keep the modal loop running, and we only +// return if we got something else to pass to processResponse. +NSModalResponse QCocoaMessageDialog::runModal() const +{ + NSModalResponse response = NSModalResponseContinue; + while (response == NSModalResponseContinue) + response = [m_alert runModal]; + return response; +} + +void QCocoaMessageDialog::exec() +{ + Q_ASSERT(m_alert); + + if (modality() == Qt::WindowModal) { + qCDebug(lcQpaDialogs) << "Running local event loop for window modal" << m_alert; + QEventLoop eventLoop; + QScopedValueRollback updateGuard(m_eventLoop, &eventLoop); + m_eventLoop->exec(QEventLoop::DialogExec); + } else { + qCDebug(lcQpaDialogs) << "Running modal" << m_alert; + QCocoaEventDispatcher::clearCurrentThreadCocoaEventDispatcherInterruptFlag(); + processResponse(runModal()); + } +} + +// Custom modal response code to record that the dialog was hidden by us +static const NSInteger kModalResponseDialogHidden = NSAlertThirdButtonReturn + 1; + +static Qt::CheckState checkStateFor(NSControlStateValue state) +{ + switch (state) { + case NSControlStateValueOn: return Qt::Checked; + case NSControlStateValueOff: return Qt::Unchecked; + case NSControlStateValueMixed: return Qt::PartiallyChecked; + } + Q_UNREACHABLE(); +} + +void QCocoaMessageDialog::processResponse(NSModalResponse response) +{ + qCDebug(lcQpaDialogs) << "Processing response" << response << "for" << m_alert; + + // We can't re-use the same dialog for the next show() anyways, + // since the options may have changed, so get rid of it now, + // before we emit anything that might recurse back to hide/show/etc. + auto alert = std::exchange(m_alert, nil); + [alert autorelease]; + + if (alert.showsSuppressionButton) + emit checkBoxStateChanged(checkStateFor(alert.suppressionButton.state)); + + if (response >= NSAlertFirstButtonReturn) { + // Safe range for user-defined modal responses + if (response == kModalResponseDialogHidden) { + // Dialog was explicitly hidden by us, so nothing to report + qCDebug(lcQpaDialogs) << "Dialog was hidden; ignoring response"; + } else { + // Dialog buttons + if (response <= StandardButton::LastButton) { + Q_ASSERT(response >= StandardButton::FirstButton); + auto standardButton = StandardButton(response); + emit clicked(standardButton, buttonRole(standardButton)); + } else { + auto *customButton = options()->customButton(response); + Q_ASSERT(customButton); + emit clicked(StandardButton(customButton->id), customButton->role); + } + } + } else { + // We have to consider NSModalResponses beyond the ones specific to + // the alert buttons as the alert may be canceled programmatically. + + switch (response) { + case NSModalResponseContinue: + // Modal session is continuing (returned by runModalSession: only) + Q_UNREACHABLE(); + case NSModalResponseOK: + emit accept(); + break; + case NSModalResponseCancel: + case NSModalResponseStop: // Modal session was broken with stopModal + case NSModalResponseAbort: // Modal session was broken with abortModal + emit reject(); + break; + default: + qCWarning(lcQpaDialogs) << "Unrecognized modal response" << response; + } + } + + if (m_eventLoop) + m_eventLoop->exit(response); +} + +void QCocoaMessageDialog::hide() +{ + if (!m_alert) + return; + + if (m_alert.window.visible) { + qCDebug(lcQpaDialogs) << "Hiding" << modality() << m_alert; + + // Note: Just hiding or closing the NSAlert's NWindow here is not sufficient, + // as the dialog is running a modal event loop as well, which we need to end. + + if (modality() == Qt::WindowModal) { + // Will call processResponse() synchronously + [m_alert.window.sheetParent endSheet:m_alert.window returnCode:kModalResponseDialogHidden]; + } else { + if (NSApp.modalWindow == m_alert.window) { + // Will call processResponse() asynchronously + [NSApp stopModalWithCode:kModalResponseDialogHidden]; + } else { + qCWarning(lcQpaDialogs, "Dialog is not top level modal window. Cannot hide."); + } + } + } else { + qCDebug(lcQpaDialogs) << "No need to hide already hidden" << m_alert; + auto alert = std::exchange(m_alert, nil); + [alert autorelease]; + } +} + +Qt::WindowModality QCocoaMessageDialog::modality() const +{ + Q_ASSERT(m_alert && m_alert.window); + return m_alert.window.sheetParent ? Qt::WindowModal : Qt::ApplicationModal; +} + +QT_END_NAMESPACE diff --git a/src/plugins/platforms/cocoa/qcocoamimetypes.h b/src/plugins/platforms/cocoa/qcocoamimetypes.h index a00e25bc93..fd075d35bd 100644 --- a/src/plugins/platforms/cocoa/qcocoamimetypes.h +++ b/src/plugins/platforms/cocoa/qcocoamimetypes.h @@ -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 #ifndef QCOCOAMIMETYPES_H #define QCOCOAMIMETYPES_H diff --git a/src/plugins/platforms/cocoa/qcocoamimetypes.mm b/src/plugins/platforms/cocoa/qcocoamimetypes.mm index 82ff8fe002..52e5c64538 100644 --- a/src/plugins/platforms/cocoa/qcocoamimetypes.mm +++ b/src/plugins/platforms/cocoa/qcocoamimetypes.mm @@ -1,94 +1,51 @@ -/**************************************************************************** -** -** 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 "qcocoamimetypes.h" -#include <QtGui/private/qmacmime_p.h> +#include <QtGui/qutimimeconverter.h> #include "qcocoahelpers.h" #include <QtGui/private/qcoregraphics_p.h> QT_BEGIN_NAMESPACE -class QMacPasteboardMimeTraditionalMacPlainText : public QMacInternalPasteboardMime { -public: - QMacPasteboardMimeTraditionalMacPlainText() : QMacInternalPasteboardMime(MIME_ALL) { } - QString convertorName(); +using namespace Qt::StringLiterals; - QString flavorFor(const QString &mime); - QString mimeFor(QString flav); - bool canConvert(const QString &mime, QString flav); - QVariant convertToMime(const QString &mime, QList<QByteArray> data, QString flav); - QList<QByteArray> convertFromMime(const QString &mime, QVariant data, QString flav); +class QMacMimeTraditionalMacPlainText : public QUtiMimeConverter { +public: + QString utiForMime(const QString &mime) const override; + QString mimeForUti(const QString &uti) const override; + QVariant convertToMime(const QString &mime, const QList<QByteArray> &data, + const QString &uti) const override; + QList<QByteArray> convertFromMime(const QString &mime, const QVariant &data, + const QString &uti) const override; }; -QString QMacPasteboardMimeTraditionalMacPlainText::convertorName() -{ - return QLatin1String("PlainText (traditional-mac-plain-text)"); -} - -QString QMacPasteboardMimeTraditionalMacPlainText::flavorFor(const QString &mime) +QString QMacMimeTraditionalMacPlainText::utiForMime(const QString &mime) const { - if (mime == QLatin1String("text/plain")) - return QLatin1String("com.apple.traditional-mac-plain-text"); + if (mime == "text/plain"_L1) + return "com.apple.traditional-mac-plain-text"_L1; return QString(); } -QString QMacPasteboardMimeTraditionalMacPlainText::mimeFor(QString flav) +QString QMacMimeTraditionalMacPlainText::mimeForUti(const QString &uti) const { - if (flav == QLatin1String("com.apple.traditional-mac-plain-text")) - return QLatin1String("text/plain"); + if (uti == "com.apple.traditional-mac-plain-text"_L1) + return "text/plain"_L1; return QString(); } -bool QMacPasteboardMimeTraditionalMacPlainText::canConvert(const QString &mime, QString flav) -{ - return flavorFor(mime) == flav; -} - -QVariant QMacPasteboardMimeTraditionalMacPlainText::convertToMime(const QString &mimetype, QList<QByteArray> data, QString flavor) +QVariant +QMacMimeTraditionalMacPlainText::convertToMime(const QString &mimetype, + const QList<QByteArray> &data, + const QString &uti) const { if (data.count() > 1) - qWarning("QMacPasteboardMimeTraditionalMacPlainText: Cannot handle multiple member data"); + qWarning("QMacMimeTraditionalMacPlainText: Cannot handle multiple member data"); const QByteArray &firstData = data.first(); QVariant ret; - if (flavor == QLatin1String("com.apple.traditional-mac-plain-text")) { + if (uti == "com.apple.traditional-mac-plain-text"_L1) { return QString(QCFString(CFStringCreateWithBytes(kCFAllocatorDefault, reinterpret_cast<const UInt8 *>(firstData.constData()), firstData.size(), CFStringGetSystemEncoding(), false))); @@ -98,18 +55,21 @@ QVariant QMacPasteboardMimeTraditionalMacPlainText::convertToMime(const QString return ret; } -QList<QByteArray> QMacPasteboardMimeTraditionalMacPlainText::convertFromMime(const QString &, QVariant data, QString flavor) +QList<QByteArray> +QMacMimeTraditionalMacPlainText::convertFromMime(const QString &, + const QVariant &data, + const QString &uti) const { QList<QByteArray> ret; QString string = data.toString(); - if (flavor == QLatin1String("com.apple.traditional-mac-plain-text")) + if (uti == "com.apple.traditional-mac-plain-text"_L1) ret.append(string.toLatin1()); return ret; } void QCocoaMimeTypes::initializeMimeTypes() { - new QMacPasteboardMimeTraditionalMacPlainText; + new QMacMimeTraditionalMacPlainText; } QT_END_NAMESPACE diff --git a/src/plugins/platforms/cocoa/qcocoanativeinterface.h b/src/plugins/platforms/cocoa/qcocoanativeinterface.h index e69bafce7b..a406cae366 100644 --- a/src/plugins/platforms/cocoa/qcocoanativeinterface.h +++ b/src/plugins/platforms/cocoa/qcocoanativeinterface.h @@ -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 #ifndef QCOCOANATIVEINTERFACE_H #define QCOCOANATIVEINTERFACE_H @@ -67,12 +31,6 @@ public Q_SLOTS: void onAppFocusWindowChanged(QWindow *window); private: - /* - Function to return the default background pixmap. - Needed by QWizard in the Qt widget module. - */ - Q_INVOKABLE QPixmap defaultBackgroundPixmapForQWizard(); - Q_INVOKABLE void clearCurrentThreadCocoaEventDispatcherInterruptFlag(); static void registerDraggedTypes(const QStringList &types); @@ -89,9 +47,6 @@ private: // deregisters. static void registerTouchWindow(QWindow *window, bool enable); - // Set the size of the unified title and toolbar area. - static void setContentBorderThickness(QWindow *window, int topThickness, int bottomThickness); - // Set the size for a unified toolbar content border area. // Multiple callers can register areas and the platform plugin // will extend the "unified" area to cover them. @@ -103,12 +58,6 @@ private: // Returns true if the given coordinate is inside the current // content border. static bool testContentBorderPosition(QWindow *window, int position); - - // Sets a NSToolbar instance for the given QWindow. The - // toolbar will be attached to the native NSWindow when - // that is created; - static void setNSToolbar(QWindow *window, void *nsToolbar); - }; QT_END_NAMESPACE diff --git a/src/plugins/platforms/cocoa/qcocoanativeinterface.mm b/src/plugins/platforms/cocoa/qcocoanativeinterface.mm index 7dc5e9099d..58bda2706a 100644 --- a/src/plugins/platforms/cocoa/qcocoanativeinterface.mm +++ b/src/plugins/platforms/cocoa/qcocoanativeinterface.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> @@ -62,6 +26,7 @@ #include <QtGui/qguiapplication.h> #include <qdebug.h> +#include <QtGui/private/qmacmimeregistry_p.h> #include <QtGui/private/qcoregraphics_p.h> #if QT_CONFIG(vulkan) @@ -100,49 +65,16 @@ QPlatformNativeInterface::NativeResourceForIntegrationFunction QCocoaNativeInter return NativeResourceForIntegrationFunction(QCocoaNativeInterface::registerTouchWindow); if (resource.toLower() == "setembeddedinforeignview") return NativeResourceForIntegrationFunction(QCocoaNativeInterface::setEmbeddedInForeignView); - if (resource.toLower() == "setcontentborderthickness") - return NativeResourceForIntegrationFunction(QCocoaNativeInterface::setContentBorderThickness); if (resource.toLower() == "registercontentborderarea") return NativeResourceForIntegrationFunction(QCocoaNativeInterface::registerContentBorderArea); if (resource.toLower() == "setcontentborderareaenabled") return NativeResourceForIntegrationFunction(QCocoaNativeInterface::setContentBorderAreaEnabled); - if (resource.toLower() == "setnstoolbar") - return NativeResourceForIntegrationFunction(QCocoaNativeInterface::setNSToolbar); if (resource.toLower() == "testcontentborderposition") return NativeResourceForIntegrationFunction(QCocoaNativeInterface::testContentBorderPosition); return nullptr; } -QPixmap QCocoaNativeInterface::defaultBackgroundPixmapForQWizard() -{ - // Note: starting with macOS 10.14, the KeyboardSetupAssistant app bundle no - // longer contains the "Background.png" image. This function then returns a - // null pixmap. - const int ExpectedImageWidth = 242; - const int ExpectedImageHeight = 414; - QCFType<CFArrayRef> urls = LSCopyApplicationURLsForBundleIdentifier( - CFSTR("com.apple.KeyboardSetupAssistant"), nullptr); - if (urls && CFArrayGetCount(urls) > 0) { - CFURLRef url = (CFURLRef)CFArrayGetValueAtIndex(urls, 0); - QCFType<CFBundleRef> bundle = CFBundleCreate(kCFAllocatorDefault, url); - if (bundle) { - url = CFBundleCopyResourceURL(bundle, CFSTR("Background"), CFSTR("png"), nullptr); - if (url) { - QCFType<CGImageSourceRef> imageSource = CGImageSourceCreateWithURL(url, nullptr); - QCFType<CGImageRef> image = CGImageSourceCreateImageAtIndex(imageSource, 0, nullptr); - if (image) { - int width = CGImageGetWidth(image); - int height = CGImageGetHeight(image); - if (width == ExpectedImageWidth && height == ExpectedImageHeight) - return QPixmap::fromImage(qt_mac_toQImage(image)); - } - } - } - } - return QPixmap(); -} - void QCocoaNativeInterface::clearCurrentThreadCocoaEventDispatcherInterruptFlag() { QCocoaEventDispatcher::clearCurrentThreadCocoaEventDispatcherInterruptFlag(); @@ -156,7 +88,7 @@ void QCocoaNativeInterface::onAppFocusWindowChanged(QWindow *window) void QCocoaNativeInterface::registerDraggedTypes(const QStringList &types) { - qt_mac_registerDraggedTypes(types); + QMacMimeRegistry::registerDraggedTypes(types); } void QCocoaNativeInterface::setEmbeddedInForeignView(QPlatformWindow *window, bool embedded) @@ -176,16 +108,6 @@ void QCocoaNativeInterface::registerTouchWindow(QWindow *window, bool enable) cocoaWindow->registerTouch(enable); } -void QCocoaNativeInterface::setContentBorderThickness(QWindow *window, int topThickness, int bottomThickness) -{ - if (!window) - return; - - QCocoaWindow *cocoaWindow = static_cast<QCocoaWindow *>(window->handle()); - if (cocoaWindow) - cocoaWindow->setContentBorderThickness(topThickness, bottomThickness); -} - void QCocoaNativeInterface::registerContentBorderArea(QWindow *window, quintptr identifier, int upper, int lower) { if (!window) @@ -206,15 +128,6 @@ void QCocoaNativeInterface::setContentBorderAreaEnabled(QWindow *window, quintpt cocoaWindow->setContentBorderAreaEnabled(identifier, enable); } -void QCocoaNativeInterface::setNSToolbar(QWindow *window, void *nsToolbar) -{ - QCocoaIntegration::instance()->setToolbar(window, static_cast<NSToolbar *>(nsToolbar)); - - QCocoaWindow *cocoaWindow = static_cast<QCocoaWindow *>(window->handle()); - if (cocoaWindow) - cocoaWindow->updateNSToolbar(); -} - bool QCocoaNativeInterface::testContentBorderPosition(QWindow *window, int position) { if (!window) diff --git a/src/plugins/platforms/cocoa/qcocoansmenu.h b/src/plugins/platforms/cocoa/qcocoansmenu.h index 0ea7d1331d..533aba1a21 100644 --- a/src/plugins/platforms/cocoa/qcocoansmenu.h +++ b/src/plugins/platforms/cocoa/qcocoansmenu.h @@ -1,41 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2018 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) 2018 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 #ifndef QCOCOANSMENU_H #define QCOCOANSMENU_H @@ -64,6 +28,7 @@ QT_DECLARE_NAMESPACED_OBJC_INTERFACE(QCocoaNSMenuDelegate, NSObject <NSMenuDeleg QT_DECLARE_NAMESPACED_OBJC_INTERFACE(QCocoaNSMenu, NSMenu @property (readonly, nonatomic) QCocoaMenu *platformMenu; - (instancetype)initWithPlatformMenu:(QCocoaMenu *)menu; +- (instancetype)initWithoutPlatformMenu:(NSString *)menu; ) QT_DECLARE_NAMESPACED_OBJC_INTERFACE(QCocoaNSMenuItem, NSMenuItem diff --git a/src/plugins/platforms/cocoa/qcocoansmenu.mm b/src/plugins/platforms/cocoa/qcocoansmenu.mm index 6760ae59ff..ba222a3ef4 100644 --- a/src/plugins/platforms/cocoa/qcocoansmenu.mm +++ b/src/plugins/platforms/cocoa/qcocoansmenu.mm @@ -1,41 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2018 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) 2018 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> @@ -49,8 +13,11 @@ #include <QtCore/qcoreapplication.h> #include <QtCore/qcoreevent.h> +#include <QtCore/qvarlengtharray.h> #include <QtGui/private/qapplekeymapper_p.h> +#include <QtCore/qpointer.h> + static NSString *qt_mac_removePrivateUnicode(NSString *string) { if (const int len = string.length) { @@ -87,6 +54,13 @@ static NSString *qt_mac_removePrivateUnicode(NSString *string) return self; } +- (instancetype)initWithoutPlatformMenu:(NSString *)title +{ + if (self = [super initWithTitle:title]) + self.delegate = [QCocoaNSMenuDelegate sharedMenuDelegate]; + return self; +} + - (QCocoaMenu *)platformMenu { return _platformMenu.data(); diff --git a/src/plugins/platforms/cocoa/qcocoascreen.h b/src/plugins/platforms/cocoa/qcocoascreen.h index 91c1e3c63c..7708dc1968 100644 --- a/src/plugins/platforms/cocoa/qcocoascreen.h +++ b/src/plugins/platforms/cocoa/qcocoascreen.h @@ -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 #ifndef QCOCOASCREEN_H #define QCOCOASCREEN_H @@ -70,21 +34,21 @@ public: QColorSpace colorSpace() const override { return m_colorSpace; } qreal devicePixelRatio() const override { return m_devicePixelRatio; } QSizeF physicalSize() const override { return m_physicalSize; } - QDpi logicalDpi() const override { return m_logicalDpi; } - QDpi logicalBaseDpi() const override { return m_logicalDpi; } + QDpi logicalBaseDpi() const override { return QDpi(72, 72); } qreal refreshRate() const override { return m_refreshRate; } QString name() const override { return m_name; } QPlatformCursor *cursor() const override { return m_cursor; } QWindow *topLevelAt(const QPoint &point) const override; QList<QPlatformScreen *> virtualSiblings() const override; QPlatformScreen::SubpixelAntialiasingType subpixelAntialiasingTypeHint() const override; + Qt::ScreenOrientation orientation() const override; // ---------------------------------------------------- static NSScreen *nativeScreenForDisplayId(CGDirectDisplayID displayId); NSScreen *nativeScreen() const; - void requestUpdate(); + bool requestUpdate(); void deliverUpdateRequests(); bool isRunningDisplayLink() const; @@ -119,7 +83,6 @@ private: QRect m_geometry; QRect m_availableGeometry; - QDpi m_logicalDpi; qreal m_refreshRate = 0; int m_depth = 0; QString m_name; @@ -128,6 +91,7 @@ private: QSizeF m_physicalSize; QCocoaCursor *m_cursor; qreal m_devicePixelRatio = 0; + qreal m_rotation = 0; CVDisplayLinkRef m_displayLink = nullptr; dispatch_source_t m_displayLinkSource = nullptr; @@ -145,6 +109,10 @@ QDebug operator<<(QDebug debug, const QCocoaScreen *screen); QT_END_NAMESPACE #if defined(__OBJC__) + +// @compatibility_alias doesn't work with categories or their methods +#define qt_displayId QT_MANGLE_NAMESPACE(qt_displayId) + @interface NSScreen (QtExtras) @property(readonly) CGDirectDisplayID qt_displayId; @end diff --git a/src/plugins/platforms/cocoa/qcocoascreen.mm b/src/plugins/platforms/cocoa/qcocoascreen.mm index 82fd1c44df..be562e5455 100644 --- a/src/plugins/platforms/cocoa/qcocoascreen.mm +++ b/src/plugins/platforms/cocoa/qcocoascreen.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 #include <AppKit/AppKit.h> @@ -51,7 +15,9 @@ #include <IOKit/graphics/IOGraphicsLib.h> #include <QtGui/private/qwindow_p.h> +#include <QtGui/private/qhighdpiscaling_p.h> +#include <QtCore/private/qcore_mac_p.h> #include <QtCore/private/qeventdispatcher_cf_p.h> QT_BEGIN_NAMESPACE @@ -108,6 +74,18 @@ void QCocoaScreen::initializeScreens() */ void QCocoaScreen::updateScreens() { + // Adding, updating, or removing a screen below might trigger + // Qt or the application to move a window to a different screen, + // recursing back here via QCocoaWindow::windowDidChangeScreen. + // The update code is not re-entrant, so bail out if we end up + // in this situation. The screens will stabilize eventually. + static bool updatingScreens = false; + if (updatingScreens) { + qCInfo(lcQpaScreen) << "Skipping screen update, already updating"; + return; + } + QBoolBlocker recursionGuard(updatingScreens); + uint32_t displayCount = 0; if (CGGetOnlineDisplayList(0, nullptr, &displayCount) != kCGErrorSuccess) qFatal("Failed to get number of online displays"); @@ -222,7 +200,7 @@ QCocoaScreen::~QCocoaScreen() static QString displayName(CGDirectDisplayID displayID) { QIOType<io_iterator_t> iterator; - if (IOServiceGetMatchingServices(kIOMasterPortDefault, + if (IOServiceGetMatchingServices(kIOMainPortDefault, IOServiceMatching("IODisplayConnect"), &iterator)) return QString(); @@ -232,13 +210,13 @@ static QString displayName(CGDirectDisplayID displayID) NSDictionary *info = [(__bridge NSDictionary*)IODisplayCreateInfoDictionary( display, kIODisplayOnlyPreferredName) autorelease]; - if ([[info objectForKey:@kDisplayVendorID] longValue] != CGDisplayVendorNumber(displayID)) + if ([[info objectForKey:@kDisplayVendorID] unsignedIntValue] != CGDisplayVendorNumber(displayID)) continue; - if ([[info objectForKey:@kDisplayProductID] longValue] != CGDisplayModelNumber(displayID)) + if ([[info objectForKey:@kDisplayProductID] unsignedIntValue] != CGDisplayModelNumber(displayID)) continue; - if ([[info objectForKey:@kDisplaySerialNumber] longValue] != CGDisplaySerialNumber(displayID)) + if ([[info objectForKey:@kDisplaySerialNumber] unsignedIntValue] != CGDisplaySerialNumber(displayID)) continue; NSDictionary *localizedNames = [info objectForKey:@kDisplayProductName]; @@ -269,8 +247,8 @@ void QCocoaScreen::update(CGDirectDisplayID displayId) const QRect previousGeometry = m_geometry; const QRect previousAvailableGeometry = m_availableGeometry; - const QDpi previousLogicalDpi = m_logicalDpi; const qreal previousRefreshRate = m_refreshRate; + const double previousRotation = m_rotation; // The reference screen for the geometry is always the primary screen QRectF primaryScreenGeometry = QRectF::fromCGRect(CGDisplayBounds(CGMainDisplayID())); @@ -283,27 +261,32 @@ void QCocoaScreen::update(CGDirectDisplayID displayId) m_depth = NSBitsPerPixelFromDepth(nsScreen.depth); m_colorSpace = QColorSpace::fromIccProfile(QByteArray::fromNSData(nsScreen.colorSpace.ICCProfileData)); if (!m_colorSpace.isValid()) { - qWarning() << "macOS generated a color-profile Qt couldn't parse. This shouldn't happen."; + qCWarning(lcQpaScreen) << "Failed to parse ICC profile for" << nsScreen.colorSpace + << "with ICC data" << nsScreen.colorSpace.ICCProfileData + << "- Falling back to sRGB"; m_colorSpace = QColorSpace::SRgb; } CGSize size = CGDisplayScreenSize(m_displayId); m_physicalSize = QSizeF(size.width, size.height); - m_logicalDpi.first = 72; - m_logicalDpi.second = 72; QCFType<CGDisplayModeRef> displayMode = CGDisplayCopyDisplayMode(m_displayId); float refresh = CGDisplayModeGetRefreshRate(displayMode); m_refreshRate = refresh > 0 ? refresh : 60.0; + m_rotation = CGDisplayRotation(displayId); - m_name = displayName(m_displayId); + if (@available(macOS 10.15, *)) + m_name = QString::fromNSString(nsScreen.localizedName); + else + m_name = displayName(m_displayId); const bool didChangeGeometry = m_geometry != previousGeometry || m_availableGeometry != previousAvailableGeometry; + if (m_rotation != previousRotation) + QWindowSystemInterface::handleScreenOrientationChange(screen(), orientation()); + if (didChangeGeometry) QWindowSystemInterface::handleScreenGeometryChange(screen(), geometry(), availableGeometry()); - if (m_logicalDpi != previousLogicalDpi) - QWindowSystemInterface::handleScreenLogicalDotsPerInchChange(screen(), m_logicalDpi.first, m_logicalDpi.second); if (m_refreshRate != previousRefreshRate) QWindowSystemInterface::handleScreenRefreshRateChange(screen(), m_refreshRate); } @@ -312,19 +295,33 @@ void QCocoaScreen::update(CGDirectDisplayID displayId) Q_LOGGING_CATEGORY(lcQpaScreenUpdates, "qt.qpa.screen.updates", QtCriticalMsg); -void QCocoaScreen::requestUpdate() +bool QCocoaScreen::requestUpdate() { Q_ASSERT(m_displayId); + if (!isOnline()) { + qCDebug(lcQpaScreenUpdates) << this << "is not online. Ignoring update request"; + return false; + } + if (!m_displayLink) { - CVDisplayLinkCreateWithCGDisplay(m_displayId, &m_displayLink); + qCDebug(lcQpaScreenUpdates) << "Creating display link for" << this; + if (CVDisplayLinkCreateWithCGDisplay(m_displayId, &m_displayLink) != kCVReturnSuccess) { + qCWarning(lcQpaScreenUpdates) << "Failed to create display link for" << this; + return false; + } + if (auto displayId = CVDisplayLinkGetCurrentCGDisplay(m_displayLink); displayId != m_displayId) { + qCWarning(lcQpaScreenUpdates) << "Unexpected display" << displayId << "for display link"; + CVDisplayLinkRelease(m_displayLink); + m_displayLink = nullptr; + return false; + } CVDisplayLinkSetOutputCallback(m_displayLink, [](CVDisplayLinkRef, const CVTimeStamp*, const CVTimeStamp*, CVOptionFlags, CVOptionFlags*, void* displayLinkContext) -> int { // FIXME: It would be nice if update requests would include timing info static_cast<QCocoaScreen*>(displayLinkContext)->deliverUpdateRequests(); return kCVReturnSuccess; }, this); - qCDebug(lcQpaScreenUpdates) << "Display link created for" << this; // During live window resizing -[NSWindow _resizeWithEvent:] will spin a local event loop // in event-tracking mode, dequeuing only the mouse drag events needed to update the window's @@ -379,6 +376,8 @@ void QCocoaScreen::requestUpdate() qCDebug(lcQpaScreenUpdates) << "Starting display link for" << this; CVDisplayLinkStart(m_displayLink); } + + return true; } // Helper to allow building up debug output in multiple steps @@ -472,6 +471,25 @@ void QCocoaScreen::deliverUpdateRequests() if (!platformWindow->updatesWithDisplayLink()) continue; + // QTBUG-107198: Skip updates in a live resize for a better resize experience. + if (platformWindow->isContentView() && platformWindow->view().inLiveResize) { + const QSurface::SurfaceType surfaceType = window->surfaceType(); + const bool usesMetalLayer = surfaceType == QWindow::MetalSurface || surfaceType == QWindow::VulkanSurface; + const bool usesNonDefaultContentsPlacement = [platformWindow->view() layerContentsPlacement] + != NSViewLayerContentsPlacementScaleAxesIndependently; + if (usesMetalLayer && usesNonDefaultContentsPlacement) { + static bool deliverDisplayLinkUpdatesDuringLiveResize = + qEnvironmentVariableIsSet("QT_MAC_DISPLAY_LINK_UPDATE_IN_RESIZE"); + if (!deliverDisplayLinkUpdatesDuringLiveResize) { + // Must keep the link running, we do not know what the event + // handlers for UpdateRequest (which is not sent now) would do, + // would they trigger a new requestUpdate() or not. + pauseUpdates = false; + continue; + } + } + } + platformWindow->deliverUpdateRequest(); // Another update request was triggered, keep the display link running @@ -509,41 +527,56 @@ QPlatformScreen::SubpixelAntialiasingType QCocoaScreen::subpixelAntialiasingType return type; } +Qt::ScreenOrientation QCocoaScreen::orientation() const +{ + if (m_rotation == 0) + return Qt::LandscapeOrientation; + if (m_rotation == 90) + return Qt::PortraitOrientation; + if (m_rotation == 180) + return Qt::InvertedLandscapeOrientation; + if (m_rotation == 270) + return Qt::InvertedPortraitOrientation; + return QPlatformScreen::orientation(); +} + QWindow *QCocoaScreen::topLevelAt(const QPoint &point) const { - NSPoint screenPoint = mapToNative(point); - - // Search (hit test) for the top-level window. [NSWidow windowNumberAtPoint: - // belowWindowWithWindowNumber] may return windows that are not interesting - // to Qt. The search iterates until a suitable window or no window is found. - NSInteger topWindowNumber = 0; - QWindow *window = nullptr; - do { - // Get the top-most window, below any previously rejected window. - topWindowNumber = [NSWindow windowNumberAtPoint:screenPoint - belowWindowWithWindowNumber:topWindowNumber]; - - // Continue the search if the window does not belong to this process. - NSWindow *nsWindow = [NSApp windowWithWindowNumber:topWindowNumber]; - if (!nsWindow) - continue; + __block QWindow *window = nullptr; + [NSApp enumerateWindowsWithOptions:NSWindowListOrderedFrontToBack + usingBlock:^(NSWindow *nsWindow, BOOL *stop) { + if (!nsWindow) + return; - // Continue the search if the window does not belong to Qt. - if (![nsWindow conformsToProtocol:@protocol(QNSWindowProtocol)]) - continue; + // Continue the search if the window does not belong to Qt + if (![nsWindow conformsToProtocol:@protocol(QNSWindowProtocol)]) + return; - QCocoaWindow *cocoaWindow = qnsview_cast(nsWindow.contentView).platformWindow; - if (!cocoaWindow) - continue; - window = cocoaWindow->window(); + QCocoaWindow *cocoaWindow = qnsview_cast(nsWindow.contentView).platformWindow; + if (!cocoaWindow) + return; + + QWindow *w = cocoaWindow->window(); + if (!w->isVisible()) + return; + + auto nativeGeometry = QHighDpi::toNativePixels(w->geometry(), w); + if (!nativeGeometry.contains(point)) + return; - // Continue the search if the window is not a top-level window. - if (!window->isTopLevel()) - continue; + QRegion mask = QHighDpi::toNativeLocalPosition(w->mask(), w); + if (!mask.isEmpty() && !mask.contains(point - nativeGeometry.topLeft())) + return; - // Stop searching. The current window is the correct window. - break; - } while (topWindowNumber > 0); + window = w; + + // Continue the search if the window is not a top-level window + if (!window->isTopLevel()) + return; + + *stop = true; + } + ]; return window; } @@ -812,10 +845,10 @@ QDebug operator<<(QDebug debug, const QCocoaScreen *screen) } #endif // !QT_NO_DEBUG_STREAM -#include "qcocoascreen.moc" - QT_END_NAMESPACE +#include "qcocoascreen.moc" + @implementation NSScreen (QtExtras) - (CGDirectDisplayID)qt_displayId diff --git a/src/plugins/platforms/cocoa/qcocoaservices.h b/src/plugins/platforms/cocoa/qcocoaservices.h index 62a8891f96..b6299570e8 100644 --- a/src/plugins/platforms/cocoa/qcocoaservices.h +++ b/src/plugins/platforms/cocoa/qcocoaservices.h @@ -1,45 +1,11 @@ -/**************************************************************************** -** -** 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 #ifndef QCOCOADESKTOPSERVICES_H #define QCOCOADESKTOPSERVICES_H +#include <QtCore/qurl.h> + #include <qpa/qplatformservices.h> QT_BEGIN_NAMESPACE @@ -47,8 +13,16 @@ QT_BEGIN_NAMESPACE class QCocoaServices : public QPlatformServices { public: + bool hasCapability(Capability capability) const override; + bool openUrl(const QUrl &url) override; bool openDocument(const QUrl &url) override; + bool handleUrl(const QUrl &url); + + QPlatformServiceColorPicker *colorPicker(QWindow *parent) override; + +private: + QUrl m_handlingUrl; }; QT_END_NAMESPACE diff --git a/src/plugins/platforms/cocoa/qcocoaservices.mm b/src/plugins/platforms/cocoa/qcocoaservices.mm index 1d05db5cdc..87212c265c 100644 --- a/src/plugins/platforms/cocoa/qcocoaservices.mm +++ b/src/plugins/platforms/cocoa/qcocoaservices.mm @@ -1,65 +1,73 @@ -/**************************************************************************** -** -** 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 "qcocoaservices.h" #include <AppKit/NSWorkspace.h> +#include <AppKit/NSColorSampler.h> #include <Foundation/NSURL.h> #include <QtCore/QUrl> +#include <QtCore/qscopedvaluerollback.h> + +#include <QtGui/qdesktopservices.h> +#include <QtGui/private/qcoregraphics_p.h> QT_BEGIN_NAMESPACE bool QCocoaServices::openUrl(const QUrl &url) { - const QString scheme = url.scheme(); - if (scheme.isEmpty()) - return openDocument(url); + // avoid recursing back into self + if (url == m_handlingUrl) + return false; + return [[NSWorkspace sharedWorkspace] openURL:url.toNSURL()]; } bool QCocoaServices::openDocument(const QUrl &url) { - if (!url.isValid()) - return false; + return openUrl(url); +} + +/* Callback from macOS that the application should handle a URL */ +bool QCocoaServices::handleUrl(const QUrl &url) +{ + QScopedValueRollback<QUrl> rollback(m_handlingUrl, url); + // FIXME: Add platform services callback from QDesktopServices::setUrlHandler + // so that we can warn the user if calling setUrlHandler without also setting + // up the matching keys in the Info.plist file (CFBundleURLTypes and friends). + return QDesktopServices::openUrl(url); +} - return [[NSWorkspace sharedWorkspace] openFile:url.toLocalFile().toNSString()]; +class QCocoaColorPicker : public QPlatformServiceColorPicker +{ +public: + QCocoaColorPicker() : m_colorSampler([NSColorSampler new]) {} + ~QCocoaColorPicker() { [m_colorSampler release]; } + + void pickColor() override + { + [m_colorSampler showSamplerWithSelectionHandler:^(NSColor *selectedColor) { + emit colorPicked(qt_mac_toQColor(selectedColor)); + }]; + } +private: + NSColorSampler *m_colorSampler = nullptr; +}; + + +QPlatformServiceColorPicker *QCocoaServices::colorPicker(QWindow *parent) +{ + Q_UNUSED(parent); + return new QCocoaColorPicker; +} + +bool QCocoaServices::hasCapability(Capability capability) const +{ + switch (capability) { + case ColorPicking: return true; + default: return false; + } } QT_END_NAMESPACE diff --git a/src/plugins/platforms/cocoa/qcocoasessionmanager.cpp b/src/plugins/platforms/cocoa/qcocoasessionmanager.cpp index 725fc5acc0..1f44c74aa9 100644 --- a/src/plugins/platforms/cocoa/qcocoasessionmanager.cpp +++ b/src/plugins/platforms/cocoa/qcocoasessionmanager.cpp @@ -1,41 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2019 Samuel Gaist <samuel.gaist@idiap.ch> -** 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) 2019 Samuel Gaist <samuel.gaist@idiap.ch> +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only #ifndef QT_NO_SESSIONMANAGER #include <private/qsessionmanager_p.h> diff --git a/src/plugins/platforms/cocoa/qcocoasessionmanager.h b/src/plugins/platforms/cocoa/qcocoasessionmanager.h index 89ab7bd157..8b6021c2b5 100644 --- a/src/plugins/platforms/cocoa/qcocoasessionmanager.h +++ b/src/plugins/platforms/cocoa/qcocoasessionmanager.h @@ -1,41 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2019 Samuel Gaist <samuel.gaist@idiap.ch> -** 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) 2019 Samuel Gaist <samuel.gaist@idiap.ch> +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only #ifndef QCOCOASESSIONMANAGER_H #define QCOCOASESSIONMANAGER_H diff --git a/src/plugins/platforms/cocoa/qcocoasystemtrayicon.h b/src/plugins/platforms/cocoa/qcocoasystemtrayicon.h index ddc76f6b72..75c33cc5a3 100644 --- a/src/plugins/platforms/cocoa/qcocoasystemtrayicon.h +++ b/src/plugins/platforms/cocoa/qcocoasystemtrayicon.h @@ -1,42 +1,6 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Copyright (C) 2012 Klaralvdalens Datakonsult AB, a KDAB Group company, info@kdab.com, author Christoph Schleifenbaum <christoph.schleifenbaum@kdab.com> -** 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. +// Copyright (C) 2012 Klaralvdalens Datakonsult AB, a KDAB Group company, info@kdab.com, author Christoph Schleifenbaum <christoph.schleifenbaum@kdab.com> +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only #ifndef QCOCOASYSTEMTRAYICON_P_H #define QCOCOASYSTEMTRAYICON_P_H @@ -81,12 +45,11 @@ public: bool isSystemTrayAvailable() const override; bool supportsMessages() const override; - void statusItemClicked(); + void emitActivated(); private: NSStatusItem *m_statusItem = nullptr; QStatusItemDelegate *m_delegate = nullptr; - QCocoaMenu *m_menu = nullptr; }; QT_END_NAMESPACE diff --git a/src/plugins/platforms/cocoa/qcocoasystemtrayicon.mm b/src/plugins/platforms/cocoa/qcocoasystemtrayicon.mm index 962d1e34b3..cec8301cf6 100644 --- a/src/plugins/platforms/cocoa/qcocoasystemtrayicon.mm +++ b/src/plugins/platforms/cocoa/qcocoasystemtrayicon.mm @@ -1,42 +1,6 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Copyright (C) 2012 Klaralvdalens Datakonsult AB, a KDAB Group company, info@kdab.com, author Christoph Schleifenbaum <christoph.schleifenbaum@kdab.com> -** 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. +// Copyright (C) 2012 Klaralvdalens Datakonsult AB, a KDAB Group company, info@kdab.com, author Christoph Schleifenbaum <christoph.schleifenbaum@kdab.com> +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only /**************************************************************************** ** @@ -92,6 +56,12 @@ #include "qcocoascreen.h" #include <QtGui/private/qcoregraphics_p.h> +#warning NSUserNotification was deprecated in macOS 11. \ +We should be using UserNotifications.framework instead. \ +See QTBUG-110998 for more information. +#define NSUserNotificationCenter QT_IGNORE_DEPRECATIONS(NSUserNotificationCenter) +#define NSUserNotification QT_IGNORE_DEPRECATIONS(NSUserNotification) + QT_BEGIN_NAMESPACE void QCocoaSystemTrayIcon::init() @@ -100,6 +70,8 @@ void QCocoaSystemTrayIcon::init() m_delegate = [[QStatusItemDelegate alloc] initWithSysTray:this]; + // In case the status item does not have a menu assigned to it + // we fall back to the item's button to detect activation. m_statusItem.button.target = m_delegate; m_statusItem.button.action = @selector(statusItemClicked); [m_statusItem.button sendActionOn:NSEventMaskLeftMouseDown | NSEventMaskRightMouseDown | NSEventMaskOtherMouseDown]; @@ -117,8 +89,6 @@ void QCocoaSystemTrayIcon::cleanup() [m_delegate release]; m_delegate = nil; - - m_menu = nullptr; } QRect QCocoaSystemTrayIcon::geometry() const @@ -214,12 +184,31 @@ void QCocoaSystemTrayIcon::updateIcon(const QIcon &icon) void QCocoaSystemTrayIcon::updateMenu(QPlatformMenu *menu) { - // We don't set the menu property of the NSStatusItem here, - // as that would prevent us from receiving the action for the - // click, and we wouldn't be able to emit the activated signal. - // Instead we show the menu manually when the status item is - // clicked. - m_menu = static_cast<QCocoaMenu *>(menu); + auto *nsMenu = menu ? static_cast<QCocoaMenu *>(menu)->nsMenu() : nil; + if (m_statusItem.menu == nsMenu) + return; + + if (m_statusItem.menu) { + [NSNotificationCenter.defaultCenter removeObserver:m_delegate + name:NSMenuDidBeginTrackingNotification + object:m_statusItem.menu + ]; + } + + m_statusItem.menu = nsMenu; + + if (m_statusItem.menu) { + // When a menu is assigned, NSStatusBarButtonCell will intercept the mouse + // down to pop up the menu, and we never see the NSStatusBarButton action. + // To ensure we emit the 'activated' signal in both cases we detect when + // menu starts tracking, which happens before the menu delegate is sent + // the menuWillOpen callback we use to emit aboutToShow for the menu. + [NSNotificationCenter.defaultCenter addObserver:m_delegate + selector:@selector(statusItemMenuBeganTracking:) + name:NSMenuDidBeginTrackingNotification + object:m_statusItem.menu + ]; + } } void QCocoaSystemTrayIcon::updateToolTip(const QString &toolTip) @@ -262,7 +251,7 @@ void QCocoaSystemTrayIcon::showMessage(const QString &title, const QString &mess } } -void QCocoaSystemTrayIcon::statusItemClicked() +void QCocoaSystemTrayIcon::emitActivated() { auto *mouseEvent = NSApp.currentEvent; @@ -281,9 +270,6 @@ void QCocoaSystemTrayIcon::statusItemClicked() } emit activated(activationReason); - - if (NSMenu *menu = m_menu ? m_menu->nsMenu() : nil) - QT_IGNORE_DEPRECATIONS([m_statusItem popUpStatusItemMenu:menu]); } QT_END_NAMESPACE @@ -306,7 +292,12 @@ QT_END_NAMESPACE - (void)statusItemClicked { - self.platformSystemTray->statusItemClicked(); + self.platformSystemTray->emitActivated(); +} + +- (void)statusItemMenuBeganTracking:(NSNotification*)notification +{ + self.platformSystemTray->emitActivated(); } - (BOOL)userNotificationCenter:(NSUserNotificationCenter *)center shouldPresentNotification:(NSUserNotification *)notification diff --git a/src/plugins/platforms/cocoa/qcocoatheme.h b/src/plugins/platforms/cocoa/qcocoatheme.h index 436ed94351..c49d83feae 100644 --- a/src/plugins/platforms/cocoa/qcocoatheme.h +++ b/src/plugins/platforms/cocoa/qcocoatheme.h @@ -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 #ifndef QPLATFORMTHEME_COCOA_H #define QPLATFORMTHEME_COCOA_H @@ -71,9 +35,10 @@ public: const QFont *font(Font type = SystemFont) const override; QPixmap standardPixmap(StandardPixmap sp, const QSizeF &size) const override; QIcon fileIcon(const QFileInfo &fileInfo, QPlatformTheme::IconOptions options = {}) const override; + QIconEngine *createIconEngine(const QString &iconName) const override; QVariant themeHint(ThemeHint hint) const override; - Appearance appearance() const override; + Qt::ColorScheme colorScheme() const override; QString standardButtonText(int button) const override; QKeySequence standardButtonShortcut(int button) const override; @@ -89,8 +54,10 @@ private: mutable QPalette *m_systemPalette; QMacNotificationObserver m_systemColorObserver; mutable QHash<QPlatformTheme::Palette, QPalette*> m_palettes; - mutable QHash<QPlatformTheme::Font, QFont*> m_fonts; QMacKeyValueObserver m_appearanceObserver; + + Qt::ColorScheme m_colorScheme = Qt::ColorScheme::Unknown; + void updateColorScheme(); }; QT_END_NAMESPACE diff --git a/src/plugins/platforms/cocoa/qcocoatheme.mm b/src/plugins/platforms/cocoa/qcocoatheme.mm index dfc1c8197b..f4fbfadbe4 100644 --- a/src/plugins/platforms/cocoa/qcocoatheme.mm +++ b/src/plugins/platforms/cocoa/qcocoatheme.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> @@ -51,12 +15,14 @@ #include "qcocoahelpers.h" #include <QtCore/qfileinfo.h> +#include <QtCore/private/qcore_mac_p.h> #include <QtGui/private/qfont_p.h> #include <QtGui/private/qguiapplication_p.h> #include <QtGui/private/qcoregraphics_p.h> #include <QtGui/qpainter.h> #include <QtGui/qtextformat.h> #include <QtGui/private/qcoretextfontdatabase_p.h> +#include <QtGui/private/qappleiconengine_p.h> #include <QtGui/private/qfontengine_coretext_p.h> #include <QtGui/private/qabstractfileiconengine_p.h> #include <qpa/qplatformdialoghelper.h> @@ -66,21 +32,10 @@ #include "qcocoacolordialoghelper.h" #include "qcocoafiledialoghelper.h" #include "qcocoafontdialoghelper.h" +#include "qcocoamessagedialog.h" #include <CoreServices/CoreServices.h> -#if !QT_MACOS_PLATFORM_SDK_EQUAL_OR_ABOVE(__MAC_10_14) -@interface NSColor (MojaveForwardDeclarations) -@property (class, strong, readonly) NSColor *selectedContentBackgroundColor NS_AVAILABLE_MAC(10_14); -@property (class, strong, readonly) NSColor *unemphasizedSelectedTextBackgroundColor NS_AVAILABLE_MAC(10_14); -@property (class, strong, readonly) NSColor *unemphasizedSelectedTextColor NS_AVAILABLE_MAC(10_14); -@property (class, strong, readonly) NSColor *unemphasizedSelectedContentBackgroundColor NS_AVAILABLE_MAC(10_14); -@property (class, strong, readonly) NSArray<NSColor *> *alternatingContentBackgroundColors NS_AVAILABLE_MAC(10_14); -// Missing from non-Mojave SDKs, even if introduced in 10.10 -@property (class, strong, readonly) NSColor *linkColor NS_AVAILABLE_MAC(10_10); -@end -#endif - QT_BEGIN_NAMESPACE static QPalette *qt_mac_createSystemPalette() @@ -110,14 +65,9 @@ static QPalette *qt_mac_createSystemPalette() // System palette initialization: QBrush br = qt_mac_toQBrush([NSColor selectedControlColor]); palette->setBrush(QPalette::Active, QPalette::Highlight, br); - if (__builtin_available(macOS 10.14, *)) { - const auto inactiveHighlight = qt_mac_toQBrush([NSColor unemphasizedSelectedContentBackgroundColor]); - palette->setBrush(QPalette::Inactive, QPalette::Highlight, inactiveHighlight); - palette->setBrush(QPalette::Disabled, QPalette::Highlight, inactiveHighlight); - } else { - palette->setBrush(QPalette::Inactive, QPalette::Highlight, br); - palette->setBrush(QPalette::Disabled, QPalette::Highlight, br); - } + const auto inactiveHighlight = qt_mac_toQBrush([NSColor unemphasizedSelectedContentBackgroundColor]); + palette->setBrush(QPalette::Inactive, QPalette::Highlight, inactiveHighlight); + palette->setBrush(QPalette::Disabled, QPalette::Highlight, inactiveHighlight); palette->setBrush(QPalette::Shadow, qt_mac_toQColor([NSColor shadowColor])); @@ -145,6 +95,9 @@ static QPalette *qt_mac_createSystemPalette() palette->setColor(QPalette::Inactive, QPalette::PlaceholderText, qc); palette->setColor(QPalette::Disabled, QPalette::PlaceholderText, qc); + qc = qt_mac_toQColor([NSColor controlAccentColor]); + palette->setColor(QPalette::Accent, qc); + return palette; } @@ -202,17 +155,8 @@ static QHash<QPlatformTheme::Palette, QPalette*> qt_mac_createRolePalettes() } if (mac_widget_colors[i].paletteRole == QPlatformTheme::MenuPalette || mac_widget_colors[i].paletteRole == QPlatformTheme::MenuBarPalette) { - NSColor *selectedMenuItemColor = nil; - if (__builtin_available(macOS 10.14, *)) { - // Cheap approximation for NSVisualEffectView (see deprecation note for selectedMenuItemTextColor) - selectedMenuItemColor = [[NSColor selectedContentBackgroundColor] highlightWithLevel:0.4]; - } else { - // selectedMenuItemColor would presumably be the correct color to use as the background - // for selected menu items. But that color is always blue, and doesn't follow the - // appearance color in system preferences. So we therefore deliberately choose to use - // keyboardFocusIndicatorColor instead, which appears to have the same color value. - selectedMenuItemColor = [NSColor keyboardFocusIndicatorColor]; - } + // Cheap approximation for NSVisualEffectView (see deprecation note for selectedMenuItemTextColor) + auto selectedMenuItemColor = [[NSColor controlAccentColor] highlightWithLevel:0.3]; pal.setBrush(QPalette::Highlight, qt_mac_toQColor(selectedMenuItemColor)); qc = qt_mac_toQColor([NSColor labelColor]); pal.setBrush(QPalette::ButtonText, qc); @@ -233,17 +177,10 @@ static QHash<QPlatformTheme::Palette, QPalette*> qt_mac_createRolePalettes() } else if (mac_widget_colors[i].paletteRole == QPlatformTheme::ItemViewPalette) { NSArray<NSColor *> *baseColors = nil; NSColor *activeHighlightColor = nil; - if (__builtin_available(macOS 10.14, *)) { - baseColors = [NSColor alternatingContentBackgroundColors]; - activeHighlightColor = [NSColor selectedContentBackgroundColor]; - pal.setBrush(QPalette::Inactive, QPalette::HighlightedText, - qt_mac_toQBrush([NSColor unemphasizedSelectedTextColor])); - } else { - baseColors = [NSColor controlAlternatingRowBackgroundColors]; - activeHighlightColor = [NSColor alternateSelectedControlColor]; - pal.setBrush(QPalette::Inactive, QPalette::HighlightedText, - pal.brush(QPalette::Active, QPalette::Text)); - } + baseColors = [NSColor alternatingContentBackgroundColors]; + activeHighlightColor = [NSColor selectedContentBackgroundColor]; + pal.setBrush(QPalette::Inactive, QPalette::HighlightedText, + qt_mac_toQBrush([NSColor unemphasizedSelectedTextColor])); pal.setBrush(QPalette::Base, qt_mac_toQBrush(baseColors[0])); pal.setBrush(QPalette::AlternateBase, qt_mac_toQBrush(baseColors[1])); pal.setBrush(QPalette::Active, QPalette::Highlight, @@ -277,27 +214,24 @@ const char *QCocoaTheme::name = "cocoa"; QCocoaTheme::QCocoaTheme() : m_systemPalette(nullptr) { -#if QT_MACOS_PLATFORM_SDK_EQUAL_OR_ABOVE(__MAC_10_14) if (QOperatingSystemVersion::current() >= QOperatingSystemVersion::MacOSMojave) { m_appearanceObserver = QMacKeyValueObserver(NSApp, @"effectiveAppearance", [this] { - if (__builtin_available(macOS 10.14, *)) - NSAppearance.currentAppearance = NSApp.effectiveAppearance; - + NSAppearance.currentAppearance = NSApp.effectiveAppearance; handleSystemThemeChange(); }); } -#endif m_systemColorObserver = QMacNotificationObserver(nil, NSSystemColorsDidChangeNotification, [this] { handleSystemThemeChange(); }); + + updateColorScheme(); } QCocoaTheme::~QCocoaTheme() { reset(); - qDeleteAll(m_fonts); } void QCocoaTheme::reset() @@ -311,6 +245,9 @@ void QCocoaTheme::reset() void QCocoaTheme::handleSystemThemeChange() { reset(); + + updateColorScheme(); + m_systemPalette = qt_mac_createSystemPalette(); m_palettes = qt_mac_createRolePalettes(); @@ -319,18 +256,20 @@ void QCocoaTheme::handleSystemThemeChange() QFontCache::instance()->clear(); } - QWindowSystemInterface::handleThemeChange<QWindowSystemInterface::SynchronousDelivery>(nullptr); + QWindowSystemInterface::handleThemeChange<QWindowSystemInterface::SynchronousDelivery>(); } bool QCocoaTheme::usePlatformNativeDialog(DialogType dialogType) const { - if (dialogType == QPlatformTheme::FileDialog) - return true; - if (dialogType == QPlatformTheme::ColorDialog) - return true; - if (dialogType == QPlatformTheme::FontDialog) + switch (dialogType) { + case QPlatformTheme::FileDialog: + case QPlatformTheme::ColorDialog: + case QPlatformTheme::FontDialog: + case QPlatformTheme::MessageDialog: return true; - return false; + default: + return false; + } } QPlatformDialogHelper *QCocoaTheme::createPlatformDialogHelper(DialogType dialogType) const @@ -342,6 +281,8 @@ QPlatformDialogHelper *QCocoaTheme::createPlatformDialogHelper(DialogType dialog return new QCocoaColorDialogHelper(); case QPlatformTheme::FontDialog: return new QCocoaFontDialogHelper(); + case QPlatformTheme::MessageDialog: + return new QCocoaMessageDialog; default: return nullptr; } @@ -370,12 +311,9 @@ const QPalette *QCocoaTheme::palette(Palette type) const const QFont *QCocoaTheme::font(Font type) const { - if (m_fonts.isEmpty()) { - const auto *platformIntegration = QGuiApplicationPrivate::platformIntegration(); - const auto *coreTextFontDb = static_cast<QCoreTextFontDatabase *>(platformIntegration->fontDatabase()); - m_fonts = coreTextFontDb->themeFonts(); - } - return m_fonts.value(type, nullptr); + const auto *platformIntegration = QGuiApplicationPrivate::platformIntegration(); + const auto *coreTextFontDatabase = static_cast<QCoreTextFontDatabase *>(platformIntegration->fontDatabase()); + return coreTextFontDatabase->themeFont(type); } //! \internal @@ -452,11 +390,11 @@ QPixmap QCocoaTheme::standardPixmap(StandardPixmap sp, const QSizeF &size) const if (iconType != 0) { QPixmap pixmap; IconRef icon = nullptr; - GetIconRef(kOnSystemDisk, kSystemIconsCreator, iconType, &icon); + QT_IGNORE_DEPRECATIONS(GetIconRef(kOnSystemDisk, kSystemIconsCreator, iconType, &icon)); if (icon) { pixmap = qt_mac_convert_iconref(icon, size.width(), size.height()); - ReleaseIconRef(icon); + QT_IGNORE_DEPRECATIONS(ReleaseIconRef(icon)); } return pixmap; @@ -472,19 +410,8 @@ public: QPlatformTheme::IconOptions opts) : QAbstractFileIconEngine(info, opts) {} - static QList<QSize> availableIconSizes() - { - const qreal devicePixelRatio = qGuiApp->devicePixelRatio(); - const int sizes[] = { - qRound(16 * devicePixelRatio), qRound(32 * devicePixelRatio), - qRound(64 * devicePixelRatio), qRound(128 * devicePixelRatio), - qRound(256 * devicePixelRatio) - }; - return QAbstractFileIconEngine::toSizeList(sizes, sizes + sizeof(sizes) / sizeof(sizes[0])); - } - QList<QSize> availableSizes(QIcon::Mode = QIcon::Normal, QIcon::State = QIcon::Off) override - { return QCocoaFileIconEngine::availableIconSizes(); } + { return QAppleIconEngine::availableIconSizes(); } protected: QPixmap filePixmap(const QSize &size, QIcon::Mode, QIcon::State) override @@ -503,6 +430,11 @@ QIcon QCocoaTheme::fileIcon(const QFileInfo &fileInfo, QPlatformTheme::IconOptio return QIcon(new QCocoaFileIconEngine(fileInfo, iconOptions)); } +QIconEngine *QCocoaTheme::createIconEngine(const QString &iconName) const +{ + return new QAppleIconEngine(iconName); +} + QVariant QCocoaTheme::themeHint(ThemeHint hint) const { switch (hint) { @@ -516,7 +448,7 @@ QVariant QCocoaTheme::themeHint(ThemeHint hint) const return QVariant([[NSApplication sharedApplication] isFullKeyboardAccessEnabled] ? int(Qt::TabFocusAllControls) : int(Qt::TabFocusTextControls | Qt::TabFocusListControls)); case IconPixmapSizes: - return QVariant::fromValue(QCocoaFileIconEngine::availableIconSizes()); + return QVariant::fromValue(QAppleIconEngine::availableIconSizes()); case QPlatformTheme::PasswordMaskCharacter: return QVariant(QChar(0x2022)); case QPlatformTheme::UiEffects: @@ -529,15 +461,33 @@ QVariant QCocoaTheme::themeHint(ThemeHint hint) const return !NSScreen.screensHaveSeparateSpaces; case QPlatformTheme::ShowDirectoriesFirst: return false; + case QPlatformTheme::MouseDoubleClickInterval: + return NSEvent.doubleClickInterval * 1000; + case QPlatformTheme::KeyboardInputInterval: + return NSEvent.keyRepeatDelay * 1000; + case QPlatformTheme::KeyboardAutoRepeatRate: + return 1.0 / NSEvent.keyRepeatInterval; default: break; } return QPlatformTheme::themeHint(hint); } -QPlatformTheme::Appearance QCocoaTheme::appearance() const +Qt::ColorScheme QCocoaTheme::colorScheme() const +{ + return m_colorScheme; +} + +/* + Update the theme's color scheme based on the current appearance. + + We can only reference the appearance on the main thread, but the + CoreText font engine needs to know the color scheme, and might be + used from secondary threads, so we cache the color scheme. +*/ +void QCocoaTheme::updateColorScheme() { - return qt_mac_applicationIsInDarkMode() ? Appearance::Dark : Appearance::Light; + m_colorScheme = qt_mac_applicationIsInDarkMode() ? Qt::ColorScheme::Dark : Qt::ColorScheme::Light; } QString QCocoaTheme::standardButtonText(int button) const @@ -555,12 +505,16 @@ QKeySequence QCocoaTheme::standardButtonShortcut(int button) const QPlatformMenuItem *QCocoaTheme::createPlatformMenuItem() const { - return new QCocoaMenuItem(); + auto *menuItem = new QCocoaMenuItem(); + qCDebug(lcQpaMenus) << "Created" << menuItem; + return menuItem; } QPlatformMenu *QCocoaTheme::createPlatformMenu() const { - return new QCocoaMenu(); + auto *menu = new QCocoaMenu(); + qCDebug(lcQpaMenus) << "Created" << menu; + return menu; } QPlatformMenuBar *QCocoaTheme::createPlatformMenuBar() const @@ -573,7 +527,9 @@ QPlatformMenuBar *QCocoaTheme::createPlatformMenuBar() const SLOT(onAppFocusWindowChanged(QWindow*))); } - return new QCocoaMenuBar(); + auto *menuBar = new QCocoaMenuBar(); + qCDebug(lcQpaMenus) << "Created" << menuBar; + return menuBar; } #ifndef QT_NO_SHORTCUT diff --git a/src/plugins/platforms/cocoa/qcocoavulkaninstance.h b/src/plugins/platforms/cocoa/qcocoavulkaninstance.h index e9ffab3fb4..fd6b7c6452 100644 --- a/src/plugins/platforms/cocoa/qcocoavulkaninstance.h +++ b/src/plugins/platforms/cocoa/qcocoavulkaninstance.h @@ -1,41 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2018 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of 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) 2018 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 #ifndef QCOCOAVULKANINSTANCE_H #define QCOCOAVULKANINSTANCE_H diff --git a/src/plugins/platforms/cocoa/qcocoavulkaninstance.mm b/src/plugins/platforms/cocoa/qcocoavulkaninstance.mm index b491387e07..d2986ea5e6 100644 --- a/src/plugins/platforms/cocoa/qcocoavulkaninstance.mm +++ b/src/plugins/platforms/cocoa/qcocoavulkaninstance.mm @@ -1,41 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2018 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of 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) 2018 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> diff --git a/src/plugins/platforms/cocoa/qcocoawindow.h b/src/plugins/platforms/cocoa/qcocoawindow.h index c0684504aa..ba1bc052fb 100644 --- a/src/plugins/platforms/cocoa/qcocoawindow.h +++ b/src/plugins/platforms/cocoa/qcocoawindow.h @@ -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 #ifndef QCOCOAWINDOW_H #define QCOCOAWINDOW_H @@ -75,9 +39,9 @@ class QDebug; // QCocoaWindow // // QCocoaWindow is an NSView (not an NSWindow!) in the sense -// that it relies on a NSView for all event handling and -// graphics output and does not require a NSWindow, except for -// for the window-related functions like setWindowTitle. +// that it relies on an NSView for all event handling and +// graphics output and does not require an NSWindow, except for +// the window-related functions like setWindowTitle. // // As a consequence of this it is possible to embed the QCocoaWindow // in an NSView hierarchy by getting a pointer to the "backing" @@ -194,18 +158,19 @@ public: void setWindowCursor(NSCursor *cursor); void registerTouch(bool enable); - void setContentBorderThickness(int topThickness, int bottomThickness); + void registerContentBorderArea(quintptr identifier, int upper, int lower); void setContentBorderAreaEnabled(quintptr identifier, bool enable); void setContentBorderEnabled(bool enable) override; bool testContentBorderAreaPosition(int position) const; void applyContentBorderThickness(NSWindow *window = nullptr); - void updateNSToolbar(); qreal devicePixelRatio() const override; QWindow *childWindowAt(QPoint windowPoint); bool shouldRefuseKeyWindowAndFirstResponder(); + bool windowEvent(QEvent *event) override; + QPoint bottomLeftClippedByNSWindowOffset() const override; void updateNormalGeometry(); @@ -225,7 +190,7 @@ protected: void recreateWindowIfNeeded(); QCocoaNSWindow *createNSWindow(bool shouldBePanel); - Qt::WindowState windowState() const; + Qt::WindowStates windowState() const; void applyWindowState(Qt::WindowStates newState); void toggleMaximized(); void toggleFullScreen(); @@ -251,32 +216,35 @@ public: // for QNSView void handleWindowStateChanged(HandleFlags flags = NoHandleFlags); void handleExposeEvent(const QRegion ®ion); - NSView *m_view; - QCocoaNSWindow *m_nsWindow; + static void closeAllPopups(); + static void setupPopupMonitor(); + static void removePopupMonitor(); + + NSView *m_view = nil; + QCocoaNSWindow *m_nsWindow = nil; - Qt::WindowStates m_lastReportedWindowState; - Qt::WindowModality m_windowModality; + Qt::WindowStates m_lastReportedWindowState = Qt::WindowNoState; + Qt::WindowModality m_windowModality = Qt::NonModal; static QPointer<QCocoaWindow> s_windowUnderMouse; - bool m_initialized; - bool m_inSetVisible; - bool m_inSetGeometry; - bool m_inSetStyleMask; - QCocoaMenuBar *m_menubar; + bool m_initialized = false; + bool m_inSetVisible = false; + bool m_inSetGeometry = false; + bool m_inSetStyleMask = false; - bool m_frameStrutEventsEnabled; + QCocoaMenuBar *m_menubar = nullptr; + + bool m_frameStrutEventsEnabled = false; QRect m_exposedRect; QRect m_normalGeometry; - int m_registerTouchCount; - bool m_resizableTransientParent; + int m_registerTouchCount = 0; + bool m_resizableTransientParent = false; static const int NoAlertRequest; - NSInteger m_alertRequest; + NSInteger m_alertRequest = NoAlertRequest; - bool m_drawContentBorderGradient; - int m_topContentBorderThickness; - int m_bottomContentBorderThickness; + bool m_drawContentBorderGradient = false; struct BorderRange { BorderRange(quintptr i, int u, int l) : identifier(i), upper(u), lower(l) { } @@ -290,6 +258,9 @@ public: // for QNSView QHash<quintptr, BorderRange> m_contentBorderAreas; // identifier -> uppper/lower QHash<quintptr, bool> m_enabledContentBorderAreas; // identifier -> enabled state (true/false) + static inline id s_globalMouseMonitor = 0; + static inline id s_applicationActivationObserver = 0; + #if QT_CONFIG(vulkan) VkSurfaceKHR m_vulkanSurface = nullptr; #endif diff --git a/src/plugins/platforms/cocoa/qcocoawindow.mm b/src/plugins/platforms/cocoa/qcocoawindow.mm index 70cfa42974..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]; @@ -134,24 +98,7 @@ 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_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_drawContentBorderGradient(false) - , m_topContentBorderThickness(0) - , m_bottomContentBorderThickness(0) +QCocoaWindow::QCocoaWindow(QWindow *win, WId nativeHandle) : QPlatformWindow(win) { qCDebug(lcQpaWindow) << "QCocoaWindow::QCocoaWindow" << window(); @@ -170,16 +117,24 @@ 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(); @@ -195,7 +150,10 @@ QCocoaWindow::~QCocoaWindow() QMacAutoReleasePool pool; [m_nsWindow makeFirstResponder:nil]; [m_nsWindow setContentView:nil]; - if ([m_view superview]) + + // 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 @@ -219,8 +177,7 @@ QCocoaWindow::~QCocoaWindow() object:m_view]; [m_view release]; - [m_nsWindow close]; - [m_nsWindow release]; + [m_nsWindow closeAndRelease]; } QSurfaceFormat QCocoaWindow::format() const @@ -351,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; @@ -389,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); @@ -426,13 +411,6 @@ void QCocoaWindow::setVisible(bool visible) } } } - - // 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()) { @@ -461,10 +439,28 @@ void QCocoaWindow::setVisible(bool visible) if (mainWindow && [mainWindow canBecomeKeyWindow]) [mainWindow makeKeyWindow]; } - } else { - [m_view setHidden:YES]; } + // 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]; + } + + m_view.hidden = YES; + if (parentCocoaWindow && window()->type() == Qt::Popup) { NSWindow *nativeParentWindow = parentCocoaWindow->nativeWindow(); if (m_resizableTransientParent @@ -562,9 +558,11 @@ NSUInteger QCocoaWindow::windowStyleMask(Qt::WindowFlags flags) // working (for example minimizing frameless windows, or resizing // windows that don't have zoom or fullscreen titlebar buttons). styleMask |= NSWindowStyleMaskClosable - | NSWindowStyleMaskResizable | NSWindowStyleMaskMiniaturizable; + if (type != Qt::Popup) // We only care about popups exactly. + styleMask |= NSWindowStyleMaskResizable; + if (type == Qt::Tool) styleMask |= NSWindowStyleMaskUtilityWindow; @@ -592,8 +590,6 @@ void QCocoaWindow::updateTitleBarButtons(Qt::WindowFlags windowFlags) if (!isContentView()) return; - NSWindow *window = m_view.window; - static constexpr std::pair<NSWindowButton, Qt::WindowFlags> buttons[] = { { NSWindowCloseButton, Qt::WindowCloseButtonHint }, { NSWindowMiniaturizeButton, Qt::WindowMinimizeButtonHint}, @@ -602,20 +598,38 @@ void QCocoaWindow::updateTitleBarButtons(Qt::WindowFlags windowFlags) 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; - [window standardWindowButton:button].enabled = enabled; + // 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) - [window standardWindowButton:button].hidden = hideButtons; + [m_view.window standardWindowButton:button].hidden = hideButtons; } void QCocoaWindow::setWindowFlags(Qt::WindowFlags flags) @@ -694,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) @@ -726,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 @@ -762,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() @@ -894,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; @@ -1114,28 +1141,14 @@ void QCocoaWindow::setMask(const QRegion ®ion) } } -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 @@ -1233,36 +1246,37 @@ void QCocoaWindow::windowDidEndLiveResize() void QCocoaWindow::windowDidBecomeKey() { - if (!isContentView()) + // 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 (isForeignWindow()) - return; + qCDebug(lcQpaWindow) << m_view.window << "became key window." + << "Updating focus window to" << this << "with view" << m_view; - QNSView *firstResponderView = qt_objc_cast<QNSView *>(m_view.window.firstResponder); - if (!firstResponderView) - return; - - 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. - QGuiApplicationPrivate::instance()->closeAllPopups(); + 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 @@ -1270,12 +1284,14 @@ void QCocoaWindow::windowDidResignKey() NSWindow *newKeyWindow = [NSApp keyWindow]; if (newKeyWindow && newKeyWindow != m_view.window && [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); } } @@ -1312,8 +1328,14 @@ void QCocoaWindow::windowDidOrderOffScreen() 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 @@ -1481,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; @@ -1519,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) { @@ -1550,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() @@ -1583,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(); @@ -1609,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; @@ -1708,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(); } } @@ -1722,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; } @@ -1761,22 +1855,32 @@ 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]; - // There's a bug in AppKit where calling invalidateCursorRectsForView when - // there's an override cursor active (for example when hovering over the - // window frame), will not result in a cursorUpdate: callback. To work around - // this we synthesize a cursor update event and call the callback ourselves, - // if we detect that the mouse is currently over the 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]; - if ([m_view hitTest:locationInSuperview] == m_view) { + 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 @@ -1793,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)); @@ -1839,7 +1933,7 @@ 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)) @@ -1854,7 +1948,7 @@ void QCocoaWindow::applyContentBorderThickness(NSWindow *window) break; } - int effectiveBottomContentBorderThickness = m_bottomContentBorderThickness; + int effectiveBottomContentBorderThickness = 0; [window setStyleMask:[window styleMask] | NSWindowStyleMaskTexturedBackground]; window.titlebarAppearsTransparent = YES; @@ -1876,21 +1970,6 @@ 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()) @@ -1910,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)]; @@ -1937,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()) @@ -1946,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) @@ -1989,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" diff --git a/src/plugins/platforms/cocoa/qcocoawindowmanager.h b/src/plugins/platforms/cocoa/qcocoawindowmanager.h index 2877dcd5ef..85e44e2a5d 100644 --- a/src/plugins/platforms/cocoa/qcocoawindowmanager.h +++ b/src/plugins/platforms/cocoa/qcocoawindowmanager.h @@ -1,58 +1,25 @@ -/**************************************************************************** -** -** Copyright (C) 2019 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) 2019 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 #ifndef QCOCOAWINDOWMANAGER_H #define QCOCOAWINDOWMANAGER_H #include <QtCore/qglobal.h> +#include <QtCore/private/qcore_mac_p.h> QT_BEGIN_NAMESPACE class QCocoaWindowManager { public: - static QCocoaWindowManager *instance(); + QCocoaWindowManager(); private: - QCocoaWindowManager(); + + QMacNotificationObserver m_applicationDidFinishLaunchingObserver; void initialize(); + QMacKeyValueObserver m_modalSessionObserver; void modalSessionChanged(); }; diff --git a/src/plugins/platforms/cocoa/qcocoawindowmanager.mm b/src/plugins/platforms/cocoa/qcocoawindowmanager.mm index 71bcecdbc6..ad5d5b23d8 100644 --- a/src/plugins/platforms/cocoa/qcocoawindowmanager.mm +++ b/src/plugins/platforms/cocoa/qcocoawindowmanager.mm @@ -1,41 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2019 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) 2019 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> @@ -49,19 +13,13 @@ QT_BEGIN_NAMESPACE -QCocoaWindowManager *QCocoaWindowManager::instance() -{ - static auto *instance = new QCocoaWindowManager; - return instance; -} - QCocoaWindowManager::QCocoaWindowManager() { if (NSApp) { initialize(); } else { - static auto applicationDidFinishLaunching(QMacNotificationObserver(nil, - NSApplicationDidFinishLaunchingNotification, [this] { initialize(); })); + m_applicationDidFinishLaunchingObserver = QMacNotificationObserver(nil, + NSApplicationDidFinishLaunchingNotification, [this] { initialize(); }); } } @@ -74,9 +32,9 @@ void QCocoaWindowManager::initialize() // event dispatcher sessions allows us to track session started by native // APIs as well. We need to check the initial state as well, in case there // is already a modal session running. - static auto modalSessionObserver(QMacKeyValueObserver( + m_modalSessionObserver = QMacKeyValueObserver( NSApp, @"modalWindow", [this] { modalSessionChanged(); }, - NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew)); + NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew); } void QCocoaWindowManager::modalSessionChanged() diff --git a/src/plugins/platforms/cocoa/qiosurfacegraphicsbuffer.h b/src/plugins/platforms/cocoa/qiosurfacegraphicsbuffer.h index cc7193d8b7..9fe3d01c77 100644 --- a/src/plugins/platforms/cocoa/qiosurfacegraphicsbuffer.h +++ b/src/plugins/platforms/cocoa/qiosurfacegraphicsbuffer.h @@ -1,41 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2019 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) 2019 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 #ifndef QIOSURFACEGRAPHICSBUFFER_H #define QIOSURFACEGRAPHICSBUFFER_H diff --git a/src/plugins/platforms/cocoa/qiosurfacegraphicsbuffer.mm b/src/plugins/platforms/cocoa/qiosurfacegraphicsbuffer.mm index fc187e0f51..b987723b8a 100644 --- a/src/plugins/platforms/cocoa/qiosurfacegraphicsbuffer.mm +++ b/src/plugins/platforms/cocoa/qiosurfacegraphicsbuffer.mm @@ -1,41 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2019 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) 2019 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 "qiosurfacegraphicsbuffer.h" diff --git a/src/plugins/platforms/cocoa/qmacclipboard.h b/src/plugins/platforms/cocoa/qmacclipboard.h index df892a2810..95267565f2 100644 --- a/src/plugins/platforms/cocoa/qmacclipboard.h +++ b/src/plugins/platforms/cocoa/qmacclipboard.h @@ -1,89 +1,61 @@ -/**************************************************************************** -** -** 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 #ifndef QMACCLIPBOARD_H #define QMACCLIPBOARD_H #include <QtGui> -#include <QtGui/private/qmacmime_p.h> +#include <QtGui/qutimimeconverter.h> + +#include <QtCore/qpointer.h> #include <ApplicationServices/ApplicationServices.h> QT_BEGIN_NAMESPACE -class QMacMimeData; +class QUtiMimeConverter; + class QMacPasteboard { public: enum DataRequestType { EagerRequest, LazyRequest }; private: struct Promise { - Promise() : itemId(0), convertor(nullptr) { } + Promise() : itemId(0), converter(nullptr) { } - static Promise eagerPromise(int itemId, QMacInternalPasteboardMime *c, QString m, QMacMimeData *d, int o = 0); - static Promise lazyPromise(int itemId, QMacInternalPasteboardMime *c, QString m, QMacMimeData *d, int o = 0); - Promise(int itemId, QMacInternalPasteboardMime *c, QString m, QMacMimeData *md, int o, DataRequestType drt); + static Promise eagerPromise(int itemId, const QUtiMimeConverter *c, const QString &m, QMimeData *d, int o = 0); + static Promise lazyPromise(int itemId, const QUtiMimeConverter *c, const QString &m, QMimeData *d, int o = 0); + Promise(int itemId, const QUtiMimeConverter *c, const QString &m, QMimeData *md, int o, DataRequestType drt); int itemId, offset; - QMacInternalPasteboardMime *convertor; + const QUtiMimeConverter *converter; QString mime; - QPointer<QMacMimeData> mimeData; + QPointer<QMimeData> mimeData; QVariant variantData; DataRequestType dataRequestType; + // QMimeData can be set from QVariant, holding + // QPixmap. When converting, this triggers + // QPixmap's ctor which in turn requires QGuiApplication + // to exist and thus will abort the application + // abnormally if not. + bool isPixmap = false; }; QList<Promise> promises; PasteboardRef paste; - uchar mime_type; + const QUtiMimeConverter::HandlerScope scope; mutable QPointer<QMimeData> mime; mutable bool mac_mime_source; bool resolvingBeforeDestruction; static OSStatus promiseKeeper(PasteboardRef, PasteboardItemID, CFStringRef, void *); void clear_helper(); public: - QMacPasteboard(PasteboardRef p, uchar mime_type=0); - QMacPasteboard(uchar mime_type); - QMacPasteboard(CFStringRef name=nullptr, uchar mime_type=0); + QMacPasteboard(PasteboardRef p, QUtiMimeConverter::HandlerScope scope = QUtiMimeConverter::HandlerScopeFlag::All); + QMacPasteboard(QUtiMimeConverter::HandlerScope scope); + QMacPasteboard(CFStringRef name=nullptr, QUtiMimeConverter::HandlerScope scope = QUtiMimeConverter::HandlerScopeFlag::All); ~QMacPasteboard(); - bool hasFlavor(QString flavor) const; - bool hasOSType(int c_flavor) const; + bool hasUti(const QString &uti) const; PasteboardRef pasteBoard() const; QMimeData *mimeData() const; @@ -92,7 +64,7 @@ public: QStringList formats() const; bool hasFormat(const QString &format) const; - QVariant retrieveData(const QString &format, QMetaType) const; + QVariant retrieveData(const QString &format) const; void clear(); bool sync() const; diff --git a/src/plugins/platforms/cocoa/qmacclipboard.mm b/src/plugins/platforms/cocoa/qmacclipboard.mm index 23c8749b5d..edafa3b6a1 100644 --- a/src/plugins/platforms/cocoa/qmacclipboard.mm +++ b/src/plugins/platforms/cocoa/qmacclipboard.mm @@ -1,50 +1,18 @@ -/**************************************************************************** -** -** 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 "qmacclipboard.h" +#include <QtGui/private/qmacmimeregistry_p.h> +#include <QtGui/qutimimeconverter.h> #include <QtGui/qclipboard.h> #include <QtGui/qguiapplication.h> #include <QtGui/qbitmap.h> #include <QtCore/qdatetime.h> +#include <QtCore/qmetatype.h> #include <QtCore/qdebug.h> +#include <QtCore/private/qcore_mac_p.h> #include <QtGui/qguiapplication.h> #include <QtGui/qevent.h> #include <QtCore/qurl.h> @@ -55,6 +23,8 @@ QT_BEGIN_NAMESPACE +using namespace Qt::StringLiterals; + /***************************************************************************** QMacPasteboard code *****************************************************************************/ @@ -84,45 +54,47 @@ private: QMacMimeData(); }; -QMacPasteboard::Promise::Promise(int itemId, QMacInternalPasteboardMime *c, QString m, QMacMimeData *md, int o, DataRequestType drt) - : itemId(itemId), offset(o), convertor(c), mime(m), dataRequestType(drt) +QMacPasteboard::Promise::Promise(int itemId, const QUtiMimeConverter *c, const QString &m, QMimeData *md, int o, DataRequestType drt) + : itemId(itemId), offset(o), converter(c), mime(m), dataRequestType(drt) { // Request the data from the application immediately for eager requests. if (dataRequestType == QMacPasteboard::EagerRequest) { - variantData = md->variantData(m); + variantData = static_cast<QMacMimeData *>(md)->variantData(m); + isPixmap = variantData.metaType().id() == QMetaType::QPixmap; mimeData = nullptr; } else { mimeData = md; + if (md->hasImage()) + isPixmap = md->imageData().metaType().id() == QMetaType::QPixmap; } } -QMacPasteboard::QMacPasteboard(PasteboardRef p, uchar mt) +QMacPasteboard::QMacPasteboard(PasteboardRef p, QUtiMimeConverter::HandlerScope scope) + : scope(scope) { mac_mime_source = false; - mime_type = mt ? mt : uchar(QMacInternalPasteboardMime::MIME_ALL); paste = p; CFRetain(paste); resolvingBeforeDestruction = false; } -QMacPasteboard::QMacPasteboard(uchar mt) +QMacPasteboard::QMacPasteboard(QUtiMimeConverter::HandlerScope scope) + : scope(scope) { mac_mime_source = false; - mime_type = mt ? mt : uchar(QMacInternalPasteboardMime::MIME_ALL); paste = nullptr; OSStatus err = PasteboardCreate(nullptr, &paste); - if (err == noErr) { + if (err == noErr) PasteboardSetPromiseKeeper(paste, promiseKeeper, this); - } else { + else qDebug("PasteBoard: Error creating pasteboard: [%d]", (int)err); - } resolvingBeforeDestruction = false; } -QMacPasteboard::QMacPasteboard(CFStringRef name, uchar mt) +QMacPasteboard::QMacPasteboard(CFStringRef name, QUtiMimeConverter::HandlerScope scope) + : scope(scope) { mac_mime_source = false; - mime_type = mt ? mt : uchar(QMacInternalPasteboardMime::MIME_ALL); paste = nullptr; OSStatus err = PasteboardCreate(name, &paste); if (err == noErr) { @@ -139,87 +111,91 @@ QMacPasteboard::~QMacPasteboard() Commit all promises for paste when shutting down, unless we are the stack-allocated clipboard used by QCocoaDrag. */ - if (mime_type == QMacInternalPasteboardMime::MIME_DND) + if (scope == QUtiMimeConverter::HandlerScopeFlag::DnD) resolvingBeforeDestruction = true; PasteboardResolvePromises(paste); if (paste) CFRelease(paste); } -PasteboardRef -QMacPasteboard::pasteBoard() const +PasteboardRef QMacPasteboard::pasteBoard() const { return paste; } -OSStatus QMacPasteboard::promiseKeeper(PasteboardRef paste, PasteboardItemID id, CFStringRef flavor, void *_qpaste) +OSStatus QMacPasteboard::promiseKeeper(PasteboardRef paste, PasteboardItemID id, + CFStringRef uti, void *_qpaste) { QMacPasteboard *qpaste = (QMacPasteboard*)_qpaste; const long promise_id = (long)id; // Find the kept promise - QList<QMacInternalPasteboardMime*> availableConverters - = QMacInternalPasteboardMime::all(QMacInternalPasteboardMime::MIME_ALL); - const QString flavorAsQString = QString::fromCFString(flavor); + const QList<QUtiMimeConverter*> availableConverters = QMacMimeRegistry::all(QUtiMimeConverter::HandlerScopeFlag::All); + const QString utiAsQString = QString::fromCFString(uti); QMacPasteboard::Promise promise; for (int i = 0; i < qpaste->promises.size(); i++){ - QMacPasteboard::Promise tmp = qpaste->promises[i]; - if (!availableConverters.contains(tmp.convertor)) { + const QMacPasteboard::Promise tmp = qpaste->promises[i]; + if (!availableConverters.contains(tmp.converter)) { // promise.converter is a pointer initialized by the value found - // in QMacInternalPasteboardMime's global list of QMacInternalPasteboardMimes. - // We add pointers to this list in QMacInternalPasteboardMime's ctor; - // we remove these pointers in QMacInternalPasteboardMime's dtor. + // in QUtiMimeConverter's global list of QMacMimes. + // We add pointers to this list in QUtiMimeConverter's ctor; + // we remove these pointers in QUtiMimeConverter's dtor. // If tmp.converter was not found in this list, we probably have a // dangling pointer so let's skip it. continue; } - if (tmp.itemId == promise_id && tmp.convertor->canConvert(tmp.mime, flavorAsQString)){ + if (tmp.itemId == promise_id && tmp.converter->canConvert(tmp.mime, utiAsQString)) { promise = tmp; break; } } - if (!promise.itemId && flavorAsQString == QLatin1String("com.trolltech.qt.MimeTypeName")) { + if (!promise.itemId && utiAsQString == "com.trolltech.qt.MimeTypeName"_L1) { // we have promised this data, but won't be able to convert, so return null data. // This helps in making the application/x-qt-mime-type-name hidden from normal use. QByteArray ba; - QCFType<CFDataRef> data = CFDataCreate(nullptr, (UInt8*)ba.constData(), ba.size()); - PasteboardPutItemFlavor(paste, id, flavor, data, kPasteboardFlavorNoFlags); + const QCFType<CFDataRef> data = CFDataCreate(nullptr, (UInt8*)ba.constData(), ba.size()); + PasteboardPutItemFlavor(paste, id, uti, data, kPasteboardFlavorNoFlags); return noErr; } if (!promise.itemId) { // There was no promise that could deliver data for the - // given id and flavor. This should not happen. - qDebug("Pasteboard: %d: Request for %ld, %s, but no promise found!", __LINE__, promise_id, qPrintable(flavorAsQString)); + // given id and uti. This should not happen. + qDebug("Pasteboard: %d: Request for %ld, %s, but no promise found!", __LINE__, promise_id, qPrintable(utiAsQString)); return cantGetFlavorErr; } - qCDebug(lcQpaClipboard, "PasteBoard: Calling in promise for %s[%ld] [%s] (%s) [%d]", qPrintable(promise.mime), promise_id, - qPrintable(flavorAsQString), qPrintable(promise.convertor->convertorName()), promise.offset); + qCDebug(lcQpaClipboard, "PasteBoard: Calling in promise for %s[%ld] [%s] [%d]", qPrintable(promise.mime), promise_id, + qPrintable(utiAsQString), promise.offset); // Get the promise data. If this is a "lazy" promise call variantData() // to request the data from the application. QVariant promiseData; if (promise.dataRequestType == LazyRequest) { - if (!qpaste->resolvingBeforeDestruction && !promise.mimeData.isNull()) - promiseData = promise.mimeData->variantData(promise.mime); + if (!qpaste->resolvingBeforeDestruction && !promise.mimeData.isNull()) { + if (promise.isPixmap && !QGuiApplication::instance()) { + qCWarning(lcQpaClipboard, + "Cannot keep promise, data contains QPixmap and requires livining QGuiApplication"); + return cantGetFlavorErr; + } + promiseData = static_cast<QMacMimeData *>(promise.mimeData.data())->variantData(promise.mime); + } } else { promiseData = promise.variantData; } - QList<QByteArray> md = promise.convertor->convertFromMime(promise.mime, promiseData, flavorAsQString); + const QList<QByteArray> md = promise.converter->convertFromMime(promise.mime, promiseData, utiAsQString); if (md.size() <= promise.offset) return cantGetFlavorErr; const QByteArray &ba = md[promise.offset]; - QCFType<CFDataRef> data = CFDataCreate(nullptr, (UInt8*)ba.constData(), ba.size()); - PasteboardPutItemFlavor(paste, id, flavor, data, kPasteboardFlavorNoFlags); + const QCFType<CFDataRef> data = CFDataCreate(nullptr, (UInt8*)ba.constData(), ba.size()); + PasteboardPutItemFlavor(paste, id, uti, data, kPasteboardFlavorNoFlags); return noErr; } -bool -QMacPasteboard::hasOSType(int c_flavor) const +bool QMacPasteboard::hasUti(const QString &uti) const { if (!paste) return false; @@ -230,48 +206,8 @@ QMacPasteboard::hasOSType(int c_flavor) const if (PasteboardGetItemCountSafe(paste, &cnt) || !cnt) return false; - qCDebug(lcQpaClipboard, "PasteBoard: hasOSType [%c%c%c%c]", (c_flavor>>24)&0xFF, (c_flavor>>16)&0xFF, - (c_flavor>>8)&0xFF, (c_flavor>>0)&0xFF); - for (uint index = 1; index <= cnt; ++index) { - - PasteboardItemID id; - if (PasteboardGetItemIdentifier(paste, index, &id) != noErr) - return false; - - QCFType<CFArrayRef> types; - if (PasteboardCopyItemFlavors(paste, id, &types ) != noErr) - return false; - - const int type_count = CFArrayGetCount(types); - for (int i = 0; i < type_count; ++i) { - CFStringRef flavor = (CFStringRef)CFArrayGetValueAtIndex(types, i); - CFStringRef preferredTag = UTTypeCopyPreferredTagWithClass(flavor, kUTTagClassOSType); - const int os_flavor = UTGetOSTypeFromString(preferredTag); - if (preferredTag) - CFRelease(preferredTag); - if (os_flavor == c_flavor) { - qCDebug(lcQpaClipboard, " - Found!"); - return true; - } - } - } - qCDebug(lcQpaClipboard, " - NotFound!"); - return false; -} - -bool -QMacPasteboard::hasFlavor(QString c_flavor) const -{ - if (!paste) - return false; - - sync(); - - ItemCount cnt = 0; - if (PasteboardGetItemCountSafe(paste, &cnt) || !cnt) - return false; - - qCDebug(lcQpaClipboard, "PasteBoard: hasFlavor [%s]", qPrintable(c_flavor)); + qCDebug(lcQpaClipboard, "PasteBoard: hasUti [%s]", qPrintable(uti)); + const QCFString c_uti(uti); for (uint index = 1; index <= cnt; ++index) { PasteboardItemID id; @@ -279,7 +215,7 @@ QMacPasteboard::hasFlavor(QString c_flavor) const return false; PasteboardFlavorFlags flags; - if (PasteboardGetItemFlavorFlags(paste, id, QCFString(c_flavor), &flags) == noErr) { + if (PasteboardGetItemFlavorFlags(paste, id, c_uti, &flags) == noErr) { qCDebug(lcQpaClipboard, " - Found!"); return true; } @@ -288,17 +224,20 @@ QMacPasteboard::hasFlavor(QString c_flavor) const return false; } -class QMacPasteboardMimeSource : public QMimeData { +class QMacPasteboardMimeSource : public QMimeData +{ const QMacPasteboard *paste; public: QMacPasteboardMimeSource(const QMacPasteboard *p) : QMimeData(), paste(p) { } ~QMacPasteboardMimeSource() { } - virtual QStringList formats() const { return paste->formats(); } - virtual QVariant retrieveData(const QString &format, QMetaType type) const { return paste->retrieveData(format, type); } + QStringList formats() const override { return paste->formats(); } + QVariant retrieveData(const QString &format, QMetaType) const override + { + return paste->retrieveData(format); + } }; -QMimeData -*QMacPasteboard::mimeData() const +QMimeData *QMacPasteboard::mimeData() const { if (!mime) { mac_mime_source = true; @@ -308,8 +247,7 @@ QMimeData return mime; } -void -QMacPasteboard::setMimeData(QMimeData *mime_src, DataRequestType dataRequestType) +void QMacPasteboard::setMimeData(QMimeData *mime_src, DataRequestType dataRequestType) { if (!paste) return; @@ -320,7 +258,7 @@ QMacPasteboard::setMimeData(QMimeData *mime_src, DataRequestType dataRequestType delete mime; mime = mime_src; - QList<QMacInternalPasteboardMime*> availableConverters = QMacInternalPasteboardMime::all(mime_type); + const QList<QUtiMimeConverter*> availableConverters = QMacMimeRegistry::all(scope); if (mime != nullptr) { clear_helper(); QStringList formats = mime_src->formats(); @@ -328,38 +266,34 @@ QMacPasteboard::setMimeData(QMimeData *mime_src, DataRequestType dataRequestType // QMimeData sub classes reimplementing the formats() might not expose the // temporary "application/x-qt-mime-type-name" mimetype. So check the existence // of this mime type while doing drag and drop. - QString dummyMimeType(QLatin1String("application/x-qt-mime-type-name")); + QString dummyMimeType("application/x-qt-mime-type-name"_L1); if (!formats.contains(dummyMimeType)) { QByteArray dummyType = mime_src->data(dummyMimeType); - if (!dummyType.isEmpty()) { + if (!dummyType.isEmpty()) formats.append(dummyMimeType); - } } - for (int f = 0; f < formats.size(); ++f) { - QString mimeType = formats.at(f); - for (QList<QMacInternalPasteboardMime *>::Iterator it = availableConverters.begin(); it != availableConverters.end(); ++it) { - QMacInternalPasteboardMime *c = (*it); + for (const auto &mimeType : formats) { + for (const auto *c : availableConverters) { // Hack: The Rtf handler converts incoming Rtf to Html. We do // not want to convert outgoing Html to Rtf but instead keep // posting it as Html. Skip the Rtf handler here. - if (c->convertorName() == QLatin1String("Rtf")) + if (c->utiForMime("text/html"_L1) == "public.rtf"_L1) continue; - QString flavor(c->flavorFor(mimeType)); - if (!flavor.isEmpty()) { - QMacMimeData *mimeData = static_cast<QMacMimeData*>(mime_src); + const QString uti(c->utiForMime(mimeType)); + if (!uti.isEmpty()) { - int numItems = c->count(mime_src); + const int numItems = c->count(mime_src); for (int item = 0; item < numItems; ++item) { - const NSInteger itemID = item+1; //id starts at 1 + const NSInteger itemID = item + 1; //id starts at 1 //QMacPasteboard::Promise promise = (dataRequestType == QMacPasteboard::EagerRequest) ? // QMacPasteboard::Promise::eagerPromise(itemID, c, mimeType, mimeData, item) : // QMacPasteboard::Promise::lazyPromise(itemID, c, mimeType, mimeData, item); - QMacPasteboard::Promise promise(itemID, c, mimeType, mimeData, item, dataRequestType); + const QMacPasteboard::Promise promise(itemID, c, mimeType, mime_src, item, dataRequestType); promises.append(promise); - PasteboardPutItemFlavor(paste, reinterpret_cast<PasteboardItemID>(itemID), QCFString(flavor), 0, kPasteboardFlavorNoFlags); - qCDebug(lcQpaClipboard, " - adding %ld %s [%s] <%s> [%d]", - itemID, qPrintable(mimeType), qPrintable(flavor), qPrintable(c->convertorName()), item); + PasteboardPutItemFlavor(paste, reinterpret_cast<PasteboardItemID>(itemID), QCFString(uti), 0, kPasteboardFlavorNoFlags); + qCDebug(lcQpaClipboard, " - adding %ld %s [%s] [%d]", + itemID, qPrintable(mimeType), qPrintable(uti), item); } } } @@ -367,8 +301,7 @@ QMacPasteboard::setMimeData(QMimeData *mime_src, DataRequestType dataRequestType } } -QStringList -QMacPasteboard::formats() const +QStringList QMacPasteboard::formats() const { if (!paste) return QStringList(); @@ -393,11 +326,11 @@ QMacPasteboard::formats() const const int type_count = CFArrayGetCount(types); for (int i = 0; i < type_count; ++i) { - const QString flavor = QString::fromCFString((CFStringRef)CFArrayGetValueAtIndex(types, i)); - qCDebug(lcQpaClipboard, " -%s", qPrintable(QString(flavor))); - QString mimeType = QMacInternalPasteboardMime::flavorToMime(mime_type, flavor); + const QString uti = QString::fromCFString((CFStringRef)CFArrayGetValueAtIndex(types, i)); + qCDebug(lcQpaClipboard, " -%s", qPrintable(QString(uti))); + const QString mimeType = QMacMimeRegistry::flavorToMime(scope, uti); if (!mimeType.isEmpty() && !ret.contains(mimeType)) { - qCDebug(lcQpaClipboard, " -<%lld> %s [%s]", ret.size(), qPrintable(mimeType), qPrintable(QString(flavor))); + qCDebug(lcQpaClipboard, " -<%lld> %s [%s]", ret.size(), qPrintable(mimeType), qPrintable(QString(uti))); ret << mimeType; } } @@ -405,8 +338,7 @@ QMacPasteboard::formats() const return ret; } -bool -QMacPasteboard::hasFormat(const QString &format) const +bool QMacPasteboard::hasFormat(const QString &format) const { if (!paste) return false; @@ -430,9 +362,9 @@ QMacPasteboard::hasFormat(const QString &format) const const int type_count = CFArrayGetCount(types); for (int i = 0; i < type_count; ++i) { - const QString flavor = QString::fromCFString((CFStringRef)CFArrayGetValueAtIndex(types, i)); - qCDebug(lcQpaClipboard, " -%s [0x%x]", qPrintable(QString(flavor)), mime_type); - QString mimeType = QMacInternalPasteboardMime::flavorToMime(mime_type, flavor); + const QString uti = QString::fromCFString((CFStringRef)CFArrayGetValueAtIndex(types, i)); + qCDebug(lcQpaClipboard, " -%s [0x%x]", qPrintable(uti), uchar(scope)); + QString mimeType = QMacMimeRegistry::flavorToMime(scope, uti); if (!mimeType.isEmpty()) qCDebug(lcQpaClipboard, " - %s", qPrintable(mimeType)); if (mimeType == format) @@ -442,8 +374,7 @@ QMacPasteboard::hasFormat(const QString &format) const return false; } -QVariant -QMacPasteboard::retrieveData(const QString &format, QMetaType) const +QVariant QMacPasteboard::retrieveData(const QString &format) const { if (!paste) return QVariant(); @@ -455,18 +386,17 @@ QMacPasteboard::retrieveData(const QString &format, QMetaType) const return QByteArray(); qCDebug(lcQpaClipboard, "Pasteboard: retrieveData [%s]", qPrintable(format)); - const QList<QMacInternalPasteboardMime *> mimes = QMacInternalPasteboardMime::all(mime_type); - for (int mime = 0; mime < mimes.size(); ++mime) { - QMacInternalPasteboardMime *c = mimes.at(mime); - QString c_flavor = c->flavorFor(format); - if (!c_flavor.isEmpty()) { + const QList<QUtiMimeConverter *> availableConverters = QMacMimeRegistry::all(scope); + for (const auto *c : availableConverters) { + const QString c_uti = c->utiForMime(format); + if (!c_uti.isEmpty()) { // Converting via PasteboardCopyItemFlavorData below will for some UITs result // in newlines mapping to '\r' instead of '\n'. To work around this we shortcut // the conversion via NSPasteboard's NSStringPboardType if possible. - if (c_flavor == QLatin1String("com.apple.traditional-mac-plain-text") - || c_flavor == QLatin1String("public.utf8-plain-text") - || c_flavor == QLatin1String("public.utf16-plain-text")) { - QString str = qt_mac_get_pasteboardString(paste); + if (c_uti == "com.apple.traditional-mac-plain-text"_L1 + || c_uti == "public.utf8-plain-text"_L1 + || c_uti == "public.utf16-plain-text"_L1) { + const QString str = qt_mac_get_pasteboardString(paste); if (!str.isEmpty()) return str; } @@ -484,26 +414,29 @@ QMacPasteboard::retrieveData(const QString &format, QMetaType) const const int type_count = CFArrayGetCount(types); for (int i = 0; i < type_count; ++i) { - CFStringRef flavor = static_cast<CFStringRef>(CFArrayGetValueAtIndex(types, i)); - if (c_flavor == QString::fromCFString(flavor)) { + const CFStringRef uti = static_cast<CFStringRef>(CFArrayGetValueAtIndex(types, i)); + if (c_uti == QString::fromCFString(uti)) { QCFType<CFDataRef> macBuffer; - if (PasteboardCopyItemFlavorData(paste, id, flavor, &macBuffer) == noErr) { - QByteArray buffer((const char *)CFDataGetBytePtr(macBuffer), CFDataGetLength(macBuffer)); + if (PasteboardCopyItemFlavorData(paste, id, uti, &macBuffer) == noErr) { + QByteArray buffer((const char *)CFDataGetBytePtr(macBuffer), + CFDataGetLength(macBuffer)); if (!buffer.isEmpty()) { - qCDebug(lcQpaClipboard, " - %s [%s] (%s)", qPrintable(format), qPrintable(c_flavor), qPrintable(c->convertorName())); + qCDebug(lcQpaClipboard, " - %s [%s]", qPrintable(format), + qPrintable(c_uti)); buffer.detach(); //detach since we release the macBuffer retList.append(buffer); break; //skip to next element } } } else { - qCDebug(lcQpaClipboard, " - NoMatch %s [%s] (%s)", qPrintable(c_flavor), qPrintable(QString::fromCFString(flavor)), qPrintable(c->convertorName())); + qCDebug(lcQpaClipboard, " - NoMatch %s [%s]", qPrintable(c_uti), + qPrintable(QString::fromCFString(uti))); } } } if (!retList.isEmpty()) { - ret = c->convertToMime(format, retList, c_flavor); + ret = c->convertToMime(format, retList, c_uti); return ret; } } @@ -518,15 +451,13 @@ void QMacPasteboard::clear_helper() promises.clear(); } -void -QMacPasteboard::clear() +void QMacPasteboard::clear() { qCDebug(lcQpaClipboard, "PasteBoard: clear!"); clear_helper(); } -bool -QMacPasteboard::sync() const +bool QMacPasteboard::sync() const { if (!paste) return false; diff --git a/src/plugins/platforms/cocoa/qmultitouch_mac.mm b/src/plugins/platforms/cocoa/qmultitouch_mac.mm index 5521b7525c..f818b04acd 100644 --- a/src/plugins/platforms/cocoa/qmultitouch_mac.mm +++ b/src/plugins/platforms/cocoa/qmultitouch_mac.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> @@ -46,10 +10,12 @@ QT_BEGIN_NAMESPACE -QHash<qint64, QCocoaTouch*> QCocoaTouch::_currentTouches; -QHash<quint64, QPointingDevice*> QCocoaTouch::_touchDevices; -QPointF QCocoaTouch::_screenReferencePos; -QPointF QCocoaTouch::_trackpadReferencePos; +using namespace Qt::StringLiterals; + +Q_CONSTINIT QHash<qint64, QCocoaTouch*> QCocoaTouch::_currentTouches; +Q_CONSTINIT QHash<quint64, QPointingDevice*> QCocoaTouch::_touchDevices; +Q_CONSTINIT QPointF QCocoaTouch::_screenReferencePos; +Q_CONSTINIT QPointF QCocoaTouch::_trackpadReferencePos; int QCocoaTouch::_idAssignmentCount = 0; int QCocoaTouch::_touchCount = 0; bool QCocoaTouch::_updateInternalStateOnly = true; @@ -179,7 +145,7 @@ QCocoaTouch::getCurrentTouchPointList(NSEvent *event, bool acceptSingleTouch) // Next: sadly, we need to check that our touch hash is in // sync with cocoa. This is typically not the case after a system - // gesture happend (like a four-finger-swipe to show expose). + // gesture happened (like a four-finger-swipe to show expose). if (_touchCount != _currentTouches.size()) { // Remove all instances, and basically start from scratch: @@ -221,7 +187,7 @@ QPointingDevice *QCocoaTouch::getTouchDevice(QInputDevice::DeviceType type, quin { QPointingDevice *ret = _touchDevices.value(id); if (!ret) { - ret = new QPointingDevice(type == QInputDevice::DeviceType::TouchScreen ? QLatin1String("touchscreen") : QLatin1String("trackpad"), + ret = new QPointingDevice(type == QInputDevice::DeviceType::TouchScreen ? "touchscreen"_L1 : "trackpad"_L1, id, type, QPointingDevice::PointerType::Finger, QInputDevice::Capability::Position | QInputDevice::Capability::NormalizedPosition | diff --git a/src/plugins/platforms/cocoa/qmultitouch_mac_p.h b/src/plugins/platforms/cocoa/qmultitouch_mac_p.h index ff4479fa4c..d47d37729f 100644 --- a/src/plugins/platforms/cocoa/qmultitouch_mac_p.h +++ b/src/plugins/platforms/cocoa/qmultitouch_mac_p.h @@ -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 // // W A R N I N G diff --git a/src/plugins/platforms/cocoa/qnsview.h b/src/plugins/platforms/cocoa/qnsview.h index 526e4c0be2..7f845a5c3b 100644 --- a/src/plugins/platforms/cocoa/qnsview.h +++ b/src/plugins/platforms/cocoa/qnsview.h @@ -1,45 +1,11 @@ -/**************************************************************************** -** -** 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 #ifndef QNSVIEW_H #define QNSVIEW_H +#include <AppKit/NSView.h> + #include <QtCore/private/qcore_mac_p.h> QT_BEGIN_NAMESPACE @@ -66,6 +32,12 @@ QT_DECLARE_NAMESPACED_OBJC_INTERFACE(QNSView, NSView - (void)cancelComposingText; @end +Q_FORWARD_DECLARE_OBJC_CLASS(NSColorSpace); + +@interface QNSView (DrawingAPI) +@property (nonatomic, readonly) NSColorSpace *colorSpace; +@end + @interface QNSView (QtExtras) @property (nonatomic, readonly) QCocoaWindow *platformWindow; @end diff --git a/src/plugins/platforms/cocoa/qnsview.mm b/src/plugins/platforms/cocoa/qnsview.mm index d3f1a211de..48ffa5c1cc 100644 --- a/src/plugins/platforms/cocoa/qnsview.mm +++ b/src/plugins/platforms/cocoa/qnsview.mm @@ -1,46 +1,11 @@ -/**************************************************************************** -** -** 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 <QtGui/qtguiglobal.h> #include <AppKit/AppKit.h> #include <MetalKit/MetalKit.h> +#include <UniformTypeIdentifiers/UTCoreTypes.h> #include "qnsview.h" #include "qcocoawindow.h" @@ -57,6 +22,7 @@ #include <QtCore/QPointer> #include <QtCore/QSet> #include <QtCore/qsysinfo.h> +#include <QtCore/private/qcore_mac_p.h> #include <QtGui/QAccessible> #include <QtGui/QImage> #include <private/qguiapplication_p.h> @@ -69,13 +35,7 @@ #include "qcocoaglcontext.h" #endif #include "qcocoaintegration.h" - -// Private interface -@interface QNSView () -- (BOOL)isTransparentForUserInput; -@property (assign) NSView* previousSuperview; -@property (assign) NSWindow* previousWindow; -@end +#include <QtGui/private/qmacmimeregistry_p.h> @interface QNSView (Drawing) <CALayerDelegate> - (void)initDrawing; @@ -117,6 +77,21 @@ QT_NAMESPACE_ALIAS_OBJC_CLASS(QNSViewMouseMoveHelper); @end @interface QNSView (ComplexText) <NSTextInputClient> +@property (readonly) QObject* focusObject; +@end + +@interface QT_MANGLE_NAMESPACE(QNSViewMenuHelper) : NSObject +- (instancetype)initWithView:(QNSView *)theView; +@end +QT_NAMESPACE_ALIAS_OBJC_CLASS(QNSViewMenuHelper); + +// Private interface +@interface QNSView () +- (BOOL)isTransparentForUserInput; +@property (assign) NSView* previousSuperview; +@property (assign) NSWindow* previousWindow; +@property (retain) QNSViewMenuHelper* menuHelper; +@property (nonatomic, retain) NSColorSpace *colorSpace; @end @implementation QNSView { @@ -136,19 +111,30 @@ QT_NAMESPACE_ALIAS_OBJC_CLASS(QNSViewMouseMoveHelper); // Keys bool m_lastKeyDead; bool m_sendKeyEvent; + bool m_sendKeyEventWithoutText; NSEvent *m_currentlyInterpretedKeyEvent; QSet<quint32> m_acceptedKeyDowns; // Text QString m_composingText; QPointer<QObject> m_composingFocusObject; + NSDraggingContext m_lastSeenContext; } +@synthesize colorSpace = m_colorSpace; + - (instancetype)initWithCocoaWindow:(QCocoaWindow *)platformWindow { if ((self = [super initWithFrame:NSZeroRect])) { m_platformWindow = platformWindow; + // NSViews are by default visible, but QWindows are not. + // We should ideally pick up the actual QWindow state here, + // but QWindowPrivate::setVisible() expects to control the + // order of events tightly, so we need to wait for a call + // to QCocoaWindow::setVisible(). + self.hidden = YES; + self.focusRingType = NSFocusRingTypeNone; self.previousSuperview = nil; @@ -163,6 +149,9 @@ QT_NAMESPACE_ALIAS_OBJC_CLASS(QNSViewMouseMoveHelper); m_lastKeyDead = false; m_sendKeyEvent = false; m_currentlyInterpretedKeyEvent = nil; + m_lastSeenContext = NSDraggingContextWithinApplication; + + self.menuHelper = [[[QNSViewMenuHelper alloc] initWithView:self] autorelease]; } return self; } @@ -288,15 +277,29 @@ QT_NAMESPACE_ALIAS_OBJC_CLASS(QNSViewMouseMoveHelper); return focusWindow; } +/* + Invoked when the view is hidden, either directly, + or in response to an ancestor being hidden. +*/ - (void)viewDidHide { + qCDebug(lcQpaWindow) << "Did hide" << self; + if (!m_platformWindow->isExposed()) return; m_platformWindow->handleExposeEvent(QRegion()); +} + +/* + Invoked when the view is unhidden, either directly, + or in response to an ancestor being unhidden. +*/ +- (void)viewDidUnhide +{ + qCDebug(lcQpaWindow) << "Did unhide" << self; - // Note: setNeedsDisplay is automatically called for - // viewDidUnhide so no reason to override it here. + [self setNeedsDisplay:YES]; } - (BOOL)isTransparentForUserInput @@ -329,7 +332,7 @@ QT_NAMESPACE_ALIAS_OBJC_CLASS(QNSViewMouseMoveHelper); // QWindow activation from QCocoaWindow::windowDidBecomeKey instead. The only // exception is if the window can never become key, in which case we naturally // cannot wait for that to happen. - QWindowSystemInterface::handleWindowActivated<QWindowSystemInterface::SynchronousDelivery>( + QWindowSystemInterface::handleFocusWindowChanged<QWindowSystemInterface::SynchronousDelivery>( [self topLevelWindow], Qt::ActiveWindowFocusReason); } @@ -399,7 +402,7 @@ QT_NAMESPACE_ALIAS_OBJC_CLASS(QNSViewMouseMoveHelper); #include "qnsview_keys.mm" #include "qnsview_complextext.mm" #include "qnsview_menus.mm" -#ifndef QT_NO_ACCESSIBILITY +#if QT_CONFIG(accessibility) #include "qnsview_accessibility.mm" #endif diff --git a/src/plugins/platforms/cocoa/qnsview_accessibility.mm b/src/plugins/platforms/cocoa/qnsview_accessibility.mm index cd16ca2133..e781f21a6c 100644 --- a/src/plugins/platforms/cocoa/qnsview_accessibility.mm +++ b/src/plugins/platforms/cocoa/qnsview_accessibility.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 // This file is included from qnsview.mm, and only used to organize the code @@ -49,6 +13,15 @@ @implementation QNSView (Accessibility) +- (void)activateQtAccessibility +{ + // Activate the Qt accessibility machinery for all entry points + // below that may be triggered by system accessibility queries, + // as otherwise Qt is not aware that the system needs to know + // about all accessibility state changes in Qt. + QCocoaIntegration::instance()->accessibility()->setActive(true); +} + - (id)childAccessibleElement { QCocoaWindow *platformWindow = self.platformWindow; @@ -68,8 +41,7 @@ - (id)accessibilityAttributeValue:(NSString *)attribute { - // activate accessibility updates - QCocoaIntegration::instance()->accessibility()->setActive(true); + [self activateQtAccessibility]; if ([attribute isEqualToString:NSAccessibilityChildrenAttribute]) return NSAccessibilityUnignoredChildrenForOnlyChild([self childAccessibleElement]); @@ -79,11 +51,13 @@ - (id)accessibilityHitTest:(NSPoint)point { + [self activateQtAccessibility]; return [[self childAccessibleElement] accessibilityHitTest:point]; } - (id)accessibilityFocusedUIElement { + [self activateQtAccessibility]; return [[self childAccessibleElement] accessibilityFocusedUIElement]; } diff --git a/src/plugins/platforms/cocoa/qnsview_complextext.mm b/src/plugins/platforms/cocoa/qnsview_complextext.mm index c1e1364f32..d7f8f4baf0 100644 --- a/src/plugins/platforms/cocoa/qnsview_complextext.mm +++ b/src/plugins/platforms/cocoa/qnsview_complextext.mm @@ -1,41 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2021 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) 2021 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 // This file is included from qnsview.mm, and only used to organize the code @@ -43,6 +7,17 @@ // ------------- Text insertion ------------- +- (QObject*)focusObject +{ + // The text input system may still hold a reference to our QNSView, + // even after QCocoaWindow has been destructed, delivering text input + // events to us, so we need to guard for this situation explicitly. + if (!m_platformWindow) + return nullptr; + + return m_platformWindow->window()->focusObject(); +} + /* Inserts the given text, potentially replacing existing text. @@ -88,8 +63,7 @@ } } - QObject *focusObject = m_platformWindow->window()->focusObject(); - if (queryInputMethod(focusObject)) { + if (queryInputMethod(self.focusObject)) { QInputMethodEvent inputMethodEvent; const bool isAttributedString = [text isKindOfClass:NSAttributedString.class]; @@ -111,7 +85,7 @@ inputMethodEvent.setCommitString(commitString, replaceFrom, replaceLength); } - QCoreApplication::sendEvent(focusObject, &inputMethodEvent); + QCoreApplication::sendEvent(self.focusObject, &inputMethodEvent); } m_composingText.clear(); @@ -122,6 +96,9 @@ { Q_UNUSED(sender); + if (!m_platformWindow) + return; + // Depending on the input method, pressing enter may // result in simply dismissing the input method editor, // without confirming the composition. In other cases @@ -148,9 +125,14 @@ KeyEvent newlineEvent(m_currentlyInterpretedKeyEvent ? m_currentlyInterpretedKeyEvent : NSApp.currentEvent); newlineEvent.type = QEvent::KeyPress; - newlineEvent.key = Qt::Key_Return; - newlineEvent.text = QLatin1Char(kReturnCharCode); - newlineEvent.nativeVirtualKey = kVK_Return; + + const bool isEnter = newlineEvent.modifiers & Qt::KeypadModifier; + newlineEvent.key = isEnter ? Qt::Key_Enter : Qt::Key_Return; + newlineEvent.text = isEnter ? QLatin1Char(kEnterCharCode) + : QLatin1Char(kReturnCharCode); + newlineEvent.nativeVirtualKey = isEnter ? quint32(kVK_ANSI_KeypadEnter) + : quint32(kVK_Return); + qCDebug(lcQpaKeys) << "Inserting newline via" << newlineEvent; newlineEvent.sendWindowSystemEvent(m_platformWindow->window()); } @@ -273,7 +255,7 @@ // Update the composition, now that we've computed the replacement range m_composingText = preeditString; - if (QObject *focusObject = m_platformWindow->window()->focusObject()) { + if (QObject *focusObject = self.focusObject) { m_composingFocusObject = focusObject; if (queryInputMethod(focusObject)) { QInputMethodEvent event(preeditString, preeditAttributes); @@ -315,8 +297,7 @@ */ - (NSRange)markedRange { - QObject *focusObject = m_platformWindow->window()->focusObject(); - if (auto queryResult = queryInputMethod(focusObject, Qt::ImAbsolutePosition)) { + if (auto queryResult = queryInputMethod(self.focusObject, Qt::ImAbsolutePosition)) { int absoluteCursorPosition = queryResult.value(Qt::ImAbsolutePosition).toInt(); // The cursor position as reflected by Qt::ImAbsolutePosition is not @@ -351,7 +332,7 @@ << "for focus object" << m_composingFocusObject; if (!m_composingText.isEmpty()) { - QObject *focusObject = m_platformWindow->window()->focusObject(); + QObject *focusObject = self.focusObject; if (queryInputMethod(focusObject)) { QInputMethodEvent e; e.setCommitString(m_composingText); @@ -398,8 +379,20 @@ // pass the originating key event up the responder chain if applicable. qCDebug(lcQpaKeys) << "Trying to perform command" << selector; - if (![self tryToPerform:selector with:self]) + if (![self tryToPerform:selector with:self]) { m_sendKeyEvent = true; + + if (![NSStringFromSelector(selector) hasPrefix:@"insert"]) { + // The text input system determined that the key event was not + // meant for text insertion, and instead asked us to treat it + // as a (possibly noop) command. This typically happens for key + // events with either ⌘ or ⌃, function keys such as F1-F35, + // arrow keys, etc. We reflect that when sending the key event + // later on, by removing the text from the event, so that the + // event does not result in text insertion on the client side. + m_sendKeyEventWithoutText = true; + } + } } // ------------- Various text properties ------------- @@ -412,8 +405,7 @@ */ - (NSRange)selectedRange { - QObject *focusObject = m_platformWindow->window()->focusObject(); - if (auto queryResult = queryInputMethod(focusObject, + if (auto queryResult = queryInputMethod(self.focusObject, Qt::ImCursorPosition | Qt::ImAbsolutePosition | Qt::ImAnchorPosition)) { // Unfortunately the Qt::InputMethodQuery values are all relative @@ -460,8 +452,7 @@ */ - (NSAttributedString *)attributedSubstringForProposedRange:(NSRange)range actualRange:(NSRangePointer)actualRange { - QObject *focusObject = m_platformWindow->window()->focusObject(); - if (auto queryResult = queryInputMethod(focusObject, + if (auto queryResult = queryInputMethod(self.focusObject, Qt::ImAbsolutePosition | Qt::ImTextBeforeCursor | Qt::ImTextAfterCursor)) { const int absoluteCursorPosition = queryResult.value(Qt::ImAbsolutePosition).toInt(); const QString textBeforeCursor = queryResult.value(Qt::ImTextBeforeCursor).toString(); @@ -497,8 +488,8 @@ Q_UNUSED(range); Q_UNUSED(actualRange); - QWindow *window = m_platformWindow->window(); - if (queryInputMethod(window->focusObject())) { + QWindow *window = m_platformWindow ? m_platformWindow->window() : nullptr; + if (window && queryInputMethod(window->focusObject())) { QRect cursorRect = qApp->inputMethod()->cursorRectangle().toRect(); cursorRect.moveBottomLeft(window->mapToGlobal(cursorRect.bottomLeft())); return QCocoaScreen::mapToNative(cursorRect); @@ -514,6 +505,32 @@ return NSNotFound; } +/* + Returns the window level of the text input. + + This allows the input method to place its input panel + above the text input. +*/ +- (NSInteger)windowLevel +{ + // The default level assumed by input methods is NSFloatingWindowLevel, + // but our NSWindow level could be higher than that for many reasons, + // including being set via QWindow::setFlags() or directly on the + // NSWindow, or because we're embedded into a native view hierarchy. + // Return the actual window level to account for this. + auto level = m_platformWindow ? m_platformWindow->nativeWindow().level + : NSNormalWindowLevel; + + // The logic above only covers our own window though. In some cases, + // such as when a completer is active, the text input has a lower + // window level than another window that's also visible, and we don't + // want the input panel to be sandwiched between these two windows. + // Account for this by explicitly using NSPopUpMenuWindowLevel as + // the minimum window level, which corresponds to the highest level + // one can get via QWindow::setFlags(), except for Qt::ToolTip. + return qMax(level, NSPopUpMenuWindowLevel); +} + // ------------- Helper functions ------------- /* diff --git a/src/plugins/platforms/cocoa/qnsview_dragging.mm b/src/plugins/platforms/cocoa/qnsview_dragging.mm index 6ea96ac956..4f7d35a0d6 100644 --- a/src/plugins/platforms/cocoa/qnsview_dragging.mm +++ b/src/plugins/platforms/cocoa/qnsview_dragging.mm @@ -1,41 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2018 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) 2018 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 // This file is included from qnsview.mm, and only used to organize the code @@ -52,12 +16,12 @@ NSPasteboardTypeRTF, NSPasteboardTypeTabularText, NSPasteboardTypeFont, NSPasteboardTypeRuler, NSFileContentsPboardType, NSPasteboardTypeRTFD , NSPasteboardTypeHTML, - NSPasteboardTypeURL, NSPasteboardTypePDF, (NSString *)kUTTypeVCard, - (NSString *)kPasteboardTypeFileURLPromise, (NSString *)kUTTypeInkText, + NSPasteboardTypeURL, NSPasteboardTypePDF, UTTypeVCard.identifier, + (NSString *)kPasteboardTypeFileURLPromise, NSPasteboardTypeMultipleTextSelection, mimeTypeGeneric]]; // Add custom types supported by the application - for (const QString &customType : qt_mac_enabledDraggedTypes()) + for (const QString &customType : QMacMimeRegistry::enabledDraggedTypes()) [supportedTypes addObject:customType.toNSString()]; [self registerForDraggedTypes:supportedTypes]; @@ -81,8 +45,8 @@ static QPoint mapWindowCoordinates(QWindow *source, QWindow *target, QPoint poin - (NSDragOperation)draggingSession:(NSDraggingSession *)session sourceOperationMaskForDraggingContext:(NSDraggingContext)context { Q_UNUSED(session); - Q_UNUSED(context); + m_lastSeenContext = context; QCocoaDrag* nativeDrag = QCocoaIntegration::instance()->drag(); return qt_mac_mapDropActions(nativeDrag->currentDrag()->supportedActions()); } @@ -97,8 +61,11 @@ static QPoint mapWindowCoordinates(QWindow *source, QWindow *target, QPoint poin // // Since Qt already takes care of tracking the keyboard modifiers, we // don't need (or want) Cocoa to filter anything. Instead, we'll let - // the application do the actual filtering. - return YES; + // the application do the actual filtering. But only while dragging + // within application, otherwise ignored modifiers may end up in a + // wrong drop operation executed. + + return m_lastSeenContext == NSDraggingContextWithinApplication; } - (BOOL)wantsPeriodicDraggingUpdates @@ -286,6 +253,8 @@ static QPoint mapWindowCoordinates(QWindow *source, QWindow *target, QPoint poin Q_UNUSED(screenPoint); Q_UNUSED(operation); + m_lastSeenContext = NSDraggingContextWithinApplication; + if (!m_platformWindow) return; diff --git a/src/plugins/platforms/cocoa/qnsview_drawing.mm b/src/plugins/platforms/cocoa/qnsview_drawing.mm index d5e5a14835..bf102e43f8 100644 --- a/src/plugins/platforms/cocoa/qnsview_drawing.mm +++ b/src/plugins/platforms/cocoa/qnsview_drawing.mm @@ -1,41 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2018 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) 2018 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 // This file is included from qnsview.mm, and only used to organize the code @@ -49,6 +13,14 @@ << " QT_MAC_WANTS_LAYER/_q_mac_wantsLayer has no effect."; } + // Pick up and persist requested color space from surface format + const QSurfaceFormat surfaceFormat = m_platformWindow->format(); + if (QColorSpace colorSpace = surfaceFormat.colorSpace(); colorSpace.isValid()) { + NSData *iccData = colorSpace.iccProfile().toNSData(); + self.colorSpace = [[[NSColorSpace alloc] initWithICCProfileData:iccData] autorelease]; + } + + // Trigger creation of the layer self.wantsLayer = YES; } @@ -64,6 +36,12 @@ return YES; } +- (NSColorSpace*)colorSpace +{ + // If no explicit color space was set, use the NSWindow's color space + return m_colorSpace ? m_colorSpace : self.window.colorSpace; +} + // ----------------------- Layer setup ----------------------- - (BOOL)shouldUseMetalLayer @@ -129,12 +107,7 @@ [super setLayer:layer]; - // When adding a view to a view hierarchy the backing properties will change - // which results in updating the contents scale, but in case of switching the - // layer on a view that's already in a view hierarchy we need to manually ensure - // the scale is up to date. - if (self.superview) - [self updateLayerContentsScale]; + [self propagateBackingProperties]; if (self.opaque && lcQpaDrawing().isDebugEnabled()) { // If the view claims to be opaque we expect it to fill the entire @@ -167,8 +140,7 @@ { qCDebug(lcQpaDrawing) << "Backing properties changed for" << self; - if (self.layer) - [self updateLayerContentsScale]; + [self propagateBackingProperties]; // Ideally we would plumb this situation through QPA in a way that lets // clients invalidate their own caches, recreate QBackingStore, etc. @@ -177,8 +149,11 @@ [self setNeedsDisplay:YES]; } -- (void)updateLayerContentsScale +- (void)propagateBackingProperties { + if (!self.layer) + return; + // We expect clients to fill the layer with retina aware content, // based on the devicePixelRatio of the QWindow, so we set the // layer's content scale to match that. By going via devicePixelRatio @@ -189,6 +164,12 @@ auto devicePixelRatio = m_platformWindow->devicePixelRatio(); qCDebug(lcQpaDrawing) << "Updating" << self.layer << "content scale to" << devicePixelRatio; self.layer.contentsScale = devicePixelRatio; + + if ([self.layer isKindOfClass:CAMetalLayer.class]) { + CAMetalLayer *metalLayer = static_cast<CAMetalLayer *>(self.layer); + metalLayer.colorspace = self.colorSpace.CGColorSpace; + qCDebug(lcQpaDrawing) << "Set" << metalLayer << "color space to" << metalLayer.colorspace; + } } /* @@ -214,8 +195,10 @@ - (void)drawRect:(NSRect)dirtyBoundingRect { Q_UNUSED(dirtyBoundingRect); - Q_ASSERT_X(!self.layer, "QNSView", - "The drawRect code path should not be hit when we are layer backed"); + // As we are layer backed we shouldn't really end up here, but AppKit will + // in some cases call this method just because we implement it. + // FIXME: Remove drawRect and switch from displayLayer to updateLayer + qCWarning(lcQpaDrawing) << "[QNSView drawRect] called for layer backed view"; } /* diff --git a/src/plugins/platforms/cocoa/qnsview_gestures.mm b/src/plugins/platforms/cocoa/qnsview_gestures.mm index b92f69c7e5..7c64e3356f 100644 --- a/src/plugins/platforms/cocoa/qnsview_gestures.mm +++ b/src/plugins/platforms/cocoa/qnsview_gestures.mm @@ -1,41 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2018 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) 2018 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 // This file is included from qnsview.mm, and only used to organize the code diff --git a/src/plugins/platforms/cocoa/qnsview_keys.mm b/src/plugins/platforms/cocoa/qnsview_keys.mm index 01c3a4ea0d..abee622e65 100644 --- a/src/plugins/platforms/cocoa/qnsview_keys.mm +++ b/src/plugins/platforms/cocoa/qnsview_keys.mm @@ -1,46 +1,65 @@ -/**************************************************************************** -** -** Copyright (C) 2018 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) 2018 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 // This file is included from qnsview.mm, and only used to organize the code +/* + Determines if the text represents one of the "special keys" on macOS + + As a legacy from OpenStep, macOS reserves the range 0xF700-0xF8FF of the + Unicode private use area for representing function keys on the keyboard: + + http://www.unicode.org/Public/MAPPINGS/VENDORS/APPLE/CORPCHAR.TXT + + https://developer.apple.com/documentation/appkit/nsevent/specialkey + + These code points are not supposed to have any glyphs associated with them, + but since we can't guarantee that the system doesn't have a font that does + provide glyphs for this range (Arial Unicode MS e.g.) we need to filter + the text of our key events up front. +*/ +static bool isSpecialKey(const QString &text) +{ + if (text.length() != 1) + return false; + + const char16_t unicode = text.at(0).unicode(); + if (unicode >= 0xF700 && unicode <= 0xF8FF) + return true; + + return false; +} + +static bool sendAsShortcut(const KeyEvent &keyEvent, QWindow *window) +{ + KeyEvent shortcutEvent = keyEvent; + shortcutEvent.type = QEvent::Shortcut; + qCDebug(lcQpaKeys) << "Trying potential shortcuts in" << window + << "for" << shortcutEvent; + + if (shortcutEvent.sendWindowSystemEvent(window)) { + qCDebug(lcQpaKeys) << "Found matching shortcut; will not send as key event"; + return true; + } + qCDebug(lcQpaKeys) << "No matching shortcuts; continuing with key event delivery"; + return false; +} + @implementation QNSView (Keys) +- (bool)performKeyEquivalent:(NSEvent *)nsevent +{ + // Implemented to handle shortcuts for modified Tab keys, which are + // handled by Cocoa and not delivered to your keyDown implementation. + if (nsevent.type == NSEventTypeKeyDown && m_composingText.isEmpty()) { + const bool ctrlDown = [nsevent modifierFlags] & NSEventModifierFlagControl; + const bool isTabKey = nsevent.keyCode == kVK_Tab; + if (ctrlDown && isTabKey && sendAsShortcut(KeyEvent(nsevent), [self topLevelWindow])) + return YES; + } + return NO; +} + - (bool)handleKeyEvent:(NSEvent *)nsevent { qCDebug(lcQpaKeys) << "Handling" << nsevent; @@ -52,23 +71,20 @@ // We will send a key event unless the input method handles it QBoolBlocker sendKeyEventGuard(m_sendKeyEvent, true); + // Assume we should send key events with text, unless told + // otherwise by doCommandBySelector. + m_sendKeyEventWithoutText = false; + + bool didInterpretKeyEvent = false; + if (keyEvent.type == QEvent::KeyPress) { if (m_composingText.isEmpty()) { - KeyEvent shortcutEvent = keyEvent; - shortcutEvent.type = QEvent::Shortcut; - qCDebug(lcQpaKeys) << "Trying potential shortcuts in" << window - << "for" << shortcutEvent; - - if (shortcutEvent.sendWindowSystemEvent(window)) { - qCDebug(lcQpaKeys) << "Found matching shortcut; will not send as key event"; + if (sendAsShortcut(keyEvent, window)) return true; - } else { - qCDebug(lcQpaKeys) << "No matching shortcuts; continuing with key event delivery"; - } } - QObject *focusObject = m_platformWindow->window()->focusObject(); + QObject *focusObject = m_platformWindow ? m_platformWindow->window()->focusObject() : nullptr; if (m_sendKeyEvent && focusObject) { if (auto queryResult = queryInputMethod(focusObject, Qt::ImHints)) { auto hints = static_cast<Qt::InputMethodHints>(queryResult.value(Qt::ImHints).toUInt()); @@ -97,8 +113,12 @@ qCDebug(lcQpaKeys) << "Interpreting key event for focus object" << focusObject; m_currentlyInterpretedKeyEvent = nsevent; - [self interpretKeyEvents:@[nsevent]]; + if (![self.inputContext handleEvent:nsevent]) { + qCDebug(lcQpaKeys) << "Input context did not consume event"; + m_sendKeyEvent = true; + } m_currentlyInterpretedKeyEvent = 0; + didInterpretKeyEvent = true; // If the last key we sent was dead, then pass the next // key to the IM as well to complete composition. @@ -111,7 +131,10 @@ bool accepted = true; if (m_sendKeyEvent && m_composingText.isEmpty()) { - KeyEvent keyEvent(nsevent); + // Trust text input system on whether to send the event with text or not, + // or otherwise apply heuristics to filter out private use symbols. + if (didInterpretKeyEvent ? m_sendKeyEventWithoutText : isSpecialKey(keyEvent.text)) + keyEvent.text = {}; qCDebug(lcQpaKeys) << "Sending as" << keyEvent; accepted = keyEvent.sendWindowSystemEvent(window); } @@ -243,9 +266,16 @@ KeyEvent::KeyEvent(NSEvent *nsevent) default: break; // Must be manually set } - if (nsevent.type == NSEventTypeKeyDown || nsevent.type == NSEventTypeKeyUp) { + switch (nsevent.type) { + case NSEventTypeKeyDown: + case NSEventTypeKeyUp: + case NSEventTypeFlagsChanged: nativeVirtualKey = nsevent.keyCode; + default: + break; + } + if (nsevent.type == NSEventTypeKeyDown || nsevent.type == NSEventTypeKeyUp) { NSString *charactersIgnoringModifiers = nsevent.charactersIgnoringModifiers; NSString *characters = nsevent.characters; @@ -265,11 +295,7 @@ KeyEvent::KeyEvent(NSEvent *nsevent) key = QAppleKeyMapper::fromCocoaKey(character); } - // Ignore text for the U+F700-U+F8FF range. This is used by Cocoa when - // delivering function keys (e.g. arrow keys, backspace, F1-F35, etc.) - if (!(modifiers & (Qt::ControlModifier | Qt::MetaModifier)) - && (character.unicode() < 0xf700 || character.unicode() > 0xf8ff)) - text = QString::fromNSString(characters); + text = QString::fromNSString(characters); isRepeat = nsevent.ARepeat; } @@ -286,10 +312,9 @@ bool KeyEvent::sendWindowSystemEvent(QWindow *window) const case QEvent::KeyPress: case QEvent::KeyRelease: { static const int count = 1; - static const bool tryShortcutOverride = false; QWindowSystemInterface::handleExtendedKeyEvent(window, timestamp, type, key, modifiers, nativeScanCode, nativeVirtualKey, nativeModifiers, - text, isRepeat, count, tryShortcutOverride); + text, isRepeat, count); // FIXME: Make handleExtendedKeyEvent synchronous return QWindowSystemInterface::flushWindowSystemEvents(); } diff --git a/src/plugins/platforms/cocoa/qnsview_menus.mm b/src/plugins/platforms/cocoa/qnsview_menus.mm index 8cfac5556a..2840936975 100644 --- a/src/plugins/platforms/cocoa/qnsview_menus.mm +++ b/src/plugins/platforms/cocoa/qnsview_menus.mm @@ -1,41 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2018 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) 2018 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 // This file is included from qnsview.mm, and only used to organize the code @@ -45,22 +9,56 @@ #include "qcocoamenu.h" #include "qcocoamenubar.h" -static bool selectorIsCutCopyPaste(SEL selector) +@implementation QNSView (Menus) + +// Qt does not (yet) have a mechanism for propagating generic actions, +// so we can only support actions that originate from a QCocoaNSMenuItem, +// where we can forward the action by emitting QPlatformMenuItem::activated(). +// But waiting for forwardInvocation to check that the sender is a +// QCocoaNSMenuItem is too late, as AppKit has at that point chosen +// our view as the target for the action, and if we can't handle it +// the action will not propagate up the responder chain as it should. +// Instead, we hook in early in the process of determining the target +// via the supplementalTargetForAction API, and if we can support the +// action we forward it to a helper. The helper must be tied to the +// view, as the menu validation logic depends on the view's state. + +- (id)supplementalTargetForAction:(SEL)action sender:(id)sender { - return (selector == @selector(cut:) - || selector == @selector(copy:) - || selector == @selector(paste:) - || selector == @selector(selectAll:)); + qCDebug(lcQpaMenus) << "Resolving action target for" << action << "from" << sender << "via" << self; + + if (qt_objc_cast<QCocoaNSMenuItem *>(sender)) { + // The supplemental target must support the selector, but we + // determine so dynamically, so check here before continuing. + if ([self.menuHelper respondsToSelector:action]) + return self.menuHelper; + } else { + qCDebug(lcQpaMenus) << "Ignoring action for menu item we didn't create"; + } + + return [super supplementalTargetForAction:action sender:sender]; } -@interface QNSView (Menus) -- (void)qt_itemFired:(QCocoaNSMenuItem *)item; @end -@implementation QNSView (Menus) +@interface QNSViewMenuHelper () +@property (assign) QNSView* view; +@end + +@implementation QNSViewMenuHelper + +- (instancetype)initWithView:(QNSView *)theView +{ + if ((self = [super init])) + self.view = theView; + + return self; +} - (BOOL)validateMenuItem:(NSMenuItem*)item { + qCDebug(lcQpaMenus) << "Validating" << item << "for" << self.view; + auto *nativeItem = qt_objc_cast<QCocoaNSMenuItem *>(item); if (!nativeItem) return item.enabled; // FIXME Test with with Qt as plugin or embedded QWindow. @@ -87,7 +85,7 @@ static bool selectorIsCutCopyPaste(SEL selector) } if ((!menuWindow || menuWindow->window() != QGuiApplication::modalWindow()) - && (!menubar || menubar->cocoaWindow() != self.platformWindow)) + && (!menubar || menubar->cocoaWindow() != self.view.platformWindow)) return NO; } @@ -96,41 +94,42 @@ static bool selectorIsCutCopyPaste(SEL selector) - (BOOL)respondsToSelector:(SEL)selector { - // Not exactly true. Both copy: and selectAll: can work on non key views. - if (selectorIsCutCopyPaste(selector)) - return ([NSApp keyWindow] == self.window) && (self.window.firstResponder == self); + // See QCocoaMenuItem::resolveTargetAction() + + if (selector == @selector(cut:) + || selector == @selector(copy:) + || selector == @selector(paste:) + || selector == @selector(selectAll:)) { + // Not exactly true. Both copy: and selectAll: can work on non key views. + return NSApp.keyWindow == self.view.window + && self.view.window.firstResponder == self.view; + } - return [super respondsToSelector:selector]; -} + if (selector == @selector(qt_itemFired:)) + return YES; -- (void)qt_itemFired:(QCocoaNSMenuItem *)item -{ - auto *appDelegate = [QCocoaApplicationDelegate sharedDelegate]; - [appDelegate qt_itemFired:item]; + return [super respondsToSelector:selector]; } - (NSMethodSignature *)methodSignatureForSelector:(SEL)selector { - if (selectorIsCutCopyPaste(selector)) { - NSMethodSignature *itemFiredSign = [super methodSignatureForSelector:@selector(qt_itemFired:)]; - return itemFiredSign; - } + // Double check, in case something has cached that we respond + // to the selector, but the result has changed since then. + if (![self respondsToSelector:selector]) + return nil; - return [super methodSignatureForSelector:selector]; + auto *appDelegate = [QCocoaApplicationDelegate sharedDelegate]; + return [appDelegate methodSignatureForSelector:@selector(qt_itemFired:)]; } - (void)forwardInvocation:(NSInvocation *)invocation { - if (selectorIsCutCopyPaste(invocation.selector)) { - NSObject *sender; - [invocation getArgument:&sender atIndex:2]; - if (auto *nativeItem = qt_objc_cast<QCocoaNSMenuItem *>(sender)) { - [self qt_itemFired:nativeItem]; - return; - } - } - - [super forwardInvocation:invocation]; + NSObject *sender; + [invocation getArgument:&sender atIndex:2]; + qCDebug(lcQpaMenus) << "Forwarding" << invocation.selector << "from" << sender; + Q_ASSERT(qt_objc_cast<QCocoaNSMenuItem *>(sender)); + invocation.selector = @selector(qt_itemFired:); + [invocation invokeWithTarget:[QCocoaApplicationDelegate sharedDelegate]]; } @end diff --git a/src/plugins/platforms/cocoa/qnsview_mouse.mm b/src/plugins/platforms/cocoa/qnsview_mouse.mm index d977867890..2fd57fe68e 100644 --- a/src/plugins/platforms/cocoa/qnsview_mouse.mm +++ b/src/plugins/platforms/cocoa/qnsview_mouse.mm @@ -1,44 +1,10 @@ -/**************************************************************************** -** -** Copyright (C) 2018 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) 2018 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 // This file is included from qnsview.mm, and only used to organize the code +using namespace Qt::StringLiterals; + static const QPointingDevice *pointingDeviceFor(qint64 deviceID) { // macOS will in many cases not report a deviceID (0 value). @@ -60,7 +26,7 @@ static const QPointingDevice *pointingDeviceFor(qint64 deviceID) return primaryDevice; } else { // Register a new device. Name and capabilities may need updating later. - const auto *device = new QPointingDevice(QLatin1String("mouse"), deviceID, + const auto *device = new QPointingDevice("mouse"_L1, deviceID, QInputDevice::DeviceType::Mouse, QPointingDevice::PointerType::Generic, QInputDevice::Capability::Scroll | QInputDevice::Capability::Position, 1, 3, QString(), QPointingDeviceUniqueId(), QCocoaIntegration::instance()); @@ -243,20 +209,19 @@ static const QPointingDevice *pointingDeviceFor(qint64 deviceID) case NSEventTypeOtherMouseUp: return QEvent::NonClientAreaMouseButtonRelease; + case NSEventTypeMouseMoved: case NSEventTypeLeftMouseDragged: case NSEventTypeRightMouseDragged: case NSEventTypeOtherMouseDragged: return QEvent::NonClientAreaMouseMove; default: - break; + Q_UNREACHABLE(); } - - return QEvent::None; }(); qCInfo(lcQpaMouse) << eventType << "at" << qtWindowPoint << "with" << m_frameStrutButtons << "in" << self.window; - QWindowSystemInterface::handleFrameStrutMouseEvent(m_platformWindow->window(), + QWindowSystemInterface::handleMouseEvent(m_platformWindow->window(), timestamp, qtWindowPoint, qtScreenPoint, m_frameStrutButtons, button, eventType); } @end @@ -518,10 +483,6 @@ static const QPointingDevice *pointingDeviceFor(qint64 deviceID) - (void)cursorUpdate:(NSEvent *)theEvent { - // Note: We do not get this callback when moving from a subview that - // uses the legacy cursorRect API, so the cursor is reset to the arrow - // cursor. See rdar://34183708 - if (!NSApp.active) return; @@ -582,6 +543,30 @@ static const QPointingDevice *pointingDeviceFor(qint64 deviceID) [self handleMouseEvent: theEvent]; } +- (BOOL)shouldPropagateMouseEnterExit +{ + Q_ASSERT(m_platformWindow); + + // We send out enter and leave events mainly from mouse move events (mouseMovedImpl), + // but in some case (see mouseEnteredImpl:) we also want to propagate enter/leave + // events from the platform. We only do this for windows that themselves are not + // handled by another parent QWindow. + + if (m_platformWindow->isContentView()) + return true; + + // Windows manually embedded into a native view does not have a QWindow parent + if (m_platformWindow->isEmbedded()) + return true; + + // Windows embedded via fromWinId do, but the parent isn't a QNSView + QPlatformWindow *parentWindow = m_platformWindow->QPlatformWindow::parent(); + if (parentWindow && parentWindow->isForeignWindow()) + return true; + + return false; +} + - (void)mouseEnteredImpl:(NSEvent *)theEvent { Q_UNUSED(theEvent); @@ -605,8 +590,7 @@ static const QPointingDevice *pointingDeviceFor(qint64 deviceID) // in time (s_windowUnderMouse). The latter is also used to also send out enter/leave // events when the application is activated/deactivated. - // Top-level windows generate enter events for sub-windows. - if (!m_platformWindow->isContentView()) + if (![self shouldPropagateMouseEnterExit]) return; QPointF windowPoint; @@ -632,8 +616,7 @@ static const QPointingDevice *pointingDeviceFor(qint64 deviceID) if (!m_platformWindow) return; - // Top-level windows generate leave events for sub-windows. - if (!m_platformWindow->isContentView()) + if (![self shouldPropagateMouseEnterExit]) return; QCocoaWindow *windowToLeave = QCocoaWindow::s_windowUnderMouse; @@ -770,7 +753,7 @@ static const QPointingDevice *pointingDeviceFor(qint64 deviceID) if (theEvent.hasPreciseScrollingDeltas) { auto *devicePriv = QPointingDevicePrivate::get(const_cast<QPointingDevice *>(device)); if (!devicePriv->capabilities.testFlag(QInputDevice::Capability::PixelScroll)) { - devicePriv->name = QLatin1String("trackpad or magic mouse"); + devicePriv->name = "trackpad or magic mouse"_L1; devicePriv->deviceType = QInputDevice::DeviceType::TouchPad; devicePriv->capabilities |= QInputDevice::Capability::PixelScroll; qCDebug(lcInputDevices) << "mouse scrolling: updated capabilities" << device; diff --git a/src/plugins/platforms/cocoa/qnsview_tablet.mm b/src/plugins/platforms/cocoa/qnsview_tablet.mm index 81d90a6fbe..4c6e351b3f 100644 --- a/src/plugins/platforms/cocoa/qnsview_tablet.mm +++ b/src/plugins/platforms/cocoa/qnsview_tablet.mm @@ -1,41 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2020 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) 2020 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 // This file is included from qnsview.mm, and only used to organize the code @@ -70,14 +34,18 @@ Q_GLOBAL_STATIC(QCocoaTabletDeviceMap, devicesInProximity) // We use devicesInProximity because deviceID is typically 0, // so QInputDevicePrivate::fromId() won't work. - const auto *device = devicesInProximity->value(theEvent.deviceID); - if (!device) { - // Error: Unknown tablet device. Qt also gets into this state - // when running on a VM. This appears to be harmless; don't - // print a warning. - return false; + const auto deviceId = theEvent.deviceID; + const auto *device = devicesInProximity->value(deviceId); + if (!device && deviceId == 0) { + // Application started up with stylus in proximity already, so we missed the proximity event? + // Create a generic tablet device for now. + device = tabletToolInstance(theEvent); + devicesInProximity->insert(deviceId, device); } + if (Q_UNLIKELY(!device)) + return false; + bool down = (eventType != NSEventTypeMouseMoved); qreal pressure; diff --git a/src/plugins/platforms/cocoa/qnsview_touch.mm b/src/plugins/platforms/cocoa/qnsview_touch.mm index 7f7847849d..97ed5b7624 100644 --- a/src/plugins/platforms/cocoa/qnsview_touch.mm +++ b/src/plugins/platforms/cocoa/qnsview_touch.mm @@ -1,41 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2018 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) 2018 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 // This file is included from qnsview.mm, and only used to organize the code @@ -61,7 +25,10 @@ Q_LOGGING_CATEGORY(lcQpaTouch, "qt.qpa.input.touch") const NSTimeInterval timestamp = [event timestamp]; const QList<QWindowSystemInterface::TouchPoint> points = QCocoaTouch::getCurrentTouchPointList(event, [self shouldSendSingleTouch]); qCDebug(lcQpaTouch) << "touchesBeganWithEvent" << points << "from device" << Qt::hex << [event deviceID]; - QWindowSystemInterface::handleTouchEvent(m_platformWindow->window(), timestamp * 1000, QCocoaTouch::getTouchDevice(QInputDevice::DeviceType::TouchPad, [event deviceID]), points); + QWindowSystemInterface::handleTouchEvent<QWindowSystemInterface::SynchronousDelivery>( + m_platformWindow->window(), timestamp * 1000, + QCocoaTouch::getTouchDevice(QInputDevice::DeviceType::TouchPad, [event deviceID]), + points); } - (void)touchesMovedWithEvent:(NSEvent *)event @@ -72,7 +39,10 @@ Q_LOGGING_CATEGORY(lcQpaTouch, "qt.qpa.input.touch") const NSTimeInterval timestamp = [event timestamp]; const QList<QWindowSystemInterface::TouchPoint> points = QCocoaTouch::getCurrentTouchPointList(event, [self shouldSendSingleTouch]); qCDebug(lcQpaTouch) << "touchesMovedWithEvent" << points << "from device" << Qt::hex << [event deviceID]; - QWindowSystemInterface::handleTouchEvent(m_platformWindow->window(), timestamp * 1000, QCocoaTouch::getTouchDevice(QInputDevice::DeviceType::TouchPad, [event deviceID]), points); + QWindowSystemInterface::handleTouchEvent<QWindowSystemInterface::SynchronousDelivery>( + m_platformWindow->window(), timestamp * 1000, + QCocoaTouch::getTouchDevice(QInputDevice::DeviceType::TouchPad, [event deviceID]), + points); } - (void)touchesEndedWithEvent:(NSEvent *)event @@ -83,7 +53,10 @@ Q_LOGGING_CATEGORY(lcQpaTouch, "qt.qpa.input.touch") const NSTimeInterval timestamp = [event timestamp]; const QList<QWindowSystemInterface::TouchPoint> points = QCocoaTouch::getCurrentTouchPointList(event, [self shouldSendSingleTouch]); qCDebug(lcQpaTouch) << "touchesEndedWithEvent" << points << "from device" << Qt::hex << [event deviceID]; - QWindowSystemInterface::handleTouchEvent(m_platformWindow->window(), timestamp * 1000, QCocoaTouch::getTouchDevice(QInputDevice::DeviceType::TouchPad, [event deviceID]), points); + QWindowSystemInterface::handleTouchEvent<QWindowSystemInterface::SynchronousDelivery>( + m_platformWindow->window(), timestamp * 1000, + QCocoaTouch::getTouchDevice(QInputDevice::DeviceType::TouchPad, [event deviceID]), + points); } - (void)touchesCancelledWithEvent:(NSEvent *)event @@ -94,7 +67,10 @@ Q_LOGGING_CATEGORY(lcQpaTouch, "qt.qpa.input.touch") const NSTimeInterval timestamp = [event timestamp]; const QList<QWindowSystemInterface::TouchPoint> points = QCocoaTouch::getCurrentTouchPointList(event, [self shouldSendSingleTouch]); qCDebug(lcQpaTouch) << "touchesCancelledWithEvent" << points << "from device" << Qt::hex << [event deviceID]; - QWindowSystemInterface::handleTouchEvent(m_platformWindow->window(), timestamp * 1000, QCocoaTouch::getTouchDevice(QInputDevice::DeviceType::TouchPad, [event deviceID]), points); + QWindowSystemInterface::handleTouchEvent<QWindowSystemInterface::SynchronousDelivery>( + m_platformWindow->window(), timestamp * 1000, + QCocoaTouch::getTouchDevice(QInputDevice::DeviceType::TouchPad, [event deviceID]), + points); } @end diff --git a/src/plugins/platforms/cocoa/qnswindow.h b/src/plugins/platforms/cocoa/qnswindow.h index f7dccb1ab2..8f842eba85 100644 --- a/src/plugins/platforms/cocoa/qnswindow.h +++ b/src/plugins/platforms/cocoa/qnswindow.h @@ -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 #ifndef QNSWINDOW_H #define QNSWINDOW_H @@ -44,6 +8,9 @@ #include <QPointer> #include <QtCore/private/qcore_mac_p.h> +#include <AppKit/NSWindow.h> +#include <AppKit/NSPanel.h> + QT_FORWARD_DECLARE_CLASS(QCocoaWindow) #if defined(__OBJC__) @@ -69,6 +36,8 @@ QT_FORWARD_DECLARE_CLASS(QCocoaWindow) typedef NSWindow<QNSWindowProtocol> QCocoaNSWindow; +QCocoaNSWindow *qnswindow_cast(NSWindow *window); + #else class QCocoaNSWindow; #endif // __OBJC__ diff --git a/src/plugins/platforms/cocoa/qnswindow.mm b/src/plugins/platforms/cocoa/qnswindow.mm index 7c9e0dce2d..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) @@ -94,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" @@ -134,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 @@ -203,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 @@ -271,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]]; @@ -332,8 +290,13 @@ OSStatus CGSClearWindowTags(const CGSConnectionID, const CGSWindowID, int *, int // 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 && window->flags().testFlag(Qt::FramelessWindowHint)) - return [NSColor clearColor]; + 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 diff --git a/src/plugins/platforms/cocoa/qnswindowdelegate.h b/src/plugins/platforms/cocoa/qnswindowdelegate.h index 6d86fb76d2..e5b3c4b3a9 100644 --- a/src/plugins/platforms/cocoa/qnswindowdelegate.h +++ b/src/plugins/platforms/cocoa/qnswindowdelegate.h @@ -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 #ifndef QNSWINDOWDELEGATE_H #define QNSWINDOWDELEGATE_H diff --git a/src/plugins/platforms/cocoa/qnswindowdelegate.mm b/src/plugins/platforms/cocoa/qnswindowdelegate.mm index ecaa14bcf7..1db7772771 100644 --- a/src/plugins/platforms/cocoa/qnswindowdelegate.mm +++ b/src/plugins/platforms/cocoa/qnswindowdelegate.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> @@ -59,9 +23,7 @@ static inline bool isWhiteSpace(const QString &s) static QCocoaWindow *toPlatformWindow(NSWindow *window) { - if ([window conformsToProtocol:@protocol(QNSWindowProtocol)]) - return static_cast<QCocoaNSWindow *>(window).platformWindow; - return nullptr; + return qnswindow_cast(window).platformWindow; } @implementation QNSWindowDelegate |