aboutsummaryrefslogtreecommitdiffstats
path: root/sources
diff options
context:
space:
mode:
authorFriedemann Kleint <Friedemann.Kleint@qt.io>2021-11-09 13:31:22 +0100
committerFriedemann Kleint <Friedemann.Kleint@qt.io>2021-11-15 08:48:44 +0000
commita421aab61ad03afa595aa0ba7e53d84b1edbe892 (patch)
tree0ff3173d7e051d5a93ad9e54a0785f2b9ddedc0f /sources
parentf160c0f1f2053029c118eb5818074f875a3776b4 (diff)
Add a QmlUncreatable decorator
Add a class type that stores the reason and registers the type in the call operator. This is then queried by the register type helper functions. Pick-to: 6.2 Task-number: PYSIDE-1709 Change-Id: I0dd0c4c1e05e6e0ee6f22a6947b85546fc373fb9 Reviewed-by: Fabian Kosmale <fabian.kosmale@qt.io>
Diffstat (limited to 'sources')
-rw-r--r--sources/pyside6/PySide6/QtQml/CMakeLists.txt1
-rw-r--r--sources/pyside6/PySide6/QtQml/pysideqmlregistertype.cpp6
-rw-r--r--sources/pyside6/PySide6/QtQml/pysideqmluncreatable.cpp196
-rw-r--r--sources/pyside6/PySide6/QtQml/pysideqmluncreatable.h69
-rw-r--r--sources/pyside6/PySide6/QtQml/typesystem_qml.xml1
-rw-r--r--sources/pyside6/PySide6/glue/qtqml.cpp1
-rw-r--r--sources/pyside6/doc/extras/QtQml.QmlUncreatable.rst30
-rw-r--r--sources/pyside6/doc/extras/QtQml.qmlRegisterUncreatableType.rst2
-rw-r--r--sources/pyside6/tests/QtQml/registeruncreatabletype.py10
9 files changed, 311 insertions, 5 deletions
diff --git a/sources/pyside6/PySide6/QtQml/CMakeLists.txt b/sources/pyside6/PySide6/QtQml/CMakeLists.txt
index b23d3b12d..63ca1f7a4 100644
--- a/sources/pyside6/PySide6/QtQml/CMakeLists.txt
+++ b/sources/pyside6/PySide6/QtQml/CMakeLists.txt
@@ -3,6 +3,7 @@ project(QtQml)
set(QtQml_static_sources "${QtQml_SOURCE_DIR}/pysideqmlregistertype.cpp"
"${QtQml_SOURCE_DIR}/pysideqmlmetacallerror.cpp"
"${QtQml_SOURCE_DIR}/pysideqmllistproperty.cpp"
+ "${QtQml_SOURCE_DIR}/pysideqmluncreatable.cpp"
"${QtQml_SOURCE_DIR}/pysideqmlvolatilebool.cpp")
set(QtQml_SRC
diff --git a/sources/pyside6/PySide6/QtQml/pysideqmlregistertype.cpp b/sources/pyside6/PySide6/QtQml/pysideqmlregistertype.cpp
index ffa337b93..1dd0507f7 100644
--- a/sources/pyside6/PySide6/QtQml/pysideqmlregistertype.cpp
+++ b/sources/pyside6/PySide6/QtQml/pysideqmlregistertype.cpp
@@ -39,6 +39,7 @@
#include "pysideqmlregistertype.h"
#include "pysideqmlregistertype_p.h"
+#include "pysideqmluncreatable.h"
#include <limits>
@@ -422,7 +423,10 @@ static PyObject *qmlElementMacroHelper(PyObject *pyObj,
PyObject *PySide::qmlElementMacro(PyObject *pyObj)
{
- return qmlElementMacroHelper(pyObj, "QmlElement");
+ auto *noCreationReason = PySide::qmlNoCreationReason(pyObj);
+ const auto mode = noCreationReason != nullptr
+ ? RegisterMode::Uncreatable : RegisterMode::Normal;
+ return qmlElementMacroHelper(pyObj, "QmlElement", mode, noCreationReason);
}
PyObject *PySide::qmlAnonymousMacro(PyObject *pyObj)
diff --git a/sources/pyside6/PySide6/QtQml/pysideqmluncreatable.cpp b/sources/pyside6/PySide6/QtQml/pysideqmluncreatable.cpp
new file mode 100644
index 000000000..e59476146
--- /dev/null
+++ b/sources/pyside6/PySide6/QtQml/pysideqmluncreatable.cpp
@@ -0,0 +1,196 @@
+/****************************************************************************
+**
+** Copyright (C) 2021 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 "pysideqmluncreatable.h"
+
+#include <shiboken.h>
+#include <signature.h>
+#include <sbkcppstring.h>
+
+#include <string>
+#include <unordered_map>
+
+#include <QtCore/QtGlobal>
+
+struct PySideQmlUncreatablePrivate
+{
+ std::string reason;
+};
+
+using UncreatableReasonMap = std::unordered_map<PyObject *, std::string>;
+
+// Types and their nocreationReason. FIXME: Store this in PySide::TypeUserData
+// once it is moved to libpyside?
+UncreatableReasonMap &uncreatableReasonMap()
+{
+ static UncreatableReasonMap result;
+ return result;
+}
+
+extern "C"
+{
+
+// The call operator is passed the class type and registers the reason
+// in the uncreatableReasonMap()
+static PyObject *classCall(PyObject *self, PyObject *args, PyObject * /* kw */)
+{
+ if (!PyTuple_Check(args) || PyTuple_Size(args) != 1) {
+ PyErr_Format(PyExc_TypeError,
+ "The QmlUncreatable decorator takes exactly 1 positional argument (%zd given)",
+ PyTuple_Size(args));
+ return nullptr;
+ }
+
+ PyObject *klass = PyTuple_GetItem(args, 0);
+ // This will sometimes segfault if you mistakenly use it on a function declaration
+ if (!PyType_Check(klass)) {
+ PyErr_SetString(PyExc_TypeError,
+ "This decorator can only be used on class declarations");
+ return nullptr;
+ }
+
+ PyTypeObject *klassType = reinterpret_cast<PyTypeObject *>(klass);
+ if (!Shiboken::ObjectType::checkType(klassType)) {
+ PyErr_SetString(PyExc_TypeError,
+ "This decorator can only be used on classes that are subclasses of QObject");
+ return nullptr;
+ }
+
+ auto data = reinterpret_cast<PySideQmlUncreatable *>(self);
+ uncreatableReasonMap().insert({klass, data->d->reason});
+
+ Py_INCREF(klass);
+ return klass;
+}
+
+static PyObject *qmlUncreatableTpNew(PyTypeObject *subtype, PyObject * /* args */,
+ PyObject * /* kwds */)
+{
+ auto *me = reinterpret_cast<PySideQmlUncreatable *>(subtype->tp_alloc(subtype, 0));
+ me->d = new PySideQmlUncreatablePrivate;
+ return reinterpret_cast<PyObject *>(me);
+}
+
+static int qmlUncreatableTpInit(PyObject *self, PyObject *args, PyObject * /* kwds */)
+{
+ PySideQmlUncreatable *data = reinterpret_cast<PySideQmlUncreatable *>(self);
+ PySideQmlUncreatablePrivate *pData = data->d;
+
+ bool ok = false;
+ const auto argsCount = PyTuple_Size(args);
+ if (argsCount == 0) {
+ ok = true; // QML-generated reason
+ } else if (argsCount == 1) {
+ PyObject *arg = PyTuple_GET_ITEM(args, 0);
+ if (arg == Py_None) {
+ ok = true; // QML-generated reason
+ } else if (PyUnicode_Check(arg)) {
+ ok = true;
+ Shiboken::String::toCppString(arg, &(pData->reason));
+ }
+ }
+
+ if (!ok) {
+ PyErr_Format(PyExc_TypeError,
+ "QmlUncreatable() takes a single string argument or no argument");
+ return -1;
+ }
+
+ return 0;
+}
+
+static void qmlUncreatableFree(void *self)
+{
+ auto pySelf = reinterpret_cast<PyObject *>(self);
+ auto data = reinterpret_cast<PySideQmlUncreatable *>(self);
+
+ delete data->d;
+ Py_TYPE(pySelf)->tp_base->tp_free(self);
+}
+
+static PyType_Slot PySideQmlUncreatableType_slots[] = {
+ {Py_tp_call, reinterpret_cast<void *>(classCall)},
+ {Py_tp_init, reinterpret_cast<void *>(qmlUncreatableTpInit)},
+ {Py_tp_new, reinterpret_cast<void *>(qmlUncreatableTpNew)},
+ {Py_tp_free, reinterpret_cast<void *>(qmlUncreatableFree)},
+ {Py_tp_dealloc, reinterpret_cast<void *>(Sbk_object_dealloc)},
+ {0, nullptr}
+};
+
+static PyType_Spec PySideQmlUncreatableType_spec = {
+ "2:PySide6.QtCore.qmlUncreatable",
+ sizeof(PySideQmlUncreatable),
+ 0,
+ Py_TPFLAGS_DEFAULT,
+ PySideQmlUncreatableType_slots,
+};
+
+PyTypeObject *PySideQmlUncreatableTypeF(void)
+{
+ static auto *type = SbkType_FromSpec(&PySideQmlUncreatableType_spec);
+ return type;
+}
+
+} // extern "C"
+
+static const char *qmlUncreatable_SignatureStrings[] = {
+ "PySide6.QtQml.QmlUncreatable(self,reason:str)",
+ nullptr // Sentinel
+};
+
+void initQmlUncreatable(PyObject *module)
+{
+ if (InitSignatureStrings(PySideQmlUncreatableTypeF(), qmlUncreatable_SignatureStrings) < 0)
+ return;
+
+ Py_INCREF(PySideQmlUncreatableTypeF());
+ PyModule_AddObject(module, "QmlUncreatable",
+ reinterpret_cast<PyObject *>(PySideQmlUncreatableTypeF()));
+}
+
+namespace PySide
+{
+const char *qmlNoCreationReason(PyObject *type)
+{
+ const auto &map = uncreatableReasonMap();
+ auto it = map.find(type);
+ return it != map.cend() ? it->second.c_str() : nullptr;
+}
+
+} // PySide
diff --git a/sources/pyside6/PySide6/QtQml/pysideqmluncreatable.h b/sources/pyside6/PySide6/QtQml/pysideqmluncreatable.h
new file mode 100644
index 000000000..89a5f1223
--- /dev/null
+++ b/sources/pyside6/PySide6/QtQml/pysideqmluncreatable.h
@@ -0,0 +1,69 @@
+/****************************************************************************
+**
+** Copyright (C) 2021 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 PYSIDEQMLUNCREATABLE_H
+#define PYSIDEQMLUNCREATABLE_H
+
+#include <sbkpython.h>
+
+// The QmlUncreatable decorator modifies QmlElement to register an uncreatable
+// type. Due to the (reverse) execution order of decorators, it needs to follow
+// QmlElement.
+extern "C"
+{
+ extern PyTypeObject *PySideQmlUncreatableTypeF(void);
+
+ struct PySideQmlUncreatablePrivate;
+ struct PySideQmlUncreatable
+ {
+ PyObject_HEAD
+ PySideQmlUncreatablePrivate* d;
+ };
+
+} // extern "C"
+
+void initQmlUncreatable(PyObject *module);
+
+namespace PySide
+{
+ // Return the reason if a type is not creatable.
+ const char *qmlNoCreationReason(PyObject *type);
+} // PySide
+
+#endif // PYSIDEQMLUNCREATABLE_H
diff --git a/sources/pyside6/PySide6/QtQml/typesystem_qml.xml b/sources/pyside6/PySide6/QtQml/typesystem_qml.xml
index 5772202ea..15f44c276 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 "pysideqmlregistertype.h"
#include "pysideqmllistproperty.h"
+ #include "pysideqmluncreatable.h"
#include "pysideqmlvolatilebool.h"
#include "pysideqmlmetacallerror_p.h"
#include &lt;signalmanager.h&gt;
diff --git a/sources/pyside6/PySide6/glue/qtqml.cpp b/sources/pyside6/PySide6/glue/qtqml.cpp
index 44f3dc653..73b44faf2 100644
--- a/sources/pyside6/PySide6/glue/qtqml.cpp
+++ b/sources/pyside6/PySide6/glue/qtqml.cpp
@@ -74,6 +74,7 @@ int %0 = PySide::qmlRegisterType(%ARGUMENT_NAMES, false);
// @snippet init
initQtQmlListProperty(module);
+initQmlUncreatable(module);
initQtQmlVolatileBool(module);
PySide::SignalManager::setQmlMetaCallErrorHandler(PySide::qmlMetaCallErrorHandler);
// @snippet init
diff --git a/sources/pyside6/doc/extras/QtQml.QmlUncreatable.rst b/sources/pyside6/doc/extras/QtQml.QmlUncreatable.rst
new file mode 100644
index 000000000..ef6803cee
--- /dev/null
+++ b/sources/pyside6/doc/extras/QtQml.QmlUncreatable.rst
@@ -0,0 +1,30 @@
+.. currentmodule:: PySide6.QtQml
+.. _QmlUncreatable:
+
+QmlUncreatable
+**************
+
+.. py:decorator:: QmlUncreatable
+
+Declares that the decorated type shall not be creatable from QML. This takes
+effect if the type is available in QML, by a preceding ``QmlElement``
+decorator. The reason will be emitted as error message if an attempt to create
+the type from QML is detected.
+
+Some QML types are implicitly uncreatable, in particular types exposed with
+``QmlAnonymous``.
+
+Passing None or no argument will cause a standard message to be used instead.
+
+.. code-block:: python
+ QML_IMPORT_NAME = "com.library.name"
+ QML_IMPORT_MAJOR_VERSION = 1
+ QML_IMPORT_MINOR_VERSION = 0 # Optional
+
+
+ @QmlElement
+ @QmlUncreatable("BaseClassForQml is an abstract base class")
+ class BaseClassForQml(QObject):
+ # ...
+
+.. note:: The order of the decorators matters, ``QmlUncreatable`` needs to be preceded by ``QmlElement``.
diff --git a/sources/pyside6/doc/extras/QtQml.qmlRegisterUncreatableType.rst b/sources/pyside6/doc/extras/QtQml.qmlRegisterUncreatableType.rst
index 2f34cd987..be25f5f1e 100644
--- a/sources/pyside6/doc/extras/QtQml.qmlRegisterUncreatableType.rst
+++ b/sources/pyside6/doc/extras/QtQml.qmlRegisterUncreatableType.rst
@@ -34,3 +34,5 @@ qmlRegisterUncreatableType
Indeed, it is normal for the new library to allow QML written to
previous versions to continue to work, even if more advanced
versions of some of its types are available.
+
+ Alternatively, the :ref:`QmlUncreatable` decorator can be used.
diff --git a/sources/pyside6/tests/QtQml/registeruncreatabletype.py b/sources/pyside6/tests/QtQml/registeruncreatabletype.py
index c9c9b3bae..67cf1a3b6 100644
--- a/sources/pyside6/tests/QtQml/registeruncreatabletype.py
+++ b/sources/pyside6/tests/QtQml/registeruncreatabletype.py
@@ -39,11 +39,16 @@ from helper.helper import qmlcomponent_errorstring
from PySide6.QtCore import Property, QObject, QUrl
from PySide6.QtGui import QGuiApplication
-from PySide6.QtQml import qmlRegisterUncreatableType, QQmlEngine, QQmlComponent
+from PySide6.QtQml import QmlElement, QmlUncreatable, QQmlEngine, QQmlComponent
noCreationReason = 'Cannot create an item of type: Uncreatable (expected)'
+QML_IMPORT_NAME = "Charts"
+QML_IMPORT_MAJOR_VERSION = 1
+
+@QmlElement
+@QmlUncreatable(noCreationReason)
class Uncreatable(QObject):
def __init__(self, parent=None):
QObject.__init__(self, parent)
@@ -63,9 +68,6 @@ class TestQmlSupport(unittest.TestCase):
def testIt(self):
app = QGuiApplication([])
- self.assertTrue(qmlRegisterUncreatableType(Uncreatable, 'Charts', 1, 0,
- 'Uncreatable', noCreationReason) != -1)
-
engine = QQmlEngine()
file = Path(__file__).resolve().parent / 'registeruncreatable.qml'
self.assertTrue(file.is_file())