/*************************************************************************** * Copyright (c) 2004 Jürgen Riegel * * * * This file is part of the FreeCAD CAx development system. * * * * This library is free software; you can redistribute it and/or * * modify it under the terms of the GNU Library General Public * * License as published by the Free Software Foundation; either * * version 2 of the License, or (at your option) any later version. * * * * This library is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU Library General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this library; see the file COPYING.LIB. If not, * * write to the Free Software Foundation, Inc., 59 Temple Place, * * Suite 330, Boston, MA 02111-1307, USA * * * ***************************************************************************/ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "Document.h" #include "DocumentPy.h" #include "Application.h" #include "Command.h" #include "Control.h" #include "FileDialog.h" #include "MainWindow.h" #include "MDIView.h" #include "NotificationArea.h" #include "Selection.h" #include "Thumbnail.h" #include "Tree.h" #include "View3DInventor.h" #include "View3DInventorViewer.h" #include "ViewProviderDocumentObject.h" #include "ViewProviderDocumentObjectGroup.h" #include "WaitCursor.h" FC_LOG_LEVEL_INIT("Gui", true, true) using namespace Gui; namespace sp = std::placeholders; namespace Gui { // Pimpl class struct DocumentP { Thumbnail thumb; int _iWinCount; int _iDocId; bool _isClosing; bool _isModified; bool _isTransacting; bool _changeViewTouchDocument; bool _editWantsRestore; bool _editWantsRestorePrevious; int _editMode; int _editModePrevious; ViewProvider* _editViewProvider; ViewProvider* _editViewProviderPrevious; App::DocumentObject* _editingObject; ViewProviderDocumentObject* _editViewProviderParent; std::string _editSubname; std::string _editSubElement; Base::Matrix4D _editingTransform; View3DInventorViewer* _editingViewer; std::set _editObjs; Application* _pcAppWnd; // the doc/Document App::Document* _pcDocument; /// List of all registered views std::list baseViews; /// List of all registered views std::list passiveViews; std::map _ViewProviderMap; std::map _CoinMap; std::map _ViewProviderMapAnnotation; std::list _redoViewProviders; using Connection = fastsignals::connection; using AdvancedConnection = fastsignals::advanced_connection; Connection connectNewObject; Connection connectDelObject; Connection connectCngObject; Connection connectRenObject; AdvancedConnection connectActObject; Connection connectSaveDocument; Connection connectRestDocument; Connection connectStartLoadDocument; Connection connectFinishLoadDocument; Connection connectShowHidden; Connection connectFinishRestoreDocument; Connection connectFinishRestoreObject; Connection connectExportObjects; Connection connectImportObjects; Connection connectFinishImportObjects; Connection connectUndoDocument; Connection connectRedoDocument; Connection connectRecomputed; Connection connectSkipRecompute; Connection connectTransactionAppend; Connection connectTransactionRemove; Connection connectTouchedObject; Connection connectChangePropertyEditor; AdvancedConnection connectChangeDocument; using ConnectionBlock = fastsignals::shared_connection_block; ConnectionBlock connectActObjectBlocker; ConnectionBlock connectChangeDocumentBlocker; static ViewProviderDocumentObject* throwIfCastFails(ViewProvider* p) { if (auto vp = freecad_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 = freecad_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 = freecad_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 = freecad_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 */ /// @namespace Gui @class Document int Document::_iDocCount = 0; Document::Document(App::Document* pcDocument, Application* app) { d = new DocumentP; d->_iWinCount = 1; // new instance d->_iDocId = (++_iDocCount); d->_isClosing = false; d->_isModified = false; d->_isTransacting = false; d->_pcAppWnd = app; d->_pcDocument = pcDocument; d->_editViewProvider = nullptr; d->_editViewProviderPrevious = nullptr; d->_editingObject = nullptr; d->_editViewProviderParent = nullptr; d->_editingViewer = nullptr; d->_editMode = 0; d->_editModePrevious = 0; d->_editWantsRestore = false; d->_editWantsRestorePrevious = false; // NOLINTBEGIN // Setup the connections d->connectNewObject = pcDocument->signalNewObject.connect( std::bind(&Gui::Document::slotNewObject, this, sp::_1) ); d->connectDelObject = pcDocument->signalDeletedObject.connect( std::bind(&Gui::Document::slotDeletedObject, this, sp::_1) ); d->connectCngObject = pcDocument->signalChangedObject.connect( std::bind(&Gui::Document::slotChangedObject, this, sp::_1, sp::_2) ); d->connectRenObject = pcDocument->signalRelabelObject.connect( std::bind(&Gui::Document::slotRelabelObject, this, sp::_1) ); d->connectActObject = pcDocument->signalActivatedObject.connect( std::bind(&Gui::Document::slotActivatedObject, this, sp::_1), fastsignals::advanced_tag() ); d->connectActObjectBlocker = fastsignals::shared_connection_block(d->connectActObject, false); d->connectSaveDocument = pcDocument->signalSaveDocument.connect( std::bind(&Gui::Document::Save, this, sp::_1) ); d->connectRestDocument = pcDocument->signalRestoreDocument.connect( std::bind(&Gui::Document::Restore, this, sp::_1) ); d->connectStartLoadDocument = App::GetApplication().signalStartRestoreDocument.connect( std::bind(&Gui::Document::slotStartRestoreDocument, this, sp::_1) ); d->connectFinishLoadDocument = App::GetApplication().signalFinishRestoreDocument.connect( std::bind(&Gui::Document::slotFinishRestoreDocument, this, sp::_1) ); d->connectShowHidden = App::GetApplication().signalShowHidden.connect( std::bind(&Gui::Document::slotShowHidden, this, sp::_1) ); d->connectChangePropertyEditor = pcDocument->signalChangePropertyEditor.connect( std::bind(&Gui::Document::slotChangePropertyEditor, this, sp::_1, sp::_2) ); d->connectChangeDocument = d->_pcDocument->signalChanged.connect // use the same slot function (std::bind(&Gui::Document::slotChangePropertyEditor, this, sp::_1, sp::_2), fastsignals::advanced_tag()); d->connectChangeDocumentBlocker = fastsignals::shared_connection_block(d->connectChangeDocument, true); d->connectFinishRestoreObject = pcDocument->signalFinishRestoreObject.connect( std::bind(&Gui::Document::slotFinishRestoreObject, this, sp::_1) ); d->connectExportObjects = pcDocument->signalExportViewObjects.connect( std::bind(&Gui::Document::exportObjects, this, sp::_1, sp::_2) ); d->connectImportObjects = pcDocument->signalImportViewObjects.connect( std::bind(&Gui::Document::importObjects, this, sp::_1, sp::_2, sp::_3) ); d->connectFinishImportObjects = pcDocument->signalFinishImportObjects.connect( std::bind(&Gui::Document::slotFinishImportObjects, this, sp::_1) ); d->connectUndoDocument = pcDocument->signalUndo.connect( std::bind(&Gui::Document::slotUndoDocument, this, sp::_1) ); d->connectRedoDocument = pcDocument->signalRedo.connect( std::bind(&Gui::Document::slotRedoDocument, this, sp::_1) ); d->connectRecomputed = pcDocument->signalRecomputed.connect( std::bind(&Gui::Document::slotRecomputed, this, sp::_1) ); d->connectSkipRecompute = pcDocument->signalSkipRecompute.connect( std::bind(&Gui::Document::slotSkipRecompute, this, sp::_1, sp::_2) ); d->connectTouchedObject = pcDocument->signalTouchedObject.connect( std::bind(&Gui::Document::slotTouchedObject, this, sp::_1) ); d->connectTransactionAppend = pcDocument->signalTransactionAppend.connect( std::bind(&Gui::Document::slotTransactionAppend, this, sp::_1, sp::_2) ); d->connectTransactionRemove = pcDocument->signalTransactionRemove.connect( std::bind(&Gui::Document::slotTransactionRemove, this, sp::_1, sp::_2) ); // NOLINTEND pcDocument->setPreRecomputeHook([this] { callSignalBeforeRecompute(); }); // pointer to the python class // NOTE: As this Python object doesn't get returned to the interpreter we // mustn't increment it (Werner Jan-12-2006) Base::PyGILStateLocker lock; _pcDocPy = new Gui::DocumentPy(this); ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath( "User parameter:BaseApp/Preferences/Document" ); if (hGrp->GetBool("UsingUndo", true)) { d->_pcDocument->setUndoMode(1); // set the maximum stack size d->_pcDocument->setMaxUndoStackSize(hGrp->GetInt("MaxUndoSize", 20)); } d->_changeViewTouchDocument = hGrp->GetBool("ChangeViewProviderTouchDocument", true); } Document::~Document() { // disconnect everything to avoid to be double-deleted // in case an exception is raised somewhere d->connectNewObject.disconnect(); d->connectDelObject.disconnect(); d->connectCngObject.disconnect(); d->connectRenObject.disconnect(); d->connectActObject.disconnect(); d->connectSaveDocument.disconnect(); d->connectRestDocument.disconnect(); d->connectStartLoadDocument.disconnect(); d->connectFinishLoadDocument.disconnect(); d->connectShowHidden.disconnect(); d->connectFinishRestoreObject.disconnect(); d->connectExportObjects.disconnect(); d->connectImportObjects.disconnect(); d->connectFinishImportObjects.disconnect(); d->connectUndoDocument.disconnect(); d->connectRedoDocument.disconnect(); d->connectRecomputed.disconnect(); d->connectSkipRecompute.disconnect(); d->connectTransactionAppend.disconnect(); d->connectTransactionRemove.disconnect(); d->connectTouchedObject.disconnect(); d->connectChangePropertyEditor.disconnect(); d->connectChangeDocument.disconnect(); // e.g. if document gets closed from within a Python command d->_isClosing = true; // calls Document::detachView() and alter the view list std::list temp = d->baseViews; for (auto& it : temp) { it->deleteSelf(); } std::map::iterator jt; for (jt = d->_ViewProviderMap.begin(); jt != d->_ViewProviderMap.end(); ++jt) { delete jt->second; } std::map::iterator it2; for (it2 = d->_ViewProviderMapAnnotation.begin(); it2 != d->_ViewProviderMapAnnotation.end(); ++it2) { delete it2->second; } // remove the reference from the object Base::PyGILStateLocker lock; _pcDocPy->setInvalid(); _pcDocPy->DecRef(); delete d; } //***************************************************************************************************** // 3D viewer handling //***************************************************************************************************** bool Document::setEdit(Gui::ViewProvider* p, int ModNum, const char* subname) { 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(); } } View3DInventor* Document::openEditingView3D(const 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) { getMainWindow()->setActiveWindow(view3d); } else { view3d = dynamic_cast(setActiveView(vp)); } return view3d; } View3DInventor* Document::openEditingView3D(const App::DocumentObject* obj) { if (auto vp = freecad_cast(Application::Instance->getViewProvider(obj))) { return openEditingView3D(vp); } return nullptr; } bool Document::trySetEdit(Gui::ViewProvider* p, int ModNum, const char* subname) { auto vp = DocumentP::throwIfCastFails(p); 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) { resetIfEditing(); return vp->getDocument()->setEdit(vp, ModNum, _subname.c_str()); } } } // Fix for #13852: When switching edit directly between sketches, resetIfEditing() // triggers unsetEdit() on the previous sketch which restores its selection. // This clobbers the selection of the new sketch that ParentFinder relies on. // Moving resetIfEditing() after ParentFinder ensures we resolve the parent context correctly // using the current selection before closing the previous edit. resetIfEditing(); d->throwIfNotInMap(obj, getDocument()); Application::Instance->setEditDocument(this); if (!d->tryStartEditing(vp, obj, _subname.c_str(), ModNum)) { d->setDocumentNameOfTaskDialog(getDocument()); return false; } d->setDocumentNameOfTaskDialog(getDocument()); auto view3d = openEditingView3D(vp); d->setEditingViewerIfPossible(view3d, ModNum); d->signalEditMode(); App::AutoTransaction::setEnable(false); return true; } const Base::Matrix4D& Document::getEditingTransform() const { return d->_editingTransform; } void Document::setEditingTransform(const Base::Matrix4D& mat) { d->_editObjs.clear(); d->_editingTransform = mat; auto activeView = dynamic_cast(getActiveView()); if (activeView) { activeView->getViewer()->setEditingTransform(mat); } } void Document::resetEdit() { bool vpIsNotNull = d->_editViewProvider != nullptr; bool vpHasChanged = d->_editViewProvider != d->_editViewProviderPrevious; int modeToRestore = d->_editModePrevious; Gui::ViewProvider* vpToRestore = d->_editViewProviderPrevious; bool shouldRestorePrevious = d->_editWantsRestorePrevious; Application::Instance->setEditDocument(nullptr); if (vpIsNotNull && vpHasChanged && shouldRestorePrevious) { setEdit(vpToRestore, modeToRestore); } } void Document::_resetEdit() { std::list::iterator it; if (d->_editViewProvider) { for (it = d->baseViews.begin(); it != d->baseViews.end(); ++it) { auto activeView = dynamic_cast(*it); if (activeView) { activeView->getViewer()->resetEditingViewProvider(); } } d->_editViewProvider->finishEditing(); d->_editViewProviderPrevious = d->_editViewProvider; d->_editModePrevious = d->_editMode; d->_editWantsRestorePrevious = d->_editWantsRestore; d->_editWantsRestore = false; // Have to check d->_editViewProvider below, because there is a chance // the editing object gets deleted inside the above call to // 'finishEditing()', which will trigger our slotDeletedObject(), which // nullifies _editViewProvider. if (d->_editViewProvider && d->_editViewProvider->isDerivedFrom()) { auto vpd = static_cast(d->_editViewProvider); vpd->getDocument()->signalResetEdit(*vpd); } d->_editViewProvider = nullptr; // The logic below is not necessary anymore, because this method is // changed into a private one, _resetEdit(). And the exposed // resetEdit() above calls into Application->setEditDocument(0) which // will prevent recursive calling. App::GetApplication().closeActiveTransaction(); } d->_editViewProviderParent = nullptr; d->_editingViewer = nullptr; d->_editObjs.clear(); d->_editingObject = nullptr; if (Application::Instance->editDocument() == this) { Application::Instance->setEditDocument(nullptr); } } ViewProvider* Document::getInEdit( ViewProviderDocumentObject** parentVp, std::string* subname, int* mode, std::string* subelement ) const { if (parentVp) { *parentVp = d->_editViewProviderParent; } if (subname) { *subname = d->_editSubname; } if (subelement) { *subelement = d->_editSubElement; } if (mode) { *mode = d->_editMode; } if (d->_editViewProvider) { // there is only one 3d view which is in edit mode auto activeView = dynamic_cast(getActiveView()); if (activeView && activeView->getViewer()->isEditingViewProvider()) { return d->_editViewProvider; } } return nullptr; } void Document::setInEdit(ViewProviderDocumentObject* parentVp, const char* subname) { if (d->_editViewProvider) { d->_editViewProviderParent = parentVp; d->_editSubname = subname ? subname : ""; } } void Document::setAnnotationViewProvider(const char* name, ViewProvider* pcProvider) { std::list::iterator vIt; // already in ? std::map::iterator it = d->_ViewProviderMapAnnotation.find(name); if (it != d->_ViewProviderMapAnnotation.end()) { removeAnnotationViewProvider(name); } // add d->_ViewProviderMapAnnotation[name] = pcProvider; // cycling to all views of the document for (vIt = d->baseViews.begin(); vIt != d->baseViews.end(); ++vIt) { auto activeView = dynamic_cast(*vIt); if (activeView) { activeView->getViewer()->addViewProvider(pcProvider); } } } void Document::setEditRestore(bool askRestore) { d->_editWantsRestore = askRestore; } ViewProvider* Document::getAnnotationViewProvider(const char* name) const { std::map::const_iterator it = d->_ViewProviderMapAnnotation.find(name); return ((it != d->_ViewProviderMapAnnotation.end()) ? it->second : 0); } bool Document::isAnnotationViewProvider(const ViewProvider* vp) const { std::map::const_iterator it; for (it = d->_ViewProviderMapAnnotation.begin(); it != d->_ViewProviderMapAnnotation.end(); ++it) { if (it->second == vp) { return true; } } return false; } ViewProvider* Document::takeAnnotationViewProvider(const char* name) { auto it = d->_ViewProviderMapAnnotation.find(name); if (it == d->_ViewProviderMapAnnotation.end()) { return nullptr; } ViewProvider* vp = it->second; d->_ViewProviderMapAnnotation.erase(it); // cycling to all views of the document for (auto vIt : d->baseViews) { if (auto activeView = dynamic_cast(vIt)) { activeView->getViewer()->removeViewProvider(vp); } } return vp; } void Document::removeAnnotationViewProvider(const char* name) { delete takeAnnotationViewProvider(name); } ViewProvider* Document::getViewProvider(const App::DocumentObject* Feat) const { std::map::const_iterator it = d->_ViewProviderMap.find(Feat); return ((it != d->_ViewProviderMap.end()) ? it->second : 0); } std::vector Document::getViewProvidersOfType(const Base::Type& typeId) const { std::vector Objects; for (std::map::const_iterator it = d->_ViewProviderMap.begin(); it != d->_ViewProviderMap.end(); ++it) { if (it->second->isDerivedFrom(typeId)) { Objects.push_back(it->second); } } return Objects; } ViewProvider* Document::getViewProviderByName(const char* name) const { // first check on feature name App::DocumentObject* pcFeat = getDocument()->getObject(name); if (pcFeat) { std::map::const_iterator it = d->_ViewProviderMap.find(pcFeat); if (it != d->_ViewProviderMap.end()) { return it->second; } } else { // then try annotation name std::map::const_iterator it2 = d->_ViewProviderMapAnnotation.find(name); if (it2 != d->_ViewProviderMapAnnotation.end()) { return it2->second; } } return nullptr; } bool Document::isShow(const char* name) { ViewProvider* pcProv = getViewProviderByName(name); return pcProv ? pcProv->isShow() : false; } /// put the feature in show void Document::setShow(const char* name) { ViewProvider* pcProv = getViewProviderByName(name); if (pcProv && pcProv->isDerivedFrom()) { static_cast(pcProv)->Visibility.setValue(true); } } /// set the feature in Noshow void Document::setHide(const char* name) { ViewProvider* pcProv = getViewProviderByName(name); if (pcProv && pcProv->isDerivedFrom()) { static_cast(pcProv)->Visibility.setValue(false); } } /// set the feature in Noshow void Document::setPos(const char* name, const Base::Matrix4D& rclMtrx) { ViewProvider* pcProv = getViewProviderByName(name); if (pcProv) { pcProv->setTransformation(rclMtrx); } } //***************************************************************************************************** // Document //***************************************************************************************************** void Document::slotNewObject(const App::DocumentObject& Obj) { auto pcProvider = static_cast(getViewProvider(&Obj)); if (!pcProvider) { std::string cName = Obj.getViewProviderNameStored(); for (;;) { if (cName.empty()) { // handle document object with no view provider specified FC_LOG(Obj.getFullName() << " has no view provider specified"); return; } Base::Type type = Base::Type::getTypeIfDerivedFrom( cName.c_str(), ViewProviderDocumentObject::getClassTypeId(), true ); pcProvider = static_cast(type.createInstance()); // createInstance could return a null pointer if (!pcProvider) { // type not derived from ViewProviderDocumentObject!!! FC_ERR("Invalid view provider type '" << cName << "' for " << Obj.getFullName()); return; } else if (cName != Obj.getViewProviderName() && !pcProvider->allowOverride(Obj)) { FC_WARN("View provider type '" << cName << "' does not support " << Obj.getFullName()); delete pcProvider; pcProvider = nullptr; cName = Obj.getViewProviderName(); } else { break; } } setModified(true); d->_ViewProviderMap[&Obj] = pcProvider; d->_CoinMap[pcProvider->getRoot()] = pcProvider; pcProvider->setStatus(Gui::ViewStatus::TouchDocument, d->_changeViewTouchDocument); try { // if successfully created set the right name and calculate the view // FIXME: Consider to change argument of attach() to const pointer pcProvider->attach(const_cast(&Obj)); pcProvider->updateView(); pcProvider->setActiveMode(); } catch (const Base::MemoryException& e) { FC_ERR("Memory exception in " << Obj.getFullName() << " thrown: " << e.what()); } catch (Base::Exception& e) { e.reportException(); } #ifndef FC_DEBUG catch (...) { FC_ERR("Unknown exception in Feature " << Obj.getFullName() << " thrown"); } #endif } else { try { pcProvider->reattach(const_cast(&Obj)); } catch (Base::Exception& e) { e.reportException(); } } if (pcProvider) { std::list::iterator vIt; // cycling to all views of the document for (vIt = d->baseViews.begin(); vIt != d->baseViews.end(); ++vIt) { auto activeView = dynamic_cast(*vIt); if (activeView) { activeView->getViewer()->addViewProvider(pcProvider); } } // adding to the tree signalNewObject(*pcProvider); pcProvider->pcDocument = this; // it is possible that a new viewprovider already claims children handleChildren3D(pcProvider); if (d->_isTransacting) { d->_redoViewProviders.push_back(pcProvider); } } } void Document::slotDeletedObject(const App::DocumentObject& Obj) { std::list::iterator vIt; setModified(true); // cycling to all views of the document ViewProvider* viewProvider = getViewProvider(&Obj); if (!viewProvider) { return; } if (d->_editViewProvider == viewProvider || d->_editViewProviderParent == viewProvider) { _resetEdit(); } else if (Application::Instance->editDocument()) { auto editDoc = Application::Instance->editDocument(); if (editDoc->d->_editViewProvider == viewProvider || editDoc->d->_editViewProviderParent == viewProvider) { Application::Instance->setEditDocument(nullptr); } } handleChildren3D(viewProvider, true); if (viewProvider && viewProvider->isDerivedFrom(ViewProviderDocumentObject::getClassTypeId())) { // go through the views for (vIt = d->baseViews.begin(); vIt != d->baseViews.end(); ++vIt) { auto activeView = dynamic_cast(*vIt); if (activeView) { activeView->getViewer()->removeViewProvider(viewProvider); } } // removing from tree signalDeletedObject(*(static_cast(viewProvider))); } viewProvider->beforeDelete(); } void Document::beforeDelete() { auto editDoc = Application::Instance->editDocument(); if (editDoc) { auto vp = freecad_cast(editDoc->d->_editViewProvider); auto vpp = freecad_cast(editDoc->d->_editViewProviderParent); if (editDoc == this || (vp && vp->getDocument() == this) || (vpp && vpp->getDocument() == this)) { Application::Instance->setEditDocument(nullptr); } } for (auto& v : d->_ViewProviderMap) { v.second->beforeDelete(); } } void Document::slotChangedObject(const App::DocumentObject& Obj, const App::Property& Prop) { ViewProvider* viewProvider = getViewProvider(&Obj); if (viewProvider) { try { viewProvider->update(&Prop); if (d->_editingViewer && d->_editingObject && d->_editViewProviderParent && (Prop.isDerivedFrom() // Issue ID 0004230 : getName() can return null in which case strstr() crashes || (Prop.getName() && strstr(Prop.getName(), "Scale"))) && d->_editObjs.contains(&Obj)) { Base::Matrix4D mat; auto sobj = d->_editViewProviderParent->getObject() ->getSubObject(d->_editSubname.c_str(), nullptr, &mat); if (sobj == d->_editingObject && d->_editingTransform != mat) { d->_editingTransform = mat; d->_editingViewer->setEditingTransform(d->_editingTransform); } } } catch (const Base::MemoryException& e) { FC_ERR("Memory exception in " << Obj.getFullName() << " thrown: " << e.what()); } catch (Base::Exception& e) { e.reportException(); } catch (const std::exception& e) { FC_ERR("C++ exception in " << Obj.getFullName() << " thrown " << e.what()); } catch (...) { FC_ERR("Cannot update representation for " << Obj.getFullName()); } handleChildren3D(viewProvider); if (viewProvider->isDerivedFrom()) { signalChangedObject(static_cast(*viewProvider), Prop); } } // a property of an object has changed if (!Prop.testStatus(App::Property::NoModify) && !isModified()) { FC_LOG(Prop.getFullName() << " modified"); setModified(true); } getMainWindow()->updateActions(true); } void Document::slotRelabelObject(const App::DocumentObject& Obj) { ViewProvider* viewProvider = getViewProvider(&Obj); if (viewProvider && viewProvider->isDerivedFrom()) { signalRelabelObject(*(static_cast(viewProvider))); } } void Document::slotTransactionAppend(const App::DocumentObject& obj, App::Transaction* transaction) { ViewProvider* viewProvider = getViewProvider(&obj); if (viewProvider && viewProvider->isDerivedFrom()) { transaction->addObjectDel(viewProvider); } } void Document::slotTransactionRemove(const App::DocumentObject& obj, App::Transaction* transaction) { std::map::const_iterator it = d->_ViewProviderMap.find(&obj); if (it != d->_ViewProviderMap.end()) { ViewProvider* viewProvider = it->second; auto itC = d->_CoinMap.find(viewProvider->getRoot()); if (itC != d->_CoinMap.end()) { d->_CoinMap.erase(itC); } d->_ViewProviderMap.erase(&obj); // transaction being a nullptr indicates that undo/redo is off and the object // can be safely deleted if (transaction) { transaction->addObjectNew(viewProvider); } else { delete viewProvider; } } } void Document::slotActivatedObject(const App::DocumentObject& Obj) { ViewProvider* viewProvider = getViewProvider(&Obj); if (viewProvider && viewProvider->isDerivedFrom()) { signalActivatedObject(*(static_cast(viewProvider))); } } void Document::slotUndoDocument(const App::Document& doc) { if (d->_pcDocument != &doc) { return; } signalUndoDocument(*this); getMainWindow()->updateActions(); } void Document::slotRedoDocument(const App::Document& doc) { if (d->_pcDocument != &doc) { return; } signalRedoDocument(*this); getMainWindow()->updateActions(); } void Document::slotRecomputed(const App::Document& doc) { if (d->_pcDocument != &doc) { return; } getMainWindow()->updateActions(); TreeWidget::updateStatus(); } // This function is called when some asks to recompute a document that is marked // as 'SkipRecompute'. We'll check if we are the current document, and if either // not given an explicit recomputing object list, or the given single object is // the eidting object or the active object. If the conditions are met, we'll // force recompute only that object and all its dependent objects. void Document::slotSkipRecompute(const App::Document& doc, const std::vector& objs) { if (d->_pcDocument != &doc) { return; } if (objs.size() > 1 || App::GetApplication().getActiveDocument() != &doc || !doc.testStatus(App::Document::AllowPartialRecompute)) { return; } App::DocumentObject* obj = nullptr; auto editDoc = Application::Instance->editDocument(); if (editDoc) { auto vp = freecad_cast(editDoc->getInEdit()); if (vp) { obj = vp->getObject(); } } if (!obj) { obj = doc.getActiveObject(); } if (!obj || !obj->isAttachedToDocument() || (!objs.empty() && objs.front() != obj)) { return; } obj->recomputeFeature(true); } void Document::slotTouchedObject(const App::DocumentObject& Obj) { getMainWindow()->updateActions(true); if (!isModified()) { FC_LOG(Obj.getFullName() << " touched"); setModified(true); } } // helper that guarantees signalBeforeRecompute call is executed in the GUI thread and // that the worker waits until it finishes void Document::callSignalBeforeRecompute() { auto invokeSignalBeforeRecompute = [this] { // this runs in the GUI thread this->getDocument()->signalBeforeRecompute(*this->getDocument()); }; if (QThread::currentThread() == qApp->thread()) { // already on GUI thread – no hop, just call it invokeSignalBeforeRecompute(); } else { // hop to GUI and *block* until it returns QMetaObject::invokeMethod( qApp, std::move(invokeSignalBeforeRecompute), Qt::BlockingQueuedConnection ); } } void Document::addViewProvider(Gui::ViewProviderDocumentObject* vp) { // Hint: The undo/redo first adds the view provider to the Gui // document before adding the objects to the App document. // the view provider is added by TransactionViewProvider and an // object can be there only once assert(d->_ViewProviderMap.find(vp->getObject()) == d->_ViewProviderMap.end()); vp->setStatus(Detach, false); d->_ViewProviderMap[vp->getObject()] = vp; d->_CoinMap[vp->getRoot()] = vp; } void Document::setModified(bool b) { if (d->_isModified == b) { return; } d->_isModified = b; std::list mdis = getMDIViews(); for (auto& mdi : mdis) { mdi->setWindowModified(b); } } bool Document::isModified() const { return d->_isModified; } bool Document::isAboutToClose() const { return d->_isClosing; } ViewProviderDocumentObject* Document::getViewProviderByPathFromTail(SoPath* path) const { // Get the lowest root node in the pick path! for (int i = 0; i < path->getLength(); i++) { SoNode* node = path->getNodeFromTail(i); if (node->isOfType(SoSeparator::getClassTypeId())) { auto it = d->_CoinMap.find(static_cast(node)); if (it != d->_CoinMap.end()) { return it->second; } } } return nullptr; } ViewProviderDocumentObject* Document::getViewProviderByPathFromHead(SoPath* path) const { for (int i = 0; i < path->getLength(); i++) { SoNode* node = path->getNode(i); if (node->isOfType(SoSeparator::getClassTypeId())) { auto it = d->_CoinMap.find(static_cast(node)); if (it != d->_CoinMap.end()) { return it->second; } } } return nullptr; } ViewProviderDocumentObject* Document::getViewProvider(SoNode* node) const { if (!node || !node->isOfType(SoSeparator::getClassTypeId())) { return nullptr; } auto it = d->_CoinMap.find(static_cast(node)); if (it != d->_CoinMap.end()) { return it->second; } return nullptr; } std::vector> Document::getViewProvidersByPath( SoPath* path ) const { std::vector> ret; for (int i = 0; i < path->getLength(); i++) { SoNode* node = path->getNodeFromTail(i); if (node->isOfType(SoSeparator::getClassTypeId())) { auto it = d->_CoinMap.find(static_cast(node)); if (it != d->_CoinMap.end()) { ret.emplace_back(it->second, i); } } } return ret; } App::Document* Document::getDocument() const { return d->_pcDocument; } static bool checkCanonicalPath(const std::map& docs) { std::map> paths; bool warn = false; for (auto doc : App::GetApplication().getDocuments()) { QFileInfo info(QString::fromUtf8(doc->FileName.getValue())); auto& d = paths[info.canonicalFilePath()]; d.push_back(doc); if (!warn && d.size() > 1) { if (docs.contains(d.front()) || docs.contains(d.back())) { warn = true; } } } if (!warn) { return true; } QString msg; QTextStream ts(&msg); ts << QObject::tr("Identical physical path detected. It may cause unwanted overwrite of existing document!\n\n") << QObject::tr("Are you sure you want to continue?"); auto docName = [](App::Document* doc) -> QString { if (doc->Label.getStrValue() == doc->getName()) { return QString::fromUtf8(doc->getName()); } return QStringLiteral("%1 (%2)").arg( QString::fromUtf8(doc->Label.getValue()), QString::fromUtf8(doc->getName()) ); }; int count = 0; for (auto& v : paths) { if (v.second.size() <= 1) { continue; } for (auto doc : v.second) { if (docs.contains(doc)) { FC_WARN("Physical path: " << v.first.toUtf8().constData()); for (auto d : v.second) { FC_WARN( " Document: " << docName(d).toUtf8().constData() << ": " << d->FileName.getValue() ); } if (count == 3) { ts << "\n\n" << QObject::tr("Check report view for more…"); } else if (count < 3) { ts << "\n\n" << QObject::tr("Physical path:") << ' ' << v.first << "\n" << QObject::tr("Document:") << ' ' << docName(doc) << "\n " << QObject::tr("Path:") << ' ' << QString::fromUtf8(doc->FileName.getValue()); for (auto d : v.second) { if (d == doc) { continue; } ts << "\n" << QObject::tr("Document:") << ' ' << docName(d) << "\n " << QObject::tr("Path:") << ' ' << QString::fromUtf8(d->FileName.getValue()); } } ++count; break; } } } int ret = QMessageBox::warning( getMainWindow(), QObject::tr("Identical physical path"), msg, QMessageBox::Yes, QMessageBox::No ); return ret == QMessageBox::Yes; } bool Document::askIfSavingFailed(const QString& error) { int ret = QMessageBox::question( getMainWindow(), QObject::tr("Could not save document"), QObject::tr( "There was an issue trying to save the file. " "This may be because some of the parent folders do not exist, " "or you do not have sufficient permissions, " "or for other reasons. Error details:\n\n\"%1\"\n\n" "Would you like to save the file with a different name?" ) .arg(error), QMessageBox::Yes, QMessageBox::No ); if (ret == QMessageBox::No) { // TODO: Understand what exactly is supposed to be returned here getMainWindow()->showMessage(QObject::tr("Saving aborted"), 2000); return false; } else if (ret == QMessageBox::Yes) { return saveAs(); } return false; } /// Save the document bool Document::save() { if (d->_pcDocument->isSaved()) { try { std::vector docs; std::map dmap; try { docs = getDocument()->getDependentDocuments(); for (auto it = docs.begin(); it != docs.end();) { App::Document* doc = *it; if (doc == getDocument()) { dmap[doc] = doc->mustExecute(); ++it; continue; } auto gdoc = Application::Instance->getDocument(doc); if ((gdoc && !gdoc->isModified()) || doc->testStatus(App::Document::PartialDoc) || doc->testStatus(App::Document::TempDoc)) { it = docs.erase(it); continue; } dmap[doc] = doc->mustExecute(); ++it; } } catch (const Base::RuntimeError& e) { FC_ERR(e.what()); docs = {getDocument()}; dmap.clear(); dmap[getDocument()] = getDocument()->mustExecute(); } if (docs.size() > 1) { int ret = QMessageBox::question( getMainWindow(), QObject::tr("Save dependent files"), QObject::tr( "The file contains external dependencies. " "Do you want to save the dependent files, too?" ), QMessageBox::Yes, QMessageBox::No ); if (ret != QMessageBox::Yes) { docs = {getDocument()}; dmap.clear(); dmap[getDocument()] = getDocument()->mustExecute(); } } if (!checkCanonicalPath(dmap)) { return false; } Gui::WaitCursor wc; // save all documents for (auto doc : docs) { // Changed 'mustExecute' status may be triggered by saving external document if (!dmap[doc] && doc->mustExecute()) { App::AutoTransaction trans("Recompute"); Command::doCommand( Command::Doc, "App.getDocument(\"%s\").recompute()", doc->getName() ); } Command::doCommand(Command::Doc, "App.getDocument(\"%s\").save()", doc->getName()); auto gdoc = Application::Instance->getDocument(doc); if (gdoc) { gdoc->setModified(false); } } } catch (const Base::FileException& e) { e.reportException(); return askIfSavingFailed(QString::fromUtf8(e.what())); } catch (const Base::Exception& e) { QMessageBox::critical( getMainWindow(), QObject::tr("Saving document failed"), QString::fromLatin1(e.what()) ); return false; } return true; } else { return saveAs(); } } /// Save the document under a new file name bool Document::saveAs() { getMainWindow()->showMessage(QObject::tr("Save document under new filename…")); QString exe = qApp->applicationName(); QString name = QString::fromUtf8(getDocument()->FileName.getValue()); if (name.isEmpty()) { name = QString::fromUtf8(getDocument()->Label.getValue()); } QString fn = FileDialog::getSaveFileName( getMainWindow(), QObject::tr("Save %1 Document").arg(exe), name, QStringLiteral("%1 %2 (*.FCStd *.kc)").arg(exe, QObject::tr("Document")) ); if (!fn.isEmpty()) { QFileInfo fi; fi.setFile(fn); const char* DocName = App::GetApplication().getDocumentName(getDocument()); // save as new file name try { Gui::WaitCursor wc; std::string escapedstr = Base::Tools::escapedUnicodeFromUtf8(fn.toUtf8()); escapedstr = Base::Tools::escapeEncodeFilename(escapedstr); Command::doCommand( Command::Doc, "App.getDocument(\"%s\").saveAs(u\"%s\")", DocName, escapedstr.c_str() ); // App::Document::saveAs() may modify the passed file name fi.setFile(QString::fromUtf8(d->_pcDocument->FileName.getValue())); setModified(false); getMainWindow()->appendRecentFile(fi.filePath()); } catch (const Base::FileException& e) { e.reportException(); return askIfSavingFailed(QString::fromUtf8(e.what())); } catch (const Base::Exception& e) { QMessageBox::critical( getMainWindow(), QObject::tr("Saving document failed"), QString::fromLatin1(e.what()) ); } return true; } else { getMainWindow()->showMessage(QObject::tr("Saving aborted"), 2000); return false; } } void Document::saveAll() { std::vector docs; try { docs = App::Document::getDependentDocuments(App::GetApplication().getDocuments(), true); } catch (Base::Exception& e) { e.reportException(); int ret = QMessageBox::critical( getMainWindow(), QObject::tr("Failed to save document"), QObject::tr("Documents contains cyclic dependencies. Do you still want to save them?"), QMessageBox::Yes, QMessageBox::No ); if (ret != QMessageBox::Yes) { return; } docs = App::GetApplication().getDocuments(); } std::map dmap; for (auto doc : docs) { if (doc->testStatus(App::Document::PartialDoc) || doc->testStatus(App::Document::TempDoc)) { continue; } dmap[doc] = doc->mustExecute(); } if (!checkCanonicalPath(dmap)) { return; } for (auto doc : docs) { if (doc->testStatus(App::Document::PartialDoc) || doc->testStatus(App::Document::TempDoc)) { continue; } auto gdoc = Application::Instance->getDocument(doc); if (!gdoc) { continue; } if (!doc->isSaved()) { if (!gdoc->saveAs()) { break; } } Gui::WaitCursor wc; try { // Changed 'mustExecute' status may be triggered by saving external document if (!dmap[doc] && doc->mustExecute()) { App::AutoTransaction trans("Recompute"); Command::doCommand(Command::Doc, "App.getDocument('%s').recompute()", doc->getName()); } Command::doCommand(Command::Doc, "App.getDocument('%s').save()", doc->getName()); gdoc->setModified(false); } catch (const Base::Exception& e) { QMessageBox::critical( getMainWindow(), QObject::tr("Failed to save document") + QStringLiteral(": %1").arg(QString::fromUtf8(doc->getName())), QString::fromLatin1(e.what()) ); break; } } } /// Save a copy of the document under a new file name bool Document::saveCopy() { getMainWindow()->showMessage(QObject::tr("Save a copy of the document under new filename…")); QString exe = qApp->applicationName(); QString fn = FileDialog::getSaveFileName( getMainWindow(), QObject::tr("Save %1 Document").arg(exe), QString::fromUtf8(getDocument()->FileName.getValue()), QObject::tr("%1 document (*.FCStd *.kc)").arg(exe) ); if (!fn.isEmpty()) { const char* DocName = App::GetApplication().getDocumentName(getDocument()); // save as new file name Gui::WaitCursor wc; QString pyfn = Base::Tools::escapeEncodeFilename(fn); Command::doCommand( Command::Doc, "App.getDocument(\"%s\").saveCopy(\"%s\")", DocName, (const char*)pyfn.toUtf8() ); return true; } else { getMainWindow()->showMessage(QObject::tr("Saving aborted"), 2000); return false; } } unsigned int Document::getMemSize() const { unsigned int size = 0; // size of the view providers in the document std::map::const_iterator it; for (it = d->_ViewProviderMap.begin(); it != d->_ViewProviderMap.end(); ++it) { size += it->second->getMemSize(); } return size; } /** * Adds a separate XML file to the projects file that contains information about the view providers. */ void Document::Save(Base::Writer& writer) const { // It's only possible to add extra information if force of XML is disabled if (!writer.isForceXML()) { writer.addFile("GuiDocument.xml", this); ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath( "User parameter:BaseApp/Preferences/Document" ); if (hGrp->GetBool("SaveThumbnail", true)) { int size = hGrp->GetInt("ThumbnailSize", 256); size = Base::clamp(size, 64, 512); std::list mdi = getMDIViews(); View3DInventorViewer* view = nullptr; for (const auto& it : mdi) { if (it->isDerivedFrom()) { view = static_cast(it)->getViewer(); break; } } d->thumb.setFileName(d->_pcDocument->FileName.getValue()); d->thumb.setSize(size); d->thumb.setViewer(view); d->thumb.Save(writer); } } } /** * Loads a separate XML file from the projects file with information about the view providers. */ void Document::Restore(Base::XMLReader& reader) { reader.addFile("GuiDocument.xml", this); // hide all elements to avoid to update the 3d view when loading data files // RestoreDocFile then restores the visibility status again std::map::iterator it; for (it = d->_ViewProviderMap.begin(); it != d->_ViewProviderMap.end(); ++it) { it->second->startRestoring(); it->second->setStatus(Gui::isRestoring, true); } } /** * Restores the properties of the view providers. */ void Document::RestoreDocFile(Base::Reader& reader) { // We must create an XML parser to read from the input stream std::shared_ptr localreader = std::make_shared("GuiDocument.xml", reader); localreader->FileVersion = reader.getFileVersion(); localreader->readElement("Document"); long scheme = localreader->getAttribute("SchemaVersion"); localreader->DocumentSchema = scheme; localreader->ProgramVersion = d->_pcDocument->getProgramVersion(); bool hasExpansion = localreader->hasAttribute("HasExpansion"); if (hasExpansion) { auto tree = TreeWidget::instance(); if (tree) { auto docItem = tree->getDocumentItem(this); if (docItem) { docItem->Restore(*localreader); } } } // At this stage all the document objects and their associated view providers exist. // Now we must restore the properties of the view providers only. // // SchemeVersion "1" if (scheme == 1) { // read the viewproviders itself localreader->readElement("ViewProviderData"); int Cnt = localreader->getAttribute("Count"); for (int i = 0; i < Cnt; i++) { localreader->readElement("ViewProvider"); std::string name = localreader->getAttribute("name"); bool expanded = false; if (!hasExpansion && localreader->hasAttribute("expanded")) { const char* attr = localreader->getAttribute("expanded"); if (strcmp(attr, "1") == 0) { expanded = true; } } int treeRank = -1; if (localreader->hasAttribute("treeRank")) { treeRank = localreader->getAttribute("treeRank"); } auto pObj = freecad_cast(getViewProviderByName(name.c_str())); // check if this feature has been registered if (pObj) { pObj->Restore(*localreader); } if (pObj && treeRank >= 0) { pObj->setTreeRank(treeRank); } if (pObj && expanded) { this->signalExpandObject(*pObj, TreeItemMode::ExpandItem, 0, 0); } localreader->readEndElement("ViewProvider"); } localreader->readEndElement("ViewProviderData"); // read camera settings localreader->readElement("Camera"); const char* ppReturn = localreader->getAttribute("settings"); cameraSettings.clear(); if (!Base::Tools::isNullOrEmpty(ppReturn)) { saveCameraSettings(ppReturn); try { const char** pReturnIgnore = nullptr; std::list mdi = getMDIViews(); for (const auto& it : mdi) { if (it->onHasMsg("SetCamera")) { it->onMsg(cameraSettings.c_str(), pReturnIgnore); } } } catch (const Base::Exception& e) { Base::Console().error("%s\n", e.what()); } } } reader.initLocalReader(localreader); // reset modified flag setModified(false); } void Document::slotStartRestoreDocument(const App::Document& doc) { if (d->_pcDocument != &doc) { return; } // disable this signal while loading a document d->connectActObjectBlocker.block(); } void Document::slotFinishRestoreObject(const App::DocumentObject& obj) { auto vpd = freecad_cast(getViewProvider(&obj)); if (vpd) { vpd->setStatus(Gui::isRestoring, false); vpd->finishRestoring(); if (!vpd->canAddToSceneGraph()) { toggleInSceneGraph(vpd); } } } void Document::slotFinishRestoreDocument(const App::Document& doc) { if (d->_pcDocument != &doc) { return; } d->connectActObjectBlocker.unblock(); App::DocumentObject* act = doc.getActiveObject(); if (act) { ViewProvider* viewProvider = getViewProvider(act); if (viewProvider && viewProvider->isDerivedFrom()) { signalActivatedObject(*(static_cast(viewProvider))); } } // reset modified flag setModified(doc.testStatus(App::Document::LinkStampChanged)); } void Document::slotShowHidden(const App::Document& doc) { if (d->_pcDocument != &doc) { return; } Application::Instance->signalShowHidden(*this); } /** * Saves the properties of the view providers. */ void Document::SaveDocFile(Base::Writer& writer) const { writer.Stream() << "" << std::endl << "" << std::endl; writer.Stream() << "getDocumentItem(this); if (docItem) { hasExpansion = true; writer.Stream() << " HasExpansion=\"1\">" << std::endl; docItem->Save(writer); } } if (!hasExpansion) { writer.Stream() << ">" << std::endl; } // writing the view provider names itself writer.Stream() << writer.ind() << "_ViewProviderMap.size() << "\">" << std::endl; bool xml = writer.isForceXML(); // writer.setForceXML(true); writer.incInd(); // indentation for 'ViewProvider name' for (const auto& it : d->_ViewProviderMap) { const App::DocumentObject* doc = it.first; ViewProviderDocumentObject* obj = it.second; writer.Stream() << writer.ind() << "getNameInDocument() << "\"" << " expanded=\"" << (doc->testStatus(App::Expand) ? 1 : 0) << "\"" << " treeRank=\"" << obj->getTreeRank() << "\""; if (obj->hasExtensions()) { writer.Stream() << " Extensions=\"True\""; } writer.Stream() << ">" << std::endl; obj->Save(writer); writer.Stream() << writer.ind() << "" << std::endl; } writer.setForceXML(xml); writer.decInd(); // indentation for 'ViewProvider name' writer.Stream() << writer.ind() << "" << std::endl; writer.decInd(); // indentation for 'ViewProviderData Count' // save camera settings std::list mdi = getMDIViews(); for (const auto& it : mdi) { if (it->onHasMsg("GetCamera")) { const char* ppReturn = nullptr; it->onMsg("GetCamera", &ppReturn); if (saveCameraSettings(ppReturn)) { break; } } } writer.incInd(); // indentation for camera settings writer.Stream() << writer.ind() << "\n"; writer.decInd(); // indentation for camera settings writer.Stream() << "" << std::endl; } void Document::exportObjects(const std::vector& obj, Base::Writer& writer) { writer.Stream() << "" << std::endl; writer.Stream() << "" << std::endl; std::map views; for (const auto& it : obj) { Document* doc = Application::Instance->getDocument(it->getDocument()); if (doc) { ViewProvider* vp = doc->getViewProvider(it); if (vp) { views[it] = vp; } } } // writing the view provider names itself writer.incInd(); // indentation for 'ViewProviderData Count' writer.Stream() << writer.ind() << "" << std::endl; bool xml = writer.isForceXML(); // writer.setForceXML(true); writer.incInd(); // indentation for 'ViewProvider name' std::map::const_iterator jt; for (jt = views.begin(); jt != views.end(); ++jt) { const App::DocumentObject* doc = jt->first; ViewProvider* vp = jt->second; writer.Stream() << writer.ind() << "getExportName() << "\" " << "expanded=\"" << (doc->testStatus(App::Expand) ? 1 : 0) << "\""; if (vp->hasExtensions()) { writer.Stream() << " Extensions=\"True\""; } writer.Stream() << ">" << std::endl; vp->Save(writer); writer.Stream() << writer.ind() << "" << std::endl; } writer.setForceXML(xml); writer.decInd(); // indentation for 'ViewProvider name' writer.Stream() << writer.ind() << "" << std::endl; writer.decInd(); // indentation for 'ViewProviderData Count' writer.incInd(); // indentation for camera settings writer.Stream() << writer.ind() << "" << std::endl; writer.decInd(); // indentation for camera settings writer.Stream() << "" << std::endl; } void Document::importObjects( const std::vector& obj, Base::Reader& reader, const std::map& nameMapping ) { // We must create an XML parser to read from the input stream std::shared_ptr localreader = std::make_shared("GuiDocument.xml", reader); localreader->readElement("Document"); long scheme = localreader->getAttribute("SchemaVersion"); // At this stage all the document objects and their associated view providers exist. // Now we must restore the properties of the view providers only. // // SchemeVersion "1" if (scheme == 1) { // read the viewproviders itself localreader->readElement("ViewProviderData"); int Cnt = localreader->getAttribute("Count"); auto it = obj.begin(); for (int i = 0; i < Cnt && it != obj.end(); ++i, ++it) { // The stored name usually doesn't match with the current name anymore // thus we try to match by type. This should work because the order of // objects should not have changed localreader->readElement("ViewProvider"); std::string name = localreader->getAttribute("name"); auto jt = nameMapping.find(name); if (jt != nameMapping.end()) { name = jt->second; } bool expanded = false; if (localreader->hasAttribute("expanded")) { const char* attr = localreader->getAttribute("expanded"); if (strcmp(attr, "1") == 0) { expanded = true; } } Gui::ViewProvider* pObj = this->getViewProviderByName(name.c_str()); if (pObj) { pObj->setStatus(Gui::isRestoring, true); auto vpd = freecad_cast(pObj); if (vpd) { vpd->startRestoring(); } pObj->Restore(*localreader); if (expanded && vpd) { this->signalExpandObject(*vpd, TreeItemMode::ExpandItem, 0, 0); } } localreader->readEndElement("ViewProvider"); if (it == obj.end()) { break; } } localreader->readEndElement("ViewProviderData"); } localreader->readEndElement("Document"); // In the file GuiDocument.xml new data files might be added if (localreader->hasFilenames()) { reader.initLocalReader(localreader); } } void Document::slotFinishImportObjects(const std::vector& objs) { (void)objs; // finishRestoring() is now triggered by signalFinishRestoreObject // // for(auto obj : objs) { // auto vp = getViewProvider(obj); // if(!vp) continue; // vp->setStatus(Gui::isRestoring,false); // auto vpd = freecad_cast(vp); // if(vpd) vpd->finishRestoring(); // } } void Document::addRootObjectsToGroup( const std::vector& obj, App::DocumentObjectGroup* grp ) { std::map rootMap; for (const auto it : obj) { rootMap[it] = true; } // get the view providers and check which objects are children for (const auto it : obj) { Gui::ViewProvider* vp = getViewProvider(it); if (vp) { std::vector child = vp->claimChildren(); for (const auto& jt : child) { auto kt = rootMap.find(jt); if (kt != rootMap.end()) { kt->second = false; } } } } // all objects that are not children of other objects can be added to the group for (const auto& it : rootMap) { if (it.second) { grp->addObject(it.first); } } } MDIView* Document::createView(const Base::Type& typeId, CreateViewMode mode) { if (!typeId.isDerivedFrom(MDIView::getClassTypeId())) { return nullptr; } std::list theViews = this->getMDIViewsOfType(typeId); if (typeId == View3DInventor::getClassTypeId()) { QOpenGLWidget* shareWidget = nullptr; // VBO rendering doesn't work correctly when we don't share the OpenGL widgets if (!theViews.empty()) { auto firstView = static_cast(theViews.front()); shareWidget = qobject_cast(firstView->getViewer()->getGLWidget()); const char* ppReturn = nullptr; firstView->onMsg("GetCamera", &ppReturn); saveCameraSettings(ppReturn); } auto view3D = new View3DInventor(this, getMainWindow(), shareWidget); if (!theViews.empty()) { auto firstView = static_cast(theViews.front()); std::string overrideMode = firstView->getViewer()->getOverrideMode(); view3D->getViewer()->setOverrideMode(overrideMode); } // attach the viewproviders. we need to make sure that we only attach the toplevel ones // and not viewproviders which are claimed by other providers. To ensure this we first // add all providers and then remove the ones already claimed std::map::const_iterator It1; std::vector child_vps; for (It1 = d->_ViewProviderMap.begin(); It1 != d->_ViewProviderMap.end(); ++It1) { view3D->getViewer()->addViewProvider(It1->second); std::vector children = It1->second->claimChildren3D(); child_vps.insert(child_vps.end(), children.begin(), children.end()); } std::map::const_iterator It2; for (It2 = d->_ViewProviderMapAnnotation.begin(); It2 != d->_ViewProviderMapAnnotation.end(); ++It2) { view3D->getViewer()->addViewProvider(It2->second); std::vector children = It2->second->claimChildren3D(); child_vps.insert(child_vps.end(), children.begin(), children.end()); } for (App::DocumentObject* obj : child_vps) { view3D->getViewer()->removeViewProvider(getViewProvider(obj)); } // When cloning the view, don't increment the window counter as the old view will be deleted // shortly after. if (mode != CreateViewMode::Clone) { const char* name = getDocument()->Label.getValue(); QString title = QStringLiteral("%1 : %2[*]").arg(QString::fromUtf8(name)).arg(d->_iWinCount++); view3D->setWindowTitle(title); } view3D->setWindowModified(this->isModified()); view3D->resize(400, 300); if (!cameraSettings.empty()) { const char* ppReturn = nullptr; view3D->onMsg(cameraSettings.c_str(), &ppReturn); } // When cloning the view, don't add the view to the main window. The whole purpose of the // workaround using cloned views is that the view can be shown in undocked/fullscreen mode // without having been docked before. if (mode != CreateViewMode::Clone) { getMainWindow()->addWindow(view3D); } view3D->getViewer()->redraw(); return view3D; } return nullptr; } const char* Document::getCameraSettings() const { return cameraSettings.size() > 10 ? cameraSettings.c_str() + 10 : cameraSettings.c_str(); } bool Document::saveCameraSettings(const char* settings) const { if (!settings) { return false; } // skip starting comment lines bool skipping = false; char c = *settings; for (; c; c = *(++settings)) { if (skipping) { if (c == '\n') { skipping = false; } } else if (c == '#') { skipping = true; } else if (!std::isspace(c)) { break; } } if (!c) { return false; } cameraSettings = std::string("SetCamera ") + settings; return true; } void Document::attachView(Gui::BaseView* pcView, bool bPassiv) { if (!bPassiv) { d->baseViews.push_back(pcView); } else { d->passiveViews.push_back(pcView); } } void Document::detachView(Gui::BaseView* pcView, bool bPassiv) { if (bPassiv) { if (find(d->passiveViews.begin(), d->passiveViews.end(), pcView) != d->passiveViews.end()) { d->passiveViews.remove(pcView); } } else { if (find(d->baseViews.begin(), d->baseViews.end(), pcView) != d->baseViews.end()) { d->baseViews.remove(pcView); } // last view? if (d->baseViews.empty()) { // decouple a passive view std::list::iterator it = d->passiveViews.begin(); while (it != d->passiveViews.end()) { (*it)->setDocument(nullptr); it = d->passiveViews.begin(); } // is already closing the document, and is not linked by other documents if (!d->_isClosing && App::PropertyXLink::getDocumentInList(getDocument()).empty()) { d->_pcAppWnd->onLastWindowClosed(this); } } } } void Document::onUpdate() { #ifdef FC_LOGUPDATECHAIN Base::Console().log("Acti: Gui::Document::onUpdate()"); #endif std::list::iterator it; for (it = d->baseViews.begin(); it != d->baseViews.end(); ++it) { (*it)->onUpdate(); } for (it = d->passiveViews.begin(); it != d->passiveViews.end(); ++it) { (*it)->onUpdate(); } } void Document::onRelabel() { #ifdef FC_LOGUPDATECHAIN Base::Console().log("Acti: Gui::Document::onRelabel()"); #endif std::list::iterator it; for (it = d->baseViews.begin(); it != d->baseViews.end(); ++it) { (*it)->onRelabel(this); } for (it = d->passiveViews.begin(); it != d->passiveViews.end(); ++it) { (*it)->onRelabel(this); } d->connectChangeDocumentBlocker.unblock(); } bool Document::isLastView() { if (d->baseViews.size() <= 1) { return true; } return false; } /** * This method checks if the document can be closed. It checks on * the save state of the document and is able to abort the closing. */ bool Document::canClose(bool checkModify, bool checkLink) { if (d->_isClosing) { return true; } if (!getDocument()->isClosable()) { QMessageBox::warning( getActiveView(), QObject::tr("Document not closable"), QObject::tr("The document is not closable for the moment.") ); return false; } // else if (!Gui::Control().isAllowedAlterDocument()) { // std::string name = Gui::Control().activeDialog()->getDocumentName(); // if (name == this->getDocument()->getName()) { // QMessageBox::warning(getActiveView(), // QObject::tr("Document not closable"), // QObject::tr("The document is in editing mode and thus cannot be closed for the // moment.\n" // "You either have to finish or cancel the editing in the task panel.")); // Gui::TaskView::TaskDialog* dlg = Gui::Control().activeDialog(); // if (dlg) Gui::Control().showDialog(dlg); // return false; // } // } if (checkLink && !App::PropertyXLink::getDocumentInList(getDocument()).empty()) { return true; } if (getDocument()->testStatus(App::Document::TempDoc)) { return true; } bool ok = true; if (checkModify && isModified() && !getDocument()->testStatus(App::Document::PartialDoc)) { int res = getMainWindow()->confirmSave(getDocument(), getActiveView()); switch (res) { case MainWindow::ConfirmSaveResult::Cancel: ok = false; break; case MainWindow::ConfirmSaveResult::SaveAll: case MainWindow::ConfirmSaveResult::Save: ok = save(); if (!ok) { const QString docName = QString::fromStdString(getDocument()->Label.getStrValue()); const QString text = (!docName.isEmpty() ? QObject::tr("Failed to save document '%1'. Would you like to cancel the closure?") .arg(docName) : QObject::tr( "Document saving failed. Would you like to cancel the closure?" )); int ret = QMessageBox::question( getActiveView(), QObject::tr("Unable to save document"), text, QMessageBox::Discard | QMessageBox::Cancel, QMessageBox::Discard ); if (ret == QMessageBox::Discard) { ok = true; } } break; case MainWindow::ConfirmSaveResult::DiscardAll: case MainWindow::ConfirmSaveResult::Discard: ok = true; break; } } if (ok) { // If a task dialog is open that doesn't allow other commands to modify // the document it must be closed by resetting the edit mode of the // corresponding view provider. if (!Gui::Control().isAllowedAlterDocument()) { std::string name = Gui::Control().activeDialog()->getDocumentName(); if (name == this->getDocument()->getName()) { // getInEdit() only checks if the currently active MDI view is // a 3D view and that it is in edit mode. However, when closing a // document then the edit mode must be reset independent of the // active view. if (d->_editViewProvider) { this->_resetEdit(); } } } } return ok; } std::list Document::getMDIViews() const { std::list views; for (std::list::const_iterator it = d->baseViews.begin(); it != d->baseViews.end(); ++it) { auto view = dynamic_cast(*it); if (view) { views.push_back(view); } } return views; } std::list Document::getMDIViewsOfType(const Base::Type& typeId) const { std::list views; for (std::list::const_iterator it = d->baseViews.begin(); it != d->baseViews.end(); ++it) { auto view = dynamic_cast(*it); if (view && view->isDerivedFrom(typeId)) { views.push_back(view); } } return views; } /// send messages to the active view bool Document::sendMsgToViews(const char* pMsg) { std::list::iterator it; const char** pReturnIgnore = nullptr; for (it = d->baseViews.begin(); it != d->baseViews.end(); ++it) { if ((*it)->onMsg(pMsg, pReturnIgnore)) { return true; } } for (it = d->passiveViews.begin(); it != d->passiveViews.end(); ++it) { if ((*it)->onMsg(pMsg, pReturnIgnore)) { return true; } } return false; } bool Document::sendMsgToFirstView(const Base::Type& typeId, const char* pMsg, const char** ppReturn) { // first try the active view Gui::MDIView* view = getActiveView(); if (view && view->isDerivedFrom(typeId)) { if (view->onMsg(pMsg, ppReturn)) { return true; } } // now try the other views std::list views = getMDIViewsOfType(typeId); for (const auto& it : views) { if ((it != view) && it->onMsg(pMsg, ppReturn)) { return true; } } return false; } /// Getter for the active view MDIView* Document::getActiveView() const { // get the main window's active view MDIView* active = getMainWindow()->activeWindow(); // get all MDI views of the document std::list mdis = getMDIViews(); // check whether the active view is part of this document bool ok = false; for (const auto& mdi : mdis) { if (mdi == active) { ok = true; break; } } if (ok) { return active; } // the active view is not part of this document, just use the last view const auto& windows = Gui::getMainWindow()->windows(); for (auto rit = mdis.rbegin(); rit != mdis.rend(); ++rit) { // Some view is removed from window list for some reason, e.g. TechDraw // hidden page has view but not in the list. By right, the view will // self delete, but not the case for TechDraw, especially during // document restore. if (windows.contains(*rit) || (*rit)->isDerivedFrom()) { return *rit; } } return nullptr; } MDIView* Document::setActiveView(const ViewProviderDocumentObject* vp, Base::Type typeId) { MDIView* view = nullptr; if (!vp) { view = getActiveView(); } else { view = vp->getMDIView(); if (!view) { auto obj = vp->getObject(); if (!obj) { view = getActiveView(); } else { auto linked = obj->getLinkedObject(true); if (linked != obj) { auto vpLinked = freecad_cast( Application::Instance->getViewProvider(linked) ); if (vpLinked) { view = vpLinked->getMDIView(); } } if (!view && typeId.isBad()) { MDIView* active = getActiveView(); if (active && active->containsViewProvider(vp)) { view = active; } else { typeId = View3DInventor::getClassTypeId(); } } } } } if (!view || (!typeId.isBad() && !view->isDerivedFrom(typeId))) { view = nullptr; for (auto* v : d->baseViews) { if (v->isDerivedFrom() && (typeId.isBad() || v->isDerivedFrom(typeId))) { view = static_cast(v); break; } } } if (!view && !typeId.isBad()) { view = createView(typeId); } if (view) { getMainWindow()->setActiveWindow(view); } return view; } /** * @brief Document::setActiveWindow * If this document is active and the view is part of it then it will be * activated. If the document is not active or if the view is already active * nothing is done. * @param view */ void Document::setActiveWindow(Gui::MDIView* view) { // get the main window's active view MDIView* active = getMainWindow()->activeWindow(); // view is already active if (active == view) { return; } // get all MDI views of the document std::list mdis = getMDIViews(); // this document is not active if (std::ranges::find(mdis, active) == mdis.end()) { return; } // the view is not part of the document if (std::ranges::find(mdis, view) == mdis.end()) { return; } getMainWindow()->setActiveWindow(view); } Gui::MDIView* Document::getViewOfNode(SoNode* node) const { std::list mdis = getMDIViewsOfType(View3DInventor::getClassTypeId()); for (const auto& mdi : mdis) { auto view = static_cast(mdi); if (view->getViewer()->searchNode(node)) { return mdi; } } return nullptr; } Gui::MDIView* Document::getViewOfViewProvider(const Gui::ViewProvider* vp) const { return getViewOfNode(vp->getRoot()); } Gui::MDIView* Document::getEditingViewOfViewProvider(Gui::ViewProvider* vp) const { std::list mdis = getMDIViewsOfType(View3DInventor::getClassTypeId()); for (const auto& mdi : mdis) { auto view = static_cast(mdi); View3DInventorViewer* viewer = view->getViewer(); // there is only one 3d view which is in edit mode if (viewer->hasViewProvider(vp) && viewer->isEditingViewProvider()) { return mdi; } } return nullptr; } //-------------------------------------------------------------------------- // UNDO REDO transaction handling //-------------------------------------------------------------------------- /** Open a new Undo transaction on the active document * This method opens a new UNDO transaction on the active document. This transaction * will later appear in the UNDO/REDO dialog with the name of the command. If the user * recall the transaction everything changed on the document between OpenCommand() and * CommitCommand will be undone (or redone). You can use an alternative name for the * operation default is the command name. * @see CommitCommand(),AbortCommand() */ void Document::openCommand(const char* sName) { getDocument()->openTransaction(sName); } void Document::commitCommand() { getDocument()->commitTransaction(); } void Document::abortCommand() { getDocument()->abortTransaction(); } bool Document::hasPendingCommand() const { return getDocument()->hasPendingTransaction(); } /// Get a string vector with the 'Undo' actions std::vector Document::getUndoVector() const { return getDocument()->getAvailableUndoNames(); } /// Get a string vector with the 'Redo' actions std::vector Document::getRedoVector() const { return getDocument()->getAvailableRedoNames(); } bool Document::checkTransactionID(bool undo, int iSteps) { if (!iSteps) { return false; } std::vector ids; for (int i = 0; i < iSteps; i++) { int id = getDocument()->getTransactionID(undo, i); if (!id) { break; } ids.push_back(id); } std::set prompts; std::map dmap; for (auto doc : App::GetApplication().getDocuments()) { if (doc == getDocument()) { continue; } for (auto id : ids) { int steps = undo ? doc->getAvailableUndos(id) : doc->getAvailableRedos(id); if (!steps) { continue; } int& currentSteps = dmap[doc]; if (currentSteps + 1 != steps) { prompts.insert(doc); } if (currentSteps < steps) { currentSteps = steps; } } } if (!prompts.empty()) { std::ostringstream str; int i = 0; for (auto doc : prompts) { if (i++ == 5) { str << "...\n"; break; } str << " " << doc->getName() << "\n"; } int ret = QMessageBox::warning( getMainWindow(), undo ? QObject::tr("Undo") : QObject::tr("Redo"), QStringLiteral("%1,\n%2%3") .arg( QObject::tr( "There are grouped transactions in the following documents with " "other preceding transactions" ), QString::fromStdString(str.str()), QObject::tr( "Choose 'Yes' to roll back all preceding transactions.\n" "Choose 'No' to roll back in the active document only.\n" "Choose 'Abort' to abort" ) ), QMessageBox::Yes | QMessageBox::No | QMessageBox::Abort, QMessageBox::Yes ); if (ret == QMessageBox::Abort) { return false; } if (ret == QMessageBox::No) { return true; } } for (auto& v : dmap) { for (int i = 0; i < v.second; ++i) { if (undo) { v.first->undo(); } else { v.first->redo(); } } } return true; } bool Document::isPerformingTransaction() const { return d->_isTransacting; } /// Will UNDO one or more steps void Document::undo(int iSteps) { Base::FlagToggler<> flag(d->_isTransacting); if (!checkTransactionID(true, iSteps)) { return; } for (int i = 0; i < iSteps; i++) { getDocument()->undo(); } App::GetApplication().signalUndo(); } /// Will REDO one or more steps void Document::redo(int iSteps) { Base::FlagToggler<> flag(d->_isTransacting); if (!checkTransactionID(false, iSteps)) { return; } for (int i = 0; i < iSteps; i++) { getDocument()->redo(); } App::GetApplication().signalRedo(); for (auto it : d->_redoViewProviders) { handleChildren3D(it); } d->_redoViewProviders.clear(); } PyObject* Document::getPyObject() { _pcDocPy->IncRef(); return _pcDocPy; } void Document::handleChildren3D(ViewProvider* viewProvider, bool deleting) { // check for children if (viewProvider && viewProvider->getChildRoot()) { std::vector children = viewProvider->claimChildren3D(); SoGroup* childGroup = viewProvider->getChildRoot(); SoGroup* frontGroup = viewProvider->getFrontRoot(); SoGroup* backGroup = viewProvider->getFrontRoot(); // size not the same -> build up the list new if (deleting || childGroup->getNumChildren() != static_cast(children.size())) { std::set oldChildren; for (int i = 0, count = childGroup->getNumChildren(); i < count; ++i) { auto it = d->_CoinMap.find(static_cast(childGroup->getChild(i))); if (it == d->_CoinMap.end()) { continue; } oldChildren.insert(it->second); } Gui::coinRemoveAllChildren(childGroup); Gui::coinRemoveAllChildren(frontGroup); Gui::coinRemoveAllChildren(backGroup); if (!deleting) { for (const auto& it : children) { if (auto ChildViewProvider = dynamic_cast(getViewProvider(it))) { auto itOld = oldChildren.find(ChildViewProvider); if (itOld != oldChildren.end()) { oldChildren.erase(itOld); } if (SoSeparator* childRootNode = ChildViewProvider->getRoot()) { if (childRootNode == childGroup) { Base::Console().warning( "Document::handleChildren3D: Do not add " "group of '%s' to itself\n", it->getNameInDocument() ); } else if (childGroup) { childGroup->addChild(childRootNode); } } if (SoSeparator* childFrontNode = ChildViewProvider->getFrontRoot()) { if (childFrontNode == frontGroup) { Base::Console().warning( "Document::handleChildren3D: Do not add " "foreground group of '%s' to itself\n", it->getNameInDocument() ); } else if (frontGroup) { frontGroup->addChild(childFrontNode); } } if (SoSeparator* childBackNode = ChildViewProvider->getBackRoot()) { if (childBackNode == backGroup) { Base::Console().warning( "Document::handleChildren3D: Do not add " "background group of '%s' to itself\n", it->getNameInDocument() ); } else if (backGroup) { backGroup->addChild(childBackNode); } } // cycling to all views of the document to remove the viewprovider from the // viewer itself for (Gui::BaseView* vIt : d->baseViews) { auto activeView = dynamic_cast(vIt); if (activeView && activeView->getViewer()->hasViewProvider(ChildViewProvider)) { // @Note hasViewProvider() // remove the viewprovider serves the purpose of detaching the // inventor nodes from the top level root in the viewer. However, if // some of the children were grouped beneath the object earlier they // are not anymore part of the toplevel inventor node. we need to // check for that. activeView->getViewer()->removeViewProvider(ChildViewProvider); } } } } } // add the remaining old children back to toplevel invertor node for (auto vpd : oldChildren) { auto obj = vpd->getObject(); if (!obj || !obj->isAttachedToDocument()) { continue; } for (BaseView* view : d->baseViews) { auto activeView = dynamic_cast(view); if (activeView && !activeView->getViewer()->hasViewProvider(vpd)) { activeView->getViewer()->addViewProvider(vpd); } } } } } } void Document::toggleInSceneGraph(ViewProvider* vp) { // FIXME: What's the point of having this function? // for (auto view : d->baseViews) { auto activeView = dynamic_cast(view); if (!activeView) { continue; } auto root = vp->getRoot(); if (!root) { continue; } auto scenegraph = dynamic_cast(activeView->getViewer()->getSceneGraph()); if (!scenegraph) { continue; } // If it cannot be added then only check the top-level nodes if (!vp->canAddToSceneGraph()) { int idx = scenegraph->findChild(root); if (idx >= 0) { scenegraph->removeChild(idx); } } else { // Do a deep search of the scene because the root node // isn't necessarily a top-level node when claimed by // another view provider. // This is to avoid to add a node twice to the scene. SoSearchAction sa; sa.setNode(root); sa.setSearchingAll(false); sa.apply(scenegraph); SoPath* path = sa.getPath(); if (!path) { scenegraph->addChild(root); } } } } void Document::slotChangePropertyEditor(const App::Document& doc, const App::Property& Prop) { if (getDocument() == &doc) { FC_LOG(Prop.getFullName() << " editor changed"); setModified(true); getMainWindow()->setUserSchema(doc.UnitSystem.getValue()); } } std::vector Document::getTreeRootObjects() const { std::vector docObjects = d->_pcDocument->getObjects(); std::unordered_map rootMap; for (auto it : docObjects) { rootMap[it] = true; } for (auto obj : docObjects) { ViewProvider* vp = Application::Instance->getViewProvider(obj); if (!vp) { continue; } std::vector children = vp->claimChildren(); for (auto child : children) { rootMap[child] = false; } } std::vector rootObjs; for (const auto& it : rootMap) { if (it.second) { rootObjs.push_back(it.first); } } return rootObjs; }