From 7b79012ae19015f2fc5147b1133257db45de158d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20Tr=C3=B6ger?= Date: Wed, 12 Feb 2020 12:16:12 +0100 Subject: [PATCH] Implement and test extension events --- src/App/Application.h | 13 +++++++++++ src/App/DocumentObserverPython.cpp | 33 ++++++++++++++++++++++++++++ src/App/DocumentObserverPython.h | 9 ++++++++ src/App/ExtensionContainerPyImp.cpp | 8 +++++-- src/Mod/Test/Document.py | 34 +++++++++++++++++++++++++++++ 5 files changed, 95 insertions(+), 2 deletions(-) diff --git a/src/App/Application.h b/src/App/Application.h index 1605a48ce4..642cfce019 100644 --- a/src/App/Application.h +++ b/src/App/Application.h @@ -48,6 +48,7 @@ class DocumentObject; class ApplicationObserver; class Property; class AutoTransaction; +class ExtensionContainer; enum GetLinkOption { /// Get all links (both directly and in directly) linked to the given object @@ -261,6 +262,18 @@ public: /// signal on about changing the editor mode of a property boost::signals2::signal signalChangePropertyEditor; //@} + + /** @name Signals of extension changes + * These signals are emitted on dynamic extension addition. Dynamic extensions are the ones added by python (c++ ones are part + * of the class definition, hence not dynamic) + * The extension in question is provided as parameter. + */ + //@{ + /// signal before adding the extension + boost::signals2::signal signalBeforeAddingDynamicExtension; + /// signal after the extension was added + boost::signals2::signal signalAddedDynamicExtension; + //@} /** @name methods for parameter handling */ diff --git a/src/App/DocumentObserverPython.cpp b/src/App/DocumentObserverPython.cpp index edaeef5eb0..40b74d447f 100644 --- a/src/App/DocumentObserverPython.cpp +++ b/src/App/DocumentObserverPython.cpp @@ -111,6 +111,8 @@ DocumentObserverPython::DocumentObserverPython(const Py::Object& obj) : inst(obj FC_PY_ELEMENT_ARG1(AppendDynamicProperty, AppendDynamicProperty) FC_PY_ELEMENT_ARG1(RemoveDynamicProperty, RemoveDynamicProperty) FC_PY_ELEMENT_ARG2(ChangePropertyEditor, ChangePropertyEditor) + FC_PY_ELEMENT_ARG2(BeforeAddingDynamicExtension, BeforeAddingDynamicExtension) + FC_PY_ELEMENT_ARG2(AddedDynamicExtension, AddedDynamicExtension) } DocumentObserverPython::~DocumentObserverPython() @@ -541,3 +543,34 @@ void DocumentObserverPython::slotFinishSaveDocument(const App::Document& doc, co e.ReportException(); } } + +void DocumentObserverPython::slotBeforeAddingDynamicExtension(const App::ExtensionContainer& extcont, std::string extension) +{ + Base::PyGILStateLocker lock; + try { + Py::Tuple args(2); + args.setItem(0, Py::Object(const_cast(extcont).getPyObject())); + args.setItem(1, Py::String(extension)); + Base::pyCall(pyBeforeAddingDynamicExtension.ptr(),args.ptr()); + } + catch (Py::Exception&) { + Base::PyException e; // extract the Python error text + e.ReportException(); + } +} + +void DocumentObserverPython::slotAddedDynamicExtension(const App::ExtensionContainer& extcont, std::string extension) +{ + Base::PyGILStateLocker lock; + try { + Py::Tuple args(2); + args.setItem(0, Py::Object(const_cast(extcont).getPyObject())); + args.setItem(1, Py::String(extension)); + Base::pyCall(pyAddedDynamicExtension.ptr(),args.ptr()); + } + catch (Py::Exception&) { + Base::PyException e; // extract the Python error text + e.ReportException(); + } +} + diff --git a/src/App/DocumentObserverPython.h b/src/App/DocumentObserverPython.h index b1d0411d9d..b6e36b92cb 100644 --- a/src/App/DocumentObserverPython.h +++ b/src/App/DocumentObserverPython.h @@ -28,6 +28,8 @@ #include #include +#include + namespace App { @@ -105,6 +107,11 @@ private: void slotStartSaveDocument(const App::Document&, const std::string&); /** Called when an document has been saved*/ void slotFinishSaveDocument(const App::Document&, const std::string&); + /** Called before an object gets a new extension added*/ + void slotBeforeAddingDynamicExtension(const App::ExtensionContainer&, std::string extension); + /** Called when an object gets a dynamic extension added*/ + void slotAddedDynamicExtension(const App::ExtensionContainer&, std::string extension); + private: Py::Object inst; @@ -145,6 +152,8 @@ private: Connection pyAppendDynamicProperty; Connection pyRemoveDynamicProperty; Connection pyChangePropertyEditor; + Connection pyBeforeAddingDynamicExtension; + Connection pyAddedDynamicExtension; }; } //namespace App diff --git a/src/App/ExtensionContainerPyImp.cpp b/src/App/ExtensionContainerPyImp.cpp index 36750d223d..4c3dfe8581 100644 --- a/src/App/ExtensionContainerPyImp.cpp +++ b/src/App/ExtensionContainerPyImp.cpp @@ -207,7 +207,7 @@ PyObject* ExtensionContainerPy::addExtension(PyObject *args) { str << "No extension found of type '" << typeId << "'" << std::ends; throw Py::Exception(Base::BaseExceptionFreeCADError,str.str()); } - + //register the extension App::Extension* ext = static_cast(extension.createInstance()); //check if this really is a python extension! @@ -217,7 +217,8 @@ PyObject* ExtensionContainerPy::addExtension(PyObject *args) { str << "Extension is not a python addable version: '" << typeId << "'" << std::ends; throw Py::Exception(Base::BaseExceptionFreeCADError,str.str()); } - + + GetApplication().signalBeforeAddingDynamicExtension(*getExtensionContainerPtr(), typeId); ext->initExtension(getExtensionContainerPtr()); //set the proxy to allow python overrides @@ -260,6 +261,9 @@ PyObject* ExtensionContainerPy::addExtension(PyObject *args) { } Py_DECREF(obj); + + //throw the appropriate event + GetApplication().signalAddedDynamicExtension(*getExtensionContainerPtr(), typeId); Py_Return; } diff --git a/src/Mod/Test/Document.py b/src/Mod/Test/Document.py index 2b381ea673..de64e6ac8d 100644 --- a/src/Mod/Test/Document.py +++ b/src/Mod/Test/Document.py @@ -1504,6 +1504,16 @@ class DocumentObserverCases(unittest.TestCase): self.signal.append('DocFinishSave') self.parameter.append(obj) self.parameter2.append(name) + + def slotBeforeAddingDynamicExtension(self, obj, extension): + self.signal.append('ObjBeforeDynExt') + self.parameter.append(obj) + self.parameter2.append(extension) + + def slotAddedDynamicExtension(self, obj, extension): + self.signal.append('ObjDynExt') + self.parameter.append(obj) + self.parameter2.append(extension) class GuiObserver(): @@ -1777,6 +1787,18 @@ class DocumentObserverCases(unittest.TestCase): self.failUnless(self.Obs.parameter2.pop() == 'Prop') self.failUnless(not self.Obs.signal and not self.Obs.parameter and not self.Obs.parameter2) + pyobj.addExtension("App::GroupExtensionPython", None) + self.failUnless(self.Obs.signal.pop() == 'ObjDynExt') + self.failUnless(self.Obs.parameter.pop() is pyobj) + self.failUnless(self.Obs.parameter2.pop() == 'App::GroupExtensionPython') + self.failUnless(self.Obs.signal.pop(0) == 'ObjBeforeDynExt') + self.failUnless(self.Obs.parameter.pop(0) is pyobj) + self.failUnless(self.Obs.parameter2.pop(0) == 'App::GroupExtensionPython') + #a proxy property was changed, hence those events are also in the signal list + self.Obs.signal = [] + self.Obs.parameter = [] + self.Obs.parameter2 = [] + FreeCAD.closeDocument(self.Doc1.Name) self.Obs.signal = [] self.Obs.parameter = [] @@ -1907,6 +1929,18 @@ class DocumentObserverCases(unittest.TestCase): self.failUnless(self.GuiObs.parameter.pop(0) is obj.ViewObject) self.failUnless(not self.GuiObs.signal and not self.GuiObs.parameter and not self.GuiObs.parameter2) + obj.ViewObject.addExtension("Gui::ViewProviderGroupExtensionPython", None) + self.failUnless(self.Obs.signal.pop() == 'ObjDynExt') + self.failUnless(self.Obs.parameter.pop() is obj.ViewObject) + self.failUnless(self.Obs.parameter2.pop() == 'Gui::ViewProviderGroupExtensionPython') + self.failUnless(self.Obs.signal.pop() == 'ObjBeforeDynExt') + self.failUnless(self.Obs.parameter.pop() is obj.ViewObject) + self.failUnless(self.Obs.parameter2.pop() == 'Gui::ViewProviderGroupExtensionPython') + #a proxy property was changed, hence those events are also in the signal list (but of GUI observer) + self.GuiObs.signal = [] + self.GuiObs.parameter = [] + self.GuiObs.parameter2 = [] + vo = obj.ViewObject FreeCAD.ActiveDocument.removeObject(obj.Name) self.failUnless(self.Obs.signal.pop(0) == 'ObjDeleted')