From 9c0b95b569aba9a8d307cde6e6e05585343d6452 Mon Sep 17 00:00:00 2001 From: tritao Date: Wed, 15 Jan 2025 21:07:15 +0000 Subject: [PATCH 1/2] Gui: Split `SelectionObserverPython` call logic into separate handler class. --- src/Gui/Selection/SelectionObserverPython.cpp | 76 +++++++++++-------- src/Gui/Selection/SelectionObserverPython.h | 45 +++++++---- 2 files changed, 73 insertions(+), 48 deletions(-) diff --git a/src/Gui/Selection/SelectionObserverPython.cpp b/src/Gui/Selection/SelectionObserverPython.cpp index e1f670df28..936e074b1f 100644 --- a/src/Gui/Selection/SelectionObserverPython.cpp +++ b/src/Gui/Selection/SelectionObserverPython.cpp @@ -32,37 +32,16 @@ using namespace Gui; std::vector SelectionObserverPython::_instances; -SelectionObserverPython::SelectionObserverPython(const Py::Object& obj, ResolveMode resolve) - : SelectionObserver(true, resolve), inst(obj) +void SelectionObserverPythonHandler::init(const Py::Object& obj) { + this->inst = obj; + #undef FC_PY_ELEMENT #define FC_PY_ELEMENT(_name) FC_PY_GetCallable(obj.ptr(),#_name,py_##_name); FC_PY_SEL_OBSERVER } -SelectionObserverPython::~SelectionObserverPython() = default; - -void SelectionObserverPython::addObserver(const Py::Object& obj, ResolveMode resolve) -{ - _instances.push_back(new SelectionObserverPython(obj, resolve)); -} - -void SelectionObserverPython::removeObserver(const Py::Object& obj) -{ - SelectionObserverPython* obs=nullptr; - for (std::vector::iterator it = - _instances.begin(); it != _instances.end(); ++it) { - if ((*it)->inst == obj) { - obs = *it; - _instances.erase(it); - break; - } - } - - delete obs; -} - -void SelectionObserverPython::onSelectionChanged(const SelectionChanges& msg) +void SelectionObserverPythonHandler::handleSelectionChanged(const SelectionChanges& msg) { switch (msg.Type) { @@ -92,7 +71,7 @@ void SelectionObserverPython::onSelectionChanged(const SelectionChanges& msg) } } -void SelectionObserverPython::pickedListChanged() +void SelectionObserverPythonHandler::pickedListChanged() { if(py_pickedListChanged.isNone()) return; @@ -106,7 +85,7 @@ void SelectionObserverPython::pickedListChanged() } } -void SelectionObserverPython::addSelection(const SelectionChanges& msg) +void SelectionObserverPythonHandler::addSelection(const SelectionChanges& msg) { if(py_addSelection.isNone()) return; @@ -129,7 +108,7 @@ void SelectionObserverPython::addSelection(const SelectionChanges& msg) } } -void SelectionObserverPython::removeSelection(const SelectionChanges& msg) +void SelectionObserverPythonHandler::removeSelection(const SelectionChanges& msg) { if(py_removeSelection.isNone()) return; @@ -147,7 +126,7 @@ void SelectionObserverPython::removeSelection(const SelectionChanges& msg) } } -void SelectionObserverPython::setSelection(const SelectionChanges& msg) +void SelectionObserverPythonHandler::setSelection(const SelectionChanges& msg) { if(py_setSelection.isNone()) return; @@ -163,7 +142,7 @@ void SelectionObserverPython::setSelection(const SelectionChanges& msg) } } -void SelectionObserverPython::clearSelection(const SelectionChanges& msg) +void SelectionObserverPythonHandler::clearSelection(const SelectionChanges& msg) { if(py_clearSelection.isNone()) return; @@ -179,7 +158,7 @@ void SelectionObserverPython::clearSelection(const SelectionChanges& msg) } } -void SelectionObserverPython::setPreselection(const SelectionChanges& msg) +void SelectionObserverPythonHandler::setPreselection(const SelectionChanges& msg) { if(py_setPreselection.isNone()) return; @@ -197,7 +176,7 @@ void SelectionObserverPython::setPreselection(const SelectionChanges& msg) } } -void SelectionObserverPython::removePreselection(const SelectionChanges& msg) +void SelectionObserverPythonHandler::removePreselection(const SelectionChanges& msg) { if(py_removePreselection.isNone()) return; @@ -214,3 +193,36 @@ void SelectionObserverPython::removePreselection(const SelectionChanges& msg) e.ReportException(); } } + + +SelectionObserverPython::SelectionObserverPython(const Py::Object& obj, ResolveMode resolve) + : SelectionObserver(true, resolve) +{ + this->init(obj); +} + +SelectionObserverPython::~SelectionObserverPython() = default; + +void SelectionObserverPython::addObserver(const Py::Object& obj, ResolveMode resolve) +{ + _instances.push_back(new SelectionObserverPython(obj, resolve)); +} + +void SelectionObserverPython::removeObserver(const Py::Object& obj) +{ + SelectionObserverPython* obs=nullptr; + for (auto it =_instances.begin(); it != _instances.end(); ++it) { + if ((*it)->inst == obj) { + obs = *it; + _instances.erase(it); + break; + } + } + + delete obs; +} + +void SelectionObserverPython::onSelectionChanged(const SelectionChanges& msg) +{ + handleSelectionChanged(msg); +} diff --git a/src/Gui/Selection/SelectionObserverPython.h b/src/Gui/Selection/SelectionObserverPython.h index 3f5643487e..c7d258d20e 100644 --- a/src/Gui/Selection/SelectionObserverPython.h +++ b/src/Gui/Selection/SelectionObserverPython.h @@ -30,26 +30,16 @@ namespace Gui { -/** - * The SelectionObserverPython class implements a mechanism to register - * a Python class instance implementing the required interface in order - * to be notified on selection changes. - * - * @author Werner Mayer - */ -class GuiExport SelectionObserverPython : public SelectionObserver +class GuiExport SelectionObserverPythonHandler { public: /// Constructor - explicit SelectionObserverPython(const Py::Object& obj, ResolveMode resolve = ResolveMode::OldStyleElement); - ~SelectionObserverPython() override; + explicit SelectionObserverPythonHandler() = default; + void init(const Py::Object& obj); + void handleSelectionChanged(const SelectionChanges& msg); - static void addObserver(const Py::Object& obj, ResolveMode resolve = ResolveMode::OldStyleElement); - static void removeObserver(const Py::Object& obj); - -private: - void onSelectionChanged(const SelectionChanges& msg) override; +protected: void addSelection(const SelectionChanges&); void removeSelection(const SelectionChanges&); void setSelection(const SelectionChanges&); @@ -58,9 +48,10 @@ private: void removePreselection(const SelectionChanges&); void pickedListChanged(); -private: Py::Object inst; +private: + #define FC_PY_SEL_OBSERVER \ FC_PY_ELEMENT(onSelectionChanged) \ FC_PY_ELEMENT(addSelection) \ @@ -75,6 +66,28 @@ private: #define FC_PY_ELEMENT(_name) Py::Object py_##_name; FC_PY_SEL_OBSERVER +}; + +/** + * The SelectionObserverPython class implements a mechanism to register + * a Python class instance implementing the required interface in order + * to be notified on selection changes. + * + * @author Werner Mayer + */ +class GuiExport SelectionObserverPython : public SelectionObserverPythonHandler, public SelectionObserver +{ + +public: + /// Constructor + explicit SelectionObserverPython(const Py::Object& obj, ResolveMode resolve = ResolveMode::OldStyleElement); + ~SelectionObserverPython() override; + + static void addObserver(const Py::Object& obj, ResolveMode resolve = ResolveMode::OldStyleElement); + static void removeObserver(const Py::Object& obj); + +private: + void onSelectionChanged(const SelectionChanges& msg) override; static std::vector _instances; }; From b1a24deac64af09c78da1a518b96c3f9475f3cc7 Mon Sep 17 00:00:00 2001 From: tritao Date: Wed, 15 Jan 2025 21:05:07 +0000 Subject: [PATCH 2/2] Gui: Provide `ViewProviderFeaturePython` selection callbacks Adds support for Python-based selection callbacks to `ViewProviderFeaturePython` objects. It follows the same conventions as `SelectionObserverPython`, follows an example: ```python def setPreselection(self, document, object, element): print("setPreselection: %s.%s.%s"%(document, object, element)) def removePreselection(self, document, object, element): print("removePreselection: %s.%s.%s"%(document, object, element)) def addSelection(self, document, object, element, position): print("addSelection: %s.%s.%s at %s"%(document, object, element, str(position))) def removeSelection(self,document, object, element): print("removeSelection: %s.%s.%s"%(document, object, element)) def setSelection(self,doc): sel = FreeCADGui.Selection.getSelection(doc) print("setSelection: %s"%sel) def clearSelection(self,doc): print("clearSelection\n") ``` --- src/Gui/Selection/Selection.cpp | 41 ++++++++++++++++++- src/Gui/Selection/SelectionObserverPython.cpp | 19 +++++++-- src/Gui/Selection/SelectionObserverPython.h | 9 ++-- src/Gui/ViewProvider.h | 3 ++ src/Gui/ViewProviderFeaturePython.cpp | 10 +++++ src/Gui/ViewProviderFeaturePython.h | 10 ++++- 6 files changed, 84 insertions(+), 8 deletions(-) diff --git a/src/Gui/Selection/Selection.cpp b/src/Gui/Selection/Selection.cpp index b8fdab737e..b7664a06f5 100644 --- a/src/Gui/Selection/Selection.cpp +++ b/src/Gui/Selection/Selection.cpp @@ -25,6 +25,7 @@ #ifndef _PreComp_ # include +# include # include # include #endif @@ -52,6 +53,7 @@ #include "SelectionFilterPy.h" #include "SelectionObserverPython.h" #include "Tree.h" +#include "ViewProvider.h" #include "ViewProviderDocumentObject.h" @@ -391,6 +393,25 @@ void SelectionSingleton::enablePickedList(bool enable) } } +static void notifyDocumentObjectViewProvider(const SelectionChanges& changes) { + const auto* doc = App::GetApplication().getDocument(changes.pDocName); + if (!doc) { + return; + } + + const auto* obj = doc->getObject(changes.pObjectName); + if (!obj) { + return; + } + + auto* vp = Application::Instance->getViewProvider(obj); + if (!vp) { + return; + } + + vp->onSelectionChanged(changes); +} + void SelectionSingleton::notify(SelectionChanges &&Chng) { if(Notifying) { @@ -401,7 +422,7 @@ void SelectionSingleton::notify(SelectionChanges &&Chng) NotificationQueue.push_back(std::move(Chng)); while(!NotificationQueue.empty()) { const auto &msg = NotificationQueue.front(); - bool notify; + bool notify = false; switch(msg.Type) { case SelectionChanges::AddSelection: notify = isSelected(msg.pDocName, msg.pObjectName, msg.pSubName, ResolveMode::NoResolve); @@ -420,6 +441,9 @@ void SelectionSingleton::notify(SelectionChanges &&Chng) notify = true; } if(notify) { + // Notify the view provider of the object. + notifyDocumentObjectViewProvider(msg); + Notify(msg); try { signalSelectionChanged(msg); @@ -1454,6 +1478,21 @@ void SelectionSingleton::clearCompleteSelection(bool clearPreSelect) clearPreSelect?"Gui.Selection.clearSelection()" :"Gui.Selection.clearSelection(False)"); + // Send the clear selection notification to all view providers associated with the + // objects being deselected. + + std::set viewProviders; + for (_SelObj& sel : _SelList) { + if (auto vp = Application::Instance->getViewProvider(sel.pObject)) { + viewProviders.insert(vp); + } + } + + for (auto& vp : viewProviders) { + SelectionChanges Chng(SelectionChanges::ClrSelection); + vp->onSelectionChanged(Chng); + } + _SelList.clear(); SelectionChanges Chng(SelectionChanges::ClrSelection); diff --git a/src/Gui/Selection/SelectionObserverPython.cpp b/src/Gui/Selection/SelectionObserverPython.cpp index 936e074b1f..2d41f5c45a 100644 --- a/src/Gui/Selection/SelectionObserverPython.cpp +++ b/src/Gui/Selection/SelectionObserverPython.cpp @@ -32,15 +32,28 @@ using namespace Gui; std::vector SelectionObserverPython::_instances; -void SelectionObserverPythonHandler::init(const Py::Object& obj) +void SelectionObserverPythonHandler::init(PyObject* obj) { this->inst = obj; #undef FC_PY_ELEMENT -#define FC_PY_ELEMENT(_name) FC_PY_GetCallable(obj.ptr(),#_name,py_##_name); +#define FC_PY_ELEMENT(_name) FC_PY_GetCallable(obj,#_name,py_##_name); FC_PY_SEL_OBSERVER } +SelectionObserverPythonHandler::~SelectionObserverPythonHandler() +{ +#undef FC_PY_ELEMENT +#define FC_PY_ELEMENT(_name) py_##_name = Py::None(); + + try { + FC_PY_SEL_OBSERVER + } + catch (Py::Exception& e) { + e.clear(); + } +} + void SelectionObserverPythonHandler::handleSelectionChanged(const SelectionChanges& msg) { switch (msg.Type) @@ -198,7 +211,7 @@ void SelectionObserverPythonHandler::removePreselection(const SelectionChanges& SelectionObserverPython::SelectionObserverPython(const Py::Object& obj, ResolveMode resolve) : SelectionObserver(true, resolve) { - this->init(obj); + this->init(obj.ptr()); } SelectionObserverPython::~SelectionObserverPython() = default; diff --git a/src/Gui/Selection/SelectionObserverPython.h b/src/Gui/Selection/SelectionObserverPython.h index c7d258d20e..5eed3a217c 100644 --- a/src/Gui/Selection/SelectionObserverPython.h +++ b/src/Gui/Selection/SelectionObserverPython.h @@ -36,7 +36,9 @@ class GuiExport SelectionObserverPythonHandler public: /// Constructor explicit SelectionObserverPythonHandler() = default; - void init(const Py::Object& obj); + virtual ~SelectionObserverPythonHandler(); + + void init(PyObject* obj); void handleSelectionChanged(const SelectionChanges& msg); protected: @@ -48,10 +50,10 @@ protected: void removePreselection(const SelectionChanges&); void pickedListChanged(); - Py::Object inst; - private: + PyObject* inst{nullptr}; + #define FC_PY_SEL_OBSERVER \ FC_PY_ELEMENT(onSelectionChanged) \ FC_PY_ELEMENT(addSelection) \ @@ -89,6 +91,7 @@ public: private: void onSelectionChanged(const SelectionChanges& msg) override; + Py::Object inst; static std::vector _instances; }; diff --git a/src/Gui/ViewProvider.h b/src/Gui/ViewProvider.h index 4f394a0496..7b199119a2 100644 --- a/src/Gui/ViewProvider.h +++ b/src/Gui/ViewProvider.h @@ -74,6 +74,7 @@ class View3DInventorViewer; class ViewProviderPy; class ObjectItem; class MDIView; +class SelectionChanges; enum ViewStatus { UpdateData = 0, @@ -164,6 +165,8 @@ public: /// indicates if the ViewProvider use the new Selection model virtual bool useNewSelectionModel() const; virtual bool isSelectable() const {return true;} + /// called when the selection changes for the view provider + virtual void onSelectionChanged(const SelectionChanges&) {} /// return a hit element given the picked point which contains the full node path virtual bool getElementPicked(const SoPickedPoint *, std::string &subname) const; /// return a hit element to the selection path or 0 diff --git a/src/Gui/ViewProviderFeaturePython.cpp b/src/Gui/ViewProviderFeaturePython.cpp index 7b24735582..23c4c1383c 100644 --- a/src/Gui/ViewProviderFeaturePython.cpp +++ b/src/Gui/ViewProviderFeaturePython.cpp @@ -36,6 +36,7 @@ #include #include #include +#include #include "ViewProviderFeaturePython.h" #include "Application.h" @@ -44,6 +45,7 @@ #include "PythonWrapper.h" #include "View3DInventorViewer.h" #include "ViewProviderDocumentObjectPy.h" +#include "Selection.h" FC_LOG_LEVEL_INIT("ViewProviderFeaturePython", true, true) @@ -74,6 +76,8 @@ ViewProviderFeaturePythonImp::~ViewProviderFeaturePythonImp() catch (Py::Exception& e) { e.clear(); } + + this->selectionObserver.~SelectionObserverPythonHandler(); } void ViewProviderFeaturePythonImp::init(PyObject *pyobj) { @@ -84,6 +88,8 @@ void ViewProviderFeaturePythonImp::init(PyObject *pyobj) { #define FC_PY_ELEMENT(_name) FC_PY_ELEMENT_INIT(_name) FC_PY_VIEW_OBJECT + + this->selectionObserver.init(pyobj); } #define FC_PY_CALL_CHECK(_name) _FC_PY_CALL_CHECK(_name,return(NotImplemented)) @@ -202,6 +208,10 @@ ViewProviderFeaturePythonImp::useNewSelectionModel() const return Accepted; } +void ViewProviderFeaturePythonImp::onSelectionChanged(const SelectionChanges& changes) { + this->selectionObserver.handleSelectionChanged(changes); +} + bool ViewProviderFeaturePythonImp::getElement(const SoDetail *det, std::string &res) const { _FC_PY_CALL_CHECK(getElement,return(false)); diff --git a/src/Gui/ViewProviderFeaturePython.h b/src/Gui/ViewProviderFeaturePython.h index 023247019e..cc2737db8f 100644 --- a/src/Gui/ViewProviderFeaturePython.h +++ b/src/Gui/ViewProviderFeaturePython.h @@ -26,17 +26,19 @@ #include #include #include +#include #include "ViewProviderGeometryObject.h" #include "Document.h" - class SoSensor; class SoDragger; class SoNode; namespace Gui { +class SelectionChanges; + class GuiExport ViewProviderFeaturePythonImp { public: @@ -55,6 +57,7 @@ public: QIcon getIcon() const; bool claimChildren(std::vector&) const; ValueT useNewSelectionModel() const; + void onSelectionChanged(const SelectionChanges&); ValueT getElementPicked(const SoPickedPoint *pp, std::string &subname) const; bool getElement(const SoDetail *det, std::string &) const; bool getDetail(const char*, SoDetail *&det) const; @@ -130,6 +133,7 @@ public: private: ViewProviderDocumentObject* object; App::PropertyPythonObject &Proxy; + SelectionObserverPythonHandler selectionObserver; bool has__object__{false}; #define FC_PY_VIEW_OBJECT \ @@ -253,6 +257,10 @@ public: return ViewProviderT::useNewSelectionModel(); } } + /// called when the selection changes for the view provider + void onSelectionChanged(const SelectionChanges& changes) override { + return imp->onSelectionChanged(changes); + } bool getElementPicked(const SoPickedPoint *pp, std::string &subname) const override { auto ret = imp->getElementPicked(pp,subname); if(ret == ViewProviderFeaturePythonImp::NotImplemented)