From c24670ccd2d2d59c749c690335d3548f32c3ffd9 Mon Sep 17 00:00:00 2001 From: Thomas Perl Date: Thu, 9 Oct 2014 11:30:18 +0200 Subject: [PATCH] WIP QObject wrapper code --- examples/qobject_reference.py | 44 +++++++++ examples/qobject_reference.qml | 38 ++++++++ src/converter.h | 6 ++ src/pyobject_converter.h | 22 ++++- src/qobject_ref.cpp | 75 +++++++++++++++ src/qobject_ref.h | 60 ++++++++++++ src/qpython_priv.cpp | 163 +++++++++++++++++++++++++++++++++ src/qvariant_converter.h | 11 +++ src/src.pro | 4 + tests/tests.cpp | 13 +++ tests/tests.h | 1 + tests/tests.pro | 3 + 12 files changed, 439 insertions(+), 1 deletion(-) create mode 100644 examples/qobject_reference.py create mode 100644 examples/qobject_reference.qml create mode 100644 src/qobject_ref.cpp create mode 100644 src/qobject_ref.h diff --git a/examples/qobject_reference.py b/examples/qobject_reference.py new file mode 100644 index 0000000..e252164 --- /dev/null +++ b/examples/qobject_reference.py @@ -0,0 +1,44 @@ +import pyotherside +import threading +import time + +class Wrapper(object): + def __init__(self, capsule): + object.__setattr__(self, '_capsule', capsule) + + def __getattr__(self, attr): + return pyotherside.get_attribute(self._capsule, attr) + + def __setattr__(self, attr, value): + return pyotherside.set_attribute(self._capsule, attr, value) + +class MethodWrapper(object): + def __init__(self, capsule): + self._capsule = capsule + + def __call__(self, *args): + return pyotherside.call_method(self._capsule, args) + +def do_something(bar): + while True: + print('got: ', MethodWrapper(Wrapper(bar).dynamicFunction)(1, 2, 3)) + time.sleep(1) + +def foo(bar): + bar2 = Wrapper(bar) + print(bar2.x, bar2.color, bar2.scale) + bar2.x = bar2.x * 3 + + print(bar2.dynamicFunction) + method = MethodWrapper(bar2.dynamicFunction) + result = method(1, 2, 3) + print('result:', result) + + threading.Thread(target=do_something, args=[bar]).start() + + #print(bar) + #print(pyotherside.get_attribute(bar, 'x')) + #print('bar.x: ' + bar.x) + #return bar.x * 2 + return bar + diff --git a/examples/qobject_reference.qml b/examples/qobject_reference.qml new file mode 100644 index 0000000..00909cb --- /dev/null +++ b/examples/qobject_reference.qml @@ -0,0 +1,38 @@ + +import QtQuick 2.0 +import io.thp.pyotherside 1.4 + +Rectangle { + id: root + width: 400 + height: 400 + + Rectangle { + id: foo + x: 123 + y: 123 + width: 20 + height: 20 + color: 'blue' + + function dynamicFunction(a, b, c) { + console.log('dynamic: ' + a + ', ' + b + ', ' + c); + rotation += 4; + return 'hello'; + } + } + + Python { + id: py + + Component.onCompleted: { + addImportPath(Qt.resolvedUrl('.')); + importModule('qobject_reference', function () { + call('qobject_reference.foo', [foo], function (result) { + console.log('got result: ' + result); + result.color = 'green'; + }); + }); + } + } +} diff --git a/src/converter.h b/src/converter.h index c66d1d1..ff24c90 100644 --- a/src/converter.h +++ b/src/converter.h @@ -20,6 +20,7 @@ #define PYOTHERSIDE_CONVERTER_H #include "pyobject_ref.h" +#include "qobject_ref.h" struct ConverterDate { ConverterDate(int y, int m, int d) @@ -105,6 +106,7 @@ class Converter { TIME, DATETIME, PYOBJECT, + QOBJECT, }; virtual enum Type type(V&) = 0; @@ -118,6 +120,7 @@ class Converter { virtual ConverterTime time(V&) = 0; virtual ConverterDateTime dateTime(V&) = 0; virtual PyObjectRef pyObject(V&) = 0; + virtual QObjectRef qObject(V&) = 0; virtual V fromInteger(long long v) = 0; virtual V fromFloating(double v) = 0; @@ -127,6 +130,7 @@ class Converter { virtual V fromTime(ConverterTime time) = 0; virtual V fromDateTime(ConverterDateTime dateTime) = 0; virtual V fromPyObject(const PyObjectRef &pyobj) = 0; + virtual V fromQObject(const QObjectRef &qobj) = 0; virtual ListBuilder *newList() = 0; virtual DictBuilder *newDict() = 0; virtual V none() = 0; @@ -193,6 +197,8 @@ convert(F from) return tconv.fromDateTime(fconv.dateTime(from)); case FC::PYOBJECT: return tconv.fromPyObject(fconv.pyObject(from)); + case FC::QOBJECT: + return tconv.fromQObject(fconv.qObject(from)); } return tconv.none(); diff --git a/src/pyobject_converter.h b/src/pyobject_converter.h index d33350c..731632c 100644 --- a/src/pyobject_converter.h +++ b/src/pyobject_converter.h @@ -23,6 +23,7 @@ #include "Python.h" #include "datetime.h" +#include class PyObjectListBuilder : public ListBuilder { @@ -138,7 +139,10 @@ class PyObjectConverter : public Converter { } virtual enum Type type(PyObject *&o) { - if (PyBool_Check(o)) { + if (PyCapsule_CheckExact(o)) { + qDebug() << "Is a capsule"; + return QOBJECT; + } else if (PyBool_Check(o)) { return BOOLEAN; } else if (PyLong_Check(o)) { return INTEGER; @@ -203,6 +207,15 @@ class PyObjectConverter : public Converter { PyDateTime_DATE_GET_MICROSECOND(o) / 1000); } virtual PyObjectRef pyObject(PyObject *&o) { return PyObjectRef(o); } + virtual QObjectRef qObject(PyObject *&o) { + QObjectRef *ref = static_cast(PyCapsule_GetPointer(o, "QObjectRef")); + + if (ref) { + return QObjectRef(*ref); + } + + return QObjectRef(); + } virtual PyObject * fromInteger(long long v) { return PyLong_FromLong((long)v); } virtual PyObject * fromFloating(double v) { return PyFloat_FromDouble(v); } @@ -214,6 +227,13 @@ class PyObjectConverter : public Converter { return PyDateTime_FromDateAndTime(v.y, v.m, v.d, v.time.h, v.time.m, v.time.s, v.time.ms * 1000); } virtual PyObject * fromPyObject(const PyObjectRef &pyobj) { return pyobj.newRef(); } + virtual PyObject * fromQObject(const QObjectRef &qobj) { + PyObject *result = PyCapsule_New(new QObjectRef(qobj), "QObjectRef", NULL); + //PyObject *x = PyLong_FromLong(1337); + //PyObject_SetAttrString(result, "x", x); + //Py_CLEAR(x); + return result; + } virtual ListBuilder *newList() { return new PyObjectListBuilder(); } virtual DictBuilder *newDict() { return new PyObjectDictBuilder(); } virtual PyObject * none() { Py_RETURN_NONE; } diff --git a/src/qobject_ref.cpp b/src/qobject_ref.cpp new file mode 100644 index 0000000..ff75911 --- /dev/null +++ b/src/qobject_ref.cpp @@ -0,0 +1,75 @@ + +/** + * PyOtherSide: Asynchronous Python 3 Bindings for Qt 5 + * Copyright (c) 2014, Thomas Perl + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH + * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, + * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR + * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + * PERFORMANCE OF THIS SOFTWARE. + **/ + +#include "qobject_ref.h" + +QObjectRef::QObjectRef(QObject *obj) + : qobject(obj) +{ + if (qobject) { + QObject::connect(qobject, SIGNAL(destroyed(QObject *)), + this, SLOT(handleDestroyed(QObject *))); + } +} + +QObjectRef::QObjectRef(const QObjectRef &other) + : qobject(other.qobject) +{ + if (qobject) { + QObject::connect(qobject, SIGNAL(destroyed(QObject *)), + this, SLOT(handleDestroyed(QObject *))); + } +} + +QObjectRef::~QObjectRef() +{ + if (qobject) { + QObject::disconnect(qobject, SIGNAL(destroyed(QObject *)), + this, SLOT(handleDestroyed(QObject *))); + } +} + +QObjectRef & +QObjectRef::operator=(const QObjectRef &other) +{ + if (this != &other) { + if (qobject != other.qobject) { + if (qobject) { + QObject::disconnect(qobject, SIGNAL(destroyed(QObject *)), + this, SLOT(handleDestroyed(QObject *))); + } + + if (other.qobject) { + qobject = other.qobject; + QObject::connect(qobject, SIGNAL(destroyed(QObject *)), + this, SLOT(handleDestroyed(QObject *))); + } + } + } + + return *this; +} + +void +QObjectRef::handleDestroyed(QObject *obj) +{ + if (obj == qobject) { + // Have to remove internal reference, as qobject is destroyed + qobject = NULL; + } +} diff --git a/src/qobject_ref.h b/src/qobject_ref.h new file mode 100644 index 0000000..325a91d --- /dev/null +++ b/src/qobject_ref.h @@ -0,0 +1,60 @@ + +/** + * PyOtherSide: Asynchronous Python 3 Bindings for Qt 5 + * Copyright (c) 2014, Thomas Perl + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH + * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, + * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR + * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + * PERFORMANCE OF THIS SOFTWARE. + **/ + +#ifndef PYOTHERSIDE_QOBJECT_REF_H +#define PYOTHERSIDE_QOBJECT_REF_H + +#include + +class QObjectRef : public QObject { + Q_OBJECT + +public: + explicit QObjectRef(QObject *obj=0); + virtual ~QObjectRef(); + + QObjectRef(const QObjectRef &other); + QObjectRef &operator=(const QObjectRef &other); + + QObject *value() const { return qobject; } + operator bool() const { return (qobject != 0); } + +private slots: + void handleDestroyed(QObject *obj); + +private: + QObject *qobject; +}; + +class QObjectMethodRef { +public: + QObjectMethodRef(const QObjectRef &object, const QString &method) + : m_object(object) + , m_method(method) + { + } + + const QObjectRef &object() { return m_object; } + const QString &method() { return m_method; } + +private: + QObjectRef m_object; + QString m_method; +}; + +#endif // PYOTHERSIDE_QOBJECT_REF_H diff --git a/src/qpython_priv.cpp b/src/qpython_priv.cpp index ba0d220..5145dcb 100644 --- a/src/qpython_priv.cpp +++ b/src/qpython_priv.cpp @@ -28,6 +28,11 @@ #include #include +#include +#include +#include +#include + static QPythonPriv *priv = NULL; static QString @@ -135,6 +140,159 @@ pyotherside_qrc_list_dir(PyObject *self, PyObject *dirname) return convertQVariantToPyObject(dir.entryList()); } +PyObject * +pyotherside_get_attribute(PyObject *self, PyObject *args) +{ + // (capsule, attrname, [fallback]) + // + PyObject *capsule; + PyObject *attrname; + PyObject *fallback = NULL; + + if (!PyArg_UnpackTuple(args, "get_attribute", 2, 3, &capsule, &attrname, &fallback)) { + return NULL; + } + + if (!PyCapsule_CheckExact(capsule)) { + // TODO: Exception + return NULL; + } + + if (!PyUnicode_Check(attrname)) { + // TODO: Exception + return NULL; + } + + QObjectRef *ref = static_cast(PyCapsule_GetPointer(capsule, "QObjectRef")); + QObject *o = ref->value(); + + if (o) { + const QMetaObject *metaObject = o->metaObject(); + QString attrName = convertPyObjectToQVariant(attrname).toString(); + + for (int i=0; ipropertyCount(); i++) { + QMetaProperty property = metaObject->property(i); + if (attrName == property.name()) { + return convertQVariantToPyObject(property.read(o)); + } + } + + for (int i=0; imethodCount(); i++) { + QMetaMethod method = metaObject->method(i); + if (attrName == method.name()) { + return PyCapsule_New(new QObjectMethodRef(*ref, attrName), "QObjectMethodRef", NULL); + } + } + } + + Py_RETURN_NONE; +} + +PyObject * +pyotherside_set_attribute(PyObject *self, PyObject *args) +{ + // (capsule, attrname, value) + PyObject *capsule; + PyObject *attrname; + PyObject *value; + + if (!PyArg_UnpackTuple(args, "set_attribute", 3, 3, &capsule, &attrname, &value)) { + return NULL; + } + + if (!PyCapsule_CheckExact(capsule)) { + // TODO: Exception + return NULL; + } + + if (!PyUnicode_Check(attrname)) { + // TODO: Exception + return NULL; + } + + QObjectRef *ref = static_cast(PyCapsule_GetPointer(capsule, "QObjectRef")); + QObject *o = ref->value(); + + if (o) { + const QMetaObject *metaObject = o->metaObject(); + QString attrName = convertPyObjectToQVariant(attrname).toString(); + + for (int i=0; ipropertyCount(); i++) { + QMetaProperty property = metaObject->property(i); + if (attrName == property.name()) { + if (!property.write(o, convertPyObjectToQVariant(value))) { + // TODO: Exception + return NULL; + } + + Py_RETURN_NONE; + } + } + } + + Py_RETURN_NONE; +} + +PyObject * +pyotherside_call_method(PyObject *self, PyObject *args) +{ + // (capsule, args) + PyObject *capsule; + PyObject *methargs; + + if (!PyArg_UnpackTuple(args, "call_method", 2, 2, &capsule, &methargs)) { + return NULL; + } + + if (!PyCapsule_CheckExact(capsule)) { + qDebug() << "not a capsule"; + // TODO: Exception + return NULL; + } + + if (!PyTuple_Check(methargs)) { + qDebug() << "not a tuple"; + // TODO: Exception + return NULL; + } + + QList qargs = convertPyObjectToQVariant(methargs).toList(); + QObjectMethodRef *ref = static_cast(PyCapsule_GetPointer(capsule, "QObjectMethodRef")); + + QList genericArguments; + for (int j=0; jobject().value(); + const QMetaObject *metaObject = o->metaObject(); + + for (int i=0; imethodCount(); i++) { + QMetaMethod method = metaObject->method(i); + + if (method.name() == ref->method()) { + QVariant result; + if (method.invoke(o, Qt::DirectConnection, + Q_RETURN_ARG(QVariant, result), genericArguments.value(0), + genericArguments.value(1), genericArguments.value(2), + genericArguments.value(3), genericArguments.value(4), + genericArguments.value(5), genericArguments.value(6), + genericArguments.value(7), genericArguments.value(8), + genericArguments.value(9))) { + return convertQVariantToPyObject(result); + } + + qDebug() << "No result"; + Py_RETURN_NONE; + } + } + + Py_RETURN_NONE; +} + + + static PyMethodDef PyOtherSideMethods[] = { /* Introduced in PyOtherSide 1.0 */ {"send", pyotherside_send, METH_VARARGS, "Send data to Qt."}, @@ -149,6 +307,11 @@ static PyMethodDef PyOtherSideMethods[] = { {"qrc_get_file_contents", pyotherside_qrc_get_file_contents, METH_O, "Get file contents from a Qt Resource."}, {"qrc_list_dir", pyotherside_qrc_list_dir, METH_O, "Get directory entries from a Qt Resource."}, + /* Introduced in PyOtherSide 1.4 */ + {"get_attribute", pyotherside_get_attribute, METH_VARARGS, "Get attribute of QObject"}, + {"set_attribute", pyotherside_set_attribute, METH_VARARGS, "Set attribute of QObject"}, + {"call_method", pyotherside_call_method, METH_VARARGS, "Call method on QObject"}, + /* sentinel */ {NULL, NULL, 0, NULL}, }; diff --git a/src/qvariant_converter.h b/src/qvariant_converter.h index 999a032..bf69524 100644 --- a/src/qvariant_converter.h +++ b/src/qvariant_converter.h @@ -112,6 +112,10 @@ class QVariantConverter : public Converter { virtual ~QVariantConverter() {} virtual enum Type type(QVariant &v) { + if (v.canConvert()) { + return QOBJECT; + } + QMetaType::Type t = (QMetaType::Type)v.type(); switch (t) { case QMetaType::Bool: @@ -196,6 +200,10 @@ class QVariantConverter : public Converter { return v.value(); } + virtual QObjectRef qObject(QVariant &v) { + return QObjectRef(v.value()); + } + virtual ListBuilder *newList() { return new QVariantListBuilder; } @@ -218,6 +226,9 @@ class QVariantConverter : public Converter { virtual QVariant fromPyObject(const PyObjectRef &pyobj) { return QVariant::fromValue(pyobj); } + virtual QVariant fromQObject(const QObjectRef &qobj) { + return QVariant::fromValue(qobj.value()); + } virtual QVariant none() { return QVariant(); }; private: diff --git a/src/src.pro b/src/src.pro index 5d28149..bb4fd85 100644 --- a/src/src.pro +++ b/src/src.pro @@ -47,6 +47,10 @@ HEADERS += global_libpython_loader.h SOURCES += pyobject_ref.cpp HEADERS += pyobject_ref.h +# QObject wrapper class exposed to Python +SOURCES += qobject_ref.cpp +HEADERS += qobject_ref.h + # GIL helper HEADERS += ensure_gil_state.h diff --git a/tests/tests.cpp b/tests/tests.cpp index d3c222a..0dd8fb8 100644 --- a/tests/tests.cpp +++ b/tests/tests.cpp @@ -222,6 +222,19 @@ TestPyOtherSide::testPyObjectRefRoundTrip() QVERIFY(destructor_called); } +void +TestPyOtherSide::testQObjectRef() +{ + QObject *o = new QObject(); + QObjectRef ref(o); + + QVERIFY(ref.value() == o); + + delete o; + + QVERIFY(ref.value() == NULL); +} + void TestPyOtherSide::testQVariantConverter() { diff --git a/tests/tests.h b/tests/tests.h index 5479b8f..dcd7efa 100644 --- a/tests/tests.h +++ b/tests/tests.h @@ -36,6 +36,7 @@ class TestPyOtherSide : public QObject { void testPyObjectConverter(); void testPyObjectRefRoundTrip(); void testPyObjectRefAssignment(); + void testQObjectRef(); void testConvertToPythonAndBack(); void testSetToList(); }; diff --git a/tests/tests.pro b/tests/tests.pro index 5b930bd..8930c26 100644 --- a/tests/tests.pro +++ b/tests/tests.pro @@ -11,6 +11,7 @@ SOURCES += ../src/qpython.cpp SOURCES += ../src/qpython_worker.cpp SOURCES += ../src/qpython_priv.cpp SOURCES += ../src/pyobject_ref.cpp +SOURCES += ../src/qobject_ref.cpp HEADERS += ../src/qpython.h HEADERS += ../src/qpython_worker.h @@ -18,6 +19,8 @@ HEADERS += ../src/qpython_priv.h HEADERS += ../src/converter.h HEADERS += ../src/qvariant_converter.h HEADERS += ../src/pyobject_converter.h +HEADERS += ../src/pyobject_ref.h +HEADERS += ../src/qobject_ref.h DEPENDPATH += . ../src INCLUDEPATH += . ../src