From d404a37feda44a1a45764870f81ef3fad5ecc4bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mika=C3=ABl=20Capelle?= Date: Sun, 10 May 2020 16:01:14 +0200 Subject: [PATCH] Add/fix bindings for ISaveGame and ISaveGameInfoWidget. --- src/runner/gamefeatureswrappers.cpp | 9 ++------ src/runner/gamefeatureswrappers.h | 7 ++++++ src/runner/pythonrunner.cpp | 17 ++++++++++++-- src/runner/pythonwrapperutilities.h | 36 ++++++++++++++++++++++++++++- 4 files changed, 59 insertions(+), 10 deletions(-) diff --git a/src/runner/gamefeatureswrappers.cpp b/src/runner/gamefeatureswrappers.cpp index 91a5e8f..a00b8b1 100644 --- a/src/runner/gamefeatureswrappers.cpp +++ b/src/runner/gamefeatureswrappers.cpp @@ -9,7 +9,6 @@ #include #include -#include "gilock.h" #include "pythonwrapperutilities.h" ///////////////////////////// @@ -106,7 +105,7 @@ bool LocalSavegamesWrapper::prepareProfile(MOBase::IProfile * profile) MOBase::ISaveGame const * SaveGameInfoWrapper::getSaveGameInfo(QString const & file) const { - return basicWrapperFunctionImplementation(this, "getSaveGameInfo", file); + return basicWrapperFunctionImplementation(this, m_SaveGames[file], "getSaveGameInfo", file); } SaveGameInfoWrapper::MissingAssets SaveGameInfoWrapper::getMissingAssets(QString const & file) const @@ -116,11 +115,7 @@ SaveGameInfoWrapper::MissingAssets SaveGameInfoWrapper::getMissingAssets(QString MOBase::ISaveGameInfoWidget* SaveGameInfoWrapper::getSaveGameWidget(QWidget* parent) const { - // This will require a lot of works as ISaveGameInfoWidget inherits QWidget and I currently found no - // way of exposing a class that inherits QWidget without having to expose manually the whole QWidget, - // and even with this, I am not sure how this would fit with PyQt/sip. - qCritical("Calling method with unimplemented from_python converter."); - return nullptr; + return basicWrapperFunctionImplementation(this, m_SaveGameWidget, "getSaveGameWidget", parent); } bool SaveGameInfoWrapper::hasScriptExtenderSave(QString const & file) const diff --git a/src/runner/gamefeatureswrappers.h b/src/runner/gamefeatureswrappers.h index 05191ef..9eee37c 100644 --- a/src/runner/gamefeatureswrappers.h +++ b/src/runner/gamefeatureswrappers.h @@ -1,6 +1,8 @@ #ifndef GAMEFEATURESWRAPPERS_H #define GAMEFEATURESWRAPPERS_H +#include + #include #include #include @@ -71,6 +73,11 @@ public: virtual MissingAssets getMissingAssets(QString const &file) const override; virtual MOBase::ISaveGameInfoWidget *getSaveGameWidget(QWidget *parent = 0) const override; virtual bool hasScriptExtenderSave(QString const &file) const override; + +private: + // We need to keep the python objects alive: + mutable std::map m_SaveGames; + mutable boost::python::object m_SaveGameWidget; }; class ScriptExtenderWrapper : public ScriptExtender, public boost::python::wrapper diff --git a/src/runner/pythonrunner.cpp b/src/runner/pythonrunner.cpp index 7ee3524..97e8f8c 100644 --- a/src/runner/pythonrunner.cpp +++ b/src/runner/pythonrunner.cpp @@ -703,7 +703,7 @@ BOOST_PYTHON_MODULE(mobase) .def("isCustom", &ExecutableInfo::isCustom) ; - bpy::class_("ISaveGame") + bpy::class_, ISaveGameWrapper*, boost::noncopyable>("ISaveGame") .def("getFilename", bpy::pure_virtual(&ISaveGame::getFilename)) .def("getCreationTime", bpy::pure_virtual(&ISaveGame::getCreationTime)) .def("getSaveGroupIdentifier", bpy::pure_virtual(&ISaveGame::getSaveGroupIdentifier)) @@ -711,7 +711,20 @@ BOOST_PYTHON_MODULE(mobase) .def("hasScriptExtenderFile", bpy::pure_virtual(&ISaveGame::hasScriptExtenderFile)) ; - // TODO: ISaveGameInfoWidget. + // This is tricky because there is no way to tell boost::python that ISaveGameInfoWidget inherits + // QWidget (bpy::bases crashes when loading the module... ). Without it, the python class + // does not expose the QWidget methods, which makes it useless. + // There is two way to do this: 1) expose the widget via a `_widget()` method that basically returns + // the object, but as a QWidget, or 2) override __getattr__ to forward everything to the QWidget (note + // that __getattr__ is only called if the attribute is not found in the class by standard mean). + bpy::class_, ISaveGameInfoWidgetWrapper*, boost::noncopyable>("ISaveGameInfoWidget", bpy::init>()) + .def("setSave", bpy::pure_virtual(&ISaveGameInfoWidget::setSave)) + .def("__getattr__", +[](ISaveGameInfoWidget *w, bpy::str str) -> bpy::object { + // Create an object corresponding to the widget: + bpy::object obj{ (QWidget*)w }; + return obj.attr(str); + }) + ; bpy::class_("FileInfo", bpy::init<>()) .def_readwrite("filePath", &IOrganizer::FileInfo::filePath) diff --git a/src/runner/pythonwrapperutilities.h b/src/runner/pythonwrapperutilities.h index d11acc2..6b25478 100644 --- a/src/runner/pythonwrapperutilities.h +++ b/src/runner/pythonwrapperutilities.h @@ -3,18 +3,26 @@ #include +#include + #include #include "error.h" +#include "gilock.h" +/** + * @brief Call the given method on the wrapper with the given arguments, with proper + * exception handling. + */ template ReturnType basicWrapperFunctionImplementation(const WrapperType *wrapper, const char *methodName, Args... args) { try { GILock lock; boost::python::override implementation = wrapper->get_override(methodName); - if (!implementation) + if (!implementation) { throw pyexcept::MissingImplementation(wrapper->className, methodName); + } return implementation(args...).as(); } catch (const boost::python::error_already_set&) { @@ -28,6 +36,32 @@ ReturnType basicWrapperFunctionImplementation(const WrapperType *wrapper, const } } +/** + * @brief Similar to the first-overload but also stores the python object in the given reference. + */ +template +ReturnType basicWrapperFunctionImplementation(const WrapperType* wrapper, boost::python::object &ref, const char* methodName, Args... args) +{ + try { + GILock lock; + boost::python::override implementation = wrapper->get_override(methodName); + if (!implementation) { + throw pyexcept::MissingImplementation(wrapper->className, methodName); + } + ref = implementation(args...); + return boost::python::extract(ref)(); + } + catch (const boost::python::error_already_set&) { + throw pyexcept::PythonError(); + } + catch (pyexcept::MissingImplementation const& missingImplementation) { + throw missingImplementation; + } + catch (...) { + throw pyexcept::UnknownException(); + } +} + template ReturnType basicWrapperFunctionImplementationWithDefault(WrapperType* wrapper, Fn fn, const char* methodName, Args... args) {