diff options
Diffstat (limited to 'src')
32 files changed, 829 insertions, 142 deletions
diff --git a/src/android/jar/src/org/qtproject/qt/android/QtAccessibilityDelegate.java b/src/android/jar/src/org/qtproject/qt/android/QtAccessibilityDelegate.java index 1d4ec370d39..93407d52e95 100644 --- a/src/android/jar/src/org/qtproject/qt/android/QtAccessibilityDelegate.java +++ b/src/android/jar/src/org/qtproject/qt/android/QtAccessibilityDelegate.java @@ -241,12 +241,15 @@ class QtAccessibilityDelegate extends View.AccessibilityDelegate return; } - final AccessibilityEvent event = - obtainAccessibilityEvent(AccessibilityEvent.TYPE_ANNOUNCEMENT); + final CharSequence className = getNodeForVirtualViewId(viewId).getClassName(); + final int eventType = + className != null && className.equals("android.widget.ProgressBar") + ? AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED + : AccessibilityEvent.TYPE_ANNOUNCEMENT; + final AccessibilityEvent event = obtainAccessibilityEvent(eventType); event.setEnabled(true); - event.setClassName(getNodeForVirtualViewId(viewId).getClassName()); - + event.setClassName(className); event.setContentDescription(value); if (event.getText().isEmpty() && TextUtils.isEmpty(event.getContentDescription())) { diff --git a/src/corelib/global/qcompilerdetection.qdoc b/src/corelib/global/qcompilerdetection.qdoc index 9b49a1491ab..e64dca74667 100644 --- a/src/corelib/global/qcompilerdetection.qdoc +++ b/src/corelib/global/qcompilerdetection.qdoc @@ -277,6 +277,18 @@ \row \li \c{constexpr} \li C++11 \li yes \li required \row \li \c{constinit} \li C++20 \li no \li required \endtable + + If declaration and definition of a Q_CONSTINIT variable are separate, this + macro goes only on the definition, and not on the declaration: + + \code + class C { + ~~~~ + static int count; // declaration, no Q_CONSTINIT here + }; + ~~~ + Q_CONSTINIT int C::count = 0; // definition + \endcode */ /*! diff --git a/src/corelib/global/qrandom.cpp b/src/corelib/global/qrandom.cpp index dc7d90f4b30..8d545242095 100644 --- a/src/corelib/global/qrandom.cpp +++ b/src/corelib/global/qrandom.cpp @@ -699,7 +699,7 @@ inline QRandomGenerator::SystemGenerator &QRandomGenerator::SystemGenerator::sel Returns the minimum value that QRandomGenerator may ever generate. That is, 0. - \sa max(), QRandomGenerator64::min() + \sa max() */ /*! @@ -708,7 +708,7 @@ inline QRandomGenerator::SystemGenerator &QRandomGenerator::SystemGenerator::sel Returns the maximum value that QRandomGenerator may ever generate. That is, \c {std::numeric_limits<result_type>::max()}. - \sa min(), QRandomGenerator64::max() + \sa min() */ /*! diff --git a/src/corelib/itemmodels/qrangemodel.cpp b/src/corelib/itemmodels/qrangemodel.cpp index b0c6c46b125..f533605c721 100644 --- a/src/corelib/itemmodels/qrangemodel.cpp +++ b/src/corelib/itemmodels/qrangemodel.cpp @@ -7,6 +7,8 @@ #include <QtCore/private/qabstractitemmodel_p.h> +#include <variant> + QT_BEGIN_NAMESPACE class QRangeModelPrivate : QAbstractItemModelPrivate @@ -18,11 +20,95 @@ public: : impl(std::move(impl)) {} -private: std::unique_ptr<QRangeModelImplBase, QRangeModelImplBase::Deleter> impl; friend class QRangeModelImplBase; + static QRangeModelPrivate *get(QRangeModel *model) { return model->d_func(); } + static const QRangeModelPrivate *get(const QRangeModel *model) { return model->d_func(); } + mutable QHash<int, QByteArray> m_roleNames; + QRangeModel::AutoConnectPolicy m_autoConnectPolicy = QRangeModel::AutoConnectPolicy::None; + bool m_dataChangedDispatchBlocked = false; + + static void emitDataChanged(const QModelIndex &index, int role) + { + const auto *model = static_cast<const QRangeModel *>(index.model()); + if (!get(model)->m_dataChangedDispatchBlocked) { + const auto *emitter = QRangeModelImplBase::getImplementation(model); + const_cast<QRangeModelImplBase *>(emitter)->dataChanged(index, index, {role}); + } + } +}; + +struct PropertyChangedHandler +{ + PropertyChangedHandler(const QPersistentModelIndex &index, int role) + : storage{Data{index, role}} + {} + + // move-only + ~PropertyChangedHandler() = default; + PropertyChangedHandler(PropertyChangedHandler &&other) noexcept + : connection(std::move(other.connection)), storage(std::move(other.storage)) + { + Q_ASSERT(std::holds_alternative<Data>(storage)); + // A moved-from handler is essentially a reference to the moved-to + // handler (which lives inside QSlotObject/QCallableObject). This + // way we can update the stored handler with the created connection. + other.storage = this; + } + PropertyChangedHandler &operator=(PropertyChangedHandler &&) = delete; + PropertyChangedHandler(const PropertyChangedHandler &) = delete; + PropertyChangedHandler &operator=(const PropertyChangedHandler &) = delete; + + // we can assign a connection to a moved-from handler to update the + // handler stored in the QSlotObject/QCallableObject. + PropertyChangedHandler &operator=(const QMetaObject::Connection &connection) noexcept + { + Q_ASSERT(std::holds_alternative<PropertyChangedHandler *>(storage)); + std::get<PropertyChangedHandler *>(storage)->connection = connection; + return *this; + } + + void operator()(); + +private: + QMetaObject::Connection connection; + struct Data + { + QPersistentModelIndex index; + int role = -1; + }; + std::variant<PropertyChangedHandler *, Data> storage; +}; + +void PropertyChangedHandler::operator()() +{ + Q_ASSERT(std::holds_alternative<Data>(storage)); + const auto &data = std::get<Data>(storage); + if (!data.index.isValid()) { + if (!QObject::disconnect(connection)) + qWarning() << "Failed to break connection for" << Qt::ItemDataRole(data.role); + } else { + QRangeModelPrivate::emitDataChanged(data.index, data.role); + } +} + +struct ConstPropertyChangedHandler +{ + ConstPropertyChangedHandler(const QModelIndex &index, int role) + : index(index), role(role) + {} + + // move-only + ~ConstPropertyChangedHandler() = default; + ConstPropertyChangedHandler(ConstPropertyChangedHandler &&other) noexcept = default; + + void operator()() { QRangeModelPrivate::emitDataChanged(index, role); } + +private: + QModelIndex index; + int role = -1; }; QRangeModel::QRangeModel(QRangeModelImplBase *impl, QObject *parent) @@ -40,6 +126,96 @@ const QRangeModelImplBase *QRangeModelImplBase::getImplementation(const QRangeMo return model->d_func()->impl.get(); } +QScopedValueRollback<bool> QRangeModelImplBase::blockDataChangedDispatch() +{ + return QScopedValueRollback(m_rangeModel->d_func()->m_dataChangedDispatchBlocked, true); +} + +/*! + \internal + + Using \a metaObject, return a mapping of roles to the matching QMetaProperties. +*/ +QHash<int, QMetaProperty> QRangeModelImplBase::roleProperties(const QAbstractItemModel &model, + const QMetaObject &metaObject) +{ + const auto roles = model.roleNames(); + QHash<int, QMetaProperty> result; + for (auto &&[role, roleName] : roles.asKeyValueRange()) { + if (role == Qt::RangeModelDataRole) + continue; + result[role] = metaObject.property(metaObject.indexOfProperty(roleName)); + } + return result; +} + +template <auto Handler> +static bool connectPropertiesHelper(const QModelIndex &index, QObject *item, QObject *context, + const QHash<int, QMetaProperty> &properties) +{ + if (!item) + return false; + for (auto &&[role, property] : properties.asKeyValueRange()) { + if (property.hasNotifySignal()) { + if (!Handler(index, item, context, role, property)) + return false; + } else { + qWarning() << "Property" << property.name() << "for" << Qt::ItemDataRole(role) + << "has no notify signal"; + } + } + return true; +} + +bool QRangeModelImplBase::connectProperty(const QModelIndex &index, QObject *item, QObject *context, + int role, const QMetaProperty &property) +{ + if (!item) + return false; + PropertyChangedHandler handler{index, role}; + auto connection = property.enclosingMetaObject()->connect(item, property.notifySignal(), + context, std::move(handler)); + if (!connection) { + qWarning() << "Failed to connect to" << item << property.name(); + return false; + } else { + // handler is now in moved-from state, and acts like a reference to + // the handler that is stored in the QSlotObject/QCallableObject. + // This assignment updates the stored handler's connection with the + // QMetaObject::Connection handle, and should look harmless for + // static analyzers. + handler = connection; + } + return true; +} + +bool QRangeModelImplBase::connectProperties(const QModelIndex &index, QObject *item, QObject *context, + const QHash<int, QMetaProperty> &properties) +{ + return connectPropertiesHelper<QRangeModelImplBase::connectProperty>(index, item, context, properties); +} + +bool QRangeModelImplBase::connectPropertyConst(const QModelIndex &index, QObject *item, QObject *context, + int role, const QMetaProperty &property) +{ + if (!item) + return false; + ConstPropertyChangedHandler handler{index, role}; + if (!property.enclosingMetaObject()->connect(item, property.notifySignal(), + context, std::move(handler))) { + qWarning() << "Failed to connect to" << item << property.name(); + return false; + } else { + return true; + } +} + +bool QRangeModelImplBase::connectPropertiesConst(const QModelIndex &index, QObject *item, QObject *context, + const QHash<int, QMetaProperty> &properties) +{ + return connectPropertiesHelper<QRangeModelImplBase::connectPropertyConst>(index, item, context, properties); +} + /*! \class QRangeModel \inmodule QtCore @@ -1142,6 +1318,9 @@ void QRangeModel::setRoleNames(const QHash<int, QByteArray> &names) return; beginResetModel(); d->impl->call<QRangeModelImplBase::InvalidateCaches>(); + if (d->m_autoConnectPolicy != AutoConnectPolicy::None) + d->impl->call<QRangeModelImplBase::SetAutoConnectPolicy>(); + d->m_roleNames = names; endResetModel(); Q_EMIT roleNamesChanged(); @@ -1153,6 +1332,73 @@ void QRangeModel::resetRoleNames() } /*! + \enum QRangeModel::AutoConnectPolicy + \since 6.11 + + This enum defines if and when QRangeModel auto-connects changed-signals for + properties to the \l{QAbstractItemModel::}{dataChanged()} signal of the + model. Only properties that match one of the \l{roleNames()}{role names} + are connected. + + \value None No connections are made automatically. + \value Full The signals for all relevant properties are connected + automatically, for all QObject items. This includes QObject + items that are added to newly inserted rows and columns. + \value OnRead Signals for relevant properties are connected the first time + the model reads the property. + + The memory overhead of making automatic connections can be substantial. A + Full auto-connection does not require any book-keeping in addition to the + connection itself, but each connection takes memory, and connecting all + properties of all objects can be very costly, especially if only a few + properties of a subset of objects will ever change. + + The OnRead connection policy will not connect to objects or properties that + are never read from (for instance, never rendered in a view), but remembering + which connections have been made requires some book-keeping overhead, and + unpredictable memory growth over time. For instance, scrolling down a long + list of items can easily result in thousands of new connections being made. + + \sa autoConnectPolicy, roleNames() +*/ + +/*! + \property QRangeModel::autoConnectPolicy + \brief if and when the model auto-connects to property changed notifications. + + If QRangeModel operates on a data structure that holds the same type of + QObject subclass as its item type, then it can automatically connect the + properties of the QObjects to the dataChanged() signal. This is done for + those properties that match one of the \l{roleNames()}{role names}. + + By default, the value of this property is \l{QRangeModel::AutoConnectPolicy::} + {None}, so no such connections are made. Changing the value of this property + always breaks all existing connections. + + \note Connections are not broken or created if QObjects in the data + structure that QRangeModel operates on are swapped out. + + \sa roleNames() +*/ + +QRangeModel::AutoConnectPolicy QRangeModel::autoConnectPolicy() const +{ + Q_D(const QRangeModel); + return d->m_autoConnectPolicy; +} + +void QRangeModel::setAutoConnectPolicy(QRangeModel::AutoConnectPolicy policy) +{ + Q_D(QRangeModel); + if (d->m_autoConnectPolicy == policy) + return; + + d->m_autoConnectPolicy = policy; + d->impl->call<QRangeModelImplBase::SetAutoConnectPolicy>(); + Q_EMIT autoConnectPolicyChanged(); +} + +/*! \reimp */ void QRangeModel::sort(int column, Qt::SortOrder order) diff --git a/src/corelib/itemmodels/qrangemodel.h b/src/corelib/itemmodels/qrangemodel.h index aacaf0f9e5a..d15f40d37a9 100644 --- a/src/corelib/itemmodels/qrangemodel.h +++ b/src/corelib/itemmodels/qrangemodel.h @@ -16,8 +16,17 @@ class Q_CORE_EXPORT QRangeModel : public QAbstractItemModel Q_OBJECT Q_PROPERTY(QHash<int, QByteArray> roleNames READ roleNames WRITE setRoleNames RESET resetRoleNames NOTIFY roleNamesChanged FINAL) + Q_PROPERTY(AutoConnectPolicy autoConnectPolicy READ autoConnectPolicy WRITE setAutoConnectPolicy + NOTIFY autoConnectPolicyChanged FINAL) public: + enum class AutoConnectPolicy { + None, + Full, + OnRead, + }; + Q_ENUM(AutoConnectPolicy) + enum class RowCategory { Default, MultiRoleItem, @@ -100,8 +109,12 @@ public: Qt::DropActions supportedDragActions() const override; Qt::DropActions supportedDropActions() const override; + AutoConnectPolicy autoConnectPolicy() const; + void setAutoConnectPolicy(AutoConnectPolicy policy); + Q_SIGNALS: void roleNamesChanged(); + void autoConnectPolicyChanged(); protected Q_SLOTS: void resetInternalData() override; @@ -128,6 +141,10 @@ void QRangeModelImplBase::changePersistentIndexList(const QModelIndexList &from, { m_rangeModel->changePersistentIndexList(from, to); } +QHash<int, QByteArray> QRangeModelImplBase::roleNames() const +{ + return m_rangeModel->roleNames(); +} void QRangeModelImplBase::dataChanged(const QModelIndex &from, const QModelIndex &to, const QList<int> &roles) { @@ -196,6 +213,11 @@ const QAbstractItemModel &QRangeModelImplBase::itemModel() const return *m_rangeModel; } +QRangeModelImplBase::AutoConnectPolicy QRangeModelImplBase::autoConnectPolicy() const +{ + return QRangeModelImplBase::AutoConnectPolicy(m_rangeModel->autoConnectPolicy()); +} + // Helper templates that we can forward declare in the _impl header, // where QRangeModel is not yet defined. namespace QRangeModelDetails diff --git a/src/corelib/itemmodels/qrangemodel_impl.h b/src/corelib/itemmodels/qrangemodel_impl.h index c2f473542d7..a635dd69379 100644 --- a/src/corelib/itemmodels/qrangemodel_impl.h +++ b/src/corelib/itemmodels/qrangemodel_impl.h @@ -20,6 +20,8 @@ #include <QtCore/qmetaobject.h> #include <QtCore/qvariant.h> #include <QtCore/qmap.h> +#include <QtCore/qscopedvaluerollback.h> +#include <QtCore/qset.h> #include <algorithm> #include <functional> @@ -856,7 +858,7 @@ namespace QRangeModelDetails static constexpr bool is_default = is_any_of<protocol, ListProtocol, TableProtocol, DefaultTreeProtocol>(); }; - template <bool cacheProperties> + template <bool cacheProperties, bool itemsAreQObjects> struct PropertyData { static constexpr bool cachesProperties = false; @@ -864,7 +866,7 @@ namespace QRangeModelDetails }; template <> - struct PropertyData<true> + struct PropertyData<true, false> { static constexpr bool cachesProperties = true; mutable QHash<int, QMetaProperty> properties; @@ -875,6 +877,32 @@ namespace QRangeModelDetails } }; + template <> + struct PropertyData<true, true> : PropertyData<true, false> + { + struct Connection { + QObject *sender; + int role; + + friend bool operator==(const Connection &lhs, const Connection &rhs) noexcept + { + return lhs.sender == rhs.sender && lhs.role == rhs.role; + } + friend size_t qHash(const Connection &c, size_t seed) noexcept + { + return qHashMulti(seed, c.sender, c.role); + } + }; + + QObject *context = nullptr; + mutable QSet<Connection> connections; + + void invalidateCaches() + { + properties.clear(); + } + }; + // The storage of the model data. We might store it as a pointer, or as a // (copied- or moved-into) value (or smart pointer). But we always return a // raw pointer. @@ -899,7 +927,8 @@ namespace QRangeModelDetails template <typename ModelStorage, typename ItemType> struct ModelData : Storage<ModelStorage>, - PropertyData<has_metaobject_v<ItemType>> + PropertyData<has_metaobject_v<ItemType>, + std::is_base_of_v<QObject, std::remove_pointer_t<ItemType>>> { using WrappedStorage = Storage<wrapped_t<ModelStorage>>; using iterator = typename WrappedStorage::iterator; @@ -969,8 +998,14 @@ protected: } public: - // overridable prototypes (quasi-pure-virtual methods) + // keep in sync with QRangeModel::AutoConnectPolicy + enum class AutoConnectPolicy { + None, + Full, + OnRead, + }; + // overridable prototypes (quasi-pure-virtual methods) void invalidateCaches(); bool setHeaderData(int section, Qt::Orientation orientation, const QVariant &data, int role); bool setData(const QModelIndex &index, const QVariant &data, int role); @@ -991,10 +1026,11 @@ public: QVariant headerData(int section, Qt::Orientation orientation, int role) const; QVariant data(const QModelIndex &index, int role) const; QMap<int, QVariant> itemData(const QModelIndex &index) const; - QHash<int, QByteArray> roleNames() const; + inline QHash<int, QByteArray> roleNames() const; QModelIndex parent(const QModelIndex &child) const; void multiData(const QModelIndex &index, QModelRoleDataSpan roleDataSpan) const; + void setAutoConnectPolicy(); // bindings for overriding @@ -1023,6 +1059,7 @@ public: // 6.11 using MultiData = Method<&Self::multiData>; + using SetAutoConnectPolicy = Method<&Self::setAutoConnectPolicy>; template <typename C> using MethodTemplates = std::tuple< @@ -1048,7 +1085,8 @@ public: typename C::Data, typename C::ItemData, typename C::RoleNames, - typename C::MultiData + typename C::MultiData, + typename C::SetAutoConnectPolicy >; static Q_CORE_EXPORT QRangeModelImplBase *getImplementation(QRangeModel *model); @@ -1056,6 +1094,8 @@ public: private: friend class QRangeModelPrivate; + friend struct PropertyChangedHandler; + QRangeModel *m_rangeModel; protected: @@ -1081,6 +1121,7 @@ protected: inline bool beginMoveRows(const QModelIndex &sourceParent, int sourceFirst, int sourceLast, const QModelIndex &destParent, int destRow); inline void endMoveRows(); + inline AutoConnectPolicy autoConnectPolicy() const; inline QAbstractItemModel &itemModel(); inline const QAbstractItemModel &itemModel() const; @@ -1088,6 +1129,19 @@ protected: Q_CORE_EXPORT static QHash<int, QByteArray> roleNamesForMetaObject(const QAbstractItemModel &model, const QMetaObject &metaObject); Q_CORE_EXPORT static QHash<int, QByteArray> roleNamesForSimpleType(); + + Q_CORE_EXPORT QScopedValueRollback<bool> blockDataChangedDispatch(); + + Q_CORE_EXPORT static QHash<int, QMetaProperty> roleProperties(const QAbstractItemModel &model, + const QMetaObject &metaObject); + Q_CORE_EXPORT static bool connectProperty(const QModelIndex &index, QObject *item, QObject *context, + int role, const QMetaProperty &property); + Q_CORE_EXPORT static bool connectPropertyConst(const QModelIndex &index, QObject *item, QObject *context, + int role, const QMetaProperty &property); + Q_CORE_EXPORT static bool connectProperties(const QModelIndex &index, QObject *item, QObject *context, + const QHash<int, QMetaProperty> &properties); + Q_CORE_EXPORT static bool connectPropertiesConst(const QModelIndex &index, QObject *item, QObject *context, + const QHash<int, QMetaProperty> &properties); }; template <typename Structure, typename Range, @@ -1161,6 +1215,16 @@ protected: QRangeModelDetails::is_owning_or_raw_pointer<row_type>(); static constexpr int static_column_count = QRangeModelDetails::static_size_v<row_type>; static constexpr bool one_dimensional_range = static_column_count == 0; + static constexpr bool itemsAreQObjects = std::is_base_of_v<QObject, std::remove_pointer_t< + typename row_traits::item_type>>; + + auto maybeBlockDataChangedDispatch() + { + if constexpr (itemsAreQObjects) + return this->blockDataChangedDispatch(); + else + return false; + } // A row might be a value (or range of values), or a pointer. // row_ptr is always a pointer, and const_row_ptr is a pointer to const. @@ -1202,6 +1266,8 @@ protected: || std::is_copy_constructible_v<row_type>, "The range holding a move-only row-type must support insert(pos, start, end)"); + using AutoConnectPolicy = typename Ancestor::AutoConnectPolicy; + public: static constexpr bool isMutable() { @@ -1341,8 +1407,9 @@ public: { QMap<int, QVariant> result; bool tried = false; - const auto readItemData = [this, &result, &tried](const auto &value){ + const auto readItemData = [this, &index, &result, &tried](const auto &value){ Q_UNUSED(this); + Q_UNUSED(index); using value_type = q20::remove_cvref_t<decltype(value)>; using multi_role = QRangeModelDetails::is_multi_role<value_type>; using wrapped_value_type = QRangeModelDetails::wrapped_t<value_type>; @@ -1392,7 +1459,7 @@ public: const int role = *it; if (role == Qt::RangeModelDataRole) continue; - QVariant data = readRole(role, QRangeModelDetails::pointerTo(value)); + QVariant data = readRole(index, role, QRangeModelDetails::pointerTo(value)); if (data.isValid()) result[role] = std::move(data); } @@ -1471,7 +1538,7 @@ public: else roleData.setData(QVariant::fromValue(QRangeModelDetails::pointerTo(value))); } else { - roleData.setData(readRole(roleData.role(), + roleData.setData(readRole(index, roleData.role(), QRangeModelDetails::pointerTo(value))); } } @@ -1518,6 +1585,8 @@ public: ? QList<int>{} : QList<int>{role}); } }); + // we emit dataChanged at the end, block dispatches from auto-connected properties + [[maybe_unused]] auto dataChangedBlocker = maybeBlockDataChangedDispatch(); const auto writeData = [this, column = index.column(), &data, role](auto &&target) -> bool { using value_type = q20::remove_cvref_t<decltype(target)>; @@ -1641,6 +1710,8 @@ public: if (success) Q_EMIT this->dataChanged(index, index, data.keys()); }); + // we emit dataChanged at the end, block dispatches from auto-connected properties + [[maybe_unused]] auto dataChangedBlocker = maybeBlockDataChangedDispatch(); bool tried = false; auto writeItemData = [this, &tried, &data](auto &target) -> bool { @@ -1789,6 +1860,81 @@ public: return this->itemModel().QAbstractItemModel::roleNames(); } + template <typename Fn> + bool forEachColumn(const row_type &row, int rowIndex, const QModelIndex &parent, Fn &&fn) const + { + const auto &model = this->itemModel(); + if constexpr (one_dimensional_range) { + return fn(model.index(rowIndex, 0, parent), QRangeModelDetails::pointerTo(row)); + } else if constexpr (dynamicColumns()) { + int columnIndex = -1; + return std::all_of(row.begin(), row.end(), [&](const auto &item) { + return fn(model.index(rowIndex, ++columnIndex, parent), + QRangeModelDetails::pointerTo(item)); + }); + } else { // tuple-like + int columnIndex = -1; + return std::apply([fn = std::forward<Fn>(fn), &model, rowIndex, &columnIndex, parent] + (const auto &...item) { + return (fn(model.index(rowIndex, ++columnIndex, parent), + QRangeModelDetails::pointerTo(item)) && ...); + }, row); + } + } + + bool autoConnectPropertiesInRow(const row_type &row, int rowIndex, const QModelIndex &parent) const + { + if (!QRangeModelDetails::isValid(row)) + return false; + return forEachColumn(row, rowIndex, parent, [this](const QModelIndex &index, QObject *item) { + if constexpr (isMutable()) + return Self::connectProperties(index, item, m_data.context, m_data.properties); + else + return Self::connectPropertiesConst(index, item, m_data.context, m_data.properties); + }); + } + + void clearConnectionInRow(const row_type &row, int rowIndex, const QModelIndex &parent) const + { + if (!QRangeModelDetails::isValid(row)) + return; + forEachColumn(row, rowIndex, parent, [this](const QModelIndex &, QObject *item) { + m_data.connections.removeIf([item](const auto &connection) { + return connection.sender == item; + }); + return true; + }); + } + + void setAutoConnectPolicy() + { + // will be 'void' if columns don't all have the same type + if constexpr (itemsAreQObjects) { + using item_type = std::remove_pointer_t<typename row_traits::item_type>; + + delete m_data.context; + m_data.connections = {}; + switch (this->autoConnectPolicy()) { + case AutoConnectPolicy::None: + m_data.context = nullptr; + break; + case AutoConnectPolicy::Full: + m_data.context = new QObject(&this->itemModel()); + m_data.properties = QRangeModelImplBase::roleProperties(this->itemModel(), + item_type::staticMetaObject); + if (!m_data.properties.isEmpty()) + that().autoConnectPropertiesImpl(); + break; + case AutoConnectPolicy::OnRead: + m_data.context = new QObject(&this->itemModel()); + break; + } + } else { +#ifndef QT_NO_DEBUG + qWarning("All items in the range must be QObject subclasses"); +#endif + } + } template <typename InsertFn> bool doInsertColumns(int column, int count, const QModelIndex &parent, InsertFn insertFn) @@ -1808,6 +1954,23 @@ public: this->endInsertColumns(); + // endInsertColumns emits columnsInserted, at which point clients might + // have populated the new columns with objects (if the columns aren't objects + // themselves). + if constexpr (itemsAreQObjects) { + if (m_data.context && this->autoConnectPolicy() == AutoConnectPolicy::Full) { + for (int r = 0; r < that().rowCount(parent); ++r) { + for (int c = column; c < column + count; ++c) { + const QModelIndex index = that().index(r, c, parent); + writeAt(index, [this, &index](QObject *item){ + return Self::connectProperties(index, item, + m_data.context, m_data.properties); + }); + } + } + } + } + return true; } @@ -1833,6 +1996,22 @@ public: if (!children) return false; + if constexpr (itemsAreQObjects) { + if (m_data.context && this->autoConnectPolicy() == AutoConnectPolicy::OnRead) { + for (int r = 0; r < that().rowCount(parent); ++r) { + for (int c = column; c < column + count; ++c) { + const QModelIndex index = that().index(r, c, parent); + writeAt(index, [this](QObject *item){ + m_data.connections.removeIf([item](const auto &connection) { + return connection.sender == item; + }); + return true; + }); + } + } + } + } + this->beginRemoveColumns(parent, column, column + count - 1); for (auto &child : *children) { const auto start = QRangeModelDetails::pos(child, column); @@ -1896,6 +2075,19 @@ public: this->endInsertRows(); + // endInsertRows emits rowsInserted, at which point clients might + // have populated the new row with objects (if the rows aren't objects + // themselves). + if constexpr (itemsAreQObjects) { + if (m_data.context && this->autoConnectPolicy() == AutoConnectPolicy::Full) { + const auto begin = QRangeModelDetails::pos(children, row); + const auto end = std::next(begin, count); + int rowIndex = row; + for (auto it = begin; it != end; ++it, ++rowIndex) + autoConnectPropertiesInRow(*it, rowIndex, parent); + } + } + return true; } @@ -1933,6 +2125,16 @@ public: if (!children) return false; + if constexpr (itemsAreQObjects) { + if (m_data.context && this->autoConnectPolicy() == AutoConnectPolicy::OnRead) { + const auto begin = QRangeModelDetails::pos(children, row); + const auto end = std::next(begin, count); + int rowIndex = row; + for (auto it = begin; it != end; ++it, ++rowIndex) + clearConnectionInRow(*it, rowIndex, parent); + } + } + this->beginRemoveRows(parent, row, row + count - 1); [[maybe_unused]] bool callEndRemoveColumns = false; if constexpr (dynamicColumns()) { @@ -2041,6 +2243,8 @@ public: using MoveRows = Override<QRangeModelImplBase::MoveRows, &Self::moveRows>; using MultiData = Override<QRangeModelImplBase::MultiData, &Self::multiData>; + using SetAutoConnectPolicy = Override<QRangeModelImplBase::SetAutoConnectPolicy, + &Self::setAutoConnectPolicy>; protected: ~QRangeModelImpl() @@ -2211,23 +2415,37 @@ protected: } template <typename ItemType> - QVariant readRole(int role, ItemType *gadget) const + QVariant readRole(const QModelIndex &index, int role, ItemType *gadget) const { using item_type = std::remove_pointer_t<ItemType>; QVariant result; QMetaProperty prop = roleProperty<item_type>(role); - if (!prop.isValid() && role == Qt::EditRole) + if (!prop.isValid() && role == Qt::EditRole) { + role = Qt::DisplayRole; prop = roleProperty<item_type>(Qt::DisplayRole); + } - if (prop.isValid()) + if (prop.isValid()) { + if constexpr (itemsAreQObjects) { + const typename ModelData::Connection connection = {gadget, role}; + if (prop.hasNotifySignal() && this->autoConnectPolicy() == AutoConnectPolicy::OnRead + && !m_data.connections.contains(connection)) { + if constexpr (isMutable()) + Self::connectProperty(index, gadget, m_data.context, role, prop); + else + Self::connectPropertyConst(index, gadget, m_data.context, role, prop); + m_data.connections.insert(connection); + } + } result = readProperty(prop, gadget); + } return result; } template <typename ItemType> - QVariant readRole(int role, const ItemType &gadget) const + QVariant readRole(const QModelIndex &index, int role, const ItemType &gadget) const { - return readRole(role, &gadget); + return readRole(index, role, &gadget); } template <typename ItemType> @@ -2608,6 +2826,28 @@ protected: } } + bool autoConnectPropertiesRange(const range_type &range, const QModelIndex &parent) const + { + int rowIndex = 0; + for (const auto &row : range) { + if (!this->autoConnectPropertiesInRow(row, rowIndex, parent)) + return false; + Q_ASSERT(QRangeModelDetails::isValid(row)); + const auto &children = this->protocol().childRows(QRangeModelDetails::refTo(row)); + if (!autoConnectPropertiesRange(children, + this->itemModel().index(rowIndex, 0, parent))) { + return false; + } + ++rowIndex; + } + return true; + } + + bool autoConnectPropertiesImpl() const + { + return autoConnectPropertiesRange(*this->m_data.model(), {}); + } + decltype(auto) rowDataImpl(const QModelIndex &index) const { const_row_ptr parentRow = static_cast<const_row_ptr>(index.constInternalPointer()); @@ -2818,6 +3058,17 @@ protected: void resetParentInChildren(range_type *) { } + + bool autoConnectPropertiesImpl() const + { + bool result = true; + int rowIndex = 0; + for (const auto &row : *this->m_data.model()) { + result &= this->autoConnectPropertiesInRow(row, rowIndex, {}); + ++rowIndex; + } + return result; + } }; QT_END_NAMESPACE diff --git a/src/corelib/kernel/qvariant.h b/src/corelib/kernel/qvariant.h index 9b219d089b5..9117c827afe 100644 --- a/src/corelib/kernel/qvariant.h +++ b/src/corelib/kernel/qvariant.h @@ -377,7 +377,7 @@ public: >::value) : QVariant(std::in_place, QMetaType::fromType<q20::remove_cvref_t<T>>()) { - char *data = static_cast<char *>(const_cast<void *>(constData())); + void *data = const_cast<void *>(constData()); new (data) T(il, std::forward<Args>(args)...); } diff --git a/src/corelib/tools/qmultimap.qdoc b/src/corelib/tools/qmultimap.qdoc index fdf65f28876..dc705fdb2eb 100644 --- a/src/corelib/tools/qmultimap.qdoc +++ b/src/corelib/tools/qmultimap.qdoc @@ -457,7 +457,7 @@ Returns the number of items associated with key \a key. - \sa contains(), QMultiMap::count() + \sa contains(), QMultiMap::count(const Key &key, const T &value) */ /*! \fn template <class Key, class T> qsizetype QMultiMap<Key, T>::count(const Key &key, const T &value) const diff --git a/src/gui/image/qicon.h b/src/gui/image/qicon.h index 1e16f42626b..f4b50b07d8a 100644 --- a/src/gui/image/qicon.h +++ b/src/gui/image/qicon.h @@ -265,6 +265,8 @@ private: friend Q_GUI_EXPORT QDataStream &operator>>(QDataStream &, QIcon &); #endif + friend class QIconPrivate; + public: typedef QIconPrivate * DataPtr; inline DataPtr &data_ptr() { return d; } diff --git a/src/gui/image/qicon_p.h b/src/gui/image/qicon_p.h index 60985d538dc..29d88f8f222 100644 --- a/src/gui/image/qicon_p.h +++ b/src/gui/image/qicon_p.h @@ -34,6 +34,11 @@ public: delete engine; } + static QIconPrivate *get(QIcon *icon) { return icon->d; } + static const QIconPrivate *get(const QIcon *icon) { return icon->d; } + + enum IconEngineHook { PlatformIconHook = 1000 }; + static qreal pixmapDevicePixelRatio(qreal displayDevicePixelRatio, const QSize &requestedSize, const QSize &actualSize); QIconEngine *engine; diff --git a/src/gui/kernel/qevent.cpp b/src/gui/kernel/qevent.cpp index 7b1e76cc78f..a95f94dea7e 100644 --- a/src/gui/kernel/qevent.cpp +++ b/src/gui/kernel/qevent.cpp @@ -4211,7 +4211,8 @@ QDebug operator<<(QDebug dbg, const QEvent *e) const Qt::MouseButtons buttons = spe->buttons(); dbg << eventClassName(type) << '('; QtDebugUtils::formatQEnum(dbg, type); - dbg << " ts=" << spe->timestamp(); + if (dbg.verbosity() > QDebug::DefaultVerbosity) + dbg << " ts=" << spe->timestamp(); if (isMouse) { if (type != QEvent::MouseMove && type != QEvent::NonClientAreaMouseMove) { dbg << ' '; @@ -4338,6 +4339,8 @@ QDebug operator<<(QDebug dbg, const QEvent *e) const QNativeGestureEvent *ne = static_cast<const QNativeGestureEvent *>(e); dbg << "QNativeGestureEvent("; QtDebugUtils::formatQEnum(dbg, ne->gestureType()); + if (dbg.verbosity() > QDebug::DefaultVerbosity) + dbg << ", ts=" << ne->timestamp(); dbg << ", fingerCount=" << ne->fingerCount() << ", localPos="; QtDebugUtils::formatQPoint(dbg, ne->position()); if (!qIsNull(ne->value())) diff --git a/src/gui/painting/qcoregraphics.mm b/src/gui/painting/qcoregraphics.mm index dfdfa8f84e7..819f3c8e5d2 100644 --- a/src/gui/painting/qcoregraphics.mm +++ b/src/gui/painting/qcoregraphics.mm @@ -11,6 +11,7 @@ #include <QtCore/qcoreapplication.h> #include <QtCore/qoperatingsystemversion.h> #include <QtGui/qcolorspace.h> +#include <QtGui/private/qicon_p.h> #if defined(Q_OS_MACOS) # include <AppKit/AppKit.h> @@ -303,6 +304,26 @@ QImage qt_mac_toQImage(CGImageRef cgImage) return image; } +QImage qt_mac_padToSquareImage(const QImage &image) +{ + if (image.width() == image.height()) + return image; + + const int size = std::max(image.width(), image.height()); + QImage squareImage(size, size, image.format()); + squareImage.setDevicePixelRatio(image.devicePixelRatio()); + squareImage.fill(Qt::transparent); + + QPoint pos((size - image.width()) / (2.0 * image.devicePixelRatio()), + (size - image.height()) / (2.0 * image.devicePixelRatio())); + + QPainter painter(&squareImage); + painter.drawImage(pos, image); + painter.end(); + + return squareImage; +} + #ifdef Q_OS_MACOS QT_END_NAMESPACE @@ -383,6 +404,19 @@ QT_END_NAMESPACE return nsImage; } + ++ (instancetype)internalImageFromQIcon:(const QT_PREPEND_NAMESPACE(QIcon) &)icon +{ + if (icon.isNull()) + return nil; + + // Check if the icon is backed by an NSImage. If so, we can use that directly. + auto *iconPrivate = QIconPrivate::get(&icon); + NSImage *iconImage = nullptr; + iconPrivate->engine->virtual_hook(QIconPrivate::PlatformIconHook, &iconImage); + return iconImage; +} + @end QT_BEGIN_NAMESPACE diff --git a/src/gui/painting/qcoregraphics_p.h b/src/gui/painting/qcoregraphics_p.h index 7bec19ffb98..9d176516d56 100644 --- a/src/gui/painting/qcoregraphics_p.h +++ b/src/gui/painting/qcoregraphics_p.h @@ -66,6 +66,7 @@ QT_END_NAMESPACE withSize:(const QT_PREPEND_NAMESPACE(QSize) &)size withMode:(QT_PREPEND_NAMESPACE(QIcon)::Mode)mode withState:(QT_PREPEND_NAMESPACE(QIcon)::State)state; ++ (instancetype)internalImageFromQIcon:(const QT_PREPEND_NAMESPACE(QIcon) &)icon; @end QT_BEGIN_NAMESPACE #endif // __OBJC__ @@ -74,6 +75,8 @@ QT_BEGIN_NAMESPACE Q_GUI_EXPORT CGImageRef qt_mac_toCGImage(const QImage &qImage); Q_GUI_EXPORT QImage qt_mac_toQImage(CGImageRef image); +Q_GUI_EXPORT QImage qt_mac_padToSquareImage(const QImage &image); + Q_GUI_EXPORT void qt_mac_drawCGImage(CGContextRef inContext, const CGRect *inBounds, CGImageRef inImage); Q_GUI_EXPORT void qt_mac_clip_cg(CGContextRef hd, const QRegion &rgn, CGAffineTransform *orig_xform); diff --git a/src/gui/painting/qdrawhelper_avx2.cpp b/src/gui/painting/qdrawhelper_avx2.cpp index d7496845197..a736b75dab2 100644 --- a/src/gui/painting/qdrawhelper_avx2.cpp +++ b/src/gui/painting/qdrawhelper_avx2.cpp @@ -38,6 +38,7 @@ BYTE_MUL_AVX2(__m256i &pixelVector, __m256i alphaChannel, __m256i colorMask, __m pixelVector = _mm256_blendv_epi8(pixelVectorAG, pixelVectorRB, colorMask); } +#if QT_CONFIG(raster_64bit) inline static void Q_DECL_VECTORCALL BYTE_MUL_RGB64_AVX2(__m256i &pixelVector, __m256i alphaChannel, __m256i colorMask, __m256i half) { @@ -55,6 +56,7 @@ BYTE_MUL_RGB64_AVX2(__m256i &pixelVector, __m256i alphaChannel, __m256i colorMas pixelVectorRB = _mm256_srli_epi32(pixelVectorRB, 16); pixelVector = _mm256_blendv_epi8(pixelVectorAG, pixelVectorRB, colorMask); } +#endif // See INTERPOLATE_PIXEL_255_SSE2 for details. inline static void Q_DECL_VECTORCALL @@ -79,6 +81,7 @@ INTERPOLATE_PIXEL_255_AVX2(__m256i srcVector, __m256i &dstVector, __m256i alphaC dstVector = _mm256_blendv_epi8(finalAG, finalRB, colorMask); } +#if QT_CONFIG(raster_64bit) inline static void Q_DECL_VECTORCALL INTERPOLATE_PIXEL_RGB64_AVX2(__m256i srcVector, __m256i &dstVector, __m256i alphaChannel, __m256i oneMinusAlphaChannel, __m256i colorMask, __m256i half) { @@ -99,6 +102,7 @@ INTERPOLATE_PIXEL_RGB64_AVX2(__m256i srcVector, __m256i &dstVector, __m256i alph finalRB = _mm256_srli_epi32(finalRB, 16); dstVector = _mm256_blendv_epi8(finalAG, finalRB, colorMask); } +#endif // See BLEND_SOURCE_OVER_ARGB32_SSE2 for details. inline static void Q_DECL_VECTORCALL BLEND_SOURCE_OVER_ARGB32_AVX2(quint32 *dst, const quint32 *src, const int length) diff --git a/src/gui/platform/darwin/qappleiconengine.mm b/src/gui/platform/darwin/qappleiconengine.mm index 3228b97fdb0..925135e9621 100644 --- a/src/gui/platform/darwin/qappleiconengine.mm +++ b/src/gui/platform/darwin/qappleiconengine.mm @@ -15,6 +15,7 @@ #include <QtGui/qstylehints.h> #include <QtGui/private/qcoregraphics_p.h> +#include <QtGui/private/qicon_p.h> QT_BEGIN_NAMESPACE @@ -302,6 +303,11 @@ QAppleIconEngine::~QAppleIconEngine() [m_image release]; } +QIcon QAppleIconEngine::fromTheme(const QString &iconName) +{ + return QIcon(new QAppleIconEngine(iconName)); +} + QIconEngine *QAppleIconEngine::clone() const { return new QAppleIconEngine(m_iconName); @@ -476,4 +482,14 @@ void QAppleIconEngine::paint(QPainter *painter, const QRect &rect, QIcon::Mode m #endif } +void QAppleIconEngine::virtual_hook(int hookIdentifier, void *data) +{ + // Expose underlying NSImage so we can pass it on to AppKit + // directly without flattening, preserving the symbol image. + if (hookIdentifier == QIconPrivate::PlatformIconHook) + *static_cast<decltype(m_image)*>(data) = m_image; + else + QIconEngine::virtual_hook(hookIdentifier, data); +} + QT_END_NAMESPACE diff --git a/src/gui/platform/darwin/qappleiconengine_p.h b/src/gui/platform/darwin/qappleiconengine_p.h index 7d20896517b..b71386ca63a 100644 --- a/src/gui/platform/darwin/qappleiconengine_p.h +++ b/src/gui/platform/darwin/qappleiconengine_p.h @@ -31,6 +31,9 @@ class Q_GUI_EXPORT QAppleIconEngine : public QIconEngine public: QAppleIconEngine(const QString &iconName); ~QAppleIconEngine(); + + static QIcon fromTheme(const QString &iconName); + QIconEngine *clone() const override; QString key() const override; QString iconName() override; @@ -44,6 +47,8 @@ public: static QList<QSize> availableIconSizes(double aspectRatio = 1.0); + void virtual_hook(int hookIdentifier, void *data) override; + private: const QString m_iconName; #if defined(Q_OS_MACOS) diff --git a/src/gui/rhi/qrhivulkan.cpp b/src/gui/rhi/qrhivulkan.cpp index 481ffd57b5d..a511eb854cb 100644 --- a/src/gui/rhi/qrhivulkan.cpp +++ b/src/gui/rhi/qrhivulkan.cpp @@ -406,7 +406,9 @@ static inline VmaAllocator toVmaAllocator(QVkAllocator a) QByteArrayList QRhiVulkanInitParams::preferredInstanceExtensions() { return { - QByteArrayLiteral("VK_KHR_get_physical_device_properties2") + QByteArrayLiteral("VK_KHR_get_physical_device_properties2"), + // to silence validation when e.g. on Wayland a surface format's colorspace is VK_COLOR_SPACE_PASS_THROUGH_EXT + QByteArrayLiteral("VK_EXT_swapchain_colorspace") }; } diff --git a/src/plugins/platforms/android/androidjniaccessibility.cpp b/src/plugins/platforms/android/androidjniaccessibility.cpp index 6154f4121d2..4168f818a67 100644 --- a/src/plugins/platforms/android/androidjniaccessibility.cpp +++ b/src/plugins/platforms/android/androidjniaccessibility.cpp @@ -43,6 +43,11 @@ namespace QtAndroidAccessibility static jmethodID m_setRangeInfoMethodID = 0; static jmethodID m_setVisibleToUserMethodID = 0; + static int RANGE_TYPE_INT = 0; + static int RANGE_TYPE_FLOAT = 0; + static int RANGE_TYPE_PERCENT = 0; + static int RANGE_TYPE_INDETERMINATE = 0; + static bool m_accessibilityActivated = false; // This object is needed to schedule the execution of the code that @@ -510,7 +515,6 @@ namespace QtAndroidAccessibility return QStringLiteral("android.widget.RadioButton"); case QAccessible::Role::ProgressBar: return QStringLiteral("android.widget.ProgressBar"); - // Range information need to be filled to announce percentages case QAccessible::Role::SpinBox: return QStringLiteral("android.widget.NumberPicker"); case QAccessible::Role::WebDocument: @@ -715,14 +719,14 @@ namespace QtAndroidAccessibility if (info.hasValue && m_setRangeInfoMethodID) { int valueType = info.currentValue.typeId(); - jint rangeType = 3; // RANGE_TYPE_INDETERMINATE + jint rangeType = RANGE_TYPE_INDETERMINATE; switch (valueType) { case QMetaType::Float: case QMetaType::Double: - rangeType = 1; // RANGE_TYPE_FLOAT + rangeType = RANGE_TYPE_FLOAT; break; case QMetaType::Int: - rangeType = 0; // RANGE_TYPE_INT + rangeType = RANGE_TYPE_INT; break; } @@ -730,7 +734,7 @@ namespace QtAndroidAccessibility float max = info.maxValue.toFloat(); float current = info.currentValue.toFloat(); if (info.role == QAccessible::ProgressBar) { - rangeType = 2; // RANGE_TYPE_PERCENT + rangeType = RANGE_TYPE_PERCENT; current = 100 * (current - min) / (max - min); min = 0.0f; max = 100.0f; @@ -800,6 +804,14 @@ namespace QtAndroidAccessibility return false; \ } +#define CHECK_AND_INIT_STATIC_FIELD(TYPE, VAR, CLASS, FIELD_NAME) \ + if (env.findStaticField<TYPE>(CLASS, FIELD_NAME) == nullptr) { \ + __android_log_print(ANDROID_LOG_FATAL, QtAndroid::qtTagText(), \ + QtAndroid::staticFieldErrorMsgFmt(), FIELD_NAME); \ + return false; \ + } \ + VAR = QJniObject::getStaticField<TYPE>(CLASS, FIELD_NAME); + bool registerNatives(QJniEnvironment &env) { if (!env.registerNativeMethods("org/qtproject/qt/android/QtNativeAccessibility", @@ -829,6 +841,18 @@ namespace QtAndroidAccessibility m_setRangeInfoMethodID, nodeInfoClass, "setRangeInfo", "(Landroid/view/accessibility/AccessibilityNodeInfo$RangeInfo;)V"); + jclass rangeInfoClass = + env->FindClass("android/view/accessibility/AccessibilityNodeInfo$RangeInfo"); + CHECK_AND_INIT_STATIC_FIELD(int, RANGE_TYPE_INT, rangeInfoClass, "RANGE_TYPE_INT"); + CHECK_AND_INIT_STATIC_FIELD(int, RANGE_TYPE_FLOAT, rangeInfoClass, "RANGE_TYPE_FLOAT"); + CHECK_AND_INIT_STATIC_FIELD(int, RANGE_TYPE_PERCENT, rangeInfoClass, "RANGE_TYPE_PERCENT"); + if (QtAndroidPrivate::androidSdkVersion() >= 36) { + CHECK_AND_INIT_STATIC_FIELD(int, RANGE_TYPE_INDETERMINATE, rangeInfoClass, + "RANGE_TYPE_INDETERMINATE"); + } else { + RANGE_TYPE_INDETERMINATE = RANGE_TYPE_FLOAT; + } + return true; } } diff --git a/src/plugins/platforms/android/androidjnimain.cpp b/src/plugins/platforms/android/androidjnimain.cpp index 9059fab757b..90f39f19edb 100644 --- a/src/plugins/platforms/android/androidjnimain.cpp +++ b/src/plugins/platforms/android/androidjnimain.cpp @@ -86,6 +86,7 @@ static AndroidBackendRegister *m_backendRegister = nullptr; static const char m_qtTag[] = "Qt"; static const char m_classErrorMsg[] = "Can't find class \"%s\""; static const char m_methodErrorMsg[] = "Can't find method \"%s%s\""; +static const char m_staticFieldErrorMsg[] = "Can't find static field \"%s\""; Q_CONSTINIT static QBasicAtomicInt startQtAndroidPluginCalled = Q_BASIC_ATOMIC_INITIALIZER(0); @@ -317,6 +318,11 @@ namespace QtAndroid return m_methodErrorMsg; } + const char *staticFieldErrorMsgFmt() + { + return m_staticFieldErrorMsg; + } + const char *qtTagText() { return m_qtTag; diff --git a/src/plugins/platforms/android/androidjnimain.h b/src/plugins/platforms/android/androidjnimain.h index 447fda5035a..1f359423dea 100644 --- a/src/plugins/platforms/android/androidjnimain.h +++ b/src/plugins/platforms/android/androidjnimain.h @@ -66,6 +66,7 @@ namespace QtAndroid const char *classErrorMsgFmt(); const char *methodErrorMsgFmt(); + const char *staticFieldErrorMsgFmt(); const char *qtTagText(); QString deviceName(); diff --git a/src/plugins/platforms/cocoa/qcocoaintegration.mm b/src/plugins/platforms/cocoa/qcocoaintegration.mm index 13f1a19a360..705429e5ccf 100644 --- a/src/plugins/platforms/cocoa/qcocoaintegration.mm +++ b/src/plugins/platforms/cocoa/qcocoaintegration.mm @@ -441,10 +441,22 @@ QPlatformKeyMapper *QCocoaIntegration::keyMapper() const void QCocoaIntegration::setApplicationIcon(const QIcon &icon) const { - // Fall back to a size that looks good on the highest resolution screen available + if (icon.isNull()) { + NSApp.applicationIconImage = nil; + return; + } + + // Request a size that looks good on the highest resolution screen available // for icon engines that don't have an intrinsic size (like SVG). - auto fallbackSize = QSizeF::fromCGSize(NSApp.dockTile.size) * qGuiApp->devicePixelRatio(); - NSApp.applicationIconImage = [NSImage imageFromQIcon:icon withSize:fallbackSize.toSize()]; + const auto dockTitleSize = QSizeF::fromCGSize(NSApp.dockTile.size).toSize(); + auto image = icon.pixmap(dockTitleSize, qGuiApp->devicePixelRatio()).toImage(); + + // The assigned image is scaled by the system to fit into the tile, + // but without taking aspect ratio into account, so let's pad the + // image up front if it's not already square. + image = qt_mac_padToSquareImage(image); + + NSApp.applicationIconImage = [NSImage imageFromQImage:image]; } void QCocoaIntegration::setApplicationBadge(qint64 number) diff --git a/src/plugins/platforms/cocoa/qcocoamenuitem.mm b/src/plugins/platforms/cocoa/qcocoamenuitem.mm index ca43ab421e0..a9a79665f7d 100644 --- a/src/plugins/platforms/cocoa/qcocoamenuitem.mm +++ b/src/plugins/platforms/cocoa/qcocoamenuitem.mm @@ -373,11 +373,21 @@ NSMenuItem *QCocoaMenuItem::sync() m_native.keyEquivalentModifierMask = NSEventModifierFlagCommand; } - const QIcon::Mode mode = m_enabled ? QIcon::Normal : QIcon::Disabled; - const QIcon::State state = m_checked ? QIcon::On : QIcon::Off; - m_native.image = [NSImage imageFromQIcon:m_icon withSize:QSize(m_iconSize, m_iconSize) - withMode:mode - withState:state]; + if (auto *image = [NSImage internalImageFromQIcon:m_icon]) { + // The icon is backed by QAppleIconEngine, in which case we + // want to pass on the underlying NSImage instead of flattening + // to a QImage, as AppKit takes care of requesting a symbol + // configuration that matches the size and look of the menu, + // which we can't replicate otherwise. Note that this ignores + // any possible explicitly set icon size of the menu item. + m_native.image = [[image copy] autorelease]; + } else { + const QIcon::Mode mode = m_enabled ? QIcon::Normal : QIcon::Disabled; + const QIcon::State state = m_checked ? QIcon::On : QIcon::Off; + m_native.image = [NSImage imageFromQIcon:m_icon withSize:QSize(m_iconSize, m_iconSize) + withMode:mode + withState:state]; + } m_native.state = m_checked ? NSControlStateValueOn : NSControlStateValueOff; return m_native; diff --git a/src/plugins/platforms/cocoa/qcocoasystemtrayicon.mm b/src/plugins/platforms/cocoa/qcocoasystemtrayicon.mm index 1505357ee40..d5c5ed8c2e2 100644 --- a/src/plugins/platforms/cocoa/qcocoasystemtrayicon.mm +++ b/src/plugins/platforms/cocoa/qcocoasystemtrayicon.mm @@ -85,6 +85,19 @@ void QCocoaSystemTrayIcon::updateIcon(const QIcon &icon) if (!m_statusItem) return; + if (auto *image = [NSImage internalImageFromQIcon:icon]) { + // The icon is backed by QAppleIconEngine, in which case we + // want to pass on the underlying NSImage instead of flattening + // to a QImage. This preserves the isTemplate property of the + // image, and allows AppKit to size and configure the icon for + // the status bar. We also enable NSVariableStatusItemLength, + // to match the behavior of SwiftUI's MenuBarExtra. + m_statusItem.button.image = [[image copy] autorelease]; + m_statusItem.button.imageScaling = NSImageScaleProportionallyDown; + m_statusItem.length = NSVariableStatusItemLength; + return; + } + // The recommended maximum title bar icon height is 18 points // (device independent pixels). The menu height on past and // current OS X versions is 22 points. Provide some future-proofing @@ -206,7 +219,17 @@ void QCocoaSystemTrayIcon::showMessage(const QString &title, const QString &mess auto *notification = [[NSUserNotification alloc] init]; notification.title = title.toNSString(); notification.informativeText = message.toNSString(); - notification.contentImage = [NSImage imageFromQIcon:icon]; + + // Request a size that looks good on the highest resolution screen available + // for icon engines that don't have an intrinsic size (like SVG). + auto image = icon.pixmap(QSize(64, 64), qGuiApp->devicePixelRatio()).toImage(); + + // The assigned image is scaled by the system to fit into the tile, + // but without taking aspect ratio into account, so let's pad the + // image up front if it's not already square. + image = qt_mac_padToSquareImage(image); + + notification.contentImage = [NSImage imageFromQImage:image]; NSUserNotificationCenter *center = NSUserNotificationCenter.defaultUserNotificationCenter; center.delegate = m_delegate; diff --git a/src/plugins/platforms/cocoa/qcocoatheme.mm b/src/plugins/platforms/cocoa/qcocoatheme.mm index f9094a8f665..52f449db08b 100644 --- a/src/plugins/platforms/cocoa/qcocoatheme.mm +++ b/src/plugins/platforms/cocoa/qcocoatheme.mm @@ -400,9 +400,9 @@ QPixmap QCocoaTheme::standardPixmap(StandardPixmap sp, const QSizeF &size) const iconType = kGenericDocumentIcon; break; case ToolBarHorizontalExtensionButton: - return QIcon::fromTheme("chevron.forward.2").pixmap(size.toSize()); + return QAppleIconEngine::fromTheme("chevron.forward.2").pixmap(size.toSize()); case ToolBarVerticalExtensionButton: - return QIcon::fromTheme("chevron.down.2").pixmap(size.toSize()); + return QAppleIconEngine::fromTheme("chevron.down.2").pixmap(size.toSize()); default: break; } diff --git a/src/plugins/platforms/eglfs/api/qeglfsdeviceintegration.cpp b/src/plugins/platforms/eglfs/api/qeglfsdeviceintegration.cpp index 56fda45e905..ad5c498032c 100644 --- a/src/plugins/platforms/eglfs/api/qeglfsdeviceintegration.cpp +++ b/src/plugins/platforms/eglfs/api/qeglfsdeviceintegration.cpp @@ -28,6 +28,10 @@ #include <sys/ioctl.h> #endif +#if defined(Q_OS_VXWORKS) +#include <fbdev.h> +#endif + #include <private/qfactoryloader_p.h> #include <private/qcore_unix_p.h> @@ -270,6 +274,13 @@ void QEglFSDeviceIntegration::waitForVSync(QPlatformSurface *surface) const if (ioctl(framebuffer, FBIO_WAITFORVSYNC, &arg) == -1) qWarning("Could not wait for vsync."); } +#elif defined(Q_OS_VXWORKS) && defined(FB_IOCTL_VSYNC) + static const bool forceSync = qEnvironmentVariableIntValue("QT_QPA_EGLFS_FORCEVSYNC"); + if (forceSync && framebuffer != -1) { + int arg = 0; + if (ioctl(framebuffer, FB_IOCTL_VSYNC, &arg) == -1) + qWarning("Could not wait for vsync."); + } #endif } diff --git a/src/plugins/platforms/ios/qiosdocumentpickercontroller.mm b/src/plugins/platforms/ios/qiosdocumentpickercontroller.mm index 6ca6554f673..c173aa426fc 100644 --- a/src/plugins/platforms/ios/qiosdocumentpickercontroller.mm +++ b/src/plugins/platforms/ios/qiosdocumentpickercontroller.mm @@ -28,6 +28,19 @@ docTypes = [self computeAllowedFileTypes:results]; } + // FIXME: Handle security scoped URLs instead of copying resource + bool asCopy = [&]{ + switch (fileDialog->options()->fileMode()) { + case QFileDialogOptions::AnyFile: + case QFileDialogOptions::ExistingFile: + case QFileDialogOptions::ExistingFiles: + return true; + default: + // Folders can't be imported + return false; + } + }(); + if (!docTypes.count) { switch (fileDialog->options()->fileMode()) { case QFileDialogOptions::AnyFile: @@ -45,7 +58,7 @@ } } - if (self = [super initForOpeningContentTypes:docTypes]) { + if (self = [super initForOpeningContentTypes:docTypes asCopy:asCopy]) { m_fileDialog = fileDialog; self.modalPresentationStyle = UIModalPresentationFormSheet; self.delegate = self; diff --git a/src/plugins/platforms/wasm/qwasmaccessibility.cpp b/src/plugins/platforms/wasm/qwasmaccessibility.cpp index d25474c9b3a..56a7c466dcd 100644 --- a/src/plugins/platforms/wasm/qwasmaccessibility.cpp +++ b/src/plugins/platforms/wasm/qwasmaccessibility.cpp @@ -377,15 +377,23 @@ emscripten::val QWasmAccessibility::createHtmlElement(QAccessibleInterface *ifac addEventListener(iface, element, "click"); } break; + case QAccessible::Grouping: case QAccessible::CheckBox: { - // QGroupBox uses both CheckBox, and a group with subwidgets - element = document.call<emscripten::val>("createElement", std::string("input")); - setAttribute(element, "type", "checkbox"); - setAttribute(element, "checked", iface->state().checked); - setProperty(element, "indeterminate", iface->state().checkStateMixed); - addEventListener(iface, element, "change"); - if (iface->childCount() > 0) { - auto checkbox = element; + // Three cases: + // 1) role=CheckBox, childCount() == 0 -> Checkbox + // 2) role=CheckBox, childCount() > 0 -> GroupBox w/checkbox + // 3) role=Grouping -> GroupBox w/label + + emscripten::val checkbox = emscripten::val::undefined(); + if (iface->role() == QAccessible::CheckBox) { + checkbox = document.call<emscripten::val>("createElement", std::string("input")); + setAttribute(checkbox, "type", "checkbox"); + setAttribute(checkbox, "checked", iface->state().checked); + setProperty(checkbox, "indeterminate", iface->state().checkStateMixed); + addEventListener(iface, checkbox, "change"); + } + + if (iface->childCount() > 0 || iface->role() == QAccessible::Grouping) { auto label = document.call<emscripten::val>("createElement", std::string("span")); const std::string id = QString::asprintf("lbid%p", iface).toStdString(); @@ -393,11 +401,16 @@ emscripten::val QWasmAccessibility::createHtmlElement(QAccessibleInterface *ifac element = document.call<emscripten::val>("createElement", std::string("div")); element.call<void>("appendChild", label); - element.call<void>("appendChild", checkbox); setAttribute(element, "role", "group"); setAttribute(element, "aria-labelledby", id); - addEventListener(iface, checkbox, "focus"); + + if (!checkbox.isUndefined()) { + element.call<void>("appendChild", checkbox); + addEventListener(iface, checkbox, "focus"); + } + } else { + element = checkbox; } } break; @@ -800,19 +813,25 @@ void QWasmAccessibility::handleCheckBoxUpdate(QAccessibleEvent *event) void QWasmAccessibility::handleGroupBoxUpdate(QAccessibleEvent *event) { QAccessibleInterface *iface = event->accessibleInterface(); - const emscripten::val parent = getHtmlElement(iface); - const emscripten::val label = parent["children"][0]; - const emscripten::val checkbox = parent["children"][1]; + + emscripten::val parent = getHtmlElement(iface); + emscripten::val label = parent["children"][0]; + emscripten::val checkbox = emscripten::val::undefined(); + if (iface->role() == QAccessible::CheckBox) + checkbox = parent["children"][1]; switch (event->type()) { case QAccessible::Focus: case QAccessible::NameChanged: { setProperty(label, "innerText", iface->text(QAccessible::Name).toStdString()); - setAttribute(checkbox, "aria-label", iface->text(QAccessible::Name).toStdString()); + if (!checkbox.isUndefined()) + setAttribute(checkbox, "aria-label", iface->text(QAccessible::Name).toStdString()); } break; case QAccessible::StateChanged: { - setAttribute(checkbox, "checked", iface->state().checked); - setProperty(checkbox, "indeterminate", iface->state().checkStateMixed); + if (!checkbox.isUndefined()) { + setAttribute(checkbox, "checked", iface->state().checked); + setProperty(checkbox, "indeterminate", iface->state().checkStateMixed); + } } break; default: qCDebug(lcQpaAccessibility) << "TODO: implement handleCheckBoxUpdate for event" << event->type(); @@ -1309,6 +1328,9 @@ void QWasmAccessibility::handleUpdateByInterfaceRole(QAccessibleEvent *event) case QAccessible::ScrollBar: handleScrollBarUpdate(event); break; + case QAccessible::Grouping: + handleGroupBoxUpdate(event); + break; default: qCDebug(lcQpaAccessibility) << "TODO: implement notifyAccessibilityUpdate for role" << iface->role(); }; diff --git a/src/plugins/styles/modernwindows/qwindows11style.cpp b/src/plugins/styles/modernwindows/qwindows11style.cpp index c8cd7c26f61..f47682ced67 100644 --- a/src/plugins/styles/modernwindows/qwindows11style.cpp +++ b/src/plugins/styles/modernwindows/qwindows11style.cpp @@ -993,7 +993,8 @@ void QWindows11Style::drawPrimitive(PrimitiveElement element, const QStyleOption const bool isMouseOver = state & State_MouseOver; const bool hasFocus = state & State_HasFocus; - if (isMouseOver && !hasFocus && !highContrastTheme) + const bool isEnabled = state & State_Enabled; + if (isMouseOver && isEnabled && hasFocus && !highContrastTheme) drawRoundedRect(painter, frameRect, Qt::NoPen, winUI3Color(subtleHighlightColor)); } break; @@ -1399,6 +1400,13 @@ void QWindows11Style::drawControl(ControlElement element, const QStyleOption *op const qreal offset = (int(rect.height()) % 2 == 0) ? 0.5f : 0.0f; if (isIndeterminate) { +#if QT_CONFIG(animation) + auto anim = d->animation(option->styleObject); + if (!anim) { + auto anim = new QStyleAnimation(option->styleObject); + anim->setFrameRate(QStyleAnimation::SixtyFps); + d->startAnimation(anim); + } constexpr auto loopDurationMSec = 4000; const auto elapsedTime = std::chrono::time_point_cast<std::chrono::milliseconds>( std::chrono::system_clock::now()); @@ -1406,14 +1414,20 @@ void QWindows11Style::drawControl(ControlElement element, const QStyleOption *op const auto handleCenter = (elapsed % loopDurationMSec) / float(loopDurationMSec); const auto isLongHandle = (elapsed / loopDurationMSec) % 2 == 0; const auto lengthFactor = (isLongHandle ? 33.0f : 25.0f) / 100.0f; +#else + constexpr auto handleCenter = 0.5f; + constexpr auto lengthFactor = 1; +#endif const auto begin = qMax(handleCenter * (1 + lengthFactor) - lengthFactor, 0.0f); const auto end = qMin(handleCenter * (1 + lengthFactor), 1.0f); const auto barBegin = begin * rect.width(); const auto barEnd = end * rect.width(); rect = QRectF(QPointF(rect.left() + barBegin, rect.top()), QPointF(rect.left() + barEnd, rect.bottom())); - const_cast<QWidget *>(widget)->update(); } else { +#if QT_CONFIG(animation) + d->stopAnimation(option->styleObject); +#endif const auto fillPercentage = (float(baropt->progress - baropt->minimum)) / (float(baropt->maximum - baropt->minimum)); rect.setWidth(rect.width() * fillPercentage); @@ -2715,7 +2729,7 @@ void QWindows11Style::drawLineEditFrame(QPainter *p, const QRectF &rect, const Q : winUI3Color(frameColorLight); drawRoundedRect(p, rect, frameCol, Qt::NoBrush); - if (!isEditable) + if (!isEditable || StyleOptionHelper::isDisabled(o)) return; QPainterStateGuard psg(p); diff --git a/src/widgets/accessible/itemviews.cpp b/src/widgets/accessible/itemviews.cpp index 7e3dfbd68db..cc3a230f9b4 100644 --- a/src/widgets/accessible/itemviews.cpp +++ b/src/widgets/accessible/itemviews.cpp @@ -44,7 +44,7 @@ int QAccessibleTable::logicalIndex(const QModelIndex &index) const return -1; #if QT_CONFIG(listview) - if (m_role == QAccessible::List) { + if (role() == QAccessible::List) { if (index.column() != qobject_cast<const QListView*>(view())->modelColumn()) return -1; else @@ -59,35 +59,10 @@ int QAccessibleTable::logicalIndex(const QModelIndex &index) const } } -QAccessibleTable::QAccessibleTable(QWidget *w) - : QAccessibleWidgetV2(w) +QAccessibleTable::QAccessibleTable(QWidget *w, QAccessible::Role role) + : QAccessibleWidgetV2(w, role) { Q_ASSERT(view()); - -#if QT_CONFIG(tableview) - if (qobject_cast<const QTableView*>(view())) { - m_role = QAccessible::Table; - } else -#endif -#if QT_CONFIG(treeview) - if (qobject_cast<const QTreeView*>(view())) { - m_role = QAccessible::Tree; - } else -#endif -#if QT_CONFIG(listview) - if (qobject_cast<const QListView*>(view())) { - m_role = QAccessible::List; - } else -#endif - { - // is this our best guess? - m_role = QAccessible::Table; - } -} - -bool QAccessibleTable::isValid() const -{ - return view() && !qt_widget_private(view())->data.in_destructor; } QAccessibleTable::~QAccessibleTable() @@ -161,7 +136,7 @@ int QAccessibleTable::columnCount() const if (!theModel) return 0; const int modelColumnCount = theModel->columnCount(theView->rootIndex()); - return m_role == QAccessible::List ? qMin(1, modelColumnCount) : modelColumnCount; + return role() == QAccessible::List ? qMin(1, modelColumnCount) : modelColumnCount; } int QAccessibleTable::rowCount() const @@ -491,36 +466,6 @@ bool QAccessibleTable::clear() } -QAccessible::Role QAccessibleTable::role() const -{ - return m_role; -} - -QAccessible::State QAccessibleTable::state() const -{ - QAccessible::State state; - const auto *w = view(); - - if (w->testAttribute(Qt::WA_WState_Visible) == false) - state.invisible = true; - if (w->focusPolicy() != Qt::NoFocus) - state.focusable = true; - if (w->hasFocus()) - state.focused = true; - if (!w->isEnabled()) - state.disabled = true; - if (w->isWindow()) { - if (w->windowFlags() & Qt::WindowSystemMenuHint) - state.movable = true; - if (w->minimumSize() != w->maximumSize()) - state.sizeable = true; - if (w->isActiveWindow()) - state.active = true; - } - - return state; -} - QAccessibleInterface *QAccessibleTable::childAt(int x, int y) const { QPoint viewportOffset = view()->viewport()->mapTo(view(), QPoint(0,0)); @@ -589,14 +534,6 @@ int QAccessibleTable::indexOfChild(const QAccessibleInterface *iface) const return -1; } -QRect QAccessibleTable::rect() const -{ - if (!view()->isVisible()) - return QRect(); - QPoint pos = view()->mapToGlobal(QPoint(0, 0)); - return QRect(pos.x(), pos.y(), view()->width(), view()->height()); -} - QAccessibleInterface *QAccessibleTable::parent() const { if (view() && view()->parent()) { diff --git a/src/widgets/accessible/itemviews_p.h b/src/widgets/accessible/itemviews_p.h index e74be151115..ceb6b913a8d 100644 --- a/src/widgets/accessible/itemviews_p.h +++ b/src/widgets/accessible/itemviews_p.h @@ -36,12 +36,7 @@ class QAccessibleTable : public QAccessibleTableInterface, public QAccessibleWidgetV2 { public: - explicit QAccessibleTable(QWidget *w); - bool isValid() const override; - - QAccessible::Role role() const override; - QAccessible::State state() const override; - QRect rect() const override; + explicit QAccessibleTable(QWidget *w, QAccessible::Role role = QAccessible::Table); QAccessibleInterface *childAt(int x, int y) const override; QAccessibleInterface *focusChild() const override; @@ -91,7 +86,7 @@ public: protected: inline QAccessible::Role cellRole() const { - switch (m_role) { + switch (role()) { case QAccessible::List: return QAccessible::ListItem; case QAccessible::Table: @@ -116,7 +111,6 @@ protected: private: // the child index for a model index inline int logicalIndex(const QModelIndex &index) const; - QAccessible::Role m_role; }; #if QT_CONFIG(treeview) @@ -124,7 +118,7 @@ class QAccessibleTree :public QAccessibleTable { public: explicit QAccessibleTree(QWidget *w) - : QAccessibleTable(w) + : QAccessibleTable(w, QAccessible::Tree) {} @@ -153,7 +147,7 @@ class QAccessibleList :public QAccessibleTable { public: explicit QAccessibleList(QWidget *w) - : QAccessibleTable(w) + : QAccessibleTable(w, QAccessible::List) {} QAccessibleInterface *child(int index) const override; diff --git a/src/widgets/styles/qstylesheetstyle.cpp b/src/widgets/styles/qstylesheetstyle.cpp index b2cfb27e814..d45515e0ecc 100644 --- a/src/widgets/styles/qstylesheetstyle.cpp +++ b/src/widgets/styles/qstylesheetstyle.cpp @@ -4658,7 +4658,14 @@ void QStyleSheetStyle::drawPrimitive(PrimitiveElement pe, const QStyleOption *op #if QT_CONFIG(scrollarea) if (const QAbstractScrollArea *sa = qobject_cast<const QAbstractScrollArea *>(w)) { const QAbstractScrollAreaPrivate *sap = sa->d_func(); - rule.drawBackground(p, opt->rect, sap->contentsOffset()); + bool callBaseClass = true; + if (rule.hasBackground()) { + if (rule.baseStyleCanDraw()) + baseStyle()->drawPrimitive(pe, opt, p, w); + else + rule.drawBackground(p, opt->rect, sap->contentsOffset()); + callBaseClass = false; + } if (rule.hasBorder()) { QRect brect = rule.borderRect(opt->rect); if (styleHint(QStyle::SH_ScrollView_FrameOnlyAroundContents, opt, w)) { @@ -4667,7 +4674,10 @@ void QStyleSheetStyle::drawPrimitive(PrimitiveElement pe, const QStyleOption *op brect = QStyle::visualRect(opt->direction, brect, r); } rule.drawBorder(p, brect); + callBaseClass = false; } + if (!callBaseClass) + return; break; } #endif diff --git a/src/widgets/widgets/qmenu.cpp b/src/widgets/widgets/qmenu.cpp index 92ff14dd44f..26d9bcc9f24 100644 --- a/src/widgets/widgets/qmenu.cpp +++ b/src/widgets/widgets/qmenu.cpp @@ -2543,14 +2543,16 @@ void QMenuPrivate::popup(const QPoint &p, QAction *atAction, PositionFunction po q->create(); if (auto waylandWindow = dynamic_cast<QNativeInterface::Private::QWaylandWindow*>(q->windowHandle()->handle())) { if (causedButton) { + const QRect controlGeometry(causedButton->mapTo(causedButton->window(), QPoint(0, 0)), causedButton->size()); + waylandWindow->setParentControlGeometry(controlGeometry); waylandWindow->setExtendedWindowType(QNativeInterface::Private::QWaylandWindow::Menu); - waylandWindow->setParentControlGeometry(causedButton->geometry()); } else if (caused) { - waylandWindow->setExtendedWindowType(QNativeInterface::Private::QWaylandWindow::SubMenu); waylandWindow->setParentControlGeometry(caused->d_func()->actionRect(caused->d_func()->currentAction)); + waylandWindow->setExtendedWindowType(QNativeInterface::Private::QWaylandWindow::SubMenu); } else if (auto menubar = qobject_cast<QMenuBar*>(causedPopup.widget)) { + QPoint menuBarWindowPosition = menubar->mapTo(menubar->window(), QPoint(0, 0)); + waylandWindow->setParentControlGeometry(menubar->actionGeometry(causedPopup.action).translated(menuBarWindowPosition)); waylandWindow->setExtendedWindowType(QNativeInterface::Private::QWaylandWindow::Menu); - waylandWindow->setParentControlGeometry(menubar->actionGeometry(causedPopup.action)); } } #endif |
