From fcf1aa8b42a41866f29a46d4da6c08fed139ef37 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mika=C3=ABl=20Capelle?= Date: Mon, 11 May 2020 23:00:29 +0200 Subject: [PATCH] Move converter to a dedicate file. Add Q_DELEGATE for QObject-derived wrappers. --- src/runner/converters.h | 467 +++++++++++++++++++++++++ src/runner/pythonrunner.cpp | 664 +++++------------------------------- 2 files changed, 555 insertions(+), 576 deletions(-) create mode 100644 src/runner/converters.h diff --git a/src/runner/converters.h b/src/runner/converters.h new file mode 100644 index 0000000..6fa50e4 --- /dev/null +++ b/src/runner/converters.h @@ -0,0 +1,467 @@ +#ifndef PYTHON_CONVERTERS_HPP +#define PYTHON_CONVERTERS_HPP + +#include +#include +#include +#include +#include +#include + +// sip and qt slots seems to conflict +#include + +#include "idownloadmanager.h" +#include "imodrepositorybridge.h" + +// Include the container converters from utils: +#include "pythonutils.h" + +namespace utils { + + namespace bpy = boost::python; + + namespace QString_converter { + + /** + * We need this since sip does not expose QString but uses standard python str. + */ + struct QString_to_python_str + { + static PyObject* convert(const QString& str) { + // It's safer to explicitly convert to unicode as if we don't, this can return + // either str or unicode without it being easy to know which to expect + bpy::object pyStr = bpy::object(qUtf8Printable(str)); + if (SIPBytes_Check(pyStr.ptr())) + pyStr = pyStr.attr("decode")("utf-8"); + return bpy::incref(pyStr.ptr()); + } + }; + + struct QString_from_python_str + { + + static void* convertible(PyObject* objPtr) { + return SIPBytes_Check(objPtr) || PyUnicode_Check(objPtr) ? objPtr : nullptr; + } + + static void construct(PyObject* objPtr, bpy::converter::rvalue_from_python_stage1_data* data) { + // Ensure the string uses 8-bit characters + PyObject* strPtr = PyUnicode_Check(objPtr) ? PyUnicode_AsUTF8String(objPtr) : objPtr; + + // Extract the character data from the python string + const char* value = SIPBytes_AsString(strPtr); + assert(value != nullptr); + + // allocate storage + void* storage = ((bpy::converter::rvalue_from_python_storage*)data)->storage.bytes; + + // construct QString in the allocated memory + new (storage) QString(value); + + data->convertible = storage; + + // Deallocate local copy if one was made + if (strPtr != objPtr) + Py_DecRef(strPtr); + } + }; + + } + + namespace QFlags_converter { + + /** + * + */ + template + struct QFlags_to_int + { + static PyObject* convert(const QFlags& flags) { + return bpy::incref(bpy::object(static_cast(flags)).ptr()); + } + }; + + template + struct QFlags_from_python_obj + { + + static void* convertible(PyObject* objPtr) { + return SIPLong_Check(objPtr) ? objPtr : nullptr; + } + + static void construct(PyObject* objPtr, bpy::converter::rvalue_from_python_stage1_data* data) { + int intVersion = (int)SIPLong_AsLong(objPtr); + T tVersion = (T)intVersion; + void* storage = ((bpy::converter::rvalue_from_python_storage>*)data)->storage.bytes; + new (storage) QFlags(tVersion); + + data->convertible = storage; + } + }; + + } + + namespace QVariant_converter { + + struct QVariant_to_python_obj + { + static PyObject* convert(const QVariant& var) { + switch (var.type()) { + case QVariant::Invalid: return bpy::incref(Py_None); + case QVariant::Int: return SIPLong_FromLong(var.toInt()); + case QVariant::UInt: return PyLong_FromUnsignedLong(var.toUInt()); + case QVariant::Bool: return PyBool_FromLong(var.toBool()); + case QVariant::String: return bpy::incref(bpy::object(var.toString()).ptr()); + case QVariant::List: { + return bpy::incref(bpy::object(var.toList()).ptr()); + } break; + case QVariant::Map: { + return bpy::incref(bpy::object(var.toMap()).ptr()); + } break; + default: { + PyErr_Format(PyExc_TypeError, "type unsupported: %d", var.type()); + throw bpy::error_already_set(); + } break; + } + } + }; + + struct QVariant_from_python_obj + { + + static void* convertible(PyObject* objPtr) { + if (!SIPBytes_Check(objPtr) && !PyUnicode_Check(objPtr) && !PyLong_Check(objPtr) && + !PyBool_Check(objPtr) && !PyList_Check(objPtr) && !PyDict_Check(objPtr) && + objPtr != Py_None) { + return nullptr; + } + return objPtr; + } + + template + static void constructVariant(const T& value, bpy::converter::rvalue_from_python_stage1_data* data) { + void* storage = ((bpy::converter::rvalue_from_python_storage*)data)->storage.bytes; + + new (storage) QVariant(value); + + data->convertible = storage; + } + + static void constructVariant(bpy::converter::rvalue_from_python_stage1_data* data) { + void* storage = ((bpy::converter::rvalue_from_python_storage*)data)->storage.bytes; + + new (storage) QVariant(); + + data->convertible = storage; + } + + static void construct(PyObject* objPtr, bpy::converter::rvalue_from_python_stage1_data* data) { + // PyBools will also return true for SIPLong_Check but not the other way around, so the order + // here is relevant + if (PyList_Check(objPtr)) { + constructVariant(bpy::extract(objPtr)(), data); + } + else if (objPtr == Py_None) { + constructVariant(data); + } + else if (PyDict_Check(objPtr)) { + constructVariant(bpy::extract(objPtr)(), data); + } + else if (SIPBytes_Check(objPtr) || PyUnicode_Check(objPtr)) { + constructVariant(bpy::extract(objPtr)(), data); + } + else if (PyBool_Check(objPtr)) { + constructVariant(bpy::extract(objPtr)(), data); + } + else if (SIPLong_Check(objPtr)) { + //QVariant doesn't have long. It has int or long long. Given that on m/s, + //long is 32 bits for 32- and 64- bit code... + constructVariant(bpy::extract(objPtr)(), data); + } + else { + PyErr_SetString(PyExc_TypeError, "type unsupported"); + throw bpy::error_already_set(); + } + } + }; + + } + + namespace QClass_converter { + + template struct MetaData; + + template <> struct MetaData { static const char* className() { return "QObject"; } }; + template <> struct MetaData { static const char* className() { return "QWidget"; } }; + template <> struct MetaData { static const char* className() { return "QDateTime"; } }; + template <> struct MetaData { static const char* className() { return "QDir"; } }; + template <> struct MetaData { static const char* className() { return "QFileInfo"; } }; + template <> struct MetaData { static const char* className() { return "QIcon"; } }; + template <> struct MetaData { static const char* className() { return "QSize"; } }; + template <> struct MetaData { static const char* className() { return "QStringList"; } }; + template <> struct MetaData { static const char* className() { return "QUrl"; } }; + template <> struct MetaData { static const char* className() { return "QVariant"; } }; + + template + struct QClass_converters + { + struct QClass_to_PyQt + { + template + static typename std::enable_if_t, T*> getSafeCopy(T* qClass) + { + return new T(*qClass); + } + + template + static typename std::enable_if_t, T*> getSafeCopy(T* qClass) + { + return qClass; + } + + static PyObject* convert(const T& object) { + const sipTypeDef* type = sipAPIAccess::sipAPI()->api_find_type(MetaData::className()); + if (type == nullptr) { + return bpy::incref(Py_None); + } + + PyObject* sipObj = sipAPIAccess::sipAPI()->api_convert_from_type((void*)getSafeCopy((T*)&object), type, 0); + if (sipObj == nullptr) { + return bpy::incref(Py_None); + } + + if (std::is_copy_constructible_v) + // Ensure Python deletes the C++ component + sipAPIAccess::sipAPI()->api_transfer_back(sipObj); + + return bpy::incref(sipObj); + } + + static PyObject* convert(T* object) { + if (object == nullptr) { + return bpy::incref(Py_None); + } + + const sipTypeDef* type = sipAPIAccess::sipAPI()->api_find_type(MetaData::className()); + if (type == nullptr) { + return bpy::incref(Py_None); + } + + PyObject* sipObj = sipAPIAccess::sipAPI()->api_convert_from_type(getSafeCopy(object), type, 0); + if (sipObj == nullptr) { + return bpy::incref(Py_None); + } + + if (std::is_copy_constructible_v) + // Ensure Python deletes the C++ component + sipAPIAccess::sipAPI()->api_transfer_back(sipObj); + + return bpy::incref(sipObj); + } + + static PyObject* convert(const T* object) { + return convert((T*)object); + } + + static PyTypeObject const* get_pytype() { + const sipTypeDef* type = sipAPIAccess::sipAPI()->api_find_type(MetaData::className()); + if (type == nullptr) { + return bpy::incref(Py_None); + } + return bpy::incref(type->td_py_type); + } + }; + + static void* QClass_from_PyQt(PyObject* objPtr) + { + // This would transfer responsibility for deconstructing the object to C++, but Boost assumes l-value converters (such as this) don't do that + // Instead, this should be called within the wrappers for functions which return deletable pointers. + //sipAPI()->api_transfer_to(objPtr, Py_None); + if (PyObject_TypeCheck(objPtr, sipAPIAccess::sipAPI()->api_simplewrapper_type)) { + sipSimpleWrapper* wrapper; + wrapper = reinterpret_cast(objPtr); + return wrapper->data; + } + else if (PyObject_TypeCheck(objPtr, sipAPIAccess::sipAPI()->api_wrapper_type)) { + sipWrapper* wrapper; + wrapper = reinterpret_cast(objPtr); + return wrapper->super.data; + } + else { + if constexpr (std::is_same_v) + { + // QStringLists aren't wrapped by PyQt - regular Python string/unicode lists are used instead + bpy::extract> extractor(objPtr); + if (extractor.check()) + return new QStringList(extractor()); + } + PyErr_SetString(PyExc_TypeError, "type not wrapped"); + bpy::throw_error_already_set(); + } + return new void*; + } + }; + + } + + namespace { + int getArgCount(PyObject* object) { + int result = 0; + PyObject* funcCode = PyObject_GetAttrString(object, "__code__"); + if (funcCode) { + PyObject* argCount = PyObject_GetAttrString(funcCode, "co_argcount"); + if (argCount) { + result = SIPLong_AsLong(argCount); + Py_DECREF(argCount); + } + Py_DECREF(funcCode); + } + return result; + } + } + + template + struct Functor_converter; + + template + struct Functor_converter + { + + struct FunctorWrapper + { + FunctorWrapper(boost::python::object callable) : m_Callable(callable) { + } + + ~FunctorWrapper() { + GILock lock; + m_Callable = bpy::object(); + } + + RET operator()(const PARAMS&...params) { + GILock lock; + if constexpr (std::is_same_v) { + m_Callable(params...); + } + else { + return bpy::extract(m_Callable(params...)); + } + } + + boost::python::object m_Callable; + }; + + static void* convertible(PyObject* object) + { + if (!PyCallable_Check(object) + || (getArgCount(object) != sizeof...(PARAMS))) { + return nullptr; + } + return object; + } + + static void construct(PyObject* object, bpy::converter::rvalue_from_python_stage1_data* data) + { + bpy::object callable(bpy::handle<>(bpy::borrowed(object))); + void* storage = ((bpy::converter::rvalue_from_python_storage>*)data)->storage.bytes; + new (storage) std::function(FunctorWrapper(callable)); + data->convertible = storage; + } + }; + + /** + * @brief Call policy that automatically downcast shared pointer of type FromType + * to shared pointer of type ToType. + */ + template + struct DowncastConverter { + + bool convertible() const { return true; } + + inline PyObject* operator()(std::shared_ptr p) const { + if (p == nullptr) { + return bpy::detail::none(); + } + else { + auto downcast_p = std::dynamic_pointer_cast(p); + bpy::object p_value = downcast_p == nullptr ? bpy::object{ p } : bpy::object{ downcast_p }; + return bpy::incref(p_value.ptr()); + } + } + + inline PyTypeObject const* get_pytype() const { + return bpy::converter::registered_pytype::get_pytype(); + } + + }; + + template + struct downcast_return { + + template + struct apply_; + + template + struct apply_> { + static_assert(std::is_convertible_v, std::shared_ptr>); + using type = DowncastConverter; + }; + + template + using apply = apply_>; + + }; + + // Functions: + inline void register_qstring_converter() { + using namespace QString_converter; + bpy::to_python_converter(); + bpy::converter::registry::push_back( + &QString_from_python_str::convertible, + &QString_from_python_str::construct, + bpy::type_id()); + } + + inline void register_qvariant_converter() { + using namespace QVariant_converter; + bpy::to_python_converter(); + bpy::converter::registry::push_back( + &QVariant_from_python_obj::convertible, + &QVariant_from_python_obj::construct, + bpy::type_id()); + } + + template + inline void register_qflags_converter() { + using T = typename Flags::enum_type; + using namespace QFlags_converter; + bpy::to_python_converter>(); + bpy::converter::registry::push_back( + &QFlags_from_python_obj::convertible, + &QFlags_from_python_obj::construct, + bpy::type_id()); + } + + template + inline void register_qclass_converter() { + using Converter = QClass_converter::QClass_converters; + bpy::converter::registry::insert(&Converter::QClass_from_PyQt, bpy::type_id()); + bpy::to_python_converter(); + bpy::to_python_converter(); + bpy::to_python_converter(); + } + + template + inline void register_functor_converter() { + using Converter = Functor_converter; + bpy::converter::registry::push_back( + &Converter::convertible, + &Converter::construct, + bpy::type_id>()); + } + + + +} + +#endif \ No newline at end of file diff --git a/src/runner/pythonrunner.cpp b/src/runner/pythonrunner.cpp index 97e8f8c..c9bc844 100644 --- a/src/runner/pythonrunner.cpp +++ b/src/runner/pythonrunner.cpp @@ -20,20 +20,17 @@ #include #include -// sip and qt slots seems to conflict -#include - #include #include #ifndef Q_MOC_RUN #include #include +#endif #include "tuple_helper.h" #include "variant_helper.h" -#include "pythonutils.h" -#endif +#include "converters.h" MOBase::IOrganizer *s_Organizer = nullptr; @@ -44,535 +41,11 @@ using namespace MOBase; namespace bpy = boost::python; namespace mp11 = boost::mp11; - -class PythonRunner : public IPythonRunner -{ - -public: - PythonRunner(const MOBase::IOrganizer *moInfo); - bool initPython(const QString &pythonDir); - QList instantiate(const QString &pluginName); - bool isPythonInstalled() const; - bool isPythonVersionSupported() const; - -private: - - void initPath(); - - /** - * @brief Append the underlying object of the given python object to the - * interface list if it is an instance (pointer) of the given type. - * - * @param obj The object to check. - * @param interfaces The list to append the object to. - * - */ - template - void appendIfInstance(bpy::object const& obj, QList &interfaces); - -private: - std::map m_PythonObjects; - const MOBase::IOrganizer *m_MOInfo; - wchar_t *m_PythonHome; -}; - - -IPythonRunner *CreatePythonRunner(MOBase::IOrganizer *moInfo, const QString &pythonDir) -{ - s_Organizer = moInfo; - PythonRunner *result = new PythonRunner(moInfo); - if (result->initPython(pythonDir)) { - return result; - } else { - delete result; - return nullptr; - } -} - -struct QString_to_python_str -{ - static PyObject *convert(const QString &str) { - // It's safer to explicitly convert to unicode as if we don't, this can return either str or unicode without it being easy to know which to expect - bpy::object pyStr = bpy::object(qUtf8Printable(str)); - if (SIPBytes_Check(pyStr.ptr())) - pyStr = pyStr.attr("decode")("utf-8"); - return bpy::incref(pyStr.ptr()); - } -}; - -template -struct QFlags_to_int -{ - static PyObject *convert(const QFlags &flags) { - return bpy::incref(bpy::object(static_cast(flags)).ptr()); - } -}; - - -struct QString_from_python_str -{ - QString_from_python_str() { - bpy::converter::registry::push_back(&convertible, &construct, bpy::type_id()); - } - - static void *convertible(PyObject *objPtr) { - return SIPBytes_Check(objPtr) || PyUnicode_Check(objPtr) ? objPtr : nullptr; - } - - static void construct(PyObject *objPtr, bpy::converter::rvalue_from_python_stage1_data *data) { - // Ensure the string uses 8-bit characters - PyObject *strPtr = PyUnicode_Check(objPtr) ? PyUnicode_AsUTF8String(objPtr) : objPtr; - - // Extract the character data from the python string - const char* value = SIPBytes_AsString(strPtr); - assert(value != nullptr); - - // allocate storage - void *storage = ((bpy::converter::rvalue_from_python_storage*)data)->storage.bytes; - - // construct QString in the allocated memory - new (storage) QString(value); - - data->convertible = storage; - - // Deallocate local copy if one was made - if (strPtr != objPtr) - Py_DecRef(strPtr); - } -}; - - -struct QVariant_to_python_obj -{ - static PyObject *convert(const QVariant &var) { - switch (var.type()) { - case QVariant::Invalid: return bpy::incref(Py_None); - case QVariant::Int: return SIPLong_FromLong(var.toInt()); - case QVariant::UInt: return PyLong_FromUnsignedLong(var.toUInt()); - case QVariant::Bool: return PyBool_FromLong(var.toBool()); - case QVariant::String: return bpy::incref(bpy::object(var.toString()).ptr()); - case QVariant::List: { - return bpy::incref(bpy::object(var.toList()).ptr()); - } break; - case QVariant::Map: { - return bpy::incref(bpy::object(var.toMap()).ptr()); - } break; - default: { - PyErr_Format(PyExc_TypeError, "type unsupported: %d", var.type()); - throw bpy::error_already_set(); - } break; - } - } -}; - - -struct QVariant_from_python_obj -{ - QVariant_from_python_obj() { - bpy::converter::registry::push_back(&convertible, &construct, bpy::type_id()); - } - - static void *convertible(PyObject *objPtr) { - if (!SIPBytes_Check(objPtr) && !PyUnicode_Check(objPtr) && !PyLong_Check(objPtr) && - !PyBool_Check(objPtr) && !PyList_Check(objPtr) && !PyDict_Check(objPtr) && - objPtr != Py_None) { - return nullptr; - } - return objPtr; - } - - template - static void constructVariant(const T &value, bpy::converter::rvalue_from_python_stage1_data *data) { - void* storage = ((bpy::converter::rvalue_from_python_storage*)data)->storage.bytes; - - new (storage) QVariant(value); - - data->convertible = storage; - } - - static void constructVariant(bpy::converter::rvalue_from_python_stage1_data *data) { - void* storage = ((bpy::converter::rvalue_from_python_storage*)data)->storage.bytes; - - new (storage) QVariant(); - - data->convertible = storage; - } - - static void construct(PyObject *objPtr, bpy::converter::rvalue_from_python_stage1_data *data) { - // PyBools will also return true for SIPLong_Check but not the other way around, so the order - // here is relevant - if (PyList_Check(objPtr)) { - constructVariant(bpy::extract(objPtr)(), data); - } else if (objPtr == Py_None) { - constructVariant(data); - } else if (PyDict_Check(objPtr)) { - constructVariant(bpy::extract(objPtr)(), data); - } else if (SIPBytes_Check(objPtr) || PyUnicode_Check(objPtr)) { - constructVariant(bpy::extract(objPtr)(), data); - } else if (PyBool_Check(objPtr)) { - constructVariant(bpy::extract(objPtr)(), data); - } else if (SIPLong_Check(objPtr)) { - //QVariant doesn't have long. It has int or long long. Given that on m/s, - //long is 32 bits for 32- and 64- bit code... - constructVariant(bpy::extract(objPtr)(), data); - } else { - PyErr_SetString(PyExc_TypeError, "type unsupported"); - throw bpy::error_already_set(); - } - } -}; - -template -struct QFlags_from_python_obj -{ - QFlags_from_python_obj() { - bpy::converter::registry::push_back( - &convertible, - &construct, - bpy::type_id>()); - } - - static void* convertible(PyObject *objPtr) { - return SIPLong_Check(objPtr) ? objPtr : nullptr; - } - - static void construct(PyObject *objPtr, bpy::converter::rvalue_from_python_stage1_data *data) { - int intVersion = (int)SIPLong_AsLong(objPtr); - T tVersion = (T)intVersion; - void *storage = ((bpy::converter::rvalue_from_python_storage> *)data)->storage.bytes; - new (storage) QFlags(tVersion); - - data->convertible = storage; - } -}; - - -struct IModRepositoryBridge_to_python -{ - static PyObject *convert(IModRepositoryBridge *bridge) - { - ModRepositoryBridgeWrapper wrapper(bridge); - - return bpy::incref(bpy::object(wrapper).ptr()); - } -}; - - - -template struct MetaData; - -template <> struct MetaData { static const char *className() { return "QObject"; } }; -template <> struct MetaData { static const char *className() { return "QObject"; } }; -template <> struct MetaData { static const char *className() { return "QObject"; } }; -template <> struct MetaData { static const char *className() { return "QWidget"; } }; -template <> struct MetaData { static const char *className() { return "QDateTime"; } }; -template <> struct MetaData { static const char *className() { return "QDir"; } }; -template <> struct MetaData { static const char *className() { return "QFileInfo"; } }; -template <> struct MetaData { static const char *className() { return "QIcon"; } }; -template <> struct MetaData { static const char *className() { return "QSize"; } }; -template <> struct MetaData { static const char *className() { return "QStringList"; } }; -template <> struct MetaData { static const char *className() { return "QUrl"; } }; -template <> struct MetaData { static const char *className() { return "QVariant"; } }; - - -template -PyObject *toPyQt(T *objPtr) -{ - if (objPtr == nullptr) { - qDebug("no input object"); - return bpy::incref(Py_None); - } - const sipTypeDef *type = sipAPIAccess::sipAPI()->api_find_type(MetaData::className()); - - if (type == nullptr) { - qDebug("failed to determine type: %s", MetaData::className()); - return bpy::incref(Py_None); - } - - PyObject *sipObj = sipAPIAccess::sipAPI()->api_convert_from_type(objPtr, type, 0); - if (sipObj == nullptr) { - qDebug("failed to convert"); - return bpy::incref(Py_None); - } - return bpy::incref(sipObj); -} - - -template -struct QClass_converters -{ - struct QClass_to_PyQt - { - template - static typename std::enable_if_t, T*> getSafeCopy(T *qClass) - { - return new T(*qClass); - } - - template - static typename std::enable_if_t, T*> getSafeCopy(T *qClass) - { - return qClass; - } - - static PyObject *convert(const T &object) { - const sipTypeDef *type = sipAPIAccess::sipAPI()->api_find_type(MetaData::className()); - if (type == nullptr) { - return bpy::incref(Py_None); - } - - PyObject *sipObj = sipAPIAccess::sipAPI()->api_convert_from_type((void*)getSafeCopy((T*)&object), type, 0); - if (sipObj == nullptr) { - return bpy::incref(Py_None); - } - - if (std::is_copy_constructible_v) - // Ensure Python deletes the C++ component - sipAPIAccess::sipAPI()->api_transfer_back(sipObj); - - return bpy::incref(sipObj); - } - - static PyObject *convert(T *object) { - if (object == nullptr) { - return bpy::incref(Py_None); - } - - const sipTypeDef *type = sipAPIAccess::sipAPI()->api_find_type(MetaData::className()); - if (type == nullptr) { - return bpy::incref(Py_None); - } - - PyObject *sipObj = sipAPIAccess::sipAPI()->api_convert_from_type(getSafeCopy(object), type, 0); - if (sipObj == nullptr) { - return bpy::incref(Py_None); - } - - if (std::is_copy_constructible_v) - // Ensure Python deletes the C++ component - sipAPIAccess::sipAPI()->api_transfer_back(sipObj); - - return bpy::incref(sipObj); - } - - static PyObject *convert(const T *object) { - return convert((T*)object); - } - }; - - static void *QClass_from_PyQt(PyObject *objPtr) - { - // This would transfer responsibility for deconstructing the object to C++, but Boost assumes l-value converters (such as this) don't do that - // Instead, this should be called within the wrappers for functions which return deletable pointers. - //sipAPI()->api_transfer_to(objPtr, Py_None); - if (PyObject_TypeCheck(objPtr, sipAPIAccess::sipAPI()->api_simplewrapper_type)) { - sipSimpleWrapper *wrapper; - wrapper = reinterpret_cast(objPtr); - return wrapper->data; - } else if (PyObject_TypeCheck(objPtr, sipAPIAccess::sipAPI()->api_wrapper_type)) { - sipWrapper *wrapper; - wrapper = reinterpret_cast(objPtr); - return wrapper->super.data; - } else { - if constexpr (std::is_same_v) - { - // QStringLists aren't wrapped by PyQt - regular Python string/unicode lists are used instead - bpy::extract> extractor(objPtr); - if (extractor.check()) - return new QStringList(extractor()); - } - PyErr_SetString(PyExc_TypeError, "type not wrapped"); - bpy::throw_error_already_set(); - } - return new void*; - } - - QClass_converters() - { - bpy::converter::registry::insert(&QClass_from_PyQt, bpy::type_id()); - bpy::to_python_converter(); - bpy::to_python_converter(); - bpy::to_python_converter(); - } -}; - - -template -struct QInterface_converters -{ - struct QInterface_to_PyQt - { - static PyObject *convert(const T &object) { - const sipTypeDef *type = sipAPIAccess::sipAPI()->api_find_type(MetaData::className()); - if (type == nullptr) { - return bpy::incref(Py_None); - } - - PyObject *sipObj = sipAPIAccess::sipAPI()->api_convert_from_type((void*)(&object), type, 0); - if (sipObj == nullptr) { - return bpy::incref(Py_None); - } - - return bpy::incref(sipObj); - } - - static PyObject *convert(T *object) { - if (object == nullptr) { - return bpy::incref(Py_None); - } - - const sipTypeDef *type = sipAPIAccess::sipAPI()->api_find_type(MetaData::className()); - if (type == nullptr) { - return bpy::incref(Py_None); - } - - PyObject *sipObj = sipAPIAccess::sipAPI()->api_convert_from_type(object, type, 0); - if (sipObj == nullptr) { - return bpy::incref(Py_None); - } - - return bpy::incref(sipObj); - } - - static PyObject *convert(const T *object) { - return convert((T*)object); - } - }; - - static void *QInterface_from_PyQt(PyObject *objPtr) - { - if (!PyObject_TypeCheck(objPtr, sipAPIAccess::sipAPI()->api_wrapper_type)) { - bpy::throw_error_already_set(); - } - - // This would transfer responsibility for deconstructing the object to C++, but Boost assumes l-value converters (such as this) don't do that - // Instead, this should be called within the wrappers for functions which return deletable pointers. - //sipAPI()->api_transfer_to(objPtr, Py_None); - - sipSimpleWrapper *wrapper = reinterpret_cast(objPtr); - return wrapper->data; - } - - QInterface_converters() - { - bpy::converter::registry::insert(&QInterface_from_PyQt, bpy::type_id()); - bpy::to_python_converter(); - bpy::to_python_converter(); - } -}; - - -int getArgCount(PyObject *object) { - int result = 0; - PyObject *funcCode = PyObject_GetAttrString(object, "__code__"); - if (funcCode) { - PyObject *argCount = PyObject_GetAttrString(funcCode, "co_argcount"); - if(argCount) { - result = SIPLong_AsLong(argCount); - Py_DECREF(argCount); - } - Py_DECREF(funcCode); - } - return result; -} - -template -struct Functor_converter; - - -template -struct Functor_converter -{ - - struct FunctorWrapper - { - FunctorWrapper(boost::python::object callable) : m_Callable(callable) { - } - - ~FunctorWrapper() { - GILock lock; - m_Callable = bpy::object(); - } - - RET operator()(const PARAMS &...params) { - GILock lock; - if constexpr (std::is_same_v) { - m_Callable(params...); - } - else { - return bpy::extract(m_Callable(params...)); - } - } - - boost::python::object m_Callable; - }; - - Functor_converter() - { - bpy::converter::registry::push_back(&convertible, &construct, bpy::type_id>()); - } - - static void *convertible(PyObject *object) - { - if (!PyCallable_Check(object) - || (getArgCount(object) != sizeof...(PARAMS))) { - return nullptr; - } - return object; - } - - static void construct(PyObject *object, bpy::converter::rvalue_from_python_stage1_data *data) - { - bpy::object callable(bpy::handle<>(bpy::borrowed(object))); - void *storage = ((bpy::converter::rvalue_from_python_storage>*)data)->storage.bytes; - new (storage) std::function(FunctorWrapper(callable)); - data->convertible = storage; - } -}; - - -/** - * @brief Call policy that automatically downcast shared pointer of type FromType - * to shared pointer of type ToType. - */ -template -struct DowncastConverter { - - bool convertible() const { return true; } - - inline PyObject* operator()(std::shared_ptr p) const { - if (p == nullptr) { - return bpy::detail::none(); - } - else { - auto downcast_p = std::dynamic_pointer_cast(p); - bpy::object p_value = downcast_p == nullptr ? bpy::object{ p } : bpy::object{ downcast_p }; - return bpy::incref(p_value.ptr()); - } - } - - inline PyTypeObject const* get_pytype() const { - return bpy::converter::registered_pytype::get_pytype(); - } - -}; - -template -struct DowncastReturn { - - template - struct apply_; - - template - struct apply_> { - static_assert(std::is_convertible_v, std::shared_ptr>); - using type = DowncastConverter; - }; - - template - using apply = apply_>; - -}; +#define Q_DELEGATE(Class, QClass, Name) \ + .def(Name, +[](Class* w) -> QClass* { return w; }, bpy::return_value_policy()) \ + .def("__getattr__", +[](Class* w, bpy::str str) -> bpy::object { \ + return bpy::object{ (QClass*)w }.attr(str); \ + }) BOOST_PYTHON_MODULE(mobase) @@ -580,23 +53,24 @@ BOOST_PYTHON_MODULE(mobase) PyEval_InitThreads(); bpy::import("PyQt5.QtCore"); + bpy::import("PyQt5.QtWidgets"); - bpy::to_python_converter(); - QVariant_from_python_obj(); + utils::register_qstring_converter(); + utils::register_qvariant_converter(); - bpy::to_python_converter(); - QString_from_python_str(); + utils::register_qclass_converter(); + utils::register_qclass_converter(); + utils::register_qclass_converter(); + utils::register_qclass_converter(); + utils::register_qclass_converter(); + utils::register_qclass_converter(); + utils::register_qclass_converter(); + utils::register_qclass_converter(); + utils::register_qclass_converter(); - //QClass_converters(); - QClass_converters(); - QClass_converters(); - QClass_converters(); - QClass_converters(); - QClass_converters(); - QClass_converters(); - QClass_converters(); - QClass_converters(); - QInterface_converters(); + // QFlags: + utils::register_qflags_converter(); + utils::register_qflags_converter(); // Pointers: bpy::register_ptr_to_python>(); @@ -635,18 +109,18 @@ BOOST_PYTHON_MODULE(mobase) bpy::register_variant>(); // Functions: - Functor_converter(); // converter for the onRefreshed-callback - Functor_converter(); - Functor_converter(); - Functor_converter(); // converter for the onModStateChanged-callback - Functor_converter(); - Functor_converter(); - Functor_converter const&)>(); - Functor_converter(QString const&)>(); + utils::register_functor_converter(); // converter for the onRefreshed-callback + utils::register_functor_converter(); + utils::register_functor_converter(); + utils::register_functor_converter(); // converter for the onModStateChanged-callback + utils::register_functor_converter(); + utils::register_functor_converter(); + utils::register_functor_converter const&)>(); + utils::register_functor_converter(QString const&)>(); - - bpy::def("toPyQt", &toPyQt); - bpy::def("toPyQt", &toPyQt); + // + // Class declarations: + // bpy::enum_("ReleaseType") .value("final", MOBase::VersionInfo::RELEASE_FINAL) @@ -719,11 +193,8 @@ BOOST_PYTHON_MODULE(mobase) // that __getattr__ is only called if the attribute is not found in the class by standard mean). bpy::class_, ISaveGameInfoWidgetWrapper*, boost::noncopyable>("ISaveGameInfoWidget", bpy::init>()) .def("setSave", bpy::pure_virtual(&ISaveGameInfoWidget::setSave)) - .def("__getattr__", +[](ISaveGameInfoWidget *w, bpy::str str) -> bpy::object { - // Create an object corresponding to the widget: - bpy::object obj{ (QWidget*)w }; - return obj.attr(str); - }) + + Q_DELEGATE(ISaveGameInfoWidget, QWidget, "_widget") ; bpy::class_("FileInfo", bpy::init<>()) @@ -849,7 +320,7 @@ BOOST_PYTHON_MODULE(mobase) // special python methods): .def("exists", static_cast(&IFileTree::exists), (bpy::arg("type") = IFileTree::FILE_OR_DIRECTORY)) .def("find", static_cast(IFileTree::*)(QString, IFileTree::FileTypes)>(&IFileTree::find), - bpy::arg("type") = IFileTree::FILE_OR_DIRECTORY, bpy::return_value_policy>(), "[optional]") + bpy::arg("type") = IFileTree::FILE_OR_DIRECTORY, bpy::return_value_policy>(), "[optional]") .def("pathTo", &IFileTree::pathTo, bpy::arg("sep") = "\\") // Kind-of-static operations: @@ -885,8 +356,8 @@ BOOST_PYTHON_MODULE(mobase) // Special methods: .def("__getitem__", static_cast(IFileTree::*)(std::size_t)>(&IFileTree::at), - bpy::return_value_policy>()) - .def("__iter__", bpy::range>>( + bpy::return_value_policy>()) + .def("__iter__", bpy::range>>( static_cast(&IFileTree::begin), static_cast(&IFileTree::end))) .def("__len__", &IFileTree::size) @@ -915,6 +386,8 @@ BOOST_PYTHON_MODULE(mobase) .def("onDescriptionAvailable", &ModRepositoryBridgeWrapper::onDescriptionAvailable) .def("onEndorsementToggled", &ModRepositoryBridgeWrapper::onEndorsementToggled) .def("onRequestFailed", &ModRepositoryBridgeWrapper::onRequestFailed) + + Q_DELEGATE(IModRepositoryBridge, QObject, "_object") ; bpy::class_("IModRepositoryBridge") @@ -952,6 +425,8 @@ BOOST_PYTHON_MODULE(mobase) .def("startDownloadURLs", bpy::pure_virtual(&IDownloadManager::startDownloadURLs)) .def("startDownloadNexusFile", bpy::pure_virtual(&IDownloadManager::startDownloadNexusFile)) .def("downloadPath", bpy::pure_virtual(&IDownloadManager::downloadPath)) + + Q_DELEGATE(IDownloadManager, QObject, "_object") ; utils::register_sequence_container>>(); @@ -1023,9 +498,6 @@ BOOST_PYTHON_MODULE(mobase) .def("__str__", &MOBase::GuessedValue::operator const QString&, bpy::return_value_policy()) ; - bpy::to_python_converter>(); - QFlags_from_python_obj(); - bpy::enum_("PluginState") .value("missing", IPluginList::STATE_MISSING) .value("inactive", IPluginList::STATE_INACTIVE) @@ -1046,9 +518,6 @@ BOOST_PYTHON_MODULE(mobase) .def("setLoadOrder", bpy::pure_virtual(&MOBase::IPluginList::setLoadOrder)) ; - bpy::to_python_converter>(); - QFlags_from_python_obj(); - bpy::enum_("ModState") .value("exists", IModList::STATE_EXISTS) .value("active", IModList::STATE_ACTIVE) @@ -1120,9 +589,6 @@ BOOST_PYTHON_MODULE(mobase) .value("preferDefaults", MOBase::IPluginGame::PREFER_DEFAULTS) ; - bpy::to_python_converter>(); - QFlags_from_python_obj(); - bpy::class_, boost::noncopyable>("IPluginGame") .def("gameName", bpy::pure_virtual(&MOBase::IPluginGame::gameName)) .def("initializeProfile", bpy::pure_virtual(&MOBase::IPluginGame::initializeProfile)) @@ -1252,6 +718,52 @@ BOOST_PYTHON_MODULE(mobase) registerGameFeaturesPythonConverters(); } +/** + * + */ +class PythonRunner : public IPythonRunner +{ + +public: + PythonRunner(const MOBase::IOrganizer* moInfo); + bool initPython(const QString& pythonDir); + QList instantiate(const QString& pluginName); + bool isPythonInstalled() const; + bool isPythonVersionSupported() const; + +private: + + void initPath(); + + /** + * @brief Append the underlying object of the given python object to the + * interface list if it is an instance (pointer) of the given type. + * + * @param obj The object to check. + * @param interfaces The list to append the object to. + * + */ + template + void appendIfInstance(bpy::object const& obj, QList& interfaces); + +private: + std::map m_PythonObjects; + const MOBase::IOrganizer* m_MOInfo; + wchar_t* m_PythonHome; +}; + +IPythonRunner* CreatePythonRunner(MOBase::IOrganizer* moInfo, const QString& pythonDir) +{ + s_Organizer = moInfo; + PythonRunner* result = new PythonRunner(moInfo); + if (result->initPython(pythonDir)) { + return result; + } + else { + delete result; + return nullptr; + } +} PythonRunner::PythonRunner(const MOBase::IOrganizer *moInfo) : m_MOInfo(moInfo)