diff --git a/src/runner/CMakeLists.txt b/src/runner/CMakeLists.txt index 060055b..8750dd7 100644 --- a/src/runner/CMakeLists.txt +++ b/src/runner/CMakeLists.txt @@ -34,6 +34,7 @@ SET(project_path "${default_project_path}" CACHE PATH "path to the other mo proj SET(lib_path "${project_path}/../../install/libs") INCLUDE_DIRECTORIES(${project_path}/uibase/src + ${project_path}/game_features/src ${PYTHON_ROOT}/Include ${SIP_ROOT} ${PYTHON_ROOT}/PC) diff --git a/src/runner/gamefeatureswrappers.cpp b/src/runner/gamefeatureswrappers.cpp new file mode 100644 index 0000000..b887e02 --- /dev/null +++ b/src/runner/gamefeatureswrappers.cpp @@ -0,0 +1,391 @@ +#include "gamefeatureswrappers.h" + +#include + +#include + +#include +#include +#include +#include + +#include "gilock.h" +#include "pycatch.h" + +///////////////////////////// +/// BSAInvalidation Wrapper + + +bool BSAInvalidationWrapper::isInvalidationBSA(const QString &bsaName) +{ + GILock lock; + + try { + return this->get_override("isInvalidationBSA")(bsaName); + } PYCATCH; +} + +void BSAInvalidationWrapper::deactivate(MOBase::IProfile *profile) +{ + GILock lock; + + try { + this->get_override("deactivate")(boost::python::ptr(profile)); + } PYCATCH; +} + +void BSAInvalidationWrapper::activate(MOBase::IProfile *profile) +{ + GILock lock; + + try { + this->get_override("activate")(boost::python::ptr(profile)); + } PYCATCH; +} +/// end BSAInvalidation Wrapper +///////////////////////////// +/// DataArchives Wrapper + + +QStringList DataArchivesWrapper::vanillaArchives() const +{ + GILock lock; + + try { + return this->get_override("vanillaArchives")(); + } PYCATCH; +} + +QStringList DataArchivesWrapper::archives(const MOBase::IProfile *profile) const +{ + GILock lock; + + try { + return this->get_override("archives")(boost::python::ptr(profile)); + } PYCATCH; +} + +void DataArchivesWrapper::addArchive(MOBase::IProfile *profile, int index, const QString &archiveName) +{ + GILock lock; + + try { + this->get_override("addArchive")(boost::python::ptr(profile), index, archiveName); + } PYCATCH; +} + +void DataArchivesWrapper::removeArchive(MOBase::IProfile * profile, const QString & archiveName) +{ + GILock lock; + + try { + this->get_override("removeArchive")(boost::python::ptr(profile), archiveName); + } PYCATCH; +} +/// end DataArchives Wrapper +///////////////////////////// +/// GamePlugins Wrapper + + +void GamePluginsWrapper::writePluginLists(const MOBase::IPluginList * pluginList) +{ + GILock lock; + + try { + this->get_override("writePluginLists")(boost::python::ptr(pluginList)); + } PYCATCH; +} + +void GamePluginsWrapper::readPluginLists(MOBase::IPluginList * pluginList) +{ + GILock lock; + + try { + this->get_override("readPluginLists")(boost::python::ptr(pluginList)); + } PYCATCH; +} +/// end GamePlugins Wrapper +///////////////////////////// +/// LocalSavegames Wrapper + + +MappingType LocalSavegamesWrapper::mappings(const QDir & profileSaveDir) const +{ + GILock lock; + + try { + return this->get_override("mappings")(profileSaveDir); + } PYCATCH; +} + +void LocalSavegamesWrapper::prepareProfile(MOBase::IProfile * profile) +{ + GILock lock; + + try { + this->get_override("prepareProfile")(boost::python::ptr(profile)); + } PYCATCH; +} +/// end LocalSavegames Wrapper +///////////////////////////// +/// SaveGameInfo Wrapper + + +MOBase::ISaveGame const * SaveGameInfoWrapper::getSaveGameInfo(QString const & file) const +{ + GILock lock; + + try { + return this->get_override("getSaveGameInfo")(file); + } PYCATCH; +} + +SaveGameInfoWrapper::MissingAssets SaveGameInfoWrapper::getMissingAssets(QString const & file) const +{ + GILock lock; + + try { + return this->get_override("getMissingAssets")(file); + } PYCATCH; +} + +MOBase::ISaveGameInfoWidget * SaveGameInfoWrapper::getSaveGameWidget(QWidget * parent) const +{ + qCritical("Calling method with unimplemented from_python converter."); + + GILock lock; + + try { + return this->get_override("getSaveGameWidget")(boost::python::ptr(parent)); + } PYCATCH; +} + +bool SaveGameInfoWrapper::hasScriptExtenderSave(QString const & file) const +{ + GILock lock; + + try { + return this->get_override("hasScriptExtenderSave")(file); + } PYCATCH; +} +/// end SaveGameInfo Wrapper +///////////////////////////// +/// ScriptExtender Wrapper + +QString ScriptExtenderWrapper::BinaryName() const +{ + GILock lock; + + try { + return this->get_override("BinaryName")(); + } PYCATCH; +} + +QString ScriptExtenderWrapper::PluginPath() const +{ + GILock lock; + + try { + return this->get_override("PluginPath")(); + } PYCATCH; +} + +QString ScriptExtenderWrapper::loaderName() const +{ + GILock lock; + + try { + return this->get_override("loaderName")(); + } PYCATCH; +} + +QString ScriptExtenderWrapper::loaderPath() const +{ + GILock lock; + + try { + return this->get_override("loaderPath")(); + } PYCATCH; +} + +QStringList ScriptExtenderWrapper::saveGameAttachmentExtensions() const +{ + GILock lock; + + try { + return this->get_override("saveGameAttachmentExtensions")(); + } PYCATCH; +} + +bool ScriptExtenderWrapper::isInstalled() const +{ + GILock lock; + + try { + return this->get_override("isInstalled")(); + } PYCATCH; +} + +QString ScriptExtenderWrapper::getExtenderVersion() const +{ + GILock lock; + + try { + return this->get_override("getExtenderVersion")(); + } PYCATCH; +} + +WORD ScriptExtenderWrapper::getArch() const +{ + GILock lock; + + try { + return this->get_override("getArch")(); + } PYCATCH; +} + +/// end ScriptExtender Wrapper +///////////////////////////// +/// UnmanagedMods Wrapper + + +QStringList UnmanagedModsWrapper::mods(bool onlyOfficial) const +{ + GILock lock; + + try { + return this->get_override("mods")(onlyOfficial); + } PYCATCH; +} + +QString UnmanagedModsWrapper::displayName(const QString & modName) const +{ + GILock lock; + + try { + return this->get_override("displayName")(modName); + } PYCATCH; +} + +QFileInfo UnmanagedModsWrapper::referenceFile(const QString & modName) const +{ + GILock lock; + + try { + return this->get_override("referenceFile")(modName); + } PYCATCH; +} + +QStringList UnmanagedModsWrapper::secondaryFiles(const QString & modName) const +{ + GILock lock; + + try { + return this->get_override("secondaryFiles")(modName); + } PYCATCH; +} +/// end UnmanagedMods Wrapper +///////////////////////////// + +template +void insertGameFeature(std::map &map, const boost::python::object &pyObject) +{ + map[std::type_index(typeid(T))] = boost::python::extract(pyObject)(); +} + +game_features_map_from_python::game_features_map_from_python() +{ + boost::python::converter::registry::push_back(&convertible, &construct, boost::python::type_id>()); +} + +void * game_features_map_from_python::convertible(PyObject * objPtr) +{ + return PyDict_Check(objPtr) ? objPtr : nullptr; +} + +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]; + // pyKey should be a Boost.Python.class corresponding to a game feature. + std::string className = boost::python::extract(pyKey.attr("__name__"))(); + if (className == "BSAInvalidation") + insertGameFeature(*result, source[pyKey]); + else if (className == "DataArchives") + insertGameFeature(*result, source[pyKey]); + else if (className == "GamePlugins") + insertGameFeature(*result, source[pyKey]); + else if (className == "LocalSavegames") + insertGameFeature(*result, source[pyKey]); + else if (className == "SaveGameInfo") + insertGameFeature(*result, source[pyKey]); + else if (className == "ScriptExtender") + insertGameFeature(*result, source[pyKey]); + else if (className == "UnmanagedMods") + insertGameFeature(*result, source[pyKey]); + } + + data->convertible = storage; +} + +void registerGameFeaturesPythonConverters() +{ + namespace bpy = boost::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)) + .def("deactivate", bpy::pure_virtual(&BSAInvalidation::deactivate)) + .def("activate", bpy::pure_virtual(&BSAInvalidation::activate)) + ; + + bpy::class_("DataArchives") + .def("vanillaArchives", bpy::pure_virtual(&DataArchives::vanillaArchives)) + .def("archives", bpy::pure_virtual(&DataArchives::archives)) + .def("addArchive", bpy::pure_virtual(&DataArchives::addArchive)) + .def("removeArchive", bpy::pure_virtual(&DataArchives::removeArchive)) + ; + + bpy::class_("GamePlugins") + .def("writePluginLists", bpy::pure_virtual(&GamePlugins::writePluginLists)) + .def("readPluginLists", bpy::pure_virtual(&GamePlugins::readPluginLists)) + ; + + bpy::class_("LocalSavegames") + .def("mappings", bpy::pure_virtual(&LocalSavegames::mappings)) + .def("prepareProfile", bpy::pure_virtual(&LocalSavegames::prepareProfile)) + ; + + bpy::class_("SaveGameInfo") + .def("getSaveGameInfo", bpy::pure_virtual(&SaveGameInfo::getSaveGameInfo), bpy::return_value_policy()) + .def("getMissingAssets", bpy::pure_virtual(&SaveGameInfo::getMissingAssets)) + .def("getSaveGameWidget", bpy::pure_virtual(&SaveGameInfo::getSaveGameWidget), bpy::return_value_policy()) + .def("hasScriptExtenderSave", bpy::pure_virtual(&SaveGameInfo::hasScriptExtenderSave)) + ; + + 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("saveGameAttachmentExtensions", bpy::pure_virtual(&ScriptExtender::saveGameAttachmentExtensions)) + .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)) + .def("displayName", bpy::pure_virtual(&UnmanagedMods::displayName)) + .def("referenceFile", bpy::pure_virtual(&UnmanagedMods::referenceFile)) + .def("secondaryFiles", bpy::pure_virtual(&UnmanagedMods::secondaryFiles)) + ; +} diff --git a/src/runner/gamefeatureswrappers.h b/src/runner/gamefeatureswrappers.h new file mode 100644 index 0000000..889889a --- /dev/null +++ b/src/runner/gamefeatureswrappers.h @@ -0,0 +1,85 @@ +#ifndef GAMEFEATURESWRAPPERS_H +#define GAMEFEATURESWRAPPERS_H + +#include +#include +#include +#include +#include +#include +#include + +// this might need turning off if Q_MOC_RUN is defined +#include + +///////////////////////////// +/// Wrapper declarations + +class BSAInvalidationWrapper : public BSAInvalidation, public boost::python::wrapper +{ + virtual bool isInvalidationBSA(const QString &bsaName) override; + virtual void deactivate(MOBase::IProfile *profile) override; + virtual void activate(MOBase::IProfile *profile) override; +}; + +class DataArchivesWrapper : public DataArchives, public boost::python::wrapper +{ + virtual QStringList vanillaArchives() const override; + virtual QStringList archives(const MOBase::IProfile *profile) const override; + virtual void addArchive(MOBase::IProfile *profile, int index, const QString &archiveName) override; + virtual void removeArchive(MOBase::IProfile *profile, const QString &archiveName) override; +}; + +class GamePluginsWrapper : public GamePlugins, public boost::python::wrapper +{ + virtual void writePluginLists(const MOBase::IPluginList *pluginList) override; + virtual void readPluginLists(MOBase::IPluginList *pluginList) override; +}; + +class LocalSavegamesWrapper : public LocalSavegames, public boost::python::wrapper +{ + virtual MappingType mappings(const QDir &profileSaveDir) const override; + virtual void prepareProfile(MOBase::IProfile *profile) override; +}; + +class SaveGameInfoWrapper : public SaveGameInfo, public boost::python::wrapper +{ + virtual MOBase::ISaveGame const *getSaveGameInfo(QString const &file) const override; + 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; +}; + +class ScriptExtenderWrapper : public ScriptExtender, public boost::python::wrapper +{ + virtual QString BinaryName() const override; + virtual QString PluginPath() const override; + virtual QString loaderName() const override; + virtual QString loaderPath() const override; + virtual QStringList saveGameAttachmentExtensions() const override; + virtual bool isInstalled() const override; + virtual QString getExtenderVersion() const override; + virtual WORD getArch() const override; +}; + +class UnmanagedModsWrapper : public UnmanagedMods, public boost::python::wrapper +{ + virtual QStringList mods(bool onlyOfficial) const override; + virtual QString displayName(const QString &modName) const override; + virtual QFileInfo referenceFile(const QString &modName) const override; + virtual QStringList secondaryFiles(const QString &modName) const override; +}; + +/// end Wrapper declarations +///////////////////////////// + +struct game_features_map_from_python +{ + game_features_map_from_python(); + static void *convertible(PyObject *objPtr); + static void construct(PyObject *objPtr, boost::python::converter::rvalue_from_python_stage1_data *data); +}; + +void registerGameFeaturesPythonConverters(); + +#endif // GAMEFEATURESWRAPPERS_H diff --git a/src/runner/proxypluginwrappers.cpp b/src/runner/proxypluginwrappers.cpp index 104575b..da807aa 100644 --- a/src/runner/proxypluginwrappers.cpp +++ b/src/runner/proxypluginwrappers.cpp @@ -1,8 +1,10 @@ #include "proxypluginwrappers.h" -#include -#include "error.h" + #include "gilock.h" +#include #include +#include "pycatch.h" +#include "sipApiAccess.h" namespace boost { @@ -21,204 +23,386 @@ namespace boost using namespace MOBase; -#define PYCATCH catch (const boost::python::error_already_set &) { reportPythonError(); throw MyException("unhandled exception"); }\ - catch (...) { throw MyException("An unknown exception was thrown in python code"); } +#define COMMON_I_PLUGIN_WRAPPER_DEFINITIONS(class_name) \ +bool class_name::init(MOBase::IOrganizer *moInfo) \ +{ \ + try { \ + return this->get_override("init")(boost::python::ptr(moInfo)); \ + } PYCATCH; \ +} \ + \ +QString class_name::name() const \ +{ \ + try { \ + return this->get_override("name")().as(); \ + } PYCATCH; \ +} \ + \ +QString class_name::author() const \ +{ \ + try { \ + return this->get_override("author")().as(); \ + } PYCATCH; \ +} \ + \ +QString class_name::description() const \ +{ \ + try { \ + return this->get_override("description")().as(); \ + } PYCATCH; \ +} \ + \ +MOBase::VersionInfo class_name::version() const \ +{ \ + try { \ + return this->get_override("version")().as(); \ + } PYCATCH; \ +} \ + \ +bool class_name::isActive() const \ +{ \ + try { \ + return this->get_override("isActive")().as(); \ + } PYCATCH; \ +} \ + \ +QList class_name::settings() const \ +{ \ + try { \ + return this->get_override("settings")().as>(); \ + } PYCATCH; \ +} +/// end COMMON_I_PLUGIN_WRAPPER_DEFINITIONS ///////////////////////////// /// IPlugin Wrapper -bool IPluginWrapper::init(MOBase::IOrganizer *moInfo) -{ - try { - return this->get_override("init")(boost::python::ptr(moInfo)); - } PYCATCH; -} -QString IPluginWrapper::name() const -{ - try { - return this->get_override("name")().as(); - } PYCATCH; -} -QString IPluginWrapper::author() const -{ - try { - return this->get_override("author")().as(); - } PYCATCH; -} - -QString IPluginWrapper::description() const -{ - try { - return this->get_override("description")().as(); - } PYCATCH; -} - -MOBase::VersionInfo IPluginWrapper::version() const -{ - try { - return this->get_override("version")().as(); - } PYCATCH; -} - -bool IPluginWrapper::isActive() const -{ - try { - return this->get_override("isActive")().as(); - } PYCATCH; -} - -QList IPluginWrapper::settings() const -{ - try { - return this->get_override("settings")().as>(); - } PYCATCH; -} +COMMON_I_PLUGIN_WRAPPER_DEFINITIONS(IPluginWrapper) /// end IPlugin Wrapper -///////////////////////////// -/// IPluginTool Wrapper +///////////////////////////////////// +/// IPluginDiagnose Wrapper -bool IPluginToolWrapper::init(MOBase::IOrganizer *moInfo) -{ - try { - return this->get_override("init")(boost::python::ptr(moInfo)); - } PYCATCH; -} +COMMON_I_PLUGIN_WRAPPER_DEFINITIONS(IPluginDiagnoseWrapper) -QString IPluginToolWrapper::name() const -{ - try { - return this->get_override("name")().as(); - } PYCATCH; -} - -QString IPluginToolWrapper::author() const -{ - try { - return this->get_override("author")().as(); - } PYCATCH; -} - -QString IPluginToolWrapper::description() const -{ - try { - return this->get_override("description")().as(); - } PYCATCH; -} - -MOBase::VersionInfo IPluginToolWrapper::version() const -{ - try { - return this->get_override("version")().as(); - } PYCATCH; -} - -bool IPluginToolWrapper::isActive() const -{ - try { - return this->get_override("isActive")().as(); - } PYCATCH; -} - -QList IPluginToolWrapper::settings() const -{ - try { - return this->get_override("settings")().as>(); - } PYCATCH; -} - -QString IPluginToolWrapper::displayName() const -{ - try { - return this->get_override("displayName")().as(); - } PYCATCH; -} - -QString IPluginToolWrapper::tooltip() const -{ - try { - return this->get_override("tooltip")().as(); - } PYCATCH; -} - -QIcon IPluginToolWrapper::icon() const -{ - try { - return this->get_override("icon")().as(); - } PYCATCH; -} - -void IPluginToolWrapper::setParentWidget(QWidget *parent) -{ - try { - this->get_override("setParentWidget")(parent); - } PYCATCH; -} - -void IPluginToolWrapper::display() const +std::vector IPluginDiagnoseWrapper::activeProblems() const { try { GILock lock; - this->get_override("display")(); + return this->get_override("activeProblems")(); } PYCATCH; } -/// end IPluginTool Wrapper +QString IPluginDiagnoseWrapper::shortDescription(unsigned int key) const +{ + try { + return this->get_override("shortDescription")(key); + } PYCATCH; +} + +QString IPluginDiagnoseWrapper::fullDescription(unsigned int key) const +{ + try { + return this->get_override("fullDescription")(key); + } PYCATCH; +} + +bool IPluginDiagnoseWrapper::hasGuidedFix(unsigned int key) const +{ + try { + return this->get_override("hasGuidedFix")(key); + } PYCATCH; +} + +void IPluginDiagnoseWrapper::startGuidedFix(unsigned int key) const +{ + try { + GILock lock; + + this->get_override("startGuidedFix")(key); + } PYCATCH; +} + +void IPluginDiagnoseWrapper::invalidate() +{ + IPluginDiagnose::invalidate(); +} +/// end IPluginDiagnose Wrapper +///////////////////////////////////// +/// IPluginFileMapper Wrapper + + +COMMON_I_PLUGIN_WRAPPER_DEFINITIONS(IPluginFileMapperWrapper) + +MappingType IPluginFileMapperWrapper::mappings() const +{ + try { + return this->get_override("mappings")(); + } PYCATCH; +} +/// end IPluginFileMapper Wrapper +///////////////////////////////////// +/// IPluginGame Wrapper + + +QString IPluginGameWrapper::gameName() const +{ + GILock lock; + try { + return this->get_override("gameName")(); + } PYCATCH; +} + +void IPluginGameWrapper::initializeProfile(const QDir & directory, ProfileSettings settings) const +{ + GILock lock; + try { + this->get_override("initializeProfile")(directory, settings); + } PYCATCH; +} + +QString IPluginGameWrapper::savegameExtension() const +{ + GILock lock; + try { + return this->get_override("savegameExtension")(); + } PYCATCH; +} + +QString IPluginGameWrapper::savegameSEExtension() const +{ + GILock lock; + try { + return this->get_override("savegameSEExtension")(); + } PYCATCH; +} + +bool IPluginGameWrapper::isInstalled() const +{ + GILock lock; + try { + return this->get_override("isInstalled")(); + } PYCATCH; +} + +QIcon IPluginGameWrapper::gameIcon() const +{ + GILock lock; + try { + return this->get_override("gameIcon")(); + } PYCATCH; +} + +QDir IPluginGameWrapper::gameDirectory() const +{ + GILock lock; + try { + return this->get_override("gameDirectory")(); + } PYCATCH; +} + +QDir IPluginGameWrapper::dataDirectory() const +{ + GILock lock; + try { + return this->get_override("dataDirectory")(); + } PYCATCH; +} + +void IPluginGameWrapper::setGamePath(const QString & path) +{ + GILock lock; + try { + this->get_override("setGamePath")(path); + } PYCATCH; +} + +QDir IPluginGameWrapper::documentsDirectory() const +{ + GILock lock; + try { + return this->get_override("documentsDirectory")(); + } PYCATCH; +} + +QDir IPluginGameWrapper::savesDirectory() const +{ + GILock lock; + try { + return this->get_override("savesDirectory")(); + } PYCATCH; +} + +QList IPluginGameWrapper::executables() const +{ + GILock lock; + try { + return this->get_override("executables")(); + } PYCATCH; +} + +QString IPluginGameWrapper::steamAPPId() const +{ + GILock lock; + try { + return this->get_override("steamAPPId")(); + } PYCATCH; +} + +QStringList IPluginGameWrapper::primaryPlugins() const +{ + GILock lock; + try { + return this->get_override("primaryPlugins")(); + } PYCATCH; +} + +QStringList IPluginGameWrapper::gameVariants() const +{ + GILock lock; + try { + return this->get_override("gameVariants")(); + } PYCATCH; +} + +void IPluginGameWrapper::setGameVariant(const QString & variant) +{ + GILock lock; + try { + this->get_override("setGameVariant")(variant); + } PYCATCH; +} + +QString IPluginGameWrapper::binaryName() const +{ + GILock lock; + try { + return this->get_override("binaryName")(); + } PYCATCH; +} + +QString IPluginGameWrapper::gameShortName() const +{ + GILock lock; + try { + return this->get_override("gameShortName")(); + } PYCATCH; +} + +QStringList IPluginGameWrapper::validShortNames() const +{ + GILock lock; + try { + return this->get_override("validShortNames")(); + } PYCATCH; +} + +QString IPluginGameWrapper::gameNexusName() const +{ + GILock lock; + try { + return this->get_override("gameNexusName")(); + } PYCATCH; +} + +QStringList IPluginGameWrapper::iniFiles() const +{ + GILock lock; + try { + return this->get_override("iniFiles")(); + } PYCATCH; +} + +QStringList IPluginGameWrapper::DLCPlugins() const +{ + GILock lock; + try { + return this->get_override("DLCPlugins")(); + } PYCATCH; +} + +QStringList IPluginGameWrapper::CCPlugins() const +{ + GILock lock; + try { + return this->get_override("CCPlugins")(); + } PYCATCH; +} + +IPluginGame::LoadOrderMechanism IPluginGameWrapper::loadOrderMechanism() const +{ + GILock lock; + try { + return this->get_override("loadOrderMechanism")(); + } PYCATCH; +} + +IPluginGame::SortMechanism IPluginGameWrapper::sortMechanism() const +{ + GILock lock; + try { + return this->get_override("sortMechanism")(); + } PYCATCH; +} + +int IPluginGameWrapper::nexusModOrganizerID() const +{ + GILock lock; + try { + return this->get_override("nexusModOrganizerID")(); + } PYCATCH; +} + +int IPluginGameWrapper::nexusGameID() const +{ + GILock lock; + try { + return this->get_override("nexusGameID")(); + } PYCATCH; +} + +bool IPluginGameWrapper::looksValid(QDir const & dir) const +{ + GILock lock; + try { + return this->get_override("looksValid")(dir); + } PYCATCH; +} + +QString IPluginGameWrapper::gameVersion() const +{ + GILock lock; + try { + return this->get_override("gameVersion")(); + } PYCATCH; +} + +QString IPluginGameWrapper::getLauncherName() const +{ + GILock lock; + try { + return this->get_override("getLauncherName")(); + } PYCATCH; +} + +COMMON_I_PLUGIN_WRAPPER_DEFINITIONS(IPluginGameWrapper) + +std::map IPluginGameWrapper::featureList() const +{ + GILock lock; + try { + return this->get_override("_featureList")(); + } PYCATCH; +} +/// end IPluginGame Wrapper ///////////////////////////////////// /// IPluginInstallerCustom Wrapper -bool IPluginInstallerCustomWrapper::init(MOBase::IOrganizer *moInfo) -{ - try { - return this->get_override("init")(boost::python::ptr(moInfo)); - } PYCATCH; -} - -QString IPluginInstallerCustomWrapper::name() const -{ - try { - return this->get_override("name")().as(); - } PYCATCH; -} - -QString IPluginInstallerCustomWrapper::author() const -{ - try { - return this->get_override("author")().as(); - } PYCATCH; -} - -QString IPluginInstallerCustomWrapper::description() const -{ - try { - return this->get_override("description")().as(); - } PYCATCH; -} - -MOBase::VersionInfo IPluginInstallerCustomWrapper::version() const -{ - try { - return this->get_override("version")(); - } PYCATCH; -} - -bool IPluginInstallerCustomWrapper::isActive() const -{ - try { - return this->get_override("isActive")(); - } PYCATCH; -} - -QList IPluginInstallerCustomWrapper::settings() const -{ - try { - return this->get_override("settings")().as>(); - } PYCATCH; -} +COMMON_I_PLUGIN_WRAPPER_DEFINITIONS(IPluginInstallerCustomWrapper) unsigned int IPluginInstallerCustomWrapper::priority() const { @@ -272,5 +456,119 @@ void IPluginInstallerCustomWrapper::setParentWidget(QWidget *parent) this->get_override("setParentWidget")(parent); } PYCATCH; } +/// end IPluginInstallerCustom Wrapper +///////////////////////////// +/// IPluginModPage Wrapper +COMMON_I_PLUGIN_WRAPPER_DEFINITIONS(IPluginModPageWrapper) + +QString IPluginModPageWrapper::displayName() const +{ + try { + return this->get_override("displayName")(); + } PYCATCH; +} + +QIcon IPluginModPageWrapper::icon() const +{ + try { + return this->get_override("icon")(); + } PYCATCH; +} + +QUrl IPluginModPageWrapper::pageURL() const +{ + try { + return this->get_override("pageURL")(); + } PYCATCH; +} + +bool IPluginModPageWrapper::useIntegratedBrowser() const +{ + try { + return this->get_override("useIntegratedBrowser")(); + } PYCATCH; +} + +bool IPluginModPageWrapper::handlesDownload(const QUrl & pageURL, const QUrl & downloadURL, MOBase::ModRepositoryFileInfo & fileInfo) const +{ + try { + return this->get_override("handlesDownload")(pageURL, downloadURL, fileInfo); + } PYCATCH; +} + +void IPluginModPageWrapper::setParentWidget(QWidget * widget) +{ + try { + this->get_override("setParentWidget")(widget); + } PYCATCH; +} +/// end IPluginModPage Wrapper +///////////////////////////// +/// IPluginPreview Wrapper + + +COMMON_I_PLUGIN_WRAPPER_DEFINITIONS(IPluginPreviewWrapper) + +std::set IPluginPreviewWrapper::supportedExtensions() const +{ + try { + return this->get_override("supportedExtensions")(); + } PYCATCH; +} + +QWidget *IPluginPreviewWrapper::genFilePreview(const QString &fileName, const QSize &maxSize) const +{ + try { + boost::python::object pyVersion = this->get_override("genFilePreview")(fileName, maxSize); + // We need responsibility for deleting the QWidget to be transferred to C++ + sipAPI()->api_transfer_to(pyVersion.ptr(), 0); + return boost::python::extract(pyVersion)(); + } PYCATCH; +} +/// end IPluginPreview Wrapper +///////////////////////////// +/// IPluginTool Wrapper + + +COMMON_I_PLUGIN_WRAPPER_DEFINITIONS(IPluginToolWrapper) + +QString IPluginToolWrapper::displayName() const +{ + try { + return this->get_override("displayName")().as(); + } PYCATCH; +} + +QString IPluginToolWrapper::tooltip() const +{ + try { + return this->get_override("tooltip")().as(); + } PYCATCH; +} + +QIcon IPluginToolWrapper::icon() const +{ + try { + return this->get_override("icon")().as(); + } PYCATCH; +} + +void IPluginToolWrapper::setParentWidget(QWidget *parent) +{ + try { + this->get_override("setParentWidget")(parent); + } PYCATCH; +} + +void IPluginToolWrapper::display() const +{ + try { + GILock lock; + + this->get_override("display")(); + } PYCATCH; +} + +/// end IPluginTool Wrapper diff --git a/src/runner/proxypluginwrappers.h b/src/runner/proxypluginwrappers.h index 6131a24..12e6829 100644 --- a/src/runner/proxypluginwrappers.h +++ b/src/runner/proxypluginwrappers.h @@ -2,68 +2,126 @@ #define PROXYPLUGINWRAPPERS_H -#include +#include +#include +#include #include #include +#include +#include +#include #ifndef Q_MOC_RUN #include #endif +#define COMMON_I_PLUGIN_WRAPPER_DECLARATIONS public: \ +virtual bool init(MOBase::IOrganizer *moInfo) override; \ +virtual QString name() const override; \ +virtual QString author() const override; \ +virtual QString description() const override; \ +virtual MOBase::VersionInfo version() const override; \ +virtual bool isActive() const override; \ +virtual QList settings() const override; + + class IPluginWrapper : public MOBase::IPlugin, public boost::python::wrapper { Q_INTERFACES(MOBase::IPlugin) -public: - virtual bool init(MOBase::IOrganizer *moInfo); - virtual QString name() const; - virtual QString author() const; - virtual QString description() const; - virtual MOBase::VersionInfo version() const; - virtual bool isActive() const; - virtual QList settings() const; + COMMON_I_PLUGIN_WRAPPER_DECLARATIONS }; -class IPluginToolWrapper: public MOBase::IPluginTool, public boost::python::wrapper +// Even though the base interface is not an IPlugin or QObject, this has to be because we have no way to pass Mod Organizer a plugin that implements multiple interfaces. +// QObject must be the first base class because moc assumes the first base class is a QObject +class IPluginDiagnoseWrapper : public QObject, public MOBase::IPluginDiagnose, public MOBase::IPlugin, public boost::python::wrapper { Q_OBJECT - Q_INTERFACES(MOBase::IPlugin MOBase::IPluginTool) + Q_INTERFACES(MOBase::IPlugin MOBase::IPluginDiagnose) public: - virtual bool init(MOBase::IOrganizer *moInfo); - virtual QString name() const; - virtual QString author() const; - virtual QString description() const; - virtual MOBase::VersionInfo version() const; - virtual bool isActive() const; - virtual QList settings() const; + virtual std::vector activeProblems() const override; + virtual QString shortDescription(unsigned int key) const override; + virtual QString fullDescription(unsigned int key) const override; + virtual bool hasGuidedFix(unsigned int key) const override; + virtual void startGuidedFix(unsigned int key) const override; + // Other functions exist, but shouldn't need wrapping as a default implementation exists + // This was protected, but Python doesn't have that, so it needs making public + virtual void invalidate(); - virtual QString displayName() const; - virtual QString tooltip() const; - virtual QIcon icon() const; - virtual void setParentWidget(QWidget *parent); - -public slots: - virtual void display() const; + COMMON_I_PLUGIN_WRAPPER_DECLARATIONS }; -class IPluginInstallerCustomWrapper: public MOBase::IPluginInstallerCustom, public boost::python::wrapper +// Even though the base interface is not an IPlugin or QObject, this has to be because we have no way to pass Mod Organizer a plugin that implements multiple interfaces. +// QObject must be the first base class because moc assumes the first base class is a QObject +class IPluginFileMapperWrapper : public QObject, public MOBase::IPluginFileMapper, public MOBase::IPlugin, public boost::python::wrapper +{ + Q_OBJECT + Q_INTERFACES(MOBase::IPlugin MOBase::IPluginFileMapper) + +public: + virtual MappingType mappings() const override; + + COMMON_I_PLUGIN_WRAPPER_DECLARATIONS +}; + + +class IPluginGameWrapper : public MOBase::IPluginGame, public boost::python::wrapper { + Q_OBJECT + Q_INTERFACES(MOBase::IPlugin MOBase::IPluginGame) + +public: + virtual QString gameName() const override; + virtual void initializeProfile(const QDir &directory, ProfileSettings settings) const override; + virtual QString savegameExtension() const override; + virtual QString savegameSEExtension() const override; + virtual bool isInstalled() const override; + virtual QIcon gameIcon() const override; + virtual QDir gameDirectory() const override; + virtual QDir dataDirectory() const override; + virtual void setGamePath(const QString &path) override; + virtual QDir documentsDirectory() const override; + virtual QDir savesDirectory() const override; + virtual QList executables() const override; + virtual QString steamAPPId() const override; + virtual QStringList primaryPlugins() const override; + virtual QStringList gameVariants() const override; + virtual void setGameVariant(const QString &variant) override; + virtual QString binaryName() const override; + virtual QString gameShortName() const override; + virtual QStringList validShortNames() const override; + virtual QString gameNexusName() const override; + virtual QStringList iniFiles() const override; + virtual QStringList DLCPlugins() const override; + virtual QStringList CCPlugins() const override; + virtual LoadOrderMechanism loadOrderMechanism() const override; + virtual SortMechanism sortMechanism() const override; + virtual int nexusModOrganizerID() const override; + virtual int nexusGameID() const override; + virtual bool looksValid(QDir const &dir) const override; + virtual QString gameVersion() const override; + virtual QString getLauncherName() const override; + + COMMON_I_PLUGIN_WRAPPER_DECLARATIONS + +protected: + // Apparently, Python developers interpret an underscore in a function name as it being protected + virtual std::map featureList() const override; + + // Thankfully, the default implementation of the templated 'T *feature()' function should allow us to get away without overriding it. +}; + + +class IPluginInstallerCustomWrapper : public MOBase::IPluginInstallerCustom, public boost::python::wrapper { Q_OBJECT Q_INTERFACES(MOBase::IPlugin MOBase::IPluginInstaller MOBase::IPluginInstallerCustom) + COMMON_I_PLUGIN_WRAPPER_DECLARATIONS public: - virtual bool init(MOBase::IOrganizer *moInfo); - virtual QString name() const; - virtual QString author() const; - virtual QString description() const; - virtual MOBase::VersionInfo version() const; - virtual bool isActive() const; - virtual QList settings() const; - virtual unsigned int priority() const; virtual bool isManualInstaller() const; virtual bool isArchiveSupported(const MOBase::DirectoryTree &tree) const; @@ -76,6 +134,51 @@ public: }; +class IPluginModPageWrapper : public MOBase::IPluginModPage, public boost::python::wrapper +{ + Q_OBJECT + Q_INTERFACES(MOBase::IPlugin MOBase::IPluginModPage) + + COMMON_I_PLUGIN_WRAPPER_DECLARATIONS +public: + virtual QString displayName() const override; + virtual QIcon icon() const override; + virtual QUrl pageURL() const override; + virtual bool useIntegratedBrowser() const override; + virtual bool handlesDownload(const QUrl &pageURL, const QUrl &downloadURL, MOBase::ModRepositoryFileInfo &fileInfo) const override; + virtual void setParentWidget(QWidget *widget) override; +}; + + +class IPluginPreviewWrapper : public MOBase::IPluginPreview, public boost::python::wrapper +{ + Q_OBJECT + Q_INTERFACES(MOBase::IPlugin MOBase::IPluginPreview) + + COMMON_I_PLUGIN_WRAPPER_DECLARATIONS +public: + virtual std::set supportedExtensions() const override; + virtual QWidget *genFilePreview(const QString &fileName, const QSize &maxSize) const override; +}; + + +class IPluginToolWrapper: public MOBase::IPluginTool, public boost::python::wrapper +{ + Q_OBJECT + Q_INTERFACES(MOBase::IPlugin MOBase::IPluginTool) + + COMMON_I_PLUGIN_WRAPPER_DECLARATIONS +public: + virtual QString displayName() const; + virtual QString tooltip() const; + virtual QIcon icon() const; + virtual void setParentWidget(QWidget *parent); + +public slots: + virtual void display() const; +}; + + #endif // PROXYPLUGINWRAPPERS_H diff --git a/src/runner/pycatch.h b/src/runner/pycatch.h new file mode 100644 index 0000000..e74ce4d --- /dev/null +++ b/src/runner/pycatch.h @@ -0,0 +1,11 @@ +#ifndef PYCATCH_H +#define PYCATCH_H + +#include + +#include "error.h" + +#define PYCATCH catch (const boost::python::error_already_set &) { reportPythonError(); throw MOBase::MyException("unhandled exception"); }\ + catch (...) { throw MOBase::MyException("An unknown exception was thrown in python code"); } + +#endif // PYCATCH_H diff --git a/src/runner/pythonrunner.cpp b/src/runner/pythonrunner.cpp index 79ad1c7..7578949 100644 --- a/src/runner/pythonrunner.cpp +++ b/src/runner/pythonrunner.cpp @@ -3,13 +3,14 @@ #pragma warning( disable : 4100 ) #pragma warning( disable : 4996 ) -#include "iplugingame.h" +#include +#include #include #include -#include -#include #include "uibasewrappers.h" #include "proxypluginwrappers.h" +#include "gamefeatureswrappers.h" +#include "sipApiAccess.h" #include #include @@ -68,26 +69,15 @@ using namespace MOBase; namespace bpy = boost::python; -struct ModRepositoryFileInfo_to_python_dict -{ - static PyObject *convert(const ModRepositoryFileInfo &info) { - PyObject *res = PyDict_New(); - PyDict_SetItemString(res, "uri", bpy::incref(bpy::object(info.uri).ptr())); - PyDict_SetItemString(res, "name", bpy::incref(bpy::object(info.name).ptr())); - PyDict_SetItemString(res, "description", bpy::incref(bpy::object(info.description.toUtf8().constData()).ptr())); - PyDict_SetItemString(res, "categoryID", PyLong_FromLong(info.categoryID)); - PyDict_SetItemString(res, "fileID", PyLong_FromLong(info.fileID)); - PyDict_SetItemString(res, "fileSize", PyLong_FromLong(static_cast(info.fileSize))); - PyDict_SetItemString(res, "version", bpy::incref(bpy::object(info.version).ptr())); - return bpy::incref(res); - } -}; - struct QString_to_python_str { static PyObject *convert(const QString &str) { - return bpy::incref(bpy::object(str.toUtf8().constData()).ptr()); + // It's safer to explicitly convert to unicode as if we don't, this can return either str or unicode without it being easy to know which to expect + bpy::object pyStr = bpy::object(str.toUtf8().constData()); + if (PyString_Check(pyStr.ptr())) + pyStr = pyStr.attr("decode")("utf-8"); + return bpy::incref(pyStr.ptr()); } }; @@ -182,6 +172,55 @@ struct GuessedValue_converters }; +template +struct QMap_converters +{ + struct QMap_to_python + { + static PyObject *convert(const QMap &map) { + bpy::dict result; + QMapIterator iter(map); + while (iter.hasNext()) { + iter.next(); + result[bpy::object(iter.key())] = bpy::object(iter.value()); + } + return bpy::incref(result.ptr()); + } + }; + + struct QMap_from_python + { + QMap_from_python() { + bpy::converter::registry::push_back(&convertible, &construct, bpy::type_id>()); + } + + static void *convertible(PyObject *objPtr) { + return PyDict_Check(objPtr) ? objPtr : nullptr; + } + + static void construct(PyObject *objPtr, bpy::converter::rvalue_from_python_stage1_data *data) { + void *storage = ((bpy::converter::rvalue_from_python_storage>*)data)->storage.bytes; + QMap *result = new (storage) QMap(); + bpy::dict source(bpy::handle<>(bpy::borrowed(objPtr))); + bpy::list keys = source.keys(); + int len = bpy::len(keys); + for (int i = 0; i < len; ++i) { + bpy::object pyKey = keys[i]; + (*result)[bpy::extract(pyKey)] = bpy::extract(source[pyKey]); + } + + data->convertible = storage; + } + }; + + QMap_converters() + { + QMap_from_python(); + bpy::to_python_converter, QMap_to_python >(); + } +}; + + struct QVariant_to_python_obj { static PyObject *convert(const QVariant &var) { @@ -189,24 +228,12 @@ struct QVariant_to_python_obj 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 bpy::incref(bpy::object(var.toString().toUtf8().constData()).ptr()); + case QVariant::String: return bpy::incref(bpy::object(var.toString()).ptr()); case QVariant::List: { - QVariantList list = var.toList(); - PyObject *result = PyList_New(list.count()); - for (QVariant var : list) { - PyList_Append(result, convert(var)); - } - return result; + return bpy::incref(bpy::object(var.toList()).ptr()); } break; case QVariant::Map: { - QVariantMap map = var.toMap(); - PyObject *result = PyDict_New(); - QMapIterator iter(map); - while (iter.hasNext()) { - iter.next(); - PyDict_SetItem(result, convert(iter.key()), convert(iter.value())); - } - return result; + return bpy::incref(bpy::object(var.toMap()).ptr()); } break; default: { PyErr_Format(PyExc_TypeError, "type unsupported: %d", var.type()); @@ -240,49 +267,21 @@ struct QVariant_from_python_obj data->convertible = storage; } - static QVariant variantFromPyObject(PyObject *objPtr) { - QVariant result; - if (PyList_Check(objPtr)) { - QVariantList resultList; - for (int i = 0; i < PyList_Size(objPtr); ++i) { - resultList.append(variantFromPyObject(PyList_GetItem(objPtr, i))); - } - result = resultList; - } else if (PyString_Check(objPtr)) { - result = PyString_AsString(objPtr); - } else if (PyBool_Check(objPtr)) { - result = (objPtr == Py_True); - } else if (PyInt_Check(objPtr)) { - //QVariant doesn't have long. It has int or long long. Given that on m/s, - //long is 32 bits for 32- and 64- bit code... - result = static_cast(PyInt_AsLong(objPtr)); - } else { - PyErr_SetString(PyExc_TypeError, "type unsupported"); - throw bpy::error_already_set(); - } - return result; - } - static void construct(PyObject *objPtr, bpy::converter::rvalue_from_python_stage1_data *data) { // PyBools will also return true for PyInt_Check but not the other way around, so the order // here is relevant if (PyList_Check(objPtr)) { - QVariantList result; - for (int i = 0; i < PyList_Size(objPtr); ++i) { - result.append(variantFromPyObject(PyList_GetItem(objPtr, i))); - } - constructVariant(result, data); - } else if (PyString_Check(objPtr)) { - const char *value = PyString_AsString(objPtr); - constructVariant(value, data); + constructVariant(bpy::extract(objPtr)(), data); + } else if (PyDict_Check(objPtr)) { + constructVariant(bpy::extract(objPtr)(), data); + } else if (PyString_Check(objPtr) || PyUnicode_Check(objPtr)) { + constructVariant(bpy::extract(objPtr)(), data); } else if (PyBool_Check(objPtr)) { - bool value = (objPtr == Py_True); - constructVariant(value, data); + constructVariant(bpy::extract(objPtr)(), data); } else if (PyInt_Check(objPtr)) { //QVariant doesn't have long. It has int or long long. Given that on m/s, //long is 32 bits for 32- and 64- bit code... - int value = static_cast(PyInt_AsLong(objPtr)); - constructVariant(value, data); + constructVariant(bpy::extract(objPtr)(), data); } else { PyErr_SetString(PyExc_TypeError, "type unsupported"); throw bpy::error_already_set(); @@ -340,6 +339,55 @@ struct QList_from_python_obj }; +template +struct std_vector_to_python_list +{ + static PyObject *convert(const std::vector &vector) + { + bpy::list pyList; + + try { + for (const T &item : vector) + pyList.append(item); + } + catch (const bpy::error_already_set&) { + reportPythonError(); + } + + return bpy::incref(pyList.ptr()); + } +}; + + +template +struct std_vector_from_python_obj +{ + std_vector_from_python_obj() { + bpy::converter::registry::push_back( + &convertible, + &construct, + bpy::type_id >()); + } + + static void* convertible(PyObject *objPtr) { + if (PyList_Check(objPtr)) return objPtr; + return nullptr; + } + + static void construct(PyObject *objPtr, bpy::converter::rvalue_from_python_stage1_data *data) { + void *storage = ((bpy::converter::rvalue_from_python_storage >*)data)->storage.bytes; + std::vector *result = new (storage) std::vector(); + bpy::list source(bpy::handle<>(bpy::borrowed(objPtr))); + int length = bpy::len(source); + for (int i = 0; i < length; ++i) { + result->push_back(bpy::extract(source[i])); + } + + data->convertible = storage; + } +}; + + template struct QFlags_from_python_obj { @@ -395,17 +443,6 @@ struct stdset_from_python_list }; -static const sipAPIDef *sipAPI() -{ - static const sipAPIDef *sipApi = nullptr; - if (sipApi == nullptr) { - sipApi = (const sipAPIDef *)PyCapsule_Import("sip._C_API", 0); - } - - return sipApi; -} - - struct IModRepositoryBridge_to_python { static PyObject *convert(IModRepositoryBridge *bridge) @@ -424,8 +461,13 @@ template <> struct MetaData { static const char *className template <> struct MetaData { static const char *className() { return "QObject"; } }; template <> struct MetaData { static const char *className() { return "QObject"; } }; template <> struct MetaData { static const char *className() { return "QWidget"; } }; +template <> struct MetaData { static const char *className() { return "QDateTime"; } }; +template <> struct MetaData { static const char *className() { return "QDir"; } }; +template <> struct MetaData { static const char *className() { return "QFileInfo"; } }; template <> struct MetaData { static const char *className() { return "QIcon"; } }; +template <> struct MetaData { static const char *className() { return "QSize"; } }; template <> struct MetaData { static const char *className() { return "QStringList"; } }; +template <> struct MetaData { static const char *className() { return "QUrl"; } }; template <> struct MetaData { static const char *className() { return "QVariant"; } }; @@ -457,17 +499,33 @@ struct QClass_converters { struct QClass_to_PyQt { + template + static typename std::enable_if_t, T*> getSafeCopy(T *qClass) + { + return new T(*qClass); + } + + template + static typename std::enable_if_t, T*> getSafeCopy(T *qClass) + { + return qClass; + } + static PyObject *convert(const T &object) { const sipTypeDef *type = sipAPI()->api_find_type(MetaData::className()); if (type == nullptr) { return bpy::incref(Py_None); } - PyObject *sipObj = sipAPI()->api_convert_from_type((void*)(&object), type, 0); + PyObject *sipObj = sipAPI()->api_convert_from_type((void*)getSafeCopy((T*)&object), type, 0); if (sipObj == nullptr) { return bpy::incref(Py_None); } + if (std::is_copy_constructible_v) + // Ensure Python deletes the C++ component + sipAPI()->api_transfer_back(sipObj); + return bpy::incref(sipObj); } @@ -481,11 +539,15 @@ struct QClass_converters return bpy::incref(Py_None); } - PyObject *sipObj = sipAPI()->api_convert_from_type(object, type, 0); + PyObject *sipObj = sipAPI()->api_convert_from_type(getSafeCopy(object), type, 0); if (sipObj == nullptr) { return bpy::incref(Py_None); } + if (std::is_copy_constructible_v) + // Ensure Python deletes the C++ component + sipAPI()->api_transfer_back(sipObj); + return bpy::incref(sipObj); } @@ -496,12 +558,21 @@ struct QClass_converters static void *QClass_from_PyQt(PyObject *objPtr) { - if (!PyObject_TypeCheck(objPtr, sipAPI()->api_wrapper_type)) { + if (!PyObject_TypeCheck(objPtr, sipAPI()->api_simplewrapper_type)) { + if (std::is_same_v) + { + // QStringLists aren't wrapped by PyQt - regular Python string/unicode lists are used instead + bpy::extract> extractor(objPtr); + if (extractor.check()) + return new QStringList(extractor()); + } PyErr_SetString(PyExc_TypeError, "type not wrapped"); bpy::throw_error_already_set(); } - sipAPI()->api_transfer_to(objPtr, 0); + // This would transfer responsibility for deconstructing the object to C++, but Boost assumes l-value converters (such as this) don't do that + // Instead, this should be called within the wrappers for functions which return deletable pointers. + //sipAPI()->api_transfer_to(objPtr, 0); sipSimpleWrapper *wrapper = reinterpret_cast(objPtr); return wrapper->data; @@ -564,7 +635,9 @@ struct QInterface_converters bpy::throw_error_already_set(); } - sipAPI()->api_transfer_to(objPtr, 0); + // This would transfer responsibility for deconstructing the object to C++, but Boost assumes l-value converters (such as this) don't do that + // Instead, this should be called within the wrappers for functions which return deletable pointers. + //sipAPI()->api_transfer_to(objPtr, 0); sipSimpleWrapper *wrapper = reinterpret_cast(objPtr); return wrapper->data; @@ -593,6 +666,7 @@ int getArgCount(PyObject *object) { return result; } +template struct Functor0_converter { @@ -601,9 +675,9 @@ struct Functor0_converter FunctorWrapper(boost::python::object callable) : m_Callable(callable) { } - void operator()() { + RET operator()() { GILock lock; - m_Callable(); + return (RET) m_Callable(); } boost::python::object m_Callable; @@ -611,7 +685,7 @@ struct Functor0_converter Functor0_converter() { - bpy::converter::registry::push_back(&convertible, &construct, bpy::type_id>()); + bpy::converter::registry::push_back(&convertible, &construct, bpy::type_id>()); } static void *convertible(PyObject *object) @@ -626,14 +700,55 @@ struct Functor0_converter static void construct(PyObject *object, bpy::converter::rvalue_from_python_stage1_data *data) { bpy::object callable(bpy::handle<>(bpy::borrowed(object))); - void *storage = ((bpy::converter::rvalue_from_python_storage>*)data)->storage.bytes; - new (storage) std::function(FunctorWrapper(callable)); + void *storage = ((bpy::converter::rvalue_from_python_storage>*)data)->storage.bytes; + new (storage) std::function(FunctorWrapper(callable)); data->convertible = storage; } }; -template +template +struct Functor1_converter +{ + + struct FunctorWrapper + { + FunctorWrapper(boost::python::object callable) : m_Callable(callable) { + } + + RET operator()(const PAR1 ¶m1) { + GILock lock; + return (RET) m_Callable(param1); + } + + boost::python::object m_Callable; + }; + + Functor1_converter() + { + bpy::converter::registry::push_back(&convertible, &construct, bpy::type_id>()); + } + + static void *convertible(PyObject *object) + { + if (!PyCallable_Check(object) + || (getArgCount(object) != 1)) { + return nullptr; + } + return object; + } + + static void construct(PyObject *object, bpy::converter::rvalue_from_python_stage1_data *data) + { + bpy::object callable(bpy::handle<>(bpy::borrowed(object))); + void *storage = ((bpy::converter::rvalue_from_python_storage>*)data)->storage.bytes; + new (storage) std::function(FunctorWrapper(callable)); + data->convertible = storage; + } +}; + + +template struct Functor2_converter { @@ -642,9 +757,9 @@ struct Functor2_converter FunctorWrapper(boost::python::object callable) : m_Callable(callable) { } - void operator()(const PAR1 ¶m1, const PAR2 ¶m2) { + RET operator()(const PAR1 ¶m1, const PAR2 ¶m2) { GILock lock; - m_Callable(param1, param2); + return (RET) m_Callable(param1, param2); } boost::python::object m_Callable; @@ -652,7 +767,7 @@ struct Functor2_converter Functor2_converter() { - bpy::converter::registry::push_back(&convertible, &construct, bpy::type_id>()); + bpy::converter::registry::push_back(&convertible, &construct, bpy::type_id>()); } static void *convertible(PyObject *object) @@ -667,8 +782,8 @@ struct Functor2_converter static void construct(PyObject *object, bpy::converter::rvalue_from_python_stage1_data *data) { bpy::object callable(bpy::handle<>(bpy::borrowed(object))); - void *storage = ((bpy::converter::rvalue_from_python_storage>*)data)->storage.bytes; - new (storage) std::function(FunctorWrapper(callable)); + void *storage = ((bpy::converter::rvalue_from_python_storage>*)data)->storage.bytes; + new (storage) std::function(FunctorWrapper(callable)); data->convertible = storage; } }; @@ -687,9 +802,14 @@ BOOST_PYTHON_MODULE(mobase) QString_from_python_str(); //QClass_converters(); + QClass_converters(); + QClass_converters(); + QClass_converters(); QClass_converters(); QClass_converters(); + QClass_converters(); QClass_converters(); + QClass_converters(); QInterface_converters(); @@ -713,14 +833,6 @@ BOOST_PYTHON_MODULE(mobase) .value("literal", MOBase::VersionInfo::SCHEME_LITERAL) ; - bpy::enum_("InstallResult") - .value("success", MOBase::IPluginInstaller::RESULT_SUCCESS) - .value("failed", MOBase::IPluginInstaller::RESULT_FAILED) - .value("canceled", MOBase::IPluginInstaller::RESULT_CANCELED) - .value("manualRequested", MOBase::IPluginInstaller::RESULT_MANUALREQUESTED) - .value("notAttempted", MOBase::IPluginInstaller::RESULT_NOTATTEMPTED) - ; - bpy::class_("VersionInfo") .def(bpy::init()) .def(bpy::init()) @@ -744,12 +856,42 @@ BOOST_PYTHON_MODULE(mobase) bpy::class_("PluginSetting", bpy::init()); + bpy::class_("ExecutableInfo", bpy::init()) + .def("withArgument", &ExecutableInfo::withArgument, bpy::return_value_policy()) + .def("withWorkingDirectory", &ExecutableInfo::withWorkingDirectory, bpy::return_value_policy()) + .def("withSteamAppId", &ExecutableInfo::withSteamAppId, bpy::return_value_policy()) + .def("asCustom", &ExecutableInfo::asCustom, bpy::return_value_policy()) + .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) + ; + + bpy::class_("ISaveGame") + .def("getFilename", bpy::pure_virtual(&ISaveGame::getFilename)) + .def("getCreationTime", bpy::pure_virtual(&ISaveGame::getCreationTime)) + .def("getSaveGroupIdentifier", bpy::pure_virtual(&ISaveGame::getSaveGroupIdentifier)) + .def("allFiles", bpy::pure_virtual(&ISaveGame::allFiles)) + .def("hasScriptExtenderFile", bpy::pure_virtual(&ISaveGame::hasScriptExtenderFile)) + ; + + // TODO: ISaveGameInfoWidget bindings + + Functor1_converter(); + Functor1_converter(); + Functor1_converter(); + Functor2_converter(); bpy::class_("IOrganizer") .def("createNexusBridge", bpy::pure_virtual(&IOrganizer::createNexusBridge), bpy::return_value_policy()) .def("profileName", bpy::pure_virtual(&IOrganizer::profileName)) .def("profilePath", bpy::pure_virtual(&IOrganizer::profilePath)) .def("downloadsPath", bpy::pure_virtual(&IOrganizer::downloadsPath)) + .def("overwritePath", bpy::pure_virtual(&IOrganizer::overwritePath)) + .def("basePath", bpy::pure_virtual(&IOrganizer::basePath)) .def("appVersion", bpy::pure_virtual(&IOrganizer::appVersion)) .def("getMod", bpy::pure_virtual(&IOrganizer::getMod), bpy::return_value_policy()) .def("createMod", bpy::pure_virtual(&IOrganizer::createMod), bpy::return_value_policy()) @@ -761,16 +903,21 @@ BOOST_PYTHON_MODULE(mobase) .def("setPersistent", bpy::pure_virtual(&IOrganizer::setPersistent)) .def("pluginDataPath", bpy::pure_virtual(&IOrganizer::pluginDataPath)) .def("installMod", bpy::pure_virtual(&IOrganizer::installMod),(bpy::arg("nameSuggestion")=""), bpy::return_value_policy()) + .def("resolvePath", bpy::pure_virtual(&IOrganizer::resolvePath)) + .def("listDirectories", bpy::pure_virtual(&IOrganizer::listDirectories)) + .def("findFiles", bpy::pure_virtual(&IOrganizer::findFiles)) + .def("getFileOrigins", bpy::pure_virtual(&IOrganizer::getFileOrigins)) + .def("findFileInfos", bpy::pure_virtual(&IOrganizer::findFileInfos)) .def("downloadManager", bpy::pure_virtual(&IOrganizer::downloadManager), bpy::return_value_policy()) .def("pluginList", bpy::pure_virtual(&IOrganizer::pluginList), bpy::return_value_policy()) .def("modList", bpy::pure_virtual(&IOrganizer::modList), bpy::return_value_policy()) - .def("startApplication", bpy::pure_virtual(&IOrganizer::startApplication), bpy::return_value_policy()) + .def("profile", bpy::pure_virtual(&IOrganizer::profile), bpy::return_value_policy()) + .def("startApplication", bpy::pure_virtual(&IOrganizer::startApplication), ((bpy::arg("args")=QStringList()), (bpy::arg("cwd")=""), (bpy::arg("profile")="")), bpy::return_value_policy()) //.def("waitForApplication", bpy::pure_virtual(&IOrganizer::waitForApplication), (bpy::arg("exitCode")=nullptr), bpy::return_value_policy()) + .def("onModInstalled", bpy::pure_virtual(&IOrganizer::onModInstalled)) .def("onAboutToRun", bpy::pure_virtual(&IOrganizer::onAboutToRun)) .def("onFinishedRun", bpy::pure_virtual(&IOrganizer::onFinishedRun)) - .def("onModInstalled", bpy::pure_virtual(&IOrganizer::onModInstalled)) - .def("refreshModList", bpy::pure_virtual(&IOrganizer::refreshModList)) - .def("profile", bpy::pure_virtual(&IOrganizer::profile), bpy::return_value_policy()) + .def("refreshModList", bpy::pure_virtual(&IOrganizer::refreshModList), (bpy::arg("saveChanges")=true)) .def("managedGame", bpy::pure_virtual(&IOrganizer::managedGame), bpy::return_value_policy()) .def("modsSortedByProfilePriority", bpy::pure_virtual(&IOrganizer::modsSortedByProfilePriority)) ; @@ -803,6 +950,29 @@ BOOST_PYTHON_MODULE(mobase) .def("requestToggleEndorsement", bpy::pure_virtual(&IModRepositoryBridge::requestToggleEndorsement)) ; + bpy::class_("ModRepositoryFileInfo") + .def(bpy::init()) + .def(bpy::init>()) + .def("toString", &ModRepositoryFileInfo::toString) + .def("createFromJson", &ModRepositoryFileInfo::createFromJson).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) + ; + bpy::class_("IDownloadManager") .def("startDownloadURLs", bpy::pure_virtual(&IDownloadManager::startDownloadURLs)) .def("startDownloadNexusFile", bpy::pure_virtual(&IDownloadManager::startDownloadNexusFile)) @@ -847,19 +1017,9 @@ BOOST_PYTHON_MODULE(mobase) .def("variants", &MOBase::GuessedValue::variants, bpy::return_value_policy()) ; - bpy::class_("IPlugin"); - - bpy::class_, boost::noncopyable>("IPluginTool") - .def("setParentWidget", bpy::pure_virtual(&MOBase::IPluginTool::setParentWidget)) - ; - - bpy::class_("IPluginInstallerCustom") - .def("setParentWidget", bpy::pure_virtual(&MOBase::IPluginInstallerCustom::setParentWidget)) - ; - bpy::to_python_converter>(); QFlags_from_python_obj(); - Functor0_converter(); // converter for the onRefreshed-callback + Functor0_converter(); // converter for the onRefreshed-callback bpy::class_("IPluginList") .def("state", bpy::pure_virtual(&MOBase::IPluginList::state)) @@ -877,7 +1037,7 @@ BOOST_PYTHON_MODULE(mobase) bpy::to_python_converter>(); QFlags_from_python_obj(); - Functor2_converter(); // converter for the onModStateChanged-callback + Functor2_converter(); // converter for the onModStateChanged-callback bpy::class_("IModList") .def("displayName", bpy::pure_virtual(&MOBase::IModList::displayName)) @@ -890,6 +1050,28 @@ BOOST_PYTHON_MODULE(mobase) .def("onModMoved", bpy::pure_virtual(&MOBase::IModList::onModMoved)) ; + bpy::class_("IPlugin"); + + bpy::class_("IPluginDiagnose") + .def("activeProblems", bpy::pure_virtual(&MOBase::IPluginDiagnose::activeProblems)) + .def("shortDescription", bpy::pure_virtual(&MOBase::IPluginDiagnose::shortDescription)) + .def("fullDescription", bpy::pure_virtual(&MOBase::IPluginDiagnose::fullDescription)) + .def("hasGuidedFix", bpy::pure_virtual(&MOBase::IPluginDiagnose::hasGuidedFix)) + .def("startGuidedFix", bpy::pure_virtual(&MOBase::IPluginDiagnose::startGuidedFix)) + .def("_invalidate", &IPluginDiagnoseWrapper::invalidate) + ; + + bpy::class_("Mapping") + .def_readwrite("source", &Mapping::source) + .def_readwrite("destination", &Mapping::destination) + .def_readwrite("isDirectory", &Mapping::isDirectory) + .def_readwrite("createTarget", &Mapping::createTarget) + ; + + bpy::class_("IPluginFileMapper") + .def("mappings", bpy::pure_virtual(&MOBase::IPluginFileMapper::mappings)) + ; + bpy::enum_("LoadOrderMechanism") .value("FileTime", MOBase::IPluginGame::LoadOrderMechanism::FileTime) .value("PluginsTxt", MOBase::IPluginGame::LoadOrderMechanism::PluginsTxt) @@ -954,21 +1136,65 @@ BOOST_PYTHON_MODULE(mobase) .def("isActive", bpy::pure_virtual(&MOBase::IPluginGame::isActive)) .def("settings", bpy::pure_virtual(&MOBase::IPluginGame::settings)) + // The syntax has to differ slightly from C++ because these are templated + .def("featureBSAInvalidation", &MOBase::IPluginGame::feature, bpy::return_value_policy()) + .def("featureDataArchives", &MOBase::IPluginGame::feature, bpy::return_value_policy()) + .def("featureGamePlugins", &MOBase::IPluginGame::feature, bpy::return_value_policy()) + .def("featureLocalSavegames", &MOBase::IPluginGame::feature, bpy::return_value_policy()) + .def("featureSaveGameInfo", &MOBase::IPluginGame::feature, bpy::return_value_policy()) + .def("featureScriptExtender", &MOBase::IPluginGame::feature, bpy::return_value_policy()) + .def("featureUnmanagedMods", &MOBase::IPluginGame::feature, bpy::return_value_policy()) ; - bpy::class_("QDir") - .def("absolutePath", &QDir::absolutePath) - ; + bpy::enum_("InstallResult") + .value("success", MOBase::IPluginInstaller::RESULT_SUCCESS) + .value("failed", MOBase::IPluginInstaller::RESULT_FAILED) + .value("canceled", MOBase::IPluginInstaller::RESULT_CANCELED) + .value("manualRequested", MOBase::IPluginInstaller::RESULT_MANUALREQUESTED) + .value("notAttempted", MOBase::IPluginInstaller::RESULT_NOTATTEMPTED) + ; + + bpy::class_("IPluginInstallerCustom") + .def("setParentWidget", bpy::pure_virtual(&MOBase::IPluginInstallerCustom::setParentWidget)) + ; + + bpy::class_("IPluginModPage") + .def("setParentWidget", bpy::pure_virtual(&MOBase::IPluginModPage::setParentWidget)) + ; + + bpy::class_("IPluginPreview") + ; + + bpy::class_, boost::noncopyable>("IPluginTool") + .def("setParentWidget", bpy::pure_virtual(&MOBase::IPluginTool::setParentWidget)) + ; GuessedValue_converters(); - bpy::to_python_converter(); + //bpy::to_python_converter(); + QList_from_python_obj(); QList_from_python_obj(); bpy::to_python_converter, QList_to_python_list >(); + QList_from_python_obj(); + bpy::to_python_converter, + QList_to_python_list>(); + QList_from_python_obj(); + bpy::to_python_converter, + QList_to_python_list>(); + + QMap_converters(); + QMap_converters(); + + std_vector_from_python_obj(); + std_vector_from_python_obj(); + bpy::to_python_converter, + std_vector_to_python_list>(); stdset_from_python_list(); + + registerGameFeaturesPythonConverters(); } @@ -1082,9 +1308,15 @@ QObject *PythonRunner::instantiate(const QString &pluginName) m_PythonObjects[pluginName] = moduleNamespace["createPlugin"](); bpy::object pluginObj = m_PythonObjects[pluginName]; - TRY_PLUGIN_TYPE(IPluginInstallerCustom, pluginObj); - TRY_PLUGIN_TYPE(IPluginTool, pluginObj); TRY_PLUGIN_TYPE(IPluginGame, pluginObj); + // Must try the wrapper because it's only a plugin extension interface in C++, so doesn't extend QObject + TRY_PLUGIN_TYPE(IPluginDiagnoseWrapper, pluginObj); + // Must try the wrapper because it's only a plugin extension interface in C++, so doesn't extend QObject + TRY_PLUGIN_TYPE(IPluginFileMapperWrapper, pluginObj); + TRY_PLUGIN_TYPE(IPluginInstallerCustom, pluginObj); + TRY_PLUGIN_TYPE(IPluginModPage, pluginObj); + TRY_PLUGIN_TYPE(IPluginPreview, pluginObj); + TRY_PLUGIN_TYPE(IPluginTool, pluginObj); } catch (const bpy::error_already_set&) { qWarning("failed to run python script \"%s\"", qPrintable(pluginName)); reportPythonError(); diff --git a/src/runner/sipapiaccess.h b/src/runner/sipapiaccess.h new file mode 100644 index 0000000..c9c95d0 --- /dev/null +++ b/src/runner/sipapiaccess.h @@ -0,0 +1,16 @@ +#ifndef SIPAPIACCESS_H +#define SIPAPIACCESS_H + +#include + +static const sipAPIDef *sipAPI() +{ + static const sipAPIDef *sipApi = nullptr; + if (sipApi == nullptr) { + sipApi = (const sipAPIDef *)PyCapsule_Import("sip._C_API", 0); + } + + return sipApi; +} + +#endif // SIPAPIACCESS_H diff --git a/src/runner/uibasewrappers.h b/src/runner/uibasewrappers.h index 780a506..36f1c0c 100644 --- a/src/runner/uibasewrappers.h +++ b/src/runner/uibasewrappers.h @@ -21,8 +21,12 @@ #include #include #include +#include +#include + #include "error.h" #include "gilock.h" +#include "pycatch.h" extern MOBase::IOrganizer *s_Organizer; @@ -434,53 +438,17 @@ struct IModListWrapper: MOBase::IModList, boost::python::wrapper { - virtual QString gameName() const override { return this->get_override("gameName")(); } - virtual void initializeProfile(const QDir &directory, ProfileSettings settings) const override { - this->get_override("initializeProfile")(directory, settings); - } - virtual QString savegameExtension() const override { return this->get_override("savegameExtension")(); } - virtual QString savegameSEExtension() const override { return this->get_override("savegameSEExtension")(); } - virtual bool isInstalled() const override { return this->get_override("isInstalled")(); } - virtual QIcon gameIcon() const override { return this->get_override("gameIcon")(); } - virtual QDir gameDirectory() const override { return this->get_override("gameDirectory")(); } - virtual QDir dataDirectory() const override { return this->get_override("dataDirectory")(); } - virtual void setGamePath(const QString &path) override { this->get_override("setGamePath")(path); } - virtual QDir documentsDirectory() const override { return this->get_override("documentsDirectory")(); } - virtual QDir savesDirectory() const override { return this->get_override("savesDirectory")(); } - virtual QList executables() const override { return this->get_override("executables")(); } - virtual QString steamAPPId() const override { return this->get_override("steamAPPId")(); } - virtual QStringList primaryPlugins() const override { return this->get_override("primaryPlugins")(); } - virtual QStringList gameVariants() const override { return this->get_override("gameVariants")(); } - virtual void setGameVariant(const QString &variant) override { this->get_override("setGameVariant")(variant); } - virtual QString binaryName() const override { return this->get_override("binaryName")(); } - virtual QString gameShortName() const override { return this->get_override("gameShortName")(); } - virtual QStringList validShortNames() const override { return this->get_override("validShortNames")(); } - virtual QString gameNexusName() const override { return this->get_override("gameNexusName")(); } - virtual QStringList iniFiles() const override { return this->get_override("iniFiles")(); } - virtual QStringList DLCPlugins() const override { return this->get_override("DLCPlugins")(); } - virtual QStringList CCPlugins() const override { return this->get_override("CCPlugins")(); } - virtual LoadOrderMechanism loadOrderMechanism() const override { return this->get_override("loadorderMechanism")(); } - virtual SortMechanism sortMechanism() const override { return this->get_override("sortMechanism")(); } - virtual int nexusModOrganizerID() const override { return this->get_override("nexusModOrganizerID")(); } - virtual int nexusGameID() const override { return this->get_override("nexusGameID")(); } - virtual bool looksValid(QDir const &dir) const override { return this->get_override("looksValid")(dir); } - virtual QString gameVersion() const override { return this->get_override("gameVersion")(); } - virtual QString getLauncherName() const override { return this->get_override("getLauncherName")(); } - - //Plugin interface. Could this bit be implemented just once? - virtual bool init(MOBase::IOrganizer *moInfo) override { return this->get_override("init")(moInfo); } - virtual QString name() const override { return this->get_override("name")(); } - virtual QString author() const override { return this->get_override("author")(); } - virtual QString description() const override { return this->get_override("description")(); } - virtual MOBase::VersionInfo version() const override { return this->get_override("version")(); } - virtual bool isActive() const override { return this->get_override("isActive")(); } - virtual QList settings() const override { return this->get_override("settings")(); } - -protected: - - virtual std::map featureList() const override { return this->get_override("featureList")(); } - +// This needs to be extendable in Python, so actually needs a wrapper +// Everything else probably doesn't +class ISaveGameWrapper : public MOBase::ISaveGame, public boost::python::wrapper +{ +public: + virtual QString getFilename() const override { try { return this->get_override("getFilename")(); } PYCATCH }; + virtual QDateTime getCreationTime() const override { try { return this->get_override("getCreationTime")(); } PYCATCH }; + virtual QString getSaveGroupIdentifier() const override { try { return this->get_override("getSaveGroupIdentifier")(); } PYCATCH }; + virtual QStringList allFiles() const override { try { return this->get_override("allFiles")(); } PYCATCH }; + virtual bool hasScriptExtenderFile() const override { try { return this->get_override("hasScriptExtenderFile")(); } PYCATCH }; }; + #endif // UIBASEWRAPPERS_H