From 9ad6c7f6ac4d2e2350104c2578341deceeb115cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mika=C3=ABl=20Capelle?= Date: Sat, 23 Apr 2022 21:39:10 +0200 Subject: [PATCH] Update pybind11 version. --- .clang-format | 20 + src/runner-pybind11/CMakeLists.txt | 2 +- src/runner-pybind11/converters-old.h | 2 +- src/runner-pybind11/converters.h | 8 - src/runner-pybind11/converters_qt.h | 296 -- src/runner-pybind11/converters_qt_sip.cpp | 62 - src/runner-pybind11/converters_qt_sip.h | 164 - src/runner-pybind11/converters_utils.h | 41 - src/runner-pybind11/error.cpp | 48 - src/runner-pybind11/error.h | 166 +- src/runner-pybind11/gamefeatureswrappers.cpp | 398 ++- .../pybind11_qt/details/pybind11_qt_enum.h | 59 + .../pybind11_qt/details/pybind11_qt_qmap.h | 71 + .../pybind11_qt/details/pybind11_qt_sip.cpp | 65 + .../pybind11_qt/details/pybind11_qt_sip.h | 188 ++ .../pybind11_qt/details/pybind11_qt_utils.cpp | 12 + .../pybind11_qt/details/pybind11_qt_utils.h | 56 + src/runner-pybind11/pybind11_qt/pybind11_qt.h | 10 + .../pybind11_qt/pybind11_qt_basic.cpp | 120 + .../pybind11_qt/pybind11_qt_basic.h | 38 + .../pybind11_qt/pybind11_qt_containers.h | 56 + .../pybind11_qt/pybind11_qt_enums.h | 34 + .../pybind11_qt/pybind11_qt_objects.h | 59 + .../pybind11_qt/pybind11_qt_qflags.h | 58 + src/runner-pybind11/pyfiletree.cpp | 483 +-- src/runner-pybind11/pythonrunner.cpp | 2798 +++++++++-------- src/runner-pybind11/pythonutils.cpp | 258 +- src/runner-pybind11/pythonutils.h | 47 +- src/runner-pybind11/pythonwrapperutilities.h | 197 +- .../wrappers/uibase_wrappers.h | 74 + 30 files changed, 3319 insertions(+), 2571 deletions(-) create mode 100644 .clang-format delete mode 100644 src/runner-pybind11/converters.h delete mode 100644 src/runner-pybind11/converters_qt.h delete mode 100644 src/runner-pybind11/converters_qt_sip.cpp delete mode 100644 src/runner-pybind11/converters_qt_sip.h delete mode 100644 src/runner-pybind11/converters_utils.h delete mode 100644 src/runner-pybind11/error.cpp create mode 100644 src/runner-pybind11/pybind11_qt/details/pybind11_qt_enum.h create mode 100644 src/runner-pybind11/pybind11_qt/details/pybind11_qt_qmap.h create mode 100644 src/runner-pybind11/pybind11_qt/details/pybind11_qt_sip.cpp create mode 100644 src/runner-pybind11/pybind11_qt/details/pybind11_qt_sip.h create mode 100644 src/runner-pybind11/pybind11_qt/details/pybind11_qt_utils.cpp create mode 100644 src/runner-pybind11/pybind11_qt/details/pybind11_qt_utils.h create mode 100644 src/runner-pybind11/pybind11_qt/pybind11_qt.h create mode 100644 src/runner-pybind11/pybind11_qt/pybind11_qt_basic.cpp create mode 100644 src/runner-pybind11/pybind11_qt/pybind11_qt_basic.h create mode 100644 src/runner-pybind11/pybind11_qt/pybind11_qt_containers.h create mode 100644 src/runner-pybind11/pybind11_qt/pybind11_qt_enums.h create mode 100644 src/runner-pybind11/pybind11_qt/pybind11_qt_objects.h create mode 100644 src/runner-pybind11/pybind11_qt/pybind11_qt_qflags.h create mode 100644 src/runner-pybind11/wrappers/uibase_wrappers.h diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..70c5760 --- /dev/null +++ b/.clang-format @@ -0,0 +1,20 @@ +--- +# We'll use defaults from the LLVM style, but with 4 columns indentation. +BasedOnStyle: LLVM +IndentWidth: 4 +--- +Language: Cpp +# Force pointers to the type for C++. +DerivePointerAlignment: false +PointerAlignment: Left +AlignConsecutiveAssignments: true +AllowShortFunctionsOnASingleLine: Inline +AllowShortIfStatementsOnASingleLine: Never +AllowShortLambdasOnASingleLine: Empty +AlwaysBreakTemplateDeclarations: Yes +AccessModifierOffset: -4 +AlignTrailingComments: true +SpacesBeforeTrailingComments: 2 +NamespaceIndentation: All +MaxEmptyLinesToKeep: 1 +BreakBeforeBraces: Stroustrup diff --git a/src/runner-pybind11/CMakeLists.txt b/src/runner-pybind11/CMakeLists.txt index 82fe39d..87e175c 100644 --- a/src/runner-pybind11/CMakeLists.txt +++ b/src/runner-pybind11/CMakeLists.txt @@ -23,7 +23,7 @@ target_link_directories(pythonrunner PRIVATE ${Boost_LIBRARY_DIRS}) target_compile_definitions(pythonrunner - PRIVATE PYTHONRUNNER_LIBRARY) + PRIVATE QT_NO_KEYWORDS PYTHONRUNNER_LIBRARY) mo2_install_target(pythonrunner INSTALLDIR bin/plugins/data) mo2_add_filter(NAME src/converters GROUPS diff --git a/src/runner-pybind11/converters-old.h b/src/runner-pybind11/converters-old.h index 3759864..6434590 100644 --- a/src/runner-pybind11/converters-old.h +++ b/src/runner-pybind11/converters-old.h @@ -426,7 +426,7 @@ namespace utils { } } catch (const boost::python::error_already_set&) { - throw pyexcept::PythonError(); + throw pyexcept::PythonError(""); } catch (...) { throw pyexcept::UnknownException(); diff --git a/src/runner-pybind11/converters.h b/src/runner-pybind11/converters.h deleted file mode 100644 index c2005a6..0000000 --- a/src/runner-pybind11/converters.h +++ /dev/null @@ -1,8 +0,0 @@ -#ifndef PYTHON_CONVERTERS_HPP -#define PYTHON_CONVERTERS_HPP - -#include -#include "converters_qt.h" -#include "converters_qt_sip.h" - -#endif diff --git a/src/runner-pybind11/converters_qt.h b/src/runner-pybind11/converters_qt.h deleted file mode 100644 index 9a50fae..0000000 --- a/src/runner-pybind11/converters_qt.h +++ /dev/null @@ -1,296 +0,0 @@ -#ifndef PYTHON_CONVERTERS_QT_HPP -#define PYTHON_CONVERTERS_QT_HPP - -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include - -#include "converters_utils.h" - -// for enum casters -namespace mo2::details { - template <> - struct enum_type_name { - constexpr static const char name[] = "QMessageBox.StandardButton"; - }; - - template <> - struct enum_type_name { - constexpr static const char name[] = "QMessageBox.Icon"; - }; -} - -namespace pybind11::detail { - - // helper class for QMap because QMap do not follow the standard std:: maps interface, - // for other containers, the pybind11 built-in xxx_caster works - // - // this code is basically a copy/paste from the pybind11 stl stuff with minor - // modifications - // - template - struct qmap_caster { - using key_conv = make_caster; - using value_conv = make_caster; - - bool load(handle src, bool convert) { - if (!isinstance(src)) { - return false; - } - auto d = reinterpret_borrow(src); - value.clear(); - for (auto it : d) { - key_conv kconv; - value_conv vconv; - if (!kconv.load(it.first.ptr(), convert) || !vconv.load(it.second.ptr(), convert)) { - return false; - } - value[cast_op(std::move(kconv))] = cast_op(std::move(vconv)); - } - return true; - } - - template - static handle cast(T &&src, return_value_policy policy, handle parent) { - dict d; - return_value_policy policy_key = policy; - return_value_policy policy_value = policy; - if (!std::is_lvalue_reference::value) { - policy_key = return_value_policy_override::policy(policy_key); - policy_value = return_value_policy_override::policy(policy_value); - } - for (auto it = src.begin(); it != src.end(); ++it) { - auto key = reinterpret_steal( - key_conv::cast(forward_like(it.key()), policy_key, parent)); - auto value = reinterpret_steal( - value_conv::cast(forward_like(it.value()), policy_value, parent)); - if (!key || !value) { - return handle(); - } - d[key] = value; - } - return d.release(); - } - - PYBIND11_TYPE_CASTER(Type, - const_name("Dict[") + key_conv::name + const_name(", ") + value_conv::name - + const_name("]")); - }; - - // QString - // - template <> - struct type_caster { - PYBIND11_TYPE_CASTER(QString, const_name("str")); - - /** - * Conversion part 1 (Python->C++): convert a PyObject into a QString - * instance or return false upon failure. The second argument - * indicates whether implicit conversions should be applied. - */ - inline bool load(handle src, bool) { - - PyObject *objPtr = src.ptr(); - - if (!PyBytes_Check(objPtr) && !PyUnicode_Check(objPtr)) { - return false; - } - - // Ensure the string uses 8-bit characters - PyObject *strPtr = PyUnicode_Check(objPtr) ? PyUnicode_AsUTF8String(objPtr) : objPtr; - - // Extract the character data from the python string - value = QString::fromUtf8(PyBytes_AsString(strPtr)); - - // Deallocate local copy if one was made - if (strPtr != objPtr) { - Py_DecRef(strPtr); - } - - return true; - } - - /** - * Conversion part 2 (C++ -> Python): convert an QString instance into - * a Python object. The second and third arguments are used to - * indicate the return value policy and parent object (for - * ``return_value_policy::reference_internal``) and are generally - * ignored by implicit casters. - */ - inline static handle cast(QString src, return_value_policy /* policy */, handle /* parent */) { - static_assert(sizeof(QChar) == 2); - return PyUnicode_FromKindAndData(PyUnicode_2BYTE_KIND, src.constData(), src.length()); - } - }; - - // QVariant - this needs to be defined BEFORE QVariantList - // - template <> - struct type_caster { - public: - PYBIND11_TYPE_CASTER(QVariant, const_name("MOVariant")); - - /** - * Conversion part 1 (Python->C++): convert a PyObject into a QVariant - * instance or return false upon failure. The second argument - * indicates whether implicit conversions should be applied. - */ - inline bool load(handle src, bool); - - /** - * Conversion part 2 (C++ -> Python): convert an QString instance into - * a Python object. The second and third arguments are used to - * indicate the return value policy and parent object (for - * ``return_value_policy::reference_internal``) and are generally - * ignored by implicit casters. - */ - inline static handle cast(QVariant var, return_value_policy policy, handle parent); - }; - - // QList - // - template - struct type_caster> : list_caster, T> {}; - - // QSet - // - template - struct type_caster> : set_caster, T> {}; - - // QMap - // - template - struct type_caster> : qmap_caster, K, V> {}; - - // QStringList - // - template <> - struct type_caster : list_caster {}; - - // QVariantList - // - template <> - struct type_caster : list_caster {}; - - // QVariantMap - // - template <> - struct type_caster : qmap_caster {}; - - bool type_caster::load(handle src, bool implicit) { - if (PyList_Check(src.ptr())) { - // we could check if all the elements can be converted to QString and store a QStringList - // in the QVariant but I am not sure that is really useful. - value = src.cast(); - return true; - } - else if (src == Py_None) { - value = QVariant(); - return true; - } - else if (PyDict_Check(src.ptr())) { - value = src.cast(); - return true; - } - else if (PyBytes_Check(src.ptr()) || PyUnicode_Check(src.ptr())) { - value = src.cast(); - return true; - } - // PyBool will also return true for PyLong_Check but not the other way around, so - // the order here is relevant. - else if (PyBool_Check(src.ptr())) { - value = src.cast(); - return true; - } - else if (PyLong_Check(src.ptr())) { - // 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... - value = src.cast(); - return true; - } - else { - return false; - } - } - - handle type_caster::cast(QVariant var, return_value_policy policy, handle parent) { - switch (var.type()) { - case QVariant::Invalid: return Py_None; - case QVariant::Int: return PyLong_FromLong(var.toInt()); - case QVariant::UInt: return PyLong_FromUnsignedLong(var.toUInt()); - case QVariant::Bool: return PyBool_FromLong(var.toBool()); - case QVariant::String: return type_caster::cast(var.toString(), policy, parent); - // We need to check for StringList here because these are not considered List - // since List is QList will StringList is QList: - case QVariant::StringList: return type_caster::cast(var.toStringList(), policy, parent); - case QVariant::List: return type_caster::cast(var.toList(), policy, parent); - case QVariant::Map: // return type_caster::cast(var.toList(), policy, parent); - default: { - PyErr_Format(PyExc_TypeError, "type unsupported: %d", var.type()); - throw pybind11::error_already_set(); - } - } - } - - // QMessageBox's enums - // - template <> - struct type_caster : - mo2::details::enum_type_caster {}; - template <> - struct type_caster : - mo2::details::enum_type_caster {}; - - // QFlags - // - template - struct type_caster> { - PYBIND11_TYPE_CASTER(QFlags, const_name("QFlags[") + make_caster::name + const_name("]")); - - /** - * Conversion part 1 (Python->C++): convert a PyObject into a QString - * instance or return false upon failure. The second argument - * indicates whether implicit conversions should be applied. - */ - bool load(handle src, bool implicit) { - PyObject* tmp = PyNumber_Long(src.ptr()); - - if (!tmp) { - return false; - } - - // we do an intermediate extraction to T but this actually - // can contains multiple values - T flag_value = static_cast(PyLong_AsLong(tmp)); - Py_DECREF(tmp); - - value = QFlags(flag_value); - - return !PyErr_Occurred(); - } - - /** - * Conversion part 2 (C++ -> Python): convert an QString instance into - * a Python object. The second and third arguments are used to - * indicate the return value policy and parent object (for - * ``return_value_policy::reference_internal``) and are generally - * ignored by implicit casters. - */ - static handle cast(QFlags const& src, return_value_policy /* policy */, handle /* parent */) { - return PyLong_FromLong(static_cast(src)); - } - - }; - - -} // namespace pybind11::detail - -#endif diff --git a/src/runner-pybind11/converters_qt_sip.cpp b/src/runner-pybind11/converters_qt_sip.cpp deleted file mode 100644 index 559c44e..0000000 --- a/src/runner-pybind11/converters_qt_sip.cpp +++ /dev/null @@ -1,62 +0,0 @@ -#include "converters_qt_sip.h" - -#include - -#include - -namespace py = pybind11; - -namespace mo2::details { - - const sipAPIDef* sipAPI() - { - QString exception; - static const sipAPIDef* sipApi = nullptr; - if (sipApi == nullptr) { - PyImport_ImportModule("PyQt6.sip"); - - auto errorObj = PyErr_Occurred(); - if (errorObj != NULL) { - PyObject* type, * value, * traceback; - PyErr_Fetch(&type, &value, &traceback); - PyErr_NormalizeException(&type, &value, &traceback); - if (traceback != NULL) { - py::handle h_type(type); - py::handle h_val(value); - py::handle h_tb(traceback); - py::object tb(py::module_::import("traceback")); - py::object fmt_exp(tb.attr("format_exception")); - py::object exp_list(fmt_exp(h_type, h_val, h_tb)); - py::object exp_str(py::str("\n").attr("join")(exp_list)); - exception = QString::fromStdString(exp_str.cast()); - } - PyErr_Restore(type, value, traceback); - throw MOBase::Exception(QString("Failed to load PyQt6: %1").arg(exception)); - } - - sipApi = (const sipAPIDef*)PyCapsule_Import("PyQt6.sip._C_API", 0); - if (sipApi == NULL) { - auto errorObj = PyErr_Occurred(); - if (errorObj != NULL) { - PyObject* type, * value, * traceback; - PyErr_Fetch(&type, &value, &traceback); - PyErr_NormalizeException(&type, &value, &traceback); - if (traceback != NULL) { - py::handle h_type(type); - py::handle h_val(value); - py::handle h_tb(traceback); - py::object tb(py::module_::import("traceback")); - py::object fmt_exp(tb.attr("format_exception")); - py::object exp_list(fmt_exp(h_type, h_val, h_tb)); - py::object exp_str(py::str("\n").attr("join")(exp_list)); - exception = QString::fromStdString(exp_str.cast()); - } - PyErr_Restore(type, value, traceback); - } - throw MOBase::Exception(QString("Failed to load SIP API: %1").arg(exception)); - } - } - return sipApi; - } - -} diff --git a/src/runner-pybind11/converters_qt_sip.h b/src/runner-pybind11/converters_qt_sip.h deleted file mode 100644 index 410dced..0000000 --- a/src/runner-pybind11/converters_qt_sip.h +++ /dev/null @@ -1,164 +0,0 @@ -#ifndef PYTHON_CONVERTERS_QT_SIP_HPP -#define PYTHON_CONVERTERS_QT_SIP_HPP - -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -namespace mo2::details { - - /** - * @brief Retrieve the SIP api. - * - * @return const sipAPIDef* - */ - const sipAPIDef* sipAPI(); - - template struct MetaData; - - template - struct MetaData>> : - MetaData> {}; - -#define METADATA(QClass) template <> struct MetaData { \ - constexpr static const char name[] = #QClass; } - - METADATA(QObject); - METADATA(QWidget); - METADATA(QMainWindow); - METADATA(QDateTime); - METADATA(QDir); - METADATA(QFileInfo); - METADATA(QIcon); - METADATA(QSize); - METADATA(QUrl); - METADATA(QPixmap); - -#undef METADATA - - // template class for most Qt types that have Python equivalent (QWidget, etc.) - // - template - struct qt_type_caster { - public: - PYBIND11_TYPE_CASTER(QtType, pybind11::detail::const_name(MetaData::name)); - - bool load(pybind11::handle src, bool) { - // this would transfer responsibility for deconstructing the object to C++, - // but pybind11 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); - // - void* data = nullptr; - if (PyObject_TypeCheck(src.ptr(), mo2::details::sipAPI()->api_simplewrapper_type)) { - data = reinterpret_cast(src.ptr())->data; - } - else if (PyObject_TypeCheck(src.ptr(), mo2::details::sipAPI()->api_wrapper_type)) { - data = reinterpret_cast(src.ptr())->super.data; - } - - if (data) { - if constexpr (std::is_pointer_v) { - value = reinterpret_cast(data); - } - else { - value = *reinterpret_cast(data); - } - return true; - } - else { - return false; - } - } - - static pybind11::handle cast( - QtType src, pybind11::return_value_policy /* policy */, pybind11::handle /* parent */) { - - const sipTypeDef* type = - mo2::details::sipAPI()->api_find_type(MetaData::name); - if (type == nullptr) { - return Py_None; - } - - PyObject* sipObj; - void* sipData; - - if constexpr (std::is_pointer_v) { - sipData = src; - } else if (std::is_copy_assignable_v) { - // we send to SIP a newly allocated object, and transfer the owernship to it - sipData = new QtType(src); - } - else { - sipData = &src; - } - - if constexpr (std::is_pointer_v) { - sipObj = mo2::details::sipAPI()->api_convert_from_type(sipData, type, 0); - } - else { - sipObj = mo2::details::sipAPI()->api_convert_from_type(sipData, type, 0); - } - - if (sipObj == nullptr) { - return Py_None; - } - - if constexpr (!std::is_pointer_v && std::is_copy_constructible_v) { - // ensure Python deletes the C++ component - mo2::details::sipAPI()->api_transfer_back(sipObj); - } - - return sipObj; - } - }; - -} - -namespace pybind11::detail { - - template <> - struct type_caster : mo2::details::qt_type_caster {}; - - template <> - struct type_caster : mo2::details::qt_type_caster {}; - - template <> - struct type_caster : mo2::details::qt_type_caster {}; - - template <> - struct type_caster : mo2::details::qt_type_caster {}; - - template <> - struct type_caster : mo2::details::qt_type_caster {}; - - template <> - struct type_caster : mo2::details::qt_type_caster {}; - template <> - struct type_caster : mo2::details::qt_type_caster {}; - - template <> - struct type_caster : mo2::details::qt_type_caster {}; - - template <> - struct type_caster : mo2::details::qt_type_caster {}; - - template <> - struct type_caster : mo2::details::qt_type_caster {}; - -} // namespace pybind11::detail - -#endif diff --git a/src/runner-pybind11/converters_utils.h b/src/runner-pybind11/converters_utils.h deleted file mode 100644 index 723893e..0000000 --- a/src/runner-pybind11/converters_utils.h +++ /dev/null @@ -1,41 +0,0 @@ -#ifndef PYTHON_CONVERTERS_UTILS_HPP -#define PYTHON_CONVERTERS_UTILS_HPP - -#include - -namespace mo2::details { - - template - struct enum_type_name; - - // helper caster for enum-like types - // - template - struct enum_type_caster { - static_assert(std::is_enum_v, - "enum_type_caster should only be used with enum types"); - - PYBIND11_TYPE_CASTER(E, pybind11::detail::const_name(enum_type_name::name)); - - bool load(pybind11::handle src, bool) { - PyObject* tmp = PyNumber_Long(src.ptr()); - - if (!tmp) { - return false; - } - - value = static_cast(PyLong_AsLong(tmp)); - Py_DECREF(tmp); - - return !PyErr_Occurred(); - } - - static pybind11::handle cast(E src, pybind11::return_value_policy, pybind11::handle) { - return PyLong_FromLongLong(static_cast(src)); - } - - }; - -} - -#endif diff --git a/src/runner-pybind11/error.cpp b/src/runner-pybind11/error.cpp deleted file mode 100644 index 2be1906..0000000 --- a/src/runner-pybind11/error.cpp +++ /dev/null @@ -1,48 +0,0 @@ -#ifndef Q_MOC_RUN -#include -#endif -#include -#include -#include "error.h" - -using namespace MOBase; -namespace bpy = boost::python; - -ErrWrapper & ErrWrapper::instance() -{ - static ErrWrapper err; - return err; -} - -void ErrWrapper::write(const char * message) -{ - buffer << message; - if (buffer.tellp() != 0 && buffer.str().back() == '\n') - { - // actually put the string in a variable so it doesn't get destroyed as soon as we get a pointer to its data - std::string string = buffer.str().substr(0, buffer.str().length() - 1); - qCritical().nospace().noquote() << string.c_str(); - buffer = std::stringstream(); - } - - if (recordingExceptionMessage) - { - lastException << message; - } -} - -void ErrWrapper::startRecordingExceptionMessage() -{ - recordingExceptionMessage = true; - lastException = std::stringstream(); -} - -void ErrWrapper::stopRecordingExceptionMessage() -{ - recordingExceptionMessage = false; -} - -QString ErrWrapper::getLastExceptionMessage() -{ - return QString::fromStdString(lastException.str()); -} diff --git a/src/runner-pybind11/error.h b/src/runner-pybind11/error.h index 89d0036..f73e4bd 100644 --- a/src/runner-pybind11/error.h +++ b/src/runner-pybind11/error.h @@ -4,120 +4,82 @@ #include #include +#include -#include #include -struct ErrWrapper -{ - static ErrWrapper& instance(); - - void write(const char* message); - - void startRecordingExceptionMessage(); - - void stopRecordingExceptionMessage(); - - QString getLastExceptionMessage(); - - std::stringstream buffer; - bool recordingExceptionMessage; - std::stringstream lastException; -}; - namespace pyexcept { - /** - * @brief Exception to throw when a python implementation does not implement - * a pure virtual function. - */ - class MissingImplementation : public MOBase::Exception { - public: - MissingImplementation(std::string const& className, std::string const& methodName) : - Exception(QString::fromStdString( - fmt::format("Python class implementing \"{}\" has no implementation of method \"{}\".", - className, methodName))) { } - - }; - - /** - * @brief Exception to throw when a python error occurs. - */ - class PythonError : public MOBase::Exception { - public: + /** + * @brief Exception to throw when a python implementation does not implement + * a pure virtual function. + */ + class MissingImplementation : public MOBase::Exception { + public: + MissingImplementation(std::string const& className, + std::string const& methodName) + : Exception(QString::fromStdString( + fmt::format("Python class implementing \"{}\" has no " + "implementation of method \"{}\".", + className, methodName))) + { + } + }; /** - * @brief Create a new PythonError, fetching the error message from python. If the message - * cannot be retrieved, `defaultErrorMessage()` is used instead. + * @brief Exception to throw when a python error occurs. */ - PythonError() : Exception(getPythonErrorMessage()) { } + class PythonError : public MOBase::Exception { + public: + /** + * @brief Create a new PythonError, fetching the error message from + * python. If the message cannot be retrieved, `defaultErrorMessage()` + * is used instead. + */ + PythonError(pybind11::error_already_set const& ex) + : Exception(ex.what()) + { + } + + /** + * @brief Create a new PythonError with the given message. + * + * @param message Message for the exception. + */ + PythonError(QString message) : Exception(message) {} + }; /** - * @brief Create a new PythonError with the given message. - * - * @param message Message for the exception. + * @brief Exception to throw when an unknown error occured. This is + * typically thrown from a catch(...) block. */ - PythonError(QString message) : Exception(message) { } + class UnknownException : public MOBase::Exception { + public: + /** + * @brief Create a new UnknownException with the default message. + * + * @see defaultErrorMessage + */ + UnknownException() : Exception(defaultErrorMessage()) {} - protected: + /** + * @brief Create a new UnknownException with the given message. + * + * @param message Message for the exception. + */ + UnknownException(QString message) : Exception(message) {} - /** - * - */ - static QString defaultErrorMessage() { - return QObject::tr("An unexpected C++ exception was thrown in python code."); - } + protected: + /** + * + */ + static QString defaultErrorMessage() + { + return QObject::tr( + "An unknown exception was thrown in python code."); + } + }; - /** - * - */ - static QString getPythonErrorMessage() { - if (PyErr_Occurred()) { - ErrWrapper& errWrapper = ErrWrapper::instance(); +} // namespace pyexcept - errWrapper.startRecordingExceptionMessage(); - PyErr_Print(); - errWrapper.stopRecordingExceptionMessage(); - - return errWrapper.getLastExceptionMessage(); - } - else { - return defaultErrorMessage(); - } - } - }; - - /** - * @brief Exception to throw when an unknown error occured. This is typically thrown - * from a catch(...) block. - */ - class UnknownException : public MOBase::Exception { - public: - - /** - * @brief Create a new UnknownException with the default message. - * - * @see defaultErrorMessage - */ - UnknownException() : Exception(defaultErrorMessage()) { } - - /** - * @brief Create a new UnknownException with the given message. - * - * @param message Message for the exception. - */ - UnknownException(QString message) : Exception(message) { } - - protected: - - /** - * - */ - static QString defaultErrorMessage() { - return QObject::tr("An unknown exception was thrown in python code."); - } - }; - -} - -#endif // ERROR_H +#endif // ERROR_H diff --git a/src/runner-pybind11/gamefeatureswrappers.cpp b/src/runner-pybind11/gamefeatureswrappers.cpp index cbfa5cc..ea8a673 100644 --- a/src/runner-pybind11/gamefeatureswrappers.cpp +++ b/src/runner-pybind11/gamefeatureswrappers.cpp @@ -3,139 +3,172 @@ #include #include +#include #include #include #include #include -#include "shared_ptr_converter.h" -#include "ifiletree.h" #include "pythonwrapperutilities.h" +#include "shared_ptr_converter.h" ///////////////////////////// /// BSAInvalidation Wrapper - -bool BSAInvalidationWrapper::isInvalidationBSA(const QString &bsaName) +bool BSAInvalidationWrapper::isInvalidationBSA(const QString& bsaName) { - return basicWrapperFunctionImplementation(this, "isInvalidationBSA", bsaName); + return basicWrapperFunctionImplementation(this, "isInvalidationBSA", + bsaName); } -void BSAInvalidationWrapper::deactivate(MOBase::IProfile *profile) +void BSAInvalidationWrapper::deactivate(MOBase::IProfile* profile) { - return basicWrapperFunctionImplementation(this, "deactivate", boost::python::ptr(profile)); + return basicWrapperFunctionImplementation( + this, "deactivate", boost::python::ptr(profile)); } -void BSAInvalidationWrapper::activate(MOBase::IProfile *profile) +void BSAInvalidationWrapper::activate(MOBase::IProfile* profile) { - return basicWrapperFunctionImplementation(this, "activate", boost::python::ptr(profile)); + return basicWrapperFunctionImplementation( + this, "activate", boost::python::ptr(profile)); } -bool BSAInvalidationWrapper::prepareProfile(MOBase::IProfile *profile) +bool BSAInvalidationWrapper::prepareProfile(MOBase::IProfile* profile) { - return basicWrapperFunctionImplementation(this, "prepareProfile", boost::python::ptr(profile)); + return basicWrapperFunctionImplementation( + this, "prepareProfile", boost::python::ptr(profile)); } /// end BSAInvalidation Wrapper ///////////////////////////// /// DataArchives Wrapper - QStringList DataArchivesWrapper::vanillaArchives() const { - return basicWrapperFunctionImplementation(this, "vanillaArchives"); + return basicWrapperFunctionImplementation(this, + "vanillaArchives"); } -QStringList DataArchivesWrapper::archives(const MOBase::IProfile *profile) const +QStringList DataArchivesWrapper::archives(const MOBase::IProfile* profile) const { - return basicWrapperFunctionImplementation(this, "archives", boost::python::ptr(profile)); + return basicWrapperFunctionImplementation( + this, "archives", boost::python::ptr(profile)); } -void DataArchivesWrapper::addArchive(MOBase::IProfile *profile, int index, const QString &archiveName) +void DataArchivesWrapper::addArchive(MOBase::IProfile* profile, int index, + const QString& archiveName) { - return basicWrapperFunctionImplementation(this, "addArchive", boost::python::ptr(profile), index, archiveName); + return basicWrapperFunctionImplementation( + this, "addArchive", boost::python::ptr(profile), index, archiveName); } -void DataArchivesWrapper::removeArchive(MOBase::IProfile *profile, const QString &archiveName) +void DataArchivesWrapper::removeArchive(MOBase::IProfile* profile, + const QString& archiveName) { - return basicWrapperFunctionImplementation(this, "removeArchive", boost::python::ptr(profile), archiveName); + return basicWrapperFunctionImplementation( + this, "removeArchive", boost::python::ptr(profile), archiveName); } /// end DataArchives Wrapper ///////////////////////////// /// GamePlugins Wrapper - -void GamePluginsWrapper::writePluginLists(const MOBase::IPluginList * pluginList) +void GamePluginsWrapper::writePluginLists(const MOBase::IPluginList* pluginList) { - return basicWrapperFunctionImplementation(this, "writePluginLists", boost::python::ptr(pluginList)); + return basicWrapperFunctionImplementation( + this, "writePluginLists", boost::python::ptr(pluginList)); } -void GamePluginsWrapper::readPluginLists(MOBase::IPluginList * pluginList) +void GamePluginsWrapper::readPluginLists(MOBase::IPluginList* pluginList) { - return basicWrapperFunctionImplementation(this, "readPluginLists", boost::python::ptr(pluginList)); + return basicWrapperFunctionImplementation( + this, "readPluginLists", boost::python::ptr(pluginList)); } QStringList GamePluginsWrapper::getLoadOrder() { - return basicWrapperFunctionImplementation(this, "getLoadOrder"); + return basicWrapperFunctionImplementation(this, + "getLoadOrder"); } bool GamePluginsWrapper::lightPluginsAreSupported() { - return basicWrapperFunctionImplementation(this, "lightPluginsAreSupported"); + return basicWrapperFunctionImplementation(this, + "lightPluginsAreSupported"); } /// end GamePlugins Wrapper ///////////////////////////// /// LocalSavegames Wrapper - -MappingType LocalSavegamesWrapper::mappings(const QDir & profileSaveDir) const +MappingType LocalSavegamesWrapper::mappings(const QDir& profileSaveDir) const { - return basicWrapperFunctionImplementation(this, "mappings", profileSaveDir); + return basicWrapperFunctionImplementation(this, "mappings", + profileSaveDir); } -bool LocalSavegamesWrapper::prepareProfile(MOBase::IProfile * profile) +bool LocalSavegamesWrapper::prepareProfile(MOBase::IProfile* profile) { - return basicWrapperFunctionImplementation(this, "prepareProfile", boost::python::ptr(profile)); + return basicWrapperFunctionImplementation( + this, "prepareProfile", boost::python::ptr(profile)); } /// end LocalSavegames Wrapper ///////////////////////////// /// ModDataChecker Wrapper -ModDataChecker::CheckReturn ModDataCheckerWrapper::dataLooksValid(std::shared_ptr fileTree) const { - return basicWrapperFunctionImplementation(this, "dataLooksValid", fileTree); +ModDataChecker::CheckReturn ModDataCheckerWrapper::dataLooksValid( + std::shared_ptr fileTree) const +{ + return basicWrapperFunctionImplementation( + this, "dataLooksValid", fileTree); } -std::shared_ptr ModDataCheckerWrapper::fix(std::shared_ptr fileTree) const { - return utils::clean_shared_ptr( - basicWrapperFunctionImplementationWithDefault>( - this, [](auto&&... args) { return nullptr; }, "fix", fileTree)); +std::shared_ptr +ModDataCheckerWrapper::fix(std::shared_ptr fileTree) const +{ + return utils::clean_shared_ptr( + basicWrapperFunctionImplementationWithDefault< + std::shared_ptr>( + this, + [](auto&&... args) { + return nullptr; + }, + "fix", fileTree)); } /// end ModDataChecker Wrapper ///////////////////////////// /// ModDataContent Wrapper -std::vector ModDataContentWrapper::getAllContents() const { - return basicWrapperFunctionImplementation>(this, "getAllContents"); +std::vector +ModDataContentWrapper::getAllContents() const +{ + return basicWrapperFunctionImplementation>( + this, "getAllContents"); } -std::vector ModDataContentWrapper::getContentsFor(std::shared_ptr fileTree) const { - return basicWrapperFunctionImplementation>(this, "getContentsFor", fileTree); +std::vector ModDataContentWrapper::getContentsFor( + std::shared_ptr fileTree) const +{ + return basicWrapperFunctionImplementation>( + this, "getContentsFor", fileTree); } /// end ModDataContent Wrapper ///////////////////////////// /// SaveGameInfo Wrapper -SaveGameInfoWrapper::MissingAssets SaveGameInfoWrapper::getMissingAssets(MOBase::ISaveGame const& save) const +SaveGameInfoWrapper::MissingAssets +SaveGameInfoWrapper::getMissingAssets(MOBase::ISaveGame const& save) const { - return basicWrapperFunctionImplementation(this, "getMissingAssets", boost::ref(save)); + return basicWrapperFunctionImplementation< + SaveGameInfoWrapper::MissingAssets>(this, "getMissingAssets", + boost::ref(save)); } -MOBase::ISaveGameInfoWidget* SaveGameInfoWrapper::getSaveGameWidget(QWidget* parent) const +MOBase::ISaveGameInfoWidget* +SaveGameInfoWrapper::getSaveGameWidget(QWidget* parent) const { - return basicWrapperFunctionImplementation(this, m_SaveGameWidget, "getSaveGameWidget", parent); + return basicWrapperFunctionImplementation( + this, m_SaveGameWidget, "getSaveGameWidget", parent); } /// end SaveGameInfo Wrapper @@ -144,201 +177,240 @@ MOBase::ISaveGameInfoWidget* SaveGameInfoWrapper::getSaveGameWidget(QWidget* par QString ScriptExtenderWrapper::BinaryName() const { - return basicWrapperFunctionImplementation(this, "BinaryName"); + return basicWrapperFunctionImplementation(this, "BinaryName"); } QString ScriptExtenderWrapper::PluginPath() const { - return basicWrapperFunctionImplementation(this, "PluginPath"); + return basicWrapperFunctionImplementation(this, "PluginPath"); } QString ScriptExtenderWrapper::loaderName() const { - return basicWrapperFunctionImplementation(this, "loaderName"); + return basicWrapperFunctionImplementation(this, "loaderName"); } QString ScriptExtenderWrapper::loaderPath() const { - return basicWrapperFunctionImplementation(this, "loaderPath"); + return basicWrapperFunctionImplementation(this, "loaderPath"); } QString ScriptExtenderWrapper::savegameExtension() const { - return basicWrapperFunctionImplementation(this, "savegameExtension"); + return basicWrapperFunctionImplementation(this, + "savegameExtension"); } bool ScriptExtenderWrapper::isInstalled() const { - return basicWrapperFunctionImplementation(this, "isInstalled"); + return basicWrapperFunctionImplementation(this, "isInstalled"); } QString ScriptExtenderWrapper::getExtenderVersion() const { - return basicWrapperFunctionImplementation(this, "getExtenderVersion"); + return basicWrapperFunctionImplementation(this, + "getExtenderVersion"); } WORD ScriptExtenderWrapper::getArch() const { - return basicWrapperFunctionImplementation(this, "getArch"); + return basicWrapperFunctionImplementation(this, "getArch"); } /// end ScriptExtender Wrapper ///////////////////////////// /// UnmanagedMods Wrapper - QStringList UnmanagedModsWrapper::mods(bool onlyOfficial) const { - return basicWrapperFunctionImplementation(this, "mods", onlyOfficial); + return basicWrapperFunctionImplementation(this, "mods", + onlyOfficial); } -QString UnmanagedModsWrapper::displayName(const QString & modName) const +QString UnmanagedModsWrapper::displayName(const QString& modName) const { - return basicWrapperFunctionImplementation(this, "displayName", modName); + return basicWrapperFunctionImplementation(this, "displayName", + modName); } -QFileInfo UnmanagedModsWrapper::referenceFile(const QString & modName) const +QFileInfo UnmanagedModsWrapper::referenceFile(const QString& modName) const { - return basicWrapperFunctionImplementation(this, "referenceFile", modName); + return basicWrapperFunctionImplementation(this, "referenceFile", + modName); } -QStringList UnmanagedModsWrapper::secondaryFiles(const QString & modName) const +QStringList UnmanagedModsWrapper::secondaryFiles(const QString& modName) const { - return basicWrapperFunctionImplementation(this, "secondaryFiles", modName); + return basicWrapperFunctionImplementation( + this, "secondaryFiles", modName); } /// end UnmanagedMods Wrapper ///////////////////////////// - game_features_map_from_python::game_features_map_from_python() { - boost::python::converter::registry::push_back(&convertible, &construct, boost::python::type_id>()); + boost::python::converter::registry::push_back( + &convertible, &construct, + boost::python::type_id>()); } -void * game_features_map_from_python::convertible(PyObject * objPtr) +void* game_features_map_from_python::convertible(PyObject* objPtr) { - return PyDict_Check(objPtr) ? objPtr : nullptr; + return PyDict_Check(objPtr) ? objPtr : nullptr; } -template -void insertGameFeature(std::map& map, const boost::python::object& pyObject) +template +void insertGameFeature(std::map& map, + const boost::python::object& pyObject) { - map[std::type_index(typeid(T))] = boost::python::extract(pyObject)(); + map[std::type_index(typeid(T))] = boost::python::extract(pyObject)(); } -void game_features_map_from_python::construct(PyObject * objPtr, boost::python::converter::rvalue_from_python_stage1_data * data) +void game_features_map_from_python::construct( + PyObject* objPtr, + boost::python::converter::rvalue_from_python_stage1_data* data) { - void *storage = ((boost::python::converter::rvalue_from_python_storage>*)data)->storage.bytes; - std::map *result = new (storage) std::map(); - boost::python::dict source(boost::python::handle<>(boost::python::borrowed(objPtr))); - boost::python::list keys = source.keys(); - int len = boost::python::len(keys); - for (int i = 0; i < len; ++i) - { - boost::python::object pyKey = keys[i]; - boost::python::object pyValue = source[pyKey]; + void* storage = ((boost::python::converter::rvalue_from_python_storage< + std::map>*)data) + ->storage.bytes; + std::map* result = + new (storage) std::map(); + boost::python::dict source( + boost::python::handle<>(boost::python::borrowed(objPtr))); + boost::python::list keys = source.keys(); + int len = boost::python::len(keys); + for (int i = 0; i < len; ++i) { + boost::python::object pyKey = keys[i]; + boost::python::object pyValue = source[pyKey]; - boost::mp11::mp_for_each< - // Must user pointers because mp_for_each construct object: - boost::mp11::mp_transform - >([&](auto* pt) { - using T = std::remove_pointer_t; - boost::python::extract extract(pyValue); - if (extract.check()) { - (*result)[std::type_index(typeid(T))] = extract(); - } - }); - } + boost::mp11::mp_for_each< + // Must user pointers because mp_for_each construct object: + boost::mp11::mp_transform>( + [&](auto* pt) { + using T = std::remove_pointer_t; + boost::python::extract extract(pyValue); + if (extract.check()) { + (*result)[std::type_index(typeid(T))] = extract(); + } + }); + } - data->convertible = storage; + data->convertible = storage; } void registerGameFeaturesPythonConverters() { - namespace bpy = boost::python; + namespace bpy = boost::python; - game_features_map_from_python(); + game_features_map_from_python(); - // Features require defs for all methods as Python can access C++ features - bpy::class_("BSAInvalidation") - .def("isInvalidationBSA", bpy::pure_virtual(&BSAInvalidation::isInvalidationBSA), bpy::arg("name")) - .def("deactivate", bpy::pure_virtual(&BSAInvalidation::deactivate), bpy::arg("profile")) - .def("activate", bpy::pure_virtual(&BSAInvalidation::activate), bpy::arg("profile")) - ; + // Features require defs for all methods as Python can access C++ features + bpy::class_("BSAInvalidation") + .def("isInvalidationBSA", + bpy::pure_virtual(&BSAInvalidation::isInvalidationBSA), + bpy::arg("name")) + .def("deactivate", bpy::pure_virtual(&BSAInvalidation::deactivate), + bpy::arg("profile")) + .def("activate", bpy::pure_virtual(&BSAInvalidation::activate), + bpy::arg("profile")); - bpy::class_("DataArchives") - .def("vanillaArchives", bpy::pure_virtual(&DataArchives::vanillaArchives)) - .def("archives", bpy::pure_virtual(&DataArchives::archives), bpy::arg("profile")) - .def("addArchive", bpy::pure_virtual(&DataArchives::addArchive), (bpy::arg("profile"), "index", "name")) - .def("removeArchive", bpy::pure_virtual(&DataArchives::removeArchive), (bpy::arg("profile"), "name")) - ; + bpy::class_("DataArchives") + .def("vanillaArchives", + bpy::pure_virtual(&DataArchives::vanillaArchives)) + .def("archives", bpy::pure_virtual(&DataArchives::archives), + bpy::arg("profile")) + .def("addArchive", bpy::pure_virtual(&DataArchives::addArchive), + (bpy::arg("profile"), "index", "name")) + .def("removeArchive", bpy::pure_virtual(&DataArchives::removeArchive), + (bpy::arg("profile"), "name")); - bpy::class_("GamePlugins") - .def("writePluginLists", bpy::pure_virtual(&GamePlugins::writePluginLists), bpy::arg("plugin_list")) - .def("readPluginLists", bpy::pure_virtual(&GamePlugins::readPluginLists), bpy::arg("plugin_list")) - .def("getLoadOrder", bpy::pure_virtual(&GamePlugins::getLoadOrder)) - .def("lightPluginsAreSupported", bpy::pure_virtual(&GamePlugins::lightPluginsAreSupported)) - ; + bpy::class_("GamePlugins") + .def("writePluginLists", + bpy::pure_virtual(&GamePlugins::writePluginLists), + bpy::arg("plugin_list")) + .def("readPluginLists", + bpy::pure_virtual(&GamePlugins::readPluginLists), + bpy::arg("plugin_list")) + .def("getLoadOrder", bpy::pure_virtual(&GamePlugins::getLoadOrder)) + .def("lightPluginsAreSupported", + bpy::pure_virtual(&GamePlugins::lightPluginsAreSupported)); - bpy::class_("LocalSavegames") - .def("mappings", bpy::pure_virtual(&LocalSavegames::mappings), bpy::arg("profile_save_dir")) - .def("prepareProfile", bpy::pure_virtual(&LocalSavegames::prepareProfile), bpy::arg("profile")) - ; + bpy::class_("LocalSavegames") + .def("mappings", bpy::pure_virtual(&LocalSavegames::mappings), + bpy::arg("profile_save_dir")) + .def("prepareProfile", + bpy::pure_virtual(&LocalSavegames::prepareProfile), + bpy::arg("profile")); - auto modDataCheckerClass = bpy::class_("ModDataChecker"); - { - bpy::scope scope = modDataCheckerClass; + auto modDataCheckerClass = + bpy::class_( + "ModDataChecker"); + { + bpy::scope scope = modDataCheckerClass; - bpy::enum_("CheckReturn") - .value("INVALID", ModDataChecker::CheckReturn::INVALID) - .value("FIXABLE", ModDataChecker::CheckReturn::FIXABLE) - .value("VALID", ModDataChecker::CheckReturn::VALID) - .export_values() - ; + bpy::enum_("CheckReturn") + .value("INVALID", ModDataChecker::CheckReturn::INVALID) + .value("FIXABLE", ModDataChecker::CheckReturn::FIXABLE) + .value("VALID", ModDataChecker::CheckReturn::VALID) + .export_values(); - modDataCheckerClass - .def("dataLooksValid", bpy::pure_virtual(&ModDataChecker::dataLooksValid), bpy::arg("filetree")) - .def("fix", bpy::pure_virtual(&ModDataChecker::fix), bpy::arg("filetree")) - ; - } + modDataCheckerClass + .def("dataLooksValid", + bpy::pure_virtual(&ModDataChecker::dataLooksValid), + bpy::arg("filetree")) + .def("fix", bpy::pure_virtual(&ModDataChecker::fix), + bpy::arg("filetree")); + } - { - bpy::scope scope = bpy::class_("ModDataContent") - .def("getAllContents", bpy::pure_virtual(&ModDataContent::getAllContents)) - .def("getContentsFor", bpy::pure_virtual(&ModDataContent::getContentsFor), bpy::arg("filetree")) - ; + { + bpy::scope scope = + bpy::class_( + "ModDataContent") + .def("getAllContents", + bpy::pure_virtual(&ModDataContent::getAllContents)) + .def("getContentsFor", + bpy::pure_virtual(&ModDataContent::getContentsFor), + bpy::arg("filetree")); - bpy::class_("Content", - bpy::init>((bpy::arg("id"), "name", "icon", bpy::arg("filter_only") = false))) - .add_property("id", &ModDataContent::Content::id) - .add_property("name", &ModDataContent::Content::name) - .add_property("icon", &ModDataContent::Content::icon) - .def("isOnlyForFilter", &ModDataContent::Content::isOnlyForFilter) - ; + bpy::class_( + "Content", bpy::init>( + (bpy::arg("id"), "name", "icon", + bpy::arg("filter_only") = false))) + .add_property("id", &ModDataContent::Content::id) + .add_property("name", &ModDataContent::Content::name) + .add_property("icon", &ModDataContent::Content::icon) + .def("isOnlyForFilter", &ModDataContent::Content::isOnlyForFilter); + } - } + bpy::class_("SaveGameInfo") + .def("getMissingAssets", + bpy::pure_virtual(&SaveGameInfo::getMissingAssets), + bpy::arg("save")) + .def("getSaveGameWidget", + bpy::pure_virtual(&SaveGameInfo::getSaveGameWidget), + bpy::return_value_policy(), + bpy::arg("parent"), "[optional]"); - bpy::class_("SaveGameInfo") - .def("getMissingAssets", bpy::pure_virtual(&SaveGameInfo::getMissingAssets), bpy::arg("save")) - .def("getSaveGameWidget", bpy::pure_virtual(&SaveGameInfo::getSaveGameWidget), bpy::return_value_policy(), - bpy::arg("parent"), "[optional]") - ; + bpy::class_("ScriptExtender") + .def("BinaryName", bpy::pure_virtual(&ScriptExtender::BinaryName)) + .def("PluginPath", bpy::pure_virtual(&ScriptExtender::PluginPath)) + .def("loaderName", bpy::pure_virtual(&ScriptExtender::loaderName)) + .def("loaderPath", bpy::pure_virtual(&ScriptExtender::loaderPath)) + .def("savegameExtension", + bpy::pure_virtual(&ScriptExtender::savegameExtension)) + .def("isInstalled", bpy::pure_virtual(&ScriptExtender::isInstalled)) + .def("getExtenderVersion", + bpy::pure_virtual(&ScriptExtender::getExtenderVersion)) + .def("getArch", bpy::pure_virtual(&ScriptExtender::getArch)); - bpy::class_("ScriptExtender") - .def("BinaryName", bpy::pure_virtual(&ScriptExtender::BinaryName)) - .def("PluginPath", bpy::pure_virtual(&ScriptExtender::PluginPath)) - .def("loaderName", bpy::pure_virtual(&ScriptExtender::loaderName)) - .def("loaderPath", bpy::pure_virtual(&ScriptExtender::loaderPath)) - .def("savegameExtension", bpy::pure_virtual(&ScriptExtender::savegameExtension)) - .def("isInstalled", bpy::pure_virtual(&ScriptExtender::isInstalled)) - .def("getExtenderVersion", bpy::pure_virtual(&ScriptExtender::getExtenderVersion)) - .def("getArch", bpy::pure_virtual(&ScriptExtender::getArch)) - ; - - bpy::class_("UnmanagedMods") - .def("mods", bpy::pure_virtual(&UnmanagedMods::mods), bpy::arg("official_only")) - .def("displayName", bpy::pure_virtual(&UnmanagedMods::displayName), bpy::arg("mod_name")) - .def("referenceFile", bpy::pure_virtual(&UnmanagedMods::referenceFile), bpy::arg("mod_name")) - .def("secondaryFiles", bpy::pure_virtual(&UnmanagedMods::secondaryFiles), bpy::arg("mod_name")) - ; + bpy::class_("UnmanagedMods") + .def("mods", bpy::pure_virtual(&UnmanagedMods::mods), + bpy::arg("official_only")) + .def("displayName", bpy::pure_virtual(&UnmanagedMods::displayName), + bpy::arg("mod_name")) + .def("referenceFile", bpy::pure_virtual(&UnmanagedMods::referenceFile), + bpy::arg("mod_name")) + .def("secondaryFiles", + bpy::pure_virtual(&UnmanagedMods::secondaryFiles), + bpy::arg("mod_name")); } diff --git a/src/runner-pybind11/pybind11_qt/details/pybind11_qt_enum.h b/src/runner-pybind11/pybind11_qt/details/pybind11_qt_enum.h new file mode 100644 index 0000000..57969f7 --- /dev/null +++ b/src/runner-pybind11/pybind11_qt/details/pybind11_qt_enum.h @@ -0,0 +1,59 @@ +#ifndef PYTHON_PYBIND11_QT_DETAILS_ENUM_HPP +#define PYTHON_PYBIND11_QT_DETAILS_ENUM_HPP + +#include + +#include + +#include "pybind11_qt_utils.h" + +namespace pybind11::detail::qt { + + // EnumData, with static members (const char[]) + // - package: name of the Python package containing the enum (e.g., + // PyQt6.QtCore) + // - name: full path to the enum, e.g. Qt.QGlobalColor + // + template + struct EnumData; + + // template class for most Qt types that have Python equivalent (QWidget, + // etc.) + // + template + struct qt_enum_caster { + + public: + PYBIND11_TYPE_CASTER(Enum, EnumData::package + const_name(".") + + EnumData::name); + + bool load(pybind11::handle src, bool) + { + if (PyLong_Check(src.ptr())) { + value = static_cast(PyLong_AsLong(src.ptr())); + return true; + } + + auto pyenum = get_attr_rec(EnumData::package.text, + EnumData::name.text); + + if (isinstance(src, pyenum)) { + value = static_cast(src.attr("value").cast()); + return true; + } + + return false; + } + + static pybind11::handle cast(Enum src, + pybind11::return_value_policy /* policy */, + pybind11::handle /* parent */) + { + auto pyenum = get_attr_rec(EnumData::package.text, + EnumData::name.text); + return pyenum(static_cast(src)); + } + }; +} // namespace pybind11::detail::qt + +#endif diff --git a/src/runner-pybind11/pybind11_qt/details/pybind11_qt_qmap.h b/src/runner-pybind11/pybind11_qt/details/pybind11_qt_qmap.h new file mode 100644 index 0000000..71d8fe5 --- /dev/null +++ b/src/runner-pybind11/pybind11_qt/details/pybind11_qt_qmap.h @@ -0,0 +1,71 @@ +#ifndef PYTHON_PYBIND11_QT_DETAILS_QMAP_HPP +#define PYTHON_PYBIND11_QT_DETAILS_QMAP_HPP + +#include + +namespace pybind11::detail::qt { + + // helper class for QMap because QMap do not follow the standard std:: maps + // interface, for other containers, the pybind11 built-in xxx_caster works + // + // this code is basically a copy/paste from the pybind11 stl stuff with + // minor modifications + // + template + struct qmap_caster { + using key_conv = make_caster; + using value_conv = make_caster; + + bool load(handle src, bool convert) + { + if (!isinstance(src)) { + return false; + } + auto d = reinterpret_borrow(src); + value.clear(); + for (auto it : d) { + key_conv kconv; + value_conv vconv; + if (!kconv.load(it.first.ptr(), convert) || + !vconv.load(it.second.ptr(), convert)) { + return false; + } + value[cast_op(std::move(kconv))] = + cast_op(std::move(vconv)); + } + return true; + } + + template + static handle cast(T&& src, return_value_policy policy, handle parent) + { + dict d; + return_value_policy policy_key = policy; + return_value_policy policy_value = policy; + if (!std::is_lvalue_reference::value) { + policy_key = + return_value_policy_override::policy(policy_key); + policy_value = + return_value_policy_override::policy(policy_value); + } + for (auto it = src.begin(); it != src.end(); ++it) { + auto key = reinterpret_steal(key_conv::cast( + forward_like(it.key()), policy_key, parent)); + auto value = reinterpret_steal(value_conv::cast( + forward_like(it.value()), policy_value, parent)); + if (!key || !value) { + return handle(); + } + d[key] = value; + } + return d.release(); + } + + PYBIND11_TYPE_CASTER(Type, const_name("Dict[") + key_conv::name + + const_name(", ") + value_conv::name + + const_name("]")); + }; + +} // namespace pybind11::qt::detail + +#endif diff --git a/src/runner-pybind11/pybind11_qt/details/pybind11_qt_sip.cpp b/src/runner-pybind11/pybind11_qt/details/pybind11_qt_sip.cpp new file mode 100644 index 0000000..d79109d --- /dev/null +++ b/src/runner-pybind11/pybind11_qt/details/pybind11_qt_sip.cpp @@ -0,0 +1,65 @@ +#include "pybind11_qt_sip.h" + +#include + +#include + +namespace py = pybind11; + +namespace pybind11::detail::qt { + + const sipAPIDef* sipAPI() + { + std::string exception; + static const sipAPIDef* sipApi = nullptr; + if (sipApi == nullptr) { + PyImport_ImportModule("PyQt6.sip"); + + auto errorObj = PyErr_Occurred(); + if (errorObj != NULL) { + PyObject *type, *value, *traceback; + PyErr_Fetch(&type, &value, &traceback); + PyErr_NormalizeException(&type, &value, &traceback); + if (traceback != NULL) { + py::handle h_type(type); + py::handle h_val(value); + py::handle h_tb(traceback); + py::object tb(py::module_::import("traceback")); + py::object fmt_exp(tb.attr("format_exception")); + py::object exp_list(fmt_exp(h_type, h_val, h_tb)); + py::object exp_str(py::str("\n").attr("join")(exp_list)); + exception = exp_str.cast(); + } + PyErr_Restore(type, value, traceback); + throw std::runtime_error{"Failed to load SIP API: " + + exception}; + } + + sipApi = (const sipAPIDef*)PyCapsule_Import("PyQt6.sip._C_API", 0); + if (sipApi == NULL) { + auto errorObj = PyErr_Occurred(); + if (errorObj != NULL) { + PyObject *type, *value, *traceback; + PyErr_Fetch(&type, &value, &traceback); + PyErr_NormalizeException(&type, &value, &traceback); + if (traceback != NULL) { + py::handle h_type(type); + py::handle h_val(value); + py::handle h_tb(traceback); + py::object tb(py::module_::import("traceback")); + py::object fmt_exp(tb.attr("format_exception")); + py::object exp_list(fmt_exp(h_type, h_val, h_tb)); + py::object exp_str( + py::str("\n").attr("join")(exp_list)); + exception = exp_str.cast(); + } + PyErr_Restore(type, value, traceback); + } + throw std::runtime_error{"Failed to load SIP API: " + + exception}; + } + } + return sipApi; + } + +} // namespace pybind11::detail::qt diff --git a/src/runner-pybind11/pybind11_qt/details/pybind11_qt_sip.h b/src/runner-pybind11/pybind11_qt/details/pybind11_qt_sip.h new file mode 100644 index 0000000..9790c3a --- /dev/null +++ b/src/runner-pybind11/pybind11_qt/details/pybind11_qt_sip.h @@ -0,0 +1,188 @@ +#ifndef PYTHON_PYBIND11_QT_DETAILS_SIP_HPP +#define PYTHON_PYBIND11_QT_DETAILS_SIP_HPP + +#include + +#include +#include + +#include +#include + +namespace pybind11::detail::qt { + + /** + * @brief Retrieve the SIP api. + * + * @return const sipAPIDef* + */ + const sipAPIDef* sipAPI(); + + template + struct MetaData; + + template + struct MetaData>> + : MetaData> { + }; + + // template class for most Qt types that have Python equivalent (QWidget, + // etc.) + // + template + struct qt_type_caster { + + static constexpr bool is_copy = std::is_copy_constructible_v; + + std::conditional_t value; + + public: + static constexpr auto name = MetaData::python_name; + + operator QClass*() + { + if constexpr (is_copy) { + return &value; + } + else { + return value; + } + } + operator QClass&() + { + if constexpr (is_copy) { + return value; + } + else { + return *value; + } + } + operator QClass&&() && + { + if constexpr (is_copy) { + return std::move(value); + } + else { + return *value; + } + } + + template + using cast_op_type = + std::conditional_t, + movable_cast_op_type, QClass*>; + + bool load(pybind11::handle src, bool) + { + auto src_type = type::of(src); + auto src_type_type = type::of(src_type); + + scoped_ostream_redirect stream( + std::cout, // std::ostream& + module_::import("sys").attr("stdout") // Python output + ); + + std::cout + << "trying to load '" + + src_type.attr("__str__")().cast() + " [" + + + src_type_type.attr("__str__")().cast() + + "]' in '" + std::string(MetaData::class_name) + + "' (" + std::string(MetaData::python_name.text) + + ")\n"; + + // special check for none for pointer classes + if constexpr (std::is_pointer_v) { + if (src.is_none()) { + value = nullptr; + return true; + } + } + + // this would transfer responsibility for deconstructing the + // object to C++, but pybind11 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); + // + void* data = nullptr; + if (PyObject_TypeCheck(src.ptr(), + qt::sipAPI()->api_simplewrapper_type)) { + data = reinterpret_cast(src.ptr())->data; + } + else if (PyObject_TypeCheck(src.ptr(), + qt::sipAPI()->api_wrapper_type)) { + data = reinterpret_cast(src.ptr())->super.data; + } + + if (data) { + if constexpr (std::is_pointer_v) { + value = reinterpret_cast(data); + } + else { + value = *reinterpret_cast(data); + } + return true; + } + else { + return false; + } + } + + static pybind11::handle cast(QClass* src, + pybind11::return_value_policy policy, + pybind11::handle /* parent */) + { + + if (!src) + return ::pybind11::none().release(); + + const sipTypeDef* type = + qt::sipAPI()->api_find_type(MetaData::class_name); + if (type == nullptr) { + return Py_None; + } + + PyObject* sipObj; + void* sipData; + + if constexpr (!is_copy) { + sipData = src; + } + else if (std::is_copy_assignable_v) { + // we send to SIP a newly allocated object, and transfer the + // owernship to it + sipData = new QClass( + policy == ::pybind11::return_value_policy::take_ownership + ? std::move(src) + : src); + } + else { + sipData = &src; + } + + if constexpr (std::is_pointer_v) { + sipObj = qt::sipAPI()->api_convert_from_type(sipData, type, 0); + } + else { + sipObj = qt::sipAPI()->api_convert_from_type(sipData, type, 0); + } + + if (sipObj == nullptr) { + return Py_None; + } + + if constexpr (!std::is_pointer_v && + std::is_copy_constructible_v) { + // ensure Python deletes the C++ component + qt::sipAPI()->api_transfer_back(sipObj); + } + + return sipObj; + } + }; + +} // namespace pybind11::detail::qt + +#endif diff --git a/src/runner-pybind11/pybind11_qt/details/pybind11_qt_utils.cpp b/src/runner-pybind11/pybind11_qt/details/pybind11_qt_utils.cpp new file mode 100644 index 0000000..c8cafeb --- /dev/null +++ b/src/runner-pybind11/pybind11_qt/details/pybind11_qt_utils.cpp @@ -0,0 +1,12 @@ +#include "pybind11_qt_utils.h" + +namespace pybind11::detail::qt { + + pybind11::object get_attr_rec(std::string_view package, + std::string_view path) + { + + return module_::import("operator") + .attr("attrgetter")(path.data())(module_::import(package.data())); + } +} // namespace pybind11::detail::qt diff --git a/src/runner-pybind11/pybind11_qt/details/pybind11_qt_utils.h b/src/runner-pybind11/pybind11_qt/details/pybind11_qt_utils.h new file mode 100644 index 0000000..71171d9 --- /dev/null +++ b/src/runner-pybind11/pybind11_qt/details/pybind11_qt_utils.h @@ -0,0 +1,56 @@ +#ifndef PYTHON_PYBIND11_QT_DETAILS_UTILS_HPP +#define PYTHON_PYBIND11_QT_DETAILS_UTILS_HPP + +#include + +#include + +namespace pybind11::detail::qt { + + /** + * @brief Convert a XXX::YYY compile time string to a XXX.YYY compile time + * string. Only one :: is allowed. + * + */ + template + constexpr descr qt_name_cpp2py(const char (&name)[N]) + { + descr res; + for (std::size_t i = 0, j = 0; i < N - 2; ++i) { + + res.text[i] = name[j]; + + if (res.text[i] == ':') { + res.text[i] = '.'; + j += 2; + } + else { + ++j; + } + } + return res; + } + + /** + * @brief Retrieve the class from the given package at the given path + * + * @param package Name of the module. + * @param path Path to the class/object in the module. + * + * @return the object at the given path in the given module + */ + pybind11::object get_attr_rec(std::string_view package, + std::string_view path); + + /** + * @brief Conditional add_pointer if T is non-copyable. + * + */ + template + using ptr_if_non_copy_t = + std::conditional_t, QClass, + std::add_pointer_t>; + +} // namespace pybind11::detail::qt + +#endif diff --git a/src/runner-pybind11/pybind11_qt/pybind11_qt.h b/src/runner-pybind11/pybind11_qt/pybind11_qt.h new file mode 100644 index 0000000..f5cd828 --- /dev/null +++ b/src/runner-pybind11/pybind11_qt/pybind11_qt.h @@ -0,0 +1,10 @@ +#ifndef PYTHON_PYBIND11_QT_HPP +#define PYTHON_PYBIND11_QT_HPP + +#include "pybind11_qt_basic.h" +#include "pybind11_qt_containers.h" +#include "pybind11_qt_enums.h" +#include "pybind11_qt_objects.h" +#include "pybind11_qt_qflags.h" + +#endif diff --git a/src/runner-pybind11/pybind11_qt/pybind11_qt_basic.cpp b/src/runner-pybind11/pybind11_qt/pybind11_qt_basic.cpp new file mode 100644 index 0000000..54323ef --- /dev/null +++ b/src/runner-pybind11/pybind11_qt/pybind11_qt_basic.cpp @@ -0,0 +1,120 @@ +#include "pybind11_qt_basic.h" + +namespace pybind11::detail { + + /** + * Conversion part 1 (Python->C++): convert a PyObject into a QString + * instance or return false upon failure. The second argument + * indicates whether implicit conversions should be applied. + */ + bool type_caster::load(handle src, bool) + { + + PyObject* objPtr = src.ptr(); + + if (!PyBytes_Check(objPtr) && !PyUnicode_Check(objPtr)) { + return false; + } + + // Ensure the string uses 8-bit characters + PyObject* strPtr = + PyUnicode_Check(objPtr) ? PyUnicode_AsUTF8String(objPtr) : objPtr; + + // Extract the character data from the python string + value = QString::fromUtf8(PyBytes_AsString(strPtr)); + + // Deallocate local copy if one was made + if (strPtr != objPtr) { + Py_DecRef(strPtr); + } + + return true; + } + + /** + * Conversion part 2 (C++ -> Python): convert an QString instance into + * a Python object. The second and third arguments are used to + * indicate the return value policy and parent object (for + * ``return_value_policy::reference_internal``) and are generally + * ignored by implicit casters. + */ + handle type_caster::cast(QString src, + return_value_policy /* policy */, + handle /* parent */) + { + static_assert(sizeof(QChar) == 2); + return PyUnicode_FromKindAndData(PyUnicode_2BYTE_KIND, src.constData(), + src.length()); + } + + bool type_caster::load(handle src, bool implicit) + { + if (PyList_Check(src.ptr())) { + // we could check if all the elements can be converted to QString + // and store a QStringList in the QVariant but I am not sure that is + // really useful. + value = src.cast(); + return true; + } + else if (src == Py_None) { + value = QVariant(); + return true; + } + else if (PyDict_Check(src.ptr())) { + value = src.cast(); + return true; + } + else if (PyBytes_Check(src.ptr()) || PyUnicode_Check(src.ptr())) { + value = src.cast(); + return true; + } + // PyBool will also return true for PyLong_Check but not the other way + // around, so the order here is relevant. + else if (PyBool_Check(src.ptr())) { + value = src.cast(); + return true; + } + else if (PyLong_Check(src.ptr())) { + // 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... + value = src.cast(); + return true; + } + else { + return false; + } + } + + handle type_caster::cast(QVariant var, return_value_policy policy, + handle parent) + { + switch (var.type()) { + case QVariant::Invalid: + return Py_None; + case QVariant::Int: + return PyLong_FromLong(var.toInt()); + case QVariant::UInt: + return PyLong_FromUnsignedLong(var.toUInt()); + case QVariant::Bool: + return PyBool_FromLong(var.toBool()); + case QVariant::String: + return type_caster::cast(var.toString(), policy, parent); + // We need to check for StringList here because these are not considered + // List since List is QList will StringList is QList: + case QVariant::StringList: + return type_caster::cast(var.toStringList(), policy, + parent); + case QVariant::List: + return type_caster::cast(var.toList(), policy, + parent); + case QVariant::Map: // return + // type_caster::cast(var.toList(), + // policy, parent); + default: { + PyErr_Format(PyExc_TypeError, "type unsupported: %d", var.type()); + throw pybind11::error_already_set(); + } + } + } + +} // namespace pybind11::detail diff --git a/src/runner-pybind11/pybind11_qt/pybind11_qt_basic.h b/src/runner-pybind11/pybind11_qt/pybind11_qt_basic.h new file mode 100644 index 0000000..9576bd7 --- /dev/null +++ b/src/runner-pybind11/pybind11_qt/pybind11_qt_basic.h @@ -0,0 +1,38 @@ +#ifndef PYTHON_PYBIND11_QT_BASIC_HPP +#define PYTHON_PYBIND11_QT_BASIC_HPP + +#include +#include + +#include + +namespace pybind11::detail { + + // QString + // + template <> + struct type_caster { + PYBIND11_TYPE_CASTER(QString, const_name("str")); + + bool load(handle src, bool); + + static handle cast(QString src, return_value_policy policy, + handle parent); + }; + + // QVariant - this needs to be defined BEFORE QVariantList + // + template <> + struct type_caster { + public: + PYBIND11_TYPE_CASTER(QVariant, const_name("MOVariant")); + + bool load(handle src, bool); + + static handle cast(QVariant var, return_value_policy policy, + handle parent); + }; + +} // namespace pybind11::detail + +#endif diff --git a/src/runner-pybind11/pybind11_qt/pybind11_qt_containers.h b/src/runner-pybind11/pybind11_qt/pybind11_qt_containers.h new file mode 100644 index 0000000..a950c37 --- /dev/null +++ b/src/runner-pybind11/pybind11_qt/pybind11_qt_containers.h @@ -0,0 +1,56 @@ +#ifndef PYTHON_PYBIND11_QT_CONTAINERS_HPP +#define PYTHON_PYBIND11_QT_CONTAINERS_HPP + +#include +#include +#include + +#include +#include + +// this needs to be included here to get proper QVariantList and QVariantMap +#include "details/pybind11_qt_qmap.h" +#include "pybind11_qt_basic.h" + +namespace pybind11::detail { + + // QList + // + template + struct type_caster> : list_caster, T> { + }; + + // QSet + // + // template + // struct type_caster> : set_caster, T> { + // }; + + // QMap + // + // template + // struct type_caster> : qt::qmap_caster, K, V> { + // }; + + // QStringList + // + template <> + struct type_caster : list_caster { + }; + + // QVariantList + // + // template <> + // struct type_caster : list_caster { + // }; + + // QVariantMap + // + // template <> + // struct type_caster + // : qt::qmap_caster { + // }; + +} // namespace pybind11::detail + +#endif diff --git a/src/runner-pybind11/pybind11_qt/pybind11_qt_enums.h b/src/runner-pybind11/pybind11_qt/pybind11_qt_enums.h new file mode 100644 index 0000000..7048fb9 --- /dev/null +++ b/src/runner-pybind11/pybind11_qt/pybind11_qt_enums.h @@ -0,0 +1,34 @@ +#ifndef PYTHON_PYBIND11_QT_ENUMS_HPP +#define PYTHON_PYBIND11_QT_ENUMS_HPP + +#include +#include + +#include "details/pybind11_qt_enum.h" +#include "details/pybind11_qt_utils.h" + +#define PYQT_ENUM(QPackage, QEnum) \ + namespace qt { \ + template <> \ + struct EnumData { \ + constexpr static const auto package = \ + const_name("PyQt6.") + const_name(#QPackage); \ + constexpr static const auto name = qt_name_cpp2py(#QEnum); \ + }; \ + } \ + template <> \ + struct type_caster : qt::qt_enum_caster { \ + } + +namespace pybind11::detail { + + PYQT_ENUM(QtCore, Qt::GlobalColor); + static_assert(std::string_view("PyQt6.QtCore.Qt.GlobalColor") == + std::string_view(type_caster::name.text)); + PYQT_ENUM(QtWidgets, QMessageBox::Icon); + +} // namespace pybind11::detail + +#undef PYQT_ENUM + +#endif diff --git a/src/runner-pybind11/pybind11_qt/pybind11_qt_objects.h b/src/runner-pybind11/pybind11_qt/pybind11_qt_objects.h new file mode 100644 index 0000000..a1c019a --- /dev/null +++ b/src/runner-pybind11/pybind11_qt/pybind11_qt_objects.h @@ -0,0 +1,59 @@ +#ifndef PYTHON_PYBIND11_QT_OBJECTS_HPP +#define PYTHON_PYBIND11_QT_OBJECTS_HPP + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "details/pybind11_qt_sip.h" +#include "details/pybind11_qt_utils.h" + +#define PYQT_CLASS(QModule, QClass) \ + namespace qt { \ + template <> \ + struct MetaData { \ + constexpr static const auto class_name = #QClass; \ + constexpr static const auto python_name = \ + const_name("PyQt6.") + const_name(#QModule) + \ + const_name(".") + const_name(#QClass); \ + }; \ + } \ + template <> \ + struct type_caster : qt::qt_type_caster { \ + }; \ + template <> \ + struct type_caster : qt::qt_type_caster { \ + } + +namespace pybind11::detail { + + // add declarations below to create bindings - the first argument is simply + // the name of the PyQt6 package containing the class, and is only used for + // the python signature + + PYQT_CLASS(QtCore, QDateTime); + PYQT_CLASS(QtCore, QDir); + PYQT_CLASS(QtCore, QFileInfo); + PYQT_CLASS(QtCore, QObject); + PYQT_CLASS(QtCore, QSize); + PYQT_CLASS(QtCore, QUrl); + + PYQT_CLASS(QtGui, QIcon); + PYQT_CLASS(QtGui, QPixmap); + + PYQT_CLASS(QtWidgets, QMainWindow); + PYQT_CLASS(QtWidgets, QWidget); + +} // namespace pybind11::detail + +#undef METADATA + +#endif diff --git a/src/runner-pybind11/pybind11_qt/pybind11_qt_qflags.h b/src/runner-pybind11/pybind11_qt/pybind11_qt_qflags.h new file mode 100644 index 0000000..0d73754 --- /dev/null +++ b/src/runner-pybind11/pybind11_qt/pybind11_qt_qflags.h @@ -0,0 +1,58 @@ +#ifndef PYTHON_PYBIND11_QT_QFLAGS_HPP +#define PYTHON_PYBIND11_QT_QFLAGS_HPP + +#include + +#include + +namespace pybind11::detail { + + // QFlags + // + template + struct type_caster> { + PYBIND11_TYPE_CASTER(QFlags, const_name("QFlags[") + + make_caster::name + + const_name("]")); + + /** + * Conversion part 1 (Python->C++): convert a PyObject into a QString + * instance or return false upon failure. The second argument + * indicates whether implicit conversions should be applied. + */ + bool load(handle src, bool implicit) + { + PyObject* tmp = PyNumber_Long(src.ptr()); + + if (!tmp) { + return false; + } + + // we do an intermediate extraction to T but this actually + // can contains multiple values + T flag_value = static_cast(PyLong_AsLong(tmp)); + Py_DECREF(tmp); + + value = QFlags(flag_value); + + return !PyErr_Occurred(); + } + + /** + * Conversion part 2 (C++ -> Python): convert an QString instance into + * a Python object. The second and third arguments are used to + * indicate the return value policy and parent object (for + * ``return_value_policy::reference_internal``) and are generally + * ignored by implicit casters. + */ + static handle cast(QFlags const& src, + return_value_policy /* policy */, + handle /* parent */) + { + return PyLong_FromLong(static_cast(src)); + } + }; + +} // namespace pybind11::details + +#endif diff --git a/src/runner-pybind11/pyfiletree.cpp b/src/runner-pybind11/pyfiletree.cpp index ee46ceb..4bac405 100644 --- a/src/runner-pybind11/pyfiletree.cpp +++ b/src/runner-pybind11/pyfiletree.cpp @@ -10,239 +10,344 @@ #include #include -#include "converters_qt.h" +#include "pybind11_qt/pybind11_qt.h" namespace py = pybind11; using namespace MOBase; namespace mo2::details { - // filetree implementation for testing purpose - // - class PyFileTree : public IFileTree { - public: + // filetree implementation for testing purpose + // + class PyFileTree : public IFileTree { + public: + using callback_t = std::function; - using callback_t = std::function; + PyFileTree(std::shared_ptr parent, QString name, + callback_t callback) + : FileTreeEntry(parent, name), IFileTree(), m_Callback(callback) + { + } - PyFileTree(std::shared_ptr parent, QString name, callback_t callback) : - FileTreeEntry(parent, name), IFileTree(), m_Callback(callback){ } + std::shared_ptr addFile(QString name, bool) override + { + if (m_Callback && !m_Callback(name, false)) { + throw UnsupportedOperationException( + "File rejected by callback."); + } + return IFileTree::addFile(name); + } - std::shared_ptr addFile(QString name, bool) override { - if (m_Callback && !m_Callback(name, false)) { - throw UnsupportedOperationException("File rejected by callback."); - } - return IFileTree::addFile(name); - } + std::shared_ptr addDirectory(QString name) override + { + if (m_Callback && !m_Callback(name, true)) { + throw UnsupportedOperationException( + "Directory rejected by callback."); + } + return IFileTree::addDirectory(name); + } - std::shared_ptr addDirectory(QString name) override { - if (m_Callback && !m_Callback(name, true)) { - throw UnsupportedOperationException("Directory rejected by callback."); - } - return IFileTree::addDirectory(name); - } + protected: + std::shared_ptr + makeDirectory(std::shared_ptr parent, + QString name) const override + { + return std::make_shared(parent, name, m_Callback); + } - protected: + bool doPopulate( + std::shared_ptr parent, + std::vector>& entries) const override + { + return true; + } + std::shared_ptr doClone() const override + { + return std::make_shared(nullptr, name(), m_Callback); + } - std::shared_ptr makeDirectory(std::shared_ptr parent, QString name) const override { - return std::make_shared(parent, name, m_Callback); - } + private: + callback_t m_Callback; + }; - bool doPopulate(std::shared_ptr parent, std::vector>& entries) const override { return true; } - std::shared_ptr doClone() const override { return std::make_shared(nullptr, name(), m_Callback); } - - private: - callback_t m_Callback; - }; - -} +} // namespace mo2::details #pragma optimize("", off) namespace pybind11 { - const void *polymorphic_type_hook::get(const FileTreeEntry *src, const std::type_info *&type) { - if (auto p = dynamic_cast(src)) { - type = &typeid(IFileTree); - return p; - } - return src; - } -} // namespace pybind11 + const void* + polymorphic_type_hook::get(const FileTreeEntry* src, + const std::type_info*& type) + { + if (auto p = dynamic_cast(src)) { + type = &typeid(IFileTree); + return p; + } + return src; + } +} // namespace pybind11 namespace mo2::python { - void add_ifiletree_bindings(pybind11::module_ &m) { + void add_ifiletree_bindings(pybind11::module_& m) + { - // FileTreeEntry Scope: - auto fileTreeEntryClass = py::class_>(m, "FileTreeEntry"); + // FileTreeEntry Scope: + auto fileTreeEntryClass = + py::class_>( + m, "FileTreeEntry"); - // we do not use the enum directly, we will mostly bind the FileTypes (with an S) - py::enum_(fileTreeEntryClass, "FileType", py::arithmetic{}); - py::class_(fileTreeEntryClass, "FileTypes") - .def_property_readonly_static("FILE", [](py::object) { return FileTreeEntry::FILE; }) - .def_property_readonly_static("DIRECTORY", [](py::object) { return FileTreeEntry::DIRECTORY; }) - .def_property_readonly_static("FILE_OR_DIRECTORY", [](py::object) { return FileTreeEntry::FILE_OR_DIRECTORY; }) + // we do not use the enum directly, we will mostly bind the FileTypes + // (with an S) + py::enum_(fileTreeEntryClass, "FileType", + py::arithmetic{}); + py::class_(fileTreeEntryClass, "FileTypes") + .def_property_readonly_static("FILE", + [](py::object) { + return FileTreeEntry::FILE; + }) + .def_property_readonly_static("DIRECTORY", + [](py::object) { + return FileTreeEntry::DIRECTORY; + }) + .def_property_readonly_static( + "FILE_OR_DIRECTORY", + [](py::object) { + return FileTreeEntry::FILE_OR_DIRECTORY; + }) - .def(py::self == py::self) - .def(py::self != py::self) - .def(py::self | py::self) - ; - py::implicitly_convertible(); + .def(py::self == py::self) + .def(py::self != py::self) + .def(py::self | py::self); + py::implicitly_convertible(); - fileTreeEntryClass - .def_property_readonly_static("FILE", [](py::object) { return FileTreeEntry::FILE; }) - .def_property_readonly_static("DIRECTORY", [](py::object) { return FileTreeEntry::DIRECTORY; }) - .def_property_readonly_static("FILE_OR_DIRECTORY", [](py::object) { return FileTreeEntry::FILE_OR_DIRECTORY; }); + fileTreeEntryClass + .def_property_readonly_static("FILE", + [](py::object) { + return FileTreeEntry::FILE; + }) + .def_property_readonly_static("DIRECTORY", + [](py::object) { + return FileTreeEntry::DIRECTORY; + }) + .def_property_readonly_static("FILE_OR_DIRECTORY", [](py::object) { + return FileTreeEntry::FILE_OR_DIRECTORY; + }); - fileTreeEntryClass + fileTreeEntryClass - .def("isFile", &FileTreeEntry::isFile) - .def("isDir", &FileTreeEntry::isDir) - // Forcing the conversion to FileTypeS to avoid having to expose FileType in python: - .def("fileType", &FileTreeEntry::fileType) - // This should probably not be exposed in python since we provide automatic downcast: - // .def("getTree", static_cast(FileTreeEntry::*)()>(&FileTreeEntry::astree)) - .def("name", &FileTreeEntry::name) - .def("suffix", &FileTreeEntry::suffix) - .def("hasSuffix", [](FileTreeEntry* entry, QStringList suffixes) { return entry->hasSuffix(suffixes); }, py::arg("suffixes")) - .def("hasSuffix", [](FileTreeEntry* entry, QString suffix) { return entry->hasSuffix(suffix); }, py::arg("suffix")) - .def("parent", py::overload_cast<>(&FileTreeEntry::parent), "[optional]") - .def("path", &FileTreeEntry::path, py::arg("sep") = "\\") - .def("pathFrom", &FileTreeEntry::pathFrom, py::arg("tree"), py::arg("sep") = "\\") + .def("isFile", &FileTreeEntry::isFile) + .def("isDir", &FileTreeEntry::isDir) + // Forcing the conversion to FileTypeS to avoid having to expose + // FileType in python: + .def("fileType", &FileTreeEntry::fileType) + // This should probably not be exposed in python since we provide + // automatic downcast: .def("getTree", + // static_cast(FileTreeEntry::*)()>(&FileTreeEntry::astree)) + .def("name", &FileTreeEntry::name) + .def("suffix", &FileTreeEntry::suffix) + .def( + "hasSuffix", + [](FileTreeEntry* entry, QStringList suffixes) { + return entry->hasSuffix(suffixes); + }, + py::arg("suffixes")) + .def( + "hasSuffix", + [](FileTreeEntry* entry, QString suffix) { + return entry->hasSuffix(suffix); + }, + py::arg("suffix")) + .def("parent", py::overload_cast<>(&FileTreeEntry::parent), + "[optional]") + .def("path", &FileTreeEntry::path, py::arg("sep") = "\\") + .def("pathFrom", &FileTreeEntry::pathFrom, py::arg("tree"), + py::arg("sep") = "\\") - // Mutable operation: - .def("detach", &FileTreeEntry::detach) - .def("moveTo", &FileTreeEntry::moveTo, py::arg("tree")) + // Mutable operation: + .def("detach", &FileTreeEntry::detach) + .def("moveTo", &FileTreeEntry::moveTo, py::arg("tree")) - // Special methods: - .def("__eq__", [](const FileTreeEntry* entry, QString other) { - return entry->compare(other) == 0; - }) - .def("__eq__", [](const FileTreeEntry* entry, std::shared_ptr other) { - return entry == other.get(); - }) + // Special methods: + .def("__eq__", + [](const FileTreeEntry* entry, QString other) { + return entry->compare(other) == 0; + }) + .def("__eq__", + [](const FileTreeEntry* entry, + std::shared_ptr other) { + return entry == other.get(); + }) - // Special methods for debug: - .def("__repr__", [](const FileTreeEntry* entry) { return "FileTreeEntry(\"" + entry->name() + "\")"; }) - ; + // Special methods for debug: + .def("__repr__", [](const FileTreeEntry* entry) { + return "FileTreeEntry(\"" + entry->name() + "\")"; + }); - // IFileTree scope: - auto iFileTreeClass = py::class_>( - m, "IFileTree", py::multiple_inheritance()); + // IFileTree scope: + auto iFileTreeClass = + py::class_>( + m, "IFileTree", py::multiple_inheritance()); - py::enum_(iFileTreeClass, "InsertPolicy") - .value("FAIL_IF_EXISTS", IFileTree::InsertPolicy::FAIL_IF_EXISTS) - .value("REPLACE", IFileTree::InsertPolicy::REPLACE) - .value("MERGE", IFileTree::InsertPolicy::MERGE) - .export_values() - ; + py::enum_(iFileTreeClass, "InsertPolicy") + .value("FAIL_IF_EXISTS", IFileTree::InsertPolicy::FAIL_IF_EXISTS) + .value("REPLACE", IFileTree::InsertPolicy::REPLACE) + .value("MERGE", IFileTree::InsertPolicy::MERGE) + .export_values(); - py::enum_(iFileTreeClass, "WalkReturn") - .value("CONTINUE", IFileTree::WalkReturn::CONTINUE) - .value("STOP", IFileTree::WalkReturn::STOP) - .value("SKIP", IFileTree::WalkReturn::SKIP) - .export_values() - ; + py::enum_(iFileTreeClass, "WalkReturn") + .value("CONTINUE", IFileTree::WalkReturn::CONTINUE) + .value("STOP", IFileTree::WalkReturn::STOP) + .value("SKIP", IFileTree::WalkReturn::SKIP) + .export_values(); - // Non-mutable operations: - iFileTreeClass.def("exists", - py::overload_cast(&IFileTree::exists, py::const_), - py::arg("path"), py::arg("type") = IFileTree::FILE_OR_DIRECTORY); - iFileTreeClass.def("find", py::overload_cast(&IFileTree::find), - py::arg("path"), py::arg("type") = IFileTree::FILE_OR_DIRECTORY, "[optional]"); - iFileTreeClass.def("pathTo", - &IFileTree::pathTo, py::arg("entry"), py::arg("sep") = "\\"); + // Non-mutable operations: + iFileTreeClass.def("exists", + py::overload_cast( + &IFileTree::exists, py::const_), + py::arg("path"), + py::arg("type") = IFileTree::FILE_OR_DIRECTORY); + iFileTreeClass.def( + "find", + py::overload_cast(&IFileTree::find), + py::arg("path"), py::arg("type") = IFileTree::FILE_OR_DIRECTORY, + "[optional]"); + iFileTreeClass.def("pathTo", &IFileTree::pathTo, py::arg("entry"), + py::arg("sep") = "\\"); - // Note: walk() would probably be better as a generator in python, but it is likely impossible to construct - // from the C++ walk() method. - iFileTreeClass.def("walk", - &IFileTree::walk, py::arg("callback"), py::arg("sep") = "\\"); + // Note: walk() would probably be better as a generator in python, but + // it is likely impossible to construct from the C++ walk() method. + iFileTreeClass.def("walk", &IFileTree::walk, py::arg("callback"), + py::arg("sep") = "\\"); - // Kind-of-static operations: - iFileTreeClass.def("createOrphanTree", - &IFileTree::createOrphanTree, py::arg("name") = ""); + // Kind-of-static operations: + iFileTreeClass.def("createOrphanTree", &IFileTree::createOrphanTree, + py::arg("name") = ""); - // addFile() and addDirectory throws exception instead of returning null pointer in order - // to have better traces. - iFileTreeClass.def("addFile", [](IFileTree* w, QString path, bool replaceIfExists) { - auto result = w->addFile(path, replaceIfExists); - if (result == nullptr) { - throw std::logic_error("addFile failed"); - } - return result; - }, py::arg("path"), py::arg("replace_if_exists") = false); - iFileTreeClass.def("addDirectory", [](IFileTree* w, QString path) { - auto result = w->addDirectory(path); - if (result == nullptr) { - throw std::logic_error("addDirectory failed"); - } - MOBase::log::warn("{}", result->fileType()); - return result; - }, py::arg("path")); + // addFile() and addDirectory throws exception instead of returning null + // pointer in order to have better traces. + iFileTreeClass.def( + "addFile", + [](IFileTree* w, QString path, bool replaceIfExists) { + auto result = w->addFile(path, replaceIfExists); + if (result == nullptr) { + throw std::logic_error("addFile failed"); + } + return result; + }, + py::arg("path"), py::arg("replace_if_exists") = false); + iFileTreeClass.def( + "addDirectory", + [](IFileTree* w, QString path) { + auto result = w->addDirectory(path); + if (result == nullptr) { + throw std::logic_error("addDirectory failed"); + } + MOBase::log::warn("{}", result->fileType()); + return result; + }, + py::arg("path")); - // Merge needs custom return types depending if the user wants overrides or not. A failure is translated - // into an exception for easier tracing and handling. - iFileTreeClass.def("merge", - [](IFileTree* p, std::shared_ptr other, bool returnOverwrites) - -> std::variant { - IFileTree::OverwritesType overwrites; - auto result = p->merge(other, returnOverwrites ? &overwrites : nullptr); - if (result == IFileTree::MERGE_FAILED) { - throw std::logic_error("merge failed"); - } - if (returnOverwrites) { - return { overwrites }; - } - return { result }; - }, py::arg("other"), py::arg("overwrites") = false); + // Merge needs custom return types depending if the user wants overrides + // or not. A failure is translated into an exception for easier tracing + // and handling. + iFileTreeClass.def( + "merge", + [](IFileTree* p, std::shared_ptr other, + bool returnOverwrites) + -> std::variant { + IFileTree::OverwritesType overwrites; + auto result = + p->merge(other, returnOverwrites ? &overwrites : nullptr); + if (result == IFileTree::MERGE_FAILED) { + throw std::logic_error("merge failed"); + } + if (returnOverwrites) { + return {overwrites}; + } + return {result}; + }, + py::arg("other"), py::arg("overwrites") = false); - // Insert and erase returns an iterator, which makes no sense in python, so we convert it to bool. Erase is also - // renamed "remove" since "erase" is very C++. - iFileTreeClass.def("insert", - [](IFileTree* p, std::shared_ptr entry, IFileTree::InsertPolicy insertPolicy) { - return p->insert(entry, insertPolicy) == p->end(); - }, py::arg("entry"), py::arg("policy") = IFileTree::InsertPolicy::FAIL_IF_EXISTS); + // Insert and erase returns an iterator, which makes no sense in python, + // so we convert it to bool. Erase is also renamed "remove" since + // "erase" is very C++. + iFileTreeClass.def( + "insert", + [](IFileTree* p, std::shared_ptr entry, + IFileTree::InsertPolicy insertPolicy) { + return p->insert(entry, insertPolicy) == p->end(); + }, + py::arg("entry"), + py::arg("policy") = IFileTree::InsertPolicy::FAIL_IF_EXISTS); - iFileTreeClass.def("remove", [](IFileTree* p, QString name) { - return p->erase(name).first != p->end(); }, py::arg("name")); - iFileTreeClass.def("remove", [](IFileTree* p, std::shared_ptr entry) { - return p->erase(entry) != p->end(); }, py::arg("entry")); + iFileTreeClass.def( + "remove", + [](IFileTree* p, QString name) { + return p->erase(name).first != p->end(); + }, + py::arg("name")); + iFileTreeClass.def( + "remove", + [](IFileTree* p, std::shared_ptr entry) { + return p->erase(entry) != p->end(); + }, + py::arg("entry")); - iFileTreeClass.def("move", - &IFileTree::move, py::arg("entry"), py::arg("path"), - py::arg("policy") = IFileTree::InsertPolicy::FAIL_IF_EXISTS); - iFileTreeClass.def("copy", - [](IFileTree* w, std::shared_ptr entry, QString path, IFileTree::InsertPolicy insertPolicy) { - auto result = w->copy(entry, path, insertPolicy); - if (result == nullptr) { - throw std::logic_error("copy failed"); - } - return result; - }, py::arg("entry"), py::arg("path") = "", - py::arg("insert_policy") = IFileTree::InsertPolicy::FAIL_IF_EXISTS); + iFileTreeClass.def( + "move", &IFileTree::move, py::arg("entry"), py::arg("path"), + py::arg("policy") = IFileTree::InsertPolicy::FAIL_IF_EXISTS); + iFileTreeClass.def( + "copy", + [](IFileTree* w, std::shared_ptr entry, QString path, + IFileTree::InsertPolicy insertPolicy) { + auto result = w->copy(entry, path, insertPolicy); + if (result == nullptr) { + throw std::logic_error("copy failed"); + } + return result; + }, + py::arg("entry"), py::arg("path") = "", + py::arg("insert_policy") = IFileTree::InsertPolicy::FAIL_IF_EXISTS); - iFileTreeClass.def("clear", &IFileTree::clear); - iFileTreeClass.def("removeAll", &IFileTree::removeAll, py::arg("names")); - iFileTreeClass.def("removeIf", &IFileTree::removeIf, py::arg("filter")); + iFileTreeClass.def("clear", &IFileTree::clear); + iFileTreeClass.def("removeAll", &IFileTree::removeAll, + py::arg("names")); + iFileTreeClass.def("removeIf", &IFileTree::removeIf, py::arg("filter")); - // Special methods: - iFileTreeClass.def("__getitem__", py::overload_cast(&IFileTree::at)); - // , py::return_value_policy>() + // Special methods: + iFileTreeClass.def("__getitem__", + py::overload_cast(&IFileTree::at)); + // , py::return_value_policy>() - iFileTreeClass.def("__iter__", [](IFileTree *tree) { - return py::make_iterator(*tree); }); - iFileTreeClass.def("__len__", &IFileTree::size); - iFileTreeClass.def("__bool__", +[](const IFileTree* tree) { - return !tree->empty(); }); - iFileTreeClass.def("__repr__", +[](const IFileTree* entry) { - return "IFileTree(\"" + entry->name() + "\")"; }); - } + iFileTreeClass.def("__iter__", [](IFileTree* tree) { + return py::make_iterator(*tree); + }); + iFileTreeClass.def("__len__", &IFileTree::size); + iFileTreeClass.def( + "__bool__", +[](const IFileTree* tree) { + return !tree->empty(); + }); + iFileTreeClass.def( + "__repr__", +[](const IFileTree* entry) { + return "IFileTree(\"" + entry->name() + "\")"; + }); + } - void add_make_tree_function(pybind11::module_ &m) { - m.def("makeTree", [](mo2::details::PyFileTree::callback_t callback) -> std::shared_ptr { - return std::make_shared(nullptr, "", callback); - }, py::arg("callback") = mo2::details::PyFileTree::callback_t{}); - } + void add_make_tree_function(pybind11::module_& m) + { + m.def( + "makeTree", + [](mo2::details::PyFileTree::callback_t callback) + -> std::shared_ptr { + return std::make_shared(nullptr, "", + callback); + }, + py::arg("callback") = mo2::details::PyFileTree::callback_t{}); + } -} +} // namespace mo2::python #pragma optimize("", on) diff --git a/src/runner-pybind11/pythonrunner.cpp b/src/runner-pybind11/pythonrunner.cpp index 332c912..6b3cb7c 100644 --- a/src/runner-pybind11/pythonrunner.cpp +++ b/src/runner-pybind11/pythonrunner.cpp @@ -1,7 +1,7 @@ #include "pythonrunner.h" -#pragma warning( disable : 4100 ) -#pragma warning( disable : 4996 ) +#pragma warning(disable : 4100) +#pragma warning(disable : 4996) #include #include @@ -15,28 +15,27 @@ #include #include #include +#include #include #include #include -#include +#include #include #include -#include #include +#include -#include #include +#include -#ifndef Q_MOC_RUN -#include #include -// #include -// #include -#endif +#include +#include -#include "converters.h" #include "error.h" +#include "pybind11_qt/pybind11_qt.h" +#include "wrappers/uibase_wrappers.h" // #include "gamefeatureswrappers.h" // #include "proxypluginwrappers.h" #include "pyfiletree.h" @@ -56,1373 +55,1576 @@ namespace py = pybind11; /** * This macro should be used within a py::class_ declaration and will define two - * methods: __getattr__ and Name, where Name will simply return the object as a QClass* - * object, while __getattr__ will delegate to the underlying QClass object when required. + * methods: __getattr__ and Name, where Name will simply return the object as a + * QClass* object, while __getattr__ will delegate to the underlying QClass + * object when required. * - * This allow access to Qt interface for object exposed using boost::python (e.g., signals, - * methods from QObject or QWidget, etc.). + * This allow access to Qt interface for object exposed using boost::python + * (e.g., signals, methods from QObject or QWidget, etc.). */ -#define Q_DELEGATE(Class, QClass, Name) \ - .def(Name, +[](Class* w) -> QClass* { return w; }, py::return_value_policy()) \ - .def("__getattr__", +[](Class* w, py::str str) -> py::object { \ - return py::object{ (QClass*)w }.attr(str); \ - }) +template +auto& add_qt_delegate(py::class_& pyclass, const char* name) +{ + return pyclass + .def(name, + [](Class* w) -> QClass* { + return w; + }) + .def( + "__getattr__", +[](Class* w, py::str str) -> py::object { + return py::cast((QClass*)w).attr(str); + }); +} PYBIND11_MODULE(mobase, m) { - py::module_::import("PyQt6.QtCore"); - py::module_::import("PyQt6.QtWidgets"); - - py::detail::type_caster t1; - py::detail::type_caster t2; - - m.def("testQStringList", [](QStringList const& list) { - QStringList res = list; - for (QString& value : res) { - value = value + "_CPP"; - } - return res; - }); - - m.def("testQMap1", [](QMap const& map) { - QMap res; - for (auto it = map.begin(); it != map.end(); ++it) { - res[it.key()] = it.value().size(); - } - return res; - }); - - m.def("testQMap2", [](QMap const& map) { - QMap res; - for (auto it = map.begin(); it != map.end(); ++it) { - res[it.key()] = QString::number(it.value()); - } - return res; - }); - - m.def("testEnum", [](QMessageBox::StandardButton button) { - return py::make_tuple(button, QMessageBox::Icon::Information); - }); - - m.def("testPixmap", [](QPixmap const& pixmap) { - return pixmap.size(); - }); - - // utils::register_qstring_converter(); - // utils::register_qvariant_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(); - // utils::register_qclass_converter(); - - // // QFlags: - // utils::register_qflags_converter(); - // utils::register_qflags_converter(); - // utils::register_qflags_converter(); - - // // Enums: - // utils::register_enum_converter(); - // utils::register_enum_converter(); - - // // Pointers: - // py::register_ptr_to_python>(); - // py::register_ptr_to_python>(); - // py::implicitly_convertible, std::shared_ptr>(); - // py::register_ptr_to_python>(); - // py::register_ptr_to_python>(); - // py::implicitly_convertible, std::shared_ptr>(); - - // utils::shared_ptr_from_python>(); - // py::register_ptr_to_python>(); - - // utils::shared_ptr_from_python>(); - // py::register_ptr_to_python>(); - - // // Containers: - // utils::register_sequence_container>(); - // utils::register_sequence_container>(); - // utils::register_sequence_container>(); - // utils::register_sequence_container>(); - // utils::register_sequence_container>(); - // utils::register_sequence_container>(); - // utils::register_sequence_container(); - // utils::register_sequence_container>(); - // utils::register_sequence_container>(); - // utils::register_sequence_container>(); - // utils::register_sequence_container>(); // Required for QVariant since this is QVariantList. - // utils::register_sequence_container>>(); - // utils::register_sequence_container>>(); - // utils::register_sequence_container>>(); - // utils::register_sequence_container>(); - // utils::register_sequence_container>(); - // utils::register_sequence_container>(); - - // utils::register_set_container>(); - - // utils::register_associative_container>(); // Required for QVariant since this is QVariantMap. - // utils::register_associative_container>(); - // utils::register_associative_container>(); - // utils::register_associative_container>(); - - // utils::register_associative_container(); - - // utils::register_optional>(); - - // // Tuple: - // py::register_tuple>(); // IOrganizer::waitForApplication - // py::register_tuple>(); // IProfile::invalidationActive - // py::register_tuple>(); - // py::register_tuple, QString, int>>(); - // py::register_tuple>(); - - // // Variants: - // py::register_variant, - // std::tuple, QString, int>>>(); - // py::register_variant>(); - // py::register_variant>(); - // py::register_variant>>(); - // py::register_variant>>(); - - // // Functions: - // utils::register_functor_converter(); // converter for the onRefreshed-callback - // utils::register_functor_converter(); - // utils::register_functor_converter(); - // utils::register_functor_converter(); - // utils::register_functor_converter(); // converter for the onModMoved-callback and onPluginMoved callbacks - // utils::register_functor_converter&)>(); // converter for the onModStateChanged-callback (IModList) - // utils::register_functor_converter&)>(); // converter for the onPluginStateChanged-callback (IPluginList) - // utils::register_functor_converter(); - // utils::register_functor_converter(); - // utils::register_functor_converter>(); - // utils::register_functor_converter>(); - // utils::register_functor_converter>(); - // utils::register_functor_converter(); - // utils::register_functor_converter)>(); - // utils::register_functor_converter>(); - // utils::register_functor_converter const&)>(); - // utils::register_functor_converter(QString const&)>(); - // utils::register_functor_converter>(); - // utils::register_functor_converter>(); - // utils::register_functor_converter>(); - - // // This one is kept for backward-compatibility while we deprecate onModStateChanged for singl mod. - // utils::register_functor_converter(); // converter for the onModStateChanged-callback (IModList). - // utils::register_functor_converter(); // converter for the onPluginStateChanged-callback (IPluginList). - - // // - // // Class declarations: - // // - - // py::enum_("ReleaseType") - // .value("final", MOBase::VersionInfo::RELEASE_FINAL) - // .value("candidate", MOBase::VersionInfo::RELEASE_CANDIDATE) - // .value("beta", MOBase::VersionInfo::RELEASE_BETA) - // .value("alpha", MOBase::VersionInfo::RELEASE_ALPHA) - // .value("prealpha", MOBase::VersionInfo::RELEASE_PREALPHA) - - // .value("FINAL", MOBase::VersionInfo::RELEASE_FINAL) - // .value("CANDIDATE", MOBase::VersionInfo::RELEASE_CANDIDATE) - // .value("BETA", MOBase::VersionInfo::RELEASE_BETA) - // .value("ALPHA", MOBase::VersionInfo::RELEASE_ALPHA) - // .value("PRE_ALPHA", MOBase::VersionInfo::RELEASE_PREALPHA) - // ; - - // py::enum_("VersionScheme") - // .value("discover", MOBase::VersionInfo::SCHEME_DISCOVER) - // .value("regular", MOBase::VersionInfo::SCHEME_REGULAR) - // .value("decimalmark", MOBase::VersionInfo::SCHEME_DECIMALMARK) - // .value("numbersandletters", MOBase::VersionInfo::SCHEME_NUMBERSANDLETTERS) - // .value("date", MOBase::VersionInfo::SCHEME_DATE) - // .value("literal", MOBase::VersionInfo::SCHEME_LITERAL) - - // .value("DISCOVER", MOBase::VersionInfo::SCHEME_DISCOVER) - // .value("REGULAR", MOBase::VersionInfo::SCHEME_REGULAR) - // .value("DECIMAL_MARK", MOBase::VersionInfo::SCHEME_DECIMALMARK) - // .value("NUMBERS_AND_LETTERS", MOBase::VersionInfo::SCHEME_NUMBERSANDLETTERS) - // .value("DATE", MOBase::VersionInfo::SCHEME_DATE) - // .value("LITERAL", MOBase::VersionInfo::SCHEME_LITERAL) - // ; - - // py::class_("VersionInfo") - // .def(py::init( - // (py::arg("value"), py::arg("scheme") = VersionInfo::SCHEME_DISCOVER))) - // // Note: Order of the two init<> below is important because ReleaseType is a simple enum with an - // // implicit int conversion. - // .def(py::init( - // (py::arg("major"), "minor", "subminor", "subsubminor", py::arg("release_type") = VersionInfo::RELEASE_FINAL))) - // .def(py::init( - // (py::arg("major"), "minor", "subminor", py::arg("release_type") = VersionInfo::RELEASE_FINAL))) - // .def("clear", &VersionInfo::clear) - // .def("parse", &VersionInfo::parse, - // (py::arg("value"), py::arg("scheme") = VersionInfo::SCHEME_DISCOVER, py::arg("is_manual") = false)) - // .def("canonicalString", &VersionInfo::canonicalString) - // .def("displayString", &VersionInfo::displayString, py::arg("forced_segments") = 2) - // .def("isValid", &VersionInfo::isValid) - // .def("scheme", &VersionInfo::scheme) - // .def("__str__", &VersionInfo::canonicalString) - // .def(py::self < py::self) - // .def(py::self > py::self) - // .def(py::self <= py::self) - // .def(py::self >= py::self) - // .def(py::self != py::self) - // .def(py::self == py::self) - // ; - - // py::class_( - // "PluginSetting", py::init( - // (py::arg("key"), "description", "default_value"))) - // .def_readwrite("key", &PluginSetting::key) - // .def_readwrite("description", &PluginSetting::description) - // .def_readwrite("default_value", &PluginSetting::defaultValue); - - // py::class_("ExecutableInfo", - // py::init((py::arg("title"), "binary"))) - // .def("withArgument", &ExecutableInfo::withArgument, py::return_self<>(), py::arg("argument")) - // .def("withWorkingDirectory", &ExecutableInfo::withWorkingDirectory, py::return_self<>(), py::arg("directory")) - // .def("withSteamAppId", &ExecutableInfo::withSteamAppId, py::return_self<>(), py::arg("app_id")) - // .def("asCustom", &ExecutableInfo::asCustom, py::return_self<>()) - // .def("isValid", &ExecutableInfo::isValid) - // .def("title", &ExecutableInfo::title) - // .def("binary", &ExecutableInfo::binary) - // .def("arguments", &ExecutableInfo::arguments) - // .def("workingDirectory", &ExecutableInfo::workingDirectory) - // .def("steamAppID", &ExecutableInfo::steamAppID) - // .def("isCustom", &ExecutableInfo::isCustom) - // ; - - // py::class_("ExecutableForcedLoadSetting", - // py::init((py::arg("process"), "library"))) - // .def("withForced", &ExecutableForcedLoadSetting::withForced, py::return_self<>(), py::arg("forced")) - // .def("withEnabled", &ExecutableForcedLoadSetting::withEnabled, py::return_self<>(), py::arg("enabled")) - // .def("enabled", &ExecutableForcedLoadSetting::enabled) - // .def("forced", &ExecutableForcedLoadSetting::forced) - // .def("library", &ExecutableForcedLoadSetting::library) - // .def("process", &ExecutableForcedLoadSetting::process) - // ; - - // py::class_, boost::noncopyable>("ISaveGame") - // .def("getFilepath", py::pure_virtual(&ISaveGame::getFilepath)) - // .def("getCreationTime", py::pure_virtual(&ISaveGame::getCreationTime)) - // .def("getName", py::pure_virtual(&ISaveGame::getName)) - // .def("getSaveGroupIdentifier", py::pure_virtual(&ISaveGame::getSaveGroupIdentifier)) - // .def("allFiles", py::pure_virtual(&ISaveGame::allFiles)) - // ; - - // // See Q_DELEGATE for more details. - // py::class_, ISaveGameInfoWidgetWrapper*, boost::noncopyable>( - // "ISaveGameInfoWidget", py::init>(py::arg("parent"))) - // .def("setSave", py::pure_virtual(&ISaveGameInfoWidget::setSave), py::arg("save")) - - // Q_DELEGATE(ISaveGameInfoWidget, QWidget, "_widget") - // ; - - // // Plugin requirements: - // auto iPluginRequirementClass = py::class_< - // IPluginRequirementWrapper, py::bases<>, boost::noncopyable>("IPluginRequirement"); - // { - // py::scope scope = iPluginRequirementClass; - - // py::class_("Problem", - // py::init((py::arg("short_description"), py::arg("long_description") = ""))) - // .def("shortDescription", &IPluginRequirement::Problem::shortDescription) - // .def("longDescription", &IPluginRequirement::Problem::longDescription); - - // iPluginRequirementClass - // .def("check", py::pure_virtual(&IPluginRequirement::check), py::arg("organizer")) - // ; - // } - - // py::class_("PluginRequirementFactory") - // // pluginDependency - // .def("pluginDependency", +[](QStringList const& pluginNames) { - // return PluginRequirementFactory::pluginDependency(pluginNames); - // }, py::arg("plugins")) - // .def("pluginDependency", +[](QString const& pluginName) { - // return PluginRequirementFactory::pluginDependency(pluginName); - // }, py::arg("plugin")) - // .staticmethod("pluginDependency") - // // gameDependency - // .def("gameDependency", +[](QStringList const& gameNames) { - // return PluginRequirementFactory::gameDependency(gameNames); - // }, py::arg("games")) - // .def("gameDependency", +[](QString const& gameNames) { - // return PluginRequirementFactory::gameDependency(gameNames); - // }, py::arg("game")) - // .staticmethod("gameDependency") - // // diagnose - // .def("diagnose", &PluginRequirementFactory::diagnose, py::arg("diagnose")) - // .staticmethod("diagnose") - // // basic - // .def("basic", &PluginRequirementFactory::basic, (py::arg("checker"), "description")) - // .staticmethod("basic"); - - // py::class_("FileInfo", py::init<>()) - // .add_property("filePath", - // +[](const IOrganizer::FileInfo& info) { return info.filePath; }, - // +[](IOrganizer::FileInfo& info, QString value) { info.filePath = value; }) - // .add_property("archive", - // +[](const IOrganizer::FileInfo& info) { return info.archive; }, - // +[](IOrganizer::FileInfo& info, QString value) { info.archive = value; }) - // .add_property("origins", - // +[](const IOrganizer::FileInfo& info) { return info.origins; }, - // +[](IOrganizer::FileInfo& info, QStringList value) { info.origins = value; }) - // ; - - // py::class_("IOrganizer", py::no_init) - // .def("createNexusBridge", &IOrganizer::createNexusBridge, py::return_value_policy()) - // .def("profileName", &IOrganizer::profileName) - // .def("profilePath", &IOrganizer::profilePath) - // .def("downloadsPath", &IOrganizer::downloadsPath) - // .def("overwritePath", &IOrganizer::overwritePath) - // .def("basePath", &IOrganizer::basePath) - // .def("modsPath", &IOrganizer::modsPath) - // .def("appVersion", &IOrganizer::appVersion) - // .def("createMod", &IOrganizer::createMod, py::return_value_policy(), py::arg("name")) - // .def("getGame", &IOrganizer::getGame, py::return_value_policy(), py::arg("name")) - // .def("modDataChanged", &IOrganizer::modDataChanged, py::arg("mod")) - // .def("isPluginEnabled", +[](IOrganizer* o, IPlugin* plugin) { return o->isPluginEnabled(plugin); }, py::arg("plugin")) - // .def("isPluginEnabled", +[](IOrganizer* o, QString const& plugin) { return o->isPluginEnabled(plugin); }, py::arg("plugin")) - // .def("pluginSetting", &IOrganizer::pluginSetting, (py::arg("plugin_name"), "key")) - // .def("setPluginSetting", &IOrganizer::setPluginSetting, (py::arg("plugin_name"), "key", "value")) - // .def("persistent", &IOrganizer::persistent, (py::arg("plugin_name"), "key", py::arg("default") = QVariant())) - // .def("setPersistent", &IOrganizer::setPersistent, (py::arg("plugin_name"), "key", "value", py::arg("sync") = true)) - // .def("pluginDataPath", &IOrganizer::pluginDataPath) - // .def("installMod", &IOrganizer::installMod, py::return_value_policy(), (py::arg("filename"), py::arg("name_suggestion") = "")) - // .def("resolvePath", &IOrganizer::resolvePath, py::arg("filename")) - // .def("listDirectories", &IOrganizer::listDirectories, py::arg("directory")) - - // // Provide multiple overloads of findFiles: - // .def("findFiles", +[](const IOrganizer* o, QString const& p, std::function f) { return o->findFiles(p, f); }, - // (py::arg("path"), "filter")) - - // // In C++, it is possible to create a QStringList implicitly from a single QString. This is not possible with the current - // // converters in python (and I do not think it is a good idea to have it everywhere), but here it is nice to be able to - // // pass a single string, so we add an extra overload. - // // Important: the order matters, because a Python string can be converted to a QStringList since it is a sequence of - // // single-character strings: - // .def("findFiles", +[](const IOrganizer* o, QString const& p, const QStringList& gf) { return o->findFiles(p, gf); }, - // (py::arg("path"), "patterns")) - // .def("findFiles", +[](const IOrganizer* o, QString const& p, const QString& f) { return o->findFiles(p, QStringList{ f }); }, - // (py::arg("path"), "pattern")) - - // .def("getFileOrigins", &IOrganizer::getFileOrigins, py::arg("filename")) - // .def("findFileInfos", &IOrganizer::findFileInfos, (py::arg("path"), "filter")) - - // .def("virtualFileTree", &IOrganizer::virtualFileTree) - - // .def("downloadManager", &IOrganizer::downloadManager, py::return_value_policy()) - // .def("pluginList", &IOrganizer::pluginList, py::return_value_policy()) - // .def("modList", &IOrganizer::modList, py::return_value_policy()) - // .def("profile", &IOrganizer::profile, py::return_value_policy()) - - // // Custom implementation for startApplication and waitForApplication because 1) HANDLE (= void*) is not properly - // // converted from/to python, and 2) we need to convert the by-ptr argument to a return-tuple for waitForApplication: - // .def("startApplication", - // +[](IOrganizer* o, const QString& executable, const QStringList& args, const QString& cwd, const QString& profile, - // const QString& forcedCustomOverwrite, bool ignoreCustomOverwrite) { - // return (std::uintptr_t) o->startApplication(executable, args, cwd, profile, forcedCustomOverwrite, ignoreCustomOverwrite); - // }, (py::arg("executable"), (py::arg("args") = QStringList()), (py::arg("cwd") = ""), (py::arg("profile") = ""), - // (py::arg("forcedCustomOverwrite") = ""), (py::arg("ignoreCustomOverwrite") = false)), py::return_value_policy()) - // .def("waitForApplication", +[](IOrganizer *o, std::uintptr_t handle, bool refresh) { - // DWORD returnCode; - // bool result = o->waitForApplication((HANDLE)handle, refresh, &returnCode); - // return std::make_tuple(result, returnCode); - // }, (py::arg("handle"), py::arg("refresh") = true)) - // .def("refresh", &IOrganizer::refresh, (py::arg("save_changes") = true)) - // .def("managedGame", &IOrganizer::managedGame, py::return_value_policy()) - - // .def("onAboutToRun", &IOrganizer::onAboutToRun, py::arg("callback")) - // .def("onFinishedRun", &IOrganizer::onFinishedRun, py::arg("callback")) - // .def("onUserInterfaceInitialized", &IOrganizer::onUserInterfaceInitialized, py::arg("callback")) - // .def("onProfileCreated", &IOrganizer::onProfileCreated, py::arg("callback")) - // .def("onProfileRenamed", &IOrganizer::onProfileRenamed, py::arg("callback")) - // .def("onProfileRemoved", &IOrganizer::onProfileRemoved, py::arg("callback")) - // .def("onProfileChanged", &IOrganizer::onProfileChanged, py::arg("callback")) - - // .def("onPluginSettingChanged", &IOrganizer::onPluginSettingChanged, py::arg("callback")) - // .def("onPluginEnabled", +[](IOrganizer* o, std::function const& func) { - // o->onPluginEnabled(func); - // }, py::arg("callback")) - // .def("onPluginEnabled", +[](IOrganizer* o, QString const& name, std::function const& func) { - // o->onPluginEnabled(name, func); - // }, (py::arg("name"), py::arg("callback"))) - // .def("onPluginDisabled", +[](IOrganizer* o, std::function const& func) { - // o->onPluginDisabled(func); - // }, py::arg("callback")) - // .def("onPluginDisabled", +[](IOrganizer* o, QString const& name, std::function const& func) { - // o->onPluginDisabled(name, func); - // }, (py::arg("name"), py::arg("callback"))) - - // // DEPRECATED: - // .def("getMod", +[](IOrganizer* o, QString const& name) { - // utils::show_deprecation_warning("getMod", - // "IOrganizer::getMod(str) is deprecated, use IModList::getMod(str) instead."); - // return o->modList()->getMod(name); - // }, py::return_value_policy(), py::arg("name")) - // .def("removeMod", +[](IOrganizer* o, IModInterface *mod) { - // utils::show_deprecation_warning("removeMod", - // "IOrganizer::removeMod(IModInterface) is deprecated, use IModList::removeMod(IModInterface) instead."); - // return o->modList()->removeMod(mod); - // }, py::arg("mod")) - // .def("modsSortedByProfilePriority", +[](IOrganizer* o) { - // utils::show_deprecation_warning("modsSortedByProfilePriority", - // "IOrganizer::modsSortedByProfilePriority() is deprecated, use IModList::allModsByProfilePriority() instead."); - // return o->modList()->allModsByProfilePriority(); - // }) - // .def("refreshModList", +[](IOrganizer* o, bool s) { - // utils::show_deprecation_warning("refreshModList", - // "IOrganizer::refreshModList(bool) is deprecated, use IOrganizer::refresh(bool) instead."); - // o->refresh(s); - // }, (py::arg("save_changes") = true)) - // .def("onModInstalled", +[](IOrganizer* organizer, const std::function& func) { - // utils::show_deprecation_warning("onModInstalled", - // "IOrganizer::onModInstalled(Callable[[str], None]) is deprecated, " - // "use IModList::onModInstalled(Callable[[IModInterface], None]) instead."); - // return organizer->modList()->onModInstalled([func](MOBase::IModInterface* m) { func(m->name()); });; - // }, py::arg("callback")) - - // .def("getPluginDataPath", &IOrganizer::getPluginDataPath) - // .staticmethod("getPluginDataPath") - - // ; - - mo2::python::add_ifiletree_bindings(m); - - py::class_(m, "IProfile") - .def("name", &IProfile::name) - .def("absolutePath", &IProfile::absolutePath) - .def("localSavesEnabled", &IProfile::localSavesEnabled) - .def("localSettingsEnabled", &IProfile::localSettingsEnabled) - .def("invalidationActive", [](const IProfile* p) { - bool supported; - bool active = p->invalidationActive(&supported); - return py::make_tuple(active, supported); - }) - .def("absoluteIniFilePath", &IProfile::absoluteIniFilePath, py::arg("inifile")) - ; - - // py::class_("IModRepositoryBridge", py::no_init) - // .def("requestDescription", &IModRepositoryBridge::requestDescription, (py::arg("game_name"), "mod_id", "user_data")) - // .def("requestFiles", &IModRepositoryBridge::requestFiles, (py::arg("game_name"), "mod_id", "user_data")) - // .def("requestFileInfo", &IModRepositoryBridge::requestFileInfo, (py::arg("game_name"), "mod_id", "file_id", "user_data")) - // .def("requestDownloadURL", &IModRepositoryBridge::requestDownloadURL, (py::arg("game_name"), "mod_id", "file_id", "user_data")) - // .def("requestToggleEndorsement", &IModRepositoryBridge::requestToggleEndorsement, (py::arg("game_name"), "mod_id", "mod_version", "endorse", "user_data")) - - // Q_DELEGATE(IModRepositoryBridge, QObject, "_object") - // ; - - // py::class_("ModRepositoryFileInfo", py::no_init) - // .def(py::init(py::arg("other"))) - // .def(py::init>((py::arg("game_name"), "mod_id", "file_id"))) - // .def("__str__", &ModRepositoryFileInfo::toString) - // .def("createFromJson", &ModRepositoryFileInfo::createFromJson, py::arg("data")).staticmethod("createFromJson") - // .def_readwrite("name", &ModRepositoryFileInfo::name) - // .def_readwrite("uri", &ModRepositoryFileInfo::uri) - // .def_readwrite("description", &ModRepositoryFileInfo::description) - // .def_readwrite("version", &ModRepositoryFileInfo::version) - // .def_readwrite("newestVersion", &ModRepositoryFileInfo::newestVersion) - // .def_readwrite("categoryID", &ModRepositoryFileInfo::categoryID) - // .def_readwrite("modName", &ModRepositoryFileInfo::modName) - // .def_readwrite("gameName", &ModRepositoryFileInfo::gameName) - // .def_readwrite("modID", &ModRepositoryFileInfo::modID) - // .def_readwrite("fileID", &ModRepositoryFileInfo::fileID) - // .def_readwrite("fileSize", &ModRepositoryFileInfo::fileSize) - // .def_readwrite("fileName", &ModRepositoryFileInfo::fileName) - // .def_readwrite("fileCategory", &ModRepositoryFileInfo::fileCategory) - // .def_readwrite("fileTime", &ModRepositoryFileInfo::fileTime) - // .def_readwrite("repository", &ModRepositoryFileInfo::repository) - // .def_readwrite("userData", &ModRepositoryFileInfo::userData) - // ; - - // py::class_("IDownloadManager", py::no_init) - // .def("startDownloadURLs", &IDownloadManager::startDownloadURLs, py::arg("urls")) - // .def("startDownloadNexusFile", &IDownloadManager::startDownloadNexusFile, (py::arg("mod_id"), "file_id")) - // .def("downloadPath", &IDownloadManager::downloadPath, py::arg("id")) - // .def("onDownloadComplete", &IDownloadManager::onDownloadComplete, py::arg("callback")) - // .def("onDownloadPaused", &IDownloadManager::onDownloadPaused, py::arg("callback")) - // .def("onDownloadFailed", &IDownloadManager::onDownloadFailed, py::arg("callback")) - // .def("onDownloadRemoved", &IDownloadManager::onDownloadRemoved, py::arg("callback")) - // ; - - // py::class_("IInstallationManager", py::no_init) - // .def("getSupportedExtensions", &IInstallationManager::getSupportedExtensions) - // .def("extractFile", &IInstallationManager::extractFile, (py::arg("entry"), py::arg("silent") = false)) - // .def("extractFiles", &IInstallationManager::extractFiles, (py::arg("entries"), py::arg("silent") = false)) - // .def("createFile", +[](IInstallationManager* m, std::shared_ptr entry) { - // return m->createFile(utils::clean_shared_ptr(entry)); - // }, py::arg("entry")) - - // // accept both QString and GuessedValue since the conversion is not automatic in Python, and - // // return a tuple to get back the mod name and the mod ID - // .def("installArchive", +[](IInstallationManager* m, std::variant> modName, QString archive, int modId) { - // GuessedValue tmp; - // if (auto* p = std::get_if(&modName)) { - // tmp = *p; - // } - // else { - // tmp = std::get>(modName); - // } - // auto result = m->installArchive(tmp, archive, modId); - // return std::make_tuple(result, static_cast(tmp), modId); - // }, (py::arg("mod_name"), "archive", py::arg("mod_id") = 0)) - // ; - - py::enum_(m, "EndorsedState") - .value("ENDORSED_FALSE", EndorsedState::ENDORSED_FALSE) - .value("ENDORSED_TRUE", EndorsedState::ENDORSED_TRUE) - .value("ENDORSED_UNKNOWN", EndorsedState::ENDORSED_UNKNOWN) - .value("ENDORSED_NEVER", EndorsedState::ENDORSED_NEVER) - ; - - py::enum_(m, "TrackedState") - .value("TRACKED_FALSE", TrackedState::TRACKED_FALSE) - .value("TRACKED_TRUE", TrackedState::TRACKED_TRUE) - .value("TRACKED_UNKNOWN", TrackedState::TRACKED_UNKNOWN) - ; - - // py::class_("IModInterface", py::no_init) - // .def("name", &IModInterface::name) - // .def("absolutePath", &IModInterface::absolutePath) - - // .def("comments", &IModInterface::comments) - // .def("notes", &IModInterface::notes) - // .def("gameName", &IModInterface::gameName) - // .def("repository", &IModInterface::repository) - // .def("nexusId", &IModInterface::nexusId) - // .def("version", &IModInterface::version) - // .def("newestVersion", &IModInterface::newestVersion) - // .def("ignoredVersion", &IModInterface::ignoredVersion) - // .def("installationFile", &IModInterface::installationFile) - // .def("converted", &IModInterface::converted) - // .def("validated", &IModInterface::validated) - // .def("color", &IModInterface::color) - // .def("url", &IModInterface::url) - // .def("primaryCategory", &IModInterface::primaryCategory) - // .def("categories", &IModInterface::categories) - // .def("trackedState", &IModInterface::trackedState) - // .def("endorsedState", &IModInterface::endorsedState) - // .def("fileTree", &IModInterface::fileTree) - // .def("isOverwrite", &IModInterface::isOverwrite) - // .def("isBackup", &IModInterface::isBackup) - // .def("isSeparator", &IModInterface::isSeparator) - // .def("isForeign", &IModInterface::isForeign) - - // .def("setVersion", &IModInterface::setVersion, py::arg("version")) - // .def("setNewestVersion", &IModInterface::setNewestVersion, py::arg("version")) - // .def("setIsEndorsed", &IModInterface::setIsEndorsed, py::arg("endorsed")) - // .def("setNexusID", &IModInterface::setNexusID, py::arg("nexus_id")) - // .def("addNexusCategory", &IModInterface::addNexusCategory, py::arg("category_id")) - // .def("addCategory", &IModInterface::addCategory, py::arg("name")) - // .def("removeCategory", &IModInterface::removeCategory, py::arg("name")) - // .def("setGameName", &IModInterface::setGameName, py::arg("name")) - // .def("setUrl", &IModInterface::setUrl, py::arg("url")) - // .def("pluginSetting", &IModInterface::pluginSetting, (py::arg("plugin_name"), "key", py::arg("default") = QVariant())) - // .def("pluginSettings", &IModInterface::pluginSettings, py::arg("plugin_name")) - // .def("setPluginSetting", &IModInterface::setPluginSetting, (py::arg("plugin_name"), "key", py::arg("value"))) - // .def("clearPluginSettings", &IModInterface::clearPluginSettings, py::arg("plugin_name")) - - // ; - - py::enum_(m, "GuessQuality") - .value("INVALID", MOBase::GUESS_INVALID) - .value("FALLBACK", MOBase::GUESS_FALLBACK) - .value("GOOD", MOBase::GUESS_GOOD) - .value("META", MOBase::GUESS_META) - .value("PRESET", MOBase::GUESS_PRESET) - .value("USER", MOBase::GUESS_USER) - ; - - // py::class_, boost::noncopyable>("GuessedString") - // .def(py::init<>()) - // .def(py::init((py::arg("value"), py::arg("quality") = EGuessQuality::GUESS_USER))) - // .def("update", - // static_cast& (GuessedValue::*)(const QString&)>(&GuessedValue::update), - // py::return_self<>(), py::arg("value")) - // .def("update", - // static_cast& (GuessedValue::*)(const QString&, EGuessQuality)>(&GuessedValue::update), - // py::return_self<>(), (py::arg("value"), "quality")) - - // // Methods to simulate the assignment operator: - // .def("reset", +[](GuessedValue* gv) { - // *gv = GuessedValue(); }, py::return_self<>()) - // .def("reset", +[](GuessedValue* gv, const QString& value, EGuessQuality eq) { - // *gv = GuessedValue(value, eq); }, py::return_self<>(), (py::arg("value"), "quality")) - // .def("reset", +[](GuessedValue* gv, const GuessedValue& other) { - // *gv = other; }, py::return_self<>(), py::arg("other")) - - // // Use an intermediate lambda to avoid having to register the std::function conversion: - // .def("setFilter", +[](GuessedValue* gv, std::function(QString const&)> fn) { - // gv->setFilter([fn](QString& s) { - // auto ret = fn(s); - // return std::visit([&s](auto v) { - // if constexpr (std::is_same_v) { - // s = v; - // return true; - // } - // else if constexpr (std::is_same_v) { - // return v; - // } - // }, ret); - // }); - // }, py::arg("filter")) - - // // This makes a copy in python but it more practical than exposing an iterator: - // .def("variants", &GuessedValue::variants, py::return_value_policy()) - // .def("__str__", &MOBase::GuessedValue::operator const QString&, py::return_value_policy()) - // ; - - py::enum_(m, "PluginState") - .value("missing", IPluginList::STATE_MISSING) - .value("inactive", IPluginList::STATE_INACTIVE) - .value("active", IPluginList::STATE_ACTIVE) - - .value("MISSING", IPluginList::STATE_MISSING) - .value("INACTIVE", IPluginList::STATE_INACTIVE) - .value("ACTIVE", IPluginList::STATE_ACTIVE) - ; - - m.def("testFlags1", [](IPluginList::PluginStates states) { - std::vector res; - if (states.testFlag(IPluginList::STATE_MISSING)) { - res.push_back("missing"); - } - if (states.testFlag(IPluginList::STATE_INACTIVE)) { - res.push_back("inactive"); - } - if (states.testFlag(IPluginList::STATE_ACTIVE)) { - res.push_back("active"); - } - return res; - }); - - m.def("testFlags2", [](QStringList const& states) { - IPluginList::PluginStates res; - if (states.contains("missing")) { - res |= IPluginList::STATE_MISSING; - } - if (states.contains("inactive")) { - res |= IPluginList::STATE_INACTIVE; - } - if (states.contains("active")) { - res |= IPluginList::STATE_ACTIVE; - } - return res; - }); - - // py::class_("IPluginList", py::no_init) - // .def("state", &MOBase::IPluginList::state, py::arg("name")) - // .def("priority", &MOBase::IPluginList::priority, py::arg("name")) - // .def("setPriority", &MOBase::IPluginList::setPriority, (py::arg("name"), "priority")) - // .def("loadOrder", &MOBase::IPluginList::loadOrder, py::arg("name")) - // .def("isMaster", &MOBase::IPluginList::isMaster, py::arg("name")) - // .def("masters", &MOBase::IPluginList::masters, py::arg("name")) - // .def("origin", &MOBase::IPluginList::origin, py::arg("name")) - // .def("onRefreshed", &MOBase::IPluginList::onRefreshed, py::arg("callback")) - // .def("onPluginMoved", &MOBase::IPluginList::onPluginMoved, py::arg("callback")) - - // // Kept but deprecated for backward compatibility: - // .def("onPluginStateChanged", +[](IPluginList* modList, const std::function& fn) { - // utils::show_deprecation_warning("onPluginStateChanged", - // "onPluginStateChanged(Callable[[str, IPluginList.PluginStates], None]) is deprecated, " - // "use onPluginStateChanged(Callable[[Dict[str, IPluginList.PluginStates], None]) instead."); - // return modList->onPluginStateChanged([fn](auto const& map) { - // for (const auto& entry : map) { - // fn(entry.first, entry.second); - // } - // }); - // }, py::arg("callback")) - // .def("onPluginStateChanged", &MOBase::IPluginList::onPluginStateChanged, py::arg("callback")) - // .def("pluginNames", &MOBase::IPluginList::pluginNames) - // .def("setState", &MOBase::IPluginList::setState, (py::arg("name"), "state")) - // .def("setLoadOrder", &MOBase::IPluginList::setLoadOrder, py::arg("loadorder")) - // ; - - // py::enum_("ModState") - // .value("exists", IModList::STATE_EXISTS) - // .value("active", IModList::STATE_ACTIVE) - // .value("essential", IModList::STATE_ESSENTIAL) - // .value("empty", IModList::STATE_EMPTY) - // .value("endorsed", IModList::STATE_ENDORSED) - // .value("valid", IModList::STATE_VALID) - // .value("alternate", IModList::STATE_ALTERNATE) - - // .value("EXISTS", IModList::STATE_EXISTS) - // .value("ACTIVE", IModList::STATE_ACTIVE) - // .value("ESSENTIAL", IModList::STATE_ESSENTIAL) - // .value("EMPTY", IModList::STATE_EMPTY) - // .value("ENDORSED", IModList::STATE_ENDORSED) - // .value("VALID", IModList::STATE_VALID) - // .value("ALTERNATE", IModList::STATE_ALTERNATE) - // ; - - // py::class_("IModList", py::no_init) - // .def("displayName", &MOBase::IModList::displayName, py::arg("name")) - // .def("allMods", &MOBase::IModList::allMods) - // .def("allModsByProfilePriority", &MOBase::IModList::allModsByProfilePriority, py::arg("profile") = py::ptr((IProfile*)nullptr)) - - // .def("getMod", &MOBase::IModList::getMod, py::return_value_policy(), py::arg("name")) - // .def("removeMod", &MOBase::IModList::removeMod, py::arg("mod")) - // .def("renameMod", &MOBase::IModList::renameMod, py::return_value_policy(), (py::arg("mod"), py::arg("name"))) - - // .def("state", &MOBase::IModList::state, py::arg("name")) - // .def("setActive", - // static_cast(&MOBase::IModList::setActive), (py::arg("names"), "active")) - // .def("setActive", - // static_cast(&MOBase::IModList::setActive), (py::arg("name"), "active")) - // .def("priority", &MOBase::IModList::priority, py::arg("name")) - // .def("setPriority", &MOBase::IModList::setPriority, (py::arg("name"), "priority")) - - // // Kept but deprecated for backward compatibility: - // .def("onModStateChanged", +[](IModList* modList, const std::function& fn) { - // utils::show_deprecation_warning("onModStateChanged", - // "onModStateChanged(Callable[[str, IModList.ModStates], None]) is deprecated, " - // "use onModStateChanged(Callable[[Dict[str, IModList.ModStates], None]) instead."); - // return modList->onModStateChanged([fn](auto const& map) { - // for (const auto& entry : map) { - // fn(entry.first, entry.second); - // } - // }); - // }, py::arg("callback")) - - // .def("onModInstalled", &MOBase::IModList::onModInstalled, py::arg("callback")) - // .def("onModRemoved", &MOBase::IModList::onModRemoved, py::arg("callback")) - // .def("onModStateChanged", &MOBase::IModList::onModStateChanged, py::arg("callback")) - // .def("onModMoved", &MOBase::IModList::onModMoved, py::arg("callback")) - // ; - - // // Note: localizedName, master, requirements and enabledByDefault have to go in all the plugin wrappers declaration, - // // since the default functions are specific to each wrapper, otherwise in turns into an - // // infinite recursion mess. - // py::class_("IPlugin") - // .def("init", py::pure_virtual(&MOBase::IPlugin::init), py::arg("organizer")) - // .def("name", py::pure_virtual(&MOBase::IPlugin::name)) - // .def("localizedName", &MOBase::IPlugin::localizedName, &IPluginWrapper::localizedName_Default) - // .def("master", &MOBase::IPlugin::master, &IPluginWrapper::master_Default) - // .def("author", py::pure_virtual(&MOBase::IPlugin::author)) - // .def("description", py::pure_virtual(&MOBase::IPlugin::description)) - // .def("version", py::pure_virtual(&MOBase::IPlugin::version)) - // .def("requirements", &MOBase::IPlugin::requirements, &IPluginWrapper::requirements_Default) - // .def("settings", py::pure_virtual(&MOBase::IPlugin::settings)) - // .def("enabledByDefault", &MOBase::IPlugin::enabledByDefault, &IPluginWrapper::enabledByDefault_Default) - // ; - - // py::class_, boost::noncopyable>("IPluginDiagnose") - // .def("localizedName", &MOBase::IPlugin::localizedName, &IPluginDiagnoseWrapper::localizedName_Default) - // .def("master", &MOBase::IPlugin::master, &IPluginDiagnoseWrapper::master_Default) - // .def("requirements", &MOBase::IPlugin::requirements, &IPluginDiagnoseWrapper::requirements_Default) - // .def("enabledByDefault", &MOBase::IPlugin::enabledByDefault, &IPluginDiagnoseWrapper::enabledByDefault_Default) - - // .def("activeProblems", py::pure_virtual(&MOBase::IPluginDiagnose::activeProblems)) - // .def("shortDescription", py::pure_virtual(&MOBase::IPluginDiagnose::shortDescription), py::arg("key")) - // .def("fullDescription", py::pure_virtual(&MOBase::IPluginDiagnose::fullDescription), py::arg("key")) - // .def("hasGuidedFix", py::pure_virtual(&MOBase::IPluginDiagnose::hasGuidedFix), py::arg("key")) - // .def("startGuidedFix", py::pure_virtual(&MOBase::IPluginDiagnose::startGuidedFix), py::arg("key")) - // .def("_invalidate", &IPluginDiagnoseWrapper::invalidate) - // ; - - // py::class_("Mapping", py::init<>()) - // .def("__init__", py::make_constructor(+[](QString src, QString dst, bool dir, bool crt) -> Mapping* { - // return new Mapping{ src, dst, dir, crt }; - // }, py::default_call_policies(), - // (py::arg("source"), py::arg("destination"), py::arg("is_directory"), py::arg("create_target") = false))) - // .def_readwrite("source", &Mapping::source) - // .def_readwrite("destination", &Mapping::destination) - // .def_readwrite("isDirectory", &Mapping::isDirectory) - // .def_readwrite("createTarget", &Mapping::createTarget) - // .def("__str__", +[](Mapping * m) { - // return fmt::format(L"Mapping({}, {}, {}, {})", m->source.toStdWString(), m->destination.toStdWString(), m->isDirectory, m->createTarget); - // }) - // ; - - // py::class_, boost::noncopyable>("IPluginFileMapper") - // .def("localizedName", &MOBase::IPlugin::localizedName, &IPluginFileMapperWrapper::localizedName_Default) - // .def("master", &MOBase::IPlugin::master, &IPluginFileMapperWrapper::master_Default) - // .def("requirements", &MOBase::IPlugin::requirements, &IPluginFileMapperWrapper::requirements_Default) - // .def("enabledByDefault", &MOBase::IPlugin::enabledByDefault, &IPluginFileMapperWrapper::enabledByDefault_Default) - - // .def("mappings", py::pure_virtual(&MOBase::IPluginFileMapper::mappings)) - // ; - - // py::enum_("LoadOrderMechanism") - // .value("FileTime", MOBase::IPluginGame::LoadOrderMechanism::FileTime) - // .value("PluginsTxt", MOBase::IPluginGame::LoadOrderMechanism::PluginsTxt) - - // .value("FILE_TIME", MOBase::IPluginGame::LoadOrderMechanism::FileTime) - // .value("PLUGINS_TXT", MOBase::IPluginGame::LoadOrderMechanism::PluginsTxt) - // ; - - // py::enum_("SortMechanism") - // .value("NONE", MOBase::IPluginGame::SortMechanism::NONE) - // .value("MLOX", MOBase::IPluginGame::SortMechanism::MLOX) - // .value("BOSS", MOBase::IPluginGame::SortMechanism::BOSS) - // .value("LOOT", MOBase::IPluginGame::SortMechanism::LOOT) - // ; - - // // This doesn't actually do the conversion, but might be convenient for accessing the names for enum bits - // py::enum_("ProfileSetting") - // .value("mods", MOBase::IPluginGame::MODS) - // .value("configuration", MOBase::IPluginGame::CONFIGURATION) - // .value("savegames", MOBase::IPluginGame::SAVEGAMES) - // .value("preferDefaults", MOBase::IPluginGame::PREFER_DEFAULTS) - - // .value("MODS", MOBase::IPluginGame::MODS) - // .value("CONFIGURATION", MOBase::IPluginGame::CONFIGURATION) - // .value("SAVEGAMES", MOBase::IPluginGame::SAVEGAMES) - // .value("PREFER_DEFAULTS", MOBase::IPluginGame::PREFER_DEFAULTS) - // ; - - // py::class_, boost::noncopyable>("IPluginGame") - // .def("localizedName", &MOBase::IPlugin::localizedName, &IPluginGameWrapper::localizedName_Default) - // .def("master", &MOBase::IPlugin::master, &IPluginGameWrapper::master_Default) - - // .def("detectGame", py::pure_virtual(&MOBase::IPluginGame::detectGame)) - // .def("gameName", py::pure_virtual(&MOBase::IPluginGame::gameName)) - // .def("initializeProfile", py::pure_virtual(&MOBase::IPluginGame::initializeProfile), (py::arg("directory"), "settings")) - // .def("listSaves", py::pure_virtual(&MOBase::IPluginGame::listSaves), py::arg("folder")) - // .def("isInstalled", py::pure_virtual(&MOBase::IPluginGame::isInstalled)) - // .def("gameIcon", py::pure_virtual(&MOBase::IPluginGame::gameIcon)) - // .def("gameDirectory", py::pure_virtual(&MOBase::IPluginGame::gameDirectory)) - // .def("dataDirectory", py::pure_virtual(&MOBase::IPluginGame::dataDirectory)) - // .def("setGamePath", py::pure_virtual(&MOBase::IPluginGame::setGamePath), py::arg("path")) - // .def("documentsDirectory", py::pure_virtual(&MOBase::IPluginGame::documentsDirectory)) - // .def("savesDirectory", py::pure_virtual(&MOBase::IPluginGame::savesDirectory)) - // .def("executables", py::pure_virtual(&MOBase::IPluginGame::executables)) - // .def("executableForcedLoads", py::pure_virtual(&MOBase::IPluginGame::executableForcedLoads)) - // .def("steamAPPId", py::pure_virtual(&MOBase::IPluginGame::steamAPPId)) - // .def("primaryPlugins", py::pure_virtual(&MOBase::IPluginGame::primaryPlugins)) - // .def("gameVariants", py::pure_virtual(&MOBase::IPluginGame::gameVariants)) - // .def("setGameVariant", py::pure_virtual(&MOBase::IPluginGame::setGameVariant), py::arg("variant")) - // .def("binaryName", py::pure_virtual(&MOBase::IPluginGame::binaryName)) - // .def("gameShortName", py::pure_virtual(&MOBase::IPluginGame::gameShortName)) - // .def("primarySources", py::pure_virtual(&MOBase::IPluginGame::primarySources)) - // .def("validShortNames", py::pure_virtual(&MOBase::IPluginGame::validShortNames)) - // .def("gameNexusName", py::pure_virtual(&MOBase::IPluginGame::gameNexusName)) - // .def("iniFiles", py::pure_virtual(&MOBase::IPluginGame::iniFiles)) - // .def("DLCPlugins", py::pure_virtual(&MOBase::IPluginGame::DLCPlugins)) - // .def("CCPlugins", py::pure_virtual(&MOBase::IPluginGame::CCPlugins)) - // .def("loadOrderMechanism", py::pure_virtual(&MOBase::IPluginGame::loadOrderMechanism)) - // .def("sortMechanism", py::pure_virtual(&MOBase::IPluginGame::sortMechanism)) - // .def("nexusModOrganizerID", py::pure_virtual(&MOBase::IPluginGame::nexusModOrganizerID)) - // .def("nexusGameID", py::pure_virtual(&MOBase::IPluginGame::nexusGameID)) - // .def("looksValid", py::pure_virtual(&MOBase::IPluginGame::looksValid), py::arg("directory")) - // .def("gameVersion", py::pure_virtual(&MOBase::IPluginGame::gameVersion)) - // .def("getLauncherName", py::pure_virtual(&MOBase::IPluginGame::getLauncherName)) - - // .def("featureList", +[](MOBase::IPluginGame* p) { - // // Constructing a dict from class name to actual object: - // py::dict dict; - // mp11::mp_for_each< - // // Must user pointers because mp_for_each construct object: - // mp11::mp_transform - // >([&](auto* pt) { - // using T = std::remove_pointer_t; - // typename py::reference_existing_object::apply::type converter; - - // // Retrieve the python class object: - // const py::converter::registration* registration = py::converter::registry::query(py::type_id()); - // py::object key = py::object(py::handle<>(py::borrowed(registration->get_class_object()))); - - // // Set the object: - // dict[key] = py::handle<>(converter(p->feature())); - // }); - // return dict; - // }) - - // .def("feature", +[](MOBase::IPluginGame* p, py::object clsObj) { - // py::object feature; - // mp11::mp_for_each< - // // Must user pointers because mp_for_each construct object: - // mp11::mp_transform - // >([&](auto* pt) { - // using T = std::remove_pointer_t; - // typename py::reference_existing_object::apply::type converter; - - // // Retrieve the python class object: - // const py::converter::registration* registration = py::converter::registry::query(py::type_id()); - - // if (clsObj.ptr() == (PyObject*) registration->get_class_object()) { - // feature = py::object(py::handle<>(converter(p->feature()))); - // } - // }); - // return feature; - // }, py::arg("feature_type")) - // ; - - // py::enum_("InstallResult") - // .value("SUCCESS", MOBase::IPluginInstaller::RESULT_SUCCESS) - // .value("FAILED", MOBase::IPluginInstaller::RESULT_FAILED) - // .value("CANCELED", MOBase::IPluginInstaller::RESULT_CANCELED) - // .value("MANUAL_REQUESTED", MOBase::IPluginInstaller::RESULT_MANUALREQUESTED) - // .value("NOT_ATTEMPTED", MOBase::IPluginInstaller::RESULT_NOTATTEMPTED) - // ; - - // py::class_, boost::noncopyable>("IPluginInstaller", py::no_init) - // .def("isArchiveSupported", &IPluginInstaller::isArchiveSupported, py::arg("tree")) - // .def("priority", &IPluginInstaller::priority) - // .def("onInstallationStart", &IPluginInstaller::onInstallationStart, (py::arg("archive"), py::arg("reinstallation"), py::arg("current_mod"))) - // .def("onInstallationEnd", &IPluginInstaller::onInstallationEnd, (py::arg("result"), py::arg("new_mod"))) - // .def("isManualInstaller", &IPluginInstaller::isManualInstaller) - // .def("setParentWidget", &IPluginInstaller::setParentWidget, py::arg("parent")) - // .def("setInstallationManager", &IPluginInstaller::setInstallationManager, py::arg("manager")) - // ; - - // py::class_, boost::noncopyable>("IPluginInstallerSimple") - // .def("onInstallationStart", &IPluginInstaller::onInstallationStart, (py::arg("archive"), py::arg("reinstallation"), py::arg("current_mod"))) - // .def("onInstallationEnd", &IPluginInstaller::onInstallationEnd, (py::arg("result"), py::arg("new_mod"))) - // .def("localizedName", &MOBase::IPlugin::localizedName, &IPluginInstallerSimpleWrapper::localizedName_Default) - // .def("master", &MOBase::IPlugin::master, &IPluginInstallerSimpleWrapper::master_Default) - // .def("requirements", &MOBase::IPlugin::requirements, &IPluginInstallerSimpleWrapper::requirements_Default) - // .def("enabledByDefault", &MOBase::IPlugin::enabledByDefault, &IPluginInstallerSimpleWrapper::enabledByDefault_Default) - - // // Note: Keeping the variant here even if we always return a tuple to be consistent with the wrapper and - // // have proper stubs generation. - // .def("install", +[](IPluginInstallerSimple* p, GuessedValue& modName, std::shared_ptr& tree, QString& version, int& nexusID) - // -> std::variant, std::tuple, QString, int>> { - // auto result = p->install(modName, tree, version, nexusID); - // return std::make_tuple(result, tree, version, nexusID); - // }, (py::arg("name"), "tree", "version", "nexus_id")) - // .def("_parentWidget", &IPluginInstallerSimpleWrapper::parentWidget, py::return_value_policy()) - // .def("_manager", &IPluginInstallerSimpleWrapper::manager, py::return_value_policy()) - // ; - - // py::class_, boost::noncopyable>("IPluginInstallerCustom") - // .def("onInstallationStart", &IPluginInstaller::onInstallationStart, (py::arg("archive"), py::arg("reinstallation"), py::arg("current_mod"))) - // .def("onInstallationEnd", &IPluginInstaller::onInstallationEnd, (py::arg("result"), py::arg("new_mod"))) - // .def("localizedName", &MOBase::IPlugin::localizedName, &IPluginInstallerCustomWrapper::localizedName_Default) - // .def("master", &MOBase::IPlugin::master, &IPluginInstallerCustomWrapper::master_Default) - // .def("requirements", &MOBase::IPlugin::requirements, &IPluginInstallerCustomWrapper::requirements_Default) - // .def("enabledByDefault", &MOBase::IPlugin::enabledByDefault, &IPluginInstallerCustomWrapper::enabledByDefault_Default) - - // // Needs to add both otherwize boost does not understand: - // .def("isArchiveSupported", &IPluginInstaller::isArchiveSupported, py::arg("tree")) - // .def("isArchiveSupported", &IPluginInstallerCustom::isArchiveSupported, py::arg("archive_name")) - // .def("supportedExtensions", &IPluginInstallerCustom::supportedExtensions) - // .def("install", &IPluginInstallerCustom::install, (py::arg("mod_name"), "game_name", "archive_name", "version", "nexus_id")) - // .def("_parentWidget", &IPluginInstallerSimpleWrapper::parentWidget, py::return_value_policy()) - // .def("_manager", &IPluginInstallerCustomWrapper::manager, py::return_value_policy()) - // ; - - // py::class_, boost::noncopyable>("IPluginModPage") - // .def("localizedName", &MOBase::IPlugin::localizedName, &IPluginModPageWrapper::localizedName_Default) - // .def("master", &MOBase::IPlugin::master, &IPluginModPageWrapper::master_Default) - // .def("requirements", &MOBase::IPlugin::requirements, &IPluginModPageWrapper::requirements_Default) - // .def("enabledByDefault", &MOBase::IPlugin::enabledByDefault, &IPluginModPageWrapper::enabledByDefault_Default) - - // .def("displayName", py::pure_virtual(&IPluginModPage::displayName)) - // .def("icon", py::pure_virtual(&IPluginModPage::icon)) - // .def("pageURL", py::pure_virtual(&IPluginModPage::pageURL)) - // .def("useIntegratedBrowser", py::pure_virtual(&IPluginModPage::useIntegratedBrowser)) - // .def("handlesDownload", py::pure_virtual(&IPluginModPage::handlesDownload), (py::arg("page_url"), "download_url", "fileinfo")) - // .def("setParentWidget", &IPluginModPage::setParentWidget, &IPluginModPageWrapper::setParentWidget_Default, py::arg("parent")) - // .def("_parentWidget", &IPluginModPageWrapper::parentWidget, py::return_value_policy()) - // ; - - // py::class_, boost::noncopyable>("IPluginPreview") - // .def("localizedName", &MOBase::IPlugin::localizedName, &IPluginPreviewWrapper::localizedName_Default) - // .def("master", &MOBase::IPlugin::master, &IPluginPreviewWrapper::master_Default) - // .def("requirements", &MOBase::IPlugin::requirements, &IPluginPreviewWrapper::requirements_Default) - // .def("enabledByDefault", &MOBase::IPlugin::enabledByDefault, &IPluginPreviewWrapper::enabledByDefault_Default) - - // .def("supportedExtensions", py::pure_virtual(&IPluginPreview::supportedExtensions)) - // .def("genFilePreview", py::pure_virtual(&IPluginPreview::genFilePreview), py::return_value_policy(), - // (py::arg("filename"), "max_size")) - // ; - - // py::class_, boost::noncopyable>("IPluginTool") - // .def("localizedName", &MOBase::IPlugin::localizedName, &IPluginToolWrapper::localizedName_Default) - // .def("master", &MOBase::IPlugin::master, &IPluginToolWrapper::master_Default) - // .def("requirements", &MOBase::IPlugin::requirements, &IPluginToolWrapper::requirements_Default) - // .def("enabledByDefault", &MOBase::IPlugin::enabledByDefault, &IPluginToolWrapper::enabledByDefault_Default) - - // .def("displayName", py::pure_virtual(&IPluginTool::displayName)) - // .def("tooltip", py::pure_virtual(&IPluginTool::tooltip)) - // .def("icon", py::pure_virtual(&IPluginTool::icon)) - // .def("display", py::pure_virtual(&IPluginTool::display)) - // .def("setParentWidget", &IPluginTool::setParentWidget, &IPluginToolWrapper::setParentWidget_Default, py::arg("parent")) - // .def("_parentWidget", &IPluginToolWrapper::parentWidget, py::return_value_policy()) - // ; - - // registerGameFeaturesPythonConverters(); - - m.def("getFileVersion", &MOBase::getFileVersion, py::arg("filepath")); - m.def("getProductVersion", &MOBase::getProductVersion, py::arg("executable")); - m.def("getIconForExecutable", &MOBase::iconForExecutable, py::arg("executable")); - - // py::object widgets(py::borrowed(PyImport_AddModule("mobase.widgets"))); - // py::scope().attr("widgets") = widgets; - // { - // py::scope w_ = widgets; - // register_widgets(); - // } - - // Expose MoVariant: MoVariant is a fake object whose only purpose is to be used as a type-hint - // on the python side (e.g., def foo(x: mobase.MoVariant)). The real MoVariant is defined in the - // generated stubs, since it's only relevant when doing type-checking, but this needs to be defined, - // otherwise MoVariant is not found when actually running plugins through MO2, making them crash. - m.attr("MoVariant") = py::none(); + using namespace mo2::python; + + py::module_::import("PyQt6.QtCore"); + py::module_::import("PyQt6.QtWidgets"); + + py::detail::type_caster t1; + py::detail::type_caster t2; + + m.def("testQStringList", [](QStringList const& list) { + QStringList res = list; + for (QString& value : res) { + value = value + "_CPP"; + } + return res; + }); + + m.def("testQMap1", [](QMap const& map) { + QMap res; + for (auto it = map.begin(); it != map.end(); ++it) { + res[it.key()] = it.value().size(); + } + return res; + }); + + m.def("testQMap2", [](QMap const& map) { + QMap res; + for (auto it = map.begin(); it != map.end(); ++it) { + res[it.key()] = QString::number(it.value()); + } + return res; + }); + + m.def("testDateTime1", []() { + return QDateTime::fromString("2022-02-15T12:33:45", Qt::ISODate); + }); + + m.def("testDateTime2", [](QDateTime const& datetime) { + return datetime.toString(); + }); + + m.def("testEnum0", []() { + return Qt::GlobalColor::darkRed; + }); + + m.def("testEnum", [](Qt::GlobalColor color) { + MOBase::log::error("In C++: {}", color); + return py::make_tuple(color, QMessageBox::Icon::Information); + }); + + m.def("testPixmap", [](QPixmap const& pixmap) { + return pixmap.size(); + }); + + m.def("createSaveGame", []() -> ISaveGame* { + class SaveGame : public ISaveGame { + QString getFilepath() const override { return "filepath"; } + QDateTime getCreationTime() const override + { + return QDateTime::fromString("2022-02-15T12:33:45", + Qt::ISODate); + } + QString getName() const override { return "name"; } + QString getSaveGroupIdentifier() const override { return "group"; } + QStringList allFiles() const override { return {"file1", "file2"}; } + }; + return new SaveGame(); + }); + + m.def("testSaveGameRef", [](const ISaveGame& game) { + std::cout << "getFilepath(): " << game.getFilepath().toStdString() + << "\n"; + std::cout << "getCreationTime(): " + << game.getCreationTime().toString().toStdString() << "\n"; + std::cout << "getName(): " << game.getName().toStdString() << "\n"; + std::cout << "getSaveGroupIdentifier(): " + << game.getSaveGroupIdentifier().toStdString() << "\n"; + std::cout << "allFiles(): [ " << game.allFiles().join(" ").toStdString() + << " ]\n"; + }); + + m.def("testSaveGamePtr", [](const ISaveGame* game) { + std::cout << "getFilepath(): " << game->getFilepath().toStdString() + << "\n"; + std::cout << "getCreationTime(): " + << game->getCreationTime().toString().toStdString() << "\n"; + std::cout << "getName(): " << game->getName().toStdString() << "\n"; + std::cout << "getSaveGroupIdentifier(): " + << game->getSaveGroupIdentifier().toStdString() << "\n"; + std::cout << "allFiles(): [ " + << game->allFiles().join(" ").toStdString() << " ]\n"; + }); + + m.def("testWidget1", [](py::object o) { + auto* w = o.cast(); + if (w) { + std::cout << "background role: " << w->backgroundRole() << "\n"; + } + else { + std::cout << "null widget\n"; + } + }); + + m.def("testWidget2", []() { + QWidget* w = new QWidget(); + w->setBackgroundRole(QPalette::ColorRole::HighlightedText); + return w; + }); + + // + // Class declarations: + // + + py::enum_(m, "ReleaseType") + .value("final", MOBase::VersionInfo::RELEASE_FINAL) + .value("candidate", MOBase::VersionInfo::RELEASE_CANDIDATE) + .value("beta", MOBase::VersionInfo::RELEASE_BETA) + .value("alpha", MOBase::VersionInfo::RELEASE_ALPHA) + .value("prealpha", MOBase::VersionInfo::RELEASE_PREALPHA) + + .value("FINAL", MOBase::VersionInfo::RELEASE_FINAL) + .value("CANDIDATE", MOBase::VersionInfo::RELEASE_CANDIDATE) + .value("BETA", MOBase::VersionInfo::RELEASE_BETA) + .value("ALPHA", MOBase::VersionInfo::RELEASE_ALPHA) + .value("PRE_ALPHA", MOBase::VersionInfo::RELEASE_PREALPHA); + + py::enum_(m, "VersionScheme") + .value("discover", MOBase::VersionInfo::SCHEME_DISCOVER) + .value("regular", MOBase::VersionInfo::SCHEME_REGULAR) + .value("decimalmark", MOBase::VersionInfo::SCHEME_DECIMALMARK) + .value("numbersandletters", + MOBase::VersionInfo::SCHEME_NUMBERSANDLETTERS) + .value("date", MOBase::VersionInfo::SCHEME_DATE) + .value("literal", MOBase::VersionInfo::SCHEME_LITERAL) + + .value("DISCOVER", MOBase::VersionInfo::SCHEME_DISCOVER) + .value("REGULAR", MOBase::VersionInfo::SCHEME_REGULAR) + .value("DECIMAL_MARK", MOBase::VersionInfo::SCHEME_DECIMALMARK) + .value("NUMBERS_AND_LETTERS", + MOBase::VersionInfo::SCHEME_NUMBERSANDLETTERS) + .value("DATE", MOBase::VersionInfo::SCHEME_DATE) + .value("LITERAL", MOBase::VersionInfo::SCHEME_LITERAL); + + py::class_(m, "VersionInfo") + .def(py::init(), py::arg("value"), + py::arg("scheme") = VersionInfo::SCHEME_DISCOVER) + // note: order of the two init<> below is important because + // ReleaseType is a simple enum with an implicit int conversion. + .def(py::init(), + py::arg("major"), py::arg("minor"), py::arg("subminor"), + py::arg("subsubminor"), + py::arg("release_type") = VersionInfo::RELEASE_FINAL) + .def(py::init(), + py::arg("major"), py::arg("minor"), py::arg("subminor"), + py::arg("release_type") = VersionInfo::RELEASE_FINAL) + .def("clear", &VersionInfo::clear) + .def("parse", &VersionInfo::parse, py::arg("value"), + py::arg("scheme") = VersionInfo::SCHEME_DISCOVER, + py::arg("is_manual") = false) + .def("canonicalString", &VersionInfo::canonicalString) + .def("displayString", &VersionInfo::displayString, + py::arg("forced_segments") = 2) + .def("isValid", &VersionInfo::isValid) + .def("scheme", &VersionInfo::scheme) + .def("__str__", &VersionInfo::canonicalString) + .def(py::self < py::self) + .def(py::self > py::self) + .def(py::self <= py::self) + .def(py::self >= py::self) + .def(py::self != py::self) + .def(py::self == py::self); + + py::class_(m, "PluginSetting") + .def(py::init(), + py::arg("key"), py::arg("description"), py::arg("default_value")) + .def_readwrite("key", &PluginSetting::key) + .def_readwrite("description", &PluginSetting::description) + .def_readwrite("default_value", &PluginSetting::defaultValue); + + py::class_(m, "ExecutableInfo") + .def(py::init(), py::arg("title"), + py::arg("binary")) + .def("withArgument", &ExecutableInfo::withArgument, py::arg("argument")) + .def("withWorkingDirectory", &ExecutableInfo::withWorkingDirectory, + py::arg("directory")) + .def("withSteamAppId", &ExecutableInfo::withSteamAppId, + py::arg("app_id")) + .def("asCustom", &ExecutableInfo::asCustom) + .def("isValid", &ExecutableInfo::isValid) + .def("title", &ExecutableInfo::title) + .def("binary", &ExecutableInfo::binary) + .def("arguments", &ExecutableInfo::arguments) + .def("workingDirectory", &ExecutableInfo::workingDirectory) + .def("steamAppID", &ExecutableInfo::steamAppID) + .def("isCustom", &ExecutableInfo::isCustom); + + py::class_(m, "ExecutableForcedLoadSetting") + .def(py::init(), py::arg("process"), + py::arg("library")) + .def("withForced", &ExecutableForcedLoadSetting::withForced, + py::arg("forced")) + .def("withEnabled", &ExecutableForcedLoadSetting::withEnabled, + py::arg("enabled")) + .def("enabled", &ExecutableForcedLoadSetting::enabled) + .def("forced", &ExecutableForcedLoadSetting::forced) + .def("library", &ExecutableForcedLoadSetting::library) + .def("process", &ExecutableForcedLoadSetting::process); + + py::class_(m, "ISaveGame") + .def(py::init<>()) + .def("getFilepath", &ISaveGame::getFilepath) + .def("getCreationTime", &ISaveGame::getCreationTime) + .def("getName", &ISaveGame::getName) + .def("getSaveGroupIdentifier", &ISaveGame::getSaveGroupIdentifier) + .def("allFiles", &ISaveGame::allFiles); + + py::class_ iSaveGameInfoWidget( + m, "ISaveGameInfoWidget"); + iSaveGameInfoWidget.def(py::init<>()) + .def(py::init(), py::arg("parent")) + .def(py::init([](py::none const&) { + return new PySaveGameInfoWidget(); + })) + .def("setSave", &ISaveGameInfoWidget::setSave, py::arg("save")); + add_qt_delegate(iSaveGameInfoWidget, "widget"); + + // // Plugin requirements: + // auto iPluginRequirementClass = py::class_< + // IPluginRequirementWrapper, py::bases<>, + // boost::noncopyable>("IPluginRequirement"); + // { + // py::scope scope = iPluginRequirementClass; + + // py::class_("Problem", + // py::init((py::arg("short_description"), + // py::arg("long_description") = ""))) .def("shortDescription", + // &IPluginRequirement::Problem::shortDescription) + // .def("longDescription", + // &IPluginRequirement::Problem::longDescription); + + // iPluginRequirementClass + // .def("check", py::pure_virtual(&IPluginRequirement::check), + // py::arg("organizer")) + // ; + // } + + // py::class_("PluginRequirementFactory") + // // pluginDependency + // .def("pluginDependency", +[](QStringList const& pluginNames) { + // return PluginRequirementFactory::pluginDependency(pluginNames); + // }, py::arg("plugins")) + // .def("pluginDependency", +[](QString const& pluginName) { + // return PluginRequirementFactory::pluginDependency(pluginName); + // }, py::arg("plugin")) + // .staticmethod("pluginDependency") + // // gameDependency + // .def("gameDependency", +[](QStringList const& gameNames) { + // return PluginRequirementFactory::gameDependency(gameNames); + // }, py::arg("games")) + // .def("gameDependency", +[](QString const& gameNames) { + // return PluginRequirementFactory::gameDependency(gameNames); + // }, py::arg("game")) + // .staticmethod("gameDependency") + // // diagnose + // .def("diagnose", &PluginRequirementFactory::diagnose, + // py::arg("diagnose")) .staticmethod("diagnose") + // // basic + // .def("basic", &PluginRequirementFactory::basic, (py::arg("checker"), + // "description")) .staticmethod("basic"); + + // py::class_("FileInfo", py::init<>()) + // .add_property("filePath", + // +[](const IOrganizer::FileInfo& info) { return info.filePath; }, + // +[](IOrganizer::FileInfo& info, QString value) { info.filePath = + // value; + // }) + // .add_property("archive", + // +[](const IOrganizer::FileInfo& info) { return info.archive; }, + // +[](IOrganizer::FileInfo& info, QString value) { info.archive = + // value; + // }) + // .add_property("origins", + // +[](const IOrganizer::FileInfo& info) { return info.origins; }, + // +[](IOrganizer::FileInfo& info, QStringList value) { info.origins = + // value; }) + // ; + + // py::class_("IOrganizer", py::no_init) + // .def("createNexusBridge", &IOrganizer::createNexusBridge, + // py::return_value_policy()) + // .def("profileName", &IOrganizer::profileName) + // .def("profilePath", &IOrganizer::profilePath) + // .def("downloadsPath", &IOrganizer::downloadsPath) + // .def("overwritePath", &IOrganizer::overwritePath) + // .def("basePath", &IOrganizer::basePath) + // .def("modsPath", &IOrganizer::modsPath) + // .def("appVersion", &IOrganizer::appVersion) + // .def("createMod", &IOrganizer::createMod, + // py::return_value_policy(), + // py::arg("name")) .def("getGame", &IOrganizer::getGame, + // py::return_value_policy(), + // py::arg("name")) .def("modDataChanged", &IOrganizer::modDataChanged, + // py::arg("mod")) .def("isPluginEnabled", +[](IOrganizer* o, IPlugin* + // plugin) { return o->isPluginEnabled(plugin); }, py::arg("plugin")) + // .def("isPluginEnabled", +[](IOrganizer* o, QString const& plugin) { + // return o->isPluginEnabled(plugin); }, py::arg("plugin")) + // .def("pluginSetting", &IOrganizer::pluginSetting, + // (py::arg("plugin_name"), "key")) .def("setPluginSetting", + // &IOrganizer::setPluginSetting, (py::arg("plugin_name"), "key", + // "value")) .def("persistent", &IOrganizer::persistent, + // (py::arg("plugin_name"), "key", py::arg("default") = QVariant())) + // .def("setPersistent", &IOrganizer::setPersistent, + // (py::arg("plugin_name"), "key", "value", py::arg("sync") = true)) + // .def("pluginDataPath", &IOrganizer::pluginDataPath) + // .def("installMod", &IOrganizer::installMod, + // py::return_value_policy(), + // (py::arg("filename"), py::arg("name_suggestion") = "")) + // .def("resolvePath", &IOrganizer::resolvePath, py::arg("filename")) + // .def("listDirectories", &IOrganizer::listDirectories, + // py::arg("directory")) + + // // Provide multiple overloads of findFiles: + // .def("findFiles", +[](const IOrganizer* o, QString const& p, + // std::function f) { return o->findFiles(p, f); + // }, + // (py::arg("path"), "filter")) + + // // In C++, it is possible to create a QStringList implicitly from a + // single QString. This is not possible with the current + // // converters in python (and I do not think it is a good idea to have + // it everywhere), but here it is nice to be able to + // // pass a single string, so we add an extra overload. + // // Important: the order matters, because a Python string can be + // converted to a QStringList since it is a sequence of + // // single-character strings: + // .def("findFiles", +[](const IOrganizer* o, QString const& p, const + // QStringList& gf) { return o->findFiles(p, gf); }, + // (py::arg("path"), "patterns")) + // .def("findFiles", +[](const IOrganizer* o, QString const& p, const + // QString& f) { return o->findFiles(p, QStringList{ f }); }, + // (py::arg("path"), "pattern")) + + // .def("getFileOrigins", &IOrganizer::getFileOrigins, + // py::arg("filename")) .def("findFileInfos", + // &IOrganizer::findFileInfos, (py::arg("path"), "filter")) + + // .def("virtualFileTree", &IOrganizer::virtualFileTree) + + // .def("downloadManager", &IOrganizer::downloadManager, + // py::return_value_policy()) + // .def("pluginList", &IOrganizer::pluginList, + // py::return_value_policy()) + // .def("modList", &IOrganizer::modList, + // py::return_value_policy()) + // .def("profile", &IOrganizer::profile, + // py::return_value_policy()) + + // // Custom implementation for startApplication and waitForApplication + // because 1) HANDLE (= void*) is not properly + // // converted from/to python, and 2) we need to convert the by-ptr + // argument to a return-tuple for waitForApplication: + // .def("startApplication", + // +[](IOrganizer* o, const QString& executable, const QStringList& + // args, const QString& cwd, const QString& profile, + // const QString& forcedCustomOverwrite, bool ignoreCustomOverwrite) + // { + // return (std::uintptr_t) o->startApplication(executable, args, + // cwd, profile, forcedCustomOverwrite, ignoreCustomOverwrite); + // }, (py::arg("executable"), (py::arg("args") = QStringList()), + // (py::arg("cwd") = ""), (py::arg("profile") = ""), + // (py::arg("forcedCustomOverwrite") = ""), + // (py::arg("ignoreCustomOverwrite") = false)), + // py::return_value_policy()) + // .def("waitForApplication", +[](IOrganizer *o, std::uintptr_t handle, + // bool refresh) { + // DWORD returnCode; + // bool result = o->waitForApplication((HANDLE)handle, refresh, + // &returnCode); return std::make_tuple(result, returnCode); + // }, (py::arg("handle"), py::arg("refresh") = true)) + // .def("refresh", &IOrganizer::refresh, (py::arg("save_changes") = + // true)) .def("managedGame", &IOrganizer::managedGame, + // py::return_value_policy()) + + // .def("onAboutToRun", &IOrganizer::onAboutToRun, py::arg("callback")) + // .def("onFinishedRun", &IOrganizer::onFinishedRun, + // py::arg("callback")) .def("onUserInterfaceInitialized", + // &IOrganizer::onUserInterfaceInitialized, py::arg("callback")) + // .def("onProfileCreated", &IOrganizer::onProfileCreated, + // py::arg("callback")) .def("onProfileRenamed", + // &IOrganizer::onProfileRenamed, py::arg("callback")) + // .def("onProfileRemoved", &IOrganizer::onProfileRemoved, + // py::arg("callback")) .def("onProfileChanged", + // &IOrganizer::onProfileChanged, py::arg("callback")) + + // .def("onPluginSettingChanged", &IOrganizer::onPluginSettingChanged, + // py::arg("callback")) .def("onPluginEnabled", +[](IOrganizer* o, + // std::function const& func) { + // o->onPluginEnabled(func); + // }, py::arg("callback")) + // .def("onPluginEnabled", +[](IOrganizer* o, QString const& name, + // std::function const& func) { + // o->onPluginEnabled(name, func); + // }, (py::arg("name"), py::arg("callback"))) + // .def("onPluginDisabled", +[](IOrganizer* o, std::function const& func) { + // o->onPluginDisabled(func); + // }, py::arg("callback")) + // .def("onPluginDisabled", +[](IOrganizer* o, QString const& name, + // std::function const& func) { + // o->onPluginDisabled(name, func); + // }, (py::arg("name"), py::arg("callback"))) + + // // DEPRECATED: + // .def("getMod", +[](IOrganizer* o, QString const& name) { + // utils::show_deprecation_warning("getMod", + // "IOrganizer::getMod(str) is deprecated, use + // IModList::getMod(str) instead."); + // return o->modList()->getMod(name); + // }, py::return_value_policy(), + // py::arg("name")) .def("removeMod", +[](IOrganizer* o, IModInterface + // *mod) { + // utils::show_deprecation_warning("removeMod", + // "IOrganizer::removeMod(IModInterface) is deprecated, use + // IModList::removeMod(IModInterface) instead."); + // return o->modList()->removeMod(mod); + // }, py::arg("mod")) + // .def("modsSortedByProfilePriority", +[](IOrganizer* o) { + // utils::show_deprecation_warning("modsSortedByProfilePriority", + // "IOrganizer::modsSortedByProfilePriority() is deprecated, use + // IModList::allModsByProfilePriority() instead."); + // return o->modList()->allModsByProfilePriority(); + // }) + // .def("refreshModList", +[](IOrganizer* o, bool s) { + // utils::show_deprecation_warning("refreshModList", + // "IOrganizer::refreshModList(bool) is deprecated, use + // IOrganizer::refresh(bool) instead."); + // o->refresh(s); + // }, (py::arg("save_changes") = true)) + // .def("onModInstalled", +[](IOrganizer* organizer, const + // std::function& func) { + // utils::show_deprecation_warning("onModInstalled", + // "IOrganizer::onModInstalled(Callable[[str], None]) is deprecated, + // " "use IModList::onModInstalled(Callable[[IModInterface], None]) + // instead."); + // return + // organizer->modList()->onModInstalled([func](MOBase::IModInterface* + // m) { func(m->name()); });; + // }, py::arg("callback")) + + // .def("getPluginDataPath", &IOrganizer::getPluginDataPath) + // .staticmethod("getPluginDataPath") + + // ; + + mo2::python::add_ifiletree_bindings(m); + + py::class_(m, "IProfile") + .def("name", &IProfile::name) + .def("absolutePath", &IProfile::absolutePath) + .def("localSavesEnabled", &IProfile::localSavesEnabled) + .def("localSettingsEnabled", &IProfile::localSettingsEnabled) + .def("invalidationActive", + [](const IProfile* p) { + bool supported; + bool active = p->invalidationActive(&supported); + return py::make_tuple(active, supported); + }) + .def("absoluteIniFilePath", &IProfile::absoluteIniFilePath, + py::arg("inifile")); + + // py::class_("IModRepositoryBridge", py::no_init) + // .def("requestDescription", &IModRepositoryBridge::requestDescription, + // (py::arg("game_name"), "mod_id", "user_data")) .def("requestFiles", + // &IModRepositoryBridge::requestFiles, (py::arg("game_name"), "mod_id", + // "user_data")) .def("requestFileInfo", + // &IModRepositoryBridge::requestFileInfo, (py::arg("game_name"), + // "mod_id", "file_id", "user_data")) .def("requestDownloadURL", + // &IModRepositoryBridge::requestDownloadURL, (py::arg("game_name"), + // "mod_id", "file_id", "user_data")) .def("requestToggleEndorsement", + // &IModRepositoryBridge::requestToggleEndorsement, + // (py::arg("game_name"), "mod_id", "mod_version", "endorse", + // "user_data")) + + // Q_DELEGATE(IModRepositoryBridge, QObject, "_object") + // ; + + // py::class_("ModRepositoryFileInfo", py::no_init) + // .def(py::init(py::arg("other"))) + // .def(py::init>((py::arg("game_name"), + // "mod_id", "file_id"))) .def("__str__", + // &ModRepositoryFileInfo::toString) .def("createFromJson", + // &ModRepositoryFileInfo::createFromJson, + // py::arg("data")).staticmethod("createFromJson") + // .def_readwrite("name", &ModRepositoryFileInfo::name) + // .def_readwrite("uri", &ModRepositoryFileInfo::uri) + // .def_readwrite("description", &ModRepositoryFileInfo::description) + // .def_readwrite("version", &ModRepositoryFileInfo::version) + // .def_readwrite("newestVersion", + // &ModRepositoryFileInfo::newestVersion) .def_readwrite("categoryID", + // &ModRepositoryFileInfo::categoryID) .def_readwrite("modName", + // &ModRepositoryFileInfo::modName) .def_readwrite("gameName", + // &ModRepositoryFileInfo::gameName) .def_readwrite("modID", + // &ModRepositoryFileInfo::modID) .def_readwrite("fileID", + // &ModRepositoryFileInfo::fileID) .def_readwrite("fileSize", + // &ModRepositoryFileInfo::fileSize) .def_readwrite("fileName", + // &ModRepositoryFileInfo::fileName) .def_readwrite("fileCategory", + // &ModRepositoryFileInfo::fileCategory) .def_readwrite("fileTime", + // &ModRepositoryFileInfo::fileTime) .def_readwrite("repository", + // &ModRepositoryFileInfo::repository) .def_readwrite("userData", + // &ModRepositoryFileInfo::userData) + // ; + + // py::class_("IDownloadManager", + // py::no_init) + // .def("startDownloadURLs", &IDownloadManager::startDownloadURLs, + // py::arg("urls")) .def("startDownloadNexusFile", + // &IDownloadManager::startDownloadNexusFile, (py::arg("mod_id"), + // "file_id")) .def("downloadPath", &IDownloadManager::downloadPath, + // py::arg("id")) .def("onDownloadComplete", + // &IDownloadManager::onDownloadComplete, py::arg("callback")) + // .def("onDownloadPaused", &IDownloadManager::onDownloadPaused, + // py::arg("callback")) .def("onDownloadFailed", + // &IDownloadManager::onDownloadFailed, py::arg("callback")) + // .def("onDownloadRemoved", &IDownloadManager::onDownloadRemoved, + // py::arg("callback")) + // ; + + // py::class_("IInstallationManager", py::no_init) + // .def("getSupportedExtensions", + // &IInstallationManager::getSupportedExtensions) .def("extractFile", + // &IInstallationManager::extractFile, (py::arg("entry"), + // py::arg("silent") = false)) .def("extractFiles", + // &IInstallationManager::extractFiles, (py::arg("entries"), + // py::arg("silent") = false)) .def("createFile", + // +[](IInstallationManager* m, std::shared_ptr entry) { + // return m->createFile(utils::clean_shared_ptr(entry)); + // }, py::arg("entry")) + + // // accept both QString and GuessedValue since the conversion + // is not automatic in Python, and + // // return a tuple to get back the mod name and the mod ID + // .def("installArchive", +[](IInstallationManager* m, + // std::variant> modName, QString archive, + // int modId) { + // GuessedValue tmp; + // if (auto* p = std::get_if(&modName)) { + // tmp = *p; + // } + // else { + // tmp = std::get>(modName); + // } + // auto result = m->installArchive(tmp, archive, modId); + // return std::make_tuple(result, static_cast(tmp), modId); + // }, (py::arg("mod_name"), "archive", py::arg("mod_id") = 0)) + // ; + + py::enum_(m, "EndorsedState") + .value("ENDORSED_FALSE", EndorsedState::ENDORSED_FALSE) + .value("ENDORSED_TRUE", EndorsedState::ENDORSED_TRUE) + .value("ENDORSED_UNKNOWN", EndorsedState::ENDORSED_UNKNOWN) + .value("ENDORSED_NEVER", EndorsedState::ENDORSED_NEVER); + + py::enum_(m, "TrackedState") + .value("TRACKED_FALSE", TrackedState::TRACKED_FALSE) + .value("TRACKED_TRUE", TrackedState::TRACKED_TRUE) + .value("TRACKED_UNKNOWN", TrackedState::TRACKED_UNKNOWN); + + // py::class_("IModInterface", + // py::no_init) + // .def("name", &IModInterface::name) + // .def("absolutePath", &IModInterface::absolutePath) + + // .def("comments", &IModInterface::comments) + // .def("notes", &IModInterface::notes) + // .def("gameName", &IModInterface::gameName) + // .def("repository", &IModInterface::repository) + // .def("nexusId", &IModInterface::nexusId) + // .def("version", &IModInterface::version) + // .def("newestVersion", &IModInterface::newestVersion) + // .def("ignoredVersion", &IModInterface::ignoredVersion) + // .def("installationFile", &IModInterface::installationFile) + // .def("converted", &IModInterface::converted) + // .def("validated", &IModInterface::validated) + // .def("color", &IModInterface::color) + // .def("url", &IModInterface::url) + // .def("primaryCategory", &IModInterface::primaryCategory) + // .def("categories", &IModInterface::categories) + // .def("trackedState", &IModInterface::trackedState) + // .def("endorsedState", &IModInterface::endorsedState) + // .def("fileTree", &IModInterface::fileTree) + // .def("isOverwrite", &IModInterface::isOverwrite) + // .def("isBackup", &IModInterface::isBackup) + // .def("isSeparator", &IModInterface::isSeparator) + // .def("isForeign", &IModInterface::isForeign) + + // .def("setVersion", &IModInterface::setVersion, py::arg("version")) + // .def("setNewestVersion", &IModInterface::setNewestVersion, + // py::arg("version")) .def("setIsEndorsed", + // &IModInterface::setIsEndorsed, py::arg("endorsed")) + // .def("setNexusID", &IModInterface::setNexusID, py::arg("nexus_id")) + // .def("addNexusCategory", &IModInterface::addNexusCategory, + // py::arg("category_id")) .def("addCategory", + // &IModInterface::addCategory, py::arg("name")) .def("removeCategory", + // &IModInterface::removeCategory, py::arg("name")) .def("setGameName", + // &IModInterface::setGameName, py::arg("name")) .def("setUrl", + // &IModInterface::setUrl, py::arg("url")) .def("pluginSetting", + // &IModInterface::pluginSetting, (py::arg("plugin_name"), "key", + // py::arg("default") = QVariant())) .def("pluginSettings", + // &IModInterface::pluginSettings, py::arg("plugin_name")) + // .def("setPluginSetting", &IModInterface::setPluginSetting, + // (py::arg("plugin_name"), "key", py::arg("value"))) + // .def("clearPluginSettings", &IModInterface::clearPluginSettings, + // py::arg("plugin_name")) + + // ; + + py::enum_(m, "GuessQuality") + .value("INVALID", MOBase::GUESS_INVALID) + .value("FALLBACK", MOBase::GUESS_FALLBACK) + .value("GOOD", MOBase::GUESS_GOOD) + .value("META", MOBase::GUESS_META) + .value("PRESET", MOBase::GUESS_PRESET) + .value("USER", MOBase::GUESS_USER); + + // py::class_, + // boost::noncopyable>("GuessedString") + // .def(py::init<>()) + // .def(py::init((py::arg("value"), + // py::arg("quality") = EGuessQuality::GUESS_USER))) .def("update", + // static_cast& + // (GuessedValue::*)(const + // QString&)>(&GuessedValue::update), py::return_self<>(), + // py::arg("value")) + // .def("update", + // static_cast& + // (GuessedValue::*)(const QString&, + // EGuessQuality)>(&GuessedValue::update), + // py::return_self<>(), (py::arg("value"), "quality")) + + // // Methods to simulate the assignment operator: + // .def("reset", +[](GuessedValue* gv) { + // *gv = GuessedValue(); }, py::return_self<>()) + // .def("reset", +[](GuessedValue* gv, const QString& value, + // EGuessQuality eq) { + // *gv = GuessedValue(value, eq); }, py::return_self<>(), + // (py::arg("value"), "quality")) + // .def("reset", +[](GuessedValue* gv, const + // GuessedValue& other) { + // *gv = other; }, py::return_self<>(), py::arg("other")) + + // // Use an intermediate lambda to avoid having to register the + // std::function conversion: .def("setFilter", + // +[](GuessedValue* gv, std::function(QString const&)> fn) { + // gv->setFilter([fn](QString& s) { + // auto ret = fn(s); + // return std::visit([&s](auto v) { + // if constexpr (std::is_same_v) { + // s = v; + // return true; + // } + // else if constexpr (std::is_same_v) { + // return v; + // } + // }, ret); + // }); + // }, py::arg("filter")) + + // // This makes a copy in python but it more practical than exposing an + // iterator: .def("variants", &GuessedValue::variants, + // py::return_value_policy()) .def("__str__", + // &MOBase::GuessedValue::operator const QString&, + // py::return_value_policy()) + // ; + + py::enum_(m, "PluginState") + .value("missing", IPluginList::STATE_MISSING) + .value("inactive", IPluginList::STATE_INACTIVE) + .value("active", IPluginList::STATE_ACTIVE) + + .value("MISSING", IPluginList::STATE_MISSING) + .value("INACTIVE", IPluginList::STATE_INACTIVE) + .value("ACTIVE", IPluginList::STATE_ACTIVE); + + m.def("testFlags1", [](IPluginList::PluginStates states) { + std::vector res; + if (states.testFlag(IPluginList::STATE_MISSING)) { + res.push_back("missing"); + } + if (states.testFlag(IPluginList::STATE_INACTIVE)) { + res.push_back("inactive"); + } + if (states.testFlag(IPluginList::STATE_ACTIVE)) { + res.push_back("active"); + } + return res; + }); + + m.def("testFlags2", [](QStringList const& states) { + IPluginList::PluginStates res; + if (states.contains("missing")) { + res |= IPluginList::STATE_MISSING; + } + if (states.contains("inactive")) { + res |= IPluginList::STATE_INACTIVE; + } + if (states.contains("active")) { + res |= IPluginList::STATE_ACTIVE; + } + return res; + }); + + // py::class_("IPluginList", py::no_init) + // .def("state", &MOBase::IPluginList::state, py::arg("name")) + // .def("priority", &MOBase::IPluginList::priority, py::arg("name")) + // .def("setPriority", &MOBase::IPluginList::setPriority, + // (py::arg("name"), "priority")) .def("loadOrder", + // &MOBase::IPluginList::loadOrder, py::arg("name")) .def("isMaster", + // &MOBase::IPluginList::isMaster, py::arg("name")) .def("masters", + // &MOBase::IPluginList::masters, py::arg("name")) .def("origin", + // &MOBase::IPluginList::origin, py::arg("name")) .def("onRefreshed", + // &MOBase::IPluginList::onRefreshed, py::arg("callback")) + // .def("onPluginMoved", &MOBase::IPluginList::onPluginMoved, + // py::arg("callback")) + + // // Kept but deprecated for backward compatibility: + // .def("onPluginStateChanged", +[](IPluginList* modList, const + // std::function& fn) { + // utils::show_deprecation_warning("onPluginStateChanged", + // "onPluginStateChanged(Callable[[str, IPluginList.PluginStates], + // None]) is deprecated, " "use + // onPluginStateChanged(Callable[[Dict[str, + // IPluginList.PluginStates], None]) instead."); + // return modList->onPluginStateChanged([fn](auto const& map) { + // for (const auto& entry : map) { + // fn(entry.first, entry.second); + // } + // }); + // }, py::arg("callback")) + // .def("onPluginStateChanged", + // &MOBase::IPluginList::onPluginStateChanged, py::arg("callback")) + // .def("pluginNames", &MOBase::IPluginList::pluginNames) + // .def("setState", &MOBase::IPluginList::setState, (py::arg("name"), + // "state")) .def("setLoadOrder", &MOBase::IPluginList::setLoadOrder, + // py::arg("loadorder")) + // ; + + // py::enum_("ModState") + // .value("exists", IModList::STATE_EXISTS) + // .value("active", IModList::STATE_ACTIVE) + // .value("essential", IModList::STATE_ESSENTIAL) + // .value("empty", IModList::STATE_EMPTY) + // .value("endorsed", IModList::STATE_ENDORSED) + // .value("valid", IModList::STATE_VALID) + // .value("alternate", IModList::STATE_ALTERNATE) + + // .value("EXISTS", IModList::STATE_EXISTS) + // .value("ACTIVE", IModList::STATE_ACTIVE) + // .value("ESSENTIAL", IModList::STATE_ESSENTIAL) + // .value("EMPTY", IModList::STATE_EMPTY) + // .value("ENDORSED", IModList::STATE_ENDORSED) + // .value("VALID", IModList::STATE_VALID) + // .value("ALTERNATE", IModList::STATE_ALTERNATE) + // ; + + // py::class_("IModList", py::no_init) + // .def("displayName", &MOBase::IModList::displayName, py::arg("name")) + // .def("allMods", &MOBase::IModList::allMods) + // .def("allModsByProfilePriority", + // &MOBase::IModList::allModsByProfilePriority, py::arg("profile") = + // py::ptr((IProfile*)nullptr)) + + // .def("getMod", &MOBase::IModList::getMod, + // py::return_value_policy(), + // py::arg("name")) .def("removeMod", &MOBase::IModList::removeMod, + // py::arg("mod")) .def("renameMod", &MOBase::IModList::renameMod, + // py::return_value_policy(), + // (py::arg("mod"), py::arg("name"))) + + // .def("state", &MOBase::IModList::state, py::arg("name")) + // .def("setActive", + // static_cast(&MOBase::IModList::setActive), (py::arg("names"), "active")) + // .def("setActive", + // static_cast(&MOBase::IModList::setActive), (py::arg("name"), "active")) + // .def("priority", &MOBase::IModList::priority, py::arg("name")) + // .def("setPriority", &MOBase::IModList::setPriority, (py::arg("name"), + // "priority")) + + // // Kept but deprecated for backward compatibility: + // .def("onModStateChanged", +[](IModList* modList, const + // std::function& fn) { + // utils::show_deprecation_warning("onModStateChanged", + // "onModStateChanged(Callable[[str, IModList.ModStates], None]) is + // deprecated, " "use onModStateChanged(Callable[[Dict[str, + // IModList.ModStates], None]) instead."); + // return modList->onModStateChanged([fn](auto const& map) { + // for (const auto& entry : map) { + // fn(entry.first, entry.second); + // } + // }); + // }, py::arg("callback")) + + // .def("onModInstalled", &MOBase::IModList::onModInstalled, + // py::arg("callback")) .def("onModRemoved", + // &MOBase::IModList::onModRemoved, py::arg("callback")) + // .def("onModStateChanged", &MOBase::IModList::onModStateChanged, + // py::arg("callback")) .def("onModMoved", + // &MOBase::IModList::onModMoved, py::arg("callback")) + // ; + + // // Note: localizedName, master, requirements and enabledByDefault have to + // go in all the plugin wrappers declaration, + // // since the default functions are specific to each wrapper, otherwise in + // turns into an + // // infinite recursion mess. + // py::class_("IPlugin") + // .def("init", py::pure_virtual(&MOBase::IPlugin::init), + // py::arg("organizer")) .def("name", + // py::pure_virtual(&MOBase::IPlugin::name)) .def("localizedName", + // &MOBase::IPlugin::localizedName, + // &IPluginWrapper::localizedName_Default) .def("master", + // &MOBase::IPlugin::master, &IPluginWrapper::master_Default) + // .def("author", py::pure_virtual(&MOBase::IPlugin::author)) + // .def("description", py::pure_virtual(&MOBase::IPlugin::description)) + // .def("version", py::pure_virtual(&MOBase::IPlugin::version)) + // .def("requirements", &MOBase::IPlugin::requirements, + // &IPluginWrapper::requirements_Default) .def("settings", + // py::pure_virtual(&MOBase::IPlugin::settings)) .def("enabledByDefault", + // &MOBase::IPlugin::enabledByDefault, + // &IPluginWrapper::enabledByDefault_Default) + // ; + + // py::class_, + // boost::noncopyable>("IPluginDiagnose") + // .def("localizedName", &MOBase::IPlugin::localizedName, + // &IPluginDiagnoseWrapper::localizedName_Default) .def("master", + // &MOBase::IPlugin::master, &IPluginDiagnoseWrapper::master_Default) + // .def("requirements", &MOBase::IPlugin::requirements, + // &IPluginDiagnoseWrapper::requirements_Default) + // .def("enabledByDefault", &MOBase::IPlugin::enabledByDefault, + // &IPluginDiagnoseWrapper::enabledByDefault_Default) + + // .def("activeProblems", + // py::pure_virtual(&MOBase::IPluginDiagnose::activeProblems)) + // .def("shortDescription", + // py::pure_virtual(&MOBase::IPluginDiagnose::shortDescription), + // py::arg("key")) .def("fullDescription", + // py::pure_virtual(&MOBase::IPluginDiagnose::fullDescription), + // py::arg("key")) .def("hasGuidedFix", + // py::pure_virtual(&MOBase::IPluginDiagnose::hasGuidedFix), + // py::arg("key")) .def("startGuidedFix", + // py::pure_virtual(&MOBase::IPluginDiagnose::startGuidedFix), + // py::arg("key")) .def("_invalidate", + // &IPluginDiagnoseWrapper::invalidate) + // ; + + // py::class_("Mapping", py::init<>()) + // .def("__init__", py::make_constructor(+[](QString src, QString dst, + // bool dir, bool crt) -> Mapping* { + // return new Mapping{ src, dst, dir, crt }; + // }, py::default_call_policies(), + // (py::arg("source"), py::arg("destination"), + // py::arg("is_directory"), py::arg("create_target") = false))) + // .def_readwrite("source", &Mapping::source) + // .def_readwrite("destination", &Mapping::destination) + // .def_readwrite("isDirectory", &Mapping::isDirectory) + // .def_readwrite("createTarget", &Mapping::createTarget) + // .def("__str__", +[](Mapping * m) { + // return fmt::format(L"Mapping({}, {}, {}, {})", + // m->source.toStdWString(), m->destination.toStdWString(), + // m->isDirectory, m->createTarget); + // }) + // ; + + // py::class_, + // boost::noncopyable>("IPluginFileMapper") + // .def("localizedName", &MOBase::IPlugin::localizedName, + // &IPluginFileMapperWrapper::localizedName_Default) .def("master", + // &MOBase::IPlugin::master, &IPluginFileMapperWrapper::master_Default) + // .def("requirements", &MOBase::IPlugin::requirements, + // &IPluginFileMapperWrapper::requirements_Default) + // .def("enabledByDefault", &MOBase::IPlugin::enabledByDefault, + // &IPluginFileMapperWrapper::enabledByDefault_Default) + + // .def("mappings", + // py::pure_virtual(&MOBase::IPluginFileMapper::mappings)) + // ; + + // py::enum_("LoadOrderMechanism") + // .value("FileTime", MOBase::IPluginGame::LoadOrderMechanism::FileTime) + // .value("PluginsTxt", + // MOBase::IPluginGame::LoadOrderMechanism::PluginsTxt) + + // .value("FILE_TIME", + // MOBase::IPluginGame::LoadOrderMechanism::FileTime) + // .value("PLUGINS_TXT", + // MOBase::IPluginGame::LoadOrderMechanism::PluginsTxt) + // ; + + // py::enum_("SortMechanism") + // .value("NONE", MOBase::IPluginGame::SortMechanism::NONE) + // .value("MLOX", MOBase::IPluginGame::SortMechanism::MLOX) + // .value("BOSS", MOBase::IPluginGame::SortMechanism::BOSS) + // .value("LOOT", MOBase::IPluginGame::SortMechanism::LOOT) + // ; + + // // This doesn't actually do the conversion, but might be convenient for + // accessing the names for enum bits + // py::enum_("ProfileSetting") + // .value("mods", MOBase::IPluginGame::MODS) + // .value("configuration", MOBase::IPluginGame::CONFIGURATION) + // .value("savegames", MOBase::IPluginGame::SAVEGAMES) + // .value("preferDefaults", MOBase::IPluginGame::PREFER_DEFAULTS) + + // .value("MODS", MOBase::IPluginGame::MODS) + // .value("CONFIGURATION", MOBase::IPluginGame::CONFIGURATION) + // .value("SAVEGAMES", MOBase::IPluginGame::SAVEGAMES) + // .value("PREFER_DEFAULTS", MOBase::IPluginGame::PREFER_DEFAULTS) + // ; + + // py::class_, + // boost::noncopyable>("IPluginGame") + // .def("localizedName", &MOBase::IPlugin::localizedName, + // &IPluginGameWrapper::localizedName_Default) .def("master", + // &MOBase::IPlugin::master, &IPluginGameWrapper::master_Default) + + // .def("detectGame", + // py::pure_virtual(&MOBase::IPluginGame::detectGame)) .def("gameName", + // py::pure_virtual(&MOBase::IPluginGame::gameName)) + // .def("initializeProfile", + // py::pure_virtual(&MOBase::IPluginGame::initializeProfile), + // (py::arg("directory"), "settings")) .def("listSaves", + // py::pure_virtual(&MOBase::IPluginGame::listSaves), py::arg("folder")) + // .def("isInstalled", + // py::pure_virtual(&MOBase::IPluginGame::isInstalled)) .def("gameIcon", + // py::pure_virtual(&MOBase::IPluginGame::gameIcon)) + // .def("gameDirectory", + // py::pure_virtual(&MOBase::IPluginGame::gameDirectory)) + // .def("dataDirectory", + // py::pure_virtual(&MOBase::IPluginGame::dataDirectory)) + // .def("setGamePath", + // py::pure_virtual(&MOBase::IPluginGame::setGamePath), py::arg("path")) + // .def("documentsDirectory", + // py::pure_virtual(&MOBase::IPluginGame::documentsDirectory)) + // .def("savesDirectory", + // py::pure_virtual(&MOBase::IPluginGame::savesDirectory)) + // .def("executables", + // py::pure_virtual(&MOBase::IPluginGame::executables)) + // .def("executableForcedLoads", + // py::pure_virtual(&MOBase::IPluginGame::executableForcedLoads)) + // .def("steamAPPId", + // py::pure_virtual(&MOBase::IPluginGame::steamAPPId)) + // .def("primaryPlugins", + // py::pure_virtual(&MOBase::IPluginGame::primaryPlugins)) + // .def("gameVariants", + // py::pure_virtual(&MOBase::IPluginGame::gameVariants)) + // .def("setGameVariant", + // py::pure_virtual(&MOBase::IPluginGame::setGameVariant), + // py::arg("variant")) .def("binaryName", + // py::pure_virtual(&MOBase::IPluginGame::binaryName)) + // .def("gameShortName", + // py::pure_virtual(&MOBase::IPluginGame::gameShortName)) + // .def("primarySources", + // py::pure_virtual(&MOBase::IPluginGame::primarySources)) + // .def("validShortNames", + // py::pure_virtual(&MOBase::IPluginGame::validShortNames)) + // .def("gameNexusName", + // py::pure_virtual(&MOBase::IPluginGame::gameNexusName)) + // .def("iniFiles", py::pure_virtual(&MOBase::IPluginGame::iniFiles)) + // .def("DLCPlugins", + // py::pure_virtual(&MOBase::IPluginGame::DLCPlugins)) .def("CCPlugins", + // py::pure_virtual(&MOBase::IPluginGame::CCPlugins)) + // .def("loadOrderMechanism", + // py::pure_virtual(&MOBase::IPluginGame::loadOrderMechanism)) + // .def("sortMechanism", + // py::pure_virtual(&MOBase::IPluginGame::sortMechanism)) + // .def("nexusModOrganizerID", + // py::pure_virtual(&MOBase::IPluginGame::nexusModOrganizerID)) + // .def("nexusGameID", + // py::pure_virtual(&MOBase::IPluginGame::nexusGameID)) + // .def("looksValid", + // py::pure_virtual(&MOBase::IPluginGame::looksValid), + // py::arg("directory")) .def("gameVersion", + // py::pure_virtual(&MOBase::IPluginGame::gameVersion)) + // .def("getLauncherName", + // py::pure_virtual(&MOBase::IPluginGame::getLauncherName)) + + // .def("featureList", +[](MOBase::IPluginGame* p) { + // // Constructing a dict from class name to actual object: + // py::dict dict; + // mp11::mp_for_each< + // // Must user pointers because mp_for_each construct object: + // mp11::mp_transform + // >([&](auto* pt) { + // using T = std::remove_pointer_t; + // typename py::reference_existing_object::apply::type + // converter; + + // // Retrieve the python class object: + // const py::converter::registration* registration = + // py::converter::registry::query(py::type_id()); py::object key + // = + // py::object(py::handle<>(py::borrowed(registration->get_class_object()))); + + // // Set the object: + // dict[key] = py::handle<>(converter(p->feature())); + // }); + // return dict; + // }) + + // .def("feature", +[](MOBase::IPluginGame* p, py::object clsObj) { + // py::object feature; + // mp11::mp_for_each< + // // Must user pointers because mp_for_each construct object: + // mp11::mp_transform + // >([&](auto* pt) { + // using T = std::remove_pointer_t; + // typename py::reference_existing_object::apply::type + // converter; + + // // Retrieve the python class object: + // const py::converter::registration* registration = + // py::converter::registry::query(py::type_id()); + + // if (clsObj.ptr() == (PyObject*) registration->get_class_object()) + // { + // feature = py::object(py::handle<>(converter(p->feature()))); + // } + // }); + // return feature; + // }, py::arg("feature_type")) + // ; + + // py::enum_("InstallResult") + // .value("SUCCESS", MOBase::IPluginInstaller::RESULT_SUCCESS) + // .value("FAILED", MOBase::IPluginInstaller::RESULT_FAILED) + // .value("CANCELED", MOBase::IPluginInstaller::RESULT_CANCELED) + // .value("MANUAL_REQUESTED", + // MOBase::IPluginInstaller::RESULT_MANUALREQUESTED) + // .value("NOT_ATTEMPTED", + // MOBase::IPluginInstaller::RESULT_NOTATTEMPTED) + // ; + + // py::class_, + // boost::noncopyable>("IPluginInstaller", py::no_init) + // .def("isArchiveSupported", &IPluginInstaller::isArchiveSupported, + // py::arg("tree")) .def("priority", &IPluginInstaller::priority) + // .def("onInstallationStart", &IPluginInstaller::onInstallationStart, + // (py::arg("archive"), py::arg("reinstallation"), + // py::arg("current_mod"))) .def("onInstallationEnd", + // &IPluginInstaller::onInstallationEnd, (py::arg("result"), + // py::arg("new_mod"))) .def("isManualInstaller", + // &IPluginInstaller::isManualInstaller) .def("setParentWidget", + // &IPluginInstaller::setParentWidget, py::arg("parent")) + // .def("setInstallationManager", + // &IPluginInstaller::setInstallationManager, py::arg("manager")) + // ; + + // py::class_, + // boost::noncopyable>("IPluginInstallerSimple") + // .def("onInstallationStart", &IPluginInstaller::onInstallationStart, + // (py::arg("archive"), py::arg("reinstallation"), + // py::arg("current_mod"))) .def("onInstallationEnd", + // &IPluginInstaller::onInstallationEnd, (py::arg("result"), + // py::arg("new_mod"))) .def("localizedName", + // &MOBase::IPlugin::localizedName, + // &IPluginInstallerSimpleWrapper::localizedName_Default) .def("master", + // &MOBase::IPlugin::master, + // &IPluginInstallerSimpleWrapper::master_Default) .def("requirements", + // &MOBase::IPlugin::requirements, + // &IPluginInstallerSimpleWrapper::requirements_Default) + // .def("enabledByDefault", &MOBase::IPlugin::enabledByDefault, + // &IPluginInstallerSimpleWrapper::enabledByDefault_Default) + + // // Note: Keeping the variant here even if we always return a tuple to + // be consistent with the wrapper and + // // have proper stubs generation. + // .def("install", +[](IPluginInstallerSimple* p, GuessedValue& + // modName, std::shared_ptr& tree, QString& version, int& + // nexusID) + // -> std::variant, + // std::tuple, QString, int>> { + // auto result = p->install(modName, tree, version, nexusID); + // return std::make_tuple(result, tree, version, nexusID); + // }, (py::arg("name"), "tree", "version", "nexus_id")) + // .def("_parentWidget", &IPluginInstallerSimpleWrapper::parentWidget, + // py::return_value_policy()) .def("_manager", + // &IPluginInstallerSimpleWrapper::manager, + // py::return_value_policy()) + // ; + + // py::class_, + // boost::noncopyable>("IPluginInstallerCustom") + // .def("onInstallationStart", &IPluginInstaller::onInstallationStart, + // (py::arg("archive"), py::arg("reinstallation"), + // py::arg("current_mod"))) .def("onInstallationEnd", + // &IPluginInstaller::onInstallationEnd, (py::arg("result"), + // py::arg("new_mod"))) .def("localizedName", + // &MOBase::IPlugin::localizedName, + // &IPluginInstallerCustomWrapper::localizedName_Default) .def("master", + // &MOBase::IPlugin::master, + // &IPluginInstallerCustomWrapper::master_Default) .def("requirements", + // &MOBase::IPlugin::requirements, + // &IPluginInstallerCustomWrapper::requirements_Default) + // .def("enabledByDefault", &MOBase::IPlugin::enabledByDefault, + // &IPluginInstallerCustomWrapper::enabledByDefault_Default) + + // // Needs to add both otherwize boost does not understand: + // .def("isArchiveSupported", &IPluginInstaller::isArchiveSupported, + // py::arg("tree")) .def("isArchiveSupported", + // &IPluginInstallerCustom::isArchiveSupported, py::arg("archive_name")) + // .def("supportedExtensions", + // &IPluginInstallerCustom::supportedExtensions) .def("install", + // &IPluginInstallerCustom::install, (py::arg("mod_name"), "game_name", + // "archive_name", "version", "nexus_id")) .def("_parentWidget", + // &IPluginInstallerSimpleWrapper::parentWidget, + // py::return_value_policy()) .def("_manager", + // &IPluginInstallerCustomWrapper::manager, + // py::return_value_policy()) + // ; + + // py::class_, + // boost::noncopyable>("IPluginModPage") + // .def("localizedName", &MOBase::IPlugin::localizedName, + // &IPluginModPageWrapper::localizedName_Default) .def("master", + // &MOBase::IPlugin::master, &IPluginModPageWrapper::master_Default) + // .def("requirements", &MOBase::IPlugin::requirements, + // &IPluginModPageWrapper::requirements_Default) .def("enabledByDefault", + // &MOBase::IPlugin::enabledByDefault, + // &IPluginModPageWrapper::enabledByDefault_Default) + + // .def("displayName", py::pure_virtual(&IPluginModPage::displayName)) + // .def("icon", py::pure_virtual(&IPluginModPage::icon)) + // .def("pageURL", py::pure_virtual(&IPluginModPage::pageURL)) + // .def("useIntegratedBrowser", + // py::pure_virtual(&IPluginModPage::useIntegratedBrowser)) + // .def("handlesDownload", + // py::pure_virtual(&IPluginModPage::handlesDownload), + // (py::arg("page_url"), "download_url", "fileinfo")) + // .def("setParentWidget", &IPluginModPage::setParentWidget, + // &IPluginModPageWrapper::setParentWidget_Default, py::arg("parent")) + // .def("_parentWidget", &IPluginModPageWrapper::parentWidget, + // py::return_value_policy()) + // ; + + // py::class_, + // boost::noncopyable>("IPluginPreview") + // .def("localizedName", &MOBase::IPlugin::localizedName, + // &IPluginPreviewWrapper::localizedName_Default) .def("master", + // &MOBase::IPlugin::master, &IPluginPreviewWrapper::master_Default) + // .def("requirements", &MOBase::IPlugin::requirements, + // &IPluginPreviewWrapper::requirements_Default) .def("enabledByDefault", + // &MOBase::IPlugin::enabledByDefault, + // &IPluginPreviewWrapper::enabledByDefault_Default) + + // .def("supportedExtensions", + // py::pure_virtual(&IPluginPreview::supportedExtensions)) + // .def("genFilePreview", + // py::pure_virtual(&IPluginPreview::genFilePreview), + // py::return_value_policy(), + // (py::arg("filename"), "max_size")) + // ; + + // py::class_, + // boost::noncopyable>("IPluginTool") + // .def("localizedName", &MOBase::IPlugin::localizedName, + // &IPluginToolWrapper::localizedName_Default) .def("master", + // &MOBase::IPlugin::master, &IPluginToolWrapper::master_Default) + // .def("requirements", &MOBase::IPlugin::requirements, + // &IPluginToolWrapper::requirements_Default) .def("enabledByDefault", + // &MOBase::IPlugin::enabledByDefault, + // &IPluginToolWrapper::enabledByDefault_Default) + + // .def("displayName", py::pure_virtual(&IPluginTool::displayName)) + // .def("tooltip", py::pure_virtual(&IPluginTool::tooltip)) + // .def("icon", py::pure_virtual(&IPluginTool::icon)) + // .def("display", py::pure_virtual(&IPluginTool::display)) + // .def("setParentWidget", &IPluginTool::setParentWidget, + // &IPluginToolWrapper::setParentWidget_Default, py::arg("parent")) + // .def("_parentWidget", &IPluginToolWrapper::parentWidget, + // py::return_value_policy()) + // ; + + // registerGameFeaturesPythonConverters(); + + m.def("getFileVersion", &MOBase::getFileVersion, py::arg("filepath")); + m.def("getProductVersion", &MOBase::getProductVersion, + py::arg("executable")); + m.def("getIconForExecutable", &MOBase::iconForExecutable, + py::arg("executable")); + + // py::object widgets(py::borrowed(PyImport_AddModule("mobase.widgets"))); + // py::scope().attr("widgets") = widgets; + // { + // py::scope w_ = widgets; + // register_widgets(); + // } + + // Expose MoVariant: MoVariant is a fake object whose only purpose is to be + // used as a type-hint on the python side (e.g., def foo(x: + // mobase.MoVariant)). The real MoVariant is defined in the generated stubs, + // since it's only relevant when doing type-checking, but this needs to be + // defined, otherwise MoVariant is not found when actually running plugins + // through MO2, making them crash. + m.attr("MoVariant") = py::none(); } /** * */ -class PythonRunner : public IPythonRunner -{ +class PythonRunner : public IPythonRunner { public: - PythonRunner(); - ~PythonRunner(); + PythonRunner(); + ~PythonRunner(); - bool initPython(); + bool initPython(); - QList load(const QString& identifier); - void unload(const QString& identifier); + QList load(const QString& identifier); + void unload(const QString& identifier); - bool isPythonInitialized() const; - bool isPythonVersionSupported() const; + bool isPythonInitialized() const; + bool isPythonVersionSupported() const; private: + void initPath(); - void initPath(); + /** + * @brief Ensure that the given folder is in sys.path. + */ + void ensureFolderInPath(QString folder); - /** - * @brief Ensure that the given folder is in sys.path. - */ - void ensureFolderInPath(QString folder); - - /** - * @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(py::object const& obj, QList& interfaces); + /** + * @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(py::object const& obj, QList& interfaces); private: - - // For each "identifier" (python file or python module folder), contains the list - // of python objects to keep "alive" during the execution. - std::unordered_map> m_PythonObjects; + // For each "identifier" (python file or python module folder), contains the + // list of python objects to keep "alive" during the execution. + std::unordered_map> m_PythonObjects; }; IPythonRunner* CreatePythonRunner() { - std::unique_ptr result = std::make_unique(); - if (result->initPython()) { - return result.release(); - } - else { - return nullptr; - } -} - -PythonRunner::PythonRunner() -{ -} - -PythonRunner::~PythonRunner() { - // We need the GIL lock when destroying Python objects. - py::gil_scoped_acquire lock; - // m_Interpreter.reset(); - - // Boost.Python does not handle cyclic garbace collection, so we need to release - // everything hold by the objects before deleting the objects themselves: - // for (auto& [name, objects] : m_PythonObjects) { - // for (auto& obj : objects) { - // obj.attr("__dict__").attr("clear")(); - // } - // } - - // m_PythonObjects.clear(); -} - -static const char *argv0 = "ModOrganizer.exe"; - -struct PrintWrapper -{ - void write(const char * message) - { - buffer << message; - if (buffer.tellp() != 0 && buffer.str().back() == '\n') - { - // actually put the string in a variable so it doesn't get destroyed as soon as we get a pointer to its data - std::string string = buffer.str().substr(0, buffer.str().length() - 1); - qDebug().nospace().noquote() << string.c_str(); - buffer = std::stringstream(); + std::unique_ptr result = std::make_unique(); + if (result->initPython()) { + return result.release(); } - } + else { + return nullptr; + } +} - std::stringstream buffer; -}; +PythonRunner::PythonRunner() {} + +PythonRunner::~PythonRunner() +{ + // We need the GIL lock when destroying Python objects. + py::gil_scoped_acquire lock; + // m_Interpreter.reset(); + + // Boost.Python does not handle cyclic garbace collection, so we need to + // release everything hold by the objects before deleting the objects + // themselves: for (auto& [name, objects] : m_PythonObjects) { + // for (auto& obj : objects) { + // obj.attr("__dict__").attr("clear")(); + // } + // } + + // m_PythonObjects.clear(); +} // ErrWrapper is in error.h PYBIND11_MODULE(moprivate, m) { - py::class_(m, "PrintWrapper") - .def(py::init<>()) - .def("write", &PrintWrapper::write); - py::class_(m, "ErrWrapper") - .def(py::init<>()) - .def_static("instance", &ErrWrapper::instance, py::return_value_policy::reference) - .def("write", &ErrWrapper::write) - .def("startRecordingExceptionMessage", &ErrWrapper::startRecordingExceptionMessage) - .def("stopRecordingExceptionMessage", &ErrWrapper::stopRecordingExceptionMessage) - .def("getLastExceptionMessage", &ErrWrapper::getLastExceptionMessage); - - // expose a function to create a particular tree, only for debugging purpose, not - // in mobase. - mo2::python::add_make_tree_function(m); - + // expose a function to create a particular tree, only for debugging + // purpose, not in mobase. + mo2::python::add_make_tree_function(m); } bool PythonRunner::initPython() { - if (Py_IsInitialized()) - return true; - try { + if (Py_IsInitialized()) + return true; + try { + static const char* argv0 = "ModOrganizer.exe"; - // we initialize the interpreter "the old way" because scoped_interpreter does not - // seem to work well with PyQt - wchar_t argBuffer[MAX_PATH]; - const size_t cSize = strlen(argv0) + 1; - mbstowcs(argBuffer, argv0, MAX_PATH); - Py_SetProgramName(argBuffer); + // we initialize the interpreter "the old way" because + // scoped_interpreter does not seem to work well with PyQt + wchar_t argBuffer[MAX_PATH]; + const size_t cSize = strlen(argv0) + 1; + mbstowcs(argBuffer, argv0, MAX_PATH); + Py_SetProgramName(argBuffer); - PyImport_AppendInittab("mobase", &PyInit_mobase); - PyImport_AppendInittab("moprivate", &PyInit_moprivate); + PyImport_AppendInittab("mobase", &PyInit_mobase); + PyImport_AppendInittab("moprivate", &PyInit_moprivate); - Py_OptimizeFlag = 2; - Py_NoSiteFlag = 1; + Py_OptimizeFlag = 2; + Py_NoSiteFlag = 1; - initPath(); - Py_InitializeEx(0); + initPath(); + Py_InitializeEx(0); - if (!Py_IsInitialized()) { - return false; + if (!Py_IsInitialized()) { + return false; + } + + py::module_ mainModule = py::module_::import("__main__"); + py::object mainNamespace = mainModule.attr("__dict__"); + mainNamespace["sys"] = py::module_::import("sys"); + mainNamespace["moprivate"] = py::module_::import("moprivate"); + mainNamespace["mobase"] = py::module_::import("mobase"); + + mo2::python::configure_python_stream(); + mo2::python::configure_python_logging(mainNamespace["mobase"]); + + return true; + } + catch (const py::error_already_set& ex) { + MOBase::log::error("failed to init python: {}", ex.what()); + return false; } - - py::module_ mainModule = py::module_::import("__main__"); - py::object mainNamespace = mainModule.attr("__dict__"); - mainNamespace["sys"] = py::module_::import("sys"); - mainNamespace["moprivate"] = py::module_::import("moprivate"); - py::module_::import("site"); - py::exec("sys.stdout = moprivate.PrintWrapper()\n" - "sys.stderr = moprivate.ErrWrapper.instance()\n" - "sys.excepthook = lambda x, y, z: sys.__excepthook__(x, y, z)\n", - mainNamespace); - - mainNamespace["mobase"] = py::module_::import("mobase"); - mo2::python::configure_python_logging(mainNamespace["mobase"]); - - return true; - } catch (const py::error_already_set&) { - // construct an error to extract the message - pyexcept::PythonError err; - MOBase::log::error("failed to init python: {}", err.what()); - return false; - } } void PythonRunner::initPath() { - static QStringList paths = { - QCoreApplication::applicationDirPath() + "/pythoncore.zip", - QCoreApplication::applicationDirPath() + "/pythoncore", - IOrganizer::getPluginDataPath() - }; + static QStringList paths = { + QCoreApplication::applicationDirPath() + "/pythoncore.zip", + QCoreApplication::applicationDirPath() + "/pythoncore", + IOrganizer::getPluginDataPath()}; - Py_SetPath(paths.join(';').toStdWString().c_str()); + Py_SetPath(paths.join(';').toStdWString().c_str()); } -void PythonRunner::ensureFolderInPath(QString folder) { - py::module_ sys = py::module_::import("sys"); - py::list sysPath = sys.attr("path"); +void PythonRunner::ensureFolderInPath(QString folder) +{ + py::module_ sys = py::module_::import("sys"); + py::list sysPath = sys.attr("path"); - // Converting to QStringList for Qt::CaseInsensitive and because .index() - // raise an exception: - const QStringList currentPath = sysPath.cast(); - if (!currentPath.contains(folder, Qt::CaseInsensitive)) { - sysPath.insert(0, folder); - } + // Converting to QStringList for Qt::CaseInsensitive and because .index() + // raise an exception: + const QStringList currentPath = sysPath.cast(); + if (!currentPath.contains(folder, Qt::CaseInsensitive)) { + sysPath.insert(0, folder); + } } template -void PythonRunner::appendIfInstance(py::object const& obj, QList &interfaces) { - if (py::isinstance(obj)) { - interfaces.append(obj.cast()); - } +void PythonRunner::appendIfInstance(py::object const& obj, + QList& interfaces) +{ + if (py::isinstance(obj)) { + interfaces.append(obj.cast()); + } } QList PythonRunner::load(const QString& identifier) { - py::gil_scoped_acquire lock; + py::gil_scoped_acquire lock; - // `pluginName` can either be a python file (single-file plugin or a folder (whole module). - // - // For whole module, we simply add the parent folder to path, then we load the module with a simple - // py::import, and we retrieve the associated __dict__ from which we extract either createPlugin or - // createPlugins. - // - // For single file, we need to use py::eval_file, and we will use the context (global variables) - // from __main__ (already contains mobase, and other required module). Since the context is shared - // between called of `instantiate`, we need to make sure to remove createPlugin(s) from previous call. - try { + // `pluginName` can either be a python file (single-file plugin or a folder + // (whole module). + // + // For whole module, we simply add the parent folder to path, then we load + // the module with a simple py::import, and we retrieve the associated + // __dict__ from which we extract either createPlugin or createPlugins. + // + // For single file, we need to use py::eval_file, and we will use the + // context (global variables) from __main__ (already contains mobase, and + // other required module). Since the context is shared between called of + // `instantiate`, we need to make sure to remove createPlugin(s) from + // previous call. + try { - // Dictionary that will contain createPlugin() or createPlugins(). - py::dict moduleDict; + // Dictionary that will contain createPlugin() or createPlugins(). + py::dict moduleDict; - if (identifier.endsWith(".py")) { - py::object mainModule = py::module_::import("__main__"); - py::dict moduleNamespace = mainModule.attr("__dict__"); + if (identifier.endsWith(".py")) { + py::object mainModule = py::module_::import("__main__"); + py::dict moduleNamespace = mainModule.attr("__dict__"); - std::string temp = ToString(identifier); - py::eval_file(temp.c_str(), moduleNamespace).is_none(); - moduleDict = moduleNamespace; - } - else { - // Retrieve the module name: - QStringList parts = identifier.split("/"); - std::string moduleName = ToString(parts.takeLast()); - ensureFolderInPath(parts.join("/")); - moduleDict = py::module_::import(moduleName.c_str()).attr("__dict__"); - } - - if (py::len(moduleDict) == 0) { - MOBase::log::error("No plugins found in {}.", identifier); - return {}; - } - - // Create the plugins: - std::vector plugins; - - if (moduleDict.contains("createPlugin")) { - plugins.push_back(moduleDict["createPlugin"]()); - - // Clear for future call - PyDict_DelItemString(moduleDict.ptr(), "createPlugin"); - } - else if (moduleDict.contains("createPlugins")) { - py::object pyPlugins = moduleDict["createPlugins"](); - if (!PySequence_Check(pyPlugins.ptr())) { - MOBase::log::error("Plugin {}: createPlugins must return a list.", identifier); - } - else { - py::list pyList(pyPlugins); - int nPlugins = py::len(pyList); - for (int i = 0; i < nPlugins; ++i) { - plugins.push_back(pyList[i]); + std::string temp = ToString(identifier); + py::eval_file(temp.c_str(), moduleNamespace).is_none(); + moduleDict = moduleNamespace; + } + else { + // Retrieve the module name: + QStringList parts = identifier.split("/"); + std::string moduleName = ToString(parts.takeLast()); + ensureFolderInPath(parts.join("/")); + moduleDict = + py::module_::import(moduleName.c_str()).attr("__dict__"); } - } - // Clear for future call - PyDict_DelItemString(moduleDict.ptr(), "createPlugins"); + if (py::len(moduleDict) == 0) { + MOBase::log::error("No plugins found in {}.", identifier); + return {}; + } + + // Create the plugins: + std::vector plugins; + + if (moduleDict.contains("createPlugin")) { + plugins.push_back(moduleDict["createPlugin"]()); + + // Clear for future call + PyDict_DelItemString(moduleDict.ptr(), "createPlugin"); + } + else if (moduleDict.contains("createPlugins")) { + py::object pyPlugins = moduleDict["createPlugins"](); + if (!PySequence_Check(pyPlugins.ptr())) { + MOBase::log::error( + "Plugin {}: createPlugins must return a list.", identifier); + } + else { + py::list pyList(pyPlugins); + int nPlugins = py::len(pyList); + for (int i = 0; i < nPlugins; ++i) { + plugins.push_back(pyList[i]); + } + } + + // Clear for future call + PyDict_DelItemString(moduleDict.ptr(), "createPlugins"); + } + else { + MOBase::log::error("Plugin {}: missing a createPlugin(s) function.", + identifier); + } + + // If we have no plugins, there was an issue, and we already logged the + // problem: + if (plugins.empty()) { + return QList(); + } + + QList allInterfaceList; + + for (py::object pluginObj : plugins) { + + // Add the plugin to keep it alive: + m_PythonObjects[identifier].push_back(pluginObj); + + QList interfaceList; + + // appendIfInstance(pluginObj, interfaceList); + // Must try the wrapper because it's only a plugin extension + // interface in C++, so doesn't extend QObject + // appendIfInstance(pluginObj, + // interfaceList); Must try the wrapper because it's only a plugin + // extension interface in C++, so doesn't extend QObject + // appendIfInstance(pluginObj, + // interfaceList); + // appendIfInstance(pluginObj, + // interfaceList); + // appendIfInstance(pluginObj, + // interfaceList); appendIfInstance(pluginObj, + // interfaceList); appendIfInstance(pluginObj, + // interfaceList); appendIfInstance(pluginObj, + // interfaceList); + + if (interfaceList.isEmpty()) { + // appendIfInstance(pluginObj, interfaceList); + } + + if (interfaceList.isEmpty()) { + MOBase::log::error( + "Plugin {}: no plugin interface implemented.", identifier); + } + + // Append the plugins to the main list: + allInterfaceList.append(interfaceList); + } + + return allInterfaceList; } - else { - MOBase::log::error("Plugin {}: missing a createPlugin(s) function.", identifier); + catch (const py::error_already_set& ex) { + MOBase::log::error("Failed to import plugin from {}.", identifier); + throw pyexcept::PythonError(ex); } - - // If we have no plugins, there was an issue, and we already logged the problem: - if (plugins.empty()) { - return QList(); - } - - QList allInterfaceList; - - for (py::object pluginObj : plugins) { - - // Add the plugin to keep it alive: - m_PythonObjects[identifier].push_back(pluginObj); - - QList interfaceList; - - // appendIfInstance(pluginObj, interfaceList); - // Must try the wrapper because it's only a plugin extension interface in C++, so doesn't extend QObject - // appendIfInstance(pluginObj, interfaceList); - // Must try the wrapper because it's only a plugin extension interface in C++, so doesn't extend QObject - // appendIfInstance(pluginObj, interfaceList); - // appendIfInstance(pluginObj, interfaceList); - // appendIfInstance(pluginObj, interfaceList); - // appendIfInstance(pluginObj, interfaceList); - // appendIfInstance(pluginObj, interfaceList); - // appendIfInstance(pluginObj, interfaceList); - - if (interfaceList.isEmpty()) { - // appendIfInstance(pluginObj, interfaceList); - } - - if (interfaceList.isEmpty()) { - MOBase::log::error("Plugin {}: no plugin interface implemented.", identifier); - } - - // Append the plugins to the main list: - allInterfaceList.append(interfaceList); - } - - return allInterfaceList; - } - catch (const py::error_already_set&) { - MOBase::log::error("Failed to import plugin from {}.", identifier); - throw pyexcept::PythonError(); - } } void PythonRunner::unload(const QString& identifier) { - auto it = m_PythonObjects.find(identifier); - if (it != m_PythonObjects.end()) { + auto it = m_PythonObjects.find(identifier); + if (it != m_PythonObjects.end()) { - py::gil_scoped_acquire lock; + py::gil_scoped_acquire lock; - if (!identifier.endsWith(".py")) { + if (!identifier.endsWith(".py")) { - // At this point, the identifier is the full path to the module. - QDir folder(identifier); + // At this point, the identifier is the full path to the module. + QDir folder(identifier); - // We want to "unload" (remove from sys.modules) modules that come - // from this plugin (whose __path__ points under this module, including - // the module of the plugin itself). - py::object sys = py::module_::import("sys"); - py::dict modules = sys.attr("modules"); - py::list keys = modules.attr("keys")(); - for (std::size_t i = 0; i < py::len(keys); ++i) { - py::object mod = modules[keys[i]]; - if (PyObject_HasAttrString(mod.ptr(), "__path__")) { - QString mpath = mod.attr("__path__")[0].cast(); + // We want to "unload" (remove from sys.modules) modules that come + // from this plugin (whose __path__ points under this module, + // including the module of the plugin itself). + py::object sys = py::module_::import("sys"); + py::dict modules = sys.attr("modules"); + py::list keys = modules.attr("keys")(); + for (std::size_t i = 0; i < py::len(keys); ++i) { + py::object mod = modules[keys[i]]; + if (PyObject_HasAttrString(mod.ptr(), "__path__")) { + QString mpath = mod.attr("__path__")[0].cast(); - if (!folder.relativeFilePath(mpath).startsWith("..")) { - // If the path is under identifier, we need to unload it. - log::debug("Unloading module {} from {} for {}.", keys[i].cast(), mpath, identifier); + if (!folder.relativeFilePath(mpath).startsWith("..")) { + // If the path is under identifier, we need to unload + // it. + log::debug("Unloading module {} from {} for {}.", + keys[i].cast(), mpath, + identifier); - PyDict_DelItem(modules.ptr(), keys[i].ptr()); - } + PyDict_DelItem(modules.ptr(), keys[i].ptr()); + } + } + } } - } - } - // Boost.Python does not handle cyclic garbace collection, so we need to release - // everything hold by the objects before deleting the objects themselves (done when - // erasing from m_PythonObjects). - for (auto& obj : it->second) { - obj.attr("__dict__").attr("clear")(); - } + // Boost.Python does not handle cyclic garbace collection, so we need to + // release everything hold by the objects before deleting the objects + // themselves (done when erasing from m_PythonObjects). + for (auto& obj : it->second) { + obj.attr("__dict__").attr("clear")(); + } - log::debug("Deleting {} python objects for {}.", it->second.size(), identifier); - m_PythonObjects.erase(it); - } + log::debug("Deleting {} python objects for {}.", it->second.size(), + identifier); + m_PythonObjects.erase(it); + } } bool PythonRunner::isPythonInitialized() const { - return Py_IsInitialized() != 0; + return Py_IsInitialized() != 0; } diff --git a/src/runner-pybind11/pythonutils.cpp b/src/runner-pybind11/pythonutils.cpp index 142eb7d..a393bf9 100644 --- a/src/runner-pybind11/pythonutils.cpp +++ b/src/runner-pybind11/pythonutils.cpp @@ -2,6 +2,7 @@ #include #include +#include #include @@ -13,109 +14,172 @@ namespace py = pybind11; namespace mo2::python { - // Small structure to hold the levels - There are copy paste from - // my Python version and I assume these will not change soon: - struct PyLogLevel { - static constexpr int CRITICAL = 50; - static constexpr int ERROR = 40; - static constexpr int WARNING = 30; - static constexpr int INFO = 20; - static constexpr int DEBUG = 10; - }; + class PrintWrapper { + MOBase::log::Levels level_; + std::stringstream buffer_; - // This is the function we are going to use as our Handler .emit - // method. - void emit_function(py::object self, py::object record) - { + public: + PrintWrapper(MOBase::log::Levels level) : level_{level} {} - // There are other parameters that could be used, but this is minimal for - // now (filename, line number, etc.). - const int level = record.attr("levelno").cast(); - const std::wstring msg = py::str(record.attr("msg")).cast(); + void write(std::string_view message) + { + buffer_ << message; + if (buffer_.tellp() != 0 && buffer_.str().back() == '\n') { + const auto full_message = buffer_.str(); + MOBase::log::log( + level_, full_message.substr(0, full_message.length() - 1)); + buffer_ = std::stringstream{}; + } + } + }; - switch (level) { - case PyLogLevel::CRITICAL: - case PyLogLevel::ERROR: - MOBase::log::error("{}", msg); - break; - case PyLogLevel::WARNING: - MOBase::log::warn("{}", msg); - break; - case PyLogLevel::INFO: - MOBase::log::info("{}", msg); - break; - case PyLogLevel::DEBUG: - default: // There is a "NOTSET" level in theory: - MOBase::log::debug("{}", msg); - break; - } - }; + /** + * @brief Construct a dynamic Python type. + * + */ + template + pybind11::object make_python_type(std::string_view name, + pybind11::tuple base_classes, + Args&&... args) + { + // this is ugly but that's how it's done in C Python + auto type = py::reinterpret_borrow((PyObject*)&PyType_Type); - void configure_python_logging(py::module_ mobase) - { - // most of this is dealing with actual Python objects since it is not possible - // to derive from logging.Handler in C++ using Boost.Python, and since a lot of - // this would require extra register only for this. - - // see also https://github.com/pybind/pybind11/issues/1193#issuecomment-429451094 - - // retrieve the logging module and the Handler class. - auto logging = py::module_::import("logging"); - auto Handler = logging.attr("Handler"); - - // this is ugly but that's how it's done in C Python - auto type = py::reinterpret_borrow((PyObject*)&PyType_Type); - - // Create the "MO2Handler" python class: - auto methods = py::dict(); - methods["emit"] = py::cpp_function(emit_function); - auto MO2Handler = type("LogHandler", py::make_tuple(Handler), methods); - - // Create the default logger: - auto handler = MO2Handler(); - handler.attr("setLevel")(PyLogLevel::DEBUG); - auto logger = logging.attr("getLogger")(py::object(mobase.attr("__name__"))); - logger.attr("setLevel")(PyLogLevel::DEBUG); - logger.attr("addHandler")(handler); - - // Set mobase attributes: - mobase.attr("LogHandler") = MO2Handler; - mobase.attr("logger") = logger; - } - - void show_deprecation_warning(std::string_view name, std::string_view message, bool show_once) - { - - // Contains the list of filename / line number for which a deprecation warning has already been shown. - static std::set> DeprecatedLines; - - // Find the caller: - auto inspect = py::module_::import("inspect"); - auto current_frame = inspect.attr("currentframe")(); - py::sequence callable_frame = inspect.attr("getouterframes")(current_frame, 2); - auto filename = callable_frame[-1].attr("filename").cast(); - auto function = callable_frame[-1].attr("function").cast(); - auto lineno = callable_frame[-1].attr("lineno").cast(); - - // Only show once if requested: - if (show_once && DeprecatedLines.contains({ filename, lineno })) { - return; + // create the python class + return type(name, base_classes, py::dict(std::forward(args)...)); } - // Register the deprecation: - DeprecatedLines.emplace(filename, lineno); + void configure_python_stream() + { + // create the "MO2Handler" python class + auto printWrapper = make_python_type( + "MO2PrintWrapper", py::make_tuple(), + py::arg("write") = py::cpp_function([](std::string_view message) { + static PrintWrapper wrapper(MOBase::log::Debug); + wrapper.write(message); + })); + auto errorWrapper = make_python_type( + "MO2ErrorWrapper", py::make_tuple(), + py::arg("write") = py::cpp_function([](std::string_view message) { + static PrintWrapper wrapper(MOBase::log::Error); + wrapper.write(message); + })); + py::module_ sys = py::module_::import("sys"); + sys.attr("stdout") = printWrapper(); + sys.attr("stderr") = errorWrapper(); - auto path = relative(std::filesystem::path(filename), QCoreApplication::applicationDirPath().toStdWString()); - - // Show the message: - if (message.empty()) { - MOBase::log::warn( - "[deprecated] {} in {} [{}:{}].", name, function, path.native(), lineno); + // not sure if still needed for pybind11? + sys.attr("excepthook") = sys.attr("__excepthook__"); } - else { - MOBase::log::warn( - "[deprecated] {} in {} [{}:{}]: {}", name, function, path.native(), lineno, message); - } - } -} + // Small structure to hold the levels - There are copy paste from + // my Python version and I assume these will not change soon: + struct PyLogLevel { + static constexpr int CRITICAL = 50; + static constexpr int ERROR = 40; + static constexpr int WARNING = 30; + static constexpr int INFO = 20; + static constexpr int DEBUG = 10; + }; + + // This is the function we are going to use as our Handler .emit + // method. + void emit_function(py::object self, py::object record) + { + + // There are other parameters that could be used, but this is minimal + // for now (filename, line number, etc.). + const int level = record.attr("levelno").cast(); + const std::wstring msg = + py::str(record.attr("msg")).cast(); + + switch (level) { + case PyLogLevel::CRITICAL: + case PyLogLevel::ERROR: + MOBase::log::error("{}", msg); + break; + case PyLogLevel::WARNING: + MOBase::log::warn("{}", msg); + break; + case PyLogLevel::INFO: + MOBase::log::info("{}", msg); + break; + case PyLogLevel::DEBUG: + default: // There is a "NOTSET" level in theory: + MOBase::log::debug("{}", msg); + break; + } + }; + + void configure_python_logging(py::module_ mobase) + { + // most of this is dealing with actual Python objects since it is not + // possible to derive from logging.Handler in C++ using Boost.Python, + // and since a lot of this would require extra register only for this. + + // see also + // https://github.com/pybind/pybind11/issues/1193#issuecomment-429451094 + + // retrieve the logging module and the Handler class. + auto logging = py::module_::import("logging"); + auto Handler = logging.attr("Handler"); + + // create the "MO2Handler" python class + auto MO2Handler = + make_python_type("LogHandler", py::make_tuple(Handler), + py::arg("emit") = py::cpp_function(emit_function)); + + // create the default logger + auto handler = MO2Handler(); + handler.attr("setLevel")(PyLogLevel::DEBUG); + auto logger = + logging.attr("getLogger")(py::object(mobase.attr("__name__"))); + logger.attr("setLevel")(PyLogLevel::DEBUG); + logger.attr("addHandler")(handler); + + // set mobase attributes + mobase.attr("LogHandler") = MO2Handler; + mobase.attr("logger") = logger; + } + + void show_deprecation_warning(std::string_view name, + std::string_view message, bool show_once) + { + + // Contains the list of filename / line number for which a deprecation + // warning has already been shown. + static std::set> DeprecatedLines; + + // Find the caller: + auto inspect = py::module_::import("inspect"); + auto current_frame = inspect.attr("currentframe")(); + py::sequence callable_frame = + inspect.attr("getouterframes")(current_frame, 2); + auto filename = callable_frame[-1].attr("filename").cast(); + auto function = callable_frame[-1].attr("function").cast(); + auto lineno = callable_frame[-1].attr("lineno").cast(); + + // Only show once if requested: + if (show_once && DeprecatedLines.contains({filename, lineno})) { + return; + } + + // Register the deprecation: + DeprecatedLines.emplace(filename, lineno); + + auto path = + relative(std::filesystem::path(filename), + QCoreApplication::applicationDirPath().toStdWString()); + + // Show the message: + if (message.empty()) { + MOBase::log::warn("[deprecated] {} in {} [{}:{}].", name, function, + path.native(), lineno); + } + else { + MOBase::log::warn("[deprecated] {} in {} [{}:{}]: {}", name, + function, path.native(), lineno, message); + } + } + +} // namespace mo2::python diff --git a/src/runner-pybind11/pythonutils.h b/src/runner-pybind11/pythonutils.h index 59f936c..84286ff 100644 --- a/src/runner-pybind11/pythonutils.h +++ b/src/runner-pybind11/pythonutils.h @@ -7,26 +7,35 @@ namespace mo2::python { - /** - * @brief Configure logging for MO2 python plugin. - * - * @param mobase The mobase module. - */ - void configure_python_logging(pybind11::module_ mobase); + /** + * @brief Configure Python stdout and stderr to log to MO2. + * + */ + void configure_python_stream(); - /** - * @brief Show a deprecation warning. - * - * This methods will print a warning in MO2 log containing the location of the call to - * the deprecated function. If show_once is true, the deprecation warning will only be - * logged the first time the function is called at this location. - * - * @param name Name of the deprecated function. - * @param message Deprecation message. - * @param show_once Only show the message once per call location. - */ - void show_deprecation_warning(std::string_view name, std::string_view message = "", bool show_once = true); + /** + * @brief Configure logging for MO2 python plugin. + * + * @param mobase The mobase module. + */ + void configure_python_logging(pybind11::module_ mobase); -} + /** + * @brief Show a deprecation warning. + * + * This methods will print a warning in MO2 log containing the location of + * the call to the deprecated function. If show_once is true, the + * deprecation warning will only be logged the first time the function is + * called at this location. + * + * @param name Name of the deprecated function. + * @param message Deprecation message. + * @param show_once Only show the message once per call location. + */ + void show_deprecation_warning(std::string_view name, + std::string_view message = "", + bool show_once = true); + +} // namespace mo2::python #endif diff --git a/src/runner-pybind11/pythonwrapperutilities.h b/src/runner-pybind11/pythonwrapperutilities.h index 213eaff..5da5f7d 100644 --- a/src/runner-pybind11/pythonwrapperutilities.h +++ b/src/runner-pybind11/pythonwrapperutilities.h @@ -8,165 +8,198 @@ #include #include -#include "converters_qt_sip.h" #include "error.h" #include "gilock.h" +// #include "pybind11_qt/pybind11_qt.h" namespace details { - /** - * @brief Common stuffs for all basicWrapperFunction methods. - */ - template - ReturnType wrapperFunctionImplementation(WrapperTypePtr wrapper, bool apiTransfer, Fn fn, boost::python::object* objPtr, const char *methodName, Args... args) { - boost::python::override implementation = [&]() { - GILock lock; - return wrapper->get_override(methodName); - }(); - if (!implementation) { - if constexpr (std::is_same_v) { - throw pyexcept::MissingImplementation(wrapper->className, methodName); - } - else { - return std::invoke(fn, wrapper, args...); - } + /** + * @brief Common stuffs for all basicWrapperFunction methods. + */ + template + ReturnType + wrapperFunctionImplementation(WrapperTypePtr wrapper, bool apiTransfer, + Fn fn, boost::python::object* objPtr, + const char* methodName, Args... args) + { + boost::python::override implementation = [&]() { + GILock lock; + return wrapper->get_override(methodName); + }(); + if (!implementation) { + if constexpr (std::is_same_v) { + throw pyexcept::MissingImplementation(wrapper->className, + methodName); + } + else { + return std::invoke(fn, wrapper, args...); + } + } + + GILock lock; + try { + boost::python::object result = implementation(args...); + if (objPtr) { + *objPtr = result; + } + else if (apiTransfer) { + // pybind11::detail::qt::sipAPI()->api_transfer_to(result.ptr(), + // Py_None); + } + if constexpr (!std::is_same_v) { + return boost::python::extract(result)(); + } + } + catch (const boost::python::error_already_set&) { + throw pyexcept::PythonError(""); + } + catch (...) { + throw pyexcept::UnknownException(); + } } - GILock lock; - try { - boost::python::object result = implementation(args...); - if (objPtr) { - *objPtr = result; - } - else if (apiTransfer) { - mo2::details::sipAPI()->api_transfer_to(result.ptr(), Py_None); - } - if constexpr (!std::is_same_v) { - return boost::python::extract(result)(); - } - } - catch (const boost::python::error_already_set&) { - throw pyexcept::PythonError(); - } - catch (...) { - throw pyexcept::UnknownException(); - } - } - -} +} // namespace details /** - * @brief Call the given method on the wrapper with the given arguments, with proper - * exception handling. + * @brief Call the given method on the wrapper with the given arguments, with + * proper exception handling. * - * @param wrapper The wrapper object to use to retrieve the python method. Must have a publicly - * available `className` attribute. + * @param wrapper The wrapper object to use to retrieve the python method. Must + * have a publicly available `className` attribute. * @param methodName The name of the method. * @param args... Arguments for the method. * * @return the result of calling the given Python method on the wrapper. * * @throw pyexcept::MissingImplementation if the method does not exist. - * @throw pyexcept::PythonError if an error occurs while executing the python method. + * @throw pyexcept::PythonError if an error occurs while executing the python + * method. * @throw pyexecpt::UnknownException if an unknown error occurs. */ template -ReturnType basicWrapperFunctionImplementation(const WrapperType *wrapper, const char *methodName, Args... args) +ReturnType basicWrapperFunctionImplementation(const WrapperType* wrapper, + const char* methodName, + Args... args) { - return details::wrapperFunctionImplementation(wrapper, false, nullptr, nullptr, methodName, args...); + return details::wrapperFunctionImplementation( + wrapper, false, nullptr, nullptr, methodName, args...); } /** - * @brief Call the given method on the wrapper with the given arguments, with proper - * exception handling, and store the intermediate result in the given python object. + * @brief Call the given method on the wrapper with the given arguments, with + * proper exception handling, and store the intermediate result in the given + * python object. * - * @param wrapper The wrapper object to use to retrieve the python method. Must have a publicly - * available `className` attribute. - * @param ref Python object to which the result of `get_override()` should be stored. + * @param wrapper The wrapper object to use to retrieve the python method. Must + * have a publicly available `className` attribute. + * @param ref Python object to which the result of `get_override()` should be + * stored. * @param methodName The name of the method. * @param args... Arguments for the method. * * @return the result of calling the given Python method on the wrapper. * * @throw pyexcept::MissingImplementation if the method does not exist. - * @throw pyexcept::PythonError if an error occurs while executing the python method. + * @throw pyexcept::PythonError if an error occurs while executing the python + * method. * @throw pyexecpt::UnknownException if an unknown error occurs. */ template -ReturnType basicWrapperFunctionImplementation(const WrapperType* wrapper, boost::python::object &ref, const char* methodName, Args... args) +ReturnType basicWrapperFunctionImplementation(const WrapperType* wrapper, + boost::python::object& ref, + const char* methodName, + Args... args) { - return details::wrapperFunctionImplementation(wrapper, false, nullptr, &ref, methodName, args...); + return details::wrapperFunctionImplementation( + wrapper, false, nullptr, &ref, methodName, args...); } /** - * @brief Call the given method on the wrapper with the given arguments, with proper - * exception handling, and transfer the responsibility of the returned object to - * the C++ side. + * @brief Call the given method on the wrapper with the given arguments, with + * proper exception handling, and transfer the responsibility of the returned + * object to the C++ side. * - * @param wrapper The wrapper object to use to retrieve the python method. Must have a publicly - * available `className` attribute. + * @param wrapper The wrapper object to use to retrieve the python method. Must + * have a publicly available `className` attribute. * @param methodName The name of the method. * @param args... Arguments for the method. * * @return the result of calling the given Python method on the wrapper. * * @throw pyexcept::MissingImplementation if the method does not exist. - * @throw pyexcept::PythonError if an error occurs while executing the python method. + * @throw pyexcept::PythonError if an error occurs while executing the python + * method. * @throw pyexecpt::UnknownException if an unknown error occurs. */ template -ReturnType wrapperFunctionImplementationWithApiTransfer(const WrapperType* wrapper, const char* methodName, Args... args) +ReturnType wrapperFunctionImplementationWithApiTransfer( + const WrapperType* wrapper, const char* methodName, Args... args) { - return details::wrapperFunctionImplementation(wrapper, true, nullptr, nullptr, methodName, args...); + return details::wrapperFunctionImplementation( + wrapper, true, nullptr, nullptr, methodName, args...); } /** - * @brief Call the given method on the wrapper with the given arguments, with proper - * exception handling, falling back to the given function if the method does not exist. + * @brief Call the given method on the wrapper with the given arguments, with + * proper exception handling, falling back to the given function if the method + * does not exist. * - * @param wrapper The wrapper object to use to retrieve the python method. Must have a publicly - * available `className` attribute. + * @param wrapper The wrapper object to use to retrieve the python method. Must + * have a publicly available `className` attribute. * @param fn The function to call if the method does not exists. * @param methodName The name of the method. * @param args... Arguments for the method. * - * Note: `fn` does not have to be a member-function of `wrapper` but `std::invoke(fn, wrapper, args...)` must be valid. + * Note: `fn` does not have to be a member-function of `wrapper` but + * `std::invoke(fn, wrapper, args...)` must be valid. * * @return the result of calling the given Python method on the wrapper. * - * @throw pyexcept::PythonError if an error occurs while executing the python method. + * @throw pyexcept::PythonError if an error occurs while executing the python + * method. * @throw pyexecpt::UnknownException if an unknown error occurs. */ template -ReturnType basicWrapperFunctionImplementationWithDefault(WrapperTypePtr wrapper, Fn fn, const char* methodName, Args... args) +ReturnType basicWrapperFunctionImplementationWithDefault(WrapperTypePtr wrapper, + Fn fn, + const char* methodName, + Args... args) { - return details::wrapperFunctionImplementation(wrapper, false, fn, nullptr, methodName, args...); + return details::wrapperFunctionImplementation( + wrapper, false, fn, nullptr, methodName, args...); } /** - * @brief Call the given method on the wrapper with the given arguments, with proper - * exception handling, and store the intermediate result in the given python object, - * falling back to the given function if the method does not exist. + * @brief Call the given method on the wrapper with the given arguments, with + * proper exception handling, and store the intermediate result in the given + * python object, falling back to the given function if the method does not + * exist. * - * @param wrapper The wrapper object to use to retrieve the python method. Must have a publicly - * available `className` attribute. + * @param wrapper The wrapper object to use to retrieve the python method. Must + * have a publicly available `className` attribute. * @param fn The function to call if the method does not exists. - * @param ref Python object to which the result of `get_override()` should be stored. + * @param ref Python object to which the result of `get_override()` should be + * stored. * @param methodName The name of the method. * @param args... Arguments for the method. * - * Note: `fn` does not have to be a member-function of `wrapper` but `std::invoke(fn, wrapper, args...)` must be valid. + * Note: `fn` does not have to be a member-function of `wrapper` but + * `std::invoke(fn, wrapper, args...)` must be valid. * * @return the result of calling the given Python method on the wrapper. * - * @throw pyexcept::PythonError if an error occurs while executing the python method. + * @throw pyexcept::PythonError if an error occurs while executing the python + * method. * @throw pyexecpt::UnknownException if an unknown error occurs. */ template -ReturnType basicWrapperFunctionImplementationWithDefault(const WrapperType* wrapper, Fn fn, boost::python::object& ref, const char* methodName, Args... args) +ReturnType basicWrapperFunctionImplementationWithDefault( + const WrapperType* wrapper, Fn fn, boost::python::object& ref, + const char* methodName, Args... args) { - return details::wrapperFunctionImplementation(wrapper, false, fn, &ref, methodName, args...); + return details::wrapperFunctionImplementation( + wrapper, false, fn, &ref, methodName, args...); } - -#endif // PYTHONWRAPPERUTILITIES_H +#endif // PYTHONWRAPPERUTILITIES_H diff --git a/src/runner-pybind11/wrappers/uibase_wrappers.h b/src/runner-pybind11/wrappers/uibase_wrappers.h new file mode 100644 index 0000000..1f2a1f0 --- /dev/null +++ b/src/runner-pybind11/wrappers/uibase_wrappers.h @@ -0,0 +1,74 @@ +#ifndef PYTHON_WRAPPERS_UIBASE_H +#define PYTHON_WRAPPERS_UIBASE_H + +#include +#include +#include +#include + +#include + +#include +#include +#include +#include + +#include "pybind11_qt/pybind11_qt.h" + +namespace mo2::python { + + // this can be extended in C++, so why not in Python + class PyPluginRequirement : public MOBase::IPluginRequirement { + public: + std::optional + check(MOBase::IOrganizer* organizer) const override + { + PYBIND11_OVERRIDE_PURE(std::optional, IPluginRequirement, + check, organizer); + }; + }; + + // this needs to be extendable in Python, so actually needs a wrapper + class PySaveGame : public MOBase::ISaveGame { + public: + QString getFilepath() const override + { + PYBIND11_OVERRIDE_PURE(QString, ISaveGame, getFilepath, ); + } + + QDateTime getCreationTime() const override + { + PYBIND11_OVERRIDE_PURE(QDateTime, ISaveGame, getCreationTime, ); + } + + QString getName() const override + { + PYBIND11_OVERRIDE_PURE(QString, ISaveGame, getName, ); + } + + QString getSaveGroupIdentifier() const override + { + PYBIND11_OVERRIDE_PURE(QString, ISaveGame, + getSaveGroupIdentifier, ); + } + + QStringList allFiles() const override + { + PYBIND11_OVERRIDE_PURE(QStringList, ISaveGame, allFiles, ); + } + }; + + class PySaveGameInfoWidget : public MOBase::ISaveGameInfoWidget { + public: + // Bring the constructor: + using ISaveGameInfoWidget::ISaveGameInfoWidget; + + void setSave(MOBase::ISaveGame const& save) override + { + PYBIND11_OVERRIDE_PURE(void, ISaveGameInfoWidget, setSave, save); + } + }; + +} // namespace mo2::python + +#endif // UIBASEWRAPPERS_H