/*************************************************************************** * Copyright (c) 2021 Werner Mayer * * * * This file is part of the FreeCAD CAx development system. * * * * This library is free software; you can redistribute it and/or * * modify it under the terms of the GNU Library General Public * * License as published by the Free Software Foundation; either * * version 2 of the License, or (at your option) any later version. * * * * This library is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU Library General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this library; see the file COPYING.LIB. If not, * * write to the Free Software Foundation, Inc., 59 Temple Place, * * Suite 330, Boston, MA 02111-1307, USA * * * ***************************************************************************/ #include "PreCompiled.h" #ifndef _PreComp_ # include # include # include # include # include # include # include # include #endif #include // Uncomment this block to remove PySide C++ support and switch to its Python interface //#undef HAVE_SHIBOKEN2 //#undef HAVE_PYSIDE2 //#undef HAVE_SHIBOKEN6 //#undef HAVE_PYSIDE6 #ifdef FC_OS_WIN32 #undef max #undef min #ifdef _MSC_VER #pragma warning( disable : 4099 ) #pragma warning( disable : 4522 ) #endif #endif #if defined(__clang__) # pragma clang diagnostic push # pragma clang diagnostic ignored "-Wmismatched-tags" # pragma clang diagnostic ignored "-Wunused-parameter" # if __clang_major__ > 3 # pragma clang diagnostic ignored "-Wkeyword-macro" # endif #elif defined (__GNUC__) # pragma GCC diagnostic push # pragma GCC diagnostic ignored "-Wunused-parameter" # pragma GCC diagnostic ignored "-Wdeprecated-declarations" #endif //----------------------------------------------------------------------------- // // shiboken2 and PySide2 specific defines and includes // // class and struct used for SbkObject // #ifdef HAVE_SHIBOKEN2 # define HAVE_SHIBOKEN # ifdef HAVE_PYSIDE2 # define HAVE_PYSIDE // Since version 5.12 shiboken offers a method to get wrapper by class name (typeForTypeName) // This helps to avoid to include the PySide2 headers since MSVC has a compiler bug when // compiling together with std::bitset (https://bugreports.qt.io/browse/QTBUG-72073) // Do not use SHIBOKEN_MICRO_VERSION; it might contain a dot # define SHIBOKEN_FULL_VERSION QT_VERSION_CHECK(SHIBOKEN_MAJOR_VERSION, SHIBOKEN_MINOR_VERSION, 0) # if (SHIBOKEN_FULL_VERSION >= QT_VERSION_CHECK(5, 12, 0)) # define HAVE_SHIBOKEN_TYPE_FOR_TYPENAME # endif # ifndef HAVE_SHIBOKEN_TYPE_FOR_TYPENAME # include # include # include # endif # endif // HAVE_PYSIDE2 #endif // HAVE_SHIBOKEN2 //----------------------------------------------------------------------------- // // shiboken6 and PySide6 specific defines and includes // // class and struct used for SbkObject // #ifdef HAVE_SHIBOKEN6 # define HAVE_SHIBOKEN # ifdef HAVE_PYSIDE6 # define HAVE_PYSIDE # define HAVE_SHIBOKEN_TYPE_FOR_TYPENAME # endif // HAVE_PYSIDE6 #endif // HAVE_SHIBOKEN6 //----------------------------------------------------------------------------- #ifdef HAVE_SHIBOKEN # undef _POSIX_C_SOURCE # undef _XOPEN_SOURCE # include # include # include # include #endif // HAVE_SHIBOKEN #ifdef HAVE_PYSIDE # include #endif // HAVE_PYSIDE //----------------------------------------------------------------------------- #if defined(__clang__) # pragma clang diagnostic pop #elif defined (__GNUC__) # pragma GCC diagnostic pop #endif // Must be imported after PySide headers #ifndef _PreComp_ # include # include #endif #include #include #include #include #include "PythonWrapper.h" #include "UiLoader.h" #include "MetaTypes.h" // NOLINTBEGIN #if defined(HAVE_SHIBOKEN2) PyTypeObject** SbkPySide2_QtCoreTypes = nullptr; PyTypeObject** SbkPySide2_QtGuiTypes = nullptr; PyTypeObject** SbkPySide2_QtWidgetsTypes = nullptr; PyTypeObject** SbkPySide2_QtPrintSupportTypes = nullptr; PyTypeObject** SbkPySide2_QtUiToolsTypes = nullptr; constexpr auto &SbkPySide_QtCoreTypes = SbkPySide2_QtCoreTypes; constexpr auto &SbkPySide_QtGuiTypes = SbkPySide2_QtGuiTypes; constexpr auto &SbkPySide_QtWidgetsTypes = SbkPySide2_QtWidgetsTypes; constexpr auto &SbkPySide_QtPrintSupportTypes = SbkPySide2_QtPrintSupportTypes; constexpr auto &SbkPySide_QtUiToolsTypes = SbkPySide2_QtUiToolsTypes; #if !defined(HAVE_PYSIDE2) constexpr const char* ModuleShiboken = "shiboken2"; #endif constexpr const char* ModulePySide = "PySide2"; #elif defined(HAVE_SHIBOKEN6) PyTypeObject** SbkPySide6_QtCoreTypes = nullptr; PyTypeObject** SbkPySide6_QtGuiTypes = nullptr; PyTypeObject** SbkPySide6_QtWidgetsTypes = nullptr; PyTypeObject** SbkPySide6_QtPrintSupportTypes = nullptr; PyTypeObject** SbkPySide6_QtUiToolsTypes = nullptr; constexpr auto &SbkPySide_QtCoreTypes = SbkPySide6_QtCoreTypes; constexpr auto &SbkPySide_QtGuiTypes = SbkPySide6_QtGuiTypes; constexpr auto &SbkPySide_QtWidgetsTypes = SbkPySide6_QtWidgetsTypes; constexpr auto &SbkPySide_QtPrintSupportTypes = SbkPySide6_QtPrintSupportTypes; constexpr auto &SbkPySide_QtUiToolsTypes = SbkPySide6_QtUiToolsTypes; #if !defined(HAVE_PYSIDE6) constexpr const char* ModuleShiboken = "shiboken6"; #endif constexpr const char* ModulePySide = "PySide6"; #else static PyTypeObject** SbkPySide_DummyTypes; constexpr auto &SbkPySide_QtCoreTypes = SbkPySide_DummyTypes; constexpr auto &SbkPySide_QtGuiTypes = SbkPySide_DummyTypes; constexpr auto &SbkPySide_QtWidgetsTypes = SbkPySide_DummyTypes; constexpr auto &SbkPySide_QtPrintSupportTypes = SbkPySide_DummyTypes; constexpr auto &SbkPySide_QtUiToolsTypes = SbkPySide_DummyTypes; # if QT_VERSION < QT_VERSION_CHECK(6,0,0) constexpr const char* ModuleShiboken = "shiboken2"; constexpr const char* ModulePySide = "PySide2"; # else constexpr const char* ModuleShiboken = "shiboken6"; constexpr const char* ModulePySide = "PySide6"; # endif #endif // NOLINTEND using namespace Gui; #if defined (HAVE_SHIBOKEN) /** Example: \code ui = FreeCADGui.UiLoader() w = ui.createWidget("Gui::InputField") w.show() w.property("quantity") \endcode */ PyObject* toPythonFuncQuantityTyped(Base::Quantity cpx) { return new Base::QuantityPy(new Base::Quantity(cpx)); } PyObject* toPythonFuncQuantity(const void* cpp) { return toPythonFuncQuantityTyped(*static_cast(cpp)); } void toCppPointerConvFuncQuantity(PyObject* pyobj,void* cpp) { *static_cast(cpp) = *static_cast(pyobj)->getQuantityPtr(); } PythonToCppFunc toCppPointerCheckFuncQuantity(PyObject* obj) { if (PyObject_TypeCheck(obj, &(Base::QuantityPy::Type))) { return toCppPointerConvFuncQuantity; } return nullptr; } void BaseQuantity_PythonToCpp_QVariant(PyObject* pyIn, void* cppOut) { Base::Quantity* q = static_cast(pyIn)->getQuantityPtr(); *((QVariant*)cppOut) = QVariant::fromValue(*q); } PythonToCppFunc isBaseQuantity_PythonToCpp_QVariantConvertible(PyObject* obj) { if (PyObject_TypeCheck(obj, &(Base::QuantityPy::Type))) { return BaseQuantity_PythonToCpp_QVariant; } return nullptr; } #if defined (HAVE_PYSIDE) Base::Quantity convertWrapperToQuantity(const PySide::PyObjectWrapper &w) { auto pyIn = static_cast(w); if (PyObject_TypeCheck(pyIn, &(Base::QuantityPy::Type))) { return *static_cast(pyIn)->getQuantityPtr(); } return Base::Quantity(std::numeric_limits::quiet_NaN()); } #endif void registerTypes() { SbkConverter* convert = Shiboken::Conversions::createConverter(&Base::QuantityPy::Type, toPythonFuncQuantity); Shiboken::Conversions::setPythonToCppPointerFunctions(convert, toCppPointerConvFuncQuantity, toCppPointerCheckFuncQuantity); Shiboken::Conversions::registerConverterName(convert, "Base::Quantity"); SbkConverter* qvariant_conv = Shiboken::Conversions::getConverter("QVariant"); if (qvariant_conv) { // The type QVariant already has a converter from PyBaseObject_Type which will // come before our own converter. Shiboken::Conversions::addPythonToCppValueConversion(qvariant_conv, BaseQuantity_PythonToCpp_QVariant, isBaseQuantity_PythonToCpp_QVariantConvertible); } #if defined (HAVE_PYSIDE) QMetaType::registerConverter(&convertWrapperToQuantity); #endif } #endif // -------------------------------------------------------- namespace Gui { static std::string getPySideModuleName(const std::string& moduleName) { std::string name(ModulePySide); name += '.'; name += moduleName; return name; } static bool loadPySideModule(const std::string& moduleName, PyTypeObject**& types) { #if defined (HAVE_SHIBOKEN) && defined(HAVE_PYSIDE) if (!types) { Shiboken::AutoDecRef requiredModule(Shiboken::Module::import(getPySideModuleName(moduleName).c_str())); if (requiredModule.isNull()) { return false; } types = Shiboken::Module::getTypes(requiredModule); } #else Q_UNUSED(moduleName) Q_UNUSED(types) #endif return true; } #if defined (HAVE_SHIBOKEN) && defined(HAVE_PYSIDE) template #if defined (HAVE_SHIBOKEN2) SbkObjectType* #else PyTypeObject* #endif getPyTypeObjectForTypeName() { #if defined (HAVE_SHIBOKEN_TYPE_FOR_TYPENAME) # if defined (HAVE_SHIBOKEN2) auto sbkType = Shiboken::ObjectType::typeForTypeName(typeid(qttype).name()); if (sbkType) { return reinterpret_cast&(sbkType->type); } # else return Shiboken::ObjectType::typeForTypeName(typeid(qttype).name()); # endif #else # if defined (HAVE_SHIBOKEN2) return reinterpret_cast(Shiboken::SbkType()); # else return Shiboken::SbkType(); # endif #endif } template qttype* qt_getCppType(PyObject* pyobj) { auto type = getPyTypeObjectForTypeName(); if (type) { if (Shiboken::Object::checkType(pyobj)) { auto skbobj = reinterpret_cast(pyobj); auto pytypeobj = reinterpret_cast(type); return static_cast(Shiboken::Object::cppPointer(skbobj, pytypeobj)); } } return nullptr; } /*! * \brief The WrapperManager class * This is a helper class that records the Python wrappers of a QObject and invalidates * them when the QObject is about to be destroyed. * This is to make sure that if the Python wrapper doesn't own the QObject it won't be notified * if the QObject is destroyed. * \code * ui = Gui.UiLoader() * lineedit = ui.createWidget("QLineEdit") * lineedit.deleteLater() * # Make sure this won't crash * lineedit.show() * \endcode */ class WrapperManager : public QObject { public: static WrapperManager& instance() { static WrapperManager singleton; return singleton; } /*! * \brief addQObject * \param obj * \param pyobj * Connects destruction event of a QObject with invalidation of its PythonWrapper via a helper QObject. */ void addQObject(QObject* obj, PyObject* pyobj) { const auto PyW_unique_name = QString::number(reinterpret_cast (pyobj)); auto PyW_invalidator = findChild (PyW_unique_name, Qt::FindDirectChildrenOnly); if (PyW_invalidator == nullptr) { PyW_invalidator = new QObject(this); PyW_invalidator->setObjectName(PyW_unique_name); Py_INCREF (pyobj); } else { PyW_invalidator->disconnect(); } auto destroyedFun = [pyobj](){ Base::PyGILStateLocker lock; auto sbk_ptr = reinterpret_cast (pyobj); if (sbk_ptr != nullptr) { Shiboken::Object::setValidCpp(sbk_ptr, false); } else { Base::Console().DeveloperError("WrapperManager", "A QObject has just been destroyed after its Pythonic wrapper.\n"); } Py_DECREF (pyobj); }; QObject::connect(PyW_invalidator, &QObject::destroyed, this, destroyedFun); QObject::connect(obj, &QObject::destroyed, PyW_invalidator, &QObject::deleteLater); } private: void wrapQApplication() { // We have to explicitly hold a reference to the wrapper of the QApplication // as otherwise it can happen that when running the gc the program crashes // The code snippet below caused a crash on older versions: // mw = Gui.getMainWindow() // mw.style() // import gc // gc.collect() auto type = getPyTypeObjectForTypeName(); if (type) { PyObject* pyobj = Shiboken::Object::newObject(type, qApp, false, false, "QApplication"); addQObject(qApp, pyobj); } } WrapperManager() { wrapQApplication(); } ~WrapperManager() override = default; }; #else static std::string formatModuleError(const std::string& name) { std::string error = "Cannot load " + name + " module"; return error; } static PyObject* importShiboken() { PyObject* obj = PyImport_ImportModule(ModuleShiboken); if (obj) { return obj; } throw Py::Exception(PyExc_ImportError, formatModuleError(ModuleShiboken)); } static PyObject* importPySide(const std::string& moduleName) { std::string name = getPySideModuleName(moduleName); PyObject* obj = PyImport_ImportModule(name.c_str()); if (obj) { return obj; } throw Py::Exception(PyExc_ImportError, formatModuleError(name)); } template qttype* qt_getCppType(PyObject* pyobj) { // https://github.com/PySide/Shiboken/blob/master/shibokenmodule/typesystem_shiboken.xml Py::Module mainmod(importShiboken(), true); Py::Callable func = mainmod.getDict().getItem("getCppPointer"); Py::Tuple arguments(1); arguments[0] = Py::asObject(pyobj); // PySide pointer Py::Tuple result(func.apply(arguments)); return reinterpret_cast(PyLong_AsVoidPtr(result[0].ptr())); } template Py::Object qt_wrapInstance(qttype object, const std::string& className, const std::string& moduleName) { Py::Module mainmod(importShiboken(), true); Py::Callable func = mainmod.getDict().getItem("wrapInstance"); Py::Tuple arguments(2); arguments[0] = Py::asObject(PyLong_FromVoidPtr((void*)object)); Py::Module qtmod(importPySide(moduleName)); arguments[1] = qtmod.getDict().getItem(className); return func.apply(arguments); } const char* qt_identifyType(QObject* ptr, const std::string& moduleName) { Py::Module qtmod(importPySide(moduleName)); const QMetaObject* metaObject = ptr->metaObject(); while (metaObject) { const char* className = metaObject->className(); if (qtmod.getDict().hasKey(className)) { return className; } metaObject = metaObject->superClass(); } return nullptr; } #endif } // -------------------------------------------------------- PythonWrapper::PythonWrapper() { #if defined (HAVE_SHIBOKEN) static bool init; if (!init) { init = true; registerTypes(); } #endif } bool PythonWrapper::toCString(const Py::Object& pyobject, std::string& str) { if (PyUnicode_Check(pyobject.ptr())) { PyObject* unicode = PyUnicode_AsUTF8String(pyobject.ptr()); str = PyBytes_AsString(unicode); Py_DECREF(unicode); return true; } else if (PyBytes_Check(pyobject.ptr())) { str = PyBytes_AsString(pyobject.ptr()); return true; } #if defined (HAVE_SHIBOKEN) && defined(HAVE_PYSIDE) if (Shiboken::String::check(pyobject.ptr())) { const char* s = Shiboken::String::toCString(pyobject.ptr()); if (s) str = s; return true; } #endif return false; } QObject* PythonWrapper::toQObject(const Py::Object& pyobject) { return qt_getCppType(pyobject.ptr()); } QGraphicsItem* PythonWrapper::toQGraphicsItem(PyObject* pyPtr) { return qt_getCppType(pyPtr); } QGraphicsItem* PythonWrapper::toQGraphicsItem(const Py::Object& pyobject) { return toQGraphicsItem(pyobject.ptr()); } QGraphicsObject* PythonWrapper::toQGraphicsObject(PyObject* pyPtr) { return qt_getCppType(pyPtr); } QGraphicsObject* PythonWrapper::toQGraphicsObject(const Py::Object& pyobject) { return toQGraphicsObject(pyobject.ptr()); } Py::Object PythonWrapper::fromQImage(const QImage& img) { #if defined (HAVE_SHIBOKEN) && defined(HAVE_PYSIDE) PyObject* pyobj = Shiboken::Conversions::copyToPython(getPyTypeObjectForTypeName(), const_cast(&img)); if (pyobj) { return Py::asObject(pyobj); } throw Py::RuntimeError("Failed to wrap image"); #else // Access shiboken/PySide via Python return qt_wrapInstance(&img, "QImage", "QtGui"); #endif } QImage *PythonWrapper::toQImage(PyObject *pyobj) { return qt_getCppType(pyobj); } Py::Object PythonWrapper::fromQIcon(const QIcon* icon) { #if defined (HAVE_SHIBOKEN) && defined(HAVE_PYSIDE) const char* typeName = typeid(*const_cast(icon)).name(); PyObject* pyobj = Shiboken::Object::newObject(getPyTypeObjectForTypeName(), const_cast(icon), true, false, typeName); if (pyobj) { return Py::asObject(pyobj); } throw Py::RuntimeError("Failed to wrap icon"); #else // Access shiboken/PySide via Python return qt_wrapInstance(icon, "QIcon", "QtGui"); #endif } QIcon *PythonWrapper::toQIcon(PyObject *pyobj) { return qt_getCppType(pyobj); } Py::Object PythonWrapper::fromQDir(const QDir& dir) { #if defined (HAVE_SHIBOKEN) && defined(HAVE_PYSIDE) const char* typeName = typeid(dir).name(); PyObject* pyobj = Shiboken::Object::newObject(getPyTypeObjectForTypeName(), const_cast(&dir), false, false, typeName); if (pyobj) { return Py::asObject(pyobj); } #else Q_UNUSED(dir) #endif throw Py::RuntimeError("Failed to wrap directory"); } QDir* PythonWrapper::toQDir(PyObject* pyobj) { return qt_getCppType(pyobj); } Py::Object PythonWrapper::fromQPrinter(QPrinter* printer) { if (!printer) { return Py::None(); } #if defined (HAVE_SHIBOKEN) && defined(HAVE_PYSIDE) // Access shiboken/PySide via C++ auto type = getPyTypeObjectForTypeName(); if (!type) { // XXX: Why is QPrinter special? #if defined (HAVE_SHIBOKEN2) type = reinterpret_cast(Shiboken::Conversions::getPythonTypeObject("QPrinter")); #else type = Shiboken::Conversions::getPythonTypeObject("QPrinter"); #endif } if (type) { PyObject* pyobj = Shiboken::Object::newObject(type, printer, false, false, "QPrinter"); return Py::asObject(pyobj); } throw Py::RuntimeError("Failed to wrap printer"); #else // Access shiboken/PySide via Python return qt_wrapInstance(printer, "QPrinter", "QtCore"); #endif } Py::Object PythonWrapper::fromQObject(QObject* object, const char* className) { if (!object) { return Py::None(); } #if defined (HAVE_SHIBOKEN) && defined(HAVE_PYSIDE) // Access shiboken/PySide via C++ auto type = getPyTypeObjectForTypeName(); if (type) { std::string typeName; if (className) { typeName = className; } else { typeName = object->metaObject()->className(); } PyObject* pyobj = Shiboken::Object::newObject(type, object, false, false, typeName.c_str()); WrapperManager::instance().addQObject(object, pyobj); return Py::asObject(pyobj); } throw Py::RuntimeError("Failed to wrap object"); #else // Access shiboken/PySide via Python std::string typeName; if (className) { typeName = className; } else { typeName = object->metaObject()->className(); } return qt_wrapInstance(object, typeName, "QtCore"); #endif } Py::Object PythonWrapper::fromQWidget(QWidget* widget, const char* className) { #if defined (HAVE_SHIBOKEN) && defined(HAVE_PYSIDE) // Access shiboken/PySide via C++ auto type = getPyTypeObjectForTypeName(); if (type) { std::string typeName; if (className) { typeName = className; } else { typeName = widget->metaObject()->className(); } PyObject* pyobj = Shiboken::Object::newObject(type, widget, false, false, typeName.c_str()); WrapperManager::instance().addQObject(widget, pyobj); return Py::asObject(pyobj); } throw Py::RuntimeError("Failed to wrap widget"); #else // Access shiboken/PySide via Python std::string typeName; if (className) { typeName = className; } else { typeName = widget->metaObject()->className(); } return qt_wrapInstance(widget, typeName, "QtWidgets"); #endif } const char* PythonWrapper::getWrapperName(QObject* obj) const { #if defined (HAVE_SHIBOKEN) && defined(HAVE_PYSIDE) const QMetaObject* meta = obj->metaObject(); while (meta) { const char* typeName = meta->className(); PyTypeObject* exactType = Shiboken::Conversions::getPythonTypeObject(typeName); if (exactType) { return typeName; } meta = meta->superClass(); } #else QUiLoader ui; QStringList names = ui.availableWidgets(); const QMetaObject* meta = obj->metaObject(); while (meta) { const char* typeName = meta->className(); if (names.indexOf(QLatin1String(typeName)) >= 0) { return typeName; } meta = meta->superClass(); } #endif return "QObject"; } bool PythonWrapper::loadCoreModule() { return loadPySideModule("QtCore", SbkPySide_QtCoreTypes); } bool PythonWrapper::loadGuiModule() { return loadPySideModule("QtGui", SbkPySide_QtGuiTypes); } bool PythonWrapper::loadWidgetsModule() { return loadPySideModule("QtWidgets", SbkPySide_QtWidgetsTypes); } bool PythonWrapper::loadPrintSupportModule() { return loadPySideModule("QtPrintSupport", SbkPySide_QtPrintSupportTypes); } bool PythonWrapper::loadUiToolsModule() { return loadPySideModule("QtUiTools", SbkPySide_QtUiToolsTypes); } void PythonWrapper::createChildrenNameAttributes(PyObject* root, QObject* object) { Q_FOREACH (QObject* child, object->children()) { const QByteArray name = child->objectName().toLocal8Bit(); if (!name.isEmpty() && !name.startsWith("_") && !name.startsWith("qt_")) { bool hasAttr = PyObject_HasAttrString(root, name.constData()); if (!hasAttr) { #if defined (HAVE_SHIBOKEN) && defined(HAVE_PYSIDE) Shiboken::AutoDecRef pyChild(Shiboken::Conversions::pointerToPython(getPyTypeObjectForTypeName(), child)); PyObject_SetAttrString(root, name.constData(), pyChild); #else const char* className = qt_identifyType(child, "QtWidgets"); if (!className) { if (qobject_cast(child)) { className = "QWidget"; } else { className = "QObject"; } } Py::Object pyChild(qt_wrapInstance(child, className, "QtWidgets")); PyObject_SetAttrString(root, name.constData(), pyChild.ptr()); #endif } createChildrenNameAttributes(root, child); } createChildrenNameAttributes(root, child); } } void PythonWrapper::setParent(PyObject* pyWdg, QObject* parent) { #if defined (HAVE_SHIBOKEN) && defined(HAVE_PYSIDE) if (parent) { Shiboken::AutoDecRef pyParent(Shiboken::Conversions::pointerToPython(getPyTypeObjectForTypeName(), parent)); Shiboken::Object::setParent(pyParent, pyWdg); } #else Q_UNUSED(pyWdg); Q_UNUSED(parent); #endif }