aboutsummaryrefslogtreecommitdiffstats
path: root/sources
diff options
context:
space:
mode:
authorChristian Tismer <tismer@stackless.com>2020-06-07 17:05:32 +0200
committerChristian Tismer <tismer@stackless.com>2020-06-15 10:04:39 +0200
commitd7c52752eaa1f6017a4a8fc0218a0bdd8ea7ea0a (patch)
tree23152f460d1750ae50363978f79ed9e6e839abf0 /sources
parent3a1cf48137752ef3640ae7b1e2937e4bed34d3b2 (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')
-rw-r--r--sources/pyside2/PySide2/QtCore/typesystem_core_common.xml7
-rw-r--r--sources/pyside2/PySide2/glue/qtcore.cpp8
-rw-r--r--sources/pyside2/doc/extras/QtCore.QEnum.rst92
-rw-r--r--sources/pyside2/libpyside/CMakeLists.txt2
-rw-r--r--sources/pyside2/libpyside/dynamicqmetaobject.cpp66
-rw-r--r--sources/pyside2/libpyside/dynamicqmetaobject.h5
-rw-r--r--sources/pyside2/libpyside/pyside.cpp3
-rw-r--r--sources/pyside2/libpyside/pysideqenum.cpp258
-rw-r--r--sources/pyside2/libpyside/pysideqenum.h57
-rw-r--r--sources/pyside2/libpyside/pysidesignal.cpp1
-rw-r--r--sources/pyside2/tests/QtCore/qenum_test.py119
-rw-r--r--sources/shiboken2/generator/shiboken2/cppgenerator.cpp2
-rw-r--r--sources/shiboken2/libshiboken/basewrapper.cpp6
-rw-r--r--sources/shiboken2/libshiboken/pep384impl.cpp16
-rw-r--r--sources/shiboken2/libshiboken/pep384impl.h12
-rw-r--r--sources/shiboken2/libshiboken/sbkenum.cpp15
-rw-r--r--sources/shiboken2/libshiboken/sbkpython.h1
-rw-r--r--sources/shiboken2/libshiboken/sbkstaticstrings.cpp5
-rw-r--r--sources/shiboken2/libshiboken/sbkstaticstrings.h5
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();