Core: Refactor Document::setEdit

This commit is contained in:
wmayer
2024-10-25 18:42:58 +02:00
committed by Chris Hennes
parent 4fd0e2c89d
commit 2c8101363c
2 changed files with 319 additions and 138 deletions

View File

@@ -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<ViewProviderDocumentObject*>(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<ViewProviderDocumentObject*>(
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<ViewProviderDocumentObject*>(_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<App::DocumentObject*, std::string>
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<ViewProviderDocumentObject*>(
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<ViewProviderDocumentObject*>(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<ViewProviderDocumentObject*>(
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<App::GeoFeatureGroupExtension>();
// 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<ViewProviderDocumentObject*>(
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<View3DInventor *>(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<View3DInventor *>(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<ViewProviderDocumentObject*>(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;

View File

@@ -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<MDIView*> getMDIViews() const;
/// returns a list of all MDI views of a certain type
std::list<MDIView*> 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);