From ec7bb3ddae9779d2cc769bee1e4da3444c112806 Mon Sep 17 00:00:00 2001 From: tetektoza Date: Tue, 19 Aug 2025 22:39:42 +0200 Subject: [PATCH] Part: Allow deleting children recursively Add feature to allow deletion of all children of compounds and booleans. --- src/Gui/ViewProviderGroupExtension.cpp | 27 ++--- src/Mod/Part/Gui/ViewProviderBoolean.cpp | 128 ++++++++++++++++++---- src/Mod/Part/Gui/ViewProviderCompound.cpp | 50 ++++++++- 3 files changed, 160 insertions(+), 45 deletions(-) diff --git a/src/Gui/ViewProviderGroupExtension.cpp b/src/Gui/ViewProviderGroupExtension.cpp index 2d08b8d9f4..f3c0d14045 100644 --- a/src/Gui/ViewProviderGroupExtension.cpp +++ b/src/Gui/ViewProviderGroupExtension.cpp @@ -44,7 +44,7 @@ using namespace Gui; namespace { // helper function to recursively delete group contents while respecting view provider onDelete methods - void deleteGroupContentsRecursively(App::GroupExtension* group, ViewProvider* groupViewProvider) { + void deleteGroupContentsRecursively(App::GroupExtension* group) { if (!group) { return; } @@ -59,9 +59,7 @@ namespace { // if the child is a group, recursively delete its contents first if (child->hasExtension(App::GroupExtension::getExtensionClassTypeId())) { auto* childGroup = child->getExtensionByType(); - Gui::Document* guiDoc = Application::Instance->getDocument(child->getDocument()); - ViewProvider* childVP = guiDoc ? guiDoc->getViewProvider(child) : nullptr; - deleteGroupContentsRecursively(childGroup, childVP); + deleteGroupContentsRecursively(childGroup); } Gui::Document* guiDoc = Application::Instance->getDocument(child->getDocument()); @@ -69,7 +67,7 @@ namespace { ViewProvider* vp = guiDoc->getViewProvider(child); if (vp) { // give group_recursive_deletion marker to the VP to mark that the deletion - // is supposed to delete all of it's children + // is supposed to delete all of its children std::vector groupDeletionMarker = {"group_recursive_deletion"}; bool shouldDelete = vp->onDelete(groupDeletionMarker); @@ -217,7 +215,7 @@ void ViewProviderGroupExtension::extensionHide() { ViewProviderExtension::extensionHide(); } -bool ViewProviderGroupExtension::extensionOnDelete(const std::vector< std::string >& subNames) { +bool ViewProviderGroupExtension::extensionOnDelete(const std::vector< std::string >&) { auto* group = getExtendedViewProvider()->getObject()->getExtensionByType(); @@ -228,13 +226,9 @@ bool ViewProviderGroupExtension::extensionOnDelete(const std::vector< std::strin return true; } - std::vector allDescendants; - if (getExtendedViewProvider()->getObject()->isDerivedFrom()) { - auto* docGroup = static_cast(getExtendedViewProvider()->getObject()); - allDescendants = docGroup->getAllChildren(); - } else { - allDescendants = directChildren; - } + const auto* docGroup = + freecad_cast(getExtendedViewProvider()->getObject()); + auto allDescendants = docGroup ? docGroup->getAllChildren() : directChildren; QString message; if (allDescendants.size() == directChildren.size()) { @@ -254,16 +248,17 @@ bool ViewProviderGroupExtension::extensionOnDelete(const std::vector< std::strin QObject::tr("Delete group contents recursively?"), message, QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel, - QMessageBox::Yes + QMessageBox::No ); if (choice == QMessageBox::Cancel) { // don't delete anything if user has cancelled return false; } - else if (choice == QMessageBox::Yes) { + + if (choice == QMessageBox::Yes) { // delete all of the children recursively and call their viewprovider method - deleteGroupContentsRecursively(group, getExtendedViewProvider()); + deleteGroupContentsRecursively(group); } // if user has specified "No" then delete the group but move children to the parent or root diff --git a/src/Mod/Part/Gui/ViewProviderBoolean.cpp b/src/Mod/Part/Gui/ViewProviderBoolean.cpp index 11b4c0a9e3..4e1635fb2e 100644 --- a/src/Mod/Part/Gui/ViewProviderBoolean.cpp +++ b/src/Mod/Part/Gui/ViewProviderBoolean.cpp @@ -23,10 +23,11 @@ # include # include - - +# include +#include #include #include +#include #include #include @@ -35,6 +36,65 @@ using namespace PartGui; +namespace { + // helper function for Boolean operation deletion with user confirmation + bool handleBooleanDeletion(const std::vector& subNames, + const QString& operationName, + const QString& objectLabel, + const std::vector& inputObjects, + const QString& inputDescription) + { + if (inputObjects.empty()) { + return true; + } + + // if we are in group deletion context it means user is deleting group that contains + // this boolean and they have accepted to delete all of the group objects recursively + // so delete everything automatically + bool inGroupDeletion = !subNames.empty() && subNames[0] == "group_recursive_deletion"; + if (inGroupDeletion) { + for (auto obj : inputObjects) { + if (obj && obj->isAttachedToDocument() && !obj->isRemoving()) { + obj->getDocument()->removeObject(obj->getNameInDocument()); + } + } + return true; + } + + QMessageBox::StandardButton choice = QMessageBox::question( + Gui::getMainWindow(), + QObject::tr("Delete %1 content?").arg(operationName), + QObject::tr("The %1 '%2' has %3. Do you want to delete them as well?") + .arg(operationName.toLower()) + .arg(objectLabel) + .arg(inputDescription), + QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel, + QMessageBox::No + ); + + if (choice == QMessageBox::Cancel) { + return false; + } + + if (choice == QMessageBox::Yes) { + for (auto obj : inputObjects) { + if (obj && obj->isAttachedToDocument() && !obj->isRemoving()) { + obj->getDocument()->removeObject(obj->getNameInDocument()); + } + } + return true; + } + + for (auto obj : inputObjects) { + if (obj) { + Gui::Application::Instance->showViewProvider(obj); + } + } + + return true; + } +} + PROPERTY_SOURCE(PartGui::ViewProviderBoolean,PartGui::ViewProviderPart) ViewProviderBoolean::ViewProviderBoolean() = default; @@ -138,19 +198,37 @@ void ViewProviderBoolean::updateData(const App::Property* prop) } } -bool ViewProviderBoolean::onDelete(const std::vector &) +bool ViewProviderBoolean::onDelete(const std::vector &subNames) { // get the input shapes Part::Boolean* pBool = getObject(); App::DocumentObject *pBase = pBool->Base.getValue(); App::DocumentObject *pTool = pBool->Tool.getValue(); - if (pBase) - Gui::Application::Instance->showViewProvider(pBase); - if (pTool) - Gui::Application::Instance->showViewProvider(pTool); + // Prepare input objects list and description + std::vector inputObjects; + if (pBase) { + inputObjects.push_back(pBase); + } - return true; + if (pTool) { + inputObjects.push_back(pTool); + } + + QString inputDescription; + if (pBase && pTool) { + inputDescription = QObject::tr("base and tool objects"); + } else if (pBase) { + inputDescription = QObject::tr("base object"); + } else if (pTool) { + inputDescription = QObject::tr("tool object"); + } + + return handleBooleanDeletion(subNames, + QObject::tr("Boolean operation"), + QString::fromUtf8(pBool->Label.getValue()), + inputObjects, + inputDescription); } PROPERTY_SOURCE(PartGui::ViewProviderMultiFuse,PartGui::ViewProviderPart) @@ -229,18 +307,19 @@ void ViewProviderMultiFuse::updateData(const App::Property* prop) } } -bool ViewProviderMultiFuse::onDelete(const std::vector &) +bool ViewProviderMultiFuse::onDelete(const std::vector &subNames) { // get the input shapes Part::MultiFuse* pBool = getObject(); std::vector pShapes = pBool->Shapes.getValues(); - for (auto it : pShapes) { - if (it) { - Gui::Application::Instance->showViewProvider(it); - } - } - - return true; + + QString inputDescription = QObject::tr("%1 input objects").arg(pShapes.size()); + + return handleBooleanDeletion(subNames, + QObject::tr("Fusion"), + QString::fromUtf8(pBool->Label.getValue()), + pShapes, + inputDescription); } bool ViewProviderMultiFuse::canDragObjects() const @@ -364,18 +443,19 @@ void ViewProviderMultiCommon::updateData(const App::Property* prop) } } -bool ViewProviderMultiCommon::onDelete(const std::vector &) +bool ViewProviderMultiCommon::onDelete(const std::vector &subNames) { // get the input shapes Part::MultiCommon* pBool = getObject(); std::vector pShapes = pBool->Shapes.getValues(); - for (auto it : pShapes) { - if (it) { - Gui::Application::Instance->showViewProvider(it); - } - } - - return true; + + QString inputDescription = QObject::tr("%1 input objects").arg(pShapes.size()); + + return handleBooleanDeletion(subNames, + QObject::tr("Intersection"), + QString::fromUtf8(pBool->Label.getValue()), + pShapes, + inputDescription); } bool ViewProviderMultiCommon::canDragObjects() const diff --git a/src/Mod/Part/Gui/ViewProviderCompound.cpp b/src/Mod/Part/Gui/ViewProviderCompound.cpp index 7f56fa21ac..4b30864b78 100644 --- a/src/Mod/Part/Gui/ViewProviderCompound.cpp +++ b/src/Mod/Part/Gui/ViewProviderCompound.cpp @@ -23,9 +23,11 @@ # include # include +# include - +#include #include +#include #include #include "ViewProviderCompound.h" @@ -47,14 +49,52 @@ std::vector ViewProviderCompound::claimChildren() const return getObject()->Links.getValues(); } -bool ViewProviderCompound::onDelete(const std::vector &) +bool ViewProviderCompound::onDelete(const std::vector &subNames) { // get the input shapes Part::Compound* pComp = getObject(); std::vector pLinks = pComp->Links.getValues(); - for (auto pLink : pLinks) { - if (pLink) - Gui::Application::Instance->showViewProvider(pLink); + + if (!pLinks.empty()) { + // check group deletion marker -> it means group called this VP to delete it's content + // so delete everything recursively + bool inGroupDeletion = !subNames.empty() && subNames[0] == "group_recursive_deletion"; + + if (inGroupDeletion) { + for (auto pLink : pLinks) { + if (pLink && pLink->isAttachedToDocument() && !pLink->isRemoving()) { + pLink->getDocument()->removeObject(pLink->getNameInDocument()); + } + } + return true; + } + QMessageBox::StandardButton choice = QMessageBox::question( + Gui::getMainWindow(), + QObject::tr("Delete compound content?"), + QObject::tr("The compound '%1' has %2 child objects. Do you want to delete them as well?") + .arg(QString::fromUtf8(pComp->Label.getValue())) + .arg(pLinks.size()), + QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel, + QMessageBox::No + ); + + if (choice == QMessageBox::Cancel) { + return false; + } + + if (choice == QMessageBox::Yes) { + for (auto pLink : pLinks) { + if (pLink && pLink->isAttachedToDocument() && !pLink->isRemoving()) { + pLink->getDocument()->removeObject(pLink->getNameInDocument()); + } + } + return true; + } + + for (auto pLink : pLinks) { + if (pLink) + Gui::Application::Instance->showViewProvider(pLink); + } } return true;