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:
commit
eccdad94ee
@ -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)
|
||||
--------------------------
|
||||
|
65
examples/qobject_reference.py
Normal file
65
examples/qobject_reference.py
Normal 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
|
37
examples/qobject_reference.qml
Normal file
37
examples/qobject_reference.qml
Normal 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';
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
@ -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();
|
||||
|
@ -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
39
src/pyqobject.h
Normal 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
75
src/qobject_ref.cpp
Normal 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
60
src/qobject_ref.h
Normal 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
|
@ -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();
|
||||
}
|
||||
|
@ -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:
|
||||
|
@ -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:
|
||||
|
@ -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
|
||||
|
||||
|
@ -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()
|
||||
{
|
||||
|
@ -36,6 +36,7 @@ class TestPyOtherSide : public QObject {
|
||||
void testPyObjectConverter();
|
||||
void testPyObjectRefRoundTrip();
|
||||
void testPyObjectRefAssignment();
|
||||
void testQObjectRef();
|
||||
void testConvertToPythonAndBack();
|
||||
void testSetToList();
|
||||
};
|
||||
|
@ -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
|
||||
|
Loading…
x
Reference in New Issue
Block a user