summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMathias Hasselmann <mathias.hasselmann@kdab.com>2013-10-19 21:48:48 +0200
committerMathias Hasselmann <mathias.hasselmann@kdab.com>2013-10-19 23:18:12 +0200
commit05c12c6d9af430c3637d43866059e88af28c034b (patch)
treeb67b6e8eacb2fb73824b7c9a530e7ff64f18a8e3
downloadqtquickstreamer-05c12c6d9af430c3637d43866059e88af28c034b.tar.gz
qtquickstreamer-05c12c6d9af430c3637d43866059e88af28c034b.tar.xz
Initial version
-rw-r--r--.gitignore8
-rw-r--r--quickstreamer.pro3
-rw-r--r--src/object.cpp495
-rw-r--r--src/object.h58
-rw-r--r--src/plugin.cpp27
-rw-r--r--src/plugin.h21
-rw-r--r--src/qmldir2
-rw-r--r--src/qquickstreamerplugin.json1
-rw-r--r--src/qtquickstreamerplugin.pro27
-rw-r--r--src/src.pro4
-rw-r--r--tests/tests.pro4
-rw-r--r--tests/tst_wrapping/tst_wrapping.pro14
-rw-r--r--tests/tst_wrapping/tst_wrappingtest.cpp85
13 files changed, 749 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..1817e5b
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,8 @@
+*.o
+*.user*
+Makefile*
+*.moc
+moc_*.cpp
+*.so
+.*.sw?
+/tests/tst_wrapping/tst_wrapping
diff --git a/quickstreamer.pro b/quickstreamer.pro
new file mode 100644
index 0000000..3ca2c54
--- /dev/null
+++ b/quickstreamer.pro
@@ -0,0 +1,3 @@
+TEMPLATE = subdirs
+CONFIG += ordered
+SUBDIRS = src tests
diff --git a/src/object.cpp b/src/object.cpp
new file mode 100644
index 0000000..31c6504
--- /dev/null
+++ b/src/object.cpp
@@ -0,0 +1,495 @@
+#include "object.h"
+
+#include <5.0.1/QtCore/private/qmetaobjectbuilder_p.h>
+
+#include <QVector>
+
+#include <gst/gstobject.h>
+
+#include <functional>
+
+namespace QQuickStreamer {
+namespace Private {
+
+struct PropertyInfo
+{
+ typedef std::function<void(GstObject *target, QVariant *value)> ReadDelegate;
+ typedef std::function<void(GstObject *target, const QVariant &value)> WriteDelegate;
+
+ QByteArray name;
+ ReadDelegate read;
+ WriteDelegate write;
+};
+
+struct TypeInfo
+{
+ TypeInfo()
+ : parent(Q_NULLPTR)
+ , metaObject(Q_NULLPTR)
+ {
+ }
+
+ int readProperty(GstObject *object, int id, QVariant *value) const
+ {
+ if (parent)
+ id = parent->readProperty(object, id, value);
+ if (id < 0)
+ return id;
+
+ if (id < properties.size())
+ properties.at(id).read(object, value);
+
+ return id - properties.size();
+ }
+
+ int writeProperty(GstObject *object, int id, const QVariant &value) const
+ {
+ if (parent)
+ id = parent->writeProperty(object, id, value);
+ if (id < 0)
+ return id;
+
+ if (id < properties.size())
+ properties.at(id).write(object, value);
+
+ return id - properties.size();
+ }
+
+ const TypeInfo *parent;
+ const QMetaObject *metaObject;
+ QVector<PropertyInfo> properties;
+};
+
+static QHash<GType, TypeInfo *> cachedTypeInfo;
+
+static const TypeInfo *makeTypeInfo(GType gtype);
+
+static GQuark dataKeyQuark()
+{
+ static const GQuark quark = g_quark_from_static_string("qt-quick-streamer-object-data-key-quark");
+ return quark;
+}
+
+void *findWrapper(void *object)
+{
+ return g_object_get_qdata(G_OBJECT(object), dataKeyQuark());
+}
+
+static const TypeInfo *findTypeInfo(GType gtype)
+{
+ Q_ASSERT(g_type_is_a(gtype, G_TYPE_OBJECT));
+ const auto *typeInfo = Private::cachedTypeInfo.value(gtype);
+
+ if (typeInfo == Q_NULLPTR)
+ typeInfo = makeTypeInfo(gtype);
+
+ return typeInfo;
+}
+
+static bool isDash(char ch)
+{
+ return ch == '-' || ch == '_';
+}
+
+static QByteArray camelCaseName(const QByteArray &input)
+{
+ int nDashes = 0;
+
+ for (char ch: input) {
+ if (isDash(ch))
+ ++nDashes;
+ }
+
+ QByteArray output;
+
+ output.reserve(input.size() - nDashes);
+
+ bool wordBoundary = false;
+
+ for (char ch: input) {
+ if (isDash(ch)) {
+ wordBoundary = true;
+ continue;
+ }
+
+ if (wordBoundary) {
+ output += std::toupper(ch, std::locale::classic());
+ wordBoundary = false;
+ } else {
+ output += std::tolower(ch, std::locale::classic());
+ }
+ }
+
+ return output;
+}
+
+template<typename T>
+static QByteArray metaTypeName()
+{
+ static const QByteArray name = QMetaType::typeName(qMetaTypeId<T>());
+ return name;
+}
+
+static QByteArray metaTypeName(GType type)
+{
+ switch(type) {
+ case G_TYPE_CHAR: return metaTypeName<char>();
+ case G_TYPE_UCHAR: return metaTypeName<uchar>();
+ case G_TYPE_BOOLEAN: return metaTypeName<bool>();
+ case G_TYPE_INT: return metaTypeName<int>();
+ case G_TYPE_UINT: return metaTypeName<uint>();
+ case G_TYPE_LONG: return metaTypeName<long>();
+ case G_TYPE_ULONG: return metaTypeName<ulong>();
+ case G_TYPE_INT64: return metaTypeName<qint64>();
+ case G_TYPE_UINT64: return metaTypeName<quint64>();
+ case G_TYPE_FLOAT: return metaTypeName<float>();
+ case G_TYPE_DOUBLE: return metaTypeName<double>();
+ case G_TYPE_STRING: return metaTypeName<QString>();
+ //case G_TYPE_ENUM G_TYPE_MAKE_FUNDAMENTAL (12)
+ //case G_TYPE_FLAGS G_TYPE_MAKE_FUNDAMENTAL (13)
+ //case G_TYPE_POINTER G_TYPE_MAKE_FUNDAMENTAL (17)
+ //case G_TYPE_BOXED G_TYPE_MAKE_FUNDAMENTAL (18)
+ //case G_TYPE_PARAM G_TYPE_MAKE_FUNDAMENTAL (19)
+ //case G_TYPE_OBJECT G_TYPE_MAKE_FUNDAMENTAL (20)
+ //case G_TYPE_VARIANT G_TYPE_MAKE_FUNDAMENTAL (21)
+ }
+
+ qWarning("Ignoring unsupported GType: %s", g_type_name(type));
+ return {};
+}
+
+static void toVariant(const GValue *value, QVariant *variant)
+{
+ switch(G_VALUE_TYPE(value))
+ {
+ case G_TYPE_CHAR:
+ variant->setValue(g_value_get_schar(value));
+ break;
+
+ case G_TYPE_UCHAR:
+ variant->setValue(g_value_get_uchar(value));
+ break;
+
+ case G_TYPE_BOOLEAN:
+ variant->setValue<bool>(g_value_get_boolean(value));
+ break;
+
+ case G_TYPE_INT:
+ variant->setValue(g_value_get_int(value));
+ break;
+
+ case G_TYPE_UINT:
+ variant->setValue(g_value_get_uint(value));
+ break;
+
+ case G_TYPE_LONG:
+ variant->setValue(g_value_get_long(value));
+ break;
+
+ case G_TYPE_ULONG:
+ variant->setValue(g_value_get_ulong(value));
+ break;
+
+ case G_TYPE_INT64:
+ variant->setValue(g_value_get_int64(value));
+ break;
+
+ case G_TYPE_UINT64:
+ variant->setValue(g_value_get_uint64(value));
+ break;
+
+ case G_TYPE_FLOAT:
+ variant->setValue(g_value_get_float(value));
+ break;
+
+ case G_TYPE_DOUBLE:
+ variant->setValue(g_value_get_double(value));
+ break;
+
+ case G_TYPE_STRING:
+ variant->setValue(QString::fromUtf8(g_value_get_string(value)));
+ break;
+
+ //case G_TYPE_ENUM G_TYPE_MAKE_FUNDAMENTAL (12)
+ //case G_TYPE_FLAGS G_TYPE_MAKE_FUNDAMENTAL (13)
+ //case G_TYPE_POINTER G_TYPE_MAKE_FUNDAMENTAL (17)
+ //case G_TYPE_BOXED G_TYPE_MAKE_FUNDAMENTAL (18)
+ //case G_TYPE_PARAM G_TYPE_MAKE_FUNDAMENTAL (19)
+ //case G_TYPE_OBJECT G_TYPE_MAKE_FUNDAMENTAL (20)
+ //case G_TYPE_VARIANT G_TYPE_MAKE_FUNDAMENTAL (21)
+
+ default:
+ qWarning("Cannot convert unsupported GValue type: %s", G_VALUE_TYPE_NAME(value));
+ break;
+ }
+}
+
+static void toValue(const QVariant &variant, GValue *value)
+{
+ switch(G_VALUE_TYPE(value))
+ {
+ case G_TYPE_CHAR:
+ g_value_set_schar(value, variant.value<char>());
+ break;
+
+ case G_TYPE_UCHAR:
+ g_value_set_uchar(value, variant.value<uchar>());
+ break;
+
+ case G_TYPE_BOOLEAN:
+ g_value_set_boolean(value, variant.value<bool>());
+ break;
+
+ case G_TYPE_INT:
+ g_value_set_int(value, variant.value<int>());
+ break;
+
+ case G_TYPE_UINT:
+ g_value_set_uint(value, variant.value<uint>());
+ break;
+
+ case G_TYPE_LONG:
+ g_value_set_long(value, variant.value<long>());
+ break;
+
+ case G_TYPE_ULONG:
+ g_value_set_ulong(value, variant.value<ulong>());
+ break;
+
+ case G_TYPE_INT64:
+ g_value_set_int64(value, variant.value<qint64>());
+ break;
+
+ case G_TYPE_UINT64:
+ g_value_set_uint64(value, variant.value<quint64>());
+ break;
+
+ case G_TYPE_FLOAT:
+ g_value_set_float(value, variant.value<float>());
+ break;
+
+ case G_TYPE_DOUBLE:
+ g_value_set_double(value, variant.value<double>());
+ break;
+
+ case G_TYPE_STRING:
+ g_value_set_string(value, variant.value<QString>().toUtf8().constData());
+ break;
+
+ //case G_TYPE_ENUM G_TYPE_MAKE_FUNDAMENTAL (12)
+ //case G_TYPE_FLAGS G_TYPE_MAKE_FUNDAMENTAL (13)
+ //case G_TYPE_POINTER G_TYPE_MAKE_FUNDAMENTAL (17)
+ //case G_TYPE_BOXED G_TYPE_MAKE_FUNDAMENTAL (18)
+ //case G_TYPE_PARAM G_TYPE_MAKE_FUNDAMENTAL (19)
+ //case G_TYPE_OBJECT G_TYPE_MAKE_FUNDAMENTAL (20)
+ //case G_TYPE_VARIANT G_TYPE_MAKE_FUNDAMENTAL (21)
+
+ default:
+ qWarning("Cannot convert unsupported GValue type: %s", G_VALUE_TYPE_NAME(value));
+ break;
+ }
+}
+
+static void readGObjectProperty(GstObject *target, const QByteArray &propertyName, GType propertyType, QVariant *value)
+{
+ GValue gvalue = G_VALUE_INIT;
+ g_value_init(&gvalue, propertyType);
+ g_object_get_property(G_OBJECT(target), propertyName.constData(), &gvalue);
+ toVariant(&gvalue, value);
+ g_value_unset(&gvalue);
+}
+
+static void writeGObjectProperty(GstObject *target, const QByteArray &propertyName, GType propertyType, const QVariant &value)
+{
+ GValue gvalue = G_VALUE_INIT;
+ g_value_init(&gvalue, propertyType);
+ toValue(value, &gvalue);
+ g_object_set_property(G_OBJECT(target), propertyName.constData(), &gvalue);
+ g_value_unset(&gvalue);
+}
+
+static void readNothing(GstObject *target, const QByteArray &name, QVariant *)
+{
+ qWarning("Cannot read \"%s::%s\" which is not declared readable",
+ G_OBJECT_TYPE_NAME(target), name.constData());
+}
+
+static void writeNothing(GstObject *target, const QByteArray &name, const QVariant &)
+{
+ qWarning("Cannot write \"%s::%s\" which is not declared writable",
+ G_OBJECT_TYPE_NAME(target), name.constData());
+}
+
+template<typename... Args>
+PropertyInfo::ReadDelegate bindReadDelegate(Args... args)
+{
+ return std::bind(args...);
+}
+
+template<typename... Args>
+PropertyInfo::WriteDelegate bindWriteDelegate(Args... args)
+{
+ return std::bind(args...);
+}
+
+static const TypeInfo *makeTypeInfo(GType gtype)
+{
+ QScopedPointer<TypeInfo> typeInfo(new TypeInfo);
+
+ QMetaObjectBuilder objectBuilder;
+ objectBuilder.setClassName(g_type_name(gtype));
+
+ if (gtype != G_TYPE_OBJECT)
+ typeInfo->parent = findTypeInfo(g_type_parent(gtype));
+
+ objectBuilder.setSuperClass(typeInfo->parent ? typeInfo->parent->metaObject
+ : &QObject::staticMetaObject);
+
+ const auto gobject_class = static_cast<GObjectClass *>(g_type_class_ref(gtype));
+
+ uint nPSpecs = 0;
+ auto *const pSpecs = g_object_class_list_properties(gobject_class, &nPSpecs);
+
+ for (uint i = 0; i < nPSpecs; ++i) {
+ const auto *const pSpec = pSpecs[i];
+
+ if (pSpec->owner_type != gtype)
+ continue;
+
+ const auto &name = camelCaseName(pSpec->name);
+ const auto &type = metaTypeName(pSpec->value_type);
+
+ if (pSpec->flags & G_PARAM_DEPRECATED) {
+ qWarning("Ignoring deprecated property %s::%s", g_type_name(gtype), pSpec->name);
+ continue;
+ }
+
+ if (type.isEmpty()) {
+ qWarning("Ignoring property %s::%s of unsupported type %s",
+ g_type_name(gtype), pSpec->name, g_type_name(pSpec->value_type));
+ continue;
+ }
+
+ qDebug("Adding %s::%s", g_type_name(gtype), pSpec->name);
+
+ auto notifier = objectBuilder.addSignal(name + QByteArrayLiteral("Changed()"));
+ auto property = objectBuilder.addProperty(name, type, notifier.index());
+
+ property.setReadable(pSpec->flags & G_PARAM_READABLE);
+ property.setWritable(pSpec->flags & G_PARAM_WRITABLE);
+
+ using namespace std::placeholders;
+
+ const auto readDelegate = property.isReadable()
+ ? bindReadDelegate(&readGObjectProperty, _1, pSpec->name, pSpec->value_type, _2)
+ : bindReadDelegate(&readNothing, _1, pSpec->name, _2);
+ const auto writeDelegate = property.isWritable()
+ ? bindWriteDelegate(&writeGObjectProperty, _1, pSpec->name, pSpec->value_type, _2)
+ : bindWriteDelegate(&writeNothing, _1, pSpec->name, _2);
+
+ typeInfo->properties.append({pSpec->name, readDelegate, writeDelegate});
+ }
+
+ g_free(pSpecs);
+
+ uint nSignalIds = 0;
+ auto *const signalIds = g_signal_list_ids(gtype, &nSignalIds);
+
+ for (uint i = 0; i < nSignalIds; ++i) {
+ GSignalQuery query;
+ g_signal_query(signalIds[i], &query);
+ Q_ASSERT(signalIds[i] == query.signal_id);
+ qDebug("found signal: %s::%s", g_type_name(gtype), query.signal_name);
+ }
+
+ g_type_class_unref(gobject_class);
+
+ typeInfo->metaObject = objectBuilder.toMetaObject();
+ cachedTypeInfo.insert(gtype, typeInfo.data());
+ return typeInfo.take();
+}
+
+} // namespace Private
+
+Object::Object(GstObject *target)
+ : QObject()
+ , m_target(target)
+{
+ Q_ASSERT(m_target != Q_NULLPTR);
+ g_object_ref_sink(m_target);
+ g_object_set_qdata(G_OBJECT(target), Private::dataKeyQuark(), this);
+}
+
+Object::~Object()
+{
+ g_object_set_qdata(G_OBJECT(m_target), Private::dataKeyQuark(), Q_NULLPTR);
+ g_object_unref(m_target);
+}
+
+const QMetaObject *Object::metaObject() const
+{
+ return Private::findTypeInfo(G_OBJECT_TYPE(m_target))->metaObject;
+}
+
+void *Object::qt_metacast(const char *className)
+{
+ qDebug("%s: %s", Q_FUNC_INFO, className);
+
+ if (className == Q_NULLPTR)
+ return Q_NULLPTR;
+
+ if (strcmp(className, G_OBJECT_TYPE_NAME(m_target)) == 0)
+ return this;
+
+ return QObject::qt_metacast(className);
+}
+
+int Object::qt_metacall(QMetaObject::Call call, int id, void **args)
+{
+ qDebug("%s: call=%d, id=%d", Q_FUNC_INFO, call, id);
+ id = QObject::qt_metacall(call, id, args);
+
+ if (id < 0)
+ return id;
+
+ qDebug("-> id=%d", id);
+
+ switch(call) {
+ case QMetaObject::ReadProperty:
+ id = readProperty(id, static_cast<QVariant *>(args[0]));
+ break;
+
+ case QMetaObject::WriteProperty:
+ id = writeProperty(id, *static_cast<QVariant *>(args[0]));
+ break;
+
+ case QMetaObject::InvokeMetaMethod:
+ case QMetaObject::ResetProperty:
+ case QMetaObject::QueryPropertyDesignable:
+ case QMetaObject::QueryPropertyScriptable:
+ case QMetaObject::QueryPropertyStored:
+ case QMetaObject::QueryPropertyEditable:
+ case QMetaObject::QueryPropertyUser:
+ case QMetaObject::CreateInstance:
+ case QMetaObject::IndexOfMethod:
+ case QMetaObject::RegisterPropertyMetaType:
+ case QMetaObject::RegisterMethodArgumentMetaType:
+ break;
+ }
+
+ return id;
+}
+
+int Object::readProperty(int id, QVariant *value)
+{
+ const auto *const typeInfo = Private::findTypeInfo(G_OBJECT_TYPE(m_target));
+ return typeInfo->readProperty(m_target, id, value);
+}
+
+int Object::writeProperty(int id, const QVariant &value)
+{
+ const auto *const typeInfo = Private::findTypeInfo(G_OBJECT_TYPE(m_target));
+ return typeInfo->writeProperty(m_target, id, value);
+}
+
+} // namespace QQuickStreamer
diff --git a/src/object.h b/src/object.h
new file mode 100644
index 0000000..43fe227
--- /dev/null
+++ b/src/object.h
@@ -0,0 +1,58 @@
+#ifndef QQUICKSTREAMER_OBJECT_H
+#define QQUICKSTREAMER_OBJECT_H
+
+#include <QObject>
+
+typedef struct _GstObject GstObject;
+
+namespace QQuickStreamer {
+
+class Object : public QObject
+{
+public:
+ Q_OBJECT_CHECK
+
+ template<class Wrapper, class Target>
+ friend Wrapper *wrap(Target *);
+
+protected:
+ explicit Object(GstObject *target);
+
+public:
+ ~Object();
+
+ const QMetaObject *metaObject() const Q_DECL_OVERRIDE;
+ void *qt_metacast(const char *classname) Q_DECL_OVERRIDE;
+ int qt_metacall(QMetaObject::Call call, int id, void **args) Q_DECL_OVERRIDE;
+
+protected:
+ int readProperty(int id, QVariant *value);
+ int writeProperty(int id, const QVariant &value);
+
+private:
+ GstObject *const m_target;
+};
+
+namespace Private {
+
+void *findWrapper(void *object);
+
+}
+
+template<class Wrapper, class Target>
+inline Wrapper *wrap(Target *target)
+{
+ if (target == Q_NULLPTR)
+ return Q_NULLPTR;
+
+ Wrapper *wrapper = static_cast<Wrapper *>(Private::findWrapper(target));
+
+ if (wrapper == Q_NULLPTR)
+ wrapper = new Wrapper(target);
+
+ return wrapper;
+}
+
+} // namespace QQuickStreamer
+
+#endif // QQUICKSTREAMEROBJECT_H
diff --git a/src/plugin.cpp b/src/plugin.cpp
new file mode 100644
index 0000000..3693010
--- /dev/null
+++ b/src/plugin.cpp
@@ -0,0 +1,27 @@
+#include "plugin.h"
+//#include "qquickstreamerpipeline.h"
+
+#include <QtQml>
+
+namespace QQuickStreamer {
+
+static const auto NAMESPACE_URI = QByteArrayLiteral("QtQuickStreamer");
+
+Plugin::Plugin(QObject *parent)
+ : QQmlExtensionPlugin(parent)
+{
+}
+
+void Plugin::registerTypes(const char *uri)
+{
+ Q_ASSERT(uri == NAMESPACE_URI);
+
+ /*
+ // @uri com.kdab.QtQuickStreamer
+ qmlRegisterUncreatableType<QQuickStreamerElement>(uri, 1, 0, "Element", QStringLiteral("This is an abstract type"));
+ qmlRegisterType<QQuickStreamerBin>(uri, 1, 0, "Bin");
+ qmlRegisterType<QQuickStreamerPipeline>(uri, 1, 0, "Pipeline");
+ */
+}
+
+} // namespace QQuickStreamer
diff --git a/src/plugin.h b/src/plugin.h
new file mode 100644
index 0000000..bf5b747
--- /dev/null
+++ b/src/plugin.h
@@ -0,0 +1,21 @@
+#ifndef QQUICKSTREAMER_PLUGIN_H
+#define QQUICKSTREAMER_PLUGIN_H
+
+#include <QQmlExtensionPlugin>
+
+namespace QQuickStreamer {
+
+class Plugin : public QQmlExtensionPlugin
+{
+ Q_OBJECT
+ Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QQmlExtensionInterface" FILE "qquickstreamerplugin.json")
+
+public:
+ explicit Plugin(QObject *parent = Q_NULLPTR);
+
+ void registerTypes(const char *uri) Q_DECL_OVERRIDE;
+};
+
+} // namespace QQuickStreamer
+
+#endif // QQUICKSTREAMER_PLUGIN_H
diff --git a/src/qmldir b/src/qmldir
new file mode 100644
index 0000000..3139775
--- /dev/null
+++ b/src/qmldir
@@ -0,0 +1,2 @@
+module QtQuickStreamer
+plugin qtquickstreamerplugin
diff --git a/src/qquickstreamerplugin.json b/src/qquickstreamerplugin.json
new file mode 100644
index 0000000..0967ef4
--- /dev/null
+++ b/src/qquickstreamerplugin.json
@@ -0,0 +1 @@
+{}
diff --git a/src/qtquickstreamerplugin.pro b/src/qtquickstreamerplugin.pro
new file mode 100644
index 0000000..bd404bd
--- /dev/null
+++ b/src/qtquickstreamerplugin.pro
@@ -0,0 +1,27 @@
+TEMPLATE = lib
+CONFIG += plugin c++11
+#DESTDIR = org/qt-project/QtQuickStreamer
+
+QT += qml
+
+CONFIG += link_pkgconfig
+PKGCONFIG += gstreamer-1.0
+
+SOURCES = \
+ plugin.cpp \
+ object.cpp
+
+# qquickstreamerelement.cpp \
+# qquickstreamerbin.cpp \
+# qquickstreamerpipeline.cpp
+
+OTHER_FILES = \
+ qmldir
+
+HEADERS += \
+ plugin.h \
+ object.h
+
+# qquickstreamerelement.h \
+# qquickstreamerbin.h \
+# qquickstreamerpipeline.h
diff --git a/src/src.pro b/src/src.pro
new file mode 100644
index 0000000..37dfbd1
--- /dev/null
+++ b/src/src.pro
@@ -0,0 +1,4 @@
+TEMPLATE = subdirs
+
+SUBDIRS = \
+ qtquickstreamerplugin.pro
diff --git a/tests/tests.pro b/tests/tests.pro
new file mode 100644
index 0000000..d9d63e7
--- /dev/null
+++ b/tests/tests.pro
@@ -0,0 +1,4 @@
+TEMPLATE = subdirs
+
+SUBDIRS += \
+ tst_wrapping
diff --git a/tests/tst_wrapping/tst_wrapping.pro b/tests/tst_wrapping/tst_wrapping.pro
new file mode 100644
index 0000000..ab907c5
--- /dev/null
+++ b/tests/tst_wrapping/tst_wrapping.pro
@@ -0,0 +1,14 @@
+TEMPLATE = app
+
+QT += testlib
+QT -= gui
+
+CONFIG += c++11 link_pkgconfig
+PKGCONFIG += gstreamer-1.0
+
+LIBS += -Wl,-rpath,$$OUT_PWD/../../src -L../../src -lqtquickstreamerplugin
+
+INCLUDEPATH += ../../src
+
+SOURCES += tst_wrappingtest.cpp
+DEFINES += SRCDIR=\\\"$$PWD/\\\"
diff --git a/tests/tst_wrapping/tst_wrappingtest.cpp b/tests/tst_wrapping/tst_wrappingtest.cpp
new file mode 100644
index 0000000..4e0a2d2
--- /dev/null
+++ b/tests/tst_wrapping/tst_wrappingtest.cpp
@@ -0,0 +1,85 @@
+#include <QString>
+#include <QtTest>
+
+#include "object.h"
+
+#include <gst/gst.h>
+
+class WrappingTest : public QObject
+{
+ Q_OBJECT
+
+public:
+ WrappingTest()
+ {
+ gst_init(Q_NULLPTR, Q_NULLPTR);
+ }
+
+private slots:
+ void testCase1();
+
+ void elementNameProperty()
+ {
+ GstElement *const target = gst_pipeline_new(Q_NULLPTR);
+ auto *wrapper = QQuickStreamer::wrap<QQuickStreamer::Object>(GST_OBJECT(target));
+ QVERIFY(wrapper != Q_NULLPTR);
+
+ QSignalSpy spy(wrapper, SIGNAL(nameChanged()));
+
+ QCOMPARE(wrapper->property("name").toString(), QStringLiteral("pipeline0"));
+ QCOMPARE(spy.count(), 0);
+
+ wrapper->setProperty("name", QStringLiteral("dummy0"));
+
+ QCOMPARE(wrapper->property("name").toString(), QStringLiteral("dummy0"));
+ QCOMPARE(spy.count(), 1);
+
+ wrapper->setProperty("name", QStringLiteral("dummy1"));
+
+ QCOMPARE(wrapper->property("name").toString(), QStringLiteral("dummy1"));
+ QCOMPARE(spy.count(), 2);
+ }
+};
+
+void WrappingTest::testCase1()
+{
+ GstElement *const target = gst_pipeline_new("wrapping_test");
+
+ auto *wrapper1 = QQuickStreamer::wrap<QQuickStreamer::Object>(GST_OBJECT(target));
+ QVERIFY(wrapper1 != Q_NULLPTR);
+
+ auto *wrapper2 = QQuickStreamer::wrap<QQuickStreamer::Object>(GST_OBJECT(target));
+ QCOMPARE(wrapper1, wrapper2);
+
+ auto *metaObject = wrapper1->metaObject();
+ QVERIFY(metaObject != Q_NULLPTR);
+
+ qDebug() << G_OBJECT_TYPE_NAME(target);
+ qDebug() << metaObject->className()
+ << metaObject->propertyCount()
+ << metaObject->methodCount()
+ << metaObject->enumeratorCount();
+
+ for (int i = 0; i < metaObject->propertyCount(); ++i) {
+ qDebug() << "P" << i << metaObject->property(i).name()
+ << metaObject->property(i).typeName()
+ << (metaObject->property(i).isReadable() ? "read" : "")
+ << (metaObject->property(i).isWritable() ? "write" : "")
+ << (metaObject->property(i).isEnumType() ? "emum" : "")
+ << (metaObject->property(i).isFlagType() ? "flag" : "");
+ QVERIFY(metaObject->property(i).hasNotifySignal());
+ QCOMPARE(i, metaObject->indexOfProperty(metaObject->property(i).name()));
+ }
+
+ for (int i = 0; i < metaObject->methodCount(); ++i) {
+ qDebug() << "M" << i << metaObject->method(i).methodSignature()
+ << metaObject->method(i).methodType();
+ }
+
+ for (int i = 0; i < metaObject->enumeratorCount(); ++i)
+ qDebug() << "E" << i << metaObject->enumerator(i).name();
+}
+
+QTEST_MAIN(WrappingTest)
+
+#include "tst_wrappingtest.moc"