1
0
mirror of https://github.com/thp/pyotherside.git synced 2025-01-17 23:22:53 +08:00

WIP QObject wrapper code

This commit is contained in:
Thomas Perl 2014-10-09 11:30:18 +02:00
parent 4ef3738805
commit c24670ccd2
12 changed files with 439 additions and 1 deletions

View File

@ -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

View File

@ -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';
});
});
}
}
}

View File

@ -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<V> *newList() = 0;
virtual DictBuilder<V> *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();

View File

@ -23,6 +23,7 @@
#include "Python.h"
#include "datetime.h"
#include <QDebug>
class PyObjectListBuilder : public ListBuilder<PyObject *> {
@ -138,7 +139,10 @@ class PyObjectConverter : public Converter<PyObject *> {
}
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<PyObject *> {
PyDateTime_DATE_GET_MICROSECOND(o) / 1000);
}
virtual PyObjectRef pyObject(PyObject *&o) { return PyObjectRef(o); }
virtual QObjectRef qObject(PyObject *&o) {
QObjectRef *ref = static_cast<QObjectRef *>(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<PyObject *> {
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<PyObject *> *newList() { return new PyObjectListBuilder(); }
virtual DictBuilder<PyObject *> *newDict() { return new PyObjectDictBuilder(); }
virtual PyObject * none() { Py_RETURN_NONE; }

75
src/qobject_ref.cpp Normal file
View File

@ -0,0 +1,75 @@
/**
* PyOtherSide: Asynchronous Python 3 Bindings for Qt 5
* Copyright (c) 2014, Thomas Perl <m@thp.io>
*
* 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;
}
}

60
src/qobject_ref.h Normal file
View File

@ -0,0 +1,60 @@
/**
* PyOtherSide: Asynchronous Python 3 Bindings for Qt 5
* Copyright (c) 2014, Thomas Perl <m@thp.io>
*
* 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 <QObject>
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

View File

@ -28,6 +28,11 @@
#include <QFile>
#include <QDir>
#include <QMetaObject>
#include <QMetaProperty>
#include <QMetaMethod>
#include <QGenericArgument>
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<QObjectRef *>(PyCapsule_GetPointer(capsule, "QObjectRef"));
QObject *o = ref->value();
if (o) {
const QMetaObject *metaObject = o->metaObject();
QString attrName = convertPyObjectToQVariant(attrname).toString();
for (int i=0; i<metaObject->propertyCount(); i++) {
QMetaProperty property = metaObject->property(i);
if (attrName == property.name()) {
return convertQVariantToPyObject(property.read(o));
}
}
for (int i=0; i<metaObject->methodCount(); 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<QObjectRef *>(PyCapsule_GetPointer(capsule, "QObjectRef"));
QObject *o = ref->value();
if (o) {
const QMetaObject *metaObject = o->metaObject();
QString attrName = convertPyObjectToQVariant(attrname).toString();
for (int i=0; i<metaObject->propertyCount(); 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<QVariant> qargs = convertPyObjectToQVariant(methargs).toList();
QObjectMethodRef *ref = static_cast<QObjectMethodRef *>(PyCapsule_GetPointer(capsule, "QObjectMethodRef"));
QList<QGenericArgument> genericArguments;
for (int j=0; j<qargs.size(); j++) {
const QVariant& argument = qargs[j];
genericArguments.append(QGenericArgument(argument.typeName(), argument.constData()));
}
QObject *o = ref->object().value();
const QMetaObject *metaObject = o->metaObject();
for (int i=0; i<metaObject->methodCount(); 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},
};

View File

@ -112,6 +112,10 @@ class QVariantConverter : public Converter<QVariant> {
virtual ~QVariantConverter() {}
virtual enum Type type(QVariant &v) {
if (v.canConvert<QObject *>()) {
return QOBJECT;
}
QMetaType::Type t = (QMetaType::Type)v.type();
switch (t) {
case QMetaType::Bool:
@ -196,6 +200,10 @@ class QVariantConverter : public Converter<QVariant> {
return v.value<PyObjectRef>();
}
virtual QObjectRef qObject(QVariant &v) {
return QObjectRef(v.value<QObject *>());
}
virtual ListBuilder<QVariant> *newList() {
return new QVariantListBuilder;
}
@ -218,6 +226,9 @@ class QVariantConverter : public Converter<QVariant> {
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:

View File

@ -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

View File

@ -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()
{

View File

@ -36,6 +36,7 @@ class TestPyOtherSide : public QObject {
void testPyObjectConverter();
void testPyObjectRefRoundTrip();
void testPyObjectRefAssignment();
void testQObjectRef();
void testConvertToPythonAndBack();
void testSetToList();
};

View File

@ -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