Merge pull request #23230 from tetektoza/fix/22070_fix_deleting_compound_not_deleting_children
Core: Allow users deleting objects under group recursively
This commit is contained in:
@@ -27,12 +27,14 @@
|
||||
|
||||
#include <App/Document.h>
|
||||
#include <App/DocumentObject.h>
|
||||
#include <App/DocumentObjectGroup.h>
|
||||
#include <App/GroupExtension.h>
|
||||
#include <Base/Console.h>
|
||||
#include <Base/Tools.h>
|
||||
|
||||
#include "ViewProviderGroupExtension.h"
|
||||
#include "ViewProviderDocumentObject.h"
|
||||
#include "Application.h"
|
||||
#include "Command.h"
|
||||
#include "Document.h"
|
||||
#include "MainWindow.h"
|
||||
@@ -40,6 +42,49 @@
|
||||
|
||||
using namespace Gui;
|
||||
|
||||
namespace {
|
||||
// helper function to recursively delete group contents while respecting view provider onDelete methods
|
||||
void deleteGroupContentsRecursively(App::GroupExtension* group) {
|
||||
if (!group) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::vector<App::DocumentObject*> children = group->Group.getValues();
|
||||
|
||||
for (App::DocumentObject* child : children) {
|
||||
if (!child || !child->isAttachedToDocument() || child->isRemoving()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// if the child is a group, recursively delete its contents first
|
||||
if (child->hasExtension(App::GroupExtension::getExtensionClassTypeId())) {
|
||||
auto* childGroup = child->getExtensionByType<App::GroupExtension>();
|
||||
deleteGroupContentsRecursively(childGroup);
|
||||
}
|
||||
|
||||
Gui::Document* guiDoc = Application::Instance->getDocument(child->getDocument());
|
||||
if (guiDoc) {
|
||||
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 its children
|
||||
std::vector<std::string> groupDeletionMarker = {"group_recursive_deletion"};
|
||||
bool shouldDelete = vp->onDelete(groupDeletionMarker);
|
||||
|
||||
if (!shouldDelete) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// if the object still exists and wasn't deleted by its view provider, delete it directly
|
||||
if (child->isAttachedToDocument() && !child->isRemoving()) {
|
||||
child->getDocument()->removeObject(child->getNameInDocument());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
EXTENSION_PROPERTY_SOURCE(Gui::ViewProviderGroupExtension, Gui::ViewProviderExtension)
|
||||
|
||||
ViewProviderGroupExtension::ViewProviderGroupExtension()
|
||||
@@ -170,24 +215,53 @@ void ViewProviderGroupExtension::extensionHide() {
|
||||
ViewProviderExtension::extensionHide();
|
||||
}
|
||||
|
||||
bool ViewProviderGroupExtension::extensionOnDelete(const std::vector< std::string >& ) {
|
||||
bool ViewProviderGroupExtension::extensionOnDelete(const std::vector< std::string >&) {
|
||||
|
||||
auto* group = getExtendedViewProvider()->getObject()->getExtensionByType<App::GroupExtension>();
|
||||
// If the group is nonempty ask the user if they want to delete its content
|
||||
if (group->Group.getSize() > 0) {
|
||||
QMessageBox::StandardButton choice =
|
||||
QMessageBox::question(getMainWindow(), QObject::tr ( "Delete group content?" ),
|
||||
QObject::tr ( "The %1 is not empty, delete its content as well?")
|
||||
.arg ( QString::fromUtf8 ( getExtendedViewProvider()->getObject()->Label.getValue () ) ),
|
||||
QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes );
|
||||
|
||||
if (choice == QMessageBox::Yes) {
|
||||
Gui::Command::doCommand(Gui::Command::Doc,
|
||||
"App.getDocument(\"%s\").getObject(\"%s\").removeObjectsFromDocument()"
|
||||
, getExtendedViewProvider()->getObject()->getDocument()->getName()
|
||||
, getExtendedViewProvider()->getObject()->getNameInDocument());
|
||||
}
|
||||
|
||||
std::vector<App::DocumentObject*> directChildren = group->Group.getValues();
|
||||
|
||||
// just delete without messagebox if group is empty
|
||||
if (directChildren.empty()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const auto* docGroup =
|
||||
freecad_cast<App::DocumentObjectGroup*>(getExtendedViewProvider()->getObject());
|
||||
auto allDescendants = docGroup ? docGroup->getAllChildren() : directChildren;
|
||||
|
||||
QString message;
|
||||
if (allDescendants.size() == directChildren.size()) {
|
||||
message = QObject::tr("The group '%1' contains %2 object(s). Do you want to delete them as well?")
|
||||
.arg(QString::fromUtf8(getExtendedViewProvider()->getObject()->Label.getValue()))
|
||||
.arg(allDescendants.size());
|
||||
} else {
|
||||
// if we have nested groups
|
||||
message = QObject::tr("The group '%1' contains %2 direct children and %3 total descendants (including nested groups). Do you want to delete all of them recursively?")
|
||||
.arg(QString::fromUtf8(getExtendedViewProvider()->getObject()->Label.getValue()))
|
||||
.arg(directChildren.size())
|
||||
.arg(allDescendants.size());
|
||||
}
|
||||
|
||||
QMessageBox::StandardButton choice = QMessageBox::question(
|
||||
getMainWindow(),
|
||||
QObject::tr("Delete group contents recursively?"),
|
||||
message,
|
||||
QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel,
|
||||
QMessageBox::No
|
||||
);
|
||||
|
||||
if (choice == QMessageBox::Cancel) {
|
||||
// don't delete anything if user has cancelled
|
||||
return false;
|
||||
}
|
||||
|
||||
if (choice == QMessageBox::Yes) {
|
||||
// delete all of the children recursively and call their viewprovider method
|
||||
deleteGroupContentsRecursively(group);
|
||||
}
|
||||
// if user has specified "No" then delete the group but move children to the parent or root
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -23,10 +23,11 @@
|
||||
|
||||
# include <TopExp.hxx>
|
||||
# include <TopTools_IndexedMapOfShape.hxx>
|
||||
|
||||
|
||||
# include <QMessageBox>
|
||||
#include <App/Document.h>
|
||||
#include <Gui/Application.h>
|
||||
#include <Gui/BitmapFactory.h>
|
||||
#include <Gui/MainWindow.h>
|
||||
#include <Mod/Part/App/FeaturePartCommon.h>
|
||||
#include <Mod/Part/App/FeaturePartFuse.h>
|
||||
|
||||
@@ -35,6 +36,65 @@
|
||||
|
||||
using namespace PartGui;
|
||||
|
||||
namespace {
|
||||
// helper function for Boolean operation deletion with user confirmation
|
||||
bool handleBooleanDeletion(const std::vector<std::string>& subNames,
|
||||
const QString& operationName,
|
||||
const QString& objectLabel,
|
||||
const std::vector<App::DocumentObject*>& 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<std::string> &)
|
||||
bool ViewProviderBoolean::onDelete(const std::vector<std::string> &subNames)
|
||||
{
|
||||
// get the input shapes
|
||||
Part::Boolean* pBool = getObject<Part::Boolean>();
|
||||
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<App::DocumentObject*> 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<std::string> &)
|
||||
bool ViewProviderMultiFuse::onDelete(const std::vector<std::string> &subNames)
|
||||
{
|
||||
// get the input shapes
|
||||
Part::MultiFuse* pBool = getObject<Part::MultiFuse>();
|
||||
std::vector<App::DocumentObject*> 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<std::string> &)
|
||||
bool ViewProviderMultiCommon::onDelete(const std::vector<std::string> &subNames)
|
||||
{
|
||||
// get the input shapes
|
||||
Part::MultiCommon* pBool = getObject<Part::MultiCommon>();
|
||||
std::vector<App::DocumentObject*> 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
|
||||
|
||||
@@ -23,9 +23,11 @@
|
||||
|
||||
# include <TopExp.hxx>
|
||||
# include <TopTools_IndexedMapOfShape.hxx>
|
||||
# include <QMessageBox>
|
||||
|
||||
|
||||
#include <App/Document.h>
|
||||
#include <Gui/Application.h>
|
||||
#include <Gui/MainWindow.h>
|
||||
#include <Mod/Part/App/FeatureCompound.h>
|
||||
|
||||
#include "ViewProviderCompound.h"
|
||||
@@ -47,14 +49,52 @@ std::vector<App::DocumentObject*> ViewProviderCompound::claimChildren() const
|
||||
return getObject<Part::Compound>()->Links.getValues();
|
||||
}
|
||||
|
||||
bool ViewProviderCompound::onDelete(const std::vector<std::string> &)
|
||||
bool ViewProviderCompound::onDelete(const std::vector<std::string> &subNames)
|
||||
{
|
||||
// get the input shapes
|
||||
Part::Compound* pComp = getObject<Part::Compound>();
|
||||
std::vector<App::DocumentObject*> 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;
|
||||
|
||||
Reference in New Issue
Block a user