1
0
mirror of https://github.com/thp/pyotherside.git synced 2025-01-28 23:52:55 +08:00

Merge branch 'qobject-wrapper'

This commit is contained in:
Thomas Perl 2014-10-11 19:35:12 +02:00
commit eccdad94ee
15 changed files with 704 additions and 1 deletions

View File

@ -359,6 +359,8 @@ between Python and QML (and vice versa):
+--------------------+------------+-----------------------------+
| object | (opaque) | since PyOtherSide 1.4.0 |
+--------------------+------------+-----------------------------+
| pyotherside.QObject| QObject | since PyOtherSide 1.4.0 |
+--------------------+------------+-----------------------------+
Trying to pass in other types than the ones listed here is undefined
behavior and will usually result in an error.
@ -461,6 +463,59 @@ using :func:`Qt.resolvedUrl` from within a QML file in Qt Resources. As an
alternative, ``addImportPath('qrc:/')`` will add the root directory of the Qt
Resources to Python's module search path.
.. _qobjects in python:
Accessing QObjects from Python
==============================
.. versionadded:: 1.4.0
Since version 1.4, PyOtherSide allows passing QObjects from QML to Python, and
accessing (setting / getting) properties and calling slots and dynamic methods.
References to QObjects passed to Python can be passed back to QML transparently:
.. code-block:: python
# Assume func will be called with a QObject as sole argument
def func(qobject):
# Getting properties
print(qobject.x)
# Setting properties
qobject.x = 123
# Calling slots and dynamic functions
print(qobject.someFunction(123, 'b'))
# Returning a QObject reference to the caller
return qobject
It is possible to store a reference (bound method) to a method of a QObject.
Such references cannot be passed to QML, and can only be used in Python for the
lifetime of the QObject. If you need to pass such a bound method to QML, you
can wrap it into a Python object (or even just a lambda) and pass that instead:
.. code-block:: python
def func(qobject):
# Can store a reference to a bound method
bound_method = qobject.someFunction
# Calling the bound method
bound_method(123, 'b')
# If you need to return the bound method, you must wrap it
# in a lambda (or any other Python object), the bound method
# cannot be returned as-is for now
return lambda a, b: bound_method(a, b)
It's not possible to instantiate new QObjects from within Python, and it's
not possible to subclass QObject from within Python. Also, be aware that a
reference to a QObject in Python will become invalid when the QObject is
deleted (there's no way for PyOtherSide to prevent referenced QObjects from
being deleted, but PyOtherSide tries hard to detect the deletion of objects
and give meaningful error messages in case the reference is accessed).
Cookbook
========
@ -929,6 +984,7 @@ Version 1.4.0 (UNRELEASED)
* Add :func:`getattr` to get an attribute from a Python object
* :func:`call` and :func:`call_sync` now also accept a Python callable as
first argument
* Support for `Accessing QObjects from Python`_ (properties and slots)
Version 1.3.0 (2014-07-24)
--------------------------

View File

@ -0,0 +1,65 @@
import threading
import time
import pyotherside
# If you try to instantiate a QObject, it's unbound
unbound = pyotherside.QObject()
print(unbound)
try:
unbound.a = 1
except Exception as e:
print('Got exception:', e)
def do_something(bar):
while True:
print('got: ', bar.dynamicFunction(1, 2, 3))
time.sleep(1)
def foo(bar, py):
# Printing the objects will give some info on the
# QObject class and memory address
print('got:', bar, py)
# Ok, this is pretty wicked - we can now call into
# the PyOtherSide QML element from within Python
# (not that it's a good idea to do this, mind you..)
print(py.evaluate)
print(py.evaluate('3*3'))
try:
bar.i_am_pretty_sure_this_attr_does_not_exist = 147
except Exception as e:
print('Got exception (as expected):', e)
try:
bar.x = 'i dont think i can set this to a string'
except Exception as e:
print('Got exception (as expected):', e)
# This doesn't work yet, because we can't convert a bound
# member function to a Qt/QML type yet (fallback to None)
try:
bar.dynamicFunction(bar.dynamicFunction, 2, 3)
except Exception as e:
print('Got exception (as expected):', e)
# Property access works just like expected
print(bar.x, bar.color, bar.scale)
bar.x *= 3
# Printing a member function gives a bound method
print(bar.dynamicFunction)
# Calling a member function is just as easy
result = bar.dynamicFunction(1, 2, 3)
print('result:', result)
try:
bar.dynamicFunction(1, 2, 3, unexpected=123)
except Exception as e:
print('Got exception (as expected):', e)
threading.Thread(target=do_something, args=[bar]).start()
# Returning QObject references from Python also works
return bar

