diff --git a/src/App/Link.cpp b/src/App/Link.cpp index a7964f88c6..92fc78d38f 100644 --- a/src/App/Link.cpp +++ b/src/App/Link.cpp @@ -22,14 +22,17 @@ #include "PreCompiled.h" +#include #include #include +#include #include "Application.h" #include "ComplexGeoData.h" #include "ComplexGeoDataPy.h" #include "Document.h" -#include "GroupExtension.h" +#include "DocumentObserver.h" +#include "GeoFeatureGroupExtension.h" #include "Link.h" #include "LinkBaseExtensionPy.h" @@ -45,6 +48,99 @@ using namespace App; using namespace Base; namespace bp = boost::placeholders; +typedef boost::iterator_range CharRange; + +//////////////////////////////////////////////////////////////////////// + +/*[[[cog +import LinkParams +LinkParams.define() +]]]*/ + +// Auto generated code. See class document of LinkParams. +class LinkParamsP: public ParameterGrp::ObserverType { +public: + // Auto generated code. See class document of LinkParams. + ParameterGrp::handle handle; + + // Auto generated code. See class document of LinkParams. + std::unordered_map funcs; + + bool CopyOnChangeApplyToAll; // Auto generated code. See class document of LinkParams. + + // Auto generated code. See class document of LinkParams. + LinkParamsP() { + handle = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/Link"); + handle->Attach(this); + + CopyOnChangeApplyToAll = handle->GetBool("CopyOnChangeApplyToAll", true); + funcs["CopyOnChangeApplyToAll"] = &LinkParamsP::updateCopyOnChangeApplyToAll; + } + + // Auto generated code. See class document of LinkParams. + ~LinkParamsP() { + } + + // Auto generated code. See class document of LinkParams. + void OnChange(Base::Subject &, const char* sReason) { + if(!sReason) + return; + auto it = funcs.find(sReason); + if(it == funcs.end()) + return; + it->second(this); + } + + + // Auto generated code. See class document of LinkParams. + static void updateCopyOnChangeApplyToAll(LinkParamsP *self) { + self->CopyOnChangeApplyToAll = self->handle->GetBool("CopyOnChangeApplyToAll", true); + } +}; + +// Auto generated code. See class document of LinkParams. +LinkParamsP *instance() { + static LinkParamsP *inst = new LinkParamsP; + return inst; +} + +// Auto generated code. See class document of LinkParams. +ParameterGrp::handle LinkParams::getHandle() { + return instance()->handle; +} + +// Auto generated code. See class document of LinkParams. +const char *LinkParams::docCopyOnChangeApplyToAll() { + return QT_TRANSLATE_NOOP("LinkParams", +"Stores the last user choice of whether to apply CopyOnChange setup to all link\n" +"that links to the same configurable object"); +} + +// Auto generated code. See class document of LinkParams. +const bool & LinkParams::getCopyOnChangeApplyToAll() { + return instance()->CopyOnChangeApplyToAll; +} + +// Auto generated code. See class document of LinkParams. +const bool & LinkParams::defaultCopyOnChangeApplyToAll() { + const static bool def = true; + return def; +} + +// Auto generated code. See class document of LinkParams. +void LinkParams::setCopyOnChangeApplyToAll(const bool &v) { + instance()->handle->SetBool("CopyOnChangeApplyToAll",v); + instance()->CopyOnChangeApplyToAll = v; +} + +// Auto generated code. See class document of LinkParams. +void LinkParams::removeCopyOnChangeApplyToAll() { + instance()->handle->RemoveBool("CopyOnChangeApplyToAll"); +} +//[[[end]]] + +/////////////////////////////////////////////////////////////////////////////// + EXTENSION_PROPERTY_SOURCE(App::LinkBaseExtension, App::DocumentObjectExtension) LinkBaseExtension::LinkBaseExtension(void) @@ -130,18 +226,23 @@ void LinkBaseExtension::setProperty(int idx, Property *prop) { switch(idx) { case PropLinkMode: { static const char *linkModeEnums[] = {"None","Auto Delete","Auto Link","Auto Unlink",nullptr}; - auto propLinkMode = freecad_dynamic_cast(prop); + auto propLinkMode = static_cast(prop); if(!propLinkMode->getEnums()) propLinkMode->setEnums(linkModeEnums); break; } case PropLinkCopyOnChange: { - static const char *enums[] = {"Disabled","Enabled","Owned",nullptr}; - auto propEnum = freecad_dynamic_cast(prop); + static const char *enums[] = {"Disabled","Enabled","Owned","Tracking",nullptr}; + auto propEnum = static_cast(prop); if(!propEnum->getEnums()) propEnum->setEnums(enums); break; } + case PropLinkCopyOnChangeTouched: + case PropLinkCopyOnChangeSource: + case PropLinkCopyOnChangeGroup: + prop->setStatus(Property::Hidden, true); + break; case PropLinkTransform: case PropLinkPlacement: case PropPlacement: @@ -155,6 +256,7 @@ void LinkBaseExtension::setProperty(int idx, Property *prop) { } break; case PropElementList: + getElementListProperty()->setScope(LinkScope::Global); getElementListProperty()->setStatus(Property::Hidden,true); // fall through case PropLinkedObject: @@ -187,33 +289,35 @@ void LinkBaseExtension::setProperty(int idx, Property *prop) { static const char _GroupPrefix[] = "Configuration ("; App::DocumentObjectExecReturn *LinkBaseExtension::extensionExecute(void) { - // The actual value of LinkRecompouted is not important, just to notify view + // The actual value of LinkTouched is not important, just to notify view // provider that the link (in fact, its dependents, i.e. linked ones) have // recomputed. _LinkTouched.touch(); if(getLinkedObjectProperty()) { DocumentObject *linked = getTrueLinkedObject(true); - if(!linked) - return new App::DocumentObjectExecReturn("Link broken"); + if(!linked) { + std::ostringstream ss; + ss << "Link broken!"; + auto xlink = Base::freecad_dynamic_cast( + getLinkedObjectProperty()); + if (xlink) { + const char *objname = xlink->getObjectName(); + if (objname && objname[0]) + ss << "\nObject: " << objname; + const char *filename = xlink->getFilePath(); + if (filename && filename[0]) + ss << "\nFile: " << filename; + } + return new App::DocumentObjectExecReturn(ss.str().c_str()); + } App::DocumentObject *container = getContainer(); - setupCopyOnChange(container); - - if(hasCopyOnChange && getLinkCopyOnChangeValue()==0) { - hasCopyOnChange = false; - std::vector props; - container->getPropertyList(props); - for(auto prop : props) { - if(isCopyOnChangeProperty(container, *prop)) { - try { - container->removeDynamicProperty(prop->getName()); - } catch (Base::Exception &e) { - e.ReportException(); - } catch (...) { - } - } - } + auto source = getLinkCopyOnChangeSourceValue(); + if (source && getLinkCopyOnChangeValue() == CopyOnChangeTracking + && getLinkCopyOnChangeTouchedValue()) + { + syncCopyOnChange(); } PropertyPythonObject *proxy = nullptr; @@ -266,6 +370,25 @@ App::DocumentObjectExecReturn *LinkBaseExtension::extensionExecute(void) { return new App::DocumentObjectExecReturn(errMsg); } } + + auto parent = getContainer(); + setupCopyOnChange(parent); + + if(hasCopyOnChange && getLinkCopyOnChangeValue()==CopyOnChangeDisabled) { + hasCopyOnChange = false; + std::vector props; + parent->getPropertyList(props); + for(auto prop : props) { + if(isCopyOnChangeProperty(parent, *prop)) { + try { + parent->removeDynamicProperty(prop->getName()); + } catch (Base::Exception &e) { + e.ReportException(); + } catch (...) { + } + } + } + } } return inherited::extensionExecute(); } @@ -276,6 +399,273 @@ short LinkBaseExtension::extensionMustExecute(void) { return link->mustExecute(); } +std::vector +LinkBaseExtension::getOnChangeCopyObjects( + std::vector *excludes, + App::DocumentObject *src) +{ + auto parent = getContainer(); + if (!src) + src = getLinkCopyOnChangeSourceValue(); + if (!src || getLinkCopyOnChangeValue() == CopyOnChangeDisabled) + return {}; + + auto res = Document::getDependencyList({src}, Document::DepSort); + for (auto it=res.begin(); it!=res.end();) { + auto obj = *it; + if (obj == src) { + ++it; + continue; + } + auto prop = Base::freecad_dynamic_cast( + obj->getPropertyByName("_CopyOnChangeControl")); + static std::map dummy; + const auto & map = prop && prop->getContainer()==obj ? prop->getValues() : dummy; + const char *v = ""; + if (src->getDocument() != obj->getDocument()) + v = "-"; + auto iter = map.find("*"); + if (iter != map.end()) + v = iter->second.c_str(); + else if ((iter = map.find(parent->getNameInDocument())) != map.end()) + v = iter->second.c_str(); + if (boost::equals(v, "-")) { + if (excludes) + excludes->push_back(obj); + else { + it = res.erase(it); + continue; + } + } + ++it; + } + return res; +} + +void LinkBaseExtension::setOnChangeCopyObject( + App::DocumentObject *obj, OnChangeCopyOptions options) +{ + auto parent = getContainer(); + Base::Flags flags(options); + bool exclude = flags.testFlag(OnChangeCopyOptions::Exclude); + bool external = parent->getDocument() != obj->getDocument(); + auto prop = Base::freecad_dynamic_cast( + obj->getPropertyByName("_CopyOnChangeControl")); + + if (external == exclude && !prop) + return; + + prop = static_cast( + obj->addDynamicProperty("App::PropertyMap", "_CopyOnChangeControl")); + if (!prop) { + FC_ERR("Failed to setup copy on change object " << obj->getFullName()); + return; + } + + const char *key = flags.testFlag(OnChangeCopyOptions::ApplyAll) ? "*" : parent->getNameInDocument(); + if (external) + prop->setValue(key, exclude ? nullptr : "+"); + else + prop->setValue(key, exclude ? "-" : nullptr); +} + +// The purpose of this function is to synchronize the mutated copy to the +// original linked CopyOnChange object. It will make a new copy if any of the +// non-CopyOnChange property of the original object has changed. +void LinkBaseExtension::syncCopyOnChange() +{ + if (!getLinkCopyOnChangeValue()) + return; + auto linkProp = getLinkedObjectProperty(); + auto srcProp = getLinkCopyOnChangeSourceProperty(); + auto srcTouched = getLinkCopyOnChangeTouchedProperty(); + if (!linkProp + || !srcProp + || !srcTouched + || !srcProp->getValue() + || !linkProp->getValue() + || srcProp->getValue() == linkProp->getValue()) + return; + + auto parent = getContainer(); + + auto linked = linkProp->getValue(); + + std::vector oldObjs; + std::vector objs; + + // CopyOnChangeGroup is a hidden dynamic property for holding a LinkGroup + // for holding the mutated copy of the original linked object and all its + // dependencies. + LinkGroup *copyOnChangeGroup = nullptr; + if (auto prop = getLinkCopyOnChangeGroupProperty()) { + copyOnChangeGroup = Base::freecad_dynamic_cast(prop->getValue()); + if (!copyOnChangeGroup) { + // Create the LinkGroup if not exist + auto group = new LinkGroup; + group->LinkMode.setValue(LinkModeAutoDelete); + parent->getDocument()->addObject(group, "CopyOnChangeGroup"); + prop->setValue(group); + } else { + // If it exists, then obtain all copied objects. Note that we stores + // the dynamic property _SourceUUID in oldObjs if possible, in order + // to match the possible new copy later. + objs = copyOnChangeGroup->ElementList.getValues(); + for (auto obj : objs) { + if (!obj->getNameInDocument()) + continue; + auto prop = Base::freecad_dynamic_cast( + obj->getPropertyByName("_SourceUUID")); + if (prop && prop->getContainer() == obj) + oldObjs.emplace_back(prop); + else + oldObjs.emplace_back(obj); + } + std::sort(objs.begin(), objs.end()); + } + } + + // Obtain the original linked object and its dependency in depending order. + // The last being the original linked object. + auto srcObjs = getOnChangeCopyObjects(); + // Refresh signal connection to monitor changes + monitorOnChangeCopyObjects(srcObjs); + + // Copy the objects. Document::export/importObjects() (called by + // copyObject()) will generate a _ObjectUUID for each source object and + // match it with a _SourceUUID in the copy. + auto copiedObjs = parent->getDocument()->copyObject(srcObjs); + if(copiedObjs.empty()) + return; + + // copyObject() will return copy in order of the same order of the input, + // so the last object will be the copy of the original linked object + auto newLinked = copiedObjs.back(); + + // We are coping from the original linked object and we've already mutated + // it, so we need to copy all CopyOnChange properties from the mutated + // object to the new copy. + std::vector propList; + linked->getPropertyList(propList); + for (auto prop : propList) { + if(!prop->testStatus(Property::CopyOnChange) + || prop->getContainer()!=linked) + continue; + auto p = newLinked->getPropertyByName(prop->getName()); + if (p && p->getTypeId() == prop->getTypeId()) { + std::unique_ptr pCopy(prop->Copy()); + p->Paste(*pCopy); + } + } + + if (copyOnChangeGroup) { + // The order of the copied objects is in dependency order (because of + // getOnChangeCopyObjects()). We reverse it here so that we can later + // on delete it in reverse order to avoid error (because some parent + // objects may want to delete their own children). + std::reverse(copiedObjs.begin(), copiedObjs.end()); + copyOnChangeGroup->ElementList.setValues(copiedObjs); + } + + // Create a map to find the corresponding replacement of the new copies to + // the mutated object. The reason for doing so is that we are copying from + // the original linked object and its dependency, not the mutated objects + // which are old copies. There could be arbitary changes in the originals + // which may add or remove or change dependending orders, while the + // replacement happen between the new and old copies. + + std::map newObjs; + for (auto obj : copiedObjs) { + auto prop = Base::freecad_dynamic_cast( + obj->getPropertyByName("_SourceUUID")); + if (prop) + newObjs.insert(std::make_pair(prop->getValue(), obj)); + } + + std::vector > replacements; + for (const auto &objT : oldObjs) { + auto prop = Base::freecad_dynamic_cast(objT.getProperty()); + if (!prop) + continue; + auto it = newObjs.find(prop->getValue()); + if (it == newObjs.end()) + continue; + auto oldObj = objT.getObject(); + auto newObj = it->second.getObject(); + if (oldObj && newObj) + replacements.emplace_back(oldObj, newObj); + } + + std::vector > > propChanges; + if (!replacements.empty()) { + std::sort(copiedObjs.begin(), copiedObjs.end()); + + // Global search for links affected by the replacement. We accumulate + // the changes in propChanges without applying, in order to avoid any + // side effect of changing while searching. + for(auto doc : App::GetApplication().getDocuments()) { + for(auto o : doc->getObjects()) { + if (o == parent + || std::binary_search(objs.begin(), objs.end(), o) + || std::binary_search(copiedObjs.begin(), copiedObjs.end(), o)) + continue; + propList.clear(); + o->getPropertyList(propList); + for(auto prop : propList) { + if (prop->getContainer() != o) + continue; + auto linkProp = Base::freecad_dynamic_cast(prop); + if(!linkProp) + continue; + for (const auto &v : replacements) { + std::unique_ptr copy( + linkProp->CopyOnLinkReplace(parent,v.first,v.second)); + if(!copy) + continue; + propChanges.emplace_back(App::DocumentObjectT(prop),std::move(copy)); + } + } + } + } + } + + Base::StateLocker guard(pauseCopyOnChange); + linkProp->setValue(newLinked); + newLinked->Visibility.setValue(false); + srcTouched->setValue(false); + + // Apply the global link changes. + for(const auto &v : propChanges) { + auto prop = v.first.getProperty(); + if(prop) + prop->Paste(*v.second.get()); + } + + // Finally, remove all old copies. oldObjs stores type of DocumentObjectT + // which stores the object name. Before removing, we need to make sure the + // object exists, and it is not some new object with the same name, by + // checking objs which stores DocumentObject pointer. + for (const auto &objT : oldObjs) { + auto obj = objT.getObject(); + if (obj && std::binary_search(objs.begin(), objs.end(), obj)) + obj->getDocument()->removeObject(obj->getNameInDocument()); + } +} + +bool LinkBaseExtension::isLinkedToConfigurableObject() const +{ + if (auto linked = getLinkedObjectValue()) { + std::vector propList; + linked->getPropertyList(propList); + for (auto prop : propList) { + if(prop->testStatus(Property::CopyOnChange) + && prop->getContainer()==linked) + return true; + } + } + return false; +} + bool LinkBaseExtension::isCopyOnChangeProperty(DocumentObject *obj, const App::Property &prop) { if(obj!=prop.getContainer() || !prop.testStatus(App::Property::PropDynamic)) return false; @@ -283,14 +673,30 @@ bool LinkBaseExtension::isCopyOnChangeProperty(DocumentObject *obj, const App::P return group && boost::starts_with(group,_GroupPrefix); } -void LinkBaseExtension::setupCopyOnChange(DocumentObject *parent) { +void LinkBaseExtension::setupCopyOnChange(DocumentObject *parent, bool checkSource) { copyOnChangeConns.clear(); + copyOnChangeSrcConns.clear(); auto linked = getTrueLinkedObject(false); - if(!linked || getLinkCopyOnChangeValue()==0) + if(!linked || getLinkCopyOnChangeValue()==CopyOnChangeDisabled) return; + if (checkSource && !pauseCopyOnChange) { + PropertyLink *source = getLinkCopyOnChangeSourceProperty(); + if (source) { + source->setValue(linked); + if (auto touched = getLinkCopyOnChangeTouchedProperty()) + touched->setValue(false); + } + } + hasCopyOnChange = setupCopyOnChange(parent,linked,©OnChangeConns,hasCopyOnChange); + if (hasCopyOnChange && getLinkCopyOnChangeValue() == CopyOnChangeOwned + && getLinkedObjectValue() + && getLinkedObjectValue() == getLinkCopyOnChangeSourceValue()) + { + makeCopyOnChange(); + } } bool LinkBaseExtension::setupCopyOnChange(DocumentObject *parent, DocumentObject *linked, @@ -371,7 +777,7 @@ bool LinkBaseExtension::setupCopyOnChange(DocumentObject *parent, DocumentObject if(!copyOnChangeConns) return res; - for(auto &v : newProps) { + for(const auto &v : newProps) { // sync configuration properties copyOnChangeConns->push_back(v.second->signalChanged.connect([parent](const Property &prop) { if(!prop.testStatus(Property::CopyOnChange)) @@ -396,15 +802,19 @@ bool LinkBaseExtension::setupCopyOnChange(DocumentObject *parent, DocumentObject void LinkBaseExtension::checkCopyOnChange( App::DocumentObject *parent, const App::Property &prop) { - if(parent->getDocument()->isPerformingTransaction()) + if(!parent || !parent->getDocument() + || parent->getDocument()->isPerformingTransaction()) return; - auto linked = getTrueLinkedObject(false); - if(!linked || getLinkCopyOnChangeValue()==0 + auto linked = getLinkedObjectValue(); + if(!linked || getLinkCopyOnChangeValue()==CopyOnChangeDisabled || !isCopyOnChangeProperty(parent,prop)) return; - if(getLinkCopyOnChangeValue()==2) { + if(getLinkCopyOnChangeValue() == CopyOnChangeOwned || + (getLinkCopyOnChangeValue() == CopyOnChangeTracking + && linked != getLinkCopyOnChangeSourceValue())) + { auto p = linked->getPropertyByName(prop.getName()); if(p && p->getTypeId()==prop.getTypeId()) { std::unique_ptr pcopy(prop.Copy()); @@ -418,21 +828,80 @@ void LinkBaseExtension::checkCopyOnChange( if(!linkedProp || linkedProp->getTypeId()!=prop.getTypeId() || linkedProp->isSame(prop)) return; - auto objs = parent->getDocument()->copyObject({linked},true); + auto copied = makeCopyOnChange(); + if (copied) { + linkedProp = copied->getPropertyByName(prop.getName()); + if(linkedProp && linkedProp->getTypeId()==prop.getTypeId()) { + std::unique_ptr pcopy(prop.Copy()); + if(pcopy) + linkedProp->Paste(*pcopy); + } + } +} + +App::DocumentObject *LinkBaseExtension::makeCopyOnChange() { + auto linked = getLinkedObjectValue(); + if (pauseCopyOnChange || !linked) + return nullptr; + auto parent = getContainer(); + auto srcobjs = getOnChangeCopyObjects(nullptr, linked); + for (auto obj : srcobjs) { + if (obj->testStatus(App::PartialObject)) { + FC_THROWM(Base::RuntimeError, "Cannot copy partial loaded object: " + << obj->getFullName()); + } + } + auto objs = parent->getDocument()->copyObject(srcobjs); if(objs.empty()) - return; + return nullptr; + + monitorOnChangeCopyObjects(srcobjs); linked = objs.back(); linked->Visibility.setValue(false); - linkedProp = linked->getPropertyByName(prop.getName()); - if(linkedProp && linkedProp->getTypeId()==prop.getTypeId()) { - std::unique_ptr pcopy(prop.Copy()); - if(pcopy) - linkedProp->Paste(*pcopy); - } - getLinkCopyOnChangeProperty()->setValue((long)0); + + Base::StateLocker guard(pauseCopyOnChange); getLinkedObjectProperty()->setValue(linked); - getLinkCopyOnChangeProperty()->setValue(2); + if (getLinkCopyOnChangeValue() == CopyOnChangeEnabled) + getLinkCopyOnChangeProperty()->setValue(CopyOnChangeOwned); + + if (auto prop = getLinkCopyOnChangeGroupProperty()) { + if (auto obj = prop->getValue()) { + if (obj->getNameInDocument() && obj->getDocument()) + obj->getDocument()->removeObject(obj->getNameInDocument()); + } + auto group = new LinkGroup; + group->LinkMode.setValue(LinkModeAutoDelete); + getContainer()->getDocument()->addObject(group, "CopyOnChangeGroup"); + prop->setValue(group); + + // The order of the copied objects is in dependency order (because of + // getOnChangeCopyObjects()). We reverse it here so that we can later + // on delete it in reverse order to avoid error (because some parent + // objects may want to delete their own children). + std::reverse(objs.begin(), objs.end()); + group->ElementList.setValues(objs); + } + + return linked; +} + +void LinkBaseExtension::monitorOnChangeCopyObjects( + const std::vector &objs) +{ + copyOnChangeSrcConns.clear(); + if (getLinkCopyOnChangeValue() == CopyOnChangeDisabled) + return; + for(auto obj : objs) { + obj->setStatus(App::ObjectStatus::TouchOnColorChange, true); + copyOnChangeSrcConns.push_back(obj->signalChanged.connect( + [this](const DocumentObject &, const Property &) { + if (auto prop = this->getLinkCopyOnChangeTouchedProperty()) { + if (this->getLinkCopyOnChangeValue() != CopyOnChangeDisabled) + prop->setValue(true); + } + })); + } } App::GroupExtension *LinkBaseExtension::linkedPlainGroup() const { @@ -492,6 +961,8 @@ bool LinkBaseExtension::extensionHasChildElement() const { if(_getElementListValue().size() || (_getElementCountValue() && _getShowElementValue())) return true; + if (getLinkClaimChildValue()) + return false; DocumentObject *linked = getTrueLinkedObject(false); if(linked) { if(linked->hasChildElement()) @@ -560,7 +1031,8 @@ DocumentObject *LinkBaseExtension::getContainer(){ } DocumentObject *LinkBaseExtension::getLink(int depth) const{ - GetApplication().checkLinkDepth(depth,false); + if (!GetApplication().checkLinkDepth(depth,true)) + return nullptr; if(getLinkedObjectProperty()) return getLinkedObjectValue(); return nullptr; @@ -628,15 +1100,18 @@ int LinkBaseExtension::getElementIndex(const char *subname, const char **psubnam // redirect that reference to the first array element auto linked = getTrueLinkedObject(false); if(!linked || !linked->getNameInDocument()) return -1; - std::string sub(subname,dot-subname); if(subname[0]=='$') { - if(strcmp(sub.c_str()+1,linked->Label.getValue())==0) + CharRange sub(subname+1, dot); + if (boost::equals(sub, linked->Label.getValue())) idx = 0; - }else if(sub==linked->getNameInDocument()) - idx = 0; + } else { + CharRange sub(subname, dot); + if (boost::equals(sub, linked->getNameInDocument())) + idx = 0; + } if(idx<0) { // Lastly, try to get sub object directly from the linked object - auto sobj = linked->getSubObject(sub.c_str()); + auto sobj = linked->getSubObject(std::string(subname, dot-subname+1).c_str()); if(!sobj) return -1; if(psubname) @@ -849,8 +1324,44 @@ bool LinkBaseExtension::extensionGetSubObject(DocumentObject *&ret, const char * return true; Base::Matrix4D matNext; - if(mat) matNext = *mat; - ret = linked->getSubObject(subname,pyObj,mat?&matNext:nullptr,false,depth+1); + + // Because of the addition of LinkClaimChild, the linked object may be + // claimed as the first child. Regardless of the current value of + // LinkClaimChild, we must accept sub-object path that contains the linked + // object, because other link property may store such reference. + if (const char* dot=strchr(subname,'.')) { + auto group = getLinkCopyOnChangeGroupValue(); + if (subname[0] == '$') { + CharRange sub(subname+1,dot); + if (group && boost::equals(sub, group->Label.getValue())) + linked = group; + else if(!boost::equals(sub, linked->Label.getValue())) + dot = nullptr; + } else { + CharRange sub(subname,dot); + if (group && boost::equals(sub, group->getNameInDocument())) + linked = group; + else if (!boost::equals(sub, linked->getNameInDocument())) + dot = nullptr; + } + if (dot) { + // Because of external linked object, It is possible for and + // child object to have the exact same internal name or label + // as the parent object. To resolve this potential ambiguity, + // try assuming the current subname is referring to the parent + // (i.e. the linked object), and if it fails, try again below. + if(mat) matNext = *mat; + ret = linked->getSubObject(dot+1,pyObj,mat?&matNext:nullptr,false,depth+1); + if (ret && dot[1]) + subname = dot+1; + } + } + + if (!ret) { + if(mat) matNext = *mat; + ret = linked->getSubObject(subname,pyObj,mat?&matNext:nullptr,false,depth+1); + } + std::string postfix; if(ret) { // do not resolve the link if we are the last referenced object @@ -892,10 +1403,11 @@ void LinkBaseExtension::checkGeoElementMap(const App::DocumentObject *obj, void LinkBaseExtension::onExtendedUnsetupObject() { if(!getElementListProperty()) return; - auto objs = getElementListValue(); - getElementListProperty()->setValue(); - for(auto obj : objs) - detachElement(obj); + detachElements(); + if (auto obj = getLinkCopyOnChangeGroupValue()) { + if(obj->getNameInDocument() && !obj->isRemoving()) + obj->getDocument()->removeObject(obj->getNameInDocument()); + } } DocumentObject *LinkBaseExtension::getTrueLinkedObject( @@ -1281,13 +1793,32 @@ void LinkBaseExtension::update(App::DocumentObject *parent, const Property *prop parseSubName(); syncElementList(); - if(getLinkCopyOnChangeValue()==2) - getLinkCopyOnChangeProperty()->setValue(1); + if(getLinkCopyOnChangeValue()==CopyOnChangeOwned + && !pauseCopyOnChange + && !parent->getDocument()->isPerformingTransaction()) + getLinkCopyOnChangeProperty()->setValue(CopyOnChangeEnabled); else - setupCopyOnChange(parent); + setupCopyOnChange(parent, true); }else if(prop == getLinkCopyOnChangeProperty()) { - setupCopyOnChange(parent); + setupCopyOnChange(parent, getLinkCopyOnChangeSourceValue() == nullptr); + } else if (prop == getLinkCopyOnChangeSourceProperty()) { + if (auto source = getLinkCopyOnChangeSourceValue()) { + this->connCopyOnChangeSource = source->signalChanged.connect( + [this](const DocumentObject & obj, const Property &prop) { + auto src = getLinkCopyOnChangeSourceValue(); + if (src != &obj || getLinkCopyOnChangeValue()==CopyOnChangeDisabled) + return; + if (App::Document::isAnyRestoring() + || obj.testStatus(ObjectStatus::NoTouch) + || (prop.getType() & Prop_Output) + || prop.testStatus(Property::Output)) + return; + if (auto propTouch = getLinkCopyOnChangeTouchedProperty()) + propTouch->setValue(true); + }); + } else + this->connCopyOnChangeSource.disconnect(); }else if(prop == getLinkTransformProperty()) { auto linkPlacement = getLinkPlacementProperty(); @@ -1396,7 +1927,7 @@ void LinkBaseExtension::onExtendedDocumentRestored() { sub.resize(element - sub.c_str()); } std::vector subs; - for(auto &s : subset) + for(const auto &s : subset) subs.push_back(sub + s); xlink->setSubValues(std::move(subs)); } @@ -1409,8 +1940,14 @@ void LinkBaseExtension::onExtendedDocumentRestored() { getScaleVectorProperty()->setValue(s,s,s); } update(parent,getVisibilityListProperty()); - update(parent,getLinkedObjectProperty()); + if (auto prop = getLinkedObjectProperty()) { + Base::StateLocker guard(pauseCopyOnChange); + update(parent,prop); + } + update(parent,getLinkCopyOnChangeSourceProperty()); update(parent,getElementListProperty()); + if (getLinkCopyOnChangeValue() != CopyOnChangeDisabled) + monitorOnChangeCopyObjects(getOnChangeCopyObjects()); } void LinkBaseExtension::_handleChangedPropertyName( @@ -1515,11 +2052,7 @@ void LinkBaseExtension::setLink(int index, DocumentObject *obj, if(obj || !getElementListProperty()) LINK_THROW(Base::RuntimeError,"No PropertyLink or PropertyLinkList configured"); - - auto objs = getElementListValue(); - getElementListProperty()->setValue(); - for(auto thisObj : objs) - detachElement(thisObj); + detachElements(); return; } @@ -1554,6 +2087,16 @@ void LinkBaseExtension::setLink(int index, DocumentObject *obj, xlink->setValue(obj,std::move(subs)); } +void LinkBaseExtension::detachElements() +{ + std::vector objs; + for (auto obj : getElementListValue()) + objs.push_back(obj); + getElementListProperty()->setValue(); + for(const auto &objT : objs) + detachElement(objT.getObject()); +} + void LinkBaseExtension::detachElement(DocumentObject *obj) { if(!obj || !obj->getNameInDocument() || obj->isRemoving()) return; @@ -1642,6 +2185,14 @@ Property *LinkBaseExtension::extensionGetPropertyByName(const char* name) const return nullptr; } +bool LinkBaseExtension::isLinkMutated() const +{ + return getLinkCopyOnChangeValue() != CopyOnChangeDisabled + && getLinkedObjectValue() + && (!getLinkCopyOnChangeSourceValue() + || (getLinkedObjectValue() != getLinkCopyOnChangeSourceValue())); +} + /////////////////////////////////////////////////////////////////////////////////////////// namespace App { diff --git a/src/App/Link.h b/src/App/Link.h index 4241097f16..3d8be848e7 100644 --- a/src/App/Link.h +++ b/src/App/Link.h @@ -24,7 +24,8 @@ #define APP_LINK_H #include - +#include +#include #include "DocumentObject.h" #include "DocumentObjectExtension.h" #include "FeaturePython.h" @@ -89,6 +90,10 @@ public: (LinkTransform, bool, App::PropertyBool, false, \ "Set to false to override linked object's placement", ##__VA_ARGS__) +#define LINK_PARAM_CLAIM_CHILD(...) \ + (LinkClaimChild, bool, App::PropertyBool, false, \ + "Claim the linked object as a child", ##__VA_ARGS__) + #define LINK_PARAM_COPY_ON_CHANGE(...) \ (LinkCopyOnChange, long, App::PropertyEnumeration, ((long)0), \ "Disabled: disable copy on change\n"\ @@ -97,6 +102,16 @@ public: " the link will try to sync any change of the original linked object back to the copy.",\ ##__VA_ARGS__) +#define LINK_PARAM_COPY_ON_CHANGE_SOURCE(...) \ + (LinkCopyOnChangeSource, App::DocumentObject*, App::PropertyLink, 0, "The copy on change source object", ##__VA_ARGS__) + +#define LINK_PARAM_COPY_ON_CHANGE_GROUP(...) \ + (LinkCopyOnChangeGroup, App::DocumentObject*, App::PropertyLink, 0, \ + "Linked to a internal group object for holding on change copies", ##__VA_ARGS__) + +#define LINK_PARAM_COPY_ON_CHANGE_TOUCHED(...) \ + (LinkCopyOnChangeTouched, bool, App::PropertyBool, 0, "Indicating the copy on change source object has been changed", ##__VA_ARGS__) + #define LINK_PARAM_SCALE(...) \ (Scale, double, App::PropertyFloat, 1.0, "Scale factor", ##__VA_ARGS__) @@ -151,6 +166,7 @@ public: LINK_PARAM(PLACEMENT)\ LINK_PARAM(LINK_PLACEMENT)\ LINK_PARAM(OBJECT)\ + LINK_PARAM(CLAIM_CHILD)\ LINK_PARAM(TRANSFORM)\ LINK_PARAM(SCALE)\ LINK_PARAM(SCALE_VECTOR)\ @@ -164,6 +180,9 @@ public: LINK_PARAM(LINK_EXECUTE)\ LINK_PARAM(COLORED_ELEMENTS)\ LINK_PARAM(COPY_ON_CHANGE)\ + LINK_PARAM(COPY_ON_CHANGE_SOURCE)\ + LINK_PARAM(COPY_ON_CHANGE_GROUP)\ + LINK_PARAM(COPY_ON_CHANGE_TOUCHED)\ enum PropIndex { #define LINK_PINDEX_DEFINE(_1,_2,_param) LINK_PINDEX(_param), @@ -201,6 +220,13 @@ public: typedef std::map PropInfoMap; virtual const PropInfoMap &getPropertyInfoMap() const; + enum LinkCopyOnChangeType { + CopyOnChangeDisabled = 0, + CopyOnChangeEnabled = 1, + CopyOnChangeOwned = 2, + CopyOnChangeTracking = 3 + }; + #define LINK_PROP_GET(_1,_2,_param) \ LINK_PTYPE(_param) BOOST_PP_SEQ_CAT((get)(LINK_PNAME(_param))(Value)) () const {\ auto prop = props[LINK_PINDEX(_param)];\ @@ -300,15 +326,46 @@ public: static bool isCopyOnChangeProperty(App::DocumentObject *obj, const Property &prop); + void syncCopyOnChange(); + + /** Options used in setOnChangeCopyObject() + * Multiple options can be combined by bitwise or operator + */ + enum class OnChangeCopyOptions { + /// If set, then exclude the input from object list to copy on change, or else, include the input object. + Exclude = 1, + /// If set , then apply the setting to all links to the input object, or else, apply only to this link. + ApplyAll = 2, + }; + + /** Include or exclude object from list of objects to copy on change + * @param obj: input object + * @param options: control options. @sa OnChangeCopyOptions. + */ + void setOnChangeCopyObject(App::DocumentObject *obj, OnChangeCopyOptions options); + + std::vector getOnChangeCopyObjects( + std::vector *excludes = nullptr, + App::DocumentObject *src = nullptr); + + bool isLinkedToConfigurableObject() const; + + void monitorOnChangeCopyObjects(const std::vector &objs); + + /// Check if the linked object is a copy on change + bool isLinkMutated() const; + protected: void _handleChangedPropertyName(Base::XMLReader &reader, const char * TypeName, const char *PropName); void parseSubName() const; void update(App::DocumentObject *parent, const Property *prop); void checkCopyOnChange(App::DocumentObject *parent, const App::Property &prop); - void setupCopyOnChange(App::DocumentObject *parent); + void setupCopyOnChange(App::DocumentObject *parent, bool checkSource = false); + App::DocumentObject *makeCopyOnChange(); void syncElementList(); void detachElement(App::DocumentObject *obj); + void detachElements(); void checkGeoElementMap(const App::DocumentObject *obj, const App::DocumentObject *linked, PyObject **pyObj, const char *postfix) const; void updateGroup(); @@ -329,10 +386,14 @@ protected: mutable bool enableLabelCache; bool hasOldSubElement; - mutable bool checkingProperty = false; - std::vector copyOnChangeConns; + std::vector copyOnChangeSrcConns; bool hasCopyOnChange; + + mutable bool checkingProperty = false; + bool pauseCopyOnChange = false; + + boost::signals2::scoped_connection connCopyOnChangeSource; }; /////////////////////////////////////////////////////////////////////////// @@ -460,6 +521,7 @@ public: #define LINK_PARAMS_LINK \ LINK_PARAM_EXT_TYPE(OBJECT, App::PropertyXLink)\ + LINK_PARAM_EXT(CLAIM_CHILD)\ LINK_PARAM_EXT(TRANSFORM)\ LINK_PARAM_EXT(LINK_PLACEMENT)\ LINK_PARAM_EXT(PLACEMENT)\ @@ -468,6 +530,9 @@ public: LINK_PARAM_EXT(LINK_EXECUTE)\ LINK_PARAM_EXT_ATYPE(COLORED_ELEMENTS,App::Prop_Hidden)\ LINK_PARAM_EXT(COPY_ON_CHANGE)\ + LINK_PARAM_EXT_TYPE(COPY_ON_CHANGE_SOURCE, App::PropertyXLink)\ + LINK_PARAM_EXT(COPY_ON_CHANGE_GROUP)\ + LINK_PARAM_EXT(COPY_ON_CHANGE_TOUCHED)\ LINK_PROPS_DEFINE(LINK_PARAMS_LINK) @@ -508,6 +573,9 @@ public: LINK_PARAM_EXT(LINK_PLACEMENT)\ LINK_PARAM_EXT(PLACEMENT)\ LINK_PARAM_EXT(COPY_ON_CHANGE)\ + LINK_PARAM_EXT_TYPE(COPY_ON_CHANGE_SOURCE, App::PropertyXLink)\ + LINK_PARAM_EXT(COPY_ON_CHANGE_GROUP)\ + LINK_PARAM_EXT(COPY_ON_CHANGE_TOUCHED)\ // defines the actual properties LINK_PROPS_DEFINE(LINK_PARAMS_ELEMENT) @@ -566,6 +634,66 @@ typedef App::FeaturePythonT LinkGroupPython; } //namespace App +ENABLE_BITMASK_OPERATORS(App::Link::OnChangeCopyOptions) + +/*[[[cog +import LinkParams +LinkParams.declare() +]]]*/ + +namespace App { +/** Convenient class to obtain App::Link related parameters + + * The parameters are under group "User parameter:BaseApp/Preferences/Link" + * + * This class is auto generated by LinkParams.py. Modify that file + * instead of this one, if you want to add any parameter. You need + * to install Cog Python package for code generation: + * @code + * pip install cogapp + * @endcode + * + * Once modified, you can regenerate the header and the source file, + * @code + * python3 -m cogapp -r Link.h Link.cpp + * @endcode + * + * You can add a new parameter by adding lines in LinkParams.py. Available + * parameter types are 'Int, UInt, String, Bool, Float'. For example, to add + * a new Int type parameter, + * @code + * ParamInt(parameter_name, default_value, documentation, on_change=False) + * @endcode + * + * If there is special handling on parameter change, pass in on_change=True. + * And you need to provide a function implementation in Link.cpp with + * the following signature. + * @code + * void LinkParams:onChanged() + * @endcode + */ +class AppExport LinkParams { +public: + static ParameterGrp::handle getHandle(); + + //@{ + /** Accessor for parameter CopyOnChangeApplyToAll + * + * Stores the last user choice of whether to apply CopyOnChange setup to all link + * that links to the same configurable object + */ + static const bool & getCopyOnChangeApplyToAll(); + static const bool & defaultCopyOnChangeApplyToAll(); + static void removeCopyOnChangeApplyToAll(); + static void setCopyOnChangeApplyToAll(const bool &v); + static const char *docCopyOnChangeApplyToAll(); + //@} + +}; + +} // namespace App +//[[[end]]] + #if defined(__clang__) # pragma clang diagnostic pop diff --git a/src/App/LinkParams.py b/src/App/LinkParams.py new file mode 100644 index 0000000000..ec02477535 --- /dev/null +++ b/src/App/LinkParams.py @@ -0,0 +1,30 @@ +import sys +from os import sys, path + +# Actual code generation is done in Base/param_utils.py. + +# The following code is to import param_util.py without needing __init__.py in Base directory +sys.path.append(path.join(path.dirname(path.dirname(path.abspath(__file__))), 'Base')) +import params_utils + +from params_utils import ParamBool, ParamInt, ParamString, ParamUInt, ParamFloat + +NameSpace = 'App' +ClassName = 'LinkParams' +ParamPath = 'User parameter:BaseApp/Preferences/Link' +ClassDoc = 'Convenient class to obtain App::Link related parameters' +HeaderFile = 'Link.h' +SourceFile = 'Link.cpp' + +Params = [ + ParamBool('CopyOnChangeApplyToAll', True, '''\ +Stores the last user choice of whether to apply CopyOnChange setup to all link +that links to the same configurable object'''), +] + +def declare(): + params_utils.declare_begin(sys.modules[__name__], header=False) + params_utils.declare_end(sys.modules[__name__]) + +def define(): + params_utils.define(sys.modules[__name__], header=False) diff --git a/src/Gui/ViewProviderLink.cpp b/src/Gui/ViewProviderLink.cpp index 858d73d202..a45315f2ba 100644 --- a/src/Gui/ViewProviderLink.cpp +++ b/src/Gui/ViewProviderLink.cpp @@ -45,6 +45,7 @@ #include #endif +#include #include #include #include @@ -67,12 +68,15 @@ #include "ViewParams.h" #include "ViewProviderGeometryObject.h" +#include "ActionFunction.h" +#include "Command.h" FC_LOG_LEVEL_INIT("App::Link", true, true) using namespace Gui; using namespace Base; +typedef boost::iterator_range CharRange; //////////////////////////////////////////////////////////////////////////// static inline bool appendPathSafe(SoPath *path, SoNode *node) { @@ -218,6 +222,10 @@ public: return pcLinked->getObject()->getNameInDocument(); } + const char *getLinkedLabel() const { + return pcLinked->getObject()->Label.getValue(); + } + const char *getLinkedNameSafe() const { if(isLinked()) return getLinkedName(); @@ -965,7 +973,7 @@ void LinkView::renderDoubleSide(bool enable) { if(enable) { if(!pcShapeHints) { pcShapeHints = new SoShapeHints; - pcShapeHints->vertexOrdering = SoShapeHints::UNKNOWN_ORDERING; + pcShapeHints->vertexOrdering = SoShapeHints::CLOCKWISE; pcShapeHints->shapeType = SoShapeHints::UNKNOWN_SHAPE_TYPE; pcLinkRoot->insertChild(pcShapeHints,0); } @@ -1485,14 +1493,53 @@ bool LinkView::linkGetDetailPath(const char *subname, SoFullPath *path, SoDetail if(!subname || *subname==0) return true; auto len = path->getLength(); if(nodeArray.empty()) { - appendPath(path,pcLinkRoot); - }else{ - int idx = App::LinkBaseExtension::getArrayIndex(subname,&subname); + if(!appendPathSafe(path,pcLinkRoot)) + return false; + } else { + int idx = -1; + if (subname[0]>='0' && subname[0]<='9') { + idx = App::LinkBaseExtension::getArrayIndex(subname,&subname); + } else { + while(1) { + const char *dot = strchr(subname,'.'); + if(!dot) + break; + int i = 0; + if (subname[0] == '$') { + CharRange name(subname+1,dot); + for(auto &info : nodeArray) { + if(info->isLinked() && boost::equals(name,info->linkInfo->getLinkedLabel())) { + idx = i; + break; + } + ++i; + } + } else { + CharRange name(subname,dot); + for(auto &info : nodeArray) { + if(info->isLinked() && boost::equals(name,info->linkInfo->getLinkedName())) { + idx = i; + break; + } + ++i; + } + } + + if(idx<0) + return false; + + subname = dot+1; + if(!subname[0] || nodeArray[idx]->isGroup==0) + break; + idx = -1; + } + } + if(idx<0 || idx>=(int)nodeArray.size()) return false; - auto &info = *nodeArray[idx]; - appendPath(path,pcLinkRoot); + if(!appendPathSafe(path,pcLinkRoot)) + return false; if(info.groupIndex>=0 && !getGroupHierarchy(info.groupIndex,path)) return false; appendPath(path,info.pcSwitch); @@ -1561,7 +1608,7 @@ void LinkView::unlink(LinkInfoPtr info) { else { for(auto &info : nodeArray) { int idx; - if(!info->isLinked() && + if(info->isLinked() && (idx=info->pcRoot->findChild(pcLinkedRoot))>=0) info->pcRoot->removeChild(idx); } @@ -1792,6 +1839,10 @@ void ViewProviderLink::updateData(const App::Property *prop) { return inherited::updateData(prop); } +static inline bool canScale(const Base::Vector3d &v) { + return fabs(v.x)>1e-7 && fabs(v.y)>1e-7 && fabs(v.z)>1e-7; +} + void ViewProviderLink::updateDataPrivate(App::LinkBaseExtension *ext, const App::Property *prop) { if(!prop) return; if(prop == &ext->_ChildCache) { @@ -1807,8 +1858,10 @@ void ViewProviderLink::updateDataPrivate(App::LinkBaseExtension *ext, const App: }else if(prop==ext->getScaleProperty() || prop==ext->getScaleVectorProperty()) { if(!prop->testStatus(App::Property::User3)) { const auto &v = ext->getScaleVector(); - pcTransform->scaleFactor.setValue(v.x,v.y,v.z); - linkView->renderDoubleSide(v.x*v.y*v.z < 0); + if(canScale(v)) + pcTransform->scaleFactor.setValue(v.x,v.y,v.z); + SbMatrix matrix = convert(ext->getTransform(false)); + linkView->renderDoubleSide(matrix.det3() < 0); } }else if(prop == ext->getPlacementProperty() || prop == ext->getLinkPlacementProperty()) { auto propLinkPlacement = ext->getLinkPlacementProperty(); @@ -1816,8 +1869,19 @@ void ViewProviderLink::updateDataPrivate(App::LinkBaseExtension *ext, const App: const auto &pla = static_cast(prop)->getValue(); ViewProviderGeometryObject::updateTransform(pla, pcTransform); const auto &v = ext->getScaleVector(); - pcTransform->scaleFactor.setValue(v.x,v.y,v.z); - linkView->renderDoubleSide(v.x*v.y*v.z < 0); + if(canScale(v)) + pcTransform->scaleFactor.setValue(v.x,v.y,v.z); + SbMatrix matrix = convert(ext->getTransform(false)); + linkView->renderDoubleSide(matrix.det3() < 0); + } + }else if(prop == ext->getLinkCopyOnChangeGroupProperty()) { + if (auto group = ext->getLinkCopyOnChangeGroupValue()) { + auto vp = Base::freecad_dynamic_cast( + Application::Instance->getViewProvider(group)); + if (vp) { + vp->hide(); + vp->ShowInTree.setValue(false); + } } }else if(prop == ext->getLinkedObjectProperty()) { @@ -1918,9 +1982,9 @@ void ViewProviderLink::updateDataPrivate(App::LinkBaseExtension *ext, const App: if(touched.empty()) { for(int i=0;igetSize();++i) { Base::Matrix4D mat; - if(propPlacements->getSize()>i) + if(propPlacements && propPlacements->getSize()>i) mat = (*propPlacements)[i].toMatrix(); - if(propScales && propScales->getSize()>i) { + if(propScales && propScales->getSize()>i && canScale((*propScales)[i])) { Base::Matrix4D s; s.scale((*propScales)[i]); mat *= s; @@ -1932,9 +1996,9 @@ void ViewProviderLink::updateDataPrivate(App::LinkBaseExtension *ext, const App: if(i<0 || i>=linkView->getSize()) continue; Base::Matrix4D mat; - if(propPlacements->getSize()>i) + if(propPlacements && propPlacements->getSize()>i) mat = (*propPlacements)[i].toMatrix(); - if(propScales && propScales->getSize()>i) { + if(propScales && propScales->getSize()>i && canScale((*propScales)[i])) { Base::Matrix4D s; s.scale((*propScales)[i]); mat *= s; @@ -2076,24 +2140,33 @@ ViewProvider *ViewProviderLink::getLinkedView( std::vector ViewProviderLink::claimChildren(void) const { auto ext = getLinkExtension(); + std::vector ret; + if(ext && !ext->_getShowElementValue() && ext->_getElementCountValue()) { // in array mode without element objects, we'd better not show the // linked object's children to avoid inconsistent behavior on selection. // We claim the linked object instead - std::vector ret; if(ext) { - auto obj = ext->getTrueLinkedObject(true); + auto obj = ext->getLinkedObjectValue(); if(obj) ret.push_back(obj); } - return ret; - } else if(hasElements(ext) || isGroup(ext)) - return ext->getElementListValue(); - if(!hasSubName) { + } else if(hasElements(ext) || isGroup(ext)) { + ret = ext->getElementListValue(); + if (ext->_getElementCountValue() + && ext->getLinkClaimChildValue() + && ext->getLinkedObjectValue()) + ret.insert(ret.begin(), ext->getLinkedObjectValue()); + } else if(!hasSubName) { auto linked = getLinkedView(true); - if(linked) - return linked->claimChildren(); + if(linked) { + ret = linked->claimChildren(); + if (ext->getLinkClaimChildValue() && ext->getLinkedObjectValue()) + ret.insert(ret.begin(), ext->getLinkedObjectValue()); + } } - return std::vector(); + if (ext && ext->getLinkCopyOnChangeGroupValue()) + ret.insert(ret.begin(), ext->getLinkCopyOnChangeGroupValue()); + return ret; } bool ViewProviderLink::canDragObject(App::DocumentObject* obj) const { @@ -2279,12 +2352,29 @@ bool ViewProviderLink::getDetailPath( return false; } std::string _subname; - if(subname && subname[0] && - (isGroup(ext,true) || hasElements(ext) || ext->getElementCountValue())) { - int index = ext->getElementIndex(subname,&subname); - if(index>=0) { - _subname = std::to_string(index)+'.'+subname; - subname = _subname.c_str(); + if(subname && subname[0]) { + if (auto linked = ext->getLinkedObjectValue()) { + if (const char *dot = strchr(subname,'.')) { + if(subname[0]=='$') { + CharRange sub(subname+1, dot); + if (!boost::equals(sub, linked->Label.getValue())) + dot = nullptr; + } else { + CharRange sub(subname, dot); + if (!boost::equals(sub, linked->getNameInDocument())) + dot = nullptr; + } + if (dot && linked->getSubObject(dot+1)) + subname = dot+1; + } + } + + if (isGroup(ext,true) || hasElements(ext) || ext->getElementCountValue()) { + int index = ext->getElementIndex(subname,&subname); + if(index>=0) { + _subname = std::to_string(index)+'.'+subname; + subname = _subname.c_str(); + } } } if(linkView->linkGetDetailPath(subname,pPath,det)) @@ -2295,7 +2385,27 @@ bool ViewProviderLink::getDetailPath( bool ViewProviderLink::onDelete(const std::vector &) { auto element = freecad_dynamic_cast(getObject()); - return !element || element->canDelete(); + if (element && !element->canDelete()) + return false; + auto ext = getLinkExtension(); + if (ext->isLinkMutated()) { + auto linked = ext->getLinkedObjectValue(); + auto doc = ext->getContainer()->getDocument(); + if (linked->getDocument() == doc) { + std::deque objs; + for (auto obj : ext->getOnChangeCopyObjects(nullptr, linked)) { + if (obj->getDocument() == doc) { + // getOnChangeCopyObjects() returns object in depending + // order. So we delete it in reverse to avoid error + // reported by some parent object failing to find child + objs.emplace_front(obj->getNameInDocument()); + } + } + for (auto &name : objs) + doc->removeObject(name.c_str()); + } + } + return true; } bool ViewProviderLink::canDelete(App::DocumentObject *obj) const { @@ -2334,12 +2444,122 @@ void ViewProviderLink::setupContextMenu(QMenu* menu, QObject* receiver, const ch if (!ext) return; + _setupContextMenu(ext, menu, receiver, member); + Gui::ActionFunction* func = nullptr; + if (ext->isLinkedToConfigurableObject()) { + if (ext->getLinkCopyOnChangeValue() == 0) { + auto submenu = menu->addMenu(QObject::tr("Copy on change")); + auto act = submenu->addAction(QObject::tr("Enable")); + act->setToolTip(QObject::tr( + "Enable auto copy of linked object when its configuration is changed")); + act->setData(-1); + if (!func) func = new Gui::ActionFunction(menu); + func->trigger(act, [ext](){ + try { + App::AutoTransaction guard("Enable Link copy on change"); + ext->getLinkCopyOnChangeProperty()->setValue(1); + Command::updateActive(); + } catch (Base::Exception &e) { + e.ReportException(); + } + }); + act = submenu->addAction(QObject::tr("Tracking")); + act->setToolTip(QObject::tr( + "Copy the linked object when its configuration is changed.\n" + "Also auto redo the copy if the original linked object is changed.\n")); + act->setData(-1); + func->trigger(act, [ext](){ + try { + App::AutoTransaction guard("Enable Link tracking"); + ext->getLinkCopyOnChangeProperty()->setValue(3); + Command::updateActive(); + } catch (Base::Exception &e) { + e.ReportException(); + } + }); + } + } + + if (ext->getLinkCopyOnChangeValue() != 2 + && ext->getLinkCopyOnChangeValue() != 0) { + QAction *act = menu->addAction( + QObject::tr("Disable copy on change")); + act->setData(-1); + if (!func) func = new Gui::ActionFunction(menu); + func->trigger(act, [ext](){ + try { + App::AutoTransaction guard("Disable copy on change"); + ext->getLinkCopyOnChangeProperty()->setValue((long)0); + Command::updateActive(); + } catch (Base::Exception &e) { + e.ReportException(); + } + }); + } + + if (ext->isLinkMutated()) { + QAction* act = menu->addAction(QObject::tr("Rerefresh configurable object")); + act->setToolTip(QObject::tr( + "Synchronize the original configurable source object by\n" + "creating a new deep copy. Note that any changes made to\n" + "the current copy will be lost.\n")); + act->setData(-1); + if (!func) func = new Gui::ActionFunction(menu); + func->trigger(act, [ext](){ + try { + App::AutoTransaction guard("Link refresh"); + ext->syncCopyOnChange(); + Command::updateActive(); + } catch (Base::Exception &e) { + e.ReportException(); + } + }); + } +} + +void ViewProviderLink::_setupContextMenu( + App::LinkBaseExtension *ext, QMenu* menu, QObject* receiver, const char* member) +{ if(linkEdit(ext)) { - linkView->getLinkedView()->setupContextMenu(menu,receiver,member); - } else if(ext->getPlacementProperty() || ext->getLinkPlacementProperty()) { - QIcon iconObject = mergeGreyableOverlayIcons(Gui::BitmapFactory().pixmap("Std_TransformManip.svg")); - QAction* act = menu->addAction(iconObject, QObject::tr("Transform"), receiver, member); - act->setData(QVariant((int)ViewProvider::Transform)); + if (auto linkvp = Base::freecad_dynamic_cast(linkView->getLinkedView())) + linkvp->_setupContextMenu(ext, menu, receiver, member); + else + linkView->getLinkedView()->setupContextMenu(menu,receiver,member); + } + + if(ext->getLinkedObjectProperty() + && ext->_getShowElementProperty() + && ext->_getElementCountValue() > 1) + { + auto action = menu->addAction(QObject::tr("Toggle array elements"), [ext] { + try { + App::AutoTransaction guard(QT_TRANSLATE_NOOP("Command", "Toggle array elements")); + ext->getShowElementProperty()->setValue(!ext->getShowElementValue()); + Command::updateActive(); + } catch (Base::Exception &e) { + e.ReportException(); + } + }); + action->setToolTip(QObject::tr( + "Change whether show each link array element as individual objects")); + } + + if((ext->getPlacementProperty() && !ext->getPlacementProperty()->isReadOnly()) + || (ext->getLinkPlacementProperty() && !ext->getLinkPlacementProperty()->isReadOnly())) + { + bool found = false; + for(auto action : menu->actions()) { + if(action->data().toInt() == ViewProvider::Transform) { + found = true; + break; + } + } + if (!found) { + QIcon iconObject = mergeGreyableOverlayIcons(Gui::BitmapFactory().pixmap("Std_TransformManip.svg")); + QAction* act = menu->addAction(iconObject, QObject::tr("Transform"), receiver, member); + act->setToolTip(QObject::tr("Transform at the origin of the placement")); + act->setData(QVariant((int)ViewProvider::Transform)); + } } if(ext->getColoredElementsProperty()) { diff --git a/src/Gui/ViewProviderLink.h b/src/Gui/ViewProviderLink.h index e6fc04a44a..85f9c8a310 100644 --- a/src/Gui/ViewProviderLink.h +++ b/src/Gui/ViewProviderLink.h @@ -273,6 +273,7 @@ protected: void setEditViewer(View3DInventorViewer*, int ModNum) override; void unsetEditViewer(View3DInventorViewer*) override; bool linkEdit(const App::LinkBaseExtension *ext=nullptr) const; + void _setupContextMenu(App::LinkBaseExtension *ext, QMenu*, QObject*, const char*); enum LinkType { LinkTypeNone,