From 2c8101363c5411b22bd3220a74aae8a1210e8935 Mon Sep 17 00:00:00 2001 From: wmayer Date: Fri, 25 Oct 2024 18:42:58 +0200 Subject: [PATCH] Core: Refactor Document::setEdit --- src/Gui/Document.cpp | 450 ++++++++++++++++++++++++++++++------------- src/Gui/Document.h | 7 +- 2 files changed, 319 insertions(+), 138 deletions(-) diff --git a/src/Gui/Document.cpp b/src/Gui/Document.cpp index e79979349a..b53139b801 100644 --- a/src/Gui/Document.cpp +++ b/src/Gui/Document.cpp @@ -135,7 +135,277 @@ struct DocumentP using ConnectionBlock = boost::signals2::shared_connection_block; ConnectionBlock connectActObjectBlocker; ConnectionBlock connectChangeDocumentBlocker; + + static ViewProviderDocumentObject* throwIfCastFails(ViewProvider* p) + { + if (auto vp = dynamic_cast(p)) { + return vp; + } + + throw Base::RuntimeError("cannot edit non ViewProviderDocumentObject"); + } + + static App::DocumentObject* tryGetObject(ViewProviderDocumentObject* vp) + { + auto obj = vp->getObject(); + if (!obj->isAttachedToDocument()) { + throw Base::RuntimeError("cannot edit detached object"); + } + + return obj; + } + + void throwIfNotInMap(App::DocumentObject* obj, App::Document* doc) const + { + if (_ViewProviderMap.find(obj) == _ViewProviderMap.end()) { + // We can actually support editing external object, by calling + // View3DInventViewer::setupEditingRoot() before exiting from + // ViewProvider::setEditViewer(), which transfer all child node of the view + // provider into an editing node inside the viewer of this document. And + // that's may actually be the case, as the subname referenced sub object + // is allowed to be in other documents. + // + // We just disabling editing external parent object here, for bug + // tracking purpose. Because, bringing an unrelated external object to + // the current view for editing will confuse user, and is certainly a + // bug. By right, the top parent object should always belong to the + // editing document, and the actually editing sub object can be + // external. + // + // So, you can either call setEdit() with subname set to 0, which cause + // the code above to auto detect selection context, and dispatch the + // editing call to the correct document. Or, supply subname yourself, + // and make sure you get the document right. + // + std::stringstream str; + str << "cannot edit object '" + << obj->getNameInDocument() + << "': not found in document " + << "'" + << doc->getName() + << "'"; + throw Base::RuntimeError(str.str()); + } + } + + App::DocumentObject* tryGetSubObject(App::DocumentObject* obj, const char *subname) + { + _editingTransform = Base::Matrix4D(); + auto sobj = obj->getSubObject(subname, nullptr, &_editingTransform); + if (!sobj || !sobj->isAttachedToDocument()) { + std::stringstream str; + str << "Invalid sub object '" + << obj->getFullName() + << '.' << (subname ? subname : "") + << "'"; + throw Base::RuntimeError(str.str()); + } + + return sobj; + } + + ViewProviderDocumentObject* tryGetSubViewProvider(ViewProviderDocumentObject* vp, + App::DocumentObject* obj, + App::DocumentObject* sobj) const + { + auto svp = vp; + if (sobj != obj) { + svp = dynamic_cast( + Application::Instance->getViewProvider(sobj)); + if (!svp) { + std::stringstream str; + str << "Cannot edit '" + << sobj->getFullName() + << "' without view provider"; + throw Base::RuntimeError(str.str()); + } + } + + return svp; + } + + void setParentViewProvider(ViewProviderDocumentObject* vp) + { + _editViewProviderParent = vp; + } + + void clearSubElement() + { + _editSubElement.clear(); + _editSubname.clear(); + } + + void findElementName(const char* subname) + { + if (subname) { + const char *element = Data::findElementName(subname); + if (element) { + _editSubname = std::string(subname, element - subname); + _editSubElement = element; + } + else { + _editSubname = subname; + } + } + } + + void findSubObjectList(App::DocumentObject* obj, const char* subname) + { + auto sobjs = obj->getSubObjectList(subname); + _editObjs.clear(); + _editObjs.insert(sobjs.begin(), sobjs.end()); + } + + bool tryStartEditing(ViewProviderDocumentObject* vp, + App::DocumentObject* obj, + const char* subname, + int ModNum) + { + auto sobj = tryGetSubObject(obj, subname); + auto svp = tryGetSubViewProvider(vp, obj, sobj); + + setParentViewProvider(vp); + clearSubElement(); + findElementName(subname); + findSubObjectList(obj, subname); + return tryStartEditing(svp, sobj, ModNum); + } + + bool tryStartEditing(ViewProviderDocumentObject* svp, App::DocumentObject* sobj, int ModNum) + { + _editingObject = sobj; + _editMode = ModNum; + _editViewProvider = svp->startEditing(ModNum); + if (!_editViewProvider) { + _editViewProviderParent = nullptr; + _editObjs.clear(); + _editingObject = nullptr; + FC_LOG("object '" << sobj->getFullName() << "' refuse to edit"); + return false; + } + + return true; + } + + void setEditingViewerIfPossible(View3DInventor* view3d, int ModNum) + { + if (view3d) { + view3d->getViewer()->setEditingViewProvider(_editViewProvider, ModNum); + _editingViewer = view3d->getViewer(); + } + } + + void signalEditMode() + { + if (auto vpd = dynamic_cast(_editViewProvider)) { + vpd->getDocument()->signalInEdit(*vpd); + } + } + + void setDocumentNameOfTaskDialog(App::Document* doc) + { + Gui::TaskView::TaskDialog* dlg = Gui::Control().activeDialog(); + if (dlg) { + dlg->setDocumentName(doc->getName()); + } + } }; + +class ParentFinder +{ +public: + ParentFinder(App::DocumentObject* obj, + ViewProviderDocumentObject* vp, + const std::string& subname) + : obj{obj} + , vp{vp} + , subname{subname} + {} + + App::DocumentObject* getObject() const + { + return obj; + } + + ViewProviderDocumentObject* getViewProvider() const + { + return vp; + } + + std::string getSubname() const + { + return subname; + } + + bool findParent() + { + auto result = findParentAndSubName(obj); + App::DocumentObject* parentObj = std::get<0>(result); + + if (parentObj) { + subname = std::get<1>(result); + obj = parentObj; + vp = findParentObject(parentObj, subname.c_str()); + return true; + } + + return false; + } + +private: + static std::tuple + findParentAndSubName(App::DocumentObject* obj) + { + // No subname reference is given, we try to extract one from the current + // selection in order to obtain the correct transformation matrix below + auto sels = Gui::Selection().getCompleteSelection(ResolveMode::NoResolve); + App::DocumentObject* parentObj = nullptr; + std::string _subname; + for (auto &sel : sels) { + if (!sel.pObject || !sel.pObject->isAttachedToDocument()) { + continue; + } + if (!parentObj) { + parentObj = sel.pObject; + } + else if (parentObj != sel.pObject) { + FC_LOG("Cannot deduce subname for editing, more than one parent?"); + parentObj = nullptr; + break; + } + + auto sobj = parentObj->getSubObject(sel.SubName); + if (!sobj || (sobj != obj && sobj->getLinkedObject(true) != obj)) { + FC_LOG("Cannot deduce subname for editing, subname mismatch"); + parentObj = nullptr; + break; + } + + _subname = sel.SubName; + } + + return std::make_tuple(parentObj, _subname); + } + + static Gui::ViewProviderDocumentObject* findParentObject(App::DocumentObject* parentObj, + const char* subname) + { + FC_LOG("deduced editing reference " << parentObj->getFullName() << '.' << subname); + auto vp = dynamic_cast( + Application::Instance->getViewProvider(parentObj)); + if (!vp || !vp->getDocument()) { + throw Base::RuntimeError("invalid view provider for parent object"); + } + + return vp; + } + +private: + App::DocumentObject* obj; + ViewProviderDocumentObject* vp; + std::string subname; +}; + } // namespace Gui /* TRANSLATOR Gui::Document */ @@ -289,165 +559,73 @@ Document::~Document() bool Document::setEdit(Gui::ViewProvider* p, int ModNum, const char *subname) { - auto vp = dynamic_cast(p); - if (!vp) { - FC_ERR("cannot edit non ViewProviderDocumentObject"); + try { + return trySetEdit(p, ModNum, subname); + } + catch (const Base::Exception& e) { + FC_ERR("" << e.what()); return false; } +} +void Document::resetIfEditing() +{ // Fix regression: https://forum.freecad.org/viewtopic.php?f=19&t=43629&p=371972#p371972 // When an object is already in edit mode a subsequent call for editing is only possible // when resetting the currently edited object. if (d->_editViewProvider) { _resetEdit(); } +} - auto obj = vp->getObject(); - if(!obj->isAttachedToDocument()) { - FC_ERR("cannot edit detached object"); - return false; - } - - std::string _subname; - if(!subname || !subname[0]) { - // No subname reference is given, we try to extract one from the current - // selection in order to obtain the correct transformation matrix below - auto sels = Gui::Selection().getCompleteSelection(ResolveMode::NoResolve); - App::DocumentObject *parentObj = nullptr; - for(auto &sel : sels) { - if(!sel.pObject || !sel.pObject->isAttachedToDocument()) - continue; - if(!parentObj) - parentObj = sel.pObject; - else if(parentObj!=sel.pObject) { - FC_LOG("Cannot deduce subname for editing, more than one parent?"); - parentObj = nullptr; - break; - } - auto sobj = parentObj->getSubObject(sel.SubName); - if(!sobj || (sobj!=obj && sobj->getLinkedObject(true)!= obj)) { - FC_LOG("Cannot deduce subname for editing, subname mismatch"); - parentObj = nullptr; - break; - } - _subname = sel.SubName; - } - if(parentObj) { - FC_LOG("deduced editing reference " << parentObj->getFullName() << '.' << _subname); - subname = _subname.c_str(); - obj = parentObj; - vp = dynamic_cast( - Application::Instance->getViewProvider(obj)); - if(!vp || !vp->getDocument()) { - FC_ERR("invliad view provider for parent object"); - return false; - } - if(vp->getDocument()!=this) - return vp->getDocument()->setEdit(vp,ModNum,subname); - } - } - - if (d->_ViewProviderMap.find(obj) == d->_ViewProviderMap.end()) { - // We can actually support editing external object, by calling - // View3DInventViewer::setupEditingRoot() before exiting from - // ViewProvider::setEditViewer(), which transfer all child node of the view - // provider into an editing node inside the viewer of this document. And - // that's may actually be the case, as the subname referenced sub object - // is allowed to be in other documents. - // - // We just disabling editing external parent object here, for bug - // tracking purpose. Because, bringing an unrelated external object to - // the current view for editing will confuse user, and is certainly a - // bug. By right, the top parent object should always belong to the - // editing document, and the actually editing sub object can be - // external. - // - // So, you can either call setEdit() with subname set to 0, which cause - // the code above to auto detect selection context, and dispatch the - // editing call to the correct document. Or, supply subname yourself, - // and make sure you get the document right. - // - FC_ERR("cannot edit object '" << obj->getNameInDocument() << "': not found in document " - << "'" << getDocument()->getName() << "'"); - return false; - } - - d->_editingTransform = Base::Matrix4D(); - // Geo feature group now handles subname like link group. So no need of the - // following code. - // - // if(!subname || !subname[0]) { - // auto group = App::GeoFeatureGroupExtension::getGroupOfObject(obj); - // if(group) { - // auto ext = group->getExtensionByType(); - // d->_editingTransform = ext->globalGroupPlacement().toMatrix(); - // } - // } - auto sobj = obj->getSubObject(subname,nullptr,&d->_editingTransform); - if(!sobj || !sobj->isAttachedToDocument()) { - FC_ERR("Invalid sub object '" << obj->getFullName() - << '.' << (subname?subname:"") << "'"); - return false; - } - auto svp = vp; - if(sobj!=obj) { - svp = dynamic_cast( - Application::Instance->getViewProvider(sobj)); - if(!svp) { - FC_ERR("Cannot edit '" << sobj->getFullName() << "' without view provider"); - return false; - } - } - +View3DInventor* Document::openEditingView3D(ViewProviderDocumentObject* vp) +{ auto view3d = dynamic_cast(getActiveView()); // if the currently active view is not the 3d view search for it and activate it - if (view3d) + if (view3d) { getMainWindow()->setActiveWindow(view3d); - else + } + else { view3d = dynamic_cast(setActiveView(vp)); - Application::Instance->setEditDocument(this); + } - d->_editViewProviderParent = vp; - d->_editSubElement.clear(); - d->_editSubname.clear(); + return view3d; +} - if (subname) { - const char *element = Data::findElementName(subname); - if (element) { - d->_editSubname = std::string(subname,element-subname); - d->_editSubElement = element; - } - else { - d->_editSubname = subname; +bool Document::trySetEdit(Gui::ViewProvider* p, int ModNum, const char *subname) +{ + auto vp = DocumentP::throwIfCastFails(p); + + resetIfEditing(); + + auto obj = DocumentP::tryGetObject(vp); + + std::string _subname = subname ? subname : ""; + if (_subname.empty()) { + ParentFinder finder(obj, vp, _subname); + if (finder.findParent()) { + _subname = finder.getSubname(); + obj = finder.getObject(); + vp = finder.getViewProvider(); + if (vp->getDocument() != this) { + return vp->getDocument()->setEdit(vp, ModNum, _subname.c_str()); + } } } - auto sobjs = obj->getSubObjectList(subname); - d->_editObjs.clear(); - d->_editObjs.insert(sobjs.begin(),sobjs.end()); - d->_editingObject = sobj; + d->throwIfNotInMap(obj, getDocument()); - d->_editMode = ModNum; - d->_editViewProvider = svp->startEditing(ModNum); - if(!d->_editViewProvider) { - d->_editViewProviderParent = nullptr; - d->_editObjs.clear(); - d->_editingObject = nullptr; - FC_LOG("object '" << sobj->getFullName() << "' refuse to edit"); + Application::Instance->setEditDocument(this); + + if (!d->tryStartEditing(vp, obj, _subname.c_str(), ModNum)) { return false; } - if(view3d) { - view3d->getViewer()->setEditingViewProvider(d->_editViewProvider,ModNum); - d->_editingViewer = view3d->getViewer(); - } - Gui::TaskView::TaskDialog* dlg = Gui::Control().activeDialog(); - if (dlg) - dlg->setDocumentName(this->getDocument()->getName()); - if (d->_editViewProvider->isDerivedFrom(ViewProviderDocumentObject::getClassTypeId())) { - auto vpd = static_cast(d->_editViewProvider); - vpd->getDocument()->signalInEdit(*vpd); - } + d->setDocumentNameOfTaskDialog(getDocument()); + + auto view3d = openEditingView3D(vp); + d->setEditingViewerIfPossible(view3d, ModNum); + d->signalEditMode(); App::AutoTransaction::setEnable(false); return true; diff --git a/src/Gui/Document.h b/src/Gui/Document.h index cefe963d6a..42a3aae04c 100644 --- a/src/Gui/Document.h +++ b/src/Gui/Document.h @@ -51,6 +51,7 @@ namespace Gui { class BaseView; class MDIView; +class View3DInventor; class ViewProvider; class ViewProviderDocumentObject; class Application; @@ -217,9 +218,9 @@ public: std::list getMDIViews() const; /// returns a list of all MDI views of a certain type std::list getMDIViewsOfType(const Base::Type& typeId) const; - //@} - MDIView *setActiveView(ViewProviderDocumentObject *vp=nullptr, Base::Type typeId = Base::Type()); + View3DInventor* openEditingView3D(ViewProviderDocumentObject* vp); + //@} /** @name View provider handling */ //@{ @@ -313,6 +314,8 @@ protected: Gui::DocumentPy *_pcDocPy; private: + bool trySetEdit(Gui::ViewProvider* p, int ModNum, const char *subname); + void resetIfEditing(); //handles the scene graph nodes to correctly group child and parents void handleChildren3D(ViewProvider* viewProvider, bool deleting=false);