diff options
author | Christian Tismer <tismer@stackless.com> | 2020-06-07 17:05:32 +0200 |
---|---|---|
committer | Christian Tismer <tismer@stackless.com> | 2020-06-15 10:04:39 +0200 |
commit | d7c52752eaa1f6017a4a8fc0218a0bdd8ea7ea0a (patch) | |
tree | 23152f460d1750ae50363978f79ed9e6e839abf0 /sources | |
parent | 3a1cf48137752ef3640ae7b1e2937e4bed34d3b2 (diff) |
Implement the QEnum/QFlag decorator, V2
This implementation allows module-level and scoped QEnums
which are Python enum types. Scoped types are registered in Qt's meta
object system.
Usage of QEnum/QFlag with decorator or function call:
from enum import Enum, Flag, auto
from PySide2.QtCore import QEnum, QFlag, QObject
class Compass(QObject):
@QEnum
class Orientation(Enum):
North, East, South, West = range(4)
class Color(Flag):
RED = auto()
BLUE = auto()
GREEN = auto()
WHITE = RED | BLUE | GREEN
QFlag(Color)
Fixes: PYSIDE-957
Change-Id: Ie15f45cbd932c816b50724a96eee0c14ae1fdee8
Task-number: PYSIDE-487
Reviewed-by: Cristian Maureira-Fredes <cristian.maureira-fredes@qt.io>
Reviewed-by: Friedemann Kleint <Friedemann.Kleint@qt.io>
Diffstat (limited to 'sources')
19 files changed, 653 insertions, 27 deletions
diff --git a/sources/pyside2/PySide2/QtCore/typesystem_core_common.xml b/sources/pyside2/PySide2/QtCore/typesystem_core_common.xml index be5b0cf03..e79123398 100644 --- a/sources/pyside2/PySide2/QtCore/typesystem_core_common.xml +++ b/sources/pyside2/PySide2/QtCore/typesystem_core_common.xml @@ -656,6 +656,13 @@ </namespace-type> + <add-function signature="QEnum(PyObject*)" return-type="PyObject*"> + <inject-code class="target" position="beginning" file="../glue/qtcore.cpp" snippet="qt-qenum"/> + </add-function> + <add-function signature="QFlag(PyObject*)" return-type="PyObject*"> + <inject-code class="target" position="beginning" file="../glue/qtcore.cpp" snippet="qt-qflag"/> + </add-function> + <add-function signature="qAbs(double)" return-type="double"> <inject-code class="target" position="beginning" file="../glue/qtcore.cpp" snippet="qt-qabs"/> </add-function> diff --git a/sources/pyside2/PySide2/glue/qtcore.cpp b/sources/pyside2/PySide2/glue/qtcore.cpp index 1e9d9636e..8bd2baac1 100644 --- a/sources/pyside2/PySide2/glue/qtcore.cpp +++ b/sources/pyside2/PySide2/glue/qtcore.cpp @@ -587,6 +587,14 @@ Py_END_ALLOW_THREADS PySide::runCleanupFunctions(); // @snippet moduleshutdown +// @snippet qt-qenum +%PYARG_0 = PySide::QEnum::QEnumMacro(%1, false); +// @snippet qt-qenum + +// @snippet qt-qflag +%PYARG_0 = PySide::QEnum::QEnumMacro(%1, true); +// @snippet qt-qflag + // @snippet qt-pysideinit Shiboken::Conversions::registerConverterName(SbkPySide2_QtCoreTypeConverters[SBK_QSTRING_IDX], "unicode"); Shiboken::Conversions::registerConverterName(SbkPySide2_QtCoreTypeConverters[SBK_QSTRING_IDX], "str"); diff --git a/sources/pyside2/doc/extras/QtCore.QEnum.rst b/sources/pyside2/doc/extras/QtCore.QEnum.rst new file mode 100644 index 000000000..a5a2e31fd --- /dev/null +++ b/sources/pyside2/doc/extras/QtCore.QEnum.rst @@ -0,0 +1,92 @@ +.. currentmodule:: PySide2.QtCore +.. _QEnum: + +QEnum/QFlag +*********** + +This class decorator is equivalent to the `Q_ENUM` macro from Qt. +The decorator is used to register an Enum to the meta-object system, +which is available via `QObject.staticMetaObject`. +The enumerator must be in a QObject derived class to be registered. + + +Example +------- + +:: + + from enum import Enum, Flag, auto + + from PySide2.QtCore import QEnum, QFlag, QObject + + class Demo(QObject): + + @QEnum + class Orientation(Enum): + North, East, South, West = range(4) + + class Color(Flag): + RED = auto() + BLUE = auto() + GREEN = auto() + WHITE = RED | BLUE | GREEN + + QFlag(Color) # identical to @QFlag usage + + +Caution: +-------- + +QEnum registers a Python Enum derived class. +QFlag treats a variation of the Python Enum, the Flag class. + +Please do not confuse that with the Qt QFlags concept. Python does +not use that concept, it has its own class hierarchy, instead. +For more details, see the `Python enum documentation <https://docs.python.org/3/library/enum.html>`_. + + +Details about Qt Flags: +----------------------- + +There are some small differences between Qt flags and Python flags. +In Qt, we have for instance these declarations: + +:: + + enum QtGui::RenderHint { Antialiasing, TextAntialiasing, SmoothPixmapTransform, + HighQualityAntialiasing, NonCosmeticDefaultPen } + flags QtGui::RenderHints + +The equivalent Python notation would look like this: + +:: + + @QFlag + class RenderHints(enum.Flag) + Antialiasing = auto() + TextAntialiasing = auto() + SmoothPixmapTransform = auto() + HighQualityAntialiasing = auto() + NonCosmeticDefaultPen = auto() + + +As another example, the Qt::AlignmentFlag flag has 'AlignmentFlag' as the enum +name, but 'Alignment' as the type name. Non flag enums have the same type and +enum names. + +:: + + enum Qt::AlignmentFlag + flags Qt::Alignment + +The Python way to specify this would be + +:: + + @QFlag + class Alignment(enum.Flag): + ... + +We are considering to map all builtin enums and flags to Python enums as well +in a later release. + diff --git a/sources/pyside2/libpyside/CMakeLists.txt b/sources/pyside2/libpyside/CMakeLists.txt index 11342ec71..31f68749a 100644 --- a/sources/pyside2/libpyside/CMakeLists.txt +++ b/sources/pyside2/libpyside/CMakeLists.txt @@ -43,6 +43,7 @@ set(libpyside_SRC signalmanager.cpp globalreceiverv2.cpp pysideclassinfo.cpp + pysideqenum.cpp pysidemetafunction.cpp pysidesignal.cpp pysideslot.cpp @@ -125,6 +126,7 @@ endif() set(libpyside_HEADERS dynamicqmetaobject.h pysideclassinfo.h + pysideqenum.h pysidemacros.h signalmanager.h pyside.h diff --git a/sources/pyside2/libpyside/dynamicqmetaobject.cpp b/sources/pyside2/libpyside/dynamicqmetaobject.cpp index dae9e2059..efdf33ac9 100644 --- a/sources/pyside2/libpyside/dynamicqmetaobject.cpp +++ b/sources/pyside2/libpyside/dynamicqmetaobject.cpp @@ -44,6 +44,7 @@ #include "pysideproperty.h" #include "pysideproperty_p.h" #include "pysideslot_p.h" +#include "pysideqenum.h" #include <shiboken.h> @@ -91,6 +92,10 @@ 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 QVector<QPair<QByteArray, int> > &entries); void removeProperty(int index); const QMetaObject *update(); @@ -357,6 +362,28 @@ void MetaObjectBuilder::addInfo(const QMap<QByteArray, QByteArray> &info) m_d->addInfo(info); } +void MetaObjectBuilder::addEnumerator(const char *name, bool flag, bool scoped, + const QVector<QPair<QByteArray, int> > &entries) +{ + m_d->addEnumerator(name, flag, scoped, entries); +} + +void MetaObjectBuilderPrivate::addEnumerator(const char *name, bool flag, bool scoped, + const QVector<QPair<QByteArray, int> > &entries) +{ + auto builder = ensureBuilder(); + int have_already = builder->indexOfEnumerator(name); + if (have_already >= 0) + builder->removeEnumerator(have_already); + auto enumbuilder = builder->addEnumerator(name); + enumbuilder.setIsFlag(flag); + enumbuilder.setIsScoped(scoped); + + for (auto item : entries) + enumbuilder.addKey(item.first, item.second); + m_dirty = true; +} + void MetaObjectBuilderPrivate::removeProperty(int index) { index -= m_baseObject->propertyCount(); @@ -430,6 +457,8 @@ const QMetaObject *MetaObjectBuilder::update() return m_d->update(); } +using namespace Shiboken; + void MetaObjectBuilderPrivate::parsePythonType(PyTypeObject *type) { // Get all non-QObject-derived base types in method resolution order, filtering out the types @@ -439,7 +468,7 @@ void MetaObjectBuilderPrivate::parsePythonType(PyTypeObject *type) // existing connections. const PyObject *mro = type->tp_mro; const Py_ssize_t basesCount = PyTuple_GET_SIZE(mro); - PyTypeObject *qObjectType = Shiboken::Conversions::getPythonTypeObject("QObject*"); + PyTypeObject *qObjectType = Conversions::getPythonTypeObject("QObject*"); std::vector<PyTypeObject *> basesToCheck; // Prepend the actual type that we are parsing. @@ -470,7 +499,7 @@ void MetaObjectBuilderPrivate::parsePythonType(PyTypeObject *type) // Register signals. auto data = reinterpret_cast<PySideSignal *>(value); if (data->data->signalName.isEmpty()) - data->data->signalName = Shiboken::String::toCString(key); + data->data->signalName = String::toCString(key); for (const auto &s : data->data->signatures) { const auto sig = data->data->signalName + '(' + s.signature + ')'; if (m_baseObject->indexOfSignal(sig) == -1) { @@ -489,7 +518,7 @@ void MetaObjectBuilderPrivate::parsePythonType(PyTypeObject *type) } } - Shiboken::AutoDecRef slotAttrName(Shiboken::String::fromCString(PYSIDE_SLOT_LIST_ATTR)); + AutoDecRef slotAttrName(String::fromCString(PYSIDE_SLOT_LIST_ATTR)); // PYSIDE-315: Now take care of the rest. // Signals and slots should be separated, unless the types are modified, later. // We check for this using "is_sorted()". Sorting no longer happens at all. @@ -501,16 +530,16 @@ void MetaObjectBuilderPrivate::parsePythonType(PyTypeObject *type) while (PyDict_Next(attrs, &pos, &key, &value)) { if (Property::checkType(value)) { - const int index = m_baseObject->indexOfProperty(Shiboken::String::toCString(key)); + const int index = m_baseObject->indexOfProperty(String::toCString(key)); if (index == -1) - addProperty(Shiboken::String::toCString(key), value); + addProperty(String::toCString(key), value); } else if (PyFunction_Check(value)) { // Register slots. if (PyObject_HasAttr(value, slotAttrName)) { PyObject *signatureList = PyObject_GetAttr(value, slotAttrName); for (Py_ssize_t i = 0, i_max = PyList_Size(signatureList); i < i_max; ++i) { PyObject *pySignature = PyList_GET_ITEM(signatureList, i); - QByteArray signature(Shiboken::String::toCString(pySignature)); + QByteArray signature(String::toCString(pySignature)); // Split the slot type and its signature. QByteArray type; const int spacePos = signature.indexOf(' '); @@ -530,4 +559,29 @@ void MetaObjectBuilderPrivate::parsePythonType(PyTypeObject *type) } } } + // PYSIDE-957: Collect the delayed QEnums + auto collectedEnums = PySide::QEnum::resolveDelayedQEnums(type); + for (PyObject *obEnumType : collectedEnums) { + bool isFlag = PySide::QEnum::isFlag(obEnumType); + AutoDecRef obName(PyObject_GetAttr(obEnumType, PyMagicName::name())); + // Everything has been checked already in resolveDelayedQEnums. + // Therefore, we don't need to error-check here again. + auto name = String::toCString(obName); + AutoDecRef members(PyObject_GetAttr(obEnumType, PyMagicName::members())); + AutoDecRef items(PepMapping_Items(members)); + Py_ssize_t nr_items = PySequence_Length(items); + + QVector<QPair<QByteArray, int> > entries; + for (Py_ssize_t idx = 0; idx < nr_items; ++idx) { + AutoDecRef item(PySequence_GetItem(items, idx)); + AutoDecRef key(PySequence_GetItem(item, 0)); + AutoDecRef member(PySequence_GetItem(item, 1)); + AutoDecRef value(PyObject_GetAttr(member, Shiboken::PyName::value())); + auto ckey = String::toCString(key); + auto ivalue = PyInt_AsSsize_t(value); // int/long cheating + auto thing = QPair<QByteArray, int>(ckey, int(ivalue)); + entries.push_back(thing); + } + addEnumerator(name, isFlag, true, entries); + } } diff --git a/sources/pyside2/libpyside/dynamicqmetaobject.h b/sources/pyside2/libpyside/dynamicqmetaobject.h index 1fbe73ea4..7279d5c26 100644 --- a/sources/pyside2/libpyside/dynamicqmetaobject.h +++ b/sources/pyside2/libpyside/dynamicqmetaobject.h @@ -68,7 +68,10 @@ public: int addProperty(const char *property, PyObject *data); void addInfo(const char *key, const char *value); void addInfo(const QMap<QByteArray, QByteArray> &info); - + void addEnumerator(const char *name, + bool flag, + bool scoped, + const QVector<QPair<QByteArray, int> > &entries); void removeProperty(int index); const QMetaObject *update(); diff --git a/sources/pyside2/libpyside/pyside.cpp b/sources/pyside2/libpyside/pyside.cpp index 4a554de58..66e931164 100644 --- a/sources/pyside2/libpyside/pyside.cpp +++ b/sources/pyside2/libpyside/pyside.cpp @@ -223,8 +223,7 @@ std::size_t getSizeOfQObject(SbkObjectType *type) void initDynamicMetaObject(SbkObjectType *type, const QMetaObject *base, std::size_t cppObjSize) { //create DynamicMetaObject based on python type - auto userData = - new TypeUserData(reinterpret_cast<PyTypeObject *>(type), base, cppObjSize); + auto userData = new TypeUserData(reinterpret_cast<PyTypeObject *>(type), base, cppObjSize); userData->mo.update(); Shiboken::ObjectType::setTypeUserData(type, userData, Shiboken::callCppDestructor<TypeUserData>); diff --git a/sources/pyside2/libpyside/pysideqenum.cpp b/sources/pyside2/libpyside/pysideqenum.cpp new file mode 100644 index 000000000..f46b5536c --- /dev/null +++ b/sources/pyside2/libpyside/pysideqenum.cpp @@ -0,0 +1,258 @@ +/**************************************************************************** +** +** Copyright (C) 2020 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 <shiboken.h> + +#include "pysideqenum.h" +#include "dynamicqmetaobject.h" +#include "pyside_p.h" + + +/////////////////////////////////////////////////////////////// +// +// PYSIDE-957: Create QEnum dynamically from Python Enum +// +// +extern "C" { + +using namespace Shiboken; + +static PyObject *analyzePyEnum(PyObject *pyenum, PyObject *container = nullptr) +{ + /* + * This is the straight-forward implementation of QEnum/QFlag. It does no + * longer create an equivalent Qt enum but takes the Python enum as-is. + * + * It parses an Enum/Flag derived Python enum completely so that + * registering can be done without error checks. This would be impossible + * in MetaObjectBuilderPrivate::parsePythonType. + */ + AutoDecRef members(PyObject_GetAttr(pyenum, Shiboken::PyMagicName::members())); + if (members.isNull()) + return nullptr; + AutoDecRef items(PepMapping_Items(members)); + if (items.isNull()) + return nullptr; + int iflag = PySide::QEnum::isFlag(pyenum); + if (iflag < 0) + return nullptr; + Py_ssize_t nr_items = PySequence_Length(items); + if (nr_items < 0) + return nullptr; + + for (Py_ssize_t idx = 0; idx < nr_items; ++idx) { + AutoDecRef item(PySequence_GetItem(items, idx)); + if (item.isNull()) + return nullptr; + + // The item should be a 2-element sequence of the key name and an + // object containing the value. + AutoDecRef key(PySequence_GetItem(item, 0)); + AutoDecRef member(PySequence_GetItem(item, 1)); + if (key.isNull() || member.isNull()) + return nullptr; + if (!Shiboken::String::check(key)) { + // '%.200s' is the safety stringbuffer size of most CPython functions. + PyErr_Format(PyExc_TypeError, + "QEnum expected a string mapping as __members__, got '%.200s'", + Py_TYPE(key)->tp_name); + return nullptr; + } + + // Get the value. + AutoDecRef value(PyObject_GetAttr(member, Shiboken::PyName::value())); + if (value.isNull()) + return nullptr; + if (!PyInt_Check(value)) { // int/long cheating + PyErr_Format(PyExc_TypeError, + "QEnum expected an int value as '%.200s', got '%.200s'", + Shiboken::String::toCString(key), Py_TYPE(value)->tp_name); + return nullptr; + } + } + Py_RETURN_NONE; +} + +static Py_ssize_t get_lineno() +{ + PyObject *frame = reinterpret_cast<PyObject *>(PyEval_GetFrame()); // borrowed ref + AutoDecRef ob_lineno(PyObject_GetAttr(frame, Shiboken::PyName::f_lineno())); + if (ob_lineno.isNull() || !PyInt_Check(ob_lineno)) // int/long cheating + return -1; + return PyInt_AsSsize_t(ob_lineno); // int/long cheating +} + +static bool is_module_code() +{ + PyObject *frame = reinterpret_cast<PyObject *>(PyEval_GetFrame()); // borrowed ref + AutoDecRef ob_code(PyObject_GetAttr(frame, Shiboken::PyName::f_code())); + if (ob_code.isNull()) + return false; + AutoDecRef ob_name(PyObject_GetAttr(ob_code, Shiboken::PyName::co_name())); + if (ob_name.isNull()) + return false; + const char *codename = Shiboken::String::toCString(ob_name); + return strcmp(codename, "<module>") == 0; +} + +} // extern "C" + +namespace PySide { namespace QEnum { + +static std::map<int, PyObject *> enumCollector; + +int isFlag(PyObject *obType) +{ + /* + * Find out if this is an Enum or a Flag derived class. + * It checks also if things come from the enum module and if it is + * an Enum or Flag class at all. + * + * The function is called in MetaObjectBuilderPrivate::parsePythonType + * again to obtain the flag value. + */ + if (!PyType_Check(obType)) { + PyErr_Format(PyExc_TypeError, "a class argument was expected, not a '%.200s' instance", + Py_TYPE(obType)->tp_name); + return -1; + }; + auto *type = reinterpret_cast<PyTypeObject *>(obType); + PyObject *mro = type->tp_mro; + Py_ssize_t i, n = PyTuple_GET_SIZE(mro); + bool right_module = false; + bool have_enum = false; + bool have_flag = false; + bool have_members = PyObject_HasAttr(obType, PyMagicName::members()); + for (i = 0; i < n; i++) { + obType = PyTuple_GET_ITEM(mro, i); + type = reinterpret_cast<PyTypeObject *>(obType); + AutoDecRef mod(PyObject_GetAttr(obType, PyMagicName::module())); + QByteArray cmod = String::toCString(mod); + QByteArray cname = type->tp_name; + if (cmod == "enum") { + right_module = true; + if (cname == "Enum") + have_enum = true; + else if (cname == "Flag") + have_flag = true; + } + } + if (!right_module || !(have_enum || have_flag) || !have_members) { + PyErr_Format(PyExc_TypeError, "type %.200s does not inherit from 'Enum' or 'Flag'", + type->tp_name); + return -1; + } + return bool(have_flag); +} + +PyObject *QEnumMacro(PyObject *pyenum, bool flag) +{ + /* + * This is the official interface of 'QEnum'. It first calls 'analyzePyEnum'. + * When called as toplevel enum, it simply returns after some checks. + * Otherwise, 'pyenum' is stored for later use by the meta class registation. + */ + int computedFlag = isFlag(pyenum); + if (computedFlag < 0) + return nullptr; + if (bool(computedFlag) != flag) { + AutoDecRef name(PyObject_GetAttr(pyenum, PyMagicName::qualname())); + auto cname = String::toCString(name); + const char *e = "Enum"; + const char *f = "Flag"; + PyErr_Format(PyExc_TypeError, "expected '%s' but got '%s' (%.200s)", + flag ? f : e, flag ? e : f, cname); + return nullptr; + } + auto ok = analyzePyEnum(pyenum); + if (ok == nullptr) + return nullptr; + if (is_module_code()) { + // This is a toplevel enum which we resolve immediately. + Py_INCREF(pyenum); + return pyenum; + } + + Py_ssize_t lineno = get_lineno(); + if (lineno < 0) + return nullptr; + // Handle the rest via line number and the meta class. + Py_INCREF(pyenum); + Py_XDECREF(enumCollector[lineno]); + enumCollector[lineno] = pyenum; + Py_RETURN_NONE; +} + +std::vector<PyObject *> resolveDelayedQEnums(PyTypeObject *containerType) +{ + /* + * This is the internal interface of 'QEnum'. + * It is called at the end of the meta class call 'SbkObjectTypeTpNew' via + * MetaObjectBuilderPrivate::parsePythonType and resolves the collected + * Python Enum arguments. The result is then registered. + */ + if (enumCollector.empty()) + return {}; + PyObject *obContainerType = reinterpret_cast<PyObject *>(containerType); + Py_ssize_t lineno = get_lineno(); + + std::vector<PyObject *> result; + + auto it = enumCollector.begin(); + while (it != enumCollector.end()) { + int nr = it->first; + PyObject *pyenum = it->second; + if (nr >= lineno) { + AutoDecRef name(PyObject_GetAttr(pyenum, PyMagicName::name())); + if (name.isNull() || PyObject_SetAttr(obContainerType, name, pyenum) < 0) + return {}; + result.push_back(pyenum); + it = enumCollector.erase(it); + } else { + ++it; + } + } + return result; +} + +} // namespace Enum +} // namespace Shiboken + +// +/////////////////////////////////////////////////////////////// diff --git a/sources/pyside2/libpyside/pysideqenum.h b/sources/pyside2/libpyside/pysideqenum.h new file mode 100644 index 000000000..fc4e55982 --- /dev/null +++ b/sources/pyside2/libpyside/pysideqenum.h @@ -0,0 +1,57 @@ +/**************************************************************************** +** +** Copyright (C) 2020 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 PYSIDE_QENUM_H +#define PYSIDE_QENUM_H + +#include <pysidemacros.h> +#include <vector> + +namespace PySide { namespace QEnum { + +// PYSIDE-957: Support the QEnum macro +PYSIDE_API PyObject *QEnumMacro(PyObject *, bool); +PYSIDE_API int isFlag(PyObject *); +PYSIDE_API std::vector<PyObject *> resolveDelayedQEnums(PyTypeObject *); +PYSIDE_API void init(); + +} // namespace QEnum +} // namespace PySide + +#endif diff --git a/sources/pyside2/libpyside/pysidesignal.cpp b/sources/pyside2/libpyside/pysidesignal.cpp index 39ed1a6bc..32e1bb0c6 100644 --- a/sources/pyside2/libpyside/pysidesignal.cpp +++ b/sources/pyside2/libpyside/pysidesignal.cpp @@ -54,7 +54,6 @@ #include <utility> #define QT_SIGNAL_SENTINEL '2' -#define PyEnumMeta_Check(x) (strcmp(Py_TYPE(arg)->tp_name, "EnumMeta") == 0) namespace PySide { namespace Signal { diff --git a/sources/pyside2/tests/QtCore/qenum_test.py b/sources/pyside2/tests/QtCore/qenum_test.py index 1edb8981a..f99a893d9 100644 --- a/sources/pyside2/tests/QtCore/qenum_test.py +++ b/sources/pyside2/tests/QtCore/qenum_test.py @@ -2,7 +2,7 @@ ############################################################################# ## -## Copyright (C) 2016 The Qt Company Ltd. +## Copyright (C) 2020 The Qt Company Ltd. ## Contact: https://www.qt.io/licensing/ ## ## This file is part of the test suite of Qt for Python. @@ -40,7 +40,7 @@ sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) from init_paths import init_test_paths init_test_paths(False) -from PySide2.QtCore import Qt, QIODevice +from PySide2.QtCore import Qt, QIODevice, QObject, QEnum, QFlag class TestEnum(unittest.TestCase): @@ -68,28 +68,30 @@ class TestEnum(unittest.TestCase): # Floats with self.assertRaises(TypeError): - a = k+2.0 + a = k + 2.0 with self.assertRaises(TypeError): - a = k-2.0 + a = k - 2.0 with self.assertRaises(TypeError): - a = k*2.0 + a = k * 2.0 - @unittest.skipUnless(getattr(sys, "getobjects", None), "requires debug build") + @unittest.skipUnless(getattr(sys, "getobjects", None), "requires --with-trace-refs") + @unittest.skipUnless(getattr(sys, "gettotalrefcount", None), "requires --with-pydebug") def testEnumNew_NoLeak(self): gc.collect() total = sys.gettotalrefcount() for idx in range(1000): ret = Qt.Key(42) + gc.collect() delta = sys.gettotalrefcount() - total print("delta total refcount =", delta) if abs(delta) >= 10: - all = sys.getobjects(0) - all.sort(key=lambda x: sys.getrefcount(x), reverse=True) + all = [(sys.getrefcount(x), x) for x in sys.getobjects(0)] + all.sort(key=lambda x: x[0], reverse=True) for ob in all[:10]: - print(sys.getrefcount(ob), ob) + print(ob) self.assertTrue(abs(delta) < 10) @@ -141,6 +143,105 @@ class TestEnumPickling(unittest.TestCase): else: func() +# PYSIDE-957: The QEnum macro + +try: + import enum + HAVE_ENUM = True +except ImportError: + HAVE_ENUM = False + QEnum = QFlag = lambda x: x + import types + class Enum: pass + enum = types.ModuleType("enum") + enum.Enum = enum.Flag = enum.IntEnum = enum.IntFlag = Enum + Enum.__module__ = "enum" + Enum.__members__ = {} + del Enum + enum.auto = lambda: 42 + +HAVE_FLAG = hasattr(enum, "Flag") + +@QEnum +class OuterEnum(enum.Enum): + A = 1 + B = 2 + +class SomeClass(QObject): + + @QEnum + class SomeEnum(enum.Enum): + A = 1 + B = 2 + C = 3 + + @QEnum + class OtherEnum(enum.IntEnum): + A = 1 + B = 2 + C = 3 + + class InnerClass(QObject): + + @QEnum + class InnerEnum(enum.Enum): + X = 42 + + class SomeEnum(enum.Enum): + A = 4 + B = 5 + C = 6 + + QEnum(SomeEnum) # works even without the decorator assignment + + +@unittest.skipUnless(HAVE_ENUM, "requires 'enum' module (use 'pip install enum34' for Python 2)") +class TestQEnumMacro(unittest.TestCase): + def testTopLevel(self): + self.assertEqual(type(OuterEnum).__module__, "enum") + self.assertEqual(type(OuterEnum).__name__, "EnumMeta") + self.assertEqual(len(OuterEnum.__members__), 2) + + def testSomeClass(self): + self.assertEqual(type(SomeClass.SomeEnum).__module__, "enum") + self.assertEqual(type(SomeClass.SomeEnum).__name__, "EnumMeta") + self.assertEqual(len(SomeClass.SomeEnum.__members__), 3) + with self.assertRaises(TypeError): + int(SomeClass.SomeEnum.C) == 6 + self.assertEqual(SomeClass.OtherEnum.C, 3) + + @unittest.skipIf(sys.version_info[0] < 3, "we cannot support nested classes in Python 2") + def testInnerClass(self): + self.assertEqual(SomeClass.InnerClass.InnerEnum.__qualname__, + "SomeClass.InnerClass.InnerEnum") + with self.assertRaises(TypeError): + int(SomeClass.InnerClass.InnerEnum.X) == 42 + + @unittest.skipUnless(HAVE_FLAG, "some older Python versions have no 'Flag'") + def testEnumFlag(self): + with self.assertRaises(TypeError): + class WrongFlagForEnum(QObject): + @QEnum + class Bad(enum.Flag): + pass + with self.assertRaises(TypeError): + class WrongEnuForFlag(QObject): + @QFlag + class Bad(enum.Enum): + pass + + def testIsRegistered(self): + mo = SomeClass.staticMetaObject + self.assertEqual(mo.enumeratorCount(), 2) + self.assertEqual(mo.enumerator(0).name(), "OtherEnum") + self.assertEqual(mo.enumerator(0).scope(), "SomeClass") + self.assertEqual(mo.enumerator(1).name(), "SomeEnum") + moi = SomeClass.InnerClass.staticMetaObject + self.assertEqual(moi.enumerator(0).name(), "InnerEnum") + ## Question: Should that scope not better be "SomeClass.InnerClass"? + ## But we have __qualname__ already: + self.assertEqual(moi.enumerator(0).scope(), "InnerClass") + if __name__ == '__main__': unittest.main() diff --git a/sources/shiboken2/generator/shiboken2/cppgenerator.cpp b/sources/shiboken2/generator/shiboken2/cppgenerator.cpp index 840318020..1932a7997 100644 --- a/sources/shiboken2/generator/shiboken2/cppgenerator.cpp +++ b/sources/shiboken2/generator/shiboken2/cppgenerator.cpp @@ -312,6 +312,7 @@ void CppGenerator::generateClass(QTextStream &s, const GeneratorContext &classCo s << "#include <pysidesignal.h>\n" << "#include <pysideproperty.h>\n" << "#include <pyside.h>\n" + << "#include <pysideqenum.h>\n" << "#include <qapp_macro.h>\n\n" << "QT_WARNING_DISABLE_DEPRECATED\n\n"; } @@ -5642,6 +5643,7 @@ bool CppGenerator::finishGeneration() if (usePySideExtensions()) { s << includeQDebug; s << "#include <pyside.h>\n"; + s << "#include <pysideqenum.h>\n"; s << "#include <qapp_macro.h>\n"; } diff --git a/sources/shiboken2/libshiboken/basewrapper.cpp b/sources/shiboken2/libshiboken/basewrapper.cpp index 443d25cdf..d7184569b 100644 --- a/sources/shiboken2/libshiboken/basewrapper.cpp +++ b/sources/shiboken2/libshiboken/basewrapper.cpp @@ -486,7 +486,7 @@ void SbkObjectTypeDealloc(PyObject *pyObj) } } -PyObject *SbkObjectTypeTpNew(PyTypeObject *metatype, PyObject *args, PyObject *kwds) +static PyObject *SbkObjectTypeTpNew(PyTypeObject *metatype, PyObject *args, PyObject *kwds) { // Check if all bases are new style before calling type.tp_new // Was causing gc assert errors in test_bug704.py when @@ -513,7 +513,8 @@ PyObject *SbkObjectTypeTpNew(PyTypeObject *metatype, PyObject *args, PyObject *k #ifndef IS_PY3K if (PyClass_Check(baseType)) { PyErr_Format(PyExc_TypeError, "Invalid base class used in type %s. " - "PySide only support multiple inheritance from python new style class.", metatype->tp_name); + "PySide only supports multiple inheritance from Python new style classes.", + metatype->tp_name); return 0; } #endif @@ -579,7 +580,6 @@ PyObject *SbkObjectTypeTpNew(PyTypeObject *metatype, PyObject *args, PyObject *k if (PepType_SOTP(base)->subtype_init) PepType_SOTP(base)->subtype_init(newType, args, kwds); } - return reinterpret_cast<PyObject *>(newType); } diff --git a/sources/shiboken2/libshiboken/pep384impl.cpp b/sources/shiboken2/libshiboken/pep384impl.cpp index f07cac613..5e0053e2e 100644 --- a/sources/shiboken2/libshiboken/pep384impl.cpp +++ b/sources/shiboken2/libshiboken/pep384impl.cpp @@ -668,6 +668,22 @@ PyImport_GetModule(PyObject *name) } #endif // PY_VERSION_HEX < 0x03070000 || defined(Py_LIMITED_API) + +/***************************************************************************** + * + * Python 2 incompatibilities + * + * This is incompatibly implemented as macro in Python 2. + */ +#if PY_VERSION_HEX < 0x03000000 + +PyObject *PepMapping_Items(PyObject *o) +{ + return PyObject_CallMethod(o, const_cast<char *>("items"), NULL); +} + +#endif + /***************************************************************************** * * Extra support for name mangling diff --git a/sources/shiboken2/libshiboken/pep384impl.h b/sources/shiboken2/libshiboken/pep384impl.h index 541b0e775..2bfe52254 100644 --- a/sources/shiboken2/libshiboken/pep384impl.h +++ b/sources/shiboken2/libshiboken/pep384impl.h @@ -533,6 +533,18 @@ LIBSHIBOKEN_API PyObject *PyImport_GetModule(PyObject *name); /***************************************************************************** * + * Python 2 incompatibilities + * + * This is incompatibly implemented as macro in Python 2. + */ +#if PY_VERSION_HEX < 0x03000000 +extern LIBSHIBOKEN_API PyObject *PepMapping_Items(PyObject *o); +#else +#define PepMapping_Items PyMapping_Items +#endif + +/***************************************************************************** + * * Runtime support for Python 3.8 incompatibilities * */ diff --git a/sources/shiboken2/libshiboken/sbkenum.cpp b/sources/shiboken2/libshiboken/sbkenum.cpp index f9a43845a..369b264e7 100644 --- a/sources/shiboken2/libshiboken/sbkenum.cpp +++ b/sources/shiboken2/libshiboken/sbkenum.cpp @@ -608,11 +608,16 @@ newItem(PyTypeObject *enumType, long itemValue, const char *itemName) enumObj->ob_value = itemValue; if (newValue) { - PyObject *values = PyDict_GetItem(enumType->tp_dict, Shiboken::PyName::values()); - if (!values) { - values = PyDict_New(); - PyDict_SetItem(enumType->tp_dict, Shiboken::PyName::values(), values); - Py_DECREF(values); // ^ values still alive, because setitem increfs it + auto dict = enumType->tp_dict; // Note: 'values' is borrowed + PyObject *values = PyDict_GetItemWithError(dict, Shiboken::PyName::values()); + if (values == nullptr) { + if (PyErr_Occurred()) + return nullptr; + Shiboken::AutoDecRef new_values(values = PyDict_New()); + if (values == nullptr) + return nullptr; + if (PyDict_SetItem(dict, Shiboken::PyName::values(), values) < 0) + return nullptr; } PyDict_SetItemString(values, itemName, reinterpret_cast<PyObject *>(enumObj)); } diff --git a/sources/shiboken2/libshiboken/sbkpython.h b/sources/shiboken2/libshiboken/sbkpython.h index 9dd1e712e..6755e945d 100644 --- a/sources/shiboken2/libshiboken/sbkpython.h +++ b/sources/shiboken2/libshiboken/sbkpython.h @@ -41,6 +41,7 @@ #define SBKPYTHON_H #include "sbkversion.h" +#define PyEnumMeta_Check(x) (strcmp(Py_TYPE(x)->tp_name, "EnumMeta") == 0) // Qt's "slots" macro collides with the "slots" member variables // used in some Python structs. For compilers that support push_macro, diff --git a/sources/shiboken2/libshiboken/sbkstaticstrings.cpp b/sources/shiboken2/libshiboken/sbkstaticstrings.cpp index c19665176..541d74918 100644 --- a/sources/shiboken2/libshiboken/sbkstaticstrings.cpp +++ b/sources/shiboken2/libshiboken/sbkstaticstrings.cpp @@ -55,11 +55,15 @@ namespace PyName { STATIC_STRING_IMPL(dumps, "dumps") STATIC_STRING_IMPL(loads, "loads") STATIC_STRING_IMPL(result, "result") +STATIC_STRING_IMPL(value, "value") STATIC_STRING_IMPL(values, "values") // Internal: STATIC_STRING_IMPL(classmethod, "classmethod") +STATIC_STRING_IMPL(co_name, "co_name") STATIC_STRING_IMPL(compile, "compile"); +STATIC_STRING_IMPL(f_code, "f_code") +STATIC_STRING_IMPL(f_lineno, "f_lineno") STATIC_STRING_IMPL(function, "function") STATIC_STRING_IMPL(marshal, "marshal") STATIC_STRING_IMPL(method, "method") @@ -73,6 +77,7 @@ namespace PyMagicName { STATIC_STRING_IMPL(class_, "__class__") STATIC_STRING_IMPL(ecf, "__ecf__") STATIC_STRING_IMPL(file, "__file__") +STATIC_STRING_IMPL(members, "__members__") STATIC_STRING_IMPL(module, "__module__") STATIC_STRING_IMPL(name, "__name__") STATIC_STRING_IMPL(qualname, "__qualname__") diff --git a/sources/shiboken2/libshiboken/sbkstaticstrings.h b/sources/shiboken2/libshiboken/sbkstaticstrings.h index 07d6cc60a..4078d163c 100644 --- a/sources/shiboken2/libshiboken/sbkstaticstrings.h +++ b/sources/shiboken2/libshiboken/sbkstaticstrings.h @@ -48,9 +48,13 @@ namespace Shiboken // Some often-used strings namespace PyName { +LIBSHIBOKEN_API PyObject *co_name(); LIBSHIBOKEN_API PyObject *dumps(); +LIBSHIBOKEN_API PyObject *f_code(); +LIBSHIBOKEN_API PyObject *f_lineno(); LIBSHIBOKEN_API PyObject *loads(); LIBSHIBOKEN_API PyObject *result(); +LIBSHIBOKEN_API PyObject *value(); LIBSHIBOKEN_API PyObject *values(); } // namespace PyName @@ -59,6 +63,7 @@ namespace PyMagicName LIBSHIBOKEN_API PyObject *class_(); LIBSHIBOKEN_API PyObject *ecf(); LIBSHIBOKEN_API PyObject *file(); +LIBSHIBOKEN_API PyObject *members(); LIBSHIBOKEN_API PyObject *module(); LIBSHIBOKEN_API PyObject *name(); LIBSHIBOKEN_API PyObject *qualname(); |