Gui: implement a workaround for MSYS2 to access QUiLoader via its Python interface

This commit is contained in:
wmayer
2021-09-23 09:46:20 +02:00
parent 00759f9c96
commit 0d2451a20a
4 changed files with 440 additions and 43 deletions

View File

@@ -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}
)

View File

@@ -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

View File

@@ -23,6 +23,10 @@
#include "PreCompiled.h"
#ifndef _PreComp_
# include <QAction>
# include <QActionGroup>
# include <QDir>
# include <QLayout>
# include <QFile>
# include <QTextStream>
#endif
@@ -31,9 +35,62 @@
#include "PythonWrapper.h"
#include "WidgetFactory.h"
#include <Base/Interpreter.h>
#include <functional>
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<QWidget*(const QString&, QWidget *, const QString&)> & 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<QWidget*>(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<PySideUicModule>("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<QWidget*>(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<QWidget*>(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<QLayout*>(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<QActionGroup*>(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<QAction*>(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<bool>(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<bool>(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<QWidget*>(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"

View File

@@ -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 <QUiLoader>
#else
#include <QObject>
#endif
#include <CXX/Extensions.hxx>
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;
};