Skip to content

Commit 01e1206

Browse files
committed
added basic support for QtCore.Signal, QtCore.Property and QtCore.Slot
1 parent 43ca518 commit 01e1206

17 files changed

+1035
-10
lines changed

src/PythonQt.cpp

Lines changed: 189 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,13 +46,15 @@
4646
#include "PythonQtSignal.h"
4747
#include "PythonQtSignalReceiver.h"
4848
#include "PythonQtConversion.h"
49+
#include "PythonQtProperty.h"
4950
#include "PythonQtStdIn.h"
5051
#include "PythonQtStdOut.h"
5152
#include "PythonQtCppWrapperFactory.h"
5253
#include "PythonQtVariants.h"
5354
#include "PythonQtStdDecorators.h"
5455
#include "PythonQtQFileImporter.h"
5556
#include "PythonQtBoolResult.h"
57+
#include "PythonQtSlotDecorator.h"
5658

5759
#include <QDir>
5860

@@ -261,6 +263,11 @@ void PythonQt::init(int flags, const QByteArray& pythonQtModuleName)
261263
}
262264

263265
_self->priv()->pythonQtModule().addObject("Debug", _self->priv()->_debugAPI);
266+
267+
PyModule_AddObject(pack, "Slot", (PyObject*)&PythonQtSlotDecorator_Type);
268+
PyModule_AddObject(pack, "Signal", (PyObject*)&PythonQtSignalFunction_Type);
269+
PyModule_AddObject(pack, "Property", (PyObject*)&PythonQtProperty_Type);
270+
264271
}
265272
}
266273

@@ -303,6 +310,16 @@ PythonQt::PythonQt(int flags, const QByteArray& pythonQtModuleName)
303310
}
304311
Py_INCREF(&PythonQtSignalFunction_Type);
305312

313+
if (PyType_Ready(&PythonQtSlotDecorator_Type) < 0) {
314+
std::cerr << "could not initialize PythonQtSlotDecorator_Type" << ", in " << __FILE__ << ":" << __LINE__ << std::endl;
315+
}
316+
Py_INCREF(&PythonQtSlotDecorator_Type);
317+
318+
if (PyType_Ready(&PythonQtProperty_Type) < 0) {
319+
std::cerr << "could not initialize PythonQtProperty_Type" << ", in " << __FILE__ << ":" << __LINE__ << std::endl;
320+
}
321+
Py_INCREF(&PythonQtProperty_Type);
322+
306323
PythonQtBoolResult_Type.tp_new = PyType_GenericNew;
307324
if (PyType_Ready(&PythonQtBoolResult_Type) < 0) {
308325
std::cerr << "could not initialize PythonQtBoolResult_Type" << ", in " << __FILE__ << ":" << __LINE__ << std::endl;
@@ -1911,6 +1928,178 @@ bool PythonQtPrivate::isMethodDescriptor(PyObject* object) const
19111928
return false;
19121929
}
19131930

1931+
// We need this for the dynamic meta object building:
1932+
#include <private/qmetaobjectbuilder_p.h>
1933+
1934+
const QMetaObject* PythonQtPrivate::getDynamicMetaObject(PythonQtInstanceWrapper* wrapper, const QMetaObject* prototypeMetaObject)
1935+
{
1936+
PythonQtDynamicClassInfo* info = wrapper->dynamicClassInfo();
1937+
if (info) {
1938+
if (!info->_dynamicMetaObject) {
1939+
buildDynamicMetaObject(((PythonQtClassWrapper*)Py_TYPE(wrapper)), prototypeMetaObject);
1940+
}
1941+
return info->_dynamicMetaObject;
1942+
}
1943+
return prototypeMetaObject;
1944+
}
1945+
1946+
void PythonQtPrivate::buildDynamicMetaObject(PythonQtClassWrapper* type, const QMetaObject* prototypeMetaObject)
1947+
{
1948+
QMetaObjectBuilder builder;
1949+
builder.setSuperClass(prototypeMetaObject);
1950+
builder.setClassName(((PyTypeObject*)type)->tp_name);
1951+
1952+
PyObject* dict = ((PyTypeObject*)type)->tp_dict;
1953+
Py_ssize_t pos = NULL;
1954+
PyObject* value = NULL;
1955+
PyObject* key = NULL;
1956+
static PyObject* qtSlots = PyString_FromString("_qtSlots");
1957+
1958+
bool needsMetaObject = false;
1959+
// Iterate over all members and check if they affect the QMetaObject:
1960+
// First look for signals:
1961+
while (PyDict_Next(dict, &pos, &key, &value)) {
1962+
if (PythonQtSignalFunction_Check(value)) {
1963+
// A signal object, register with the meta object
1964+
PythonQtSignalFunctionObject* signal = (PythonQtSignalFunctionObject*)value;
1965+
if (signal->_dynamicInfo) {
1966+
signal->_dynamicInfo->name = PyString_AsString(key);
1967+
foreach(QByteArray sig, signal->_dynamicInfo->signatures) {
1968+
QMetaMethodBuilder method = builder.addSignal(signal->_dynamicInfo->name + "(" + sig + ")");
1969+
needsMetaObject = true;
1970+
}
1971+
}
1972+
}
1973+
}
1974+
pos = NULL;
1975+
value = NULL;
1976+
key = NULL;
1977+
// Now look for slots: (this is a bug in QMetaObjectBuilder, all signals need to be added first)
1978+
while (PyDict_Next(dict, &pos, &key, &value)) {
1979+
if (PythonQtProperty_Check(value)) {
1980+
PythonQtProperty* prop = (PythonQtProperty*)value;
1981+
QMetaPropertyBuilder newProp = builder.addProperty(PyString_AsString(key), prop->data->cppType);
1982+
newProp.setReadable(true);
1983+
newProp.setWritable(prop->data->fset != NULL);
1984+
newProp.setResettable(prop->data->freset != NULL);
1985+
newProp.setDesignable(prop->data->designable);
1986+
newProp.setScriptable(prop->data->scriptable);
1987+
newProp.setStored(prop->data->stored);
1988+
newProp.setUser(prop->data->user);
1989+
newProp.setConstant(prop->data->constant);
1990+
newProp.setFinal(prop->data->final);
1991+
if (prop->data->notify) {
1992+
PythonQtSignalFunctionObject* signal = (PythonQtSignalFunctionObject*)prop->data->notify;
1993+
if (signal->_dynamicInfo) {
1994+
QByteArray sig = signal->_dynamicInfo->signatures.at(0);
1995+
QByteArray fullSig = signal->_dynamicInfo->name + "(" + sig + ")";
1996+
int idx = builder.indexOfSignal(fullSig);
1997+
if (idx != -1) {
1998+
newProp.setNotifySignal(builder.method(idx));
1999+
} else {
2000+
std::cerr << "could not find notify signal signature " << fullSig.constData();
2001+
}
2002+
}
2003+
}
2004+
}
2005+
if (PyFunction_Check(value) && PyObject_HasAttr(value, qtSlots)) {
2006+
// A function which has a "_qtSlots" signature list, add the slots to the meta object
2007+
PyObject* signatures = PyObject_GetAttr(value, qtSlots);
2008+
Py_ssize_t count = PyList_Size(signatures);
2009+
for (Py_ssize_t i = 0; i < count; i++) {
2010+
PyObject* signature = PyList_GET_ITEM(signatures, i);
2011+
QByteArray sig = PyString_AsString(signature);
2012+
// Split the return type and the rest of the signature,
2013+
// no spaces should be in the rest of the signature...
2014+
QList<QByteArray> parts = sig.split(' ');
2015+
QMetaMethodBuilder method = builder.addSlot(parts[1]);
2016+
// set the return type of the slot
2017+
method.setReturnType(parts[0]);
2018+
needsMetaObject = true;
2019+
}
2020+
}
2021+
// TODO: handle enums, classinfo, ...
2022+
}
2023+
if (needsMetaObject) {
2024+
type->_dynamicClassInfo->_dynamicMetaObject = builder.toMetaObject();
2025+
type->_dynamicClassInfo->_classInfo = new PythonQtClassInfo();
2026+
type->_dynamicClassInfo->_classInfo->setupQObject(type->_dynamicClassInfo->_dynamicMetaObject);
2027+
} else {
2028+
// we don't need an own meta object, just use the one from our base class
2029+
type->_dynamicClassInfo->_dynamicMetaObject = prototypeMetaObject;
2030+
}
2031+
}
2032+
2033+
2034+
int PythonQtPrivate::handleMetaCall(QObject* object, PythonQtInstanceWrapper* wrapper, QMetaObject::Call call, int id, void** args)
2035+
{
2036+
const QMetaObject* meta = object->metaObject();
2037+
int methodCount = meta->methodCount();
2038+
if (call == QMetaObject::InvokeMetaMethod) {
2039+
QMetaMethod method = meta->method(id);
2040+
if (method.methodType() == QMetaMethod::Signal) {
2041+
// just emit the signal, there is no Python code
2042+
QMetaObject::activate(object, id, args);
2043+
} else {
2044+
callMethodInPython(method, wrapper, args);
2045+
}
2046+
} else {
2047+
QMetaProperty metaProp = meta->property(id);
2048+
if (!metaProp.isValid()) {
2049+
return id - methodCount;
2050+
}
2051+
PythonQtProperty* prop = NULL;
2052+
// Get directly from the Python class, since we don't want to get the value of the property
2053+
PyObject* maybeProp = PyBaseObject_Type.tp_getattro((PyObject*)wrapper, PyString_FromString(metaProp.name()));
2054+
if (maybeProp && PythonQtProperty_Check(maybeProp)) {
2055+
prop = (PythonQtProperty*)maybeProp;
2056+
} else {
2057+
return id - methodCount;
2058+
}
2059+
const PythonQtMethodInfo::ParameterInfo& info = PythonQtMethodInfo::getParameterInfoForMetaType(metaProp.userType());
2060+
2061+
if (call == QMetaObject::WriteProperty) {
2062+
PyObject* value = PythonQtConv::ConvertQtValueToPython(info, args[0]);
2063+
bool ok = prop->data->callSetter((PyObject*)wrapper, value);
2064+
Py_XDECREF(value);
2065+
2066+
return ok ? 0 : -1;
2067+
2068+
} else if (call == QMetaObject::ReadProperty) {
2069+
2070+
PyObject* value = prop->data->callGetter((PyObject*)wrapper);
2071+
if (value) {
2072+
void* result = PythonQtConv::ConvertPythonToQt(info, value, false, NULL, args[0]);
2073+
Py_DECREF(value);
2074+
return (result == NULL ? -1 : 0);
2075+
} else {
2076+
return -1;
2077+
}
2078+
} else if (call == QMetaObject::ResetProperty) {
2079+
bool ok = prop->data->callReset((PyObject*)wrapper);
2080+
return ok ? 0 : -1;
2081+
}
2082+
}
2083+
return id - methodCount;
2084+
}
2085+
2086+
void PythonQtPrivate::callMethodInPython(QMetaMethod &method, PythonQtInstanceWrapper* wrapper, void** args)
2087+
{
2088+
QByteArray methodSig = method.methodSignature();
2089+
PyObject* func = PyObject_GetAttrString((PyObject*)wrapper, method.name());
2090+
if (func) {
2091+
const PythonQtMethodInfo* methodInfo = PythonQtMethodInfo::getCachedMethodInfo(method, NULL);
2092+
PyObject* result = PythonQtSignalTarget::call(func, methodInfo, args, false);
2093+
if (result) {
2094+
PythonQtConv::ConvertPythonToQt(methodInfo->parameters().at(0), result, false, NULL, args[0]);
2095+
// TODO: handle error?
2096+
//PythonQt::priv()->handleVirtualOverloadReturnError("devType", methodInfo, result);
2097+
}
2098+
Py_XDECREF(result);
2099+
Py_DECREF(func);
2100+
}
2101+
}
2102+
19142103
QString PythonQtPrivate::getSignature(PyObject* object)
19152104
{
19162105
QString signature;

src/PythonQt.h

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -739,6 +739,17 @@ class PYTHONQT_EXPORT PythonQtPrivate : public QObject {
739739
//! returns true if the object is a method descriptor (same as inspect.ismethoddescriptor() in inspect.py)
740740
bool isMethodDescriptor(PyObject* object) const;
741741

742+
//! get the dynamic meta object for the given wrapper. It will contain the signals/slots that have been added in Python
743+
const QMetaObject* getDynamicMetaObject(PythonQtInstanceWrapper* wrapper, const QMetaObject* prototypeMetaObject);
744+
745+
void buildDynamicMetaObject(PythonQtClassWrapper* type, const QMetaObject* prototypeMetaObject);
746+
747+
//! redirected from shell classes, tries to call the given meta call on the Python wrapper.
748+
int handleMetaCall(QObject* object, PythonQtInstanceWrapper* wrapper, QMetaObject::Call call, int id, void** args);
749+
750+
//! calls the given method on Python function with same name.
751+
void callMethodInPython(QMetaMethod &method, PythonQtInstanceWrapper* wrapper, void** args);
752+
742753
private:
743754
//! Setup the shared library suffixes by getting them from the "imp" module.
744755
void setupSharedLibrarySuffixes();

src/PythonQtClassInfo.cpp

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -289,7 +289,7 @@ bool PythonQtClassInfo::lookForEnumAndCache(const QMetaObject* meta, const char*
289289
found = true;
290290
break;
291291
} else {
292-
std::cout << "enum " << e.name() << " not found on " << className().constData() << std::endl;
292+
std::cerr << "enum " << e.name() << " not found on " << className().constData() << std::endl;
293293
}
294294
}
295295
}
@@ -1063,4 +1063,9 @@ PythonQtMemberInfo::PythonQtMemberInfo( const QMetaProperty& prop )
10631063
_property = prop;
10641064
_enumValue = NULL;
10651065
_pythonType = NULL;
1066-
}
1066+
}
1067+
1068+
PythonQtDynamicClassInfo::~PythonQtDynamicClassInfo()
1069+
{
1070+
delete _classInfo;
1071+
}

src/PythonQtClassInfo.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,16 @@
4141
#include "PythonQt.h"
4242

4343
class PythonQtSlotInfo;
44+
class PythonQtClassInfo;
45+
46+
struct PythonQtDynamicClassInfo
47+
{
48+
PythonQtDynamicClassInfo() { _dynamicMetaObject = NULL; _classInfo = NULL; }
49+
~PythonQtDynamicClassInfo();
50+
51+
const QMetaObject* _dynamicMetaObject;
52+
PythonQtClassInfo* _classInfo;
53+
};
4454

4555
struct PythonQtMemberInfo {
4656
enum Type {

src/PythonQtClassWrapper.cpp

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,6 @@ static PyObject* PythonQtInstanceWrapper_binaryfunc(PyObject* self, PyObject* ot
150150
PyObject* result = NULL;
151151
PythonQtMemberInfo opSlot = wrapper->classInfo()->member(opName);
152152
if (opSlot._type == PythonQtMemberInfo::Slot) {
153-
// TODO get rid of tuple
154153
PyObject* args = PyTuple_New(1);
155154
Py_INCREF(other);
156155
PyTuple_SET_ITEM(args, 0, other);
@@ -178,7 +177,6 @@ static PyObject* PythonQtInstanceWrapper_mul(PyObject* self, PyObject* other)
178177
PyObject* result = NULL;
179178
PythonQtMemberInfo opSlot = wrapper->classInfo()->member("__mul__");
180179
if (opSlot._type == PythonQtMemberInfo::Slot) {
181-
// TODO get rid of tuple
182180
PyObject* args = PyTuple_New(1);
183181
Py_INCREF(other);
184182
PyTuple_SET_ITEM(args, 0, other);
@@ -345,6 +343,7 @@ static int PythonQtClassWrapper_init(PythonQtClassWrapper* self, PyObject* args,
345343
if (PyType_Type.tp_init((PyObject *)self, args, kwds) < 0) {
346344
return -1;
347345
}
346+
self->_dynamicClassInfo = NULL;
348347

349348
// if we have no CPP class information, try our base class
350349
if (!self->classInfo()) {
@@ -367,6 +366,8 @@ static int PythonQtClassWrapper_init(PythonQtClassWrapper* self, PyObject* args,
367366

368367
// take the class info from the superType
369368
self->_classInfo = ((PythonQtClassWrapper*)superType)->classInfo();
369+
370+
self->_dynamicClassInfo = new PythonQtDynamicClassInfo();
370371
}
371372

372373
return 0;

src/PythonQtClassWrapper.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,8 @@ class PythonQtClassInfo;
5757
//! the type of the PythonQt class wrapper objects
5858
extern PYTHONQT_EXPORT PyTypeObject PythonQtClassWrapper_Type;
5959

60+
struct PythonQtDynamicClassInfo;
61+
6062
//---------------------------------------------------------------
6163
//! a Python wrapper object for PythonQt wrapped classes
6264
//! which inherits from the Python type object to allow
@@ -70,6 +72,8 @@ typedef struct {
7072
//! get the class info
7173
PythonQtClassInfo* classInfo() { return _classInfo; }
7274

75+
PythonQtDynamicClassInfo* _dynamicClassInfo;
76+
7377
} PythonQtClassWrapper;
7478

7579
//---------------------------------------------------------------

src/PythonQtConversion.cpp

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -462,7 +462,6 @@ void* PythonQtConv::ConvertPythonToQt(const PythonQtMethodInfo::ParameterInfo& i
462462
bool ok;
463463
int value = PyObjGetInt(obj, true, ok);
464464
if (ok && value==0) {
465-
// TODOXXX is this wise? or should it be expected from the programmer to use None?
466465
PythonQtValueStorage_ADD_VALUE_IF_NEEDED(alreadyAllocatedCPPObject,global_ptrStorage, void*, NULL, ptr);
467466
}
468467
}
@@ -1461,6 +1460,49 @@ PyObject* PythonQtConv::convertFromStringRef(const void* inObject, int /*metaTyp
14611460
return PythonQtConv::QStringToPyObject(((QStringRef*)inObject)->toString());
14621461
}
14631462

1463+
QByteArray PythonQtConv::getCPPTypeName(PyObject* type)
1464+
{
1465+
QByteArray result;
1466+
if (PyType_Check(type)) {
1467+
if (PyType_IsSubtype((PyTypeObject*)type, (PyTypeObject*)(&PythonQtClassWrapper_Type))) {
1468+
PythonQtClassWrapper* wrapper = (PythonQtClassWrapper*)type;
1469+
result = wrapper->classInfo()->className();
1470+
} else {
1471+
PyTypeObject* typeObject = reinterpret_cast<PyTypeObject*>(type);
1472+
if (typeObject == &PyFloat_Type) {
1473+
result = "double";
1474+
} else if (typeObject == &PyBool_Type) {
1475+
result = "bool";
1476+
#ifndef PY3K
1477+
} else if (typeObject == &PyInt_Type) {
1478+
result = "qint32";
1479+
#endif
1480+
} else if (typeObject == &PyLong_Type) {
1481+
result = "qint64";
1482+
} else if (isStringType(typeObject)) {
1483+
result = "QString";
1484+
} else {
1485+
result = "PyObject*";
1486+
}
1487+
}
1488+
} else if (type == Py_None) {
1489+
result = "void";
1490+
} else {
1491+
bool dummy;
1492+
result = PyObjGetString(type, true, dummy).toLatin1();
1493+
}
1494+
return result;
1495+
}
1496+
1497+
bool PythonQtConv::isStringType(PyTypeObject* type)
1498+
{
1499+
#ifdef PY3K
1500+
return type == &PyUnicode_Type
1501+
#else
1502+
return type == &PyUnicode_Type || type == &PyString_Type;
1503+
#endif
1504+
}
1505+
14641506
PyObject* PythonQtConv::convertFromQListOfPythonQtObjectPtr(const void* inObject, int /*metaTypeId*/)
14651507
{
14661508
QList<PythonQtObjectPtr>& list = *((QList<PythonQtObjectPtr>*)inObject);

0 commit comments

Comments
 (0)