diff options
| author | Friedemann Kleint <Friedemann.Kleint@qt.io> | 2022-03-02 16:03:11 +0100 |
|---|---|---|
| committer | Friedemann Kleint <Friedemann.Kleint@qt.io> | 2022-03-03 16:17:42 +0100 |
| commit | cf40e624e603dc468ec585ecddc9fbf1ee0ccddd (patch) | |
| tree | b03c2a9c3bf1a112ecba129f85cf96d46c7a6dd7 | |
| parent | e39f31bbae4c6699d5919bc046d1b9616c3f55c3 (diff) | |
Fix grouped QML properties
Register the meta type using QQmlMetaTypeInterface and use them when
creating properties in the dynamic metaobject builder.
This at least fixes grouped properties when decorators are used. It
does not work when using plain qmlRegisterType() due to an ordering
problem.
Fixes: PYSIDE-1836
Change-Id: I06db020a1ccd169da7a745cc5ef42d38ce35f5f5
Reviewed-by: Christian Tismer <tismer@stackless.com>
| -rw-r--r-- | sources/pyside6/libpyside/dynamicqmetaobject.cpp | 44 | ||||
| -rw-r--r-- | sources/pyside6/libpysideqml/pysideqmlregistertype.cpp | 13 | ||||
| -rw-r--r-- | sources/pyside6/tests/QtQml/CMakeLists.txt | 1 | ||||
| -rw-r--r-- | sources/pyside6/tests/QtQml/groupedproperty.py | 137 | ||||
| -rw-r--r-- | sources/pyside6/tests/QtQml/groupedproperty.qml | 34 |
5 files changed, 219 insertions, 10 deletions
diff --git a/sources/pyside6/libpyside/dynamicqmetaobject.cpp b/sources/pyside6/libpyside/dynamicqmetaobject.cpp index 6b412de9a..6f1c9b40a 100644 --- a/sources/pyside6/libpyside/dynamicqmetaobject.cpp +++ b/sources/pyside6/libpyside/dynamicqmetaobject.cpp @@ -45,6 +45,7 @@ #include "pysideproperty_p.h" #include "pysideslot_p.h" #include "pysideqenum.h" +#include "pyside_p.h" #include <shiboken.h> @@ -102,6 +103,10 @@ public: const QMetaObject *m_baseObject = nullptr; MetaObjects m_cachedMetaObjects; bool m_dirty = true; + +private: + QMetaPropertyBuilder + createProperty(PySideProperty *property, const QByteArray &propertyName); }; QMetaObjectBuilder *MetaObjectBuilderPrivate::ensureBuilder() @@ -300,6 +305,35 @@ int MetaObjectBuilderPrivate::getPropertyNotifyId(PySideProperty *property) cons return notifyId; } +QMetaPropertyBuilder + MetaObjectBuilderPrivate::createProperty(PySideProperty *property, + const QByteArray &propertyName) +{ + int propertyNotifyId = getPropertyNotifyId(property); + if (propertyNotifyId >= 0) + propertyNotifyId -= m_baseObject->methodCount(); + + // For QObject-derived Python types, retrieve the meta type registered + // by name from the qmlRegisterType, if there is one. This is required for + // grouped QML properties to work. + auto *builder = ensureBuilder(); + auto *typeObject = Property::getTypeObject(property); + if (typeObject != nullptr && PyType_Check(typeObject)) { + auto *pyTypeObject = reinterpret_cast<PyTypeObject *>(typeObject); + if (qstrncmp(pyTypeObject->tp_name, "PySide", 6) != 0 + && PySide::isQObjectDerived(pyTypeObject, false)) { + const QByteArray pyType(pyTypeObject->tp_name); + const auto metaType = QMetaType::fromName(pyType + '*'); + if (metaType.isValid()) { + return builder->addProperty(propertyName, pyType, + metaType, propertyNotifyId); + } + } + } + return builder->addProperty(propertyName, property->d->typeName, + propertyNotifyId); +} + int MetaObjectBuilderPrivate::addProperty(const QByteArray &propertyName, PyObject *data) { @@ -307,13 +341,9 @@ int MetaObjectBuilderPrivate::addProperty(const QByteArray &propertyName, if (index != -1) return index; - PySideProperty *property = reinterpret_cast<PySideProperty *>(data); - int propertyNotifyId = getPropertyNotifyId(property); - if (propertyNotifyId >= 0) - propertyNotifyId -= m_baseObject->methodCount(); - auto newProperty = - ensureBuilder()->addProperty(propertyName, property->d->typeName, - propertyNotifyId); + auto *property = reinterpret_cast<PySideProperty *>(data); + auto newProperty = createProperty(property, propertyName); + // Adding property attributes newProperty.setReadable(PySide::Property::isReadable(property)); newProperty.setWritable(PySide::Property::isWritable(property)); diff --git a/sources/pyside6/libpysideqml/pysideqmlregistertype.cpp b/sources/pyside6/libpysideqml/pysideqmlregistertype.cpp index ad839ec96..05a7bbffb 100644 --- a/sources/pyside6/libpysideqml/pysideqmlregistertype.cpp +++ b/sources/pyside6/libpysideqml/pysideqmlregistertype.cpp @@ -60,6 +60,7 @@ #include <QtQml/qqml.h> #include <QtQml/QJSValue> #include <QtQml/QQmlListProperty> +#include <private/qqmlmetatype_p.h> static PySide::Qml::QuickRegisterItemFunction quickRegisterItemFunction = nullptr; @@ -140,15 +141,21 @@ int qmlRegisterType(PyObject *pyObj, const char *uri, int versionMajor, // Register as simple QObject rather than Qt Quick item. if (!registered) { + using QObjectQmlList = QQmlListProperty<QObject>; // Incref the type object, don't worry about decref'ing it because // there's no way to unregister a QML type. Py_INCREF(pyObj); type.structVersion = 0; - // FIXME: Fix this to assign new type ids each time. - type.typeId = QMetaType(QMetaType::QObjectStar); - type.listId = QMetaType::fromType<QQmlListProperty<QObject> >(); + const QByteArray typeName(pyObjType->tp_name); + QByteArray ptrType = typeName + '*'; + QByteArray listType = QByteArrayLiteral("QQmlListProperty<") + typeName + '>'; + + type.typeId = QMetaType(new QQmlMetaTypeInterface(ptrType, static_cast<QObject **>(nullptr))); + type.listId = QMetaType(new QQmlListMetaTypeInterface(listType, + static_cast<QObjectQmlList*>(nullptr), + type.typeId.iface())); const auto typeInfo = qmlTypeInfo(pyObj); auto info = qmlAttachedInfo(pyObjType, typeInfo); type.attachedPropertiesFunction = info.factory; diff --git a/sources/pyside6/tests/QtQml/CMakeLists.txt b/sources/pyside6/tests/QtQml/CMakeLists.txt index f97e3eb43..0006accde 100644 --- a/sources/pyside6/tests/QtQml/CMakeLists.txt +++ b/sources/pyside6/tests/QtQml/CMakeLists.txt @@ -11,6 +11,7 @@ PYSIDE_TEST(bug_951.py) PYSIDE_TEST(bug_995.py) PYSIDE_TEST(bug_997.py) PYSIDE_TEST(bug_1029.py) +PYSIDE_TEST(groupedproperty.py) PYSIDE_TEST(listproperty.py) PYSIDE_TEST(qqmlapplicationengine_test.py) PYSIDE_TEST(qqmlnetwork_test.py) diff --git a/sources/pyside6/tests/QtQml/groupedproperty.py b/sources/pyside6/tests/QtQml/groupedproperty.py new file mode 100644 index 000000000..c3798300c --- /dev/null +++ b/sources/pyside6/tests/QtQml/groupedproperty.py @@ -0,0 +1,137 @@ +############################################################################# +## +## Copyright (C) 2022 The Qt Company Ltd. +## Contact: https://www.qt.io/licensing/ +## +## This file is part of the test suite of Qt for Python. +## +## $QT_BEGIN_LICENSE:GPL-EXCEPT$ +## 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 General Public License Usage +## Alternatively, this file may be used under the terms of the GNU +## General Public License version 3 as published by the Free Software +## Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +## 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-3.0.html. +## +## $QT_END_LICENSE$ +## +############################################################################# + +"""Test grouped properties (PYSIDE-1836).""" + +import os +import sys +import unittest + +from pathlib import Path +sys.path.append(os.fspath(Path(__file__).resolve().parents[1])) +from init_paths import init_test_paths +init_test_paths(False) + +from PySide6.QtCore import (QCoreApplication, QUrl, QObject, Property) +from PySide6.QtQml import (QQmlComponent, QQmlEngine, QmlAnonymous, QmlElement) + + +QML_IMPORT_NAME = "grouped" +QML_IMPORT_MAJOR_VERSION = 1 + + +@QmlAnonymous +class ShoeDescription(QObject): + def __init__(self, parent=None): + super().__init__(parent) + self._brand = "" + self._size = 0 + self._price = 0 + + @Property(str) + def brand(self): + return self._brand + + @brand.setter + def brand(self, b): + self._brand = b + + @Property(int) + def size(self): + return self._size + + @size.setter + def size(self, s): + self._size = s + + @Property(int) + def price(self): + return self._price + + @price.setter + def price(self, p): + self._price = p + + +@QmlElement +class Person(QObject): + def __init__(self, parent=None): + super().__init__(parent) + self._name = "" + self._shoe = ShoeDescription() + + @Property(str) + def name(self): + return self._name + + @name.setter + def name(self, n): + self._name = n + + @Property(ShoeDescription) + def shoe(self): + return self._shoe + + +def component_error(component): + result = "" + for e in component.errors(): + if result: + result += "\n" + result += str(e) + return result + + +class TestQmlGroupedProperties(unittest.TestCase): + def testIt(self): + app = QCoreApplication(sys.argv) + file = Path(__file__).resolve().parent / "groupedproperty.qml" + url = QUrl.fromLocalFile(file) + engine = QQmlEngine() + component = QQmlComponent(engine, url) + person = component.create() + self.assertTrue(person, component_error(component)) + + # Check the meta type of the property + meta_object = person.metaObject() + index = meta_object.indexOfProperty("shoe") + self.assertTrue(index > 0) + meta_property = meta_object.property(index) + meta_type = meta_property.metaType() + self.assertTrue(meta_type.isValid()) + + # Check the values + self.assertEqual(person.shoe.brand, "Bikey") + self.assertEqual(person.shoe.price, 90) + self.assertEqual(person.shoe.size, 12) + + del engine + + +if __name__ == '__main__': + unittest.main() diff --git a/sources/pyside6/tests/QtQml/groupedproperty.qml b/sources/pyside6/tests/QtQml/groupedproperty.qml new file mode 100644 index 000000000..596c21f39 --- /dev/null +++ b/sources/pyside6/tests/QtQml/groupedproperty.qml @@ -0,0 +1,34 @@ +/**************************************************************************** +** +** Copyright (C) 2022 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the test suite of Qt for Python. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** 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 General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import grouped + +Person { + name: "Bob Jones" + shoe { size: 12; brand: "Bikey"; price: 90 } +} |
