/*************************************************************************** * 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 "ViewProviderDocumentObjectPy.h" #include "ActionFunction.h" #include "Application.h" #include "Command.h" #include "Document.h" #include "MDIView.h" #include "SoFCUnifiedSelection.h" #include "Tree.h" #include "ViewProviderDocumentObject.h" #include "ViewProviderExtension.h" #include "TaskView/TaskAppearance.h" FC_LOG_LEVEL_INIT("Gui", true, true) using namespace Gui; PROPERTY_SOURCE(Gui::ViewProviderDocumentObject, Gui::ViewProvider) ViewProviderDocumentObject::ViewProviderDocumentObject() { static const char* dogroup = "Display Options"; static const char* sgroup = "Selection"; ADD_PROPERTY_TYPE(DisplayMode, ((long)0), dogroup, App::Prop_None, "Set the display mode"); ADD_PROPERTY_TYPE(Visibility, (true), dogroup, App::Prop_None, "Show the object in the 3d view"); ADD_PROPERTY_TYPE(ShowInTree, (true), dogroup, App::Prop_None, "Show the object in the tree view"); ADD_PROPERTY_TYPE(SelectionStyle, ((long)0), sgroup, App::Prop_None, "Set the object selection style"); static const char* SelectionStyleEnum[] = {"Shape", "BoundBox", nullptr}; SelectionStyle.setEnums(SelectionStyleEnum); static const char* OnTopEnum[] = {"Disabled", "Enabled", "Object", "Element", nullptr}; ADD_PROPERTY_TYPE( OnTopWhenSelected, ((long int)0), sgroup, App::Prop_None, "Enabled: Display the object on top of any other object when selected\n" "Object: On top only if the whole object is selected\n" "Element: On top only if some sub-element of the object is selected" ); OnTopWhenSelected.setEnums(OnTopEnum); sPixmap = "Feature"; } ViewProviderDocumentObject::~ViewProviderDocumentObject() { // Make sure that the property class does not destruct our string list DisplayMode.setContainer(nullptr); DisplayMode.setEnums(nullptr); } void ViewProviderDocumentObject::getTaskViewContent(std::vector& vec) const { vec.push_back(new Gui::TaskView::TaskAppearance()); } void ViewProviderDocumentObject::startRestoring() { hide(); auto vector = getExtensionsDerivedFromType(); for (Gui::ViewProviderExtension* ext : vector) { ext->extensionStartRestoring(); } } void ViewProviderDocumentObject::finishRestoring() { auto vector = getExtensionsDerivedFromType(); for (Gui::ViewProviderExtension* ext : vector) { ext->extensionFinishRestoring(); } } bool ViewProviderDocumentObject::isAttachedToDocument() const { return (!testStatus(Detach)); } const char* ViewProviderDocumentObject::detachFromDocument() { // here we can return an empty string since the object // name comes from the document object setStatus(Detach, true); return ""; } bool ViewProviderDocumentObject::removeDynamicProperty(const char* name) { App::Property* prop = getDynamicPropertyByName(name); if (!prop || prop->testStatus(App::Property::LockDynamic)) { return false; } // transactions of view providers are also managed in App::Document. App::DocumentObject* docobject = getObject(); App::Document* document = docobject ? docobject->getDocument() : nullptr; if (document) { document->addOrRemovePropertyOfObject(this, prop, false); } return ViewProvider::removeDynamicProperty(name); } App::Property* ViewProviderDocumentObject::addDynamicProperty( const char* type, const char* name, const char* group, const char* doc, short attr, bool ro, bool hidden ) { auto prop = ViewProvider::addDynamicProperty(type, name, group, doc, attr, ro, hidden); if (prop) { // transactions of view providers are also managed in App::Document. App::DocumentObject* docobject = getObject(); App::Document* document = docobject ? docobject->getDocument() : nullptr; if (document) { document->addOrRemovePropertyOfObject(this, prop, true); } } return prop; } void ViewProviderDocumentObject::onBeforeChange(const App::Property* prop) { if (isAttachedToDocument()) { App::DocumentObject* obj = getObject(); App::Document* doc = obj ? obj->getDocument() : nullptr; if (doc) { onBeforeChangeProperty(doc, prop); } } ViewProvider::onBeforeChange(prop); } void ViewProviderDocumentObject::onChanged(const App::Property* prop) { if (prop == &DisplayMode) { setActiveMode(); } else if (prop == &Visibility) { // use this bit to check whether show() or hide() must be called if (!Visibility.testStatus(App::Property::User2)) { Visibility.setStatus(App::Property::User2, true); Visibility.getValue() ? show() : hide(); Visibility.setStatus(App::Property::User2, false); } if (!Visibility.testStatus(App::Property::User1) && getObject() && getObject()->Visibility.getValue() != Visibility.getValue()) { // Changing the visibility of a document object will automatically set // the document modified but if the 'TouchDocument' flag is not set then // this is undesired behaviour. So, if this change marks the document as // modified then it must be be reversed. if (!testStatus(Gui::ViewStatus::TouchDocument)) { // Note: reverting document modified status like that is not // appropriate because we can't tell if there is any other // property being changed due to the change of Visibility here. // Temporary setting the Visibility property as 'NoModify' is // the proper way. Base::ObjectStatusLocker guard( App::Property::NoModify, &Visibility ); // bool mod = false; // if (pcDocument) // mod = pcDocument->isModified(); getObject()->Visibility.setValue(Visibility.getValue()); // if (pcDocument) // pcDocument->setModified(mod); } else { getObject()->Visibility.setValue(Visibility.getValue()); } } } else if (prop == &SelectionStyle) { if (getRoot()->isOfType(SoFCSelectionRoot::getClassTypeId())) { static_cast(getRoot())->selectionStyle = SelectionStyle.getValue() ? SoFCSelectionRoot::Box : SoFCSelectionRoot::Full; } } if (prop && !prop->testStatus(App::Property::NoModify) && pcDocument && !pcDocument->isModified() && testStatus(Gui::ViewStatus::TouchDocument)) { if (prop) { FC_LOG(prop->getFullName() << " changed"); } pcDocument->setModified(true); } ViewProvider::onChanged(prop); } void ViewProviderDocumentObject::hide() { ViewProvider::hide(); // use this bit to check whether 'Visibility' must be adjusted if (!Visibility.testStatus(App::Property::User2)) { Visibility.setStatus(App::Property::User2, true); Visibility.setValue(false); Visibility.setStatus(App::Property::User2, false); } } bool ViewProviderDocumentObject::isShowable() const { return _Showable; } void ViewProviderDocumentObject::setShowable(bool enable) { if (_Showable == enable) { return; } _Showable = enable; int which = getModeSwitch()->whichChild.getValue(); if (_Showable && which == -1 && Visibility.getValue()) { setModeSwitch(); } else if (!_Showable) { if (which >= 0) { ViewProvider::hide(); } } } void ViewProviderDocumentObject::startDefaultEditMode() { QString text = QObject::tr("Edit %1").arg(QString::fromUtf8(getObject()->Label.getValue())); Gui::Command::openCommand(text.toUtf8()); Gui::Document* document = this->getDocument(); if (document) { document->setEdit(this, ViewProvider::Default); } } void ViewProviderDocumentObject::addDefaultAction(QMenu* menu, const QString& text) { QAction* act = menu->addAction(text); act->setData(QVariant((int)ViewProvider::Default)); auto func = new Gui::ActionFunction(menu); func->trigger(act, [this]() { this->startDefaultEditMode(); }); } void ViewProviderDocumentObject::setModeSwitch() { if (isShowable()) { ViewProvider::setModeSwitch(); } } void ViewProviderDocumentObject::show() { if (TreeWidget::isObjectShowable(getObject())) { ViewProvider::show(); } else { Visibility.setValue(false); if (getObject()) { getObject()->Visibility.setValue(false); } return; } // use this bit to check whether 'Visibility' must be adjusted if (!Visibility.testStatus(App::Property::User2)) { Visibility.setStatus(App::Property::User2, true); Visibility.setValue(true); Visibility.setStatus(App::Property::User2, false); } } const char* ViewProviderDocumentObject::getTransactionText() const { return QT_TRANSLATE_NOOP("Command", "Edit"); } void ViewProviderDocumentObject::updateView() { if (!pcObject || testStatus(ViewStatus::UpdatingView)) { return; } Base::ObjectStatusLocker lock(ViewStatus::UpdatingView, this); // Disable object visibility syncing Base::ObjectStatusLocker lock2( App::Property::User1, &Visibility ); std::map Map; pcObject->getPropertyMap(Map); // Hide the object temporarily to speed up the update bool vis = ViewProvider::isShow(); if (vis) { ViewProvider::hide(); } for (const auto& it : Map) { updateData(it.second); } if (vis && Visibility.getValue()) { ViewProvider::show(); } } void ViewProviderDocumentObject::attach(App::DocumentObject* pcObj) { // save Object pointer pcObject = pcObj; if (pcObj && pcObj->isAttachedToDocument() && Visibility.getValue() != pcObj->Visibility.getValue()) { pcObj->Visibility.setValue(Visibility.getValue()); } // Retrieve the supported display modes of the view provider aDisplayModesArray = this->getDisplayModes(); if (aDisplayModesArray.empty()) { aDisplayModesArray.emplace_back(""); } // We must collect the const char* of the strings and give it to PropertyEnumeration, // but we are still responsible for them, i.e. the property class must not delete the literals. // for (auto it = aDisplayModesArray.begin(); it != aDisplayModesArray.end(); ++it) { for (const auto& it : aDisplayModesArray) { aDisplayEnumsArray.push_back(it.c_str()); } aDisplayEnumsArray.push_back(nullptr); // null termination DisplayMode.setEnums(&(aDisplayEnumsArray[0])); if (!isRestoring()) { // set the active mode const char* defmode = this->getDefaultDisplayMode(); if (defmode) { DisplayMode.setValue(defmode); } } // attach the extensions auto vector = getExtensionsDerivedFromType(); for (Gui::ViewProviderExtension* ext : vector) { ext->extensionAttach(pcObj); } } void ViewProviderDocumentObject::reattach(App::DocumentObject* pcObj) { auto vector = getExtensionsDerivedFromType(); for (Gui::ViewProviderExtension* ext : vector) { ext->extensionReattach(pcObj); } } void ViewProviderDocumentObject::update(const App::Property* prop) { // bypass view provider update to always allow changing visibility from // document object if (prop == &getObject()->Visibility) { if (!isRestoring() && Visibility.getValue() != getObject()->Visibility.getValue()) { Visibility.setValue(!Visibility.getValue()); } } else { // Disable object visibility syncing Base::ObjectStatusLocker guard( App::Property::User1, &Visibility ); ViewProvider::update(prop); } } Gui::Document* ViewProviderDocumentObject::getDocument() const { if (!pcObject) { throw Base::RuntimeError("View provider detached"); } if (pcDocument) { return pcDocument; } else { App::Document* pAppDoc = pcObject->getDocument(); return Gui::Application::Instance->getDocument(pAppDoc); } } Gui::MDIView* ViewProviderDocumentObject::getActiveView() const { if (!pcObject) { throw Base::RuntimeError("View provider detached"); } if (!pcObject->isAttachedToDocument()) { // Check if view provider is attached to a document as an annotation for (auto doc : App::GetApplication().getDocuments()) { auto guiDoc = Gui::Application::Instance->getDocument(doc); if (guiDoc->isAnnotationViewProvider(this)) { return guiDoc->getActiveView(); } } return nullptr; } App::Document* pAppDoc = pcObject->getDocument(); Gui::Document* pGuiDoc = Gui::Application::Instance->getDocument(pAppDoc); return pGuiDoc->getActiveView(); } Gui::MDIView* ViewProviderDocumentObject::getEditingView() const { if (!pcObject) { throw Base::RuntimeError("View provider detached"); } App::Document* pAppDoc = pcObject->getDocument(); Gui::Document* pGuiDoc = Gui::Application::Instance->getDocument(pAppDoc); return pGuiDoc->getEditingViewOfViewProvider(const_cast(this)); } Gui::MDIView* ViewProviderDocumentObject::getInventorView() const { if (!pcObject) { throw Base::RuntimeError("View provider detached"); } App::Document* pAppDoc = pcObject->getDocument(); Gui::Document* pGuiDoc = Gui::Application::Instance->getDocument(pAppDoc); Gui::MDIView* mdi = pGuiDoc->getEditingViewOfViewProvider( const_cast(this) ); if (!mdi) { mdi = pGuiDoc->getViewOfViewProvider(const_cast(this)); } return mdi; } Gui::MDIView* ViewProviderDocumentObject::getViewOfNode(SoNode* node) const { if (!pcObject) { throw Base::RuntimeError("View provider detached"); } App::Document* pAppDoc = pcObject->getDocument(); Gui::Document* pGuiDoc = Gui::Application::Instance->getDocument(pAppDoc); return pGuiDoc->getViewOfNode(node); } SoNode* ViewProviderDocumentObject::findFrontRootOfType(const SoType& type) const { if (!pcObject) { return nullptr; } // first get the document this object is part of and get its GUI counterpart App::Document* pAppDoc = pcObject->getDocument(); Gui::Document* pGuiDoc = Gui::Application::Instance->getDocument(pAppDoc); SoSearchAction searchAction; searchAction.setType(type); searchAction.setInterest(SoSearchAction::FIRST); // search in all view providers for the node type std::vector obj = pAppDoc->getObjects(); for (auto& it : obj) { const ViewProvider* vp = pGuiDoc->getViewProvider(it); // Ignore 'this' view provider. It could also happen that vp is 0, e.g. when // several objects have been added to the App::Document before notifying the // Gui::Document if (!vp || vp == this) { continue; } SoSeparator* front = vp->getFrontRoot(); // if (front && front->getTypeId() == type) // return front; if (front) { searchAction.apply(front); SoPath* path = searchAction.getPath(); if (path) { return path->getTail(); } } } return nullptr; } void ViewProviderDocumentObject::setActiveMode() { if (DisplayMode.isValid()) { const char* mode = DisplayMode.getValueAsString(); if (mode) { setDisplayMode(mode); } } if (!Visibility.getValue()) { ViewProvider::hide(); } } bool ViewProviderDocumentObject::canDelete(App::DocumentObject* obj) const { Q_UNUSED(obj); auto* o = getObject(); return o->hasExtension(App::GroupExtension::getExtensionClassTypeId()) || o->isDerivedFrom(); } PyObject* ViewProviderDocumentObject::getPyObject() { if (!pyViewObject) { pyViewObject = new ViewProviderDocumentObjectPy(this); } pyViewObject->IncRef(); return pyViewObject; } bool ViewProviderDocumentObject::canDropObjectEx( App::DocumentObject* obj, App::DocumentObject* owner, const char* subname, const std::vector& elements ) const { auto vector = getExtensionsDerivedFromType(); for (Gui::ViewProviderExtension* ext : vector) { if (ext->extensionCanDropObjectEx(obj, owner, subname, elements)) { return true; } } if (obj && obj->getDocument() != getObject()->getDocument()) { return false; } return canDropObject(obj); } int ViewProviderDocumentObject::replaceObject(App::DocumentObject* oldObj, App::DocumentObject* newObj) { if (!oldObj || !oldObj->isAttachedToDocument() || !newObj || !newObj->isAttachedToDocument()) { FC_THROWM(Base::RuntimeError, "Invalid object"); } auto obj = getObject(); if (!obj || !obj->isAttachedToDocument()) { FC_THROWM(Base::RuntimeError, "View provider not attached"); } int res = ViewProvider::replaceObject(oldObj, newObj); if (res >= 0) { return res; } std::vector>> propChanges; std::vector props; obj->getPropertyList(props); for (auto prop : props) { auto linkProp = freecad_cast(prop); if (!linkProp) { continue; } std::unique_ptr copy(linkProp->CopyOnLinkReplace(obj, oldObj, newObj)); if (!copy) { continue; } propChanges.emplace_back(prop, std::move(copy)); } if (propChanges.empty()) { return 0; } // Global search for affected links for (auto doc : App::GetApplication().getDocuments()) { for (auto o : doc->getObjects()) { if (o == obj) { continue; } std::vector props; o->getPropertyList(props); for (auto prop : props) { auto linkProp = freecad_cast(prop); if (!linkProp) { continue; } std::unique_ptr copy(linkProp->CopyOnLinkReplace(obj, oldObj, newObj)); if (!copy) { continue; } propChanges.emplace_back(App::DocumentObjectT(prop), std::move(copy)); } } } for (auto& v : propChanges) { auto prop = v.first.getProperty(); if (prop) { prop->Paste(*v.second.get()); } } return 1; } bool ViewProviderDocumentObject::showInTree() const { return ShowInTree.getValue(); } bool ViewProviderDocumentObject::getElementPicked(const SoPickedPoint* pp, std::string& subname) const { auto vector = getExtensionsDerivedFromType(); for (Gui::ViewProviderExtension* ext : vector) { if (ext->extensionGetElementPicked(pp, subname)) { return true; } } auto childRoot = getChildRoot(); int idx; if (!childRoot || (idx = pcModeSwitch->whichChild.getValue()) < 0 || pcModeSwitch->getChild(idx) != childRoot) { return ViewProvider::getElementPicked(pp, subname); } SoPath* path = pp->getPath(); idx = path->findNode(childRoot); if (idx < 0 || idx + 1 >= path->getLength()) { return false; } auto vp = getDocument()->getViewProvider(path->getNode(idx + 1)); if (!vp) { return false; } auto obj = vp->getObject(); if (!obj || !obj->isAttachedToDocument()) { return false; } std::ostringstream str; str << obj->getNameInDocument() << '.'; if (vp->getElementPicked(pp, subname)) { str << subname; } subname = str.str(); return true; } bool ViewProviderDocumentObject::getDetailPath( const char* subname, SoFullPath* path, bool append, SoDetail*& det ) const { auto len = path->getLength(); if (!append && len >= 2) { len -= 2; } if (ViewProvider::getDetailPath(subname, path, append, det)) { if (det || !subname || !*subname) { return true; } } if (det) { delete det; det = nullptr; } const char* dot = strchr(subname, '.'); if (!dot) { return false; } auto obj = getObject(); if (!obj || !obj->isAttachedToDocument()) { return false; } auto sobj = obj->getSubObject(std::string(subname, dot - subname + 1).c_str()); if (!sobj) { return false; } auto vp = Application::Instance->getViewProvider(sobj); if (!vp) { return false; } auto childRoot = getChildRoot(); if (!childRoot) { path->truncate(len); } else { auto idx = pcModeSwitch->whichChild.getValue(); if (idx < 0 || pcModeSwitch->getChild(idx) != childRoot) { return false; } path->append(childRoot); } bool ret = false; if (path->getLength()) { SoNode* tail = path->getTail(); const SoChildList* children = tail->getChildren(); if (children && children->find(vp->getRoot()) >= 0) { ret = vp->getDetailPath(dot + 1, path, true, det); } } return ret; } void ViewProviderDocumentObject::onPropertyStatusChanged(const App::Property& prop, unsigned long oldStatus) { (void)oldStatus; if (!App::Document::isAnyRestoring() && pcObject && pcObject->getDocument()) { pcObject->getDocument()->signalChangePropertyEditor(*pcObject->getDocument(), prop); } } ViewProviderDocumentObject* ViewProviderDocumentObject::getLinkedViewProvider( std::string* subname, bool recursive ) const { (void)subname; auto self = const_cast(this); if (!pcObject || !pcObject->isAttachedToDocument()) { return self; } auto linked = pcObject->getLinkedObject(recursive); if (!linked || linked == pcObject) { return self; } auto res = freecad_cast( Application::Instance->getViewProvider(linked) ); if (!res) { res = self; } return res; } std::string ViewProviderDocumentObject::getFullName() const { if (pcObject) { return pcObject->getFullName() + ".ViewObject"; } return std::string("?"); }