diff options
| author | Friedemann Kleint <Friedemann.Kleint@qt.io> | 2025-06-11 11:06:31 +0200 |
|---|---|---|
| committer | Friedemann Kleint <Friedemann.Kleint@qt.io> | 2025-06-17 12:04:42 +0200 |
| commit | 4aa48368667bee64e48f7c9d6b3a935411d5f23c (patch) | |
| tree | c19be3cd8624322bcee32272f1d4d410218e6abf | |
| parent | 7ae471b49d0f38d7ac1c367bcfa3378f309c6b94 (diff) | |
Enable using @QEnum/@QFlag-decorated enums as custom widget properties
Create a dynamic QMetaType for @QEnum-decorated enumerations so that Qt
Widgets Designer is able to recognize the properties.
[ChangeLog][PySide6] It is now possible to use @QEnum/@QFlag-decorated
enumerations as properties of custom widgets in Qt Widgets Designer.
Task-number: PYSIDE-2840
Change-Id: I58a16002f89678856b7f33d687cf99f00c6f0cc7
Reviewed-by: Cristian Maureira-Fredes <cristian.maureira-fredes@qt.io>
| -rw-r--r-- | sources/pyside6/libpyside/dynamicqmetaobject.cpp | 17 | ||||
| -rw-r--r-- | sources/pyside6/libpyside/pysideqenum.cpp | 108 | ||||
| -rw-r--r-- | sources/pyside6/libpyside/pysideqenum.h | 6 | ||||
| -rw-r--r-- | sources/pyside6/tests/QtCore/CMakeLists.txt | 1 | ||||
| -rw-r--r-- | sources/pyside6/tests/QtCore/qenum_designer_test.py | 83 |
5 files changed, 209 insertions, 6 deletions
diff --git a/sources/pyside6/libpyside/dynamicqmetaobject.cpp b/sources/pyside6/libpyside/dynamicqmetaobject.cpp index a3b718d83..54e814523 100644 --- a/sources/pyside6/libpyside/dynamicqmetaobject.cpp +++ b/sources/pyside6/libpyside/dynamicqmetaobject.cpp @@ -63,8 +63,8 @@ public: int addProperty(const QByteArray &property, PyObject *data); void addInfo(const QByteArray &key, const QByteArray &value); void addInfo(const QMap<QByteArray, QByteArray> &info); - void addEnumerator(const char *name, bool flag, bool scoped, - const MetaObjectBuilder::EnumValues &entries); + QMetaEnumBuilder addEnumerator(const char *name, bool flag, bool scoped, + const MetaObjectBuilder::EnumValues &entries); void removeProperty(int index); const QMetaObject *update(); @@ -384,8 +384,9 @@ void MetaObjectBuilder::addEnumerator(const char *name, bool flag, bool scoped, m_d->addEnumerator(name, flag, scoped, entries); } -void MetaObjectBuilderPrivate::addEnumerator(const char *name, bool flag, bool scoped, - const MetaObjectBuilder::EnumValues &entries) +QMetaEnumBuilder + MetaObjectBuilderPrivate::addEnumerator(const char *name, bool flag, bool scoped, + const MetaObjectBuilder::EnumValues &entries) { auto *builder = ensureBuilder(); int have_already = builder->indexOfEnumerator(name); @@ -398,6 +399,7 @@ void MetaObjectBuilderPrivate::addEnumerator(const char *name, bool flag, bool s for (const auto &item : entries) enumbuilder.addKey(item.first, item.second); m_dirty = true; + return enumbuilder; } void MetaObjectBuilderPrivate::removeProperty(int index) @@ -686,6 +688,11 @@ void MetaObjectBuilderPrivate::parsePythonType(PyTypeObject *type) auto ivalue = PyLong_AsSsize_t(value); entries.push_back(std::make_pair(ckey, int(ivalue))); } - addEnumerator(name, isFlag, true, entries); + auto enumBuilder = addEnumerator(name, isFlag, true, entries); + QByteArray qualifiedName = ensureBuilder()->className() + "::"_ba + name; + auto metaType = + PySide::QEnum::createGenericEnumMetaType(qualifiedName, + reinterpret_cast<PyTypeObject *>(obEnumType)); + enumBuilder.setMetaType(metaType); } } diff --git a/sources/pyside6/libpyside/pysideqenum.cpp b/sources/pyside6/libpyside/pysideqenum.cpp index 8fc33d5db..70dd1d6da 100644 --- a/sources/pyside6/libpyside/pysideqenum.cpp +++ b/sources/pyside6/libpyside/pysideqenum.cpp @@ -4,6 +4,7 @@ #include "pysideqenum.h" #include <autodecref.h> +#include <sbkconverter.h> #include <sbkenum.h> #include <sbkstaticstrings.h> #include <sbkstring.h> @@ -11,6 +12,8 @@ #include <map> #include <QtCore/qmetatype.h> +#include <QtCore/qdebug.h> +#include <QtCore/qlist.h> /////////////////////////////////////////////////////////////// // @@ -101,6 +104,59 @@ static bool is_module_code() } // extern "C" +// Helper code for dynamically creating QMetaType's for @QEnum + +template <class UnderlyingInt> +static void defaultCtr(const QtPrivate::QMetaTypeInterface *, void *addr) +{ + auto *i = reinterpret_cast<UnderlyingInt *>(addr); + *i = 0; +} + +template <class UnderlyingInt> +static void debugOp(const QtPrivate::QMetaTypeInterface *mti, QDebug &debug, const void *addr) +{ + const auto value = *reinterpret_cast<const UnderlyingInt *>(addr); + QDebugStateSaver saver(debug); + debug << mti->name << '('; + if constexpr (std::is_unsigned<UnderlyingInt>()) { + debug << Qt::showbase << Qt::hex; + } else { + if (value >= 0) + debug << Qt::showbase << Qt::hex; + } + debug << value << ')'; +} + +template <class UnderlyingInt> +QMetaType createEnumMetaTypeHelper(const QByteArray &name) +{ + auto *mti = new QtPrivate::QMetaTypeInterface { + 1, // revision + ushort(std::alignment_of<UnderlyingInt>()), + sizeof(UnderlyingInt), + uint(QMetaType::fromType<UnderlyingInt>().flags() | QMetaType::IsEnumeration), + {}, // typeId + nullptr, // metaObjectFn + qstrdup(name.constData()), + defaultCtr<UnderlyingInt>, + nullptr, // copyCtr + nullptr, // moveCtr + nullptr, // dtor + QtPrivate::QEqualityOperatorForType<UnderlyingInt>::equals, + QtPrivate::QLessThanOperatorForType<UnderlyingInt>::lessThan, + debugOp<UnderlyingInt>, + nullptr, // dataStreamOut + nullptr, // dataStreamIn + nullptr // legacyRegisterOp + }; + + QMetaType metaType(mti); + + metaType.id(); // enforce registration + return metaType; +} + namespace PySide::QEnum { static std::map<int, PyObject *> enumCollector; @@ -210,7 +266,57 @@ QByteArray getTypeName(PyTypeObject *type) ? result : QByteArray{}; } -} // namespace Shiboken::Enum +using GenericEnumType = int; + +using GenericEnumTypeList = QList<PyTypeObject *>; + +Q_GLOBAL_STATIC(GenericEnumTypeList, genericEnumTypeList) + +} // namespace PySide::QEnum + +extern "C" +{ + +static void genericEnumPythonToCpp(PyObject *pyIn, void *cppOut) +{ + const auto value = static_cast<PySide::QEnum::GenericEnumType>(Shiboken::Enum::getValue(pyIn)); + *reinterpret_cast<int *>(cppOut) = value; +} + +static PythonToCppFunc isGenericEnumToCppConvertible(PyObject *pyIn) +{ + + if (PySide::QEnum::genericEnumTypeList()->contains(Py_TYPE(pyIn))) + return genericEnumPythonToCpp; + return {}; +} + +static PyObject *genericEnumCppToPython(PyTypeObject *pyType, const void *cppIn) +{ + const auto value = *reinterpret_cast<const PySide::QEnum::GenericEnumType *>(cppIn); + return Shiboken::Enum::newItem(pyType, value); +} + +} // extern "C" + +namespace PySide::QEnum +{ + +QMetaType createGenericEnumMetaType(const QByteArray &name, PyTypeObject *pyType) +{ + SbkConverter *converter = Shiboken::Conversions::createConverter(pyType, + genericEnumCppToPython); + Shiboken::Conversions::addPythonToCppValueConversion(converter, + genericEnumPythonToCpp, + isGenericEnumToCppConvertible); + Shiboken::Conversions::registerConverterName(converter, name.constData()); + Shiboken::Enum::setTypeConverter(pyType, converter, nullptr); + + genericEnumTypeList()->append(pyType); + return createEnumMetaTypeHelper<GenericEnumType>(name); +} + +} // namespace PySide::QEnum // /////////////////////////////////////////////////////////////// diff --git a/sources/pyside6/libpyside/pysideqenum.h b/sources/pyside6/libpyside/pysideqenum.h index cfc361004..edc15dc9e 100644 --- a/sources/pyside6/libpyside/pysideqenum.h +++ b/sources/pyside6/libpyside/pysideqenum.h @@ -12,6 +12,8 @@ #include <QtCore/qbytearray.h> +QT_FORWARD_DECLARE_CLASS(QMetaType) + namespace PySide::QEnum { // PYSIDE-957: Support the QEnum macro @@ -25,6 +27,10 @@ PYSIDE_API void init(); // Ignore flags here; their underlying enums are of Python type flags anyways. PYSIDE_API QByteArray getTypeName(PyTypeObject *type); +// Create a QMetaType for a decorated Python enum (int), enabling +// modification of properties by Qt Widgets Designer. +QMetaType createGenericEnumMetaType(const QByteArray &name, PyTypeObject *pyType); + } // namespace PySide::QEnum #endif diff --git a/sources/pyside6/tests/QtCore/CMakeLists.txt b/sources/pyside6/tests/QtCore/CMakeLists.txt index a74216ccc..e21e8c064 100644 --- a/sources/pyside6/tests/QtCore/CMakeLists.txt +++ b/sources/pyside6/tests/QtCore/CMakeLists.txt @@ -73,6 +73,7 @@ PYSIDE_TEST(qdate_test.py) PYSIDE_TEST(qdir_test.py) PYSIDE_TEST(qeasingcurve_test.py) PYSIDE_TEST(qenum_test.py) +PYSIDE_TEST(qenum_designer_test.py) PYSIDE_TEST(qevent_test.py) PYSIDE_TEST(qfileinfo_test.py) PYSIDE_TEST(qfile_test.py) diff --git a/sources/pyside6/tests/QtCore/qenum_designer_test.py b/sources/pyside6/tests/QtCore/qenum_designer_test.py new file mode 100644 index 000000000..0f35bab16 --- /dev/null +++ b/sources/pyside6/tests/QtCore/qenum_designer_test.py @@ -0,0 +1,83 @@ +#!/usr/bin/python +# Copyright (C) 2025 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +'''Test cases for QEnum and QFlags within Qt Widgets Designer''' + +import os +import sys +import unittest + +from enum import Enum, Flag + +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 QObject, Property, QEnum, QFlag + + +class CustomWidgetBase(QObject): + @QEnum + class TestEnum(Enum): + EnumValue0 = 0 + EnumValue1 = 1 + EnumValue2 = 2 + EnumValue3 = 3 + + @QFlag + class TestFlag(Flag): + FlagValue0 = 1 + FlagValue1 = 2 + FlagValue2 = 4 + FlagValue3 = 8 + + +class CustomWidget(CustomWidgetBase): + def __init__(self, parent=None): + super().__init__(parent) + self._testEnum = CustomWidget.TestEnum.EnumValue1 + self._testFlag = (CustomWidget.TestFlag.FlagValue0 + | CustomWidget.TestFlag.FlagValue1) + + def testEnum(self): + return self._testEnum + + def setTestEnum(self, new_val): + self._testEnum = new_val + + def getTestFlag(self): + return self._testFlag + + def setTestFlag(self, new_val): + self._testFlag = new_val + + testEnum = Property(CustomWidgetBase.TestEnum, testEnum, setTestEnum) + testFlag = Property(CustomWidgetBase.TestFlag, getTestFlag, setTestFlag) + + +class TestDesignerEnum(unittest.TestCase): + """PYSIDE-2840: Test whether a custom widget with decorated enum/flag properties + allows for modifying the values from C++.""" + + def testEnum(self): + cw = CustomWidget() + # Emulate Qt Widgets Designer setting a property + cw.setProperty("testEnum", 3) + self.assertEqual(cw.testEnum, CustomWidget.TestEnum.EnumValue3) + # Emulate uic generated code + cw.setProperty("testEnum", CustomWidgetBase.TestEnum.EnumValue2) + self.assertEqual(cw.testEnum, CustomWidget.TestEnum.EnumValue2) + + # Emulate Qt Widgets Designer setting a property + cw.setProperty("testFlag", 12) + self.assertEqual(cw.testFlag, (CustomWidget.TestFlag.FlagValue2 + | CustomWidget.TestFlag.FlagValue3)) + # Emulate uic generated code + cw.setProperty("testFlag", CustomWidgetBase.TestFlag.FlagValue1) + self.assertEqual(cw.testFlag, CustomWidget.TestFlag.FlagValue1) + + +if __name__ == '__main__': + unittest.main() |