View File

@ -0,0 +1,37 @@
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('In QML, dynamicFunction got: ' + a + ', ' + b + ', ' + c);
rotation += 4;
return 'hello';
}
}
Python {
id: py
Component.onCompleted: {
addImportPath(Qt.resolvedUrl('.'));
importModule('qobject_reference', function () {
call('qobject_reference.foo', [foo, py], function (result) {
console.log('In QML, 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

@ -20,9 +20,11 @@
#define PYOTHERSIDE_PYOBJECT_CONVERTER_H
#include "converter.h"
#include "pyqobject.h"
#include "Python.h"
#include "datetime.h"
#include <QDebug>
class PyObjectListBuilder : public ListBuilder<PyObject *> {
@ -138,7 +140,13 @@ class PyObjectConverter : public Converter<PyObject *> {
}
virtual enum Type type(PyObject *&o) {
if (PyBool_Check(o)) {
if (PyObject_TypeCheck(o, &pyotherside_QObjectType)) {
return QOBJECT;
} else if (PyObject_TypeCheck(o, &pyotherside_QObjectMethodType)) {
qWarning("Cannot convert QObjectMethod yet - falling back to None");
// TODO: Implement passing QObjectMethod references around
return NONE;
} else if (PyBool_Check(o)) {
return BOOLEAN;
} else if (PyLong_Check(o)) {
return INTEGER;
@ -203,6 +211,14 @@ class PyObjectConverter : public Converter<PyObject *> {
PyDateTime_DATE_GET_MICROSECOND(o) / 1000);
}
virtual PyObjectRef pyObject(PyObject *&o) { return PyObjectRef(o); }
virtual QObjectRef qObject(PyObject *&o) {
if (PyObject_TypeCheck(o, &pyotherside_QObjectType)) {
pyotherside_QObject *result = reinterpret_cast<pyotherside_QObject *>(o);
return QObjectRef(*(result->m_qobject_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 +230,11 @@ 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) {
pyotherside_QObject *result = PyObject_New(pyotherside_QObject, &pyotherside_QObjectType);
result->m_qobject_ref = new QObjectRef(qobj);
return reinterpret_cast<PyObject *>(result);
}
virtual ListBuilder<PyObject *> *newList() { return new PyObjectListBuilder(); }
virtual DictBuilder<PyObject *> *newDict() { return new PyObjectDictBuilder(); }
virtual PyObject * none() { Py_RETURN_NONE; }

39
src/pyqobject.h Normal file
View File

@ -0,0 +1,39 @@
/**
* 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_PYQOBJECT_H
#define PYOTHERSIDE_PYQOBJECT_H
#include "Python.h"
#include "qobject_ref.h"
typedef struct {
PyObject_HEAD
QObjectRef *m_qobject_ref;
} pyotherside_QObject;
typedef struct {
PyObject_HEAD
QObjectMethodRef *m_method_ref;
} pyotherside_QObjectMethod;
extern PyTypeObject pyotherside_QObjectType;
extern PyTypeObject pyotherside_QObjectMethodType;
#endif /* PYOTHERSIDE_PYQOBJECT_H */

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
@ -43,6 +48,57 @@ qstring_from_pyobject_arg(PyObject *object)
return QString::fromUtf8(conv.string(object));
}
PyTypeObject pyotherside_QObjectType = {
PyVarObject_HEAD_INIT(NULL, 0)
"pyotherside.QObject", /* tp_name */
sizeof(pyotherside_QObject), /* tp_basicsize */
0, /* tp_itemsize */
0, /* tp_dealloc */
0, /* tp_print */
0, /* tp_getattr */
0, /* tp_setattr */
0, /* tp_reserved */
0, /* tp_repr */
0, /* tp_as_number */
0, /* tp_as_sequence */
0, /* tp_as_mapping */
0, /* tp_hash */
0, /* tp_call */
0, /* tp_str */
0, /* tp_getattro */
0, /* tp_setattro */
0, /* tp_as_buffer */
Py_TPFLAGS_DEFAULT, /* tp_flags */
"Wrapped QObject", /* tp_doc */
};
PyTypeObject pyotherside_QObjectMethodType = {
PyVarObject_HEAD_INIT(NULL, 0)
"pyotherside.QObjectMethod", /* tp_name */
sizeof(pyotherside_QObjectMethod), /* tp_basicsize */
0, /* tp_itemsize */
0, /* tp_dealloc */
0, /* tp_print */
0, /* tp_getattr */
0, /* tp_setattr */
0, /* tp_reserved */
0, /* tp_repr */
0, /* tp_as_number */
0, /* tp_as_sequence */
0, /* tp_as_mapping */
0, /* tp_hash */
0, /* tp_call */
0, /* tp_str */
0, /* tp_getattro */
0, /* tp_setattro */
0, /* tp_as_buffer */
Py_TPFLAGS_DEFAULT, /* tp_flags */
"Bound method of wrapped QObject", /* tp_doc */
};
PyObject *
pyotherside_send(PyObject *self, PyObject *args)
{
@ -135,6 +191,225 @@ pyotherside_qrc_list_dir(PyObject *self, PyObject *dirname)
return convertQVariantToPyObject(dir.entryList());
}
void
pyotherside_QObject_dealloc(pyotherside_QObject *self)
{
delete self->m_qobject_ref;
Py_TYPE(self)->tp_free((PyObject *)self);
}
PyObject *
pyotherside_QObject_repr(PyObject *o)
{
if (!PyObject_TypeCheck(o, &pyotherside_QObjectType)) {
return PyErr_Format(PyExc_TypeError, "Not a pyotherside.QObject");
}
pyotherside_QObject *pyqobject = reinterpret_cast<pyotherside_QObject *>(o);
QObjectRef *ref = pyqobject->m_qobject_ref;
if (ref) {
QObject *qobject = ref->value();
const QMetaObject *metaObject = qobject->metaObject();
return PyUnicode_FromFormat("<pyotherside.QObject wrapper for %s at %p>",
metaObject->className(), qobject);
}
return PyUnicode_FromFormat("<dangling pyotherside.QObject wrapper>");
}
PyObject *
pyotherside_QObject_getattro(PyObject *o, PyObject *attr_name)
{
if (!PyObject_TypeCheck(o, &pyotherside_QObjectType)) {
return PyErr_Format(PyExc_TypeError, "Not a pyotherside.QObject");
}
if (!PyUnicode_Check(attr_name)) {
return PyErr_Format(PyExc_TypeError, "attr_name must be a string");
}
pyotherside_QObject *pyqobject = reinterpret_cast<pyotherside_QObject *>(o);
QObjectRef *ref = pyqobject->m_qobject_ref;
if (!ref) {
return PyErr_Format(PyExc_ValueError, "Dangling QObject");
}
QObject *qobject = ref->value();
if (!qobject) {
return PyErr_Format(PyExc_ReferenceError, "Referenced QObject was deleted");
}
const QMetaObject *metaObject = qobject->metaObject();
QString attrName = convertPyObjectToQVariant(attr_name).toString();
for (int i=0; i<metaObject->propertyCount(); i++) {
QMetaProperty property = metaObject->property(i);
if (attrName == property.name()) {
return convertQVariantToPyObject(property.read(qobject));
}
}
for (int i=0; i<metaObject->methodCount(); i++) {
QMetaMethod method = metaObject->method(i);
if (attrName == method.name()) {
pyotherside_QObjectMethod *result = PyObject_New(pyotherside_QObjectMethod,
&pyotherside_QObjectMethodType);
result->m_method_ref = new QObjectMethodRef(*ref, attrName);
return reinterpret_cast<PyObject *>(result);
}
}
return PyErr_Format(PyExc_AttributeError, "Not a valid attribute");
}
int
pyotherside_QObject_setattro(PyObject *o, PyObject *attr_name, PyObject *v)
{
if (!PyObject_TypeCheck(o, &pyotherside_QObjectType)) {
PyErr_Format(PyExc_TypeError, "Not a pyotherside.QObject");
return -1;
}
if (!PyUnicode_Check(attr_name)) {
PyErr_Format(PyExc_TypeError, "attr_name must be a string");
return -1;
}
pyotherside_QObject *pyqobject = reinterpret_cast<pyotherside_QObject *>(o);
QObjectRef *ref = pyqobject->m_qobject_ref;
if (!ref) {
PyErr_Format(PyExc_ValueError, "Dangling QObject");
return -1;
}
QObject *qobject = ref->value();
if (!qobject) {
PyErr_Format(PyExc_ReferenceError, "Referenced QObject was deleted");
return -1;
}
const QMetaObject *metaObject = qobject->metaObject();
QString attrName = convertPyObjectToQVariant(attr_name).toString();
for (int i=0; i<metaObject->propertyCount(); i++) {
QMetaProperty property = metaObject->property(i);
if (attrName == property.name()) {
QVariant variant(convertPyObjectToQVariant(v));
if (!property.write(qobject, variant)) {
PyErr_Format(PyExc_AttributeError, "Could not set property %s to %s(%s)",
attrName.toUtf8().constData(),
variant.typeName(),
variant.toString().toUtf8().constData());
return -1;
}
return 0;
}
}
PyErr_Format(PyExc_AttributeError, "Property does not exist: %s",
attrName.toUtf8().constData());
return -1;
}
void
pyotherside_QObjectMethod_dealloc(pyotherside_QObjectMethod *self)
{
delete self->m_method_ref;
Py_TYPE(self)->tp_free((PyObject *)self);
}
PyObject *
pyotherside_QObjectMethod_repr(PyObject *o)
{
if (!PyObject_TypeCheck(o, &pyotherside_QObjectMethodType)) {
return PyErr_Format(PyExc_TypeError, "Not a pyotherside.QObjectMethod");
}
pyotherside_QObjectMethod *pyqobjectmethod = reinterpret_cast<pyotherside_QObjectMethod *>(o);
QObjectMethodRef *ref = pyqobjectmethod->m_method_ref;
if (!ref) {
return PyUnicode_FromFormat("<dangling pyotherside.QObjectMethod>");
}
QObjectRef oref = ref->object();
QObject *qobject = oref.value();
if (!qobject) {
return PyUnicode_FromFormat("<pyotherside.QObjectMethod '%s' bound to deleted QObject>",
ref->method().toUtf8().constData());
}
const QMetaObject *metaObject = qobject->metaObject();
return PyUnicode_FromFormat("<pyotherside.QObjectMethod '%s' bound to %s at %p>",
ref->method().toUtf8().constData(), metaObject->className(), qobject);
}
PyObject *
pyotherside_QObjectMethod_call(PyObject *callable_object, PyObject *args, PyObject *kw)
{
if (!PyObject_TypeCheck(callable_object, &pyotherside_QObjectMethodType)) {
return PyErr_Format(PyExc_TypeError, "Not a pyotherside.QObjectMethod");
}
if (!PyTuple_Check(args)) {
return PyErr_Format(PyExc_TypeError, "Argument list not a tuple");
}
if (kw) {
if (!PyMapping_Check(kw)) {
return PyErr_Format(PyExc_TypeError, "Keyword arguments not a mapping");
}
if (PyMapping_Size(kw) > 0) {
return PyErr_Format(PyExc_ValueError, "Keyword arguments not supported");
}
}
QList<QVariant> qargs = convertPyObjectToQVariant(args).toList();
pyotherside_QObjectMethod *pyqobjectmethod = reinterpret_cast<pyotherside_QObjectMethod *>(callable_object);
QObjectMethodRef *ref = pyqobjectmethod->m_method_ref;
if (!ref) {
return PyErr_Format(PyExc_ValueError, "Dangling QObject");
}
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();
if (!o) {
return PyErr_Format(PyExc_ReferenceError, "Referenced QObject was deleted");
}
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);
}
return PyErr_Format(PyExc_RuntimeError, "QObject method call failed");
}
}
return PyErr_Format(PyExc_RuntimeError, "QObject method not found: %s",
ref->method().toUtf8().constData());
}
static PyMethodDef PyOtherSideMethods[] = {
/* Introduced in PyOtherSide 1.0 */
{"send", pyotherside_send, METH_VARARGS, "Send data to Qt."},
@ -184,6 +459,32 @@ PyOtherSide_init()
// Version of PyOtherSide (new in 1.3)
PyModule_AddStringConstant(pyotherside, "version", PYOTHERSIDE_VERSION);
// QObject wrappers (new in 1.4)
pyotherside_QObjectType.tp_new = PyType_GenericNew;
pyotherside_QObjectType.tp_repr = pyotherside_QObject_repr;
pyotherside_QObjectType.tp_getattro = pyotherside_QObject_getattro;
pyotherside_QObjectType.tp_setattro = pyotherside_QObject_setattro;
pyotherside_QObjectType.tp_dealloc = (destructor)pyotherside_QObject_dealloc;
if (PyType_Ready(&pyotherside_QObjectType) < 0) {
qFatal("Could not initialize QObjectType");
// Not reached
return NULL;
}
Py_INCREF(&pyotherside_QObjectType);
PyModule_AddObject(pyotherside, "QObject", (PyObject *)(&pyotherside_QObjectType));
pyotherside_QObjectMethodType.tp_new = PyType_GenericNew;
pyotherside_QObjectMethodType.tp_repr = pyotherside_QObjectMethod_repr;
pyotherside_QObjectMethodType.tp_call = pyotherside_QObjectMethod_call;
pyotherside_QObjectMethodType.tp_dealloc = (destructor)pyotherside_QObjectMethod_dealloc;
if (PyType_Ready(&pyotherside_QObjectMethodType) < 0) {
qFatal("Could not initialize QObjectMethodType");
// Not reached
return NULL;
}
Py_INCREF(&pyotherside_QObjectMethodType);
PyModule_AddObject(pyotherside, "QObjectMethod", (PyObject *)(&pyotherside_QObjectMethodType));
return pyotherside;
}
@ -193,6 +494,7 @@ QPythonPriv::QPythonPriv()
, atexit_callback()
, image_provider()
, traceback_mod()
, pyotherside_mod()
, thread_state(NULL)
{
PyImport_AppendInittab("pyotherside", PyOtherSide_init);
@ -216,6 +518,12 @@ QPythonPriv::QPythonPriv()
PyEval_GetBuiltins());
}
// Need to "self-import" the pyotherside module here, so that Python code
// can use objects wrapped with pyotherside.QObject without crashing when
// the user's Python code doesn't "import pyotherside"
pyotherside_mod = PyObjectRef(PyImport_ImportModule("pyotherside"), true);
assert(pyotherside_mod);
// Release the GIL
thread_state = PyEval_SaveThread();
}

View File

@ -22,11 +22,13 @@
#include "Python.h"
#include "pyobject_ref.h"
#include "pyqobject.h"
#include <QObject>
#include <QVariant>
#include <QString>
class QPythonPriv : public QObject {
Q_OBJECT
@ -50,6 +52,7 @@ class QPythonPriv : public QObject {
PyObjectRef atexit_callback;
PyObjectRef image_provider;
PyObjectRef traceback_mod;
PyObjectRef pyotherside_mod;
PyThreadState *thread_state;
signals:

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,11 @@ 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
HEADERS += pyqobject.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