diff --git a/src/Ext/freecad/CMakeLists.txt b/src/Ext/freecad/CMakeLists.txt index baf6df2e52..70aa6b7d9c 100644 --- a/src/Ext/freecad/CMakeLists.txt +++ b/src/Ext/freecad/CMakeLists.txt @@ -3,16 +3,18 @@ EXECUTE_PROCESS(COMMAND ${PYTHON_EXECUTABLE} -c OUTPUT_VARIABLE python_libs OUTPUT_STRIP_TRAILING_WHITESPACE ) SET(PYTHON_MAIN_DIR ${python_libs}) -set(NAMESPACE_INIT "${CMAKE_BINARY_DIR}/Ext/freecad/__init__.py") +set(NAMESPACE_DIR "${CMAKE_BINARY_DIR}/Ext/freecad") +set(NAMESPACE_INIT "${NAMESPACE_DIR}/__init__.py") if (WIN32) - get_filename_component(FREECAD_LIBRARY_INSTALL_DIR "${CMAKE_INSTALL_BINDIR}" + get_filename_component(FREECAD_LIBRARY_INSTALL_DIR "${CMAKE_INSTALL_BINDIR}" REALPATH BASE_DIR "${CMAKE_INSTALL_PREFIX}") - set( ${CMAKE_INSTALL_BINDIR}) + set( ${CMAKE_INSTALL_BINDIR}) else() - set(FREECAD_LIBRARY_INSTALL_DIR ${CMAKE_INSTALL_LIBDIR}) + set(FREECAD_LIBRARY_INSTALL_DIR ${CMAKE_INSTALL_LIBDIR}) endif() configure_file(__init__.py.template ${NAMESPACE_INIT}) +configure_file(UiTools.py ${NAMESPACE_DIR}/UiTools.py) if (INSTALL_TO_SITEPACKAGES) SET(SITE_PACKAGE_DIR ${PYTHON_MAIN_DIR}/freecad) @@ -23,6 +25,7 @@ endif() INSTALL( FILES ${NAMESPACE_INIT} + UiTools.py DESTINATION ${SITE_PACKAGE_DIR} ) diff --git a/src/Ext/freecad/UiTools.py b/src/Ext/freecad/UiTools.py new file mode 100644 index 0000000000..aad94ebb04 --- /dev/null +++ b/src/Ext/freecad/UiTools.py @@ -0,0 +1,21 @@ +# (c) 2021 Werner Mayer LGPL + +from PySide2 import QtUiTools +from PySide2 import QtCore +import FreeCADGui as Gui + + +class QUiLoader(QtUiTools.QUiLoader): + """ + This is an extension of Qt's QUiLoader to also create custom widgets + """ + def __init__(self, arg = None): + super(QUiLoader, self).__init__(arg) + self.ui = Gui.PySideUic + + def createWidget(self, className, parent = None, name = ""): + widget = self.ui.createCustomWidget(className, parent, name) + if not widget: + widget = super(QUiLoader, self).createWidget(className, parent, name) + return widget + diff --git a/src/Gui/UiLoader.cpp b/src/Gui/UiLoader.cpp index b90f3c59db..d41f6058db 100644 --- a/src/Gui/UiLoader.cpp +++ b/src/Gui/UiLoader.cpp @@ -23,6 +23,10 @@ #include "PreCompiled.h" #ifndef _PreComp_ +# include +# include +# include +# include # include # include #endif @@ -31,9 +35,62 @@ #include "PythonWrapper.h" #include "WidgetFactory.h" #include +#include using namespace Gui; +namespace { + +QWidget* createFromWidgetFactory(const QString & className, QWidget * parent, const QString& name) +{ + QWidget* widget = nullptr; + if (WidgetFactory().CanProduce((const char*)className.toLatin1())) + widget = WidgetFactory().createWidget((const char*)className.toLatin1(), parent); + if (widget) + widget->setObjectName(name); + return widget; +} + +Py::Object wrapFromWidgetFactory(const Py::Tuple& args, const std::function & callableFunc) +{ + Gui::PythonWrapper wrap; + + // 1st argument + Py::String str(args[0]); + std::string className; + className = str.as_std_string("utf-8"); + // 2nd argument + QWidget* parent = 0; + if (wrap.loadCoreModule() && args.size() > 1) { + QObject* object = wrap.toQObject(args[1]); + if (object) + parent = qobject_cast(object); + } + + // 3rd argument + std::string objectName; + if (args.size() > 2) { + Py::String str(args[2]); + objectName = str.as_std_string("utf-8"); + } + + QWidget* widget = callableFunc(QString::fromLatin1(className.c_str()), parent, + QString::fromLatin1(objectName.c_str())); + if (!widget) { + return Py::None(); + // std::string err = "No such widget class '"; + // err += className; + // err += "'"; + // throw Py::RuntimeError(err); + } + wrap.loadGuiModule(); + wrap.loadWidgetsModule(); + + const char* typeName = wrap.getWrapperName(widget); + return wrap.fromQWidget(widget, typeName); +} + +} PySideUicModule::PySideUicModule() : Py::ExtensionModule("PySideUic") @@ -43,6 +100,8 @@ PySideUicModule::PySideUicModule() "and then execute it in a special frame to retrieve the form_class."); add_varargs_method("loadUi",&PySideUicModule::loadUi, "Addition of \"loadUi\" to PySide."); + add_varargs_method("createCustomWidget",&PySideUicModule::createCustomWidget, + "Create custom widgets."); initialize("PySideUic helper module"); // register with Python } @@ -160,6 +219,295 @@ Py::Object PySideUicModule::loadUi(const Py::Tuple& args) return Py::None(); } +Py::Object PySideUicModule::createCustomWidget(const Py::Tuple& args) +{ + return wrapFromWidgetFactory(args, &createFromWidgetFactory); +} + +// ---------------------------------------------------- + +#if !defined (HAVE_QT_UI_TOOLS) +namespace Gui { +QUiLoader::QUiLoader(QObject* parent) +{ + Base::PyGILStateLocker lock; + PythonWrapper wrap; + wrap.loadUiToolsModule(); + //PyObject* module = PyImport_ImportModule("PySide2.QtUiTools"); + PyObject* module = PyImport_ImportModule("freecad.UiTools"); + if (module) { + Py::Tuple args(1); + args[0] = wrap.fromQObject(parent); + Py::Module mod(module, true); + uiloader = mod.callMemberFunction("QUiLoader", args); + } +} + +QUiLoader::~QUiLoader() +{ + Base::PyGILStateLocker lock; + uiloader = Py::None(); +} + +QStringList QUiLoader::pluginPaths() const +{ + Base::PyGILStateLocker lock; + try { + Py::List list(uiloader.callMemberFunction("pluginPaths")); + QStringList paths; + for (const auto& it : list) { + paths << QString::fromStdString(Py::String(it).as_std_string()); + } + return paths; + } + catch (Py::Exception& e) { + e.clear(); + return QStringList(); + } +} + +void QUiLoader::clearPluginPaths() +{ + Base::PyGILStateLocker lock; + try { + uiloader.callMemberFunction("clearPluginPaths"); + } + catch (Py::Exception& e) { + e.clear(); + } +} + +void QUiLoader::addPluginPath(const QString& path) +{ + Base::PyGILStateLocker lock; + try { + Py::Tuple args(1); + args[0] = Py::String(path.toStdString()); + uiloader.callMemberFunction("addPluginPath", args); + } + catch (Py::Exception& e) { + e.clear(); + } +} + +QWidget* QUiLoader::load(QIODevice* device, QWidget* parentWidget) +{ + Base::PyGILStateLocker lock; + try { + PythonWrapper wrap; + Py::Tuple args(2); + args[0] = wrap.fromQObject(device); + args[1] = wrap.fromQObject(parentWidget); + Py::Object form(uiloader.callMemberFunction("load", args)); + return qobject_cast(wrap.toQObject(form)); + } + catch (Py::Exception& e) { + e.clear(); + return nullptr; + } +} + +QStringList QUiLoader::availableWidgets() const +{ + Base::PyGILStateLocker lock; + try { + Py::List list(uiloader.callMemberFunction("availableWidgets")); + QStringList widgets; + for (const auto& it : list) { + widgets << QString::fromStdString(Py::String(it).as_std_string()); + } + return widgets; + } + catch (Py::Exception& e) { + e.clear(); + return QStringList(); + } +} + +QStringList QUiLoader::availableLayouts() const +{ + Base::PyGILStateLocker lock; + try { + Py::List list(uiloader.callMemberFunction("availableLayouts")); + QStringList layouts; + for (const auto& it : list) { + layouts << QString::fromStdString(Py::String(it).as_std_string()); + } + return layouts; + } + catch (Py::Exception& e) { + e.clear(); + return QStringList(); + } +} + +QWidget* QUiLoader::createWidget(const QString& className, QWidget* parent, const QString& name) +{ + Base::PyGILStateLocker lock; + try { + PythonWrapper wrap; + Py::Tuple args(3); + args[0] = Py::String(className.toStdString()); + args[1] = wrap.fromQObject(parent); + args[2] = Py::String(name.toStdString()); + Py::Object form(uiloader.callMemberFunction("createWidget", args)); + return qobject_cast(wrap.toQObject(form)); + } + catch (Py::Exception& e) { + e.clear(); + return nullptr; + } +} + +QLayout* QUiLoader::createLayout(const QString& className, QObject* parent, const QString& name) +{ + Base::PyGILStateLocker lock; + try { + PythonWrapper wrap; + Py::Tuple args(3); + args[0] = Py::String(className.toStdString()); + args[1] = wrap.fromQObject(parent); + args[2] = Py::String(name.toStdString()); + Py::Object form(uiloader.callMemberFunction("createLayout", args)); + return qobject_cast(wrap.toQObject(form)); + } + catch (Py::Exception& e) { + e.clear(); + return nullptr; + } +} + +QActionGroup* QUiLoader::createActionGroup(QObject* parent, const QString& name) +{ + Base::PyGILStateLocker lock; + try { + PythonWrapper wrap; + Py::Tuple args(2); + args[0] = wrap.fromQObject(parent); + args[1] = Py::String(name.toStdString()); + Py::Object action(uiloader.callMemberFunction("createActionGroup", args)); + return qobject_cast(wrap.toQObject(action)); + } + catch (Py::Exception& e) { + e.clear(); + return nullptr; + } +} + +QAction* QUiLoader::createAction(QObject* parent, const QString& name) +{ + Base::PyGILStateLocker lock; + try { + PythonWrapper wrap; + Py::Tuple args(2); + args[0] = wrap.fromQObject(parent); + args[1] = Py::String(name.toStdString()); + Py::Object action(uiloader.callMemberFunction("createAction", args)); + return qobject_cast(wrap.toQObject(action)); + } + catch (Py::Exception& e) { + e.clear(); + return nullptr; + } +} + +void QUiLoader::setWorkingDirectory(const QDir& dir) +{ + Base::PyGILStateLocker lock; + try { + PythonWrapper wrap; + Py::Tuple args(1); + args[0] = wrap.fromQDir(dir); + uiloader.callMemberFunction("setWorkingDirectory", args); + } + catch (Py::Exception& e) { + e.clear(); + } +} + +QDir QUiLoader::workingDirectory() const +{ + Base::PyGILStateLocker lock; + try { + PythonWrapper wrap; + Py::Object dir((uiloader.callMemberFunction("workingDirectory"))); + QDir* d = wrap.toQDir(dir.ptr()); + if (d) return *d; + return QDir::current(); + } + catch (Py::Exception& e) { + e.clear(); + return QDir::current(); + } +} + +void QUiLoader::setLanguageChangeEnabled(bool enabled) +{ + Base::PyGILStateLocker lock; + try { + Py::Tuple args(1); + args[0] = Py::Boolean(enabled); + uiloader.callMemberFunction("setLanguageChangeEnabled", args); + } + catch (Py::Exception& e) { + e.clear(); + } +} + +bool QUiLoader::isLanguageChangeEnabled() const +{ + Base::PyGILStateLocker lock; + try { + Py::Boolean ok((uiloader.callMemberFunction("isLanguageChangeEnabled"))); + return static_cast(ok); + } + catch (Py::Exception& e) { + e.clear(); + return false; + } +} + +void QUiLoader::setTranslationEnabled(bool enabled) +{ + Base::PyGILStateLocker lock; + try { + Py::Tuple args(1); + args[0] = Py::Boolean(enabled); + uiloader.callMemberFunction("setTranslationEnabled", args); + } + catch (Py::Exception& e) { + e.clear(); + } +} + +bool QUiLoader::isTranslationEnabled() const +{ + Base::PyGILStateLocker lock; + try { + Py::Boolean ok((uiloader.callMemberFunction("isTranslationEnabled"))); + return static_cast(ok); + } + catch (Py::Exception& e) { + e.clear(); + return false; + } +} + +QString QUiLoader::errorString() const +{ + Base::PyGILStateLocker lock; + try { + Py::String error((uiloader.callMemberFunction("errorString"))); + return QString::fromStdString(error.as_std_string()); + } + catch (Py::Exception& e) { + e.clear(); + return QString(); + } +} +} +#endif + // ---------------------------------------------------- UiLoader::UiLoader(QObject* parent) @@ -180,11 +528,8 @@ QWidget* UiLoader::createWidget(const QString & className, QWidget * parent, { if (this->cw.contains(className)) return QUiLoader::createWidget(className, parent, name); - QWidget* w = 0; - if (WidgetFactory().CanProduce((const char*)className.toLatin1())) - w = WidgetFactory().createWidget((const char*)className.toLatin1(), parent); - if (w) w->setObjectName(name); - return w; + + return createFromWidgetFactory(className, parent, name); } // ---------------------------------------------------- @@ -279,38 +624,10 @@ Py::Object UiLoaderPy::load(const Py::Tuple& args) Py::Object UiLoaderPy::createWidget(const Py::Tuple& args) { - Gui::PythonWrapper wrap; - - // 1st argument - Py::String str(args[0]); - std::string className; - className = str.as_std_string("utf-8"); - // 2nd argument - QWidget* parent = 0; - if (wrap.loadCoreModule() && args.size() > 1) { - QObject* object = wrap.toQObject(args[1]); - if (object) - parent = qobject_cast(object); - } - - // 3rd argument - std::string objectName; - if (args.size() > 2) { - Py::String str(args[2]); - objectName = str.as_std_string("utf-8"); - } - - QWidget* widget = loader.createWidget(QString::fromLatin1(className.c_str()), parent, - QString::fromLatin1(objectName.c_str())); - if (!widget) { - std::string err = "No such widget class '"; - err += className; - err += "'"; - throw Py::RuntimeError(err); - } - wrap.loadGuiModule(); - wrap.loadWidgetsModule(); - - const char* typeName = wrap.getWrapperName(widget); - return wrap.fromQWidget(widget, typeName); + return wrapFromWidgetFactory(args, std::bind(&UiLoader::createWidget, &loader, + std::placeholders::_1, + std::placeholders::_2, + std::placeholders::_3)); } + +#include "moc_UiLoader.cpp" diff --git a/src/Gui/UiLoader.h b/src/Gui/UiLoader.h index e4567c0191..3ba1f6b85d 100644 --- a/src/Gui/UiLoader.h +++ b/src/Gui/UiLoader.h @@ -24,9 +24,26 @@ #ifndef GUI_UILOADER_H #define GUI_UILOADER_H +#if !defined (__MINGW32__) +#define HAVE_QT_UI_TOOLS +#endif + +#if defined (HAVE_QT_UI_TOOLS) #include +#else +#include +#endif #include +QT_BEGIN_NAMESPACE +class QLayout; +class QAction; +class QActionGroup; +class QDir; +class QIODevice; +class QWidget; +QT_END_NAMESPACE + namespace Gui { @@ -40,8 +57,46 @@ public: private: Py::Object loadUiType(const Py::Tuple& args); Py::Object loadUi(const Py::Tuple& args); + Py::Object createCustomWidget(const Py::Tuple&); }; +#if !defined (HAVE_QT_UI_TOOLS) +class QUiLoader : public QObject +{ + Q_OBJECT +public: + explicit QUiLoader(QObject* parent = nullptr); + ~QUiLoader(); + + QStringList pluginPaths() const; + void clearPluginPaths(); + void addPluginPath(const QString& path); + + QWidget* load(QIODevice* device, QWidget* parentWidget = nullptr); + QStringList availableWidgets() const; + QStringList availableLayouts() const; + + virtual QWidget* createWidget(const QString& className, QWidget* parent = nullptr, const QString& name = QString()); + virtual QLayout* createLayout(const QString& className, QObject* parent = nullptr, const QString& name = QString()); + virtual QActionGroup* createActionGroup(QObject* parent = nullptr, const QString& name = QString()); + virtual QAction* createAction(QObject* parent = nullptr, const QString& name = QString()); + + void setWorkingDirectory(const QDir& dir); + QDir workingDirectory() const; + + void setLanguageChangeEnabled(bool enabled); + bool isLanguageChangeEnabled() const; + + void setTranslationEnabled(bool enabled); + bool isTranslationEnabled() const; + + QString errorString() const; + +private: + Py::Object uiloader; +}; +#endif + /** * The UiLoader class provides the abitlity to use the widget factory * framework of FreeCAD within the framework provided by Qt. This class @@ -60,6 +115,7 @@ public: */ QWidget* createWidget(const QString & className, QWidget * parent=0, const QString& name = QString()); + private: QStringList cw; };