From 1dd5cab7066092ffc6c9a1306bbbd7fabeea72a4 Mon Sep 17 00:00:00 2001 From: Tannin Date: Fri, 10 May 2013 10:57:35 +0200 Subject: [PATCH] - initial support for exposing the nexusbridge to python - started improving usability of nexus bridge - support for installers implemented in python --- src/proxy/INexusBridge.sip | 34 ++ src/proxy/interfaces.sip | 3 + src/proxy/proxyPython.pro | 30 +- src/proxy/proxypython.cpp | 774 +++++++++++++++++++++----------- src/proxy/proxypython.h | 20 +- src/proxy/pythonpluginwrapper.h | 1 + src/proxy/pythontoolwrapper.cpp | 13 +- src/proxy/pythontoolwrapper.h | 9 +- src/proxy/setup.cfg | 2 + src/proxy/setup.py | 27 ++ src/proxy/uibasewrappers.h | 62 +++ 11 files changed, 700 insertions(+), 275 deletions(-) create mode 100644 src/proxy/INexusBridge.sip create mode 100644 src/proxy/interfaces.sip create mode 100644 src/proxy/setup.cfg create mode 100644 src/proxy/setup.py create mode 100644 src/proxy/uibasewrappers.h diff --git a/src/proxy/INexusBridge.sip b/src/proxy/INexusBridge.sip new file mode 100644 index 0000000..d1e4e19 --- /dev/null +++ b/src/proxy/INexusBridge.sip @@ -0,0 +1,34 @@ +%Import QtCore/QtCoremod.sip +%Import QtGui/QtGuimod.sip + + +namespace MOBase +{ + +class INexusBridge : public QObject +{ + +%TypeHeaderCode +#include +%End + +public: + + virtual void requestDescription(int modID, QVariant userData) = 0; + virtual void requestFiles(int modID, QVariant userData) = 0; + virtual void requestFileInfo(int modID, int fileID, QVariant userData) = 0; + virtual void requestDownloadURL(int modID, int fileID, QVariant userData) = 0; + virtual void requestToggleEndorsement(int modID, bool endorse, QVariant userData) = 0; + +signals: + + void nxmDescriptionAvailable(int modID, QVariant userData, QVariant resultData); + void nxmFilesAvailable(int modID, QVariant userData, QVariant resultData); + void nxmFileInfoAvailable(int modID, int fileID, QVariant userData, QVariant resultData); + void nxmDownloadURLsAvailable(int modID, int fileID, QVariant userData, QVariant resultData); + void nxmEndorsementToggled(int modID, QVariant userData, QVariant resultData); + void nxmRequestFailed(int modID, QVariant userData, const QString &errorMessage); + +}; + +}; diff --git a/src/proxy/interfaces.sip b/src/proxy/interfaces.sip new file mode 100644 index 0000000..593887e --- /dev/null +++ b/src/proxy/interfaces.sip @@ -0,0 +1,3 @@ +%Module interfaces + +%Include INexusBridge.sip diff --git a/src/proxy/proxyPython.pro b/src/proxy/proxyPython.pro index 21ba803..32c6343 100644 --- a/src/proxy/proxyPython.pro +++ b/src/proxy/proxyPython.pro @@ -13,22 +13,34 @@ CONFIG += dll DEFINES += PROXYPYTHON_LIBRARY -INCLUDEPATH += "$(BOOSTPATH)" "C:/Program Files (x86)/Python2.7/include" +INCLUDEPATH += "$(BOOSTPATH)" "$$(PYTHONPATH)/include" +INCLUDEPATH += "$$(SIPPATH)/siplib" SOURCES += proxypython.cpp \ - pythontoolwrapper.cpp \ - pythonpluginwrapper.cpp + proxypluginwrappers.cpp HEADERS += proxypython.h \ - pythontoolwrapper.h \ - pythonpluginwrapper.h + proxypluginwrappers.h \ + uibasewrappers.h -LIBS += -L"C:/Program Files (x86)/Python2.7/libs" -L"$(BOOSTPATH)/stage/lib" -lpython27 +LIBS += -L"$$(PYTHONPATH)/libs" -L"$(BOOSTPATH)/stage/lib" -lpython27 include(../plugin_template.pri) OTHER_FILES += \ - iorganizer.sip \ - configure.py + INexusBridge.sip \ + interfaces.sip \ + setup.py \ + setup.cfg -QMAKE_POST_LINK += xcopy /y /s /i *.py $$quote($$DSTDIR)\\plugins $$escape_expand(\\n) +WINPWD = $$PWD +WINPWD ~= s,/,$$QMAKE_DIR_SEP,g + + +SIPPATH="" + +QMAKE_POST_LINK += SET VS90COMNTOOLS=%VS100COMNTOOLS% $$escape_expand(\\n) + +#QMAKE_POST_LINK += $$(PYTHONPATH)/Lib/site-packages/PyQt4/sip.exe -I "$$(PYTHONPATH)/Lib/site-packages/PyQt4/sip/PyQt4" -c . -t WS_WIN -t Qt_4_8_4 -x Py_v2 -b interfaces.sbf -g interfaces.sip $$escape_expand(\\n) +QMAKE_POST_LINK += $$(PYTHONPATH)/python.exe $$WINPWD\\setup.py build_ext --sip-opts=\"-I C:\\Python27\\Lib\\site-packages\\PyQt4\\sip\\PyQt4 -e -g -t WS_WIN -t Qt_4_8_4\" build $$escape_expand(\\n) +QMAKE_POST_LINK += copy $$WINPWD\\build\\lib.win32-2.7\\interfaces.pyd $$quote($$DSTDIR)\\plugins\\data\\ $$escape_expand(\\n) diff --git a/src/proxy/proxypython.cpp b/src/proxy/proxypython.cpp index c8f08a2..d7bf555 100644 --- a/src/proxy/proxypython.cpp +++ b/src/proxy/proxypython.cpp @@ -1,123 +1,211 @@ -#include "proxypython.h" -#include -#include -#include -#include -#include - #pragma warning( push ) -#pragma warning( disable : 4100 ) +#pragma warning( disable : 4100 ) // a lot of unreferenced formal parameters from boost libraries + +#include "proxypython.h" -#include "pythontoolwrapper.h" #include #pragma warning( pop ) +#include +#include +#include +#include +#include +#include "proxypluginwrappers.h" +#include "uibasewrappers.h" + +// sip and qt slots seems to conflict +#include + using namespace MOBase; -using namespace boost::python; +namespace bpy = boost::python; -/* -long int unwrap(QObject* ptr) { - return reinterpret_cast(ptr); + +static void reportPythonError() +{ + if (PyErr_Occurred()) { + // prints to s_ErrIO buffer + PyErr_Print(); + // extract data from python buffer + bpy::object mainModule = bpy::import("__main__"); + bpy::object mainNamespace = mainModule.attr("__dict__"); + bpy::object errMsgObj = bpy::eval("s_ErrIO.getvalue()", mainNamespace); + QString errMsg = bpy::extract(errMsgObj.ptr()); + bpy::eval("s_ErrIO.truncate(0)", mainNamespace); + throw MyException(errMsg); + } else { + throw MyException("An unexpected C++ exception was thrown in python code"); + } } -template -T* wrap(long int ptr) { - return reinterpret_cast(ptr); -} -*/ struct QString_to_python_str { - static PyObject* convert(QString const& s) { - return boost::python::incref( - boost::python::object( - s.toUtf8().constData()).ptr()); + static PyObject *convert(QString const& str) { + return bpy::incref(bpy::object(str.toUtf8().constData()).ptr()); } }; struct QString_from_python_str { QString_from_python_str() { - boost::python::converter::registry::push_back( - &convertible, - &construct, - boost::python::type_id()); + bpy::converter::registry::push_back(&convertible, &construct, bpy::type_id()); } - static void* convertible(PyObject* obj_ptr) { - if (!PyString_Check(obj_ptr)) return 0; - return obj_ptr; + static void *convertible(PyObject *objPtr) { + return PyString_Check(objPtr) ? objPtr : NULL; } - static void construct(PyObject* obj_ptr, boost::python::converter::rvalue_from_python_stage1_data* data) { + static void construct(PyObject *objPtr, bpy::converter::rvalue_from_python_stage1_data *data) { // Extract the character data from the python string - const char* value = PyString_AsString(obj_ptr); + const char* value = PyString_AsString(objPtr); + assert(value != NULL); - // Verify that obj_ptr is a string (should be ensured by convertible()) - assert(value); - - // Grab pointer to memory into which to construct the new QString - void* storage = ( - (boost::python::converter::rvalue_from_python_storage*) - data)->storage.bytes; - - // in-place construct the new QString using the character data - // extraced from the python object + // allocate storage + void *storage = ((bpy::converter::rvalue_from_python_storage*)data)->storage.bytes; + // construct QString in the allocated mr new (storage) QString(value); - // Stash the memory chunk pointer for later use by boost.python data->convertible = storage; } }; -struct QVariant_to_python_str +template +struct GuessedValue_converters { - static PyObject* convert(QVariant const &var) { + struct GuessedValue_to_python + { + static PyObject *convert(const GuessedValue &var) { + bpy::list result; + const std::set &values = var.variants(); + for (auto iter = values.begin(); iter != values.end(); ++iter) { + result.append(bpy::make_tuple(*iter, GUESS_GOOD)); + } + return bpy::incref(result.ptr()); + } + }; + + struct GuessedValue_from_python + { + GuessedValue_from_python() { + bpy::converter::registry::push_back(&convertible, &construct, bpy::type_id >()); + } + + static void *convertible(PyObject *objPtr) { + if PyList_Check(objPtr) { + return objPtr; + } else { + return NULL; + } + } + + + static void construct(PyObject *objPtr, bpy::converter::rvalue_from_python_stage1_data* data) { + void *storage = ((bpy::converter::rvalue_from_python_storage >*)data)->storage.bytes; + GuessedValue *result = new (storage) GuessedValue(); + + bpy::list source(bpy::handle<>(bpy::borrowed(objPtr))); + int length = bpy::len(source); + for (int i = 0; i < length; ++i) { + bpy::tuple cell = bpy::extract(source[i]); + result->update(bpy::extract(cell[0]), bpy::extract(cell[1])); + } + + data->convertible = storage; + } + }; + + GuessedValue_converters() + { + GuessedValue_from_python(); + bpy::to_python_converter, GuessedValue_to_python>(); + } +}; + + +struct QVariant_to_python_obj +{ + static PyObject *convert(const QVariant &var) { switch (var.type()) { case QVariant::Int: return PyLong_FromLong(var.toInt()); case QVariant::Bool: return PyBool_FromLong(var.toBool()); - case QVariant::String: return boost::python::incref(boost::python::object(var.toString().toUtf8().constData()).ptr()); + case QVariant::String: return bpy::incref(bpy::object(var.toString().toUtf8().constData()).ptr()); + case QVariant::List: { + qDebug("to list convert"); + QVariantList list = var.toList(); + PyObject *result = PyList_New(list.count()); + foreach (QVariant var, list) { + PyList_Append(result, convert(var)); + } + return result; + } break; default: { - qCritical("variant type not supportd in python: %d", var.type()); - throw boost::python::error_already_set(); + PyErr_SetString(PyExc_TypeError, "type unsupported"); + throw bpy::error_already_set(); } break; } } }; -struct QVariant_from_python_str +struct QVariant_from_python_obj { - QVariant_from_python_str() { - boost::python::converter::registry::push_back( - &convertible, - &construct, - boost::python::type_id()); + QVariant_from_python_obj() { + bpy::converter::registry::push_back(&convertible, &construct, bpy::type_id()); } - static void* convertible(PyObject* objPtr) { - if (!PyString_Check(objPtr) && !PyInt_Check(objPtr) && !PyBool_Check(objPtr)) return 0; + static void *convertible(PyObject *objPtr) { + if (!PyString_Check(objPtr) && !PyInt_Check(objPtr) && + !PyBool_Check(objPtr) && !PyList_Check(objPtr)) { + return NULL; + } return objPtr; } template - static void constructVariant(const T &value, boost::python::converter::rvalue_from_python_stage1_data *data) { - void* storage = ((boost::python::converter::rvalue_from_python_storage*)data)->storage.bytes; + static void constructVariant(const T &value, bpy::converter::rvalue_from_python_stage1_data *data) { + void* storage = ((bpy::converter::rvalue_from_python_storage*)data)->storage.bytes; new (storage) QVariant(value); data->convertible = storage; } + static 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)) { + result = PyInt_AsLong(objPtr); + } else { + PyErr_SetString(PyExc_TypeError, "type unsupported"); + throw bpy::error_already_set(); + } + return result; + } - static void construct(PyObject *objPtr, boost::python::converter::rvalue_from_python_stage1_data* data) { + 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 (PyString_Check(objPtr)) { + 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); } else if (PyBool_Check(objPtr)) { @@ -127,222 +215,402 @@ struct QVariant_from_python_str long value = PyInt_AsLong(objPtr); constructVariant(value, data); } else { - // cause a type-error message to be printed. I guess this isn't a very clean - // way of doing that - PyString_AsString(objPtr); - throw boost::python::error_already_set(); + PyErr_SetString(PyExc_TypeError, "type unsupported"); + throw bpy::error_already_set(); } } }; - - - - -template -struct QList_ptr_to_python_list +template +struct QList_to_python_list { - typedef typename C::value_type T; - typedef typename C::const_iterator iter; - static PyObject* convert(C const &list) + static PyObject *convert(const QList &list) { - // the python list + qDebug("convert list"); + boost::python::list pyList; - // we need to wrap the pointers into PyObjects - typename boost::python::reference_existing_object::apply::type converter; - for (iter i = list.begin(); i != list.end(); ++i) { - PyObject *obj = converter(*i); - object real_obj = object(handle<>(obj)); - // append the PyObject - pyList.append(real_obj); + foreach (const T &item, list) { + pyList.append(item); } - return incref(pyList.ptr()); + return bpy::incref(pyList.ptr()); } }; -template -struct QList_ptr_from_python_list + + +template +struct QList_from_python_obj { - typedef typename C::value_type T; - QList_ptr_from_python_list() - { - converter::registry::push_back( &convertible, &construct, type_id() ); + QList_from_python_obj() { + bpy::converter::registry::push_back( + &convertible, + &construct, + bpy::type_id >()); } - static void* convertible(PyObject *obj_ptr) - { - //is this a tuple type? - if (PyTuple_Check(obj_ptr)) { - //check the tuple elements... - convert to a boost::tuple object - boost::python::tuple t( handle<>(borrowed(obj_ptr)) ); - //how many elements are there? - int n = PyTuple_Size(obj_ptr); - //can they all be converted to type 'T'? - for (int i=0; i(t[i]).check()) - return 0; + + static void* convertible(PyObject *objPtr) { + if (PyList_Check(objPtr)) return objPtr; + return NULL; + } + + static void construct(PyObject *objPtr, bpy::converter::rvalue_from_python_stage1_data *data) { + void *storage = ((bpy::converter::rvalue_from_python_storage >*)data)->storage.bytes; + QList *result = new (storage) QList(); + bpy::list source(bpy::handle<>(bpy::borrowed(objPtr))); + int length = bpy::len(source); + for (int i = 0; i < length; ++i) { + result->append(bpy::extract(source[i])); + } + + data->convertible = storage; + } +}; + + +template +struct stdset_from_python_list +{ + stdset_from_python_list() { + bpy::converter::registry::push_back( + &convertible, + &construct, + bpy::type_id >()); + } + + static void* convertible(PyObject *objPtr) { + if (PyList_Check(objPtr)) return objPtr; + return NULL; + } + + 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::set *result = new (storage) std::set(); + + bpy::list source(bpy::handle<>(bpy::borrowed(objPtr))); + int length = bpy::len(source); + try { + for (int i = 0; i < length; ++i) { + result->insert(bpy::extract(source[i])); } - //the tuple is ok! - return obj_ptr; + } catch (const bpy::error_already_set&) { + qDebug("bla"); } - //is this a list type? - else if (PyList_Check(obj_ptr)) { - //check that all of the list elements can be converted to the right type - boost::python::list l( handle<>(borrowed(obj_ptr)) ); - //how many elements are there? - int n = PyList_Size(obj_ptr); - //can all of the elements be converted to type 'T'? - for (int i=0; i(l[i]).check()) - return 0; - } - //the list is ok! - return obj_ptr; - } - //could not recognise the type... - return 0; + + data->convertible = storage; } - static void construct(PyObject *obj_ptr, converter::rvalue_from_python_stage1_data *data) +}; + + +static const sipAPIDef *sipAPI() +{ + static const sipAPIDef *sipApi = NULL; + if (sipApi == NULL) { + sipApi = (const sipAPIDef *)PyCapsule_Import("sip._C_API", 0); + } + + return sipApi; +} + + +template struct MetaData; + +template <> struct MetaData { static const char *className() { return "MOBase::INexusBridge"; } }; +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 "QIcon"; } }; +template <> struct MetaData { static const char *className() { return "QVariant"; } }; +//template <> struct MetaData > { static const char *className() { return "QList"; } }; + + +template +PyObject *toPyQt(T *objPtr) +{ + if (objPtr == NULL) { + qDebug("no input object"); + return bpy::incref(Py_None); + } + + const sipTypeDef *type = sipAPI()->api_find_type(MetaData::className()); + + if (type == NULL) { + qDebug("failed to determine type"); + return bpy::incref(Py_None); + } + + PyObject *sipObj = sipAPI()->api_convert_from_type(objPtr, type, 0); + if (sipObj == NULL) { + qDebug("failed to convert"); + return bpy::incref(Py_None); + } + return bpy::incref(sipObj); +} + + +template +struct QClass_converters +{ + struct QClass_to_PyQt { - if (PyTuple_Check(obj_ptr)) { - //convert the PyObject to a boost::python::object - boost::python::tuple t( handle<>(borrowed(obj_ptr)) ); - //locate the storage space for the result - void* storage = ((converter::rvalue_from_python_storage*)data)->storage.bytes; - //create the T container - new (storage) C(); - C *container = static_cast(storage); - //add all of the elements from the tuple - get the number of elements in the tuple - int n = PyTuple_Size(obj_ptr); - for (int i=0; iappend( extract(t[i])() ); - data->convertible = storage; + static PyObject *convert(const T &object) { + const sipTypeDef *type = sipAPI()->api_find_type(MetaData::className()); + if (type == NULL) { + return bpy::incref(Py_None); + } + + PyObject *sipObj = sipAPI()->api_convert_from_type((void*)(&object), type, 0); + if (sipObj == NULL) { + return bpy::incref(Py_None); + } + + return bpy::incref(sipObj); } - else if (PyList_Check(obj_ptr)) { - //convert the PyObject to a boost::python::object - boost::python::list l( handle<>(borrowed(obj_ptr)) ); - //locate the storage space for the result - void* storage = ((converter::rvalue_from_python_storage*)data)->storage.bytes; - //create the T container - new (storage) C(); - C *container = static_cast(storage); - //add all of the elements from the tuple - get the number of elements in the tuple - int n = PyList_Size(obj_ptr); - for (int i=0; iappend( extract(l[i])() ); - data->convertible = storage; + + static PyObject *convert(T *object) { + if (object == NULL) { + return bpy::incref(Py_None); + } + + const sipTypeDef *type = sipAPI()->api_find_type(MetaData::className()); + if (type == NULL) { + return bpy::incref(Py_None); + } + + PyObject *sipObj = sipAPI()->api_convert_from_type(object, type, 0); + if (sipObj == NULL) { + return bpy::incref(Py_None); + } + + return bpy::incref(sipObj); } + + static PyObject *convert(const T *object) { + return convert((T*)object); + } + }; + + static void *QClass_from_PyQt(PyObject *objPtr) + { + if (!PyObject_TypeCheck(objPtr, sipAPI()->api_wrapper_type)) { + PyErr_SetString(PyExc_TypeError, "type not wrapped"); + bpy::throw_error_already_set(); + } + + sipAPI()->api_transfer_to(objPtr, 0); + + sipSimpleWrapper *wrapper = reinterpret_cast(objPtr); + return wrapper->data; + } + + QClass_converters() + { + bpy::converter::registry::insert(&QClass_from_PyQt, bpy::type_id()); + bpy::to_python_converter(); + bpy::to_python_converter(); + bpy::to_python_converter(); } }; -struct IOrganizerWrapper: MOBase::IOrganizer, wrapper + +template +struct QInterface_converters { - virtual MOBase::IGameInfo &gameInfo() const { - MOBase::IGameInfo *result = this->get_override("gameInfo")(); - return *result; + struct QInterface_to_PyQt + { + static PyObject *convert(const T &object) { + const sipTypeDef *type = sipAPI()->api_find_type(MetaData::className()); + if (type == NULL) { + return bpy::incref(Py_None); + } + + PyObject *sipObj = sipAPI()->api_convert_from_type((void*)(&object), type, 0); + if (sipObj == NULL) { + return bpy::incref(Py_None); + } + + return bpy::incref(sipObj); + } + + static PyObject *convert(T *object) { + if (object == NULL) { + return bpy::incref(Py_None); + } + + const sipTypeDef *type = sipAPI()->api_find_type(MetaData::className()); + if (type == NULL) { + return bpy::incref(Py_None); + } + + PyObject *sipObj = sipAPI()->api_convert_from_type(object, type, 0); + if (sipObj == NULL) { + return bpy::incref(Py_None); + } + + return bpy::incref(sipObj); + } + + static PyObject *convert(const T *object) { + return convert((T*)object); + } + }; + + static void *QInterface_from_PyQt(PyObject *objPtr) + { + if (!PyObject_TypeCheck(objPtr, sipAPI()->api_wrapper_type)) { + bpy::throw_error_already_set(); + } + + sipAPI()->api_transfer_to(objPtr, 0); + + sipSimpleWrapper *wrapper = reinterpret_cast(objPtr); + return wrapper->data; + } + + QInterface_converters() + { + bpy::converter::registry::insert(&QInterface_from_PyQt, bpy::type_id()); + bpy::to_python_converter(); + bpy::to_python_converter(); } - virtual QString profileName() const { return this->get_override("profileName")(); } - virtual QString profilePath() const { return this->get_override("profilePath")(); } - virtual QString downloadsPath() const { return this->get_override("downloadsPath")(); } - virtual MOBase::VersionInfo appVersion() const { return this->get_override("appVersion")(); } - virtual MOBase::IModInterface *getMod(const QString &name) { return this->get_override("getMod")(name); } - virtual MOBase::IModInterface *createMod(const QString &name) { return this->get_override("createMod")(name); } - virtual bool removeMod(MOBase::IModInterface *mod) { return this->get_override("removeMod")(mod); } - virtual void modDataChanged(MOBase::IModInterface *mod) { this->get_override("modDataChanged")(mod); } - virtual QVariant pluginSetting(const QString &pluginName, const QString &key) const { qDebug("xyz"); return this->get_override("pluginSetting")(pluginName, key); } - virtual QString pluginDataPath() const { return this->get_override("pluginDataPath")(); } }; -struct IGameInfoWrapper: MOBase::IGameInfo, wrapper -{ - virtual Type type() const { return this->get_override("type")(); } - virtual QString path() const { return this->get_override("path")(); } - virtual QString binaryName() const { return this->get_override("binaryName")(); } -}; - - - -enum PluginType { - PLUGIN_TOOL, - PLUGIN_INSTALLER_SIMPLE, - PLUGIN_INSTALLER_COMPLEX -}; +BOOST_PYTHON_MEMBER_FUNCTION_OVERLOADS(updateWithQuality, MOBase::GuessedValue::update, 2, 2) BOOST_PYTHON_MODULE(mobase) { - 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); + bpy::to_python_converter(); + QVariant_from_python_obj(); - enum_("PluginType") - .value("tool", PLUGIN_TOOL) - .value("installer_simple", PLUGIN_INSTALLER_SIMPLE) - .value("installer_complex", PLUGIN_INSTALLER_COMPLEX); + bpy::to_python_converter(); + QString_from_python_str(); - enum_("GameType") + QClass_converters(); + QClass_converters(); + //QClass_converters(); + QClass_converters(); + QInterface_converters(); + + bpy::def("toPyQt", &toPyQt); + + bpy::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); + + 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::enum_("GameType") .value("oblivion", MOBase::IGameInfo::TYPE_OBLIVION) .value("fallout3", MOBase::IGameInfo::TYPE_FALLOUT3) .value("falloutnv", MOBase::IGameInfo::TYPE_FALLOUTNV) .value("skyrim", MOBase::IGameInfo::TYPE_SKYRIM); - class_("VersionInfo") - .def(init()) + bpy::class_("VersionInfo") + .def(bpy::init()) .def("parse", &MOBase::VersionInfo::parse) .def("canonicalString", &MOBase::VersionInfo::canonicalString); - class_("PluginSetting", init()); + bpy::class_("PluginSetting", bpy::init()); - class_("GameInfo") - .def("type", pure_virtual(&MOBase::IGameInfo::type)) - .def("path", pure_virtual(&MOBase::IGameInfo::path)) - .def("binaryName", pure_virtual(&MOBase::IGameInfo::binaryName)); + bpy::class_("GameInfo") + .def("type", bpy::pure_virtual(&MOBase::IGameInfo::type)) + .def("path", bpy::pure_virtual(&MOBase::IGameInfo::path)) + .def("binaryName", bpy::pure_virtual(&MOBase::IGameInfo::binaryName)); - class_("IOrganizer") - .def("gameInfo", pure_virtual(&MOBase::IOrganizer::gameInfo), return_value_policy()) - .def("profileName", pure_virtual(&MOBase::IOrganizer::profileName)) - .def("profilePath", pure_virtual(&MOBase::IOrganizer::profilePath)) - .def("downloadsPath", pure_virtual(&MOBase::IOrganizer::downloadsPath)) - .def("appVersion", pure_virtual(&MOBase::IOrganizer::appVersion)) - .def("getMod", pure_virtual(&MOBase::IOrganizer::getMod), return_value_policy()) - .def("createMod", pure_virtual(&MOBase::IOrganizer::createMod), return_value_policy()) - .def("removeMod", pure_virtual(&MOBase::IOrganizer::removeMod)) - .def("modDataChanged", pure_virtual(&MOBase::IOrganizer::modDataChanged)) - .def("pluginSetting", pure_virtual(&MOBase::IOrganizer::pluginSetting)) - .def("pluginDataPath", pure_virtual(&MOBase::IOrganizer::pluginDataPath)); + bpy::class_("IOrganizer") + .def("gameInfo", bpy::pure_virtual(&MOBase::IOrganizer::gameInfo), bpy::return_value_policy()) + .def("createNexusBridge", bpy::pure_virtual(&MOBase::IOrganizer::createNexusBridge), bpy::return_value_policy()) + .def("profileName", bpy::pure_virtual(&MOBase::IOrganizer::profileName)) + .def("profilePath", bpy::pure_virtual(&MOBase::IOrganizer::profilePath)) + .def("downloadsPath", bpy::pure_virtual(&MOBase::IOrganizer::downloadsPath)) + .def("appVersion", bpy::pure_virtual(&MOBase::IOrganizer::appVersion)) + .def("getMod", bpy::pure_virtual(&MOBase::IOrganizer::getMod), bpy::return_value_policy()) + .def("createMod", bpy::pure_virtual(&MOBase::IOrganizer::createMod), bpy::return_value_policy()) + .def("removeMod", bpy::pure_virtual(&MOBase::IOrganizer::removeMod)) + .def("modDataChanged", bpy::pure_virtual(&MOBase::IOrganizer::modDataChanged)) + .def("pluginSetting", bpy::pure_virtual(&IOrganizer::pluginSetting)) + .def("pluginDataPath", bpy::pure_virtual(&IOrganizer::pluginDataPath)); - boost::python::to_python_converter< - QString, - QString_to_python_str>(); + bpy::class_("INexusBridge") + .def("requestDescription", bpy::pure_virtual(&MOBase::IModRepositoryBridge::requestDescription)) + .def("requestFiles", bpy::pure_virtual(&MOBase::IModRepositoryBridge::requestFiles)) + .def("requestFileInfo", bpy::pure_virtual(&MOBase::IModRepositoryBridge::requestFileInfo)) + .def("requestDownloadURL", bpy::pure_virtual(&MOBase::IModRepositoryBridge::requestDownloadURL)) + .def("requestToggleEndorsement", bpy::pure_virtual(&MOBase::IModRepositoryBridge::requestToggleEndorsement)) + ; - QString_from_python_str(); + bpy::enum_("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); - boost::python::to_python_converter< - QVariant, - QVariant_to_python_str>(); + bpy::class_, boost::noncopyable>("GuessedString") + .def("update", + static_cast &(GuessedValue::*)(const QString&, EGuessQuality)>(&GuessedValue::update), + bpy::return_value_policy(), updateWithQuality()) + .def("variants", &MOBase::GuessedValue::variants, bpy::return_value_policy()); - QVariant_from_python_str(); + bpy::class_("IPluginTool") + .def("setParentWidget", bpy::pure_virtual(&MOBase::IPluginTool::setParentWidget)); + bpy::class_("IPluginInstallerCustom") + .def("setParentWidget", bpy::pure_virtual(&MOBase::IPluginInstallerCustom::setParentWidget)); -/* - boost::python::to_python_converter< - QList, - QList_ptr_to_python_list > >(); + bpy::class_("NexusFileInfo") + .def_readonly("name", &ModRepositoryFileInfo::name) + .def_readonly("uri", &ModRepositoryFileInfo::uri); +/* QString name; + QString uri; + VersionInfo version; + int categoryID; + int fileID;*/ - QList_ptr_from_python_list >();*/ + GuessedValue_converters(); + + QList_from_python_obj(); + bpy::to_python_converter, + QList_to_python_list >(); + + stdset_from_python_list(); } -static char* argv0 = "Banana?"; - ProxyPython::ProxyPython() : m_MOInfo(NULL) { - PyImport_AppendInittab("mobase", &initmobase); - Py_Initialize(); + try { + PyImport_AppendInittab("mobase", &initmobase); + Py_Initialize(); + qDebug("Python: %s", Py_GetVersion()); - PySys_SetArgv(0, &argv0); + static char* argv0 = "Banana?"; + PySys_SetArgv(0, &argv0); + + bpy::object main_module = bpy::import("__main__"); + bpy::object main_namespace = main_module.attr("__dict__"); + main_namespace["cStringIO"] = bpy::import("cStringIO"); + main_namespace["sys"] = bpy::import("sys"); + bpy::exec("s_ErrIO = cStringIO.StringIO()\n" + "sys.stderr = s_ErrIO", + main_namespace); + } catch (const bpy::error_already_set&) { + qDebug("failed to init python"); + PyErr_Print(); + if (PyErr_Occurred()) { + PyErr_Print(); + } else { + qCritical("An unexpected C++ exception was thrown in python code"); + } + } } bool ProxyPython::init(IOrganizer *moInfo) @@ -394,58 +662,54 @@ QStringList ProxyPython::pluginList(const QString &pluginPath) const } -bool handled_exec_file(str filename, object globals = object(), object locals = object()) +bool handled_exec_file(bpy::str filename, bpy::object globals = bpy::object(), bpy::object locals = bpy::object()) { - return handle_exception(boost::bind(exec_file, filename, globals, locals)); + return bpy::handle_exception(boost::bind(bpy::exec_file, filename, globals, locals)); } -QObject *ProxyPython::instantiate(const QString &pluginName) const +#define TRY_PLUGIN_TYPE(type, var) do { \ + bpy::extract extr(var); \ + if (extr.check()) { \ + QObject *res = extr; \ + return res; \ + }\ + } while (false) + + +QObject *ProxyPython::instantiate(const QString &pluginName) { try { - object main_module = import("__main__"); - object main_namespace = main_module.attr("__dict__"); + bpy::object main_module = bpy::import("__main__"); + bpy::object main_namespace = main_module.attr("__dict__"); - object mobase_module((handle<>(PyImport_ImportModule("mobase")))); + bpy::object mobase_module((bpy::handle<>(PyImport_ImportModule("mobase")))); main_namespace["mobase"] = mobase_module; + QString appendDataPath = QString("sys.path.append(\"%1\")").arg(m_MOInfo->pluginDataPath()); + + bpy::eval(appendDataPath.toUtf8().constData(), main_namespace); + + main_namespace["interfaces"] = bpy::import("interfaces"); + std::string temp = ToString(pluginName); if (handled_exec_file(temp.c_str(), main_namespace)) { - if (PyErr_Occurred()) { - PyErr_Print(); - } else { - qCritical("An unexpected C++ exception was thrown in python code"); - } + reportPythonError(); return NULL; } + m_PythonObjects[pluginName] = main_namespace["createPlugin"](); - PluginType type = boost::python::extract(main_namespace["type"]()); - - switch (type) { - case PLUGIN_TOOL: return new PythonToolWrapper( - main_namespace["init"], - main_namespace["name"], - main_namespace["author"], - main_namespace["description"], - main_namespace["version"], - main_namespace["isActive"], - main_namespace["settings"], - main_namespace["displayName"], - main_namespace["tooltip"], - main_namespace["icon"], - main_namespace["display"] - ); - case PLUGIN_INSTALLER_SIMPLE: return NULL; - case PLUGIN_INSTALLER_COMPLEX: return NULL; - } - + bpy::object pluginObj = m_PythonObjects[pluginName]; + TRY_PLUGIN_TYPE(IPluginInstallerCustom, pluginObj); + TRY_PLUGIN_TYPE(IPluginTool, pluginObj); } catch (const std::exception &e) { - qDebug("failed to run python script \"%s\": %s", qPrintable(pluginName), e.what()); - } catch (const error_already_set&) { - qDebug("failed to run python script \"%s\"", qPrintable(pluginName)); - PyErr_Print(); + qWarning("failed to run python script \"%s\": %s", qPrintable(pluginName), e.what()); + } catch (const bpy::error_already_set&) { + qWarning("failed to run python script \"%s\"", qPrintable(pluginName)); + reportPythonError(); } return NULL; } + Q_EXPORT_PLUGIN2(proxyPython, ProxyPython) diff --git a/src/proxy/proxypython.h b/src/proxy/proxypython.h index 2c266aa..ec683c2 100644 --- a/src/proxy/proxypython.h +++ b/src/proxy/proxypython.h @@ -3,10 +3,14 @@ #include -#include +#include + +#ifndef Q_MOC_RUN +#include +#endif -class ProxyPython : public MOBase::IPluginProxy +class ProxyPython : public QObject, MOBase::IPluginProxy { Q_OBJECT Q_INTERFACES(MOBase::IPlugin MOBase::IPluginProxy) @@ -23,10 +27,20 @@ public: virtual QList settings() const; QStringList pluginList(const QString &pluginPath) const; - QObject *instantiate(const QString &pluginName) const; + QObject *instantiate(const QString &pluginName); + + /** + * @return the parent widget for newly created dialogs + * @note needs to be public so it can be exposed to plugins + */ + virtual QWidget *getParentWidget() { return parentWidget(); } private: + const MOBase::IOrganizer *m_MOInfo; + + std::map m_PythonObjects; + }; #endif // PROXYPYTHON_H diff --git a/src/proxy/pythonpluginwrapper.h b/src/proxy/pythonpluginwrapper.h index 8d1d928..19c93a9 100644 --- a/src/proxy/pythonpluginwrapper.h +++ b/src/proxy/pythonpluginwrapper.h @@ -38,6 +38,7 @@ private: boost::python::object m_VersionFunction; boost::python::object m_IsActiveFunction; boost::python::object m_SettingsFunction; + }; #endif // PYTHONPLUGINWRAPPER_H diff --git a/src/proxy/pythontoolwrapper.cpp b/src/proxy/pythontoolwrapper.cpp index e63fb11..8690dff 100644 --- a/src/proxy/pythontoolwrapper.cpp +++ b/src/proxy/pythontoolwrapper.cpp @@ -15,16 +15,20 @@ PythonToolWrapper::PythonToolWrapper(const boost::python::object &initFunction, const boost::python::object &displayNameFunction, const boost::python::object &tooltipFunction, const boost::python::object &iconFunction, - const boost::python::object &displayFunction) + const boost::python::object &displayFunction, + const boost::python::object &setParentWidget) : PythonPluginWrapper(initFunction, nameFunction, authorFunction, descriptionFunction, versionFunction, isActiveFunction, settingsFunction) , m_DisplayNameFunction(displayNameFunction) , m_ToolTipFunction(tooltipFunction) , m_IconFunction(iconFunction) , m_DisplayFunction(displayFunction) + , m_SetParentWidget(setParentWidget) { } + + QString PythonToolWrapper::displayName() const { try { @@ -67,12 +71,9 @@ QIcon PythonToolWrapper::icon() const void PythonToolWrapper::display() const { try { - // can't make the dialog properly modal but we can disable input on the main window - // which has a similar effect - QWidget *mainWindow = QApplication::activeWindow(); - if (mainWindow != NULL) mainWindow->setEnabled(false); + m_SetParentWidget(parentWidget()); + m_DisplayFunction(); - if (mainWindow != NULL) mainWindow->setEnabled(true); } catch (const boost::python::error_already_set&) { reportPythonError(); } diff --git a/src/proxy/pythontoolwrapper.h b/src/proxy/pythontoolwrapper.h index 86126ce..a5e2a45 100644 --- a/src/proxy/pythontoolwrapper.h +++ b/src/proxy/pythontoolwrapper.h @@ -10,7 +10,7 @@ #include "pythonpluginwrapper.h" -class PythonToolWrapper : public QObject, public PythonPluginWrapper, public MOBase::IPluginTool +class PythonToolWrapper : public QObject, public MOBase::IPluginTool, public PythonPluginWrapper { Q_OBJECT @@ -27,7 +27,11 @@ public: const boost::python::object &displayNameFunction, const boost::python::object &tooltipFunction, const boost::python::object &iconFunction, - const boost::python::object &displayFunction); + const boost::python::object &displayFunction, + const boost::python::object &setParentWidget); + + using PythonPluginWrapper::init; + using PythonPluginWrapper::name; virtual QString displayName() const; virtual QString tooltip() const; @@ -44,6 +48,7 @@ private: boost::python::object m_IconFunction; boost::python::object m_DisplayFunction; + boost::python::object m_SetParentWidget; }; diff --git a/src/proxy/setup.cfg b/src/proxy/setup.cfg new file mode 100644 index 0000000..42529f5 --- /dev/null +++ b/src/proxy/setup.cfg @@ -0,0 +1,2 @@ +[build_ext] +sip-opts = -e -g -I $$(PYTHONPATH)/Lib/site-packages/PyQt4/sip/PyQt4 diff --git a/src/proxy/setup.py b/src/proxy/setup.py new file mode 100644 index 0000000..82ef29c --- /dev/null +++ b/src/proxy/setup.py @@ -0,0 +1,27 @@ +from distutils.core import setup, Extension +import sipdistutils +import os, sys + +path = os.path.dirname(sys.argv[0]) +if len(path) > 0: + os.chdir(path) +os.environ['PATH'] += r";C:\Program Files (x86)\Microsoft Visual Studio 10.0\VC" +print(os.environ['PATH']) + +os.environ['VS90COMNTOOLS'] = os.environ['VS100COMNTOOLS'] + +setup( + name = 'interfaces', + versione = '1.0', + ext_modules=[ + Extension("interfaces", [ "interfaces.sip" ], + include_dirs=[ r"C:\QtSDK\Desktop\Qt\4.8.4\include", + r"C:\QtSDK\Desktop\Qt\4.8.4\include\QtCore", + path + r"\..\..\uibase" ], + library_dirs=[ r"C:\QtSDK\Desktop\Qt\4.8.4\lib" ], + libraries=[ "QtCore4" ] + ), + ], + + cmdclass = {'build_ext': sipdistutils.build_ext} +) diff --git a/src/proxy/uibasewrappers.h b/src/proxy/uibasewrappers.h new file mode 100644 index 0000000..fa65530 --- /dev/null +++ b/src/proxy/uibasewrappers.h @@ -0,0 +1,62 @@ +#ifndef UIBASEWRAPPERS_H +#define UIBASEWRAPPERS_H + + +#ifndef Q_MOC_RUN +#include +#endif + +#include + +#include +#include +#include + + +struct IOrganizerWrapper: MOBase::IOrganizer, boost::python::wrapper +{ + virtual MOBase::IGameInfo &gameInfo() const { + MOBase::IGameInfo *result = this->get_override("gameInfo")(); + return *result; + } + virtual MOBase::IModRepositoryBridge *createNexusBridge() const { + return this->get_override("createNexusBridge")(); + } + virtual QString profileName() const { return this->get_override("profileName")(); } + virtual QString profilePath() const { return this->get_override("profilePath")(); } + virtual QString downloadsPath() const { return this->get_override("downloadsPath")(); } + virtual MOBase::VersionInfo appVersion() const { return this->get_override("appVersion")(); } + virtual MOBase::IModInterface *getMod(const QString &name) { return this->get_override("getMod")(name); } + virtual MOBase::IModInterface *createMod(const QString &name) { return this->get_override("createMod")(name); } + virtual bool removeMod(MOBase::IModInterface *mod) { return this->get_override("removeMod")(mod); } + virtual void modDataChanged(MOBase::IModInterface *mod) { this->get_override("modDataChanged")(mod); } + virtual QVariant pluginSetting(const QString &pluginName, const QString &key) const { return this->get_override("pluginSetting")(pluginName, key); } + virtual QString pluginDataPath() const { return this->get_override("pluginDataPath")(); } +}; + + +struct IGameInfoWrapper: MOBase::IGameInfo, boost::python::wrapper +{ + virtual Type type() const { return this->get_override("type")(); } + virtual QString path() const { return this->get_override("path")(); } + virtual QString binaryName() const { return this->get_override("binaryName")(); } +}; + + +struct INexusBridgeWrapper: MOBase::IModRepositoryBridge, boost::python::wrapper +{ + virtual void requestDescription(int modID, QVariant userData) + { this->get_override("requestDescription")(modID, userData); } + virtual void requestFiles(int modID, QVariant userData) + { this->get_override("requestFiles")(modID, userData); } + virtual void requestFileInfo(int modID, int fileID, QVariant userData) + { this->get_override("requestFileInfo")(modID, fileID, userData); } + virtual void requestDownloadURL(int modID, int fileID, QVariant userData) + { this->get_override("requestDownloadURL")(modID, fileID, userData); } + virtual void requestToggleEndorsement(int modID, bool endorse, QVariant userData) + { this->get_override("requestToggleEndorsement")(modID, endorse, userData); } + +}; + + +#endif // UIBASEWRAPPERS_H