diff options
Diffstat (limited to 'sources/pyside6/libpyside/pysidesignal.cpp')
-rw-r--r-- | sources/pyside6/libpyside/pysidesignal.cpp | 1040 |
1 files changed, 1040 insertions, 0 deletions
diff --git a/sources/pyside6/libpyside/pysidesignal.cpp b/sources/pyside6/libpyside/pysidesignal.cpp new file mode 100644 index 000000000..d14f32525 --- /dev/null +++ b/sources/pyside6/libpyside/pysidesignal.cpp @@ -0,0 +1,1040 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt for Python. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <sbkpython.h> +#include "pysidesignal.h" +#include "pysidesignal_p.h" +#include "pysidestaticstrings.h" +#include "signalmanager.h" + +#include <shiboken.h> + +#include <QtCore/QObject> +#include <QtCore/QMetaMethod> +#include <QtCore/QMetaObject> +#include <signature.h> + +#include <algorithm> +#include <utility> + +#define QT_SIGNAL_SENTINEL '2' + +namespace PySide { +namespace Signal { + //aux + class SignalSignature { + public: + SignalSignature() = default; + explicit SignalSignature(QByteArray parameterTypes) : + m_parameterTypes(std::move(parameterTypes)) {} + explicit SignalSignature(QByteArray parameterTypes, QMetaMethod::Attributes attributes) : + m_parameterTypes(std::move(parameterTypes)), + m_attributes(attributes) {} + + QByteArray m_parameterTypes; + QMetaMethod::Attributes m_attributes = QMetaMethod::Compatibility; + }; + + static QByteArray buildSignature(const QByteArray &, const QByteArray &); + static void appendSignature(PySideSignal *, const SignalSignature &); + static void instanceInitialize(PySideSignalInstance *, PyObject *, PySideSignal *, PyObject *, int); + static QByteArray parseSignature(PyObject *); + static PyObject *buildQtCompatible(const QByteArray &); +} +} + +extern "C" +{ + +// Signal methods +static int signalTpInit(PyObject *, PyObject *, PyObject *); +static void signalFree(void *); +static void signalInstanceFree(void *); +static PyObject *signalGetItem(PyObject *self, PyObject *key); +static PyObject *signalToString(PyObject *self); +static PyObject *signalDescrGet(PyObject *self, PyObject *obj, PyObject *type); + +// Signal Instance methods +static PyObject *signalInstanceConnect(PyObject *, PyObject *, PyObject *); +static PyObject *signalInstanceDisconnect(PyObject *, PyObject *); +static PyObject *signalInstanceEmit(PyObject *, PyObject *); +static PyObject *signalInstanceGetItem(PyObject *, PyObject *); + +static PyObject *signalInstanceCall(PyObject *self, PyObject *args, PyObject *kw); +static PyObject *signalCall(PyObject *, PyObject *, PyObject *); + +static PyObject *metaSignalCheck(PyObject *, PyObject *); + + +static PyMethodDef MetaSignal_methods[] = { + {"__instancecheck__", (PyCFunction)metaSignalCheck, METH_O|METH_STATIC, NULL}, + {0, 0, 0, 0} +}; + +static PyType_Slot PySideMetaSignalType_slots[] = { + {Py_tp_methods, reinterpret_cast<void *>(MetaSignal_methods)}, + {Py_tp_base, reinterpret_cast<void *>(&PyType_Type)}, + {Py_tp_free, reinterpret_cast<void *>(PyObject_GC_Del)}, + {Py_tp_dealloc, reinterpret_cast<void *>(Sbk_object_dealloc)}, + {0, 0} +}; +static PyType_Spec PySideMetaSignalType_spec = { + "2:PySide6.QtCore.MetaSignal", + 0, + // sizeof(PyHeapTypeObject) is filled in by SbkType_FromSpecWithBases + // which calls PyType_Ready which calls inherit_special. + 0, + Py_TPFLAGS_DEFAULT, + PySideMetaSignalType_slots, +}; + + +static PyTypeObject *PySideMetaSignalTypeF(void) +{ + static PyTypeObject *type = nullptr; + if (!type) { + PyObject *bases = Py_BuildValue("(O)", &PyType_Type); + type = (PyTypeObject *)SbkType_FromSpecWithBases(&PySideMetaSignalType_spec, bases); + Py_XDECREF(bases); + } + return type; +} + +static PyType_Slot PySideSignalType_slots[] = { + {Py_mp_subscript, reinterpret_cast<void *>(signalGetItem)}, + {Py_tp_descr_get, reinterpret_cast<void *>(signalDescrGet)}, + {Py_tp_call, reinterpret_cast<void *>(signalCall)}, + {Py_tp_str, reinterpret_cast<void *>(signalToString)}, + {Py_tp_init, reinterpret_cast<void *>(signalTpInit)}, + {Py_tp_new, reinterpret_cast<void *>(PyType_GenericNew)}, + {Py_tp_free, reinterpret_cast<void *>(signalFree)}, + {Py_tp_dealloc, reinterpret_cast<void *>(Sbk_object_dealloc)}, + {0, 0} +}; +static PyType_Spec PySideSignalType_spec = { + "2:PySide6.QtCore.Signal", + sizeof(PySideSignal), + 0, + Py_TPFLAGS_DEFAULT, + PySideSignalType_slots, +}; + + +PyTypeObject *PySideSignalTypeF(void) +{ + static PyTypeObject *type = nullptr; + if (!type) { + type = reinterpret_cast<PyTypeObject *>(SbkType_FromSpec(&PySideSignalType_spec)); + PyTypeObject *hold = Py_TYPE(type); + Py_TYPE(type) = PySideMetaSignalTypeF(); + Py_INCREF(Py_TYPE(type)); + Py_DECREF(hold); + } + return type; +} + +static PyMethodDef SignalInstance_methods[] = { + {"connect", (PyCFunction)signalInstanceConnect, METH_VARARGS|METH_KEYWORDS, 0}, + {"disconnect", signalInstanceDisconnect, METH_VARARGS, 0}, + {"emit", signalInstanceEmit, METH_VARARGS, 0}, + {0, 0, 0, 0} /* Sentinel */ +}; + +static PyType_Slot PySideSignalInstanceType_slots[] = { + {Py_mp_subscript, reinterpret_cast<void *>(signalInstanceGetItem)}, + {Py_tp_call, reinterpret_cast<void *>(signalInstanceCall)}, + {Py_tp_methods, reinterpret_cast<void *>(SignalInstance_methods)}, + {Py_tp_new, reinterpret_cast<void *>(PyType_GenericNew)}, + {Py_tp_free, reinterpret_cast<void *>(signalInstanceFree)}, + {Py_tp_dealloc, reinterpret_cast<void *>(Sbk_object_dealloc)}, + {0, 0} +}; +static PyType_Spec PySideSignalInstanceType_spec = { + "2:PySide6.QtCore.SignalInstance", + sizeof(PySideSignalInstance), + 0, + Py_TPFLAGS_DEFAULT, + PySideSignalInstanceType_slots, +}; + + +PyTypeObject *PySideSignalInstanceTypeF(void) +{ + static PyTypeObject *type = + reinterpret_cast<PyTypeObject *>(SbkType_FromSpec(&PySideSignalInstanceType_spec)); + return type; +} + +static int signalTpInit(PyObject *self, PyObject *args, PyObject *kwds) +{ + static PyObject *emptyTuple = nullptr; + static const char *kwlist[] = {"name", "arguments", nullptr}; + char *argName = nullptr; + PyObject *argArguments = nullptr; + + if (emptyTuple == 0) + emptyTuple = PyTuple_New(0); + + if (!PyArg_ParseTupleAndKeywords(emptyTuple, kwds, + "|sO:QtCore.Signal", const_cast<char **>(kwlist), &argName, &argArguments)) + return -1; + + bool tupledArgs = false; + PySideSignal *data = reinterpret_cast<PySideSignal *>(self); + if (!data->data) + data->data = new PySideSignalData; + if (argName) + data->data->signalName = argName; + + data->data->signalArguments = new QByteArrayList(); + if (argArguments && PySequence_Check(argArguments)) { + Py_ssize_t argument_size = PySequence_Size(argArguments); + for (Py_ssize_t i = 0; i < argument_size; ++i) { + PyObject *item = PySequence_GetItem(argArguments, i); + PyObject *strObj = PyUnicode_AsUTF8String(item); + char *s = PyBytes_AsString(strObj); + Py_DECREF(strObj); + Py_DECREF(item); + if (s != nullptr) + data->data->signalArguments->append(QByteArray(s)); + } + } + + for (Py_ssize_t i = 0, i_max = PyTuple_Size(args); i < i_max; i++) { + PyObject *arg = PyTuple_GET_ITEM(args, i); + if (PySequence_Check(arg) && !Shiboken::String::check(arg) && !PyEnumMeta_Check(arg)) { + tupledArgs = true; + const auto sig = PySide::Signal::parseSignature(arg); + PySide::Signal::appendSignature( + data, + PySide::Signal::SignalSignature(sig)); + } + } + + if (!tupledArgs) { + const auto sig = PySide::Signal::parseSignature(args); + PySide::Signal::appendSignature( + data, + PySide::Signal::SignalSignature(sig)); + } + + return 0; +} + +static void signalFree(void *self) +{ + auto pySelf = reinterpret_cast<PyObject *>(self); + auto data = reinterpret_cast<PySideSignal *>(self); + delete data->data; + data->data = nullptr; + Py_XDECREF(data->homonymousMethod); + data->homonymousMethod = 0; + + Py_TYPE(pySelf)->tp_base->tp_free(self); +} + +static PyObject *signalGetItem(PyObject *self, PyObject *key) +{ + auto data = reinterpret_cast<PySideSignal *>(self); + QByteArray sigKey; + if (key) { + sigKey = PySide::Signal::parseSignature(key); + } else { + sigKey = data->data == nullptr || data->data->signatures.isEmpty() + ? PySide::Signal::voidType() : data->data->signatures.constFirst().signature; + } + auto sig = PySide::Signal::buildSignature(data->data->signalName, sigKey); + return Shiboken::String::fromCString(sig.constData()); +} + + +static PyObject *signalToString(PyObject *self) +{ + return signalGetItem(self, 0); +} + +static void signalInstanceFree(void *self) +{ + auto pySelf = reinterpret_cast<PyObject *>(self); + auto data = reinterpret_cast<PySideSignalInstance *>(self); + + PySideSignalInstancePrivate *dataPvt = data->d; + + Py_XDECREF(dataPvt->homonymousMethod); + + if (dataPvt->next) { + Py_DECREF(dataPvt->next); + dataPvt->next = 0; + } + delete dataPvt; + data->d = 0; + Py_TYPE(pySelf)->tp_base->tp_free(self); +} + +static PyObject *signalInstanceConnect(PyObject *self, PyObject *args, PyObject *kwds) +{ + PyObject *slot = nullptr; + PyObject *type = nullptr; + static const char *kwlist[] = {"slot", "type", nullptr}; + + if (!PyArg_ParseTupleAndKeywords(args, kwds, + "O|O:SignalInstance", const_cast<char **>(kwlist), &slot, &type)) + return 0; + + PySideSignalInstance *source = reinterpret_cast<PySideSignalInstance *>(self); + Shiboken::AutoDecRef pyArgs(PyList_New(0)); + + bool match = false; + if (Py_TYPE(slot) == PySideSignalInstanceTypeF()) { + PySideSignalInstance *sourceWalk = source; + PySideSignalInstance *targetWalk; + + //find best match + while (sourceWalk && !match) { + targetWalk = reinterpret_cast<PySideSignalInstance *>(slot); + while (targetWalk && !match) { + if (QMetaObject::checkConnectArgs(sourceWalk->d->signature, targetWalk->d->signature)) { + PyList_Append(pyArgs, sourceWalk->d->source); + Shiboken::AutoDecRef sourceSignature(PySide::Signal::buildQtCompatible(sourceWalk->d->signature)); + PyList_Append(pyArgs, sourceSignature); + + PyList_Append(pyArgs, targetWalk->d->source); + Shiboken::AutoDecRef targetSignature(PySide::Signal::buildQtCompatible(targetWalk->d->signature)); + PyList_Append(pyArgs, targetSignature); + + match = true; + } + targetWalk = reinterpret_cast<PySideSignalInstance *>(targetWalk->d->next); + } + sourceWalk = reinterpret_cast<PySideSignalInstance *>(sourceWalk->d->next); + } + } else { + // Check signature of the slot (method or function) to match signal + int slotArgs = -1; + bool useSelf = false; + bool isMethod = PyMethod_Check(slot); + bool isFunction = PyFunction_Check(slot); + bool matchedSlot = false; + + QByteArray functionName; + PySideSignalInstance *it = source; + + if (isMethod || isFunction) { + PyObject *function = isMethod ? PyMethod_GET_FUNCTION(slot) : slot; + auto *objCode = reinterpret_cast<PepCodeObject *>(PyFunction_GET_CODE(function)); + useSelf = isMethod; + slotArgs = PepCode_GET_FLAGS(objCode) & CO_VARARGS ? -1 : PepCode_GET_ARGCOUNT(objCode); + if (useSelf) + slotArgs -= 1; + + // Get signature args + bool isShortCircuit = false; + int signatureArgs = 0; + QStringList argsSignature; + + argsSignature = PySide::Signal::getArgsFromSignature(it->d->signature, + &isShortCircuit); + signatureArgs = argsSignature.length(); + + // Iterate the possible types of connection for this signal and compare + // it with slot arguments + if (signatureArgs != slotArgs) { + while (it->d->next != nullptr) { + it = it->d->next; + argsSignature = PySide::Signal::getArgsFromSignature(it->d->signature, + &isShortCircuit); + signatureArgs = argsSignature.length(); + if (signatureArgs == slotArgs) { + matchedSlot = true; + break; + } + } + } + } + + // Adding references to pyArgs + PyList_Append(pyArgs, source->d->source); + + if (matchedSlot) { + // If a slot matching the same number of arguments was found, + // include signature to the pyArgs + Shiboken::AutoDecRef signature(PySide::Signal::buildQtCompatible(it->d->signature)); + PyList_Append(pyArgs, signature); + } else { + // Try the first by default if the slot was not found + Shiboken::AutoDecRef signature(PySide::Signal::buildQtCompatible(source->d->signature)); + PyList_Append(pyArgs, signature); + } + PyList_Append(pyArgs, slot); + match = true; + } + + if (type) + PyList_Append(pyArgs, type); + + if (match) { + Shiboken::AutoDecRef tupleArgs(PyList_AsTuple(pyArgs)); + Shiboken::AutoDecRef pyMethod(PyObject_GetAttr(source->d->source, + PySide::PyName::qtConnect())); + if (pyMethod.isNull()) { // PYSIDE-79: check if pyMethod exists. + PyErr_SetString(PyExc_RuntimeError, "method 'connect' vanished!"); + return 0; + } + PyObject *result = PyObject_CallObject(pyMethod, tupleArgs); + if (result == Py_True || result == Py_False) + return result; + Py_XDECREF(result); + } + if (!PyErr_Occurred()) // PYSIDE-79: inverse the logic. A Null return needs an error. + PyErr_Format(PyExc_RuntimeError, "Failed to connect signal %s.", + source->d->signature.constData()); + return 0; +} + +static int argCountInSignature(const char *signature) +{ + return QByteArray(signature).count(",") + 1; +} + +static PyObject *signalInstanceEmit(PyObject *self, PyObject *args) +{ + PySideSignalInstance *source = reinterpret_cast<PySideSignalInstance *>(self); + + Shiboken::AutoDecRef pyArgs(PyList_New(0)); + int numArgsGiven = PySequence_Fast_GET_SIZE(args); + int numArgsInSignature = argCountInSignature(source->d->signature); + + // If number of arguments given to emit is smaller than the first source signature expects, + // it is possible it's a case of emitting a signal with default parameters. + // Search through all the overloaded signals with the same name, and try to find a signature + // with the same number of arguments as given to emit, and is also marked as a cloned method + // (which in metaobject parlance means a signal with default parameters). + // @TODO: This should be improved to take into account argument types as well. The current + // assumption is there are no signals which are both overloaded on argument types and happen to + // have signatures with default parameters. + if (numArgsGiven < numArgsInSignature) { + PySideSignalInstance *possibleDefaultInstance = source; + while ((possibleDefaultInstance = possibleDefaultInstance->d->next)) { + if (possibleDefaultInstance->d->attributes & QMetaMethod::Cloned + && argCountInSignature(possibleDefaultInstance->d->signature) == numArgsGiven) { + source = possibleDefaultInstance; + break; + } + } + } + Shiboken::AutoDecRef sourceSignature(PySide::Signal::buildQtCompatible(source->d->signature)); + + PyList_Append(pyArgs, sourceSignature); + for (Py_ssize_t i = 0, max = PyTuple_Size(args); i < max; i++) + PyList_Append(pyArgs, PyTuple_GetItem(args, i)); + + Shiboken::AutoDecRef pyMethod(PyObject_GetAttr(source->d->source, + PySide::PyName::qtEmit())); + + Shiboken::AutoDecRef tupleArgs(PyList_AsTuple(pyArgs)); + return PyObject_CallObject(pyMethod, tupleArgs); +} + +static PyObject *signalInstanceGetItem(PyObject *self, PyObject *key) +{ + auto data = reinterpret_cast<PySideSignalInstance *>(self); + const auto sigName = data->d->signalName; + const auto sigKey = PySide::Signal::parseSignature(key); + const auto sig = PySide::Signal::buildSignature(sigName, sigKey); + while (data) { + if (data->d->signature == sig) { + PyObject *result = reinterpret_cast<PyObject *>(data); + Py_INCREF(result); + return result; + } + data = data->d->next; + } + + PyErr_Format(PyExc_IndexError, "Signature %s not found for signal: %s", + sig.constData(), sigName.constData()); + return 0; +} + +static PyObject *signalInstanceDisconnect(PyObject *self, PyObject *args) +{ + auto source = reinterpret_cast<PySideSignalInstance *>(self); + Shiboken::AutoDecRef pyArgs(PyList_New(0)); + + PyObject *slot; + if (PyTuple_Check(args) && PyTuple_GET_SIZE(args)) + slot = PyTuple_GET_ITEM(args, 0); + else + slot = Py_None; + + bool match = false; + if (Py_TYPE(slot) == PySideSignalInstanceTypeF()) { + PySideSignalInstance *target = reinterpret_cast<PySideSignalInstance *>(slot); + if (QMetaObject::checkConnectArgs(source->d->signature, target->d->signature)) { + PyList_Append(pyArgs, source->d->source); + Shiboken::AutoDecRef source_signature(PySide::Signal::buildQtCompatible(source->d->signature)); + PyList_Append(pyArgs, source_signature); + + PyList_Append(pyArgs, target->d->source); + Shiboken::AutoDecRef target_signature(PySide::Signal::buildQtCompatible(target->d->signature)); + PyList_Append(pyArgs, target_signature); + match = true; + } + } else { + //try the first signature + PyList_Append(pyArgs, source->d->source); + Shiboken::AutoDecRef signature(PySide::Signal::buildQtCompatible(source->d->signature)); + PyList_Append(pyArgs, signature); + + // disconnect all, so we need to use the c++ signature disconnect(qobj, signal, 0, 0) + if (slot == Py_None) + PyList_Append(pyArgs, slot); + PyList_Append(pyArgs, slot); + match = true; + } + + if (match) { + Shiboken::AutoDecRef tupleArgs(PyList_AsTuple(pyArgs)); + Shiboken::AutoDecRef pyMethod(PyObject_GetAttr(source->d->source, + PySide::PyName::qtDisconnect())); + PyObject *result = PyObject_CallObject(pyMethod, tupleArgs); + if (!result || result == Py_True) + return result; + else + Py_DECREF(result); + } + + PyErr_Format(PyExc_RuntimeError, "Failed to disconnect signal %s.", + source->d->signature.constData()); + return 0; +} + +// PYSIDE-68: Supply the missing __get__ function +static PyObject *signalDescrGet(PyObject *self, PyObject *obj, PyObject * /*type*/) +{ + auto signal = reinterpret_cast<PySideSignal *>(self); + // Return the unbound signal if there is nothing to bind it to. + if (obj == nullptr || obj == Py_None) { + Py_INCREF(self); + return self; + } + Shiboken::AutoDecRef name(Py_BuildValue("s", signal->data->signalName.data())); + return reinterpret_cast<PyObject *>(PySide::Signal::initialize(signal, name, obj)); +} + +static PyObject *signalCall(PyObject *self, PyObject *args, PyObject *kw) +{ + auto signal = reinterpret_cast<PySideSignal *>(self); + + // Native C++ signals can't be called like functions, thus we throw an exception. + // The only way calling a signal can succeed (the Python equivalent of C++'s operator() ) + // is when a method with the same name as the signal is attached to an object. + // An example is QProcess::error() (don't check the docs, but the source code of qprocess.h). + if (!signal->homonymousMethod) { + PyErr_SetString(PyExc_TypeError, "native Qt signal is not callable"); + return 0; + } + + descrgetfunc getDescriptor = Py_TYPE(signal->homonymousMethod)->tp_descr_get; + + // Check if there exists a method with the same name as the signal, which is also a static + // method in C++ land. + Shiboken::AutoDecRef homonymousMethod(getDescriptor(signal->homonymousMethod, 0, 0)); + if (PyCFunction_Check(homonymousMethod) + && (PyCFunction_GET_FLAGS(homonymousMethod.object()) & METH_STATIC)) { +#if PY_VERSION_HEX >= 0x03090000 + return PyObject_Call(homonymousMethod, args, kw); +#else + return PyCFunction_Call(homonymousMethod, args, kw); +#endif + } + + // Assumes homonymousMethod is not a static method. + ternaryfunc callFunc = Py_TYPE(signal->homonymousMethod)->tp_call; + return callFunc(homonymousMethod, args, kw); +} + +static PyObject *signalInstanceCall(PyObject *self, PyObject *args, PyObject *kw) +{ + auto PySideSignal = reinterpret_cast<PySideSignalInstance *>(self); + if (!PySideSignal->d->homonymousMethod) { + PyErr_SetString(PyExc_TypeError, "native Qt signal is not callable"); + return 0; + } + + descrgetfunc getDescriptor = Py_TYPE(PySideSignal->d->homonymousMethod)->tp_descr_get; + Shiboken::AutoDecRef homonymousMethod(getDescriptor(PySideSignal->d->homonymousMethod, PySideSignal->d->source, 0)); +#if PY_VERSION_HEX >= 0x03090000 + return PyObject_Call(homonymousMethod, args, kw); +#else + return PyCFunction_Call(homonymousMethod, args, kw); +#endif +} + +static PyObject *metaSignalCheck(PyObject * /* klass */, PyObject *arg) +{ + if (PyType_IsSubtype(Py_TYPE(arg), PySideSignalInstanceTypeF())) + Py_RETURN_TRUE; + else + Py_RETURN_FALSE; +} + +} // extern "C" + +namespace PySide { +namespace Signal { + +static const char *MetaSignal_SignatureStrings[] = { + "PySide6.QtCore.MetaSignal.__instancecheck__(object:object)->bool", + nullptr}; // Sentinel + +static const char *Signal_SignatureStrings[] = { + "PySide6.QtCore.Signal(*types:type,name:str=nullptr,arguments:str=nullptr)", + nullptr}; // Sentinel + +static const char *SignalInstance_SignatureStrings[] = { + "PySide6.QtCore.SignalInstance.connect(slot:object,type:type=nullptr)", + "PySide6.QtCore.SignalInstance.disconnect(slot:object=nullptr)", + "PySide6.QtCore.SignalInstance.emit(*args:typing.Any)", + nullptr}; // Sentinel + +void init(PyObject *module) +{ + if (InitSignatureStrings(PySideMetaSignalTypeF(), MetaSignal_SignatureStrings) < 0) + return; + Py_INCREF(PySideMetaSignalTypeF()); + PyModule_AddObject(module, "MetaSignal", reinterpret_cast<PyObject *>(PySideMetaSignalTypeF())); + + if (InitSignatureStrings(PySideSignalTypeF(), Signal_SignatureStrings) < 0) + return; + Py_INCREF(PySideSignalTypeF()); + PyModule_AddObject(module, "Signal", reinterpret_cast<PyObject *>(PySideSignalTypeF())); + + if (InitSignatureStrings(PySideSignalInstanceTypeF(), SignalInstance_SignatureStrings) < 0) + return; + Py_INCREF(PySideSignalInstanceTypeF()); + PyModule_AddObject(module, "SignalInstance", reinterpret_cast<PyObject *>(PySideSignalInstanceTypeF())); +} + +bool checkType(PyObject *pyObj) +{ + if (pyObj) + return PyType_IsSubtype(Py_TYPE(pyObj), PySideSignalTypeF()); + return false; +} + +void updateSourceObject(PyObject *source) +{ + PyTypeObject *objType = reinterpret_cast<PyTypeObject *>(PyObject_Type(source)); + + Py_ssize_t pos = 0; + PyObject *value; + PyObject *key; + + while (PyDict_Next(objType->tp_dict, &pos, &key, &value)) { + if (PyObject_TypeCheck(value, PySideSignalTypeF())) { + Shiboken::AutoDecRef signalInstance(reinterpret_cast<PyObject *>(PyObject_New(PySideSignalInstance, PySideSignalInstanceTypeF()))); + instanceInitialize(signalInstance.cast<PySideSignalInstance *>(), key, reinterpret_cast<PySideSignal *>(value), source, 0); + PyObject_SetAttr(source, key, signalInstance); + } + } + + Py_XDECREF(objType); +} + +QByteArray getTypeName(PyObject *type) +{ + if (PyType_Check(type)) { + if (PyType_IsSubtype(reinterpret_cast<PyTypeObject *>(type), + reinterpret_cast<PyTypeObject *>(SbkObject_TypeF()))) { + auto objType = reinterpret_cast<SbkObjectType *>(type); + return Shiboken::ObjectType::getOriginalName(objType); + } + // Translate python types to Qt names + auto objType = reinterpret_cast<PyTypeObject *>(type); + if (Shiboken::String::checkType(objType)) + return QByteArrayLiteral("QString"); + if (objType == &PyInt_Type) + return QByteArrayLiteral("int"); + if (objType == &PyLong_Type) + return QByteArrayLiteral("long"); + if (objType == &PyFloat_Type) + return QByteArrayLiteral("double"); + if (objType == &PyBool_Type) + return QByteArrayLiteral("bool"); + if (objType == &PyList_Type) + return QByteArrayLiteral("QVariantList"); + if (Py_TYPE(objType) == SbkEnumType_TypeF()) + return Shiboken::Enum::getCppName(objType); + return QByteArrayLiteral("PyObject"); + } + if (type == Py_None) // Must be checked before as Shiboken::String::check accepts Py_None + return voidType(); + if (Shiboken::String::check(type)) { + QByteArray result = Shiboken::String::toCString(type); + if (result == "qreal") + result = sizeof(qreal) == sizeof(double) ? "double" : "float"; + return result; + } + return QByteArray(); +} + +static QByteArray buildSignature(const QByteArray &name, const QByteArray &signature) +{ + return QMetaObject::normalizedSignature(name + '(' + signature + ')'); +} + +static QByteArray parseSignature(PyObject *args) +{ + if (args && (Shiboken::String::check(args) || !PySequence_Check(args))) + return getTypeName(args); + + QByteArray signature; + for (Py_ssize_t i = 0, i_max = PySequence_Size(args); i < i_max; i++) { + Shiboken::AutoDecRef arg(PySequence_GetItem(args, i)); + const auto typeName = getTypeName(arg); + if (!typeName.isEmpty()) { + if (!signature.isEmpty()) + signature += ','; + signature += typeName; + } + } + return signature; +} + +static void appendSignature(PySideSignal *self, const SignalSignature &signature) +{ + self->data->signatures.append({signature.m_parameterTypes, signature.m_attributes}); +} + +static void instanceInitialize(PySideSignalInstance *self, PyObject *name, PySideSignal *data, PyObject *source, int index) +{ + self->d = new PySideSignalInstancePrivate; + PySideSignalInstancePrivate *selfPvt = self->d; + selfPvt->next = nullptr; + if (data->data->signalName.isEmpty()) + data->data->signalName = Shiboken::String::toCString(name); + selfPvt->signalName = data->data->signalName; + + selfPvt->source = source; + const auto &signature = data->data->signatures.at(index); + selfPvt->signature = buildSignature(self->d->signalName, signature.signature); + selfPvt->attributes = signature.attributes; + selfPvt->homonymousMethod = 0; + if (data->homonymousMethod) { + selfPvt->homonymousMethod = data->homonymousMethod; + Py_INCREF(selfPvt->homonymousMethod); + } + index++; + + if (index < data->data->signatures.size()) { + selfPvt->next = PyObject_New(PySideSignalInstance, PySideSignalInstanceTypeF()); + instanceInitialize(selfPvt->next, name, data, source, index); + } +} + +PySideSignalInstance *initialize(PySideSignal *self, PyObject *name, PyObject *object) +{ + PySideSignalInstance *instance = PyObject_New(PySideSignalInstance, + PySideSignalInstanceTypeF()); + instanceInitialize(instance, name, self, object, 0); + auto sbkObj = reinterpret_cast<SbkObject *>(object); + if (!Shiboken::Object::wasCreatedByPython(sbkObj)) + Py_INCREF(object); // PYSIDE-79: this flag was crucial for a wrapper call. + return instance; +} + +bool connect(PyObject *source, const char *signal, PyObject *callback) +{ + Shiboken::AutoDecRef pyMethod(PyObject_GetAttr(source, + PySide::PyName::qtConnect())); + if (pyMethod.isNull()) + return false; + + Shiboken::AutoDecRef pySignature(Shiboken::String::fromCString(signal)); + Shiboken::AutoDecRef pyArgs(PyTuple_Pack(3, source, pySignature.object(), callback)); + PyObject *result = PyObject_CallObject(pyMethod, pyArgs); + if (result == Py_False) { + PyErr_Format(PyExc_RuntimeError, "Failed to connect signal %s, to python callable object.", signal); + Py_DECREF(result); + result = 0; + } + return result; +} + +PySideSignalInstance *newObjectFromMethod(PyObject *source, const QList<QMetaMethod>& methodList) +{ + PySideSignalInstance *root = nullptr; + PySideSignalInstance *previous = nullptr; + for (const QMetaMethod &m : methodList) { + PySideSignalInstance *item = PyObject_New(PySideSignalInstance, PySideSignalInstanceTypeF()); + if (!root) + root = item; + + if (previous) + previous->d->next = item; + + item->d = new PySideSignalInstancePrivate; + PySideSignalInstancePrivate *selfPvt = item->d; + selfPvt->source = source; + Py_INCREF(selfPvt->source); // PYSIDE-79: an INCREF is missing. + QByteArray cppName(m.methodSignature()); + cppName.truncate(cppName.indexOf('(')); + // separe SignalName + selfPvt->signalName = cppName; + selfPvt->signature = m.methodSignature(); + selfPvt->attributes = m.attributes(); + selfPvt->homonymousMethod = 0; + selfPvt->next = 0; + } + return root; +} + +template<typename T> +static typename T::value_type join(T t, const char *sep) +{ + typename T::value_type res; + if (t.isEmpty()) + return res; + + typename T::const_iterator it = t.begin(); + typename T::const_iterator end = t.end(); + res += *it; + ++it; + + while (it != end) { + res += sep; + res += *it; + ++it; + } + return res; +} + +static void _addSignalToWrapper(SbkObjectType *wrapperType, const char *signalName, PySideSignal *signal) +{ + auto typeDict = reinterpret_cast<PyTypeObject *>(wrapperType)->tp_dict; + PyObject *homonymousMethod; + if ((homonymousMethod = PyDict_GetItemString(typeDict, signalName))) { + Py_INCREF(homonymousMethod); + signal->homonymousMethod = homonymousMethod; + } + PyDict_SetItemString(typeDict, signalName, reinterpret_cast<PyObject *>(signal)); +} + +// This function is used by qStableSort to promote empty signatures +static bool compareSignals(const SignalSignature &sig1, const SignalSignature &) +{ + return sig1.m_parameterTypes.isEmpty(); +} + +static PyObject *buildQtCompatible(const QByteArray &signature) +{ + const auto ba = QT_SIGNAL_SENTINEL + signature; + return Shiboken::String::fromStringAndSize(ba, ba.size()); +} + +void registerSignals(SbkObjectType *pyObj, const QMetaObject *metaObject) +{ + typedef QHash<QByteArray, QList<SignalSignature> > SignalSigMap; + SignalSigMap signalsFound; + for (int i = metaObject->methodOffset(), max = metaObject->methodCount(); i < max; ++i) { + QMetaMethod method = metaObject->method(i); + + if (method.methodType() == QMetaMethod::Signal) { + QByteArray methodName(method.methodSignature()); + methodName.chop(methodName.size() - methodName.indexOf('(')); + SignalSignature signature; + signature.m_parameterTypes = join(method.parameterTypes(), ","); + if (method.attributes() & QMetaMethod::Cloned) + signature.m_attributes = QMetaMethod::Cloned; + signalsFound[methodName] << signature; + } + } + + SignalSigMap::Iterator it = signalsFound.begin(); + SignalSigMap::Iterator end = signalsFound.end(); + for (; it != end; ++it) { + PySideSignal *self = PyObject_New(PySideSignal, PySideSignalTypeF()); + self->data = new PySideSignalData; + self->data->signalName = it.key(); + self->homonymousMethod = 0; + + // Empty signatures comes first! So they will be the default signal signature + std::stable_sort(it.value().begin(), it.value().end(), &compareSignals); + SignalSigMap::mapped_type::const_iterator j = it.value().begin(); + SignalSigMap::mapped_type::const_iterator endJ = it.value().end(); + for (; j != endJ; ++j) { + const SignalSignature &sig = *j; + appendSignature(self, sig); + } + + _addSignalToWrapper(pyObj, it.key(), self); + Py_DECREF(reinterpret_cast<PyObject *>(self)); + } +} + +PyObject *getObject(PySideSignalInstance *signal) +{ + return signal->d->source; +} + +const char *getSignature(PySideSignalInstance *signal) +{ + return signal->d->signature; +} + +QStringList getArgsFromSignature(const char *signature, bool *isShortCircuit) +{ + QString qsignature = QString::fromLatin1(signature).trimmed(); + QStringList result; + + if (isShortCircuit) + *isShortCircuit = !qsignature.contains(QLatin1Char('(')); + if (qsignature.contains(QLatin1String("()")) || qsignature.contains(QLatin1String("(void)"))) + return result; + if (qsignature.endsWith(QLatin1Char(')'))) { + const int paren = qsignature.indexOf(QLatin1Char('(')); + if (paren >= 0) { + qsignature.chop(1); + qsignature.remove(0, paren + 1); + result = qsignature.split(QLatin1Char(',')); + for (QString &type : result) + type = type.trimmed(); + } + } + return result; +} + +QString getCallbackSignature(const char *signal, QObject *receiver, PyObject *callback, bool encodeName) +{ + QByteArray functionName; + int numArgs = -1; + bool useSelf = false; + bool isMethod = PyMethod_Check(callback); + bool isFunction = PyFunction_Check(callback); + + if (isMethod || isFunction) { + PyObject *function = isMethod ? PyMethod_GET_FUNCTION(callback) : callback; + auto objCode = reinterpret_cast<PepCodeObject *>(PyFunction_GET_CODE(function)); + functionName = Shiboken::String::toCString(PepFunction_GetName(function)); + useSelf = isMethod; + numArgs = PepCode_GET_FLAGS(objCode) & CO_VARARGS ? -1 : PepCode_GET_ARGCOUNT(objCode); + } else if (PyCFunction_Check(callback)) { + const PyCFunctionObject *funcObj = reinterpret_cast<const PyCFunctionObject *>(callback); + functionName = PepCFunction_GET_NAMESTR(funcObj); + useSelf = PyCFunction_GET_SELF(funcObj); + const int flags = PyCFunction_GET_FLAGS(funcObj); + + if (receiver) { + //Search for signature on metaobject + const QMetaObject *mo = receiver->metaObject(); + QByteArray prefix(functionName); + prefix += '('; + for (int i = 0; i < mo->methodCount(); i++) { + QMetaMethod me = mo->method(i); + if ((strncmp(me.methodSignature(), prefix, prefix.size()) == 0) && + QMetaObject::checkConnectArgs(signal, me.methodSignature())) { + numArgs = me.parameterTypes().size() + useSelf; + break; + } + } + } + + if (numArgs == -1) { + if (flags & METH_VARARGS) + numArgs = -1; + else if (flags & METH_NOARGS) + numArgs = 0; + } + } else if (PyCallable_Check(callback)) { + functionName = "__callback" + QByteArray::number((qlonglong)callback); + } + + Q_ASSERT(!functionName.isEmpty()); + + bool isShortCircuit = false; + + const QString functionNameS = QLatin1String(functionName); + QString signature = encodeName ? codeCallbackName(callback, functionNameS) : functionNameS; + QStringList args = getArgsFromSignature(signal, &isShortCircuit); + + if (!isShortCircuit) { + signature.append(QLatin1Char('(')); + if (numArgs == -1) + numArgs = std::numeric_limits<int>::max(); + while (args.count() && (args.count() > (numArgs - useSelf))) { + args.removeLast(); + } + signature.append(args.join(QLatin1Char(','))); + signature.append(QLatin1Char(')')); + } + return signature; +} + +bool isQtSignal(const char *signal) +{ + return (signal && signal[0] == QT_SIGNAL_SENTINEL); +} + +bool checkQtSignal(const char *signal) +{ + if (!isQtSignal(signal)) { + PyErr_SetString(PyExc_TypeError, "Use the function PySide6.QtCore.SIGNAL on signals"); + return false; + } + return true; +} + +QString codeCallbackName(PyObject *callback, const QString &funcName) +{ + if (PyMethod_Check(callback)) { + PyObject *self = PyMethod_GET_SELF(callback); + PyObject *func = PyMethod_GET_FUNCTION(callback); + return funcName + QString::number(quint64(self), 16) + QString::number(quint64(func), 16); + } + return funcName + QString::number(quint64(callback), 16); +} + +QByteArray voidType() +{ + return QByteArrayLiteral("void"); +} + +} //namespace Signal +} //namespace PySide + |