diff options
-rw-r--r-- | src/qml/doc/src/qmlfunctions.qdoc | 97 | ||||
-rw-r--r-- | src/qml/qml/qqml.cpp | 20 | ||||
-rw-r--r-- | src/qml/qml/qqml.h | 9 | ||||
-rw-r--r-- | src/qml/qml/qqmlprivate.h | 7 | ||||
-rw-r--r-- | tests/auto/qml/qqmlengine/tst_qqmlengine.cpp | 40 |
5 files changed, 171 insertions, 2 deletions
diff --git a/src/qml/doc/src/qmlfunctions.qdoc b/src/qml/doc/src/qmlfunctions.qdoc index ab54b5fd1d..9c106558fd 100644 --- a/src/qml/doc/src/qmlfunctions.qdoc +++ b/src/qml/doc/src/qmlfunctions.qdoc @@ -487,8 +487,7 @@ A QObject singleton type may be referenced via the type name with which it was registered, and this typename may be used as the target in a \l Connections type or otherwise used as any other type id would. - One exception to this is that a QObject singleton type property may not be aliased (because the - singleton type name does not identify an object within the same component as any other item). + One exception to this is that a QObject singleton type property may not be aliased. \b{NOTE:} A QObject singleton type instance returned from a singleton type provider is owned by the QML engine unless the object has explicit QQmlEngine::CppOwnership flag set. @@ -619,6 +618,100 @@ */ /*! + \fn int qmlRegisterSingletonInstance(const char *uri, int versionMajor, int + versionMinor, const char *typeName, QObject* cppObject) + \relates QQmlEngine + \since 5.14 + + This function is used to register a singleton object \a cppObject, with a + particular \a uri and \a typeName. Its version is a combination of \a + versionMajor and \a versionMinor. + + Installing a singleton type into a URI allows you to provide arbitrary + functionality (methods and properties) to QML code without requiring + individual instances of the type to be instantiated by the client. + + Use this function to register an object of the given type T as a singleton + type. + + A QObject singleton type may be referenced via the type name with which it + was registered; in turn this type name may be used as the target in a \l + Connections type, or like any other type ID. However, there's one + exception: a QObject singleton type property can't be aliased because the + singleton type name does not identify an object within the same component + as any other item. + + \note \a cppObject must outlive the QML engine in which it is used. + Moreover, \cppObject must have the same thread affinity as the engine. If + you want separate singleton instances for multiple engines, you need to use + \l {qmlRegisterSingletonType}. See \l{Threads and QObjects} for more + information about thread safety. + + Usage: + \code + // First, define your QObject which provides the functionality. + class SingletonTypeExample : public QObject + { + Q_OBJECT + Q_PROPERTY(int someProperty READ someProperty WRITE setSomeProperty NOTIFY somePropertyChanged) + + public: + explicit SingletonTypeExample(QObject* parent = nullptr) : QObject(parent) {} + + Q_INVOKABLE int doSomething() + { + setSomeProperty(5); + return m_someProperty; + } + + int someProperty() const { return m_someProperty; } + void setSomeProperty(int val) { + if (m_someProperty != val) { + m_someProperty = val; + emit somePropertyChanged(val); + } + } + + signals: + void somePropertyChanged(int newValue); + + private: + int m_someProperty = 0; + }; + \endcode + + \code + // Second, create an instance of the object + + // allocate example before the engine to ensure that it outlives it + QScopedPointer<SingletonTypeExample> example(new SingletonTypeExample); + QQmlEngine engine; + + // Third, register the singleton type provider with QML by calling this + // function in an initialization function. + qmlRegisterSingletonInstance("Qt.example.qobjectSingleton", 1, 0, "MyApi", example.get()); + \endcode + + + In order to use the registered singleton type in QML, you must import the + URI with the corresponding version. + \qml + import QtQuick 2.0 + import Qt.example.qobjectSingleton 1.0 + Item { + id: root + property int someValue: MyApi.someProperty + + Component.onCompleted: { + console.log(MyApi.doSomething()) + } + } + \endqml + + \sa qmlRegisterSingletonType + */ + +/*! \fn int qmlRegisterType(const QUrl &url, const char *uri, int versionMajor, int versionMinor, const char *qmlName); \relates QQmlEngine diff --git a/src/qml/qml/qqml.cpp b/src/qml/qml/qqml.cpp index 613816e3f7..8a50b51b5d 100644 --- a/src/qml/qml/qqml.cpp +++ b/src/qml/qml/qqml.cpp @@ -76,6 +76,26 @@ int qmlTypeId(const char *uri, int versionMajor, int versionMinor, const char *q return QQmlMetaType::typeId(uri, versionMajor, versionMinor, qmlName); } +// From qqmlprivate.h +QObject *QQmlPrivate::RegisterSingletonFunctor::operator()(QQmlEngine *qeng, QJSEngine *) +{ + if (qeng->thread() != m_object->thread()) { + QQmlError error; + error.setDescription(QLatin1String("Registered object must live in the same thread as the engine it was registered with")); + QQmlEnginePrivate::get(qeng)->warning(qeng, error); + return nullptr; + } + if (alreadyCalled) { + QQmlError error; + error.setDescription(QLatin1String("Singleton registered by registerSingletonInstance must only be accessed from one engine")); + QQmlEnginePrivate::get(qeng)->warning(qeng, error); + return nullptr; + } + alreadyCalled = true; + qeng->setObjectOwnership(m_object, QQmlEngine::CppOwnership); + return m_object; +}; + /* This method is "over generalized" to allow us to (potentially) register more types of things in the future without adding exported symbols. diff --git a/src/qml/qml/qqml.h b/src/qml/qml/qqml.h index 4f3bfb76b0..bfd1c88b28 100644 --- a/src/qml/qml/qqml.h +++ b/src/qml/qml/qqml.h @@ -667,6 +667,15 @@ inline int qmlRegisterSingletonType(const char *uri, int versionMajor, int versi return QQmlPrivate::qmlregister(QQmlPrivate::SingletonRegistration, &api); } +template<typename T> +inline auto qmlRegisterSingletonInstance(const char *uri, int versionMajor, int versionMinor, + const char *typeName, T *cppObject) -> typename std::enable_if<std::is_base_of<QObject, T>::value, int>::type +{ + QQmlPrivate::RegisterSingletonFunctor registrationFunctor; + registrationFunctor.m_object = cppObject; + return qmlRegisterSingletonType<T>(uri, versionMajor, versionMinor, typeName, registrationFunctor); +} + inline int qmlRegisterSingletonType(const QUrl &url, const char *uri, int versionMajor, int versionMinor, const char *qmlName) { if (url.isRelative()) { diff --git a/src/qml/qml/qqmlprivate.h b/src/qml/qml/qqmlprivate.h index eff78195d9..e6dd5e0b16 100644 --- a/src/qml/qml/qqmlprivate.h +++ b/src/qml/qml/qqmlprivate.h @@ -322,6 +322,13 @@ namespace QQmlPrivate int Q_QML_EXPORT qmlregister(RegistrationType, void *); void Q_QML_EXPORT qmlunregister(RegistrationType, quintptr); + struct Q_QML_EXPORT RegisterSingletonFunctor + { + QObject *operator()(QQmlEngine *, QJSEngine *); + + QObject *m_object; + bool alreadyCalled = false; + }; } QT_END_NAMESPACE diff --git a/tests/auto/qml/qqmlengine/tst_qqmlengine.cpp b/tests/auto/qml/qqmlengine/tst_qqmlengine.cpp index 77ebff313e..aae42e9ebb 100644 --- a/tests/auto/qml/qqmlengine/tst_qqmlengine.cpp +++ b/tests/auto/qml/qqmlengine/tst_qqmlengine.cpp @@ -1037,6 +1037,46 @@ void tst_qqmlengine::singletonInstance() { qmlRegisterSingletonType<CppSingleton>("Qt.test",1,0,"NotAmbiguous", [](QQmlEngine* qeng, QJSEngine* jeng) -> QObject* {return CppSingleton::create(qeng, jeng);}); // test that overloads for qmlRegisterSingleton are not ambiguous } + { + // Register QObject* directly + CppSingleton single; + int id = qmlRegisterSingletonInstance("Qt.test", 1, 0, "CppOwned", + &single); + QQmlEngine engine2; + CppSingleton *singlePtr = engine2.singletonInstance<CppSingleton *>(id); + QVERIFY(singlePtr); + QCOMPARE(&single, singlePtr); + QVERIFY(engine2.objectOwnership(singlePtr) == QQmlEngine::CppOwnership); + } + + { + CppSingleton single; + QQmlEngine engineA; + QQmlEngine engineB; + int id = qmlRegisterSingletonInstance("Qt.test", 1, 0, "CppOwned", &single); + auto singlePtr = engineA.singletonInstance<CppSingleton *>(id); + QVERIFY(singlePtr); + singlePtr = engineA.singletonInstance<CppSingleton *>(id); // accessing the singleton multiple times from the same engine is fine + QVERIFY(singlePtr); + QTest::ignoreMessage(QtMsgType::QtCriticalMsg, "<Unknown File>: qmlRegisterSingletonType(): \"CppOwned\" is not available because the callback function returns a null pointer."); + QTest::ignoreMessage(QtMsgType::QtWarningMsg, "<Unknown File>: Singleton registered by registerSingletonInstance must only be accessed from one engine"); + QCOMPARE(&single, singlePtr); + auto noSinglePtr = engineB.singletonInstance<CppSingleton *>(id); + QVERIFY(!noSinglePtr); + } + + { + CppSingleton single; + QThread newThread {}; + single.moveToThread(&newThread); + QCOMPARE(single.thread(), &newThread); + QQmlEngine engineB; + int id = qmlRegisterSingletonInstance("Qt.test", 1, 0, "CppOwned", &single); + QTest::ignoreMessage(QtMsgType::QtCriticalMsg, "<Unknown File>: qmlRegisterSingletonType(): \"CppOwned\" is not available because the callback function returns a null pointer."); + QTest::ignoreMessage(QtMsgType::QtWarningMsg, "<Unknown File>: Registered object must live in the same thread as the engine it was registered with"); + auto noSinglePtr = engineB.singletonInstance<CppSingleton *>(id); + QVERIFY(!noSinglePtr); + } { // Invalid types |