diff options
| -rw-r--r-- | sources/pyside-tools/metaobjectdump.py | 3 | ||||
| -rw-r--r-- | sources/pyside6/PySide6/QtQml/typesystem_qml.xml | 6 | ||||
| -rw-r--r-- | sources/pyside6/PySide6/glue/qtqml.cpp | 5 | ||||
| -rw-r--r-- | sources/pyside6/doc/extras/QtQml.QmlAttached.rst | 40 | ||||
| -rw-r--r-- | sources/pyside6/libpysideqml/CMakeLists.txt | 1 | ||||
| -rw-r--r-- | sources/pyside6/libpysideqml/pysideqml.cpp | 2 | ||||
| -rw-r--r-- | sources/pyside6/libpysideqml/pysideqmlattached.cpp | 253 | ||||
| -rw-r--r-- | sources/pyside6/libpysideqml/pysideqmlattached.h | 64 | ||||
| -rw-r--r-- | sources/pyside6/libpysideqml/pysideqmlattached_p.h | 57 | ||||
| -rw-r--r-- | sources/pyside6/libpysideqml/pysideqmlregistertype.cpp | 8 | ||||
| -rw-r--r-- | sources/pyside6/libpysideqml/pysideqmltypeinfo.cpp | 2 | ||||
| -rw-r--r-- | sources/pyside6/libpysideqml/pysideqmltypeinfo_p.h | 1 | ||||
| -rw-r--r-- | sources/pyside6/tests/QtQml/CMakeLists.txt | 1 | ||||
| -rw-r--r-- | sources/pyside6/tests/QtQml/registerattached.py | 126 | ||||
| -rw-r--r-- | sources/pyside6/tests/QtQml/registerattached.qml | 45 |
15 files changed, 611 insertions, 3 deletions
diff --git a/sources/pyside-tools/metaobjectdump.py b/sources/pyside-tools/metaobjectdump.py index 132a6222b..f656aa878 100644 --- a/sources/pyside-tools/metaobjectdump.py +++ b/sources/pyside-tools/metaobjectdump.py @@ -245,6 +245,9 @@ class MetaObjectDumpVisitor(ast.NodeVisitor): if isinstance(reason, str): d = _decorator("QML.UncreatableReason", reason) class_decorators.append(d) + elif name == "QmlAttached" and len(node.args) == 1: + d = _decorator("QML.Attached", node.args[0].id) + class_decorators.append(d) elif name == "QmlExtended" and len(node.args) == 1: d = _decorator("QML.Extended", node.args[0].id) class_decorators.append(d) diff --git a/sources/pyside6/PySide6/QtQml/typesystem_qml.xml b/sources/pyside6/PySide6/QtQml/typesystem_qml.xml index b5c8840bf..029bdc2c7 100644 --- a/sources/pyside6/PySide6/QtQml/typesystem_qml.xml +++ b/sources/pyside6/PySide6/QtQml/typesystem_qml.xml @@ -58,6 +58,7 @@ <inject-code class="native" position="beginning"> #include <pysideqml.h> #include <pysideqmlregistertype.h> + #include <pysideqmlattached.h> #include "pysideqmlvolatilebool.h" </inject-code> @@ -78,6 +79,11 @@ <value-type name="atomic" generate="no"/> </namespace-type> + <add-function signature="qmlAttachedPropertiesObject(PyTypeObject*,QObject*,bool=true)" + return-type="QObject*"> + <inject-code class="target" file="../glue/qtqml.cpp" snippet="qmlattachedpropertiesobject"/> + </add-function> + <add-function signature="qmlRegisterType(PyTypeObject,const char*,int,int,const char*)" return-type="int"> <inject-code class="target" file="../glue/qtqml.cpp" snippet="qmlregistertype"/> </add-function> diff --git a/sources/pyside6/PySide6/glue/qtqml.cpp b/sources/pyside6/PySide6/glue/qtqml.cpp index 3931e4d51..f9e0e15d4 100644 --- a/sources/pyside6/PySide6/glue/qtqml.cpp +++ b/sources/pyside6/PySide6/glue/qtqml.cpp @@ -42,6 +42,11 @@ const QByteArray message = %CPPSELF.toString().toUtf8(); %PYARG_0 = Shiboken::String::fromCString(message.constData()); // @snippet qmlerrror-repr +// @snippet qmlattachedpropertiesobject +auto *%0 = PySide::Qml::qmlAttachedPropertiesObject(%ARGUMENT_NAMES); +%PYARG_0 = %CONVERTTOPYTHON[QObject*](%0); +// @snippet qmlattachedpropertiesobject + // @snippet qmlregistertype int %0 = PySide::Qml::qmlRegisterType(%ARGUMENT_NAMES); %PYARG_0 = %CONVERTTOPYTHON[int](%0); diff --git a/sources/pyside6/doc/extras/QtQml.QmlAttached.rst b/sources/pyside6/doc/extras/QtQml.QmlAttached.rst new file mode 100644 index 000000000..e3fefb6b2 --- /dev/null +++ b/sources/pyside6/doc/extras/QtQml.QmlAttached.rst @@ -0,0 +1,40 @@ +.. currentmodule:: PySide6.QtQml +.. _QmlAttached: + +QmlAttached +*********** + +.. py:decorator:: QmlAttached + +This decorator declares that the enclosing type attaches the type passed as +an attached property to other types. This takes effect if the type is exposed +to QML using a ``QmlElement()`` or ``@QmlNamedElement()`` decorator. + +.. code-block:: python + + QML_IMPORT_NAME = "com.library.name" + QML_IMPORT_MAJOR_VERSION = 1 + QML_IMPORT_MINOR_VERSION = 0 # Optional + + @QmlAnonymous + class LayoutAttached(QObject): + @Property(QMargins) + def margins(self): + ... + + @QmlElement() + @QmlAttached(LayoutAttached) + class Layout(QObject): + ... + +Afterwards the class may be used in QML: + +.. code-block:: javascript + + import com.library.name 1.0 + + Layout { + Widget { + Layout.margins: [2, 2, 2, 2] + } + } diff --git a/sources/pyside6/libpysideqml/CMakeLists.txt b/sources/pyside6/libpysideqml/CMakeLists.txt index 28ec31721..33bfce1c6 100644 --- a/sources/pyside6/libpysideqml/CMakeLists.txt +++ b/sources/pyside6/libpysideqml/CMakeLists.txt @@ -2,6 +2,7 @@ set(libpysideqml_libraries Qt::Core Qt::CorePrivate Qt::Qml Qt::QmlPrivate) set(libpysideqml_SRC pysideqml.cpp + pysideqmlattached.cpp pysideqmlforeign.cpp pysideqmlextended.cpp pysideqmlregistertype.cpp diff --git a/sources/pyside6/libpysideqml/pysideqml.cpp b/sources/pyside6/libpysideqml/pysideqml.cpp index 5babc7eee..f88413f78 100644 --- a/sources/pyside6/libpysideqml/pysideqml.cpp +++ b/sources/pyside6/libpysideqml/pysideqml.cpp @@ -39,6 +39,7 @@ #include "pysideqml.h" #include "pysideqmllistproperty_p.h" +#include "pysideqmlattached_p.h" #include "pysideqmlextended_p.h" #include "pysideqmlforeign_p.h" #include "pysideqmlnamedelement_p.h" @@ -53,6 +54,7 @@ namespace PySide::Qml void init(PyObject *module) { initQtQmlListProperty(module); + initQmlAttached(module); initQmlForeign(module); initQmlExtended(module); initQmlNamedElement(module); diff --git a/sources/pyside6/libpysideqml/pysideqmlattached.cpp b/sources/pyside6/libpysideqml/pysideqmlattached.cpp new file mode 100644 index 000000000..ad8a6ac1a --- /dev/null +++ b/sources/pyside6/libpysideqml/pysideqmlattached.cpp @@ -0,0 +1,253 @@ +/**************************************************************************** +** +** Copyright (C) 2022 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt for Python. +** +** $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$ +** +****************************************************************************/ + +#include "pysideqmlattached.h" +#include "pysideqmlattached_p.h" +#include "pysideqmltypeinfo_p.h" +#include "pysideqmlregistertype_p.h" + +#include <signalmanager.h> +#include <pyside_p.h> +#include <pysideclassdecorator_p.h> + +#include <shiboken.h> +#include <signature.h> +#include <sbkstring.h> + +#include <QtCore/QtGlobal> +#include <QtQml/qqml.h> + +#include <algorithm> + +// The QmlAttached decorator modifies QmlElement to register an attached property +// type. Due to the (reverse) execution order of decorators, it needs to follow +// QmlElement. +class PySideQmlAttachedPrivate : public PySide::ClassDecorator::TypeDecoratorPrivate +{ +public: + PyObject *tp_call(PyObject *self, PyObject *args, PyObject * /* kw */) override; + const char *name() const override; +}; + +// The call operator is passed the class type and registers the type +// in QmlTypeInfo. +PyObject *PySideQmlAttachedPrivate::tp_call(PyObject *self, PyObject *args, PyObject * /* kw */) +{ + PyObject *klass = tp_call_check(args, CheckMode::WrappedType); + if (klass == nullptr) + return nullptr; + + auto *data = DecoratorPrivate::get<PySideQmlAttachedPrivate>(self); + PySide::Qml::ensureQmlTypeInfo(klass)->attachedType = data->type(); + + Py_INCREF(klass); + return klass; +} + +const char *PySideQmlAttachedPrivate::name() const +{ + return "QmlAttached"; +} + +extern "C" { + +static PyTypeObject *createPySideQmlAttachedType(void) +{ + auto typeSlots = + PySide::ClassDecorator::Methods<PySideQmlAttachedPrivate>::typeSlots(); + + PyType_Spec PySideQmlAttachedType_spec = { + "2:PySide6.QtCore.qmlAttached", + sizeof(PySideClassDecorator), + 0, + Py_TPFLAGS_DEFAULT, + typeSlots.data() + }; + return SbkType_FromSpec(&PySideQmlAttachedType_spec); +} + +PyTypeObject *PySideQmlAttached_TypeF(void) +{ + static auto *type = createPySideQmlAttachedType(); + return type; +} + +} // extern "C" + +static const char *qmlAttached_SignatureStrings[] = { + "PySide6.QtQml.QmlAttached(self,type:type)", + nullptr // Sentinel +}; + +namespace PySide::Qml { + +static QObject *attachedFactoryHelper(PyTypeObject *attachingType, QObject *o) +{ + // Call static qmlAttachedProperties() on type. If there is an error + // and nullptr is returned, a crash occurs. So, errors should at least be + // printed. + + Shiboken::GilState gilState; + Shiboken::Conversions::SpecificConverter converter("QObject"); + Q_ASSERT(converter); + + static const char methodName[] = "qmlAttachedProperties"; + static PyObject *const pyMethodName = Shiboken::String::createStaticString(methodName); + PyObject *attachingTypeObj = reinterpret_cast<PyObject *>(attachingType); + Shiboken::AutoDecRef pyResult(PyObject_CallMethodObjArgs(attachingTypeObj, pyMethodName, + attachingTypeObj /* self */, + converter.toPython(&o), + nullptr)); + if (pyResult.isNull() || PyErr_Occurred()) { + PyErr_Print(); + return nullptr; + } + + if (PyType_IsSubtype(pyResult->ob_type, qObjectType()) == 0) { + qWarning("QmlAttached: Attached objects must inherit QObject, got %s.", + pyResult->ob_type->tp_name); + return nullptr; + } + + QObject *result = nullptr; + converter.toCpp(pyResult.object(), &result); + return result; +} + +// Since the required attached factory signature does not have a void *user +// parameter to store the attaching type, we employ a template trick, storing +// the attaching types in an array and create non-type-template (int) functions +// taking the array index as template parameter. +// We initialize the attachedFactories array with factory functions +// accessing the attachingTypes[N] using template metaprogramming. + +enum { MAX_ATTACHING_TYPES = 50}; + +using AttachedFactory = QObject *(*)(QObject *); + +static int nextAttachingType = 0; +static PyTypeObject *attachingTypes[MAX_ATTACHING_TYPES]; +static AttachedFactory attachedFactories[MAX_ATTACHING_TYPES]; + +template <int N> +static QObject *attachedFactory(QObject *o) +{ + return attachedFactoryHelper(attachingTypes[N], o); +} + +template<int N> +struct AttachedFactoryInitializerBase +{ +}; + +template<int N> +struct AttachedFactoryInitializer : AttachedFactoryInitializerBase<N> +{ + static void init() + { + attachedFactories[N] = attachedFactory<N>; + AttachedFactoryInitializer<N-1>::init(); + } +}; + +template<> +struct AttachedFactoryInitializer<0> : AttachedFactoryInitializerBase<0> +{ + static void init() + { + attachedFactories[0] = attachedFactory<0>; + } +}; + +void initQmlAttached(PyObject *module) +{ + std::fill(attachingTypes, attachingTypes + MAX_ATTACHING_TYPES, nullptr); + AttachedFactoryInitializer<MAX_ATTACHING_TYPES - 1>::init(); + + if (InitSignatureStrings(PySideQmlAttached_TypeF(), qmlAttached_SignatureStrings) < 0) + return; + + Py_INCREF(PySideQmlAttached_TypeF()); + PyModule_AddObject(module, "QmlAttached", + reinterpret_cast<PyObject *>(PySideQmlAttached_TypeF())); +} + +PySide::Qml::QmlExtensionInfo qmlAttachedInfo(PyTypeObject *t, + const QSharedPointer<QmlTypeInfo> &info) +{ + PySide::Qml::QmlExtensionInfo result{nullptr, nullptr}; + if (info.isNull() || info->attachedType == nullptr) + return result; + + auto *name = reinterpret_cast<PyTypeObject *>(t)->tp_name; + if (nextAttachingType >= MAX_ATTACHING_TYPES) { + qWarning("Unable to initialize attached type \"%s\": " + "The limit %d of attached types has been reached.", + name, MAX_ATTACHING_TYPES); + return result; + } + + result.metaObject = PySide::retrieveMetaObject(info->attachedType); + if (result.metaObject == nullptr) { + qWarning("Unable to retrieve meta object for %s", name); + return result; + } + + attachingTypes[nextAttachingType] = t; + result.factory = attachedFactories[nextAttachingType]; + ++nextAttachingType; + + return result; +} + +QObject *qmlAttachedPropertiesObject(PyObject *typeObject, QObject *obj, bool create) +{ + auto *type = reinterpret_cast<PyTypeObject *>(typeObject); + auto *end = attachingTypes + nextAttachingType; + auto *typePtr = std::find(attachingTypes, end, type); + if (typePtr == end) { + qWarning("%s: Attaching type \"%s\" not found.", __FUNCTION__, type->tp_name); + return nullptr; + } + + auto func = attachedFactories[std::uintptr_t(typePtr - attachingTypes)]; + return ::qmlAttachedPropertiesObject(obj, func, create); +} + +} // namespace PySide::Qml diff --git a/sources/pyside6/libpysideqml/pysideqmlattached.h b/sources/pyside6/libpysideqml/pysideqmlattached.h new file mode 100644 index 000000000..d2d1554af --- /dev/null +++ b/sources/pyside6/libpysideqml/pysideqmlattached.h @@ -0,0 +1,64 @@ +/**************************************************************************** +** +** Copyright (C) 2022 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt for Python. +** +** $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$ +** +****************************************************************************/ + +#ifndef PYSIDEQMLATTACHED_H +#define PYSIDEQMLATTACHED_H + +#include <sbkpython.h> + +#include "pysideqmlmacros.h" + +#include <QtCore/QtGlobal> + +QT_FORWARD_DECLARE_CLASS(QObject) + +namespace PySide::Qml +{ + +/// PySide implementation of qmlAttachedPropertiesObject<T> function. +/// \param typeObject attaching type +/// \param obj attachee +/// \param create Whether to create the Attachment object +/// \return Attachment object instance +PYSIDEQML_API QObject *qmlAttachedPropertiesObject(PyObject *typeObject, QObject *obj, + bool create = true); + +} // namespace PySide::Qml + +#endif // PYSIDEQMLATTACHED_H diff --git a/sources/pyside6/libpysideqml/pysideqmlattached_p.h b/sources/pyside6/libpysideqml/pysideqmlattached_p.h new file mode 100644 index 000000000..7305ec6f5 --- /dev/null +++ b/sources/pyside6/libpysideqml/pysideqmlattached_p.h @@ -0,0 +1,57 @@ +/**************************************************************************** +** +** Copyright (C) 2022 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt for Python. +** +** $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$ +** +****************************************************************************/ + +#ifndef PYSIDEQMLATTACHED_P_H +#define PYSIDEQMLATTACHED_P_H + +#include <sbkpython.h> + +#include <QSharedPointer> + +namespace PySide::Qml { +struct QmlExtensionInfo; +struct QmlTypeInfo; + +void initQmlAttached(PyObject *module); + +PySide::Qml::QmlExtensionInfo qmlAttachedInfo(PyTypeObject *t, + const QSharedPointer<QmlTypeInfo> &info); +} // namespace PySide::Qml + +#endif // PYSIDEQMLATTACHED_P_H diff --git a/sources/pyside6/libpysideqml/pysideqmlregistertype.cpp b/sources/pyside6/libpysideqml/pysideqmlregistertype.cpp index aee013627..e935b5627 100644 --- a/sources/pyside6/libpysideqml/pysideqmlregistertype.cpp +++ b/sources/pyside6/libpysideqml/pysideqmlregistertype.cpp @@ -40,6 +40,7 @@ #include "pysideqmlregistertype.h" #include "pysideqmlregistertype_p.h" #include "pysideqmltypeinfo_p.h" +#include "pysideqmlattached_p.h" #include "pysideqmlextended_p.h" #include <limits> @@ -139,8 +140,9 @@ int qmlRegisterType(PyObject *pyObj, const char *uri, int versionMajor, type.typeId = QMetaType(QMetaType::QObjectStar); type.listId = QMetaType::fromType<QQmlListProperty<QObject> >(); const auto typeInfo = qmlTypeInfo(pyObj); - type.attachedPropertiesFunction = QQmlPrivate::attachedPropertiesFunc<QObject>(); - type.attachedPropertiesMetaObject = QQmlPrivate::attachedPropertiesMetaObject<QObject>(); + auto info = qmlAttachedInfo(pyObjType, typeInfo); + type.attachedPropertiesFunction = info.factory; + type.attachedPropertiesMetaObject = info.metaObject; type.parserStatusCast = QQmlPrivate::StaticCastSelector<QObject, QQmlParserStatus>::cast(); @@ -159,7 +161,7 @@ int qmlRegisterType(PyObject *pyObj, const char *uri, int versionMajor, type.version = QTypeRevision::fromVersion(versionMajor, versionMinor); type.elementName = qmlName; - auto info = qmlExtendedInfo(pyObj, typeInfo); + info = qmlExtendedInfo(pyObj, typeInfo); type.extensionObjectCreate = info.factory; type.extensionMetaObject = info.metaObject; type.customParser = 0; diff --git a/sources/pyside6/libpysideqml/pysideqmltypeinfo.cpp b/sources/pyside6/libpysideqml/pysideqmltypeinfo.cpp index f8256524f..7ae707330 100644 --- a/sources/pyside6/libpysideqml/pysideqmltypeinfo.cpp +++ b/sources/pyside6/libpysideqml/pysideqmltypeinfo.cpp @@ -82,6 +82,8 @@ QDebug operator<<(QDebug d, const QmlTypeInfo &i) d << ", noCreationReason=\"" << i.noCreationReason.c_str() << '"'; if (i.foreignType) d << ", foreignType=" << i.foreignType->tp_name; + if (i.attachedType) + d << ", attachedType=" << i.attachedType->tp_name; if (i.extensionType) d << ", extensionType=" << i.extensionType->tp_name; d << ')'; diff --git a/sources/pyside6/libpysideqml/pysideqmltypeinfo_p.h b/sources/pyside6/libpysideqml/pysideqmltypeinfo_p.h index 17ca967c6..fa47891f6 100644 --- a/sources/pyside6/libpysideqml/pysideqmltypeinfo_p.h +++ b/sources/pyside6/libpysideqml/pysideqmltypeinfo_p.h @@ -68,6 +68,7 @@ struct QmlTypeInfo QmlTypeFlags flags; std::string noCreationReason; PyTypeObject *foreignType = nullptr; + PyTypeObject *attachedType = nullptr; PyTypeObject *extensionType = nullptr; }; diff --git a/sources/pyside6/tests/QtQml/CMakeLists.txt b/sources/pyside6/tests/QtQml/CMakeLists.txt index 48f8ee393..f97e3eb43 100644 --- a/sources/pyside6/tests/QtQml/CMakeLists.txt +++ b/sources/pyside6/tests/QtQml/CMakeLists.txt @@ -16,6 +16,7 @@ PYSIDE_TEST(qqmlapplicationengine_test.py) PYSIDE_TEST(qqmlnetwork_test.py) PYSIDE_TEST(qquickview_test.py) PYSIDE_TEST(connect_python_qml.py) +PYSIDE_TEST(registerattached.py) PYSIDE_TEST(registerextended.py) PYSIDE_TEST(registertype.py) PYSIDE_TEST(registerforeign.py) diff --git a/sources/pyside6/tests/QtQml/registerattached.py b/sources/pyside6/tests/QtQml/registerattached.py new file mode 100644 index 000000000..1bb982dff --- /dev/null +++ b/sources/pyside6/tests/QtQml/registerattached.py @@ -0,0 +1,126 @@ +############################################################################# +## +## 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 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, + QmlAttached, QmlElement, ListProperty, + qmlAttachedPropertiesObject) + + +QML_IMPORT_NAME = "TestLayouts" +QML_IMPORT_MAJOR_VERSION = 1 + + +EXPECTED_MARGINS = [10, 20] + + +def component_error(component): + result = "" + for e in component.errors(): + if result: + result += "\n" + result += str(e) + return result + + +@QmlAnonymous +class TestLayoutAttached(QObject): + + def __init__(self, parent=None): + super().__init__(parent) + self._leftMargin = 0 + + @Property(int) + def leftMargin(self): + return self._leftMargin + + @leftMargin.setter + def leftMargin(self, m): + self._leftMargin = m + + +@QmlElement +class TestWidget(QObject): + + def __init__(self, parent=None): + super().__init__(parent) + + +@QmlElement +@QmlAttached(TestLayoutAttached) +class TestLayout(QObject): + + def __init__(self, parent=None): + super().__init__(parent) + self._widgets = [] + + def widget(self, n): + return self._widgets[n] + + def widgetCount(self): + return len(self._widgets) + + def addWidget(self, w): + self._widgets.append(w) + + @staticmethod + def qmlAttachedProperties(self, o): + return TestLayoutAttached(o) + + widgets = ListProperty(TestWidget, addWidget) + + +class TestQmlAttached(unittest.TestCase): + def testIt(self): + app = QCoreApplication(sys.argv) + file = Path(__file__).resolve().parent / 'registerattached.qml' + url = QUrl.fromLocalFile(file) + engine = QQmlEngine() + component = QQmlComponent(engine, url) + layout = component.create() + self.assertTrue(layout, component_error(component)) + + actual_margins = [] + for i in range(layout.widgetCount()): + w = layout.widget(i) + a = qmlAttachedPropertiesObject(TestLayout, w, False) + actual_margins.append(a.leftMargin) + self.assertEqual(EXPECTED_MARGINS, actual_margins) + + +if __name__ == '__main__': + unittest.main() diff --git a/sources/pyside6/tests/QtQml/registerattached.qml b/sources/pyside6/tests/QtQml/registerattached.qml new file mode 100644 index 000000000..6339d5c08 --- /dev/null +++ b/sources/pyside6/tests/QtQml/registerattached.qml @@ -0,0 +1,45 @@ +/**************************************************************************** +** +** 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 TestLayouts + +TestLayout { + id: layout + + widgets: [ + TestWidget { + id: widget1 + TestLayout.leftMargin: 10 + }, + + TestWidget { + id: widget2 + TestLayout.leftMargin: 20 + } + ] +} |
