From a986dff71a5bde2a7abaf47bcad1c715a081e89b Mon Sep 17 00:00:00 2001 From: wmayer Date: Thu, 22 Dec 2022 16:20:37 +0100 Subject: [PATCH] Gui: support of MDI views written in Python that implements onMsg() or onHasMsg(): fixes #8071 --- src/Gui/CMakeLists.txt | 2 + src/Gui/MDIViewPyWrap.cpp | 331 ++++++++++++++++++++++++++++++++++++++ src/Gui/MDIViewPyWrap.h | 86 ++++++++++ src/Gui/MainWindowPy.cpp | 43 ++++- src/Gui/MainWindowPy.h | 2 + 5 files changed, 463 insertions(+), 1 deletion(-) create mode 100644 src/Gui/MDIViewPyWrap.cpp create mode 100644 src/Gui/MDIViewPyWrap.h diff --git a/src/Gui/CMakeLists.txt b/src/Gui/CMakeLists.txt index 9eb6dc1ad8..f4e7d2ed4f 100644 --- a/src/Gui/CMakeLists.txt +++ b/src/Gui/CMakeLists.txt @@ -1060,12 +1060,14 @@ SOURCE_GROUP("Params" FILES ${Params_SRCS}) SET(View_CPP_SRCS MDIView.cpp MDIViewPy.cpp + MDIViewPyWrap.cpp GraphvizView.cpp ActiveObjectList.cpp ) SET(View_HPP_SRCS MDIView.h MDIViewPy.h + MDIViewPyWrap.h GraphvizView.h ActiveObjectList.h ) diff --git a/src/Gui/MDIViewPyWrap.cpp b/src/Gui/MDIViewPyWrap.cpp new file mode 100644 index 0000000000..762ea5ff82 --- /dev/null +++ b/src/Gui/MDIViewPyWrap.cpp @@ -0,0 +1,331 @@ +/*************************************************************************** + * Copyright (c) 2022 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 +#endif + +#include +#include + +#include "MDIViewPyWrap.h" +#include "PythonWrapper.h" + + +using namespace Gui; +namespace bp = boost::placeholders; + +namespace Gui { + +class MDIViewPyWrapImp +{ +public: + MDIViewPyWrapImp(Py::Object pyobject) + : pyobject{pyobject} + { + Base::PyGILStateLocker lock; + std::vector methods = {"onMsg", "onHasMsg", "canClose", "printDocument", "print", "printPdf", "printPreview", "redoActions", "undoActions"}; + + for (const auto& it : methods) { + if (pyobject.hasAttr(it)) { + func[it] = pyobject.getAttr(it); + } + } + } + + ~MDIViewPyWrapImp() + { + Base::PyGILStateLocker lock; + pyobject = Py::None(); + func.clear(); + } + + bool onMsg(const char* pMsg) + { + Base::PyGILStateLocker lock; + Py::Callable target(func.at("onMsg")); + Py::Boolean result(target.apply(Py::TupleN(Py::String(pMsg)))); + return static_cast(result); + } + + bool onHasMsg(const char* pMsg) + { + Base::PyGILStateLocker lock; + Py::Callable target(func.at("onHasMsg")); + Py::Boolean result(target.apply(Py::TupleN(Py::String(pMsg)))); + return static_cast(result); + } + + bool canClose() + { + Base::PyGILStateLocker lock; + Py::Callable target(func.at("canClose")); + Py::Boolean result(target.apply(Py::Tuple())); + return static_cast(result); + } + + void printDocument(QPrinter* printer) + { + Base::PyGILStateLocker lock; + PythonWrapper wrap; + wrap.loadPrintSupportModule(); + Py::Object pyprint = wrap.fromQPrinter(printer); + Py::Callable target(func.at("printDocument")); + target.apply(Py::TupleN(pyprint)); + } + + void print() + { + Base::PyGILStateLocker lock; + Py::Callable target(func.at("print")); + target.apply(Py::Tuple()); + } + + void printPdf() + { + Base::PyGILStateLocker lock; + Py::Callable target(func.at("printPdf")); + target.apply(Py::Tuple()); + } + + void printPreview() + { + Base::PyGILStateLocker lock; + Py::Callable target(func.at("printPreview")); + target.apply(Py::Tuple()); + } + + QStringList undoActions() + { + Base::PyGILStateLocker lock; + Py::Callable target(func.at("undoActions")); + Py::List list(target.apply(Py::Tuple())); + QStringList actions; + for (auto it : list) { + Py::String str(it); + actions << QString::fromStdString(str); + } + return actions; + } + + QStringList redoActions() + { + Base::PyGILStateLocker lock; + Py::Callable target(func.at("redoActions")); + Py::List list(target.apply(Py::Tuple())); + QStringList actions; + for (auto it : list) { + Py::String str(it); + actions << QString::fromStdString(str); + } + return actions; + } + +private: + std::unordered_map func; + Py::Object pyobject; +}; + +} + + +TYPESYSTEM_SOURCE_ABSTRACT(Gui::MDIViewPyWrap,Gui::MDIView) + +MDIViewPyWrap::MDIViewPyWrap(Py::Object py, Gui::Document* pcDocument,QWidget* parent, Qt::WindowFlags wflags) + : MDIView(pcDocument, parent, wflags) + , ptr(std::make_unique(py)) +{ + Base::PyGILStateLocker lock; + try { + PythonWrapper wrap; + wrap.loadWidgetsModule(); + Py::Object pywidget = py.callMemberFunction("widget"); + QWidget* widget = qobject_cast(wrap.toQObject(pywidget)); + if (widget) { + setCentralWidget(widget); + } + } + catch (Py::Exception&) { + Base::PyException e; + e.ReportException(); + } +} + +MDIViewPyWrap::~MDIViewPyWrap() +{ + ptr.reset(nullptr); +} + +PyObject* MDIViewPyWrap::getPyObject() +{ + return MDIView::getPyObject(); +} + +bool MDIViewPyWrap::onMsg(const char* pMsg,const char** ppReturn) +{ + try { + if (ptr->onMsg(pMsg)) { + return true; + } + return MDIView::onMsg(pMsg, ppReturn); + } + catch (const std::exception&) { + return MDIView::onMsg(pMsg, ppReturn); + } + catch (Py::Exception&) { + Base::PyGILStateLocker lock; + Base::PyException e; + e.ReportException(); + return false; + } +} + +bool MDIViewPyWrap::onHasMsg(const char* pMsg) const +{ + try { + if (ptr->onHasMsg(pMsg)) { + return true; + } + return MDIView::onHasMsg(pMsg); + } + catch (const std::exception&) { + return MDIView::onHasMsg(pMsg); + } + catch (Py::Exception&) { + Base::PyGILStateLocker lock; + Base::PyException e; + e.ReportException(); + return false; + } +} + +bool MDIViewPyWrap::canClose() +{ + try { + return ptr->canClose(); + } + catch (const std::exception&) { + return MDIView::canClose(); + } + catch (Py::Exception&) { + Base::PyGILStateLocker lock; + Base::PyException e; + e.ReportException(); + return false; + } +} + +void MDIViewPyWrap::print(QPrinter* printer) +{ + try { + return ptr->printDocument(printer); + } + catch (const std::exception&) { + return MDIView::print(printer); + } + catch (Py::Exception&) { + Base::PyGILStateLocker lock; + Base::PyException e; + e.ReportException(); + } +} + +void MDIViewPyWrap::print() +{ + try { + return ptr->print(); + } + catch (const std::exception&) { + return MDIView::print(); + } + catch (Py::Exception&) { + Base::PyGILStateLocker lock; + Base::PyException e; + e.ReportException(); + } +} + +void MDIViewPyWrap::printPdf() +{ + try { + return ptr->printPdf(); + } + catch (const std::exception&) { + return MDIView::printPdf(); + } + catch (Py::Exception&) { + Base::PyGILStateLocker lock; + Base::PyException e; + e.ReportException(); + } +} + +void MDIViewPyWrap::printPreview() +{ + try { + return ptr->printPreview(); + } + catch (const std::exception&) { + return MDIView::printPreview(); + } + catch (Py::Exception&) { + Base::PyGILStateLocker lock; + Base::PyException e; + e.ReportException(); + } +} + +QStringList MDIViewPyWrap::undoActions() const +{ + try { + return ptr->undoActions(); + } + catch (const std::exception&) { + return MDIView::undoActions(); + } + catch (Py::Exception&) { + Base::PyGILStateLocker lock; + Base::PyException e; + e.ReportException(); + return MDIView::undoActions(); + } +} + +QStringList MDIViewPyWrap::redoActions() const +{ + try { + return ptr->redoActions(); + } + catch (const std::exception&) { + return MDIView::redoActions(); + } + catch (Py::Exception&) { + Base::PyGILStateLocker lock; + Base::PyException e; + e.ReportException(); + return MDIView::redoActions(); + } +} + +#include "moc_MDIViewPyWrap.cpp" diff --git a/src/Gui/MDIViewPyWrap.h b/src/Gui/MDIViewPyWrap.h new file mode 100644 index 0000000000..09ac3354a2 --- /dev/null +++ b/src/Gui/MDIViewPyWrap.h @@ -0,0 +1,86 @@ +/*************************************************************************** + * Copyright (c) 2022 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 * + * * + ***************************************************************************/ + +#ifndef GUI_MDIVIEWPYWRAP_H +#define GUI_MDIVIEWPYWRAP_H + +#include +#include +#include + + +namespace Gui +{ + +class MDIViewPyWrapImp; +class GuiExport MDIViewPyWrap : public MDIView +{ + Q_OBJECT + + TYPESYSTEM_HEADER_WITH_OVERRIDE(); + +public: + /** View constructor + * Attach the view to the given document. If the document is zero + * the view will attach to the active document. Be aware, there isn't + * always an active document. + */ + MDIViewPyWrap(Py::Object py, Gui::Document* pcDocument, QWidget* parent=nullptr, Qt::WindowFlags wflags=Qt::WindowFlags()); + /** View destructor + * Detach the view from the document, if attached. + */ + ~MDIViewPyWrap() override; + + /// Message handler + bool onMsg(const char* pMsg,const char** ppReturn) override; + /// Message handler test + bool onHasMsg(const char* pMsg) const override; + /// overwrite when checking on close state + bool canClose() override; + PyObject *getPyObject() override; + /** @name Printing */ + //@{ +public Q_SLOTS: + void print(QPrinter* printer) override; + +public: + /** Print content of view */ + void print() override; + /** Print to PDF file */ + void printPdf() override; + /** Show a preview dialog */ + void printPreview() override; + //@} + + /** @name Undo/Redo actions */ + //@{ + QStringList undoActions() const override; + QStringList redoActions() const override; + //@} + +private: + std::unique_ptr ptr; +}; + +} // namespace Gui + +#endif // GUI_MDIVIEWPYWRAP_H diff --git a/src/Gui/MainWindowPy.cpp b/src/Gui/MainWindowPy.cpp index 7c0c7c2fba..001168a9ed 100644 --- a/src/Gui/MainWindowPy.cpp +++ b/src/Gui/MainWindowPy.cpp @@ -28,10 +28,12 @@ #include +#include "DocumentPy.h" #include "MainWindowPy.h" #include "MainWindow.h" #include "MDIView.h" #include "MDIViewPy.h" +#include "MDIViewPyWrap.h" #include "PythonWrapper.h" @@ -52,6 +54,8 @@ void MainWindowPy::init_type() add_varargs_method("getWindowsOfType",&MainWindowPy::getWindowsOfType,"getWindowsOfType(typeid)"); add_varargs_method("setActiveWindow", &MainWindowPy::setActiveWindow, "setActiveWindow(MDIView)"); add_varargs_method("getActiveWindow", &MainWindowPy::getActiveWindow, "getActiveWindow()"); + add_varargs_method("addWindow", &MainWindowPy::addWindow, "addWindow(MDIView)"); + add_varargs_method("removeWindow", &MainWindowPy::removeWindow, "removeWindow(MDIView)"); } PyObject *MainWindowPy::extension_object_new(struct _typeobject * /*type*/, PyObject * /*args*/, PyObject * /*kwds*/) @@ -83,7 +87,7 @@ Py::Object MainWindowPy::createWrapper(MainWindow *mw) } // copy attributes - std::list attr = {"getWindows", "getWindowsOfType", "setActiveWindow", "getActiveWindow"}; + std::list attr = {"getWindows", "getWindowsOfType", "setActiveWindow", "getActiveWindow", "addWindow", "removeWindow"}; Py::Object py = wrap.fromQWidget(mw, "QMainWindow"); Py::ExtensionObject inst(create(mw)); @@ -178,3 +182,40 @@ Py::Object MainWindowPy::getActiveWindow(const Py::Tuple& args) } return Py::None(); } + +Py::Object MainWindowPy::addWindow(const Py::Tuple& args) +{ + PyObject* obj; + if (!PyArg_ParseTuple(args.ptr(), "O", &obj)) + throw Py::Exception(); + + if (_mw) { + Py::Object py(obj); + Gui::Document* document{nullptr}; + // Check if the py object has a reference to a Gui document + if (py.hasAttr("document")) { + Py::Object attr(py.getAttr("document")); + if (PyObject_TypeCheck(attr.ptr(), &DocumentPy::Type)) { + document = static_cast(attr.ptr())->getDocumentPtr(); + } + } + + MDIViewPyWrap* mdi = new MDIViewPyWrap(py, document); + _mw->addWindow(mdi); + return Py::asObject(mdi->getPyObject()); + } + return Py::None(); +} + +Py::Object MainWindowPy::removeWindow(const Py::Tuple& args) +{ + PyObject* obj; + if (!PyArg_ParseTuple(args.ptr(), "O!", MDIViewPy::type_object(), &obj)) + throw Py::Exception(); + + if (_mw) { + MDIViewPy* mdi = static_cast(obj); + _mw->removeWindow(mdi->getMDIViewPtr()); + } + return Py::None(); +} diff --git a/src/Gui/MainWindowPy.h b/src/Gui/MainWindowPy.h index 36eded65ee..398dc1a0a3 100644 --- a/src/Gui/MainWindowPy.h +++ b/src/Gui/MainWindowPy.h @@ -51,6 +51,8 @@ public: Py::Object getWindowsOfType(const Py::Tuple&); Py::Object setActiveWindow(const Py::Tuple&); Py::Object getActiveWindow(const Py::Tuple&); + Py::Object addWindow(const Py::Tuple&); + Py::Object removeWindow(const Py::Tuple&); private: QPointer _mw;