From 90f10fd2b4e4ec4852f6aa18f5f4080ee5926952 Mon Sep 17 00:00:00 2001 From: "Zheng, Lei" Date: Sat, 29 Jun 2019 17:24:14 +0800 Subject: [PATCH] PropertyLinks: refactor property link API * Create new class PropertyLinkBase as the common parent class for all type of links. See the class document for details of its API. * Added new link scope 'Hidden' that is ignored during normal object dependency calculation, but still keep tracks of object remove, relabel, etc. * There is a new concept called 'Shadow subname' introduced in this patch, which will only be meaningful in future topological naming feature. See [here](https://git.io/fjXKR) for more details. * DocumentObject added a new API adjustRelativeLink() and a helper resolveRelativeLink() function. --- src/App/Application.cpp | 11 + src/App/DocumentObject.cpp | 85 + src/App/DocumentObject.h | 66 +- src/App/DocumentObjectPy.xml | 5 + src/App/DocumentObjectPyImp.cpp | 16 + src/App/Property.h | 11 + src/App/PropertyLinks.cpp | 3844 +++++++++++++++++++++++++++++-- src/App/PropertyLinks.h | 974 +++++++- 8 files changed, 4741 insertions(+), 271 deletions(-) diff --git a/src/App/Application.cpp b/src/App/Application.cpp index c5a69897a2f1..00488e15610d 100644 --- a/src/App/Application.cpp +++ b/src/App/Application.cpp @@ -1577,18 +1577,28 @@ void Application::initTypes(void) App ::PropertyUUID ::init(); App ::PropertyFont ::init(); App ::PropertyStringList ::init(); + App ::PropertyLinkBase ::init(); + App ::PropertyLinkListBase ::init(); App ::PropertyLink ::init(); App ::PropertyLinkChild ::init(); App ::PropertyLinkGlobal ::init(); + App ::PropertyLinkHidden ::init(); App ::PropertyLinkSub ::init(); App ::PropertyLinkSubChild ::init(); App ::PropertyLinkSubGlobal ::init(); + App ::PropertyLinkSubHidden ::init(); App ::PropertyLinkList ::init(); App ::PropertyLinkListChild ::init(); App ::PropertyLinkListGlobal ::init(); + App ::PropertyLinkListHidden ::init(); App ::PropertyLinkSubList ::init(); App ::PropertyLinkSubListChild ::init(); App ::PropertyLinkSubListGlobal ::init(); + App ::PropertyLinkSubListHidden ::init(); + App ::PropertyXLink ::init(); + App ::PropertyXLinkSub ::init(); + App ::PropertyXLinkSubList ::init(); + App ::PropertyXLinkContainer ::init(); App ::PropertyMatrix ::init(); App ::PropertyVector ::init(); App ::PropertyVectorDistance ::init(); @@ -1608,6 +1618,7 @@ void Application::initTypes(void) App ::PropertyFile ::init(); App ::PropertyFileIncluded ::init(); App ::PropertyPythonObject ::init(); + App ::PropertyExpressionContainer ::init(); App ::PropertyExpressionEngine ::init(); // Extension classes diff --git a/src/App/DocumentObject.cpp b/src/App/DocumentObject.cpp index cca0c05eb8e5..d1d0cfa28a71 100644 --- a/src/App/DocumentObject.cpp +++ b/src/App/DocumentObject.cpp @@ -1008,6 +1008,91 @@ DocumentObject *DocumentObject::resolve(const char *subname, return obj; } +DocumentObject *DocumentObject::resolveRelativeLink(std::string &subname, + DocumentObject *&link, std::string &linkSub) const +{ + if(!link || !link->getNameInDocument() || !getNameInDocument()) + return 0; + auto ret = const_cast(this); + if(link != ret) { + auto sub = subname.c_str(); + auto nextsub = sub; + for(auto dot=strchr(nextsub,'.');dot;nextsub=dot+1,dot=strchr(nextsub,'.')) { + std::string subcheck(sub,nextsub-sub); + subcheck += link->getNameInDocument(); + subcheck += '.'; + if(getSubObject(subcheck.c_str())==link) { + ret = getSubObject(std::string(sub,dot+1-sub).c_str()); + if(!ret) + return 0; + subname = std::string(dot+1); + break; + } + } + return ret; + } + + size_t pos=0,linkPos=0; + std::string linkssub,ssub; + do { + linkPos = linkSub.find('.',linkPos); + if(linkPos == std::string::npos) { + link = 0; + return 0; + } + ++linkPos; + pos = subname.find('.',pos); + if(pos == std::string::npos) { + subname.clear(); + ret = 0; + break; + } + ++pos; + }while(subname.compare(0,pos,linkSub,0,linkPos)==0); + + if(pos != std::string::npos) { + ret = getSubObject(subname.substr(0,pos).c_str()); + if(!ret) { + link = 0; + return 0; + } + subname = subname.substr(pos); + } + if(linkPos) { + link = link->getSubObject(linkSub.substr(0,linkPos).c_str()); + if(!link) + return 0; + linkSub = linkSub.substr(linkPos); + } + return ret; +} + +bool DocumentObject::adjustRelativeLinks( + const std::set &inList, + std::set *visited) +{ + if(visited) + visited->insert(this); + + bool touched = false; + std::vector props; + getPropertyList(props); + for(auto prop : props) { + auto linkProp = Base::freecad_dynamic_cast(prop); + if(linkProp && linkProp->adjustLink(inList)) + touched = true; + } + if(visited) { + for(auto obj : getOutList()) { + if(!visited->count(obj)) { + if(obj->adjustRelativeLinks(inList,visited)) + touched = true; + } + } + } + return touched; +} + const std::string &DocumentObject::hiddenMarker() { static std::string marker("!hide"); return marker; diff --git a/src/App/DocumentObject.h b/src/App/DocumentObject.h index f25362159adf..f2ba8c09e49e 100644 --- a/src/App/DocumentObject.h +++ b/src/App/DocumentObject.h @@ -388,6 +388,70 @@ class AppExport DocumentObject: public App::TransactionalObject std::string *childName=0, const char **subElement=0, PyObject **pyObj=0, Base::Matrix4D *mat=0, bool transform=true, int depth=0) const; + /** Resolve a link reference that is relative to this object reference + * + * @param subname: on input, this is the subname reference to the object + * that is to be assigned a link. On output, the reference may be offseted + * to be rid off any common parent. + * @param link: on input, this is the top parent of the link reference. On + * output, it may be altered to one of its child to be rid off any common + * parent. + * @param linkSub: on input, this the subname of the link reference. On + * output, it may be offseted to be rid off any common parent. + * + * @return The corrected top parent of the object that is to be assigned the + * link. If the output 'subname' is empty, then return the object itself. + * + * To avoid any cyclic reference, an object must not be assign a link to any + * of the object in its parent. This function can be used to resolve any + * common parents of an object and its link target. + * + * For example, with the following object hierarchy + * + * Group + * |--Group001 + * | |--Box + * | |--Cylinder + * |--Group002 + * |--Box001 + * |--Cylinder001 + * + * If you want add a link of Group.Group002.Box001 to Group.Group001, you + * can call with the following parameter (which are usually obtained from + * Selection.getSelectionEx(), check usage in TreeWidget::onDropEvent()): + * std::string subname("Group002."); + * auto link = Group; + * std::string linkSub("Group001.Box001."); + * parent = Group.resolveRelativeLink(subname,link,linkSub); + * + * The resolving result is as follow: + * return -> Group001 + * subname -> "" + * link -> Group002 + * linkSub -> "Box001." + * + * The common parent 'Group' is removed. + */ + App::DocumentObject *resolveRelativeLink(std::string &subname, + App::DocumentObject *&link, std::string &linkSub) const; + + /** Called to adjust link properties to avoid cyclic links + * + * @param inList: the recursive in-list of the future parent object, + * including the parent itself. + * @param visited: optional set holding the visited objects. If null then + * only this object is adjusted, or else all object inside the out-list of + * this object will be checked. + * + * @return Return whether the object has been modified + * + * This function tries to adjust any relative link properties (i.e. link + * properties that can hold subnames) to avoid cyclic when added to the + * future parent. + */ + virtual bool adjustRelativeLinks(const std::set &inList, + std::set *visited=0); + /** Allow object to redirect a subname path * * @param ss: input as the current subname path from \a topParent leading @@ -404,7 +468,7 @@ class AppExport DocumentObject: public App::TransactionalObject virtual bool redirectSubName(std::ostringstream &ss, DocumentObject *topParent, DocumentObject *child) const; - /** Sepecial marker to mark the object has hidden + /** Sepecial marker to mark the object as hidden * * It is used by Gui::ViewProvider::getElementColors(), but exposed here * for convenience diff --git a/src/App/DocumentObjectPy.xml b/src/App/DocumentObjectPy.xml index 8204ffa02e81..6489b5afdb6a 100644 --- a/src/App/DocumentObjectPy.xml +++ b/src/App/DocumentObjectPy.xml @@ -176,6 +176,11 @@ non-object sub-element name if any. + + + adjustRelativeLinks(parent,recursive=True) -- auto correct potential cyclic dependencies + + A list of all objects this object links to. diff --git a/src/App/DocumentObjectPyImp.cpp b/src/App/DocumentObjectPyImp.cpp index 7becef5e3e94..23e7fe7eba4e 100644 --- a/src/App/DocumentObjectPyImp.cpp +++ b/src/App/DocumentObjectPyImp.cpp @@ -791,3 +791,19 @@ Py::List DocumentObjectPy::getParents() const { ret.append(Py::TupleN(Py::Object(v.first->getPyObject(),true),Py::String(v.second))); return ret; } + +PyObject *DocumentObjectPy::adjustRelativeLinks(PyObject *args) { + PyObject *pyobj; + PyObject *recursive = Py_True; + if (!PyArg_ParseTuple(args, "O!|O",&DocumentObjectPy::Type,&pyobj,&recursive)) + return NULL; + PY_TRY { + auto obj = static_cast(pyobj)->getDocumentObjectPtr(); + auto inList = obj->getInListEx(true); + inList.insert(obj); + std::set visited; + return Py::new_reference_to(Py::Boolean( + getDocumentObjectPtr()->adjustRelativeLinks(inList, + PyObject_IsTrue(recursive)?&visited:nullptr))); + }PY_CATCH +} diff --git a/src/App/Property.h b/src/App/Property.h index 8d41a0f16d60..2e5c881f4a22 100644 --- a/src/App/Property.h +++ b/src/App/Property.h @@ -146,6 +146,12 @@ class AppExport Property : public Base::Persistence /// Get valid paths for this property; used by auto completer virtual void getPaths(std::vector & paths) const; + /// Called at the begining of Document::afterRestore(). See comments there. + virtual void afterRestore() {} + + /// Called before calling DocumentObject::onDocumentRestored() + virtual void onContainerRestored() {} + /** Property status handling */ //@{ @@ -189,6 +195,11 @@ class AppExport Property : public Base::Persistence /// Paste the value from the property (mainly for Undo/Redo and transactions) virtual void Paste(const Property &from) = 0; + /// Called when a child property has changed value + virtual void hasSetChildValue(Property &) {} + /// Called before a child property changing value + virtual void aboutToSetChildValue(Property &) {} + friend class PropertyContainer; friend struct PropertyData; friend class DynamicProperty; diff --git a/src/App/PropertyLinks.cpp b/src/App/PropertyLinks.cpp index 83e72b39f710..347d9020ade4 100644 --- a/src/App/PropertyLinks.cpp +++ b/src/App/PropertyLinks.cpp @@ -28,31 +28,359 @@ # include #endif +#include +#include +#include + +#include +#include + /// Here the FreeCAD includes sorted by Base,App,Gui...... #include #include #include #include #include +#include +#include "Application.h" #include "DocumentObject.h" #include "DocumentObjectPy.h" #include "Document.h" #include "PropertyLinks.h" +FC_LOG_LEVEL_INIT("PropertyLinks",true,true); + using namespace App; using namespace Base; using namespace std; +//************************************************************************** +//************************************************************************** +// PropertyLinkBase +//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +TYPESYSTEM_SOURCE_ABSTRACT(App::PropertyLinkBase , App::Property) + +static std::unordered_map > _LabelMap; +PropertyLinkBase::PropertyLinkBase() +{} + +PropertyLinkBase::~PropertyLinkBase() { + unregisterLabelReferences(); + unregisterElementReference(); +} + +void PropertyLinkBase::setAllowExternal(bool allow) { + setFlag(LinkAllowExternal,allow); +} + +void PropertyLinkBase::hasSetValue() { + auto owner = dynamic_cast(getContainer()); + if(owner) + owner->clearOutListCache(); + Property::hasSetValue(); +} + +void PropertyLinkBase::unregisterElementReference() { +} + +void PropertyLinkBase::unregisterLabelReferences() +{ + for(auto &label : _LabelRefs) { + auto it = _LabelMap.find(label); + if(it!=_LabelMap.end()) { + it->second.erase(this); + if(it->second.empty()) + _LabelMap.erase(it); + } + } + _LabelRefs.clear(); +} + +void PropertyLinkBase::getLabelReferences(std::vector &subs,const char *subname) { + const char *dot; + for(;(subname=strchr(subname,'$'))!=0; subname=dot+1) { + ++subname; + dot = strchr(subname,'.'); + if(!dot) break; + subs.emplace_back(subname,dot-subname); + } +} + +void PropertyLinkBase::registerLabelReferences(std::vector &&labels, bool reset) { + if(reset) + unregisterLabelReferences(); + for(auto &label : labels) { + auto res = _LabelRefs.insert(std::move(label)); + if(res.second) + _LabelMap[*res.first].insert(this); + } +} + +void PropertyLinkBase::checkLabelReferences(const std::vector &subs, bool reset) { + if(reset) + unregisterLabelReferences(); + std::vector labels; + for(auto &sub : subs) { + labels.clear(); + getLabelReferences(labels,sub.c_str()); + registerLabelReferences(std::move(labels),false); + } +} + +std::string PropertyLinkBase::updateLabelReference(const App::DocumentObject *parent, + const char *subname, App::DocumentObject *obj, const std::string &ref, const char *newLabel) +{ + if(!obj || !obj->getNameInDocument() || !parent || !parent->getNameInDocument()) + return std::string(); + + // Because the label is allowed to be the same across different + // hierarchy, we have to search for all occurance, and make sure the + // referenced sub-object at the found hierarchy is actually the given + // object. + for(const char *pos=subname; ((pos=strstr(pos,ref.c_str()))!=0); pos+=ref.size()) { + auto sub = std::string(subname,pos+ref.size()-subname); + auto sobj = parent->getSubObject(sub.c_str()); + if(sobj == obj) { + sub = subname; + sub.replace(pos+1-subname,ref.size()-2,newLabel); + return sub; + } + } + return std::string(); +} + +std::vector > > +PropertyLinkBase::updateLabelReferences(App::DocumentObject *obj, const char *newLabel) +{ + std::vector > > ret; + if(!obj || !obj->getNameInDocument()) + return ret; + auto it = _LabelMap.find(obj->Label.getStrValue()); + if(it == _LabelMap.end()) + return ret; + std::string ref("$"); + ref += obj->Label.getValue(); + ref += '.'; + std::vector props; + props.reserve(it->second.size()); + props.insert(props.end(),it->second.begin(),it->second.end()); + for(auto prop : props) { + if(!prop->getContainer()) + continue; + std::unique_ptr copy(prop->CopyOnLabelChange(obj,ref,newLabel)); + if(copy) + ret.emplace_back(prop,std::move(copy)); + } + return ret; +} + +static std::string propertyName(const Property *prop) { + if(!prop) + return std::string(); + if(!prop->getContainer() || !prop->getName()) { + auto xlink = Base::freecad_dynamic_cast(prop); + if(xlink) + return propertyName(xlink->parent()); + } + return prop->getFullName(); +} + +void PropertyLinkBase::updateElementReferences(DocumentObject *feature, bool reverse) { + (void)feature; + (void)reverse; +} + +void PropertyLinkBase::_registerElementReference(App::DocumentObject *obj, std::string &sub, ShadowSub &shadow) +{ + (void)obj; + (void)sub; + (void)shadow; +} + +class StringGuard { +public: + StringGuard(char *c) + :c(c) + { + v1 = c[0]; + v2 = c[1]; + c[0] = '.'; + c[1] = 0; + } + ~StringGuard() + { + c[0] = v1; + c[1] = v2; + } + + char *c; + char v1; + char v2; +}; + +void PropertyLinkBase::restoreLabelReference(const DocumentObject *obj, + std::string &subname, ShadowSub *shadow) +{ + std::ostringstream ss; + char *sub = &subname[0]; + char *next=sub; + for(char *dot=strchr(next,'.');dot;next=dot+1,dot=strchr(next,'.')) { + if(dot!=next && dot[-1]!='@') + continue; + DocumentObject *sobj; + try { + StringGuard guard(dot-1); + sobj = obj->getSubObject(subname.c_str()); + if(!sobj) { + FC_ERR("Failed to restore lable reference " << obj->getFullName() + << '.' << ss.str()); + return; + } + }catch(...){ + throw; + } + ss.write(sub,next-sub); + ss << '$' << sobj->Label.getStrValue() << '.'; + sub = dot+1; + } + if(sub == subname.c_str()) + return; + + size_t count = sub-subname.c_str(); + const auto &newSub = ss.str(); + if(shadow && shadow->second.size()>=count) + shadow->second = newSub + (shadow->second.c_str()+count); + if(shadow && shadow->first.size()>=count) + shadow->first = newSub + (shadow->first.c_str()+count); + subname = newSub + sub; +} + +bool PropertyLinkBase::_updateElementReference(DocumentObject *feature, + App::DocumentObject *obj, std::string &sub, ShadowSub &shadow, + bool reverse, bool notify) +{ + (void)feature; + (void)obj; + (void)reverse; + (void)notify; + shadow.second = sub; + return false; +} + +std::pair +PropertyLinkBase::tryReplaceLink(const PropertyContainer *owner, DocumentObject *obj, + const DocumentObject *parent, DocumentObject *oldObj, DocumentObject *newObj, const char *subname) +{ + std::pair res; + res.first = 0; + + if(oldObj == obj) { + if(owner == parent) { + // Do not throw on sub object error yet. It's better to let + // recompute find out the error and point out the affected objects + // to user. +#if 0 + if(subname && subname[0] && !newObj->getSubObject(subname)) { + FC_THROWM(Base::RuntimeError, + "Sub-object '" << newObj->getFullName() + << '.' << subname << "' not found when replacing link in " + << owner->getFullName() << '.' << getName()); + } +#endif + res.first = newObj; + if(subname) res.second = subname; + return res; + } + return res; + } + if(!subname || !subname[0]) + return res; + + App::DocumentObject *prev = obj; + std::size_t prevPos = 0; + std::string sub = subname; + for(auto pos=sub.find('.');pos!=std::string::npos;pos=sub.find('.',pos)) { + ++pos; + char c = sub[pos]; + sub[pos] = 0; + auto sobj = obj->getSubObject(sub.c_str()); + sub[pos] = c; + if(!sobj) + break; + if(sobj == oldObj) { + if(prev == parent) { +#if 0 + if(sub[pos] && !newObj->getSubObject(sub.c_str()+pos)) { + FC_THROWM(Base::RuntimeError, + "Sub-object '" << newObj->getFullName() + << '.' << (sub.c_str()+pos) << "' not found when replacing link in " + << owner->getFullName() << '.' << getName()); + } +#endif + if(sub[prevPos] == '$') + sub.replace(prevPos+1,pos-1-prevPos,newObj->Label.getValue()); + else + sub.replace(prevPos,pos-1-prevPos,newObj->getNameInDocument()); + res.first = obj; + res.second = std::move(sub); + return res; + } + break; + }else if(prev == parent) + break; + prev = sobj; + prevPos = pos; + } + return res; +} + +std::pair > +PropertyLinkBase::tryReplaceLinkSubs(const PropertyContainer *owner, + DocumentObject *obj, const DocumentObject *parent, DocumentObject *oldObj, + DocumentObject *newObj, const std::vector &subs) +{ + std::pair > res; + res.first = 0; + + auto r = tryReplaceLink(owner,obj,parent,oldObj,newObj); + if(r.first) { + res.first = r.first; + res.second = subs; + return res; + } + for(auto it=subs.begin();it!=subs.end();++it) { + auto r = tryReplaceLink(owner,obj,parent,oldObj,newObj,it->c_str()); + if(r.first) { + if(!res.first) { + res.first = r.first; + res.second.insert(res.second.end(),subs.begin(),it); + } + res.second.push_back(std::move(r.second)); + }else if(res.first) + res.second.push_back(*it); + } + return res; +} + +//************************************************************************** +//************************************************************************** +// PropertyLinkListBase +//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +TYPESYSTEM_SOURCE_ABSTRACT(App::PropertyLinkListBase , App::PropertyLinkBase) + //************************************************************************** //************************************************************************** // PropertyLink //++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ -TYPESYSTEM_SOURCE(App::PropertyLink, App::Property) -TYPESYSTEM_SOURCE(App::PropertyLinkChild, App::PropertyLink) -TYPESYSTEM_SOURCE(App::PropertyLinkGlobal, App::PropertyLink) +TYPESYSTEM_SOURCE(App::PropertyLink, App::PropertyLinkBase) +TYPESYSTEM_SOURCE(App::PropertyLinkChild , App::PropertyLink) +TYPESYSTEM_SOURCE(App::PropertyLinkGlobal , App::PropertyLink) +TYPESYSTEM_SOURCE(App::PropertyLinkHidden , App::PropertyLink) //************************************************************************** // Construction/Destruction @@ -67,11 +395,21 @@ PropertyLink::PropertyLink() PropertyLink::~PropertyLink() { - + resetLink(); +} + +//************************************************************************** +// Base class implementer + +void PropertyLink::resetLink() { //in case this property gets dynamically removed #ifndef USE_OLD_DAG // maintain the back link in the DocumentObject class if it is from a document object - if (_pcLink && getContainer() && getContainer()->isDerivedFrom(App::DocumentObject::getClassTypeId())) { + if (_pcScope!=LinkScope::Hidden && + _pcLink && + getContainer() && + getContainer()->isDerivedFrom(App::DocumentObject::getClassTypeId())) + { App::DocumentObject* parent = static_cast(getContainer()); // before accessing internals make sure the object is not about to be destroyed // otherwise the backlink contains dangling pointers @@ -81,19 +419,19 @@ PropertyLink::~PropertyLink() } } #endif - + _pcLink = 0; } -//************************************************************************** -// Base class implementer - void PropertyLink::setValue(App::DocumentObject * lValue) { + auto parent = dynamic_cast(getContainer()); + if(!testFlag(LinkAllowExternal) && parent && lValue && parent->getDocument()!=lValue->getDocument()) + throw Base::ValueError("PropertyLink does not support external object"); + aboutToSetValue(); #ifndef USE_OLD_DAG // maintain the back link in the DocumentObject class if it is from a document object - if (getContainer() && getContainer()->isDerivedFrom(App::DocumentObject::getClassTypeId())) { - App::DocumentObject* parent = static_cast(getContainer()); + if (_pcScope!=LinkScope::Hidden && parent) { // before accessing internals make sure the object is not about to be destroyed // otherwise the backlink contains dangling pointers if (!parent->testStatus(ObjectStatus::Destroy)) { @@ -144,7 +482,7 @@ void PropertyLink::setPyObject(PyObject *value) void PropertyLink::Save (Base::Writer &writer) const { - writer.Stream() << writer.ind() << "getNameInDocument():"") <<"\"/>" << std::endl; + writer.Stream() << writer.ind() << "getExportName():"") <<"\"/>" << std::endl; } void PropertyLink::Restore(Base::XMLReader &reader) @@ -152,7 +490,7 @@ void PropertyLink::Restore(Base::XMLReader &reader) // read my element reader.readElement("Link"); // get the value of my attribute - std::string name = reader.getAttribute("value"); + std::string name = reader.getName(reader.getAttribute("value")); // Property not in a DocumentObject! assert(getContainer()->getTypeId().isDerivedFrom(App::DocumentObject::getClassTypeId()) ); @@ -197,13 +535,45 @@ void PropertyLink::Paste(const Property &from) setValue(static_cast(from)._pcLink); } +void PropertyLink::getLinks(std::vector &objs, + bool all, std::vector *subs, bool newStyle) const +{ + (void)newStyle; + (void)subs; + if((all||_pcScope!=LinkScope::Hidden) && _pcLink && _pcLink->getNameInDocument()) + objs.push_back(_pcLink); +} + +void PropertyLink::breakLink(App::DocumentObject *obj, bool clear) { + if(_pcLink == obj || (clear && getContainer()==obj)) + setValue(0); +} + +bool PropertyLink::adjustLink(const std::set &inList) { + (void)inList; + return false; +} + +Property *PropertyLink::CopyOnLinkReplace(const App::DocumentObject *parent, + App::DocumentObject *oldObj, App::DocumentObject *newObj) const +{ + auto res = tryReplaceLink(getContainer(),_pcLink,parent,oldObj,newObj); + if(res.first) { + auto p = new PropertyLink(); + p->_pcLink = res.first; + return p; + } + return 0; +} + //************************************************************************** // PropertyLinkList //++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ -TYPESYSTEM_SOURCE(App::PropertyLinkList, App::PropertyLists) +TYPESYSTEM_SOURCE(App::PropertyLinkList, App::PropertyLinkListBase) TYPESYSTEM_SOURCE(App::PropertyLinkListChild, App::PropertyLinkList) TYPESYSTEM_SOURCE(App::PropertyLinkListGlobal, App::PropertyLinkList) +TYPESYSTEM_SOURCE(App::PropertyLinkListHidden, App::PropertyLinkList) //************************************************************************** // Construction/Destruction @@ -219,7 +589,11 @@ PropertyLinkList::~PropertyLinkList() //in case this property gety dynamically removed #ifndef USE_OLD_DAG //maintain the back link in the DocumentObject class - if (!_lValueList.empty() && getContainer() && getContainer()->isDerivedFrom(App::DocumentObject::getClassTypeId())) { + if (_pcScope!=LinkScope::Hidden && + !_lValueList.empty() && + getContainer() && + getContainer()->isDerivedFrom(App::DocumentObject::getClassTypeId())) + { App::DocumentObject* parent = static_cast(getContainer()); // before accessing internals make sure the object is not about to be destroyed // otherwise the backlink contains dangling pointers @@ -236,56 +610,77 @@ PropertyLinkList::~PropertyLinkList() void PropertyLinkList::setSize(int newSize) { + for(int i=newSize;i<(int)_lValueList.size();++i) { + auto obj = _lValueList[i]; + if(!obj && !obj->getNameInDocument()) + continue; + _nameMap.erase(obj->getNameInDocument()); +#ifndef USE_OLD_DAG + if (_pcScope!=LinkScope::Hidden) + obj->_removeBackLink(static_cast(getContainer())); +#endif + } _lValueList.resize(newSize); } -int PropertyLinkList::getSize(void) const -{ - return static_cast(_lValueList.size()); +void PropertyLinkList::setSize(int newSize, const_reference def) { + auto oldSize = getSize(); + setSize(newSize); + for(auto i=oldSize;i=0 && idx<(int)_lValueList.size()) { + obj = _lValueList[idx]; + if(obj == value) return; + } + + if(!value || !value->getNameInDocument()) + throw Base::ValueError("invalid document object"); + + _nameMap.clear(); + #ifndef USE_OLD_DAG - //maintain the back link in the DocumentObject class if (getContainer() && getContainer()->isDerivedFrom(App::DocumentObject::getClassTypeId())) { App::DocumentObject* parent = static_cast(getContainer()); // before accessing internals make sure the object is not about to be destroyed // otherwise the backlink contains dangling pointers - if (!parent->testStatus(ObjectStatus::Destroy)) { - for(auto *obj : _lValueList) { - if (obj) - obj->_removeBackLink(parent); - } - if (lValue) - lValue->_addBackLink(parent); + if (!parent->testStatus(ObjectStatus::Destroy) && _pcScope!=LinkScope::Hidden) { + if(obj) + obj->_removeBackLink(static_cast(getContainer())); + if(value) + value->_addBackLink(static_cast(getContainer())); } } #endif - - if (lValue){ - aboutToSetValue(); - _lValueList.resize(1); - _lValueList[0] = lValue; - hasSetValue(); + + inherited::set1Value(idx,value); +} + +void PropertyLinkList::setValues(const std::vector& lValue) { + if(lValue.size()==1 && !lValue[0]) { + // one null element means clear, as backward compatibility for old code + setValues(std::vector()); + return; } - else { - aboutToSetValue(); - _lValueList.clear(); - hasSetValue(); + + auto parent = Base::freecad_dynamic_cast(getContainer()); + for(auto obj : lValue) { + if(!obj || !obj->getNameInDocument()) + throw Base::ValueError("PropertyLinkList: invalid document object"); + if(!testFlag(LinkAllowExternal) && parent && parent->getDocument()!=obj->getDocument()) + throw Base::ValueError("PropertyLinkList does not support external object"); } -} + _nameMap.clear(); -void PropertyLinkList::setValues(const std::vector& lValue) -{ - aboutToSetValue(); #ifndef USE_OLD_DAG //maintain the back link in the DocumentObject class - if (getContainer() && getContainer()->isDerivedFrom(App::DocumentObject::getClassTypeId())) { - App::DocumentObject* parent = static_cast(getContainer()); + if (parent) { // before accessing internals make sure the object is not about to be destroyed // otherwise the backlink contains dangling pointers - if (!parent->testStatus(ObjectStatus::Destroy)) { + if (!parent->testStatus(ObjectStatus::Destroy) && _pcScope!=LinkScope::Hidden) { for(auto *obj : _lValueList) { if (obj) obj->_removeBackLink(parent); @@ -297,8 +692,7 @@ void PropertyLinkList::setValues(const std::vector& lValue) } } #endif - _lValueList = lValue; - hasSetValue(); + inherited::setValues(lValue); } PyObject *PropertyLinkList::getPyObject(void) @@ -310,42 +704,23 @@ PyObject *PropertyLinkList::getPyObject(void) Py::List sequence(count); #endif for (int i = 0; igetPyObject())); + auto obj = _lValueList[i]; + if(obj && obj->getNameInDocument()) + sequence.setItem(i, Py::asObject(_lValueList[i]->getPyObject())); + else + sequence.setItem(i, Py::None()); } return Py::new_reference_to(sequence); } -void PropertyLinkList::setPyObject(PyObject *value) -{ - if (PyTuple_Check(value) || PyList_Check(value)) { - Py::Sequence list(value); - Py::Sequence::size_type size = list.size(); - std::vector values; - values.resize(size); - - for (Py::Sequence::size_type i = 0; i < size; i++) { - Py::Object item = list[i]; - if (!PyObject_TypeCheck(*item, &(DocumentObjectPy::Type))) { - std::string error = std::string("type in list must be 'DocumentObject', not "); - error += (*item)->ob_type->tp_name; - throw Base::TypeError(error); - } - - values[i] = static_cast(*item)->getDocumentObjectPtr(); - } - - setValues(values); - } - else if (PyObject_TypeCheck(value, &(DocumentObjectPy::Type))) { - DocumentObjectPy *pcObject = static_cast(value); - setValue(pcObject->getDocumentObjectPtr()); - } - else { +DocumentObject *PropertyLinkList::getPyValue(PyObject *item) const { + if (!PyObject_TypeCheck(item, &(DocumentObjectPy::Type))) { std::string error = std::string("type must be 'DocumentObject' or list of 'DocumentObject', not "); - error += value->ob_type->tp_name; + error += item->ob_type->tp_name; throw Base::TypeError(error); } + return static_cast(item)->getDocumentObjectPtr(); } void PropertyLinkList::Save(Base::Writer &writer) const @@ -355,7 +730,7 @@ void PropertyLinkList::Save(Base::Writer &writer) const for (int i = 0; igetNameInDocument() << "\"/>" << endl; + writer.Stream() << writer.ind() << "getExportName() << "\"/>" << endl; else writer.Stream() << writer.ind() << "" << endl; } @@ -384,7 +759,7 @@ void PropertyLinkList::Restore(Base::XMLReader &reader) values.reserve(count); for (int i = 0; i < count; i++) { reader.readElement("Link"); - std::string name = reader.getAttribute("value"); + std::string name = reader.getName(reader.getAttribute("value")); // In order to do copy/paste it must be allowed to have defined some // referenced objects in XML which do not exist anymore in the new // document. Thus, we should silently ignore this. @@ -395,8 +770,8 @@ void PropertyLinkList::Restore(Base::XMLReader &reader) if (child) values.push_back(child); else if (reader.isVerbose()) - Base::Console().Warning("Lost link to '%s' while loading, maybe " - "an object was not loaded correctly\n", name.c_str()); + FC_WARN("Lost link to " << (document?document->getName():"") << " " << name + << " while loading, maybe an object was not loaded correctly"); } reader.readEndElement("LinkList"); @@ -405,6 +780,38 @@ void PropertyLinkList::Restore(Base::XMLReader &reader) setValues(values); } +Property *PropertyLinkList::CopyOnLinkReplace(const App::DocumentObject *parent, + App::DocumentObject *oldObj, App::DocumentObject *newObj) const +{ + std::vector links; + bool copied = false; + bool found = false; + for(auto it=_lValueList.begin();it!=_lValueList.end();++it) { + auto res = tryReplaceLink(getContainer(),*it,parent,oldObj,newObj); + if(res.first) { + found = true; + if(!copied) { + copied = true; + links.insert(links.end(),_lValueList.begin(),it); + } + links.push_back(res.first); + } else if(*it == newObj) { + // in case newObj already exists here, we shall remove all existing + // entry, and instert it to take over oldObj's position. + if(!copied) { + copied = true; + links.insert(links.end(),_lValueList.begin(),it); + } + }else if(copied) + links.push_back(*it); + } + if(!found) + return 0; + auto p= new PropertyLinkList(); + p->_lValueList = std::move(links); + return p; +} + Property *PropertyLinkList::Copy(void) const { PropertyLinkList *p = new PropertyLinkList(); @@ -414,7 +821,10 @@ Property *PropertyLinkList::Copy(void) const void PropertyLinkList::Paste(const Property &from) { - setValues(dynamic_cast(from)._lValueList); + if(!from.isDerivedFrom(PropertyLinkList::getClassTypeId())) + throw Base::TypeError("Incompatible property to paste to"); + + setValues(static_cast(from)._lValueList); } unsigned int PropertyLinkList::getMemSize(void) const @@ -422,13 +832,65 @@ unsigned int PropertyLinkList::getMemSize(void) const return static_cast(_lValueList.size() * sizeof(App::DocumentObject *)); } +DocumentObject *PropertyLinkList::find(const std::string &name, int *pindex) const { + if(_nameMap.empty() || _nameMap.size()>_lValueList.size()) { + _nameMap.clear(); + for(int i=0;i<(int)_lValueList.size();++i) { + auto obj = _lValueList[i]; + if(obj && obj->getNameInDocument()) + _nameMap[obj->getNameInDocument()] = i; + } + } + auto it = _nameMap.find(name); + if(it == _nameMap.end()) + return 0; + if(pindex) *pindex = it->second; + return _lValueList[it->second]; +} + +void PropertyLinkList::getLinks(std::vector &objs, + bool all, std::vector *subs, bool newStyle) const +{ + (void)subs; + (void)newStyle; + if(all||_pcScope!=LinkScope::Hidden) { + objs.reserve(objs.size()+_lValueList.size()); + for(auto obj : _lValueList) { + if(obj && obj->getNameInDocument()) + objs.push_back(obj); + } + } +} + +void PropertyLinkList::breakLink(App::DocumentObject *obj, bool clear) { + if(clear && getContainer()==obj) { + setValues({}); + return; + } + std::vector values; + values.reserve(_lValueList.size()); + for(auto o : _lValueList) { + if(o != obj) + values.push_back(o); + } + if(values.size()!=_lValueList.size()) + setValues(values); +} + +bool PropertyLinkList::adjustLink(const std::set &inList) { + (void)inList; + return false; +} + + //************************************************************************** // PropertyLinkSub //++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ -TYPESYSTEM_SOURCE(App::PropertyLinkSub, App::Property) +TYPESYSTEM_SOURCE(App::PropertyLinkSub, App::PropertyLinkBase) TYPESYSTEM_SOURCE(App::PropertyLinkSubChild, App::PropertyLinkSub) TYPESYSTEM_SOURCE(App::PropertyLinkSubGlobal, App::PropertyLinkSub) +TYPESYSTEM_SOURCE(App::PropertyLinkSubHidden, App::PropertyLinkSub) //************************************************************************** // Construction/Destruction @@ -449,7 +911,7 @@ PropertyLinkSub::~PropertyLinkSub() App::DocumentObject* parent = static_cast(getContainer()); // before accessing internals make sure the object is not about to be destroyed // otherwise the backlink contains dangling pointers - if (!parent->testStatus(ObjectStatus::Destroy)) { + if (!parent->testStatus(ObjectStatus::Destroy) && _pcScope!=LinkScope::Hidden) { if (_pcLinkSub) _pcLinkSub->_removeBackLink(parent); } @@ -457,18 +919,28 @@ PropertyLinkSub::~PropertyLinkSub() #endif } -//************************************************************************** -// Base class implementer +void PropertyLinkSub::setValue(App::DocumentObject * lValue, + const std::vector &SubList, std::vector &&shadows) +{ + setValue(lValue,std::vector(SubList),std::move(shadows)); +} -void PropertyLinkSub::setValue(App::DocumentObject * lValue, const std::vector &SubList) +void PropertyLinkSub::setValue(App::DocumentObject * lValue, + std::vector &&subs, std::vector &&shadows) { + auto parent = Base::freecad_dynamic_cast(getContainer()); + if(lValue) { + if(!lValue->getNameInDocument()) + throw Base::ValueError("PropertyLinkSub: invalid document object"); + if(!testFlag(LinkAllowExternal) && parent && parent->getDocument()!=lValue->getDocument()) + throw Base::ValueError("PropertyLinkSub does not support external object"); + } aboutToSetValue(); #ifndef USE_OLD_DAG - if (getContainer() && getContainer()->isDerivedFrom(App::DocumentObject::getClassTypeId())) { - App::DocumentObject* parent = static_cast(getContainer()); + if(parent) { // before accessing internals make sure the object is not about to be destroyed // otherwise the backlink contains dangling pointers - if (!parent->testStatus(ObjectStatus::Destroy)) { + if (!parent->testStatus(ObjectStatus::Destroy) && _pcScope!=LinkScope::Hidden) { if (_pcLinkSub) _pcLinkSub->_removeBackLink(parent); if (lValue) @@ -477,7 +949,12 @@ void PropertyLinkSub::setValue(App::DocumentObject * lValue, const std::vector& PropertyLinkSub::getSubValues(void) const return _cSubList; } -std::vector PropertyLinkSub::getSubValuesStartsWith(const char* starter) const +static inline const std::string &getSubNameWithStyle(const std::string &subName, + const PropertyLinkBase::ShadowSub &shadow, bool newStyle) +{ + if(!newStyle) { + if(shadow.second.size()) + return shadow.second; + }else if(shadow.first.size()) + return shadow.first; + return subName; +} + +std::vector PropertyLinkSub::getSubValues(bool newStyle) const { + assert(_cSubList.size() == _ShadowSubList.size()); + std::vector ret; + ret.reserve(_cSubList.size()); + for(size_t i=0;i<_ShadowSubList.size();++i) + ret.push_back(getSubNameWithStyle(_cSubList[i],_ShadowSubList[i],newStyle)); + return ret; +} + +std::vector PropertyLinkSub::getSubValuesStartsWith(const char* starter, bool newStyle) const { + (void)newStyle; + std::vector temp; for(std::vector::const_iterator it=_cSubList.begin();it!=_cSubList.end();++it) if(strncmp(starter,it->c_str(),strlen(starter))==0) @@ -511,7 +1010,7 @@ PyObject *PropertyLinkSub::getPyObject(void) Py::List list(static_cast(_cSubList.size())); if (_pcLinkSub) { _pcLinkSub->getPyObject(); - tup[0] = Py::Object(_pcLinkSub->getPyObject()); + tup[0] = Py::asObject(_pcLinkSub->getPyObject()); for(unsigned int i = 0;i<_cSubList.size(); i++) list[i] = Py::String(_cSubList[i]); tup[1] = list; @@ -532,12 +1031,14 @@ void PropertyLinkSub::setPyObject(PyObject *value) Py::Sequence seq(value); if(seq.size() == 0) setValue(NULL); + else if(seq.size()!=2) + throw Base::ValueError("Expect input sequence of size 2"); else if (PyObject_TypeCheck(seq[0].ptr(), &(DocumentObjectPy::Type))) { DocumentObjectPy *pcObj = (DocumentObjectPy*)seq[0].ptr(); if (seq[1].isString()) { std::vector vals; vals.push_back((std::string)Py::String(seq[1])); - setValue(pcObj->getDocumentObjectPtr(),vals); + setValue(pcObj->getDocumentObjectPtr(),std::move(vals)); } else if (seq[1].isSequence()) { Py::Sequence list(seq[1]); @@ -545,7 +1046,7 @@ void PropertyLinkSub::setPyObject(PyObject *value) unsigned int i=0; for (Py::Sequence::iterator it = list.begin();it!=list.end();++it,++i) vals[i] = Py::String(*it); - setValue(pcObj->getDocumentObjectPtr(),vals); + setValue(pcObj->getDocumentObjectPtr(),std::move(vals)); } else { std::string error = std::string("type of second element in tuple must be str or sequence of str"); @@ -568,77 +1069,496 @@ void PropertyLinkSub::setPyObject(PyObject *value) } } -void PropertyLinkSub::Save (Base::Writer &writer) const +static bool updateLinkReference(App::PropertyLinkBase *prop, + App::DocumentObject *feature, bool reverse, bool notify, + App::DocumentObject *link, std::vector &subs, std::vector &mapped, + std::vector &shadows) { - const char* internal_name = ""; - // it can happen that the object is still alive but is not part of the document anymore and thus - // returns 0 - if (_pcLinkSub && _pcLinkSub->getNameInDocument()) - internal_name = _pcLinkSub->getNameInDocument(); - writer.Stream() << writer.ind() << "" << std::endl; - writer.incInd(); - for(unsigned int i = 0;i<_cSubList.size(); i++) - writer.Stream() << writer.ind() << "" << endl; - writer.decInd(); - writer.Stream() << writer.ind() << "" << endl ; + if(!feature) { + shadows.clear(); + prop->unregisterElementReference(); + } + shadows.resize(subs.size()); + if(!link || !link->getNameInDocument()) + return false; + auto owner = dynamic_cast(prop->getContainer()); + if(owner && owner->isRestoring()) + return false; + int i=0; + bool touched = false; + for(auto &sub : subs) { + if(prop->_updateElementReference( + feature,link,sub,shadows[i++],reverse,notify&&!touched)) + touched = true; + } + if(!touched) + return false; + for(int idx : mapped) { + if(idx<(int)subs.size() && shadows[idx].first.size()) + subs[idx] = shadows[idx].first; + } + mapped.clear(); + if(owner && feature) + owner->onUpdateElementReference(prop); + return true; } -void PropertyLinkSub::Restore(Base::XMLReader &reader) -{ - // read my element - reader.readElement("LinkSub"); - // get the values of my attributes - std::string name = reader.getAttribute("value"); - int count = reader.getAttributeAsInteger("count"); +void PropertyLinkSub::afterRestore() { + _ShadowSubList.resize(_cSubList.size()); + if(!testFlag(LinkRestoreLabel) ||!_pcLinkSub || !_pcLinkSub->getNameInDocument()) + return; + setFlag(LinkRestoreLabel,false); + for(std::size_t i=0;i<_cSubList.size();++i) + restoreLabelReference(_pcLinkSub,_cSubList[i],&_ShadowSubList[i]); +} - // Property not in a DocumentObject! - assert(getContainer()->getTypeId().isDerivedFrom(App::DocumentObject::getClassTypeId()) ); +void PropertyLinkSub::onContainerRestored() { + unregisterElementReference(); + if(!_pcLinkSub || !_pcLinkSub->getNameInDocument()) + return; + for(std::size_t i=0;i<_cSubList.size();++i) + _registerElementReference(_pcLinkSub,_cSubList[i],_ShadowSubList[i]); +} - std::vector values(count); - for (int i = 0; i < count; i++) { - reader.readElement("Sub"); - values[i] = reader.getAttribute("value"); - } +void PropertyLinkSub::updateElementReference(DocumentObject *feature, bool reverse, bool notify) { + if(!updateLinkReference(this,feature,reverse,notify,_pcLinkSub,_cSubList,_mapped,_ShadowSubList)) + return; + if(notify) + hasSetValue(); +} - reader.readEndElement("LinkSub"); +bool PropertyLinkSub::referenceChanged() const { + return !_mapped.empty(); +} - DocumentObject *pcObject; - if (!name.empty()) { - App::Document* document = static_cast(getContainer())->getDocument(); - pcObject = document ? document->getObject(name.c_str()) : 0; - if (!pcObject) { - if (reader.isVerbose()) { - Base::Console().Warning("Lost link to '%s' while loading, maybe " - "an object was not loaded correctly\n",name.c_str()); - } +std::string PropertyLinkBase::importSubName(Base::XMLReader &reader, const char *sub, bool &restoreLabel) { + if(!reader.doNameMapping()) + return sub; + std::ostringstream str; + for(const char *dot=strchr(sub,'.');dot;sub=dot+1,dot=strchr(sub,'.')) { + size_t count = dot-sub; + const char *tail = "."; + if(count && dot[-1] == '@') { + // tail=='@' means we are exporting a label reference. So retain + // this marker so that the label can be restored in afterRestore(). + tail = "@."; + --count; + restoreLabel = true; } - setValue(pcObject,values); - } - else { - setValue(0); + str << reader.getName(std::string(sub,count).c_str()) << tail; } + str << sub; + return str.str(); } -Property *PropertyLinkSub::Copy(void) const +const char *PropertyLinkBase::exportSubName(std::string &output, + const App::DocumentObject *obj, const char *sub, bool first_obj) { - PropertyLinkSub *p= new PropertyLinkSub(); - p->_pcLinkSub = _pcLinkSub; - p->_cSubList = _cSubList; - return p; -} - -void PropertyLinkSub::Paste(const Property &from) + std::ostringstream str; + const char *res = sub; + + if(!sub || !sub[0]) + return res; + + bool touched = false; + if(first_obj) { + auto dot = strchr(sub,'.'); + if(!dot) + return res; + const char *hash; + for(hash=sub;hashgetNameInDocument()) + doc = obj->getDocument(); + } + if(!doc) { + FC_ERR("Failed to get document for the first object in " << sub); + return res; + } + obj = doc->getObject(std::string(sub,dot-sub).c_str()); + if(!obj || !obj->getNameInDocument()) + return res; + if(hash) { + if(!obj->isExporting()) + str << doc->getName() << '#'; + sub = hash+1; + } + }else if(!obj || !obj->getNameInDocument()) + return res; + + for(const char *dot=strchr(sub,'.');dot;sub=dot+1,dot=strchr(sub,'.')) { + // name with trailing '.' + auto name = std::string(sub,dot-sub+1); + if(first_obj) + first_obj = false; + else + obj = obj->getSubObject(name.c_str()); + if(!obj || !obj->getNameInDocument()) { + FC_WARN("missing sub object '" << name << "' in '" << sub <<"'"); + break; + } + if(obj->isExporting()) { + if(name[0] == '$') { + if(name.compare(1,name.size()-2,obj->Label.getValue())!=0) { + str << obj->getExportName(true) << "@."; + touched = true; + continue; + } + } else if(name.compare(0,name.size()-1,obj->getNameInDocument())==0) { + str << obj->getExportName(true) << '.'; + touched = true; + continue; + } + } + str << name; + } + if(!touched) + return res; + str << sub; + output = str.str(); + return output.c_str(); +} + +App::DocumentObject *PropertyLinkBase::tryImport(const App::Document *doc, + const App::DocumentObject *obj, const std::map &nameMap) +{ + if(doc && obj && obj->getNameInDocument()) { + auto it = nameMap.find(obj->getExportName(true)); + if(it!=nameMap.end()) { + obj = doc->getObject(it->second.c_str()); + if(!obj) + FC_THROWM(Base::RuntimeError,"Cannot find import object " << it->second); + } + } + return const_cast(obj); +} + +std::string PropertyLinkBase::tryImportSubName(const App::DocumentObject *obj, const char *_subname, + const App::Document *doc, const std::map &nameMap) +{ + if(!doc || !obj || !obj->getNameInDocument()) + return std::string(); + + std::ostringstream ss; + std::string subname(_subname); + char *sub = &subname[0]; + char *next = sub; + for(char *dot=strchr(next,'.');dot;next=dot+1,dot=strchr(next,'.')) { + StringGuard guard(dot); + auto sobj = obj->getSubObject(subname.c_str()); + if(!sobj) { + FC_ERR("Failed to restore lable reference " << obj->getFullName() << '.' << subname); + return std::string(); + } + dot[0] = 0; + if(next[0] == '$') { + if(strcmp(next+1,sobj->Label.getValue())!=0) + continue; + } else if(strcmp(next,sobj->getNameInDocument())!=0) { + continue; + } + auto it = nameMap.find(sobj->getExportName(true)); + if(it == nameMap.end()) + continue; + auto imported = doc->getObject(it->second.c_str()); + if(!imported) + FC_THROWM(RuntimeError, "Failed to find imported object " << it->second); + ss.write(sub,next-sub); + if(next[0] == '$') + ss << '$' << imported->Label.getStrValue() << '.'; + else + ss << it->second << '.'; + sub = dot+1; + } + if(sub!=subname.c_str()) + return ss.str(); + return std::string(); +} + +#define ATTR_SHADOWED "shadowed" +#define ATTR_SHADOW "shadow" +#define ATTR_MAPPED "mapped" + +// We do not have topo naming yet, ignore shadow sub for now +#define IGNORE_SHADOW true + +void PropertyLinkSub::Save (Base::Writer &writer) const +{ + assert(_cSubList.size() == _ShadowSubList.size()); + + std::string internal_name; + // it can happen that the object is still alive but is not part of the document anymore and thus + // returns 0 + if (_pcLinkSub && _pcLinkSub->getNameInDocument()) + internal_name = _pcLinkSub->getExportName(); + writer.Stream() << writer.ind() << "" << std::endl; + writer.incInd(); + auto owner = dynamic_cast(getContainer()); + bool exporting = owner && owner->isExporting(); + for(unsigned int i = 0;i<_cSubList.size(); i++) { + const auto &shadow = _ShadowSubList[i]; + // shadow.second stores the old style element name. For backward + // compatibility reason, we shall store the old name into attribute + // 'value' whenver possible. + const auto &sub = shadow.second.empty()?_cSubList[i]:shadow.second; + writer.Stream() << writer.ind() << "" << endl; + } + writer.decInd(); + writer.Stream() << writer.ind() << "" << endl ; +} + +void PropertyLinkSub::Restore(Base::XMLReader &reader) +{ + // read my element + reader.readElement("LinkSub"); + // get the values of my attributes + std::string name = reader.getName(reader.getAttribute("value")); + int count = reader.getAttributeAsInteger("count"); + + // Property not in a DocumentObject! + assert(getContainer()->getTypeId().isDerivedFrom(App::DocumentObject::getClassTypeId()) ); + App::Document* document = static_cast(getContainer())->getDocument(); + + DocumentObject *pcObject = 0; + if (!name.empty()) { + pcObject = document ? document->getObject(name.c_str()) : 0; + if (!pcObject) { + if (reader.isVerbose()) { + FC_WARN("Lost link to " << name + << " while loading, maybe an object was not loaded correctly"); + } + } + } + + std::vector mapped; + std::vector values(count); + std::vector shadows(count); + bool restoreLabel=false; + // Sub may store '.' separated object names, so be aware of the possible mapping when import + for (int i = 0; i < count; i++) { + reader.readElement("Sub"); + shadows[i].second = importSubName(reader,reader.getAttribute("value"),restoreLabel); + if(reader.hasAttribute(ATTR_SHADOWED) && !IGNORE_SHADOW) { + values[i] = shadows[i].first = + importSubName(reader,reader.getAttribute(ATTR_SHADOWED),restoreLabel); + } else { + values[i] = shadows[i].second; + if(reader.hasAttribute(ATTR_SHADOW) && !IGNORE_SHADOW) + shadows[i].first = importSubName(reader,reader.getAttribute(ATTR_SHADOW),restoreLabel); + } + if(reader.hasAttribute(ATTR_MAPPED)) + mapped.push_back(i); + } + setFlag(LinkRestoreLabel,restoreLabel); + + reader.readEndElement("LinkSub"); + + if(pcObject) { + setValue(pcObject,std::move(values),std::move(shadows)); + _mapped = std::move(mapped); + } + else { + setValue(0); + } +} + +template +std::vector updateLinkSubs(const App::DocumentObject *obj, + const std::vector &subs, Func *f, Args&&... args ) +{ + if(!obj || !obj->getNameInDocument()) + return {}; + + std::vector res; + for(auto it=subs.begin();it!=subs.end();++it) { + const auto &sub = *it; + auto new_sub = (*f)(obj,sub.c_str(),std::forward(args)...); + if(new_sub.size()) { + if(res.empty()) { + res.reserve(subs.size()); + res.insert(res.end(),subs.begin(),it); + } + res.push_back(std::move(new_sub)); + }else if(res.size()) + res.push_back(sub); + } + return res; +} + +Property *PropertyLinkSub::CopyOnImportExternal( + const std::map &nameMap) const +{ + auto owner = dynamic_cast(getContainer()); + if(!owner || !owner->getDocument()) + return 0; + if(!_pcLinkSub || !_pcLinkSub->getNameInDocument()) + return 0; + + auto subs = updateLinkSubs(_pcLinkSub,_cSubList, + &tryImportSubName,owner->getDocument(),nameMap); + auto linked = tryImport(owner->getDocument(),_pcLinkSub,nameMap); + if(subs.empty() && linked==_pcLinkSub) + return 0; + + PropertyLinkSub *p= new PropertyLinkSub(); + p->_pcLinkSub = linked; + if(subs.empty()) + p->_cSubList = _cSubList; + else + p->_cSubList = std::move(subs); + return p; +} + +Property *PropertyLinkSub::CopyOnLabelChange(App::DocumentObject *obj, + const std::string &ref, const char *newLabel) const +{ + auto owner = dynamic_cast(getContainer()); + if(!owner || !owner->getDocument()) + return 0; + if(!_pcLinkSub || !_pcLinkSub->getNameInDocument()) + return 0; + + auto subs = updateLinkSubs(_pcLinkSub,_cSubList,&updateLabelReference,obj,ref,newLabel); + if(subs.empty()) + return 0; + + PropertyLinkSub *p= new PropertyLinkSub(); + p->_pcLinkSub = _pcLinkSub; + p->_cSubList = std::move(subs); + return p; +} + +Property *PropertyLinkSub::CopyOnLinkReplace(const App::DocumentObject *parent, + App::DocumentObject *oldObj, App::DocumentObject *newObj) const +{ + auto res = tryReplaceLinkSubs(getContainer(),_pcLinkSub,parent,oldObj,newObj,_cSubList); + if(res.first) { + PropertyLinkSub *p= new PropertyLinkSub(); + p->_pcLinkSub = res.first; + p->_cSubList = std::move(res.second); + return p; + } + return 0; +} + +Property *PropertyLinkSub::Copy(void) const +{ + PropertyLinkSub *p= new PropertyLinkSub(); + p->_pcLinkSub = _pcLinkSub; + p->_cSubList = _cSubList; + return p; +} + +void PropertyLinkSub::Paste(const Property &from) +{ + if(!from.isDerivedFrom(PropertyLinkSub::getClassTypeId())) + throw Base::TypeError("Incompatible property to paste to"); + auto &link = static_cast(from); + setValue(link._pcLinkSub, link._cSubList); +} + +void PropertyLinkSub::getLinks(std::vector &objs, + bool all, std::vector *subs, bool newStyle) const +{ + if(all||_pcScope!=LinkScope::Hidden) { + if(_pcLinkSub && _pcLinkSub->getNameInDocument()) { + objs.push_back(_pcLinkSub); + if(subs) + *subs = getSubValues(newStyle); + } + } +} + +void PropertyLinkSub::breakLink(App::DocumentObject *obj, bool clear) { + if(obj == _pcLinkSub || (clear && getContainer()==obj)) + setValue(0); +} + +static App::DocumentObject *adjustLinkSubs(App::PropertyLinkBase *prop, + const std::set &inList, + App::DocumentObject *link, std::vector &subs, + std::map > *links=0) { - setValue(dynamic_cast(from)._pcLinkSub, dynamic_cast(from)._cSubList); + App::DocumentObject *newLink = 0; + for(auto &sub : subs) { + size_t pos = sub.find('.'); + for(;pos!=std::string::npos;pos=sub.find('.',pos+1)) { + auto sobj = link->getSubObject(sub.substr(0,pos+1).c_str()); + if(!sobj || + (!prop->testFlag(PropertyLinkBase::LinkAllowExternal) && + sobj->getDocument()!=link->getDocument())) + { + pos = std::string::npos; + break; + } + if(!newLink) { + if(inList.count(sobj)) + continue; + newLink = sobj; + if(links) + (*links)[sobj].push_back(sub.substr(pos+1)); + else + sub = sub.substr(pos+1); + }else if(links) + (*links)[sobj].push_back(sub.substr(pos+1)); + else if(sobj == newLink) + sub = sub.substr(pos+1); + break; + } + if(pos == std::string::npos) + return 0; + } + return newLink; +} + +bool PropertyLinkSub::adjustLink(const std::set &inList) { + if (_pcScope==LinkScope::Hidden) + return false; + if(!_pcLinkSub || !_pcLinkSub->getNameInDocument() || !inList.count(_pcLinkSub)) + return false; + auto subs = _cSubList; + auto link = adjustLinkSubs(this,inList,_pcLinkSub,subs); + if(link) { + setValue(link,std::move(subs)); + return true; + } + return false; } //************************************************************************** // PropertyLinkSubList //++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ -TYPESYSTEM_SOURCE(App::PropertyLinkSubList, App::PropertyLists) +TYPESYSTEM_SOURCE(App::PropertyLinkSubList, App::PropertyLinkBase) TYPESYSTEM_SOURCE(App::PropertyLinkSubListChild, App::PropertyLinkSubList) TYPESYSTEM_SOURCE(App::PropertyLinkSubListGlobal, App::PropertyLinkSubList) +TYPESYSTEM_SOURCE(App::PropertyLinkSubListHidden, App::PropertyLinkSubList) //************************************************************************** // Construction/Destruction @@ -658,7 +1578,7 @@ PropertyLinkSubList::~PropertyLinkSubList() App::DocumentObject* parent = static_cast(getContainer()); // before accessing internals make sure the object is not about to be destroyed // otherwise the backlink contains dangling pointers - if (!parent->testStatus(ObjectStatus::Destroy)) { + if (!parent->testStatus(ObjectStatus::Destroy) && _pcScope!=LinkScope::Hidden) { for(auto *obj : _lValueList) { if (obj) obj->_removeBackLink(parent); @@ -672,6 +1592,7 @@ void PropertyLinkSubList::setSize(int newSize) { _lValueList.resize(newSize); _lSubList .resize(newSize); + _ShadowSubList.resize(newSize); } int PropertyLinkSubList::getSize(void) const @@ -681,13 +1602,19 @@ int PropertyLinkSubList::getSize(void) const void PropertyLinkSubList::setValue(DocumentObject* lValue,const char* SubName) { + auto parent = Base::freecad_dynamic_cast(getContainer()); + if(lValue) { + if(!lValue->getNameInDocument()) + throw Base::ValueError("PropertyLinkSubList: invalid document object"); + if(!testFlag(LinkAllowExternal) && parent && parent->getDocument()!=lValue->getDocument()) + throw Base::ValueError("PropertyLinkSubList does not support external object"); + } #ifndef USE_OLD_DAG //maintain backlinks - if (getContainer() && getContainer()->isDerivedFrom(App::DocumentObject::getClassTypeId())) { - App::DocumentObject* parent = static_cast(getContainer()); + if(parent) { // before accessing internals make sure the object is not about to be destroyed // otherwise the backlink contains dangling pointers - if (!parent->testStatus(ObjectStatus::Destroy)) { + if (!parent->testStatus(ObjectStatus::Destroy) && _pcScope!=LinkScope::Hidden) { for(auto *obj : _lValueList) { if (obj) obj->_removeBackLink(parent); @@ -704,28 +1631,35 @@ void PropertyLinkSubList::setValue(DocumentObject* lValue,const char* SubName) _lValueList[0]=lValue; _lSubList.resize(1); _lSubList[0]=SubName; - hasSetValue(); } else { aboutToSetValue(); _lValueList.clear(); _lSubList.clear(); - hasSetValue(); } + updateElementReference(0); + checkLabelReferences(_lSubList); + hasSetValue(); } void PropertyLinkSubList::setValues(const std::vector& lValue,const std::vector& lSubNames) { + auto parent = Base::freecad_dynamic_cast(getContainer()); + for(auto obj : lValue) { + if(!obj || !obj->getNameInDocument()) + throw Base::ValueError("PropertyLinkSubList: invalid document object"); + if(!testFlag(LinkAllowExternal) && parent && parent->getDocument()!=obj->getDocument()) + throw Base::ValueError("PropertyLinkSubList does not support external object"); + } if (lValue.size() != lSubNames.size()) throw Base::ValueError("PropertyLinkSubList::setValues: size of subelements list != size of objects list"); #ifndef USE_OLD_DAG //maintain backlinks. - if (getContainer() && getContainer()->isDerivedFrom(App::DocumentObject::getClassTypeId())) { - App::DocumentObject* parent = static_cast(getContainer()); + if(parent) { // before accessing internals make sure the object is not about to be destroyed // otherwise the backlink contains dangling pointers - if (!parent->testStatus(ObjectStatus::Destroy)) { + if (!parent->testStatus(ObjectStatus::Destroy) && _pcScope!=LinkScope::Hidden) { //_lValueList can contain items multiple times, but we trust the document //object to ensure that this works for(auto *obj : _lValueList) { @@ -751,21 +1685,37 @@ void PropertyLinkSubList::setValues(const std::vector& lValue,c if (*it != nullptr) _lSubList[i] = *it; } + updateElementReference(0); + checkLabelReferences(_lSubList); hasSetValue(); } -void PropertyLinkSubList::setValues(const std::vector& lValue,const std::vector& lSubNames) +void PropertyLinkSubList::setValues(const std::vector& lValue, + const std::vector& lSubNames, std::vector &&ShadowSubList) +{ + setValues(std::vector(lValue), + std::vector(lSubNames),std::move(ShadowSubList)); +} + +void PropertyLinkSubList::setValues(std::vector&& lValue, + std::vector&& lSubNames, std::vector &&ShadowSubList) { + auto parent = Base::freecad_dynamic_cast(getContainer()); + for(auto obj : lValue) { + if(!obj || !obj->getNameInDocument()) + throw Base::ValueError("PropertyLinkSubList: invalid document object"); + if(!testFlag(LinkAllowExternal) && parent && parent->getDocument()!=obj->getDocument()) + throw Base::ValueError("PropertyLinkSubList does not support external object"); + } if (lValue.size() != lSubNames.size()) throw Base::ValueError("PropertyLinkSubList::setValues: size of subelements list != size of objects list"); #ifndef USE_OLD_DAG //maintain backlinks. - if (getContainer() && getContainer()->isDerivedFrom(App::DocumentObject::getClassTypeId())) { - App::DocumentObject* parent = static_cast(getContainer()); + if(parent) { // before accessing internals make sure the object is not about to be destroyed // otherwise the backlink contains dangling pointers - if (!parent->testStatus(ObjectStatus::Destroy)) { + if (!parent->testStatus(ObjectStatus::Destroy) && _pcScope!=LinkScope::Hidden) { //_lValueList can contain items multiple times, but we trust the document //object to ensure that this works for(auto *obj : _lValueList) { @@ -784,20 +1734,31 @@ void PropertyLinkSubList::setValues(const std::vector& lValue,c #endif aboutToSetValue(); - _lValueList = lValue; - _lSubList = lSubNames; + _lValueList = std::move(lValue); + _lSubList = std::move(lSubNames); + if(ShadowSubList.size()==_lSubList.size()) + _ShadowSubList = std::move(ShadowSubList); + else + updateElementReference(0); + checkLabelReferences(_lSubList); hasSetValue(); } void PropertyLinkSubList::setValue(DocumentObject* lValue, const std::vector &SubList) { -#ifndef USE_OLD_DAG + auto parent = dynamic_cast(getContainer()); + if(lValue) { + if(!lValue->getNameInDocument()) + throw Base::ValueError("PropertyLinkSubList: invalid document object"); + if(!testFlag(LinkAllowExternal) && parent && parent->getDocument()!=lValue->getDocument()) + throw Base::ValueError("PropertyLinkSubList does not support external object"); + } +#ifndef USE_OLD_DAG //maintain backlinks. - if (getContainer() && getContainer()->isDerivedFrom(App::DocumentObject::getClassTypeId())) { - App::DocumentObject* parent = static_cast(getContainer()); + if(parent) { // before accessing internals make sure the object is not about to be destroyed // otherwise the backlink contains dangling pointers - if (!parent->testStatus(ObjectStatus::Destroy)) { + if (!parent->testStatus(ObjectStatus::Destroy) && _pcScope!=LinkScope::Hidden) { //_lValueList can contain items multiple times, but we trust the document //object to ensure that this works for(auto *obj : _lValueList) { @@ -827,6 +1788,8 @@ void PropertyLinkSubList::setValue(DocumentObject* lValue, const std::vector_lSubList = SubList; this->_lValueList.insert(this->_lValueList.begin(), size, lValue); } + updateElementReference(0); + checkLabelReferences(_lSubList); hasSetValue(); } @@ -846,7 +1809,8 @@ const string PropertyLinkSubList::getPyReprString() const strm << "("; App::DocumentObject* obj = this->_lValueList[i]; if (obj) { - strm << "App.getDocument('" << obj->getDocument()->getName() << "')." << obj->getNameInDocument(); + strm << "App.getDocument('" << obj->getDocument()->getName() + << "').getObject('" << obj->getNameInDocument() << "')"; } else { strm << "None"; } @@ -910,15 +1874,23 @@ void PropertyLinkSubList::setSubListValues(const std::vector PropertyLinkSubList::getSubListValues() const +std::vector PropertyLinkSubList::getSubListValues(bool newStyle) const { std::vector values; if (_lValueList.size() != _lSubList.size()) throw Base::ValueError("PropertyLinkSubList::getSubListValues: size of subelements list != size of objects list"); + assert(_ShadowSubList.size() == _lSubList.size()); + for (std::size_t i = 0; i < _lValueList.size(); i++) { App::DocumentObject* link = _lValueList[i]; - std::string sub = _lSubList[i]; + std::string sub; + if(newStyle && _ShadowSubList[i].first.size()) + sub = _ShadowSubList[i].first; + else if(!newStyle && _ShadowSubList[i].second.size()) + sub = _ShadowSubList[i].second; + else + sub = _lSubList[i]; if (values.size() == 0 || values.back().first != link){ //new object started, start a new subset. values.push_back(SubSet(link, std::vector())); @@ -940,7 +1912,7 @@ PyObject *PropertyLinkSubList::getPyObject(void) #endif for (std::size_t i = 0; igetPyObject()); + tup[0] = Py::asObject(subLists[i].first->getPyObject()); const std::vector& sub = subLists[i].second; Py::Tuple items(sub.size()); @@ -979,81 +1951,161 @@ void PropertyLinkSubList::setPyObject(PyObject *value) PropertyLinkSub dummy; dummy.setPyObject(value); this->setValue(dummy.getValue(), dummy.getSubValues()); + return; } - catch (Base::TypeError&) { - if (PyTuple_Check(value) || PyList_Check(value)) { - Py::Sequence list(value); - Py::Sequence::size_type size = list.size(); - - std::vector values; - values.reserve(size); - std::vector SubNames; - SubNames.reserve(size); - for (Py::Sequence::size_type i=0; i(tup[0].ptr()); - values.push_back(pcObj->getDocumentObjectPtr()); - SubNames.push_back(Py::String(tup[1])); - } - else if (tup[1].isSequence()) { - Py::Sequence list(tup[1]); - for (Py::Sequence::iterator it = list.begin(); it != list.end(); ++it) { - SubNames.push_back(Py::String(*it)); - } - - DocumentObjectPy *pcObj; - pcObj = static_cast(tup[0].ptr()); - values.insert(values.end(), list.size(), pcObj->getDocumentObjectPtr()); - } - } - else { - std::string error = std::string("type of first item must be 'DocumentObject', not "); - error += Py_TYPE(tup[0].ptr())->tp_name; - throw Base::TypeError(error); + catch (...) {} + try { + // try PropertyLinkList syntax + PropertyLinkList dummy; + dummy.setPyObject(value); + const auto &values = dummy.getValues(); + std::vector subs(values.size()); + this->setValues(values,subs); + return; + }catch(...) {} + +#define SUBLIST_THROW \ + throw Base::TypeError(\ + "Expects sequence of items of type DocObj, (DocObj,SubName), or (DocObj, (SubName,...))") + + if (!PyTuple_Check(value) && !PyList_Check(value)) + SUBLIST_THROW; + + Py::Sequence list(value); + Py::Sequence::size_type size = list.size(); + + std::vector values; + values.reserve(size); + std::vector SubNames; + SubNames.reserve(size); + for (Py::Sequence::size_type i=0; i(seq[0].ptr())->getDocumentObjectPtr(); + if (seq[1].isString()) { + values.push_back(obj); + SubNames.push_back(Py::String(seq[1])); + } else if (seq[1].isSequence()) { + Py::Sequence list(seq[1]); + for (Py::Sequence::iterator it = list.begin(); it != list.end(); ++it) { + values.push_back(obj); + SubNames.push_back(Py::String(*it)); } - } - else if (PyObject_TypeCheck(*item, &(DocumentObjectPy::Type))) { - DocumentObjectPy *pcObj; - pcObj = static_cast(*item); - values.push_back(pcObj->getDocumentObjectPtr()); - } - else if (item.isString()) { - SubNames.push_back(Py::String(item)); - } + } else + SUBLIST_THROW; } + } else if (PyObject_TypeCheck(*item, &(DocumentObjectPy::Type))) { + DocumentObjectPy *pcObj; + pcObj = static_cast(*item); + values.push_back(pcObj->getDocumentObjectPtr()); + SubNames.emplace_back(); + } else + SUBLIST_THROW; + } + setValues(values,SubNames); +} - setValues(values,SubNames); - } - else { - std::string error = std::string("type must be 'DocumentObject' or list of 'DocumentObject', not "); - error += value->ob_type->tp_name; - throw Base::TypeError(error); +void PropertyLinkSubList::afterRestore() { + assert(_lSubList.size() == _ShadowSubList.size()); + if(!testFlag(LinkRestoreLabel)) + return; + setFlag(LinkRestoreLabel,false); + for(size_t i=0;i<_lSubList.size();++i) + restoreLabelReference(_lValueList[i],_lSubList[i],&_ShadowSubList[i]); +} + +void PropertyLinkSubList::onContainerRestored() { + unregisterElementReference(); + for(size_t i=0;i<_lSubList.size();++i) + _registerElementReference(_lValueList[i],_lSubList[i],_ShadowSubList[i]); +} + +void PropertyLinkSubList::updateElementReference(DocumentObject *feature, bool reverse, bool notify) { + if(!feature) { + _ShadowSubList.clear(); + unregisterElementReference(); + } + _ShadowSubList.resize(_lSubList.size()); + auto owner = freecad_dynamic_cast(getContainer()); + if(owner && owner->isRestoring()) + return; + int i=0; + bool touched = false; + for(auto &sub : _lSubList) { + auto obj = _lValueList[i]; + if(_updateElementReference(feature,obj,sub,_ShadowSubList[i++],reverse,notify&&!touched)) + touched = true; + } + if(!touched) + return; + + std::vector mapped; + mapped.reserve(_mapped.size()); + for(int idx : _mapped) { + if(idx<(int)_lSubList.size()) { + if(_ShadowSubList[idx].first.size()) + _lSubList[idx] = _ShadowSubList[idx].first; + else + mapped.push_back(idx); } } + _mapped.swap(mapped); + if(owner && feature) + owner->onUpdateElementReference(this); + if(notify) + hasSetValue(); +} + +bool PropertyLinkSubList::referenceChanged() const{ + return !_mapped.empty(); } void PropertyLinkSubList::Save (Base::Writer &writer) const { - writer.Stream() << writer.ind() << "" << endl; + assert(_lSubList.size() == _ShadowSubList.size()); + + int count = 0; + for(auto obj : _lValueList) { + if(obj && obj->getNameInDocument()) + ++count; + } + writer.Stream() << writer.ind() << "" << endl; writer.incInd(); + auto owner = dynamic_cast(getContainer()); + bool exporting = owner && owner->isExporting(); for (int i = 0; i < getSize(); i++) { - if (_lValueList[i]) { - writer.Stream() << writer.ind() << - "getNameInDocument() << "\" " << - "sub=\"" << _lSubList[i] << "\"/>" << endl; - } - else { - writer.Stream() << writer.ind() << - "" << endl; + auto obj = _lValueList[i]; + if(!obj || !obj->getNameInDocument()) + continue; + const auto &shadow = _ShadowSubList[i]; + // shadow.second stores the old style element name. For backward + // compatibility reason, we shall store the old name into attribute + // 'value' whenver possible. + const auto &sub = shadow.second.empty()?_lSubList[i]:shadow.second; + + writer.Stream() << writer.ind() << "getExportName() << "\" sub=\""; + if(exporting) { + std::string exportName; + writer.Stream() << exportSubName(exportName,obj,sub.c_str()); + if(shadow.second.size() && _lSubList[i]==shadow.first) + writer.Stream() << "\" " ATTR_MAPPED "=\"1"; + } else { + writer.Stream() << sub; + if(_lSubList[i].size()) { + if(sub!=_lSubList[i]) { + // Stores the actual value that is shadowed. For new version FC, + // we will restore this shadowed value instead. + writer.Stream() << "\" " ATTR_SHADOWED "=\"" << _lSubList[i]; + }else if(shadow.first.size()) { + // Here means the user set value is old style element name. + // We shall then store the shadow somewhere else. + writer.Stream() << "\" " ATTR_SHADOW "=\"" << shadow.first; + } + } } + writer.Stream() << "\"/>" << endl; } writer.decInd(); @@ -1071,29 +2123,196 @@ void PropertyLinkSubList::Restore(Base::XMLReader &reader) values.reserve(count); std::vector SubNames; SubNames.reserve(count); + std::vector shadows; + shadows.reserve(count); + DocumentObject* father = dynamic_cast(getContainer()); + App::Document* document = father ? father->getDocument() : 0; + std::vector mapped; + bool restoreLabel=false; for (int i = 0; i < count; i++) { reader.readElement("Link"); - std::string name = reader.getAttribute("obj"); + std::string name = reader.getName(reader.getAttribute("obj")); // In order to do copy/paste it must be allowed to have defined some // referenced objects in XML which do not exist anymore in the new // document. Thus, we should silently ignore this. // Property not in an object! - DocumentObject* father = dynamic_cast(getContainer()); - App::Document* document = father ? father->getDocument() : 0; DocumentObject* child = document ? document->getObject(name.c_str()) : 0; - if (child) + if (child) { values.push_back(child); - else if (reader.isVerbose()) + shadows.emplace_back(); + auto &shadow = shadows.back(); + shadow.second = importSubName(reader,reader.getAttribute("sub"),restoreLabel); + if(reader.hasAttribute(ATTR_SHADOWED) && !IGNORE_SHADOW) { + shadow.first = importSubName(reader,reader.getAttribute(ATTR_SHADOWED),restoreLabel); + SubNames.push_back(shadow.first); + }else{ + SubNames.push_back(shadow.second); + if(reader.hasAttribute(ATTR_SHADOW) && !IGNORE_SHADOW) + shadow.first = importSubName(reader,reader.getAttribute(ATTR_SHADOW),restoreLabel); + } + if(reader.hasAttribute(ATTR_MAPPED)) + mapped.push_back(i); + } else if (reader.isVerbose()) Base::Console().Warning("Lost link to '%s' while loading, maybe " "an object was not loaded correctly\n",name.c_str()); - std::string subName = reader.getAttribute("sub"); - SubNames.push_back(subName); } + setFlag(LinkRestoreLabel,restoreLabel); reader.readEndElement("LinkSubList"); // assignment - setValues(values,SubNames); + setValues(values,std::move(SubNames),std::move(shadows)); + _mapped.swap(mapped); +} + +Property *PropertyLinkSubList::CopyOnImportExternal( + const std::map &nameMap) const +{ + auto owner = dynamic_cast(getContainer()); + if(!owner || !owner->getDocument() || _lValueList.size()!=_lSubList.size()) + return 0; + std::vector values; + std::vector subs; + auto itSub = _lSubList.begin(); + for(auto itValue=_lValueList.begin();itValue!=_lValueList.end();++itValue,++itSub) { + auto value = *itValue; + const auto &sub = *itSub; + if(!value || !value->getNameInDocument()) { + if(values.size()) { + values.push_back(value); + subs.push_back(sub); + } + continue; + } + auto linked = tryImport(owner->getDocument(),value,nameMap); + auto new_sub = tryImportSubName(value,sub.c_str(),owner->getDocument(),nameMap); + if(linked!=value || new_sub.size()) { + if(values.empty()) { + values.reserve(_lValueList.size()); + values.insert(values.end(),_lValueList.begin(),itValue); + subs.reserve(_lSubList.size()); + subs.insert(subs.end(),_lSubList.begin(),itSub); + } + values.push_back(linked); + subs.push_back(std::move(new_sub)); + }else if(values.size()) { + values.push_back(linked); + subs.push_back(sub); + } + } + if(values.empty()) + return 0; + std::unique_ptr p(new PropertyLinkSubList); + p->_lValueList = std::move(values); + p->_lSubList = std::move(subs); + return p.release(); +} + +Property *PropertyLinkSubList::CopyOnLabelChange(App::DocumentObject *obj, + const std::string &ref, const char *newLabel) const +{ + auto owner = dynamic_cast(getContainer()); + if(!owner || !owner->getDocument()) + return 0; + std::vector values; + std::vector subs; + auto itSub = _lSubList.begin(); + for(auto itValue=_lValueList.begin();itValue!=_lValueList.end();++itValue,++itSub) { + auto value = *itValue; + const auto &sub = *itSub; + if(!value || !value->getNameInDocument()) { + if(values.size()) { + values.push_back(value); + subs.push_back(sub); + } + continue; + } + auto new_sub = updateLabelReference(value,sub.c_str(),obj,ref,newLabel); + if(new_sub.size()) { + if(values.empty()) { + values.reserve(_lValueList.size()); + values.insert(values.end(),_lValueList.begin(),itValue); + subs.reserve(_lSubList.size()); + subs.insert(subs.end(),_lSubList.begin(),itSub); + } + values.push_back(value); + subs.push_back(std::move(new_sub)); + }else if(values.size()) { + values.push_back(value); + subs.push_back(sub); + } + } + if(values.empty()) + return 0; + std::unique_ptr p(new PropertyLinkSubList); + p->_lValueList = std::move(values); + p->_lSubList = std::move(subs); + return p.release(); +} + +Property *PropertyLinkSubList::CopyOnLinkReplace(const App::DocumentObject *parent, + App::DocumentObject *oldObj, App::DocumentObject *newObj) const +{ + std::vector values; + std::vector subs; + auto itSub = _lSubList.begin(); + std::vector positions; + for(auto itValue=_lValueList.begin();itValue!=_lValueList.end();++itValue,++itSub) { + auto value = *itValue; + const auto &sub = *itSub; + if(!value || !value->getNameInDocument()) { + if(values.size()) { + values.push_back(value); + subs.push_back(sub); + } + continue; + } + auto res = tryReplaceLink(getContainer(),value,parent,oldObj,newObj,sub.c_str()); + if(res.first) { + if(values.empty()) { + values.reserve(_lValueList.size()); + values.insert(values.end(),_lValueList.begin(),itValue); + subs.reserve(_lSubList.size()); + subs.insert(subs.end(),_lSubList.begin(),itSub); + } + if(res.first == newObj) { + // check for duplication + auto itS = subs.begin(); + for(auto itV=values.begin();itV!=values.end();) { + if(*itV == res.first && *itS == res.second) { + itV = values.erase(itV); + itS = subs.erase(itS); + } else { + ++itV; + ++itS; + } + } + positions.push_back(values.size()); + } + values.push_back(res.first); + subs.push_back(std::move(res.second)); + }else if(values.size()) { + bool duplicate = false; + if(value == newObj) { + for(auto pos : positions) { + if(sub == subs[pos]) { + duplicate = true; + break; + } + } + } + if(!duplicate) { + values.push_back(value); + subs.push_back(sub); + } + } + } + if(values.empty()) + return 0; + std::unique_ptr p(new PropertyLinkSubList); + p->_lValueList = std::move(values); + p->_lSubList = std::move(subs); + return p.release(); } Property *PropertyLinkSubList::Copy(void) const @@ -1106,7 +2325,10 @@ Property *PropertyLinkSubList::Copy(void) const void PropertyLinkSubList::Paste(const Property &from) { - setValues(dynamic_cast(from)._lValueList, dynamic_cast(from)._lSubList); + if(!from.isDerivedFrom(PropertyLinkSubList::getClassTypeId())) + throw Base::TypeError("Incompatible property to paste to"); + auto &link = static_cast(from); + setValues(link._lValueList, link._lSubList); } unsigned int PropertyLinkSubList::getMemSize (void) const @@ -1114,6 +2336,2152 @@ unsigned int PropertyLinkSubList::getMemSize (void) const unsigned int size = static_cast(_lValueList.size() * sizeof(App::DocumentObject *)); for(int i = 0;i PropertyLinkSubList::getSubValues(bool newStyle) const { + assert(_lSubList.size() == _ShadowSubList.size()); + std::vector ret; + ret.reserve(_ShadowSubList.size()); + for(size_t i=0;i<_ShadowSubList.size();++i) + ret.push_back(getSubNameWithStyle(_lSubList[i],_ShadowSubList[i],newStyle)); + return ret; +} + +void PropertyLinkSubList::getLinks(std::vector &objs, + bool all, std::vector *subs, bool newStyle) const +{ + if(all||_pcScope!=LinkScope::Hidden) { + objs.reserve(objs.size()+_lValueList.size()); + for(auto obj : _lValueList) { + if(obj && obj->getNameInDocument()) + objs.push_back(obj); + } + if(subs) { + auto _subs = getSubValues(newStyle); + subs->reserve(subs->size()+_subs.size()); + std::move(_subs.begin(),_subs.end(),std::back_inserter(*subs)); + } + } +} + +void PropertyLinkSubList::breakLink(App::DocumentObject *obj, bool clear) { + std::vector values; + std::vector subs; + + if(clear && getContainer()==obj) { + setValues(values,subs); + return; + } + assert(_lValueList.size()==_lSubList.size()); + + values.reserve(_lValueList.size()); + subs.reserve(_lSubList.size()); + + int i=-1; + for(auto o : _lValueList) { + ++i; + if(o==obj) + continue; + values.push_back(o); + subs.push_back(_lSubList[i]); + } + if(values.size()!=_lValueList.size()) + setValues(values,subs); +} + +bool PropertyLinkSubList::adjustLink(const std::set &inList) { + if (_pcScope==LinkScope::Hidden) + return false; + auto subs = _lSubList; + auto links = _lValueList; + int idx = -1; + bool touched = false; + for(std::string &sub : subs) { + ++idx; + auto &link = links[idx]; + if(!link || !link->getNameInDocument() || !inList.count(link)) + continue; + touched = true; + size_t pos = sub.find('.'); + for(;pos!=std::string::npos;pos=sub.find('.',pos+1)) { + auto sobj = link->getSubObject(sub.substr(0,pos+1).c_str()); + if(!sobj || sobj->getDocument()!=link->getDocument()) { + pos = std::string::npos; + break; + } + if(!inList.count(sobj)) { + link = sobj; + sub = sub.substr(pos+1); + break; + } + } + if(pos == std::string::npos) + return false; + } + if(touched) + setValues(links,subs); + return touched; +} + +//************************************************************************** +// DocInfo +//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +// Key on aboslute path. +// Becuase of possible symbolic links, multiple entry may refer to the same +// file. We use QFileInfo::canonicalPath to resolve that. +typedef std::map DocInfoMap; +DocInfoMap _DocInfoMap; + +class App::DocInfo : + public std::enable_shared_from_this +{ +public: + typedef boost::signals2::scoped_connection Connection; + Connection connFinishRestoreDocument; + Connection connDeleteDocument; + Connection connSaveDocument; + Connection connDeletedObject; + + DocInfoMap::iterator myPos; + std::string myPath; + App::Document *pcDoc; + std::set links; + + static std::string getDocPath( + const char *filename, App::Document *pDoc, bool relative, QString *fullPath = 0) + { + bool absolute; + // make sure the filename is aboluste path + QString path = QDir::cleanPath(QString::fromUtf8(filename)); + if((absolute=QFileInfo(path).isAbsolute())) { + if(fullPath) + *fullPath = path; + if(!relative) + return std::string(path.toUtf8().constData()); + } + + const char *docPath = pDoc->FileName.getValue(); + if(!docPath || *docPath==0) + throw Base::RuntimeError("Owner document not saved"); + + QDir docDir(QFileInfo(QString::fromUtf8(docPath)).absoluteDir()); + if(!absolute) { + path = QDir::cleanPath(docDir.absoluteFilePath(path)); + if(fullPath) + *fullPath = path; + } + + if(relative) + return std::string(docDir.relativeFilePath(path).toUtf8().constData()); + else + return std::string(path.toUtf8().constData()); + } + + static DocInfoPtr get(const char *filename, + App::Document *pDoc,PropertyXLink *l, const char *objName) + { + QString path; + l->filePath = getDocPath(filename,pDoc,true,&path); + + FC_LOG("finding doc " << filename); + + auto it = _DocInfoMap.find(path); + DocInfoPtr info; + if(it != _DocInfoMap.end()) { + info = it->second; + if(!info->pcDoc) { + QString fullpath(info->getFullPath()); + if(fullpath.size() && + App::GetApplication().addPendingDocument( + fullpath.toUtf8().constData(),objName, + l->testFlag(PropertyLinkBase::LinkAllowPartial))==0) + { + for(App::Document *doc : App::GetApplication().getDocuments()) { + if(getFullPath(doc->FileName.getValue()) == fullpath) { + info->attach(doc); + break; + } + } + } + } + } else { + info = std::make_shared(); + auto ret = _DocInfoMap.insert(std::make_pair(path,info)); + info->init(ret.first,objName,l); + } + info->links.insert(l); + return info; + } + + static QString getFullPath(const char *p) { + if(!p) return QString(); + return QFileInfo(QString::fromUtf8(p)).canonicalFilePath(); + } + + QString getFullPath() const { + return QFileInfo(myPos->first).canonicalFilePath(); + } + + const char *filePath() const { + return myPath.c_str(); + } + + DocInfo() + :pcDoc(0) + {} + + ~DocInfo() { + } + + void deinit() { + FC_LOG("deinit " << (pcDoc?pcDoc->getName():filePath())); + assert(links.empty()); + connFinishRestoreDocument.disconnect(); + connDeleteDocument.disconnect(); + connSaveDocument.disconnect(); + connDeletedObject.disconnect(); + + auto me = shared_from_this(); + _DocInfoMap.erase(myPos); + myPos = _DocInfoMap.end(); + myPath.clear(); + pcDoc = 0; + } + + void init(DocInfoMap::iterator pos, const char *objName, PropertyXLink *l) { + myPos = pos; + myPath = myPos->first.toUtf8().constData(); + App::Application &app = App::GetApplication(); + connFinishRestoreDocument = app.signalFinishRestoreDocument.connect( + boost::bind(&DocInfo::slotFinishRestoreDocument,this,_1)); + connDeleteDocument = app.signalDeleteDocument.connect( + boost::bind(&DocInfo::slotDeleteDocument,this,_1)); + connSaveDocument = app.signalSaveDocument.connect( + boost::bind(&DocInfo::slotSaveDocument,this,_1)); + + QString fullpath(getFullPath()); + if(fullpath.isEmpty()) + FC_ERR("document not found " << filePath()); + else{ + for(App::Document *doc : App::GetApplication().getDocuments()) { + if(getFullPath(doc->FileName.getValue()) == fullpath) { + attach(doc); + return; + } + } + FC_LOG("document pending " << filePath()); + app.addPendingDocument(fullpath.toUtf8().constData(),objName, + l->testFlag(PropertyLinkBase::LinkAllowPartial)); + } + } + + void attach(Document *doc) { + assert(!pcDoc); + pcDoc = doc; + FC_LOG("attaching " << doc->getName() << ", " << doc->FileName.getValue()); + std::map > parentLinks; + for(auto it=links.begin(),itNext=it;it!=links.end();it=itNext) { + ++itNext; + auto link = *it; + if(link->_pcLink) + continue; + if(link->parentProp) { + parentLinks[link->parentProp].push_back(link); + continue; + } + auto obj = doc->getObject(link->objectName.c_str()); + if(!obj) + FC_WARN("object '" << link->objectName << "' not found in document '" + << doc->getName() << "'"); + else + link->restoreLink(obj); + } + for(auto &v : parentLinks) { + v.first->setFlag(PropertyLinkBase::LinkRestoring); + v.first->aboutToSetValue(); + for(auto link : v.second) { + auto obj = doc->getObject(link->objectName.c_str()); + if(!obj) + FC_WARN("object '" << link->objectName << "' not found in document '" + << doc->getName() << "'"); + else + link->restoreLink(obj); + } + v.first->hasSetValue(); + v.first->setFlag(PropertyLinkBase::LinkRestoring,false); + } + } + + void remove(PropertyXLink *l) { + auto it = links.find(l); + if(it != links.end()) { + links.erase(it); + if(links.empty()) + deinit(); + } + } + + void slotFinishRestoreDocument(const App::Document &doc) { + if(pcDoc) return; + QString fullpath(getFullPath()); + if(!fullpath.isEmpty() && getFullPath(doc.FileName.getValue())==fullpath) + attach(const_cast(&doc)); + } + + void slotSaveDocument(const App::Document &doc) { + if(!pcDoc) { + slotFinishRestoreDocument(doc); + return; + } + if(&doc!=pcDoc) return; + + QFileInfo info(myPos->first); + QString path(info.canonicalFilePath()); + const char *filename = doc.FileName.getValue(); + QString docPath(getFullPath(filename)); + + if(path.isEmpty() || path!=docPath) { + FC_LOG("document '" << doc.getName() << "' path changed"); + auto me = shared_from_this(); + auto ret = _DocInfoMap.insert(std::make_pair(path,me)); + if(!ret.second) { + // is that even possible? + FC_WARN("document '" << doc.getName() << "' path exists, detach"); + slotDeleteDocument(doc); + return; + } + _DocInfoMap.erase(myPos); + myPos = ret.first; + + std::set tmp; + tmp.swap(links); + for(auto link : tmp) { + auto owner = static_cast(link->getContainer()); + QString path = QString::fromUtf8(link->filePath.c_str()); + // adjust file path for each PropertyXLink + DocInfo::get(filename,owner->getDocument(),link,link->objectName.c_str()); + } + } + + // time stamp changed, touch the linking document. Unfortunately, there + // is no way to setModfied() for an App::Document. We don't want to touch + // all PropertyXLink for a document, because the linked object is + // potentially unchanged. So we just touch at most one. + std::set docs; + for(auto link : links) { + auto doc = static_cast(link->getContainer())->getDocument(); + auto ret = docs.insert(doc); + if(ret.second && !doc->isTouched()) + link->touch(); + } + } + + void slotDeleteDocument(const App::Document &doc) { + for(auto it=links.begin(),itNext=it;it!=links.end();it=itNext) { + ++itNext; + auto link = *it; + auto obj = dynamic_cast(link->getContainer()); + if(obj && obj->getDocument() == &doc) { + links.erase(it); + // must call unlink here, so that PropertyLink::resetLink can + // remove back link before the owner object is marked as being + // destroyed + link->unlink(); + } + } + if(links.empty()) { + deinit(); + return; + } + if(pcDoc!=&doc) return; + std::map > parentLinks; + for(auto link : links) { + link->setFlag(PropertyLinkBase::LinkDetached); + if(link->parentProp) + parentLinks[link->parentProp].push_back(link); + else + parentLinks[0].push_back(link); + } + for(auto &v : parentLinks) { + if(v.first) { + v.first->setFlag(PropertyLinkBase::LinkDetached); + v.first->aboutToSetValue(); + } + for(auto l : v.second) + l->detach(); + if(v.first) { + v.first->hasSetValue(); + v.first->setFlag(PropertyLinkBase::LinkDetached,false); + } + } + pcDoc = 0; + } + + bool hasXLink(const App::Document *doc) const{ + for(auto link : links) { + auto obj = dynamic_cast(link->getContainer()); + if(obj && obj->getDocument() == doc) + return true; + } + return false; + } + + static void breakLinks(App::DocumentObject *obj, bool clear) { + auto doc = obj->getDocument(); + for(auto itD=_DocInfoMap.begin(),itDNext=itD;itD!=_DocInfoMap.end();itD=itDNext) { + ++itDNext; + auto docInfo = itD->second; + if(docInfo->pcDoc != doc) + continue; + auto &links = docInfo->links; + std::set parentLinks; + for(auto it=links.begin(),itNext=it;it!=links.end();it=itNext) { + ++itNext; + auto link = *it; + if(link->_pcLink!=obj && !(clear && link->getContainer()==obj)) + continue; + if(link->parentProp) + parentLinks.insert(link->parentProp); + else + link->breakLink(obj,clear); + } + for(auto link : parentLinks) + link->breakLink(obj,clear); + } + } +}; + +void PropertyLinkBase::breakLinks(App::DocumentObject *link, + const std::vector &objs, bool clear) +{ + std::vector props; + for(auto obj : objs) { + props.clear(); + obj->getPropertyList(props); + for(auto prop : props) { + auto linkProp = dynamic_cast(prop); + if(linkProp) + linkProp->breakLink(link,clear); + } + } + DocInfo::breakLinks(link,clear); +} + +//************************************************************************** +// PropertyXLink +//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +TYPESYSTEM_SOURCE(App::PropertyXLink , App::PropertyLink) + +PropertyXLink::PropertyXLink(bool _allowPartial, PropertyLinkBase *parent) + :parentProp(parent) +{ + setAllowPartial(_allowPartial); + setAllowExternal(true); + if(parent) + setContainer(parent->getContainer()); +} + +PropertyXLink::~PropertyXLink() { + unlink(); +} + +void PropertyXLink::unlink() { + if(docInfo) { + docInfo->remove(this); + docInfo.reset(); + } + objectName.clear(); + resetLink(); +} + +void PropertyXLink::detach() { + if(docInfo && _pcLink) { + aboutToSetValue(); + resetLink(); + updateElementReference(0); + hasSetValue(); + } +} + +void PropertyXLink::aboutToSetValue() { + if(parentProp) + parentProp->aboutToSetChildValue(*this); + else + PropertyLinkBase::aboutToSetValue(); +} + +void PropertyXLink::hasSetValue() { + if(parentProp) + parentProp->hasSetChildValue(*this); + else + PropertyLinkBase::hasSetValue(); +} + +void PropertyXLink::setSubName(const char *subname) +{ + std::vector subs; + if(subname && subname[0]) + subs.emplace_back(subname); + aboutToSetValue(); + _setSubValues(std::move(subs)); + hasSetValue(); +} + +void PropertyXLink::_setSubValues(std::vector &&subs, + std::vector &&shadows) +{ + _SubList = std::move(subs); + _ShadowSubList.clear(); + if(shadows.size() == _SubList.size()) + _ShadowSubList = std::move(shadows); + else + updateElementReference(0); + checkLabelReferences(_SubList); +} + +void PropertyXLink::setValue(App::DocumentObject * lValue) { + setValue(lValue,0); +} + +void PropertyXLink::setValue(App::DocumentObject * lValue, const char *subname) +{ + std::vector subs; + if(subname && subname[0]) + subs.emplace_back(subname); + _setValue(lValue,std::move(subs)); +} + +void PropertyXLink::restoreLink(App::DocumentObject *lValue) { + assert(!_pcLink && lValue && docInfo); + + auto owner = dynamic_cast(getContainer()); + if(!owner || !owner->getNameInDocument()) + throw Base::RuntimeError("invalid container"); + + bool touched = owner->isTouched(); + setFlag(LinkDetached,false); + setFlag(LinkRestoring); + aboutToSetValue(); +#ifndef USE_OLD_DAG + if (!owner->testStatus(ObjectStatus::Destroy) && _pcScope!=LinkScope::Hidden) + lValue->_addBackLink(owner); +#endif + _pcLink=lValue; + updateElementReference(0); + hasSetValue(); + setFlag(LinkRestoring,false); + + if(!touched && + owner->isTouched() && + docInfo && + docInfo->pcDoc && + stamp==docInfo->pcDoc->LastModifiedDate.getValue()) + { + owner->purgeTouched(); + } +} + +void PropertyXLink::_setValue(App::DocumentObject *lValue, + std::vector &&subs, std::vector &&shadows) +{ + if(_pcLink==lValue && _SubList==subs) + return; + + if(lValue && (!lValue->getNameInDocument() || !lValue->getDocument())) { + throw Base::ValueError("Invalid object"); + return; + } + + auto owner = dynamic_cast(getContainer()); + if(!owner || !owner->getNameInDocument()) + throw Base::RuntimeError("invalid container"); + + if(lValue == owner) + throw Base::ValueError("self linking"); + + DocInfoPtr info; + const char *name = ""; + if(lValue) { + name = lValue->getNameInDocument(); + if(lValue->getDocument() != owner->getDocument()) { + if(!docInfo || lValue->getDocument()!=docInfo->pcDoc) + { + const char *filename = lValue->getDocument()->FileName.getValue(); + if(!filename || *filename==0) + throw Base::RuntimeError("Linked document not saved"); + FC_LOG("xlink set to new document " << lValue->getDocument()->getName()); + info = DocInfo::get(filename,owner->getDocument(),this,name); + assert(info && info->pcDoc == lValue->getDocument()); + }else + info = docInfo; + } + } + + setFlag(LinkDetached,false); + aboutToSetValue(); +#ifndef USE_OLD_DAG + if (!owner->testStatus(ObjectStatus::Destroy) && _pcScope!=LinkScope::Hidden) { + if(_pcLink) + _pcLink->_removeBackLink(owner); + if(lValue) + lValue->_addBackLink(owner); + } +#endif + if(docInfo!=info) { + unlink(); + docInfo = info; + } + _pcLink=lValue; + if(docInfo && docInfo->pcDoc) + stamp=docInfo->pcDoc->LastModifiedDate.getValue(); + objectName = std::move(name); + _setSubValues(std::move(subs),std::move(shadows)); + hasSetValue(); +} + +void PropertyXLink::_setValue(std::string &&filename, std::string &&name, + std::vector &&subs, std::vector &&shadows) +{ + if(name.empty()) { + _setValue(0,std::move(subs),std::move(shadows)); + return; + } + auto owner = dynamic_cast(getContainer()); + if(!owner || !owner->getNameInDocument()) + throw Base::RuntimeError("invalid container"); + + DocumentObject *pObject=0; + DocInfoPtr info; + if(filename.size()) { + info = DocInfo::get(filename.c_str(),owner->getDocument(),this,name.c_str()); + if(info->pcDoc) + pObject = info->pcDoc->getObject(name.c_str()); + }else + pObject = owner->getDocument()->getObject(name.c_str()); + + if(pObject) { + _setValue(pObject,std::move(subs),std::move(shadows)); + return; + } + setFlag(LinkDetached,false); + aboutToSetValue(); +#ifndef USE_OLD_DAG + if (_pcLink && !owner->testStatus(ObjectStatus::Destroy) && _pcScope!=LinkScope::Hidden) + _pcLink->_removeBackLink(owner); +#endif + _pcLink = 0; + if(docInfo!=info) { + unlink(); + docInfo = info; + } + if(docInfo && docInfo->pcDoc) + stamp=docInfo->pcDoc->LastModifiedDate.getValue(); + objectName = std::move(name); + _setSubValues(std::move(subs),std::move(shadows)); + hasSetValue(); +} + +App::Document *PropertyXLink::getDocument() const { + return docInfo?docInfo->pcDoc:0; +} + +const char *PropertyXLink::getDocumentPath() const { + return docInfo?docInfo->filePath():filePath.c_str(); +} + +const char *PropertyXLink::getObjectName() const { + return objectName.c_str(); +} + +bool PropertyXLink::upgrade(Base::XMLReader &reader, const char *typeName) { + if(strcmp(typeName,App::PropertyLinkGlobal::getClassTypeId().getName())==0 || + strcmp(typeName,App::PropertyLink::getClassTypeId().getName())==0 || + strcmp(typeName,App::PropertyLinkChild::getClassTypeId().getName())==0) + { + PropertyLink::Restore(reader); + return true; + } + FC_ERR("Cannot upgrade from " << typeName); + return false; +} + +int PropertyXLink::checkRestore(std::string *msg) const { + if(!docInfo) { + if(!_pcLink && objectName.size()) { + // this condition means linked object not found + if(msg) { + std::ostringstream ss; + ss << "Link not restored" << std::endl; + ss << "Object: " << objectName; + if(filePath.size()) + ss << std::endl << "File: " << filePath; + *msg = ss.str(); + } + return 2; + } + return 0; + } + if(!_pcLink) { + if(testFlag(LinkAllowPartial) && + (!docInfo->pcDoc || + docInfo->pcDoc->testStatus(App::Document::PartialDoc))) + { + return 0; + } + if(msg) { + std::ostringstream ss; + ss << "Link not restored" << std::endl; + ss << "Linked object: " << objectName; + if(docInfo->pcDoc) + ss << std::endl << "Linked document: " << docInfo->pcDoc->Label.getValue(); + else if(filePath.size()) + ss << std::endl << "Linked file: " << filePath; + *msg = ss.str(); + } + return 2; + } + if(!docInfo->pcDoc || stamp==docInfo->pcDoc->LastModifiedDate.getValue()) + return 0; + + if(msg) { + std::ostringstream ss; + ss << "Time stamp changed on link " + << _pcLink->getFullName(); + *msg = ss.str(); + } + return 1; +} + +void PropertyXLink::afterRestore() { + assert(_SubList.size() == _ShadowSubList.size()); + if(!testFlag(LinkRestoreLabel) || !_pcLink || !_pcLink->getNameInDocument()) + return; + setFlag(LinkRestoreLabel,false); + for(size_t i=0;i<_SubList.size();++i) + restoreLabelReference(_pcLink,_SubList[i],&_ShadowSubList[i]); +} + +void PropertyXLink::onContainerRestored() { + if(!_pcLink || !_pcLink->getNameInDocument()) + return; + for(size_t i=0;i<_SubList.size();++i) + _registerElementReference(_pcLink,_SubList[i],_ShadowSubList[i]); +} + +void PropertyXLink::updateElementReference(DocumentObject *feature,bool reverse,bool notify) { + if(!updateLinkReference(this,feature,reverse,notify,_pcLink,_SubList,_mapped,_ShadowSubList)) + return; + if(notify) + hasSetValue(); +} + +bool PropertyXLink::referenceChanged() const{ + return !_mapped.empty(); +} + +void PropertyXLink::Save (Base::Writer &writer) const { + auto owner = dynamic_cast(getContainer()); + if(!owner || !owner->getDocument()) + return; + + assert(_SubList.size() == _ShadowSubList.size()); + + auto exporting = owner->isExporting(); + if(_pcLink && exporting && _pcLink->isExporting()) { + // this means, we are exporting the owner and the linked object together. + // Lets save the export name + writer.Stream() << writer.ind() << "getExportName(); + }else { + const char *path = filePath.c_str(); + std::string _path; + if(exporting) { + // Here means we are exporting the owner but not exporting the + // linked object. Try to use aboslute file path for easy transition + // into document at different directory + if(docInfo) + _path = docInfo->filePath(); + else { + auto pDoc = owner->getDocument(); + const char *docPath = pDoc->FileName.getValue(); + if(docPath && docPath[0]) { + if(filePath.size()) + _path = DocInfo::getDocPath(filePath.c_str(),pDoc,false); + else + _path = docPath; + }else + FC_WARN("PropertyXLink export without saving the document"); + } + if(_path.size()) + path = _path.c_str(); + } + writer.Stream() << writer.ind() + << "pcDoc?docInfo->pcDoc->LastModifiedDate.getValue():"") + << "\" name=\"" << objectName; + } + + if(testFlag(LinkAllowPartial)) + writer.Stream() << "\" partial=\"1"; + + if(_SubList.empty()) { + writer.Stream() << "\"/>" << std::endl; + } else if(_SubList.size() == 1) { + const auto &subName = _SubList[0]; + const auto &shadowSub = _ShadowSubList[0]; + const auto &sub = shadowSub.second.empty()?subName:shadowSub.second; + if(exporting) { + std::string exportName; + writer.Stream() << "\" sub=\"" << exportSubName(exportName,_pcLink,sub.c_str()); + if(shadowSub.second.size() && shadowSub.first==subName) + writer.Stream() << "\" " ATTR_MAPPED "=\"1"; + }else{ + writer.Stream() << "\" sub=\"" << sub; + if(sub.size()) { + if(sub!=subName) + writer.Stream() << "\" " ATTR_SHADOWED "=\"" << subName; + else if(shadowSub.first.size()) + writer.Stream() << "\" " ATTR_SHADOW "=\"" << shadowSub.first; + } + } + writer.Stream() << "\"/>" << std::endl; + }else { + writer.Stream() <<"\" count=\"" << _SubList.size() << "\">" << std::endl; + writer.incInd(); + for(unsigned int i = 0;i<_SubList.size(); i++) { + const auto &shadow = _ShadowSubList[i]; + // shadow.second stores the old style element name. For backward + // compatibility reason, we shall store the old name into attribute + // 'value' whenver possible. + const auto &sub = shadow.second.empty()?_SubList[i]:shadow.second; + writer.Stream() << writer.ind() << "" << endl; + } + writer.decInd(); + writer.Stream() << writer.ind() << "" << endl ; + } +} + +void PropertyXLink::Restore(Base::XMLReader &reader) +{ + // read my element + reader.readElement("XLink"); + std::string stamp,file; + if(reader.hasAttribute("stamp")) + stamp = reader.getAttribute("stamp"); + if(reader.hasAttribute("file")) + file = reader.getAttribute("file"); + setFlag(LinkAllowPartial, + reader.hasAttribute("partial") && + reader.getAttributeAsInteger("partial")); + std::string name; + if(file.empty()) + name = reader.getName(reader.getAttribute("name")); + else + name = reader.getAttribute("name"); + + assert(getContainer()->getTypeId().isDerivedFrom(App::DocumentObject::getClassTypeId())); + DocumentObject *object = 0; + if(name.size() && file.empty()) { + DocumentObject* parent = static_cast(getContainer()); + Document *document = parent->getDocument(); + object = document ? document->getObject(name.c_str()) : 0; + if(!object) { + if(reader.isVerbose()) { + FC_WARN("Lost link to '" << name << "' while loading, maybe " + "an object was not loaded correctly"); + } + } + } + + std::vector subs; + std::vector shadows; + std::vector mapped; + bool restoreLabel = false; + if(reader.hasAttribute("sub")) { + if(reader.hasAttribute(ATTR_MAPPED)) + mapped.push_back(0); + subs.emplace_back(); + auto &subname = subs.back(); + shadows.emplace_back(); + auto &shadow = shadows.back(); + shadow.second = importSubName(reader,reader.getAttribute("sub"),restoreLabel); + if(reader.hasAttribute(ATTR_SHADOWED) && !IGNORE_SHADOW) + subname = shadow.first = importSubName(reader,reader.getAttribute(ATTR_SHADOWED),restoreLabel); + else { + subname = shadow.second; + if(reader.hasAttribute(ATTR_SHADOW) && !IGNORE_SHADOW) + shadow.first = importSubName(reader,reader.getAttribute(ATTR_SHADOW),restoreLabel); + } + }else if(reader.hasAttribute("count")) { + int count = reader.getAttributeAsInteger("count"); + subs.resize(count); + shadows.resize(count); + for (int i = 0; i < count; i++) { + reader.readElement("Sub"); + shadows[i].second = importSubName(reader,reader.getAttribute("value"),restoreLabel); + if(reader.hasAttribute(ATTR_SHADOWED) && !IGNORE_SHADOW) + subs[i] = shadows[i].first = + importSubName(reader,reader.getAttribute(ATTR_SHADOWED),restoreLabel); + else { + subs[i] = shadows[i].second; + if(reader.hasAttribute(ATTR_SHADOW) && !IGNORE_SHADOW) + shadows[i].first = importSubName(reader,reader.getAttribute(ATTR_SHADOW),restoreLabel); + } + if(reader.hasAttribute(ATTR_MAPPED)) + mapped.push_back(i); + } + reader.readEndElement("XLink"); + } + setFlag(LinkRestoreLabel,restoreLabel); + + if (name.empty()) { + setValue(0); + return; + } + + if(file.size() || (!object && name.size())) { + this->stamp = stamp; + _setValue(std::move(file),std::move(name),std::move(subs),std::move(shadows)); + }else + _setValue(object,std::move(subs),std::move(shadows)); + _mapped = std::move(mapped); +} + +Property *PropertyXLink::CopyOnImportExternal( + const std::map &nameMap) const +{ + auto owner = Base::freecad_dynamic_cast(getContainer()); + if(!owner || !owner->getDocument() || !_pcLink || !_pcLink->getNameInDocument()) + return 0; + + auto subs = updateLinkSubs(_pcLink,_SubList, + &tryImportSubName,owner->getDocument(),nameMap); + auto linked = tryImport(owner->getDocument(),_pcLink,nameMap); + if(subs.empty() && linked==_pcLink) + return 0; + + PropertyXLink *p= createInstance(); + copyTo(*p,linked,&subs); + return p; +} + +Property *PropertyXLink::CopyOnLinkReplace(const App::DocumentObject *parent, + App::DocumentObject *oldObj, App::DocumentObject *newObj) const +{ + auto res = tryReplaceLinkSubs(getContainer(),_pcLink,parent,oldObj,newObj,_SubList); + if(!res.first) + return 0; + PropertyXLink *p= createInstance(); + copyTo(*p,res.first,&res.second); + return p; +} + +PropertyXLink *PropertyXLink::createInstance() const { + return new PropertyXLink(); +} + +Property *PropertyXLink::CopyOnLabelChange(App::DocumentObject *obj, + const std::string &ref, const char *newLabel) const +{ + auto owner = dynamic_cast(getContainer()); + if(!owner || !owner->getDocument() || !_pcLink || !_pcLink->getNameInDocument()) + return 0; + auto subs = updateLinkSubs(_pcLink,_SubList,&updateLabelReference,obj,ref,newLabel); + if(subs.empty()) + return 0; + PropertyXLink *p= createInstance(); + copyTo(*p,_pcLink,&subs); + return p; +} + +void PropertyXLink::copyTo(PropertyXLink &other, + DocumentObject *linked, std::vector *subs) const +{ + if(!linked) + linked = _pcLink; + if(linked && linked->getNameInDocument()) { + other.docName = linked->getDocument()->getName(); + other.objectName = linked->getNameInDocument(); + }else{ + other.objectName = objectName; + other.docName.clear(); + other.docInfo = docInfo; + other.filePath = filePath; + } + if(subs) + other._SubList = std::move(*subs); + else + other._SubList = _SubList; + other._Flags = _Flags; +} + +Property *PropertyXLink::Copy(void) const +{ + PropertyXLink *p= createInstance(); + copyTo(*p); + return p; +} + +void PropertyXLink::Paste(const Property &from) +{ + if(!from.isDerivedFrom(PropertyXLink::getClassTypeId())) + throw Base::TypeError("Incompatible proeprty to paste to"); + + const auto &other = static_cast(from); + if(other.docName.size()) { + auto doc = GetApplication().getDocument(other.docName.c_str()); + if(!doc) { + FC_WARN("Document '" << other.docName << "' not found"); + return; + } + auto obj = doc->getObject(other.objectName.c_str()); + if(!obj) { + FC_WARN("Object '" << other.docName << '#' << other.objectName << "' not found"); + return; + } + _setValue(obj,std::vector(other._SubList)); + } else + _setValue(std::string(other.filePath),std::string(other.objectName), + std::vector(other._SubList)); + setFlag(LinkAllowPartial,other.testFlag(LinkAllowPartial)); +} + +bool PropertyXLink::supportXLink(const App::Property *prop) { + return prop->isDerivedFrom(PropertyXLink::getClassTypeId()) || + prop->isDerivedFrom(PropertyXLinkSubList::getClassTypeId()) || + prop->isDerivedFrom(PropertyXLinkContainer::getClassTypeId()); +} + +bool PropertyXLink::hasXLink(const App::Document *doc) { + for(auto &v : _DocInfoMap) { + if(v.second->hasXLink(doc)) + return true; + } + return false; +} + +bool PropertyXLink::hasXLink( + const std::vector &objs, std::vector *unsaved) +{ + std::set docs; + bool ret = false; + for(auto o : objs) { + if(o && o->getNameInDocument() && docs.insert(o->getDocument()).second) { + if(!hasXLink(o->getDocument())) + continue; + if(!unsaved) + return true; + ret = true; + if(!o->getDocument()->isSaved()) + unsaved->push_back(o->getDocument()); + } + } + return ret; +} + +std::map > +PropertyXLink::getDocumentOutList(App::Document *doc) { + std::map > ret; + for(auto &v : _DocInfoMap) { + for(auto link : v.second->links) { + if(!v.second->pcDoc) continue; + auto obj = dynamic_cast(link->getContainer()); + if(!obj || !obj->getNameInDocument() || !obj->getDocument()) + continue; + if(doc && obj->getDocument()!=doc) + continue; + ret[obj->getDocument()].insert(v.second->pcDoc); + } + } + return ret; +} + +std::map > +PropertyXLink::getDocumentInList(App::Document *doc) { + std::map > ret; + for(auto &v : _DocInfoMap) { + if(!v.second->pcDoc || (doc && doc!=v.second->pcDoc)) + continue; + auto &docs = ret[v.second->pcDoc]; + for(auto link : v.second->links) { + auto obj = dynamic_cast(link->getContainer()); + if(obj && obj->getNameInDocument() && obj->getDocument()) + docs.insert(obj->getDocument()); + } + } + return ret; +} + +PyObject *PropertyXLink::getPyObject(void) +{ + if(!_pcLink) + Py_Return; + if(_SubList.empty()) + return _pcLink->getPyObject(); + Py::Tuple ret(2); + ret.setItem(0,Py::Object(_pcLink->getPyObject(),true)); + ret.setItem(1,Py::String(getSubName(true))); + return Py::new_reference_to(ret); +} + +void PropertyXLink::setPyObject(PyObject *value) { + if(PySequence_Check(value)) { + Py::Sequence seq(value); + if(seq.size()!=2) + throw Base::ValueError("Expect input sequence of size 2"); + std::string subname; + PyObject *pyObj = seq[0].ptr(); + PyObject *pySub = seq[1].ptr(); + if(pyObj == Py_None) { + setValue(0); + return; + } else if(!PyObject_TypeCheck(pyObj, &DocumentObjectPy::Type)) + throw Base::TypeError("Expect the first element to be of 'DocumentObject'"); + if (PyUnicode_Check(pySub)) { +#if PY_MAJOR_VERSION >= 3 + subname = PyUnicode_AsUTF8(pySub); +#else + PyObject* unicode = PyUnicode_AsUTF8String(pySub); + subname = PyString_AsString(unicode); + Py_DECREF(unicode); + }else if (PyString_Check(pySub)) { + subname = PyString_AsString(pySub); +#endif + }else + throw Base::TypeError("Expect the second element to be a string"); + setValue(static_cast(pyObj)->getDocumentObjectPtr(), subname.c_str()); + } else if(PyObject_TypeCheck(value, &(DocumentObjectPy::Type))) { + setValue(static_cast(value)->getDocumentObjectPtr()); + } else if (Py_None == value) { + setValue(0); + } else { + throw Base::TypeError("type must be 'DocumentObject', 'None', or '(DocumentObject, SubName)"); + } +} + +const char *PropertyXLink::getSubName(bool newStyle) const { + if(_SubList.empty() || _ShadowSubList.empty()) + return ""; + return getSubNameWithStyle(_SubList[0],_ShadowSubList[0],newStyle).c_str(); +} + +void PropertyXLink::getLinks(std::vector &objs, + bool all, std::vector *subs, bool newStyle) const +{ + if((all||_pcScope!=LinkScope::Hidden) && _pcLink && _pcLink->getNameInDocument()) { + objs.push_back(_pcLink); + if(subs) + if(subs && _SubList.size()==_ShadowSubList.size()) + *subs = getSubValues(newStyle); + } +} + +bool PropertyXLink::adjustLink(const std::set &inList) { + if (_pcScope==LinkScope::Hidden) + return false; + if(!_pcLink || !_pcLink->getNameInDocument() || !inList.count(_pcLink)) + return false; + auto subs = _SubList; + auto link = adjustLinkSubs(this,inList,_pcLink,subs); + if(link) { + _setValue(link,std::move(subs)); + return true; + } + return false; +} + +std::vector PropertyXLink::getSubValues(bool newStyle) const { + assert(_SubList.size() == _ShadowSubList.size()); + std::vector ret; + ret.reserve(_SubList.size()); + for(size_t i=0;i<_ShadowSubList.size();++i) + ret.push_back(getSubNameWithStyle(_SubList[i],_ShadowSubList[i],newStyle)); + return ret; +} + +std::vector PropertyXLink::getSubValuesStartsWith(const char* starter, bool newStyle) const +{ + (void)newStyle; + + std::vector temp; + for(std::vector::const_iterator it=_SubList.begin();it!=_SubList.end();++it) + if(strncmp(starter,it->c_str(),strlen(starter))==0) + temp.push_back(*it); + return temp; +} + +void PropertyXLink::setAllowPartial(bool enable) { + setFlag(LinkAllowPartial,enable); + if(enable) + return; + auto owner = dynamic_cast(getContainer()); + if(!owner) + return; + if(!App::GetApplication().isRestoring() && + !owner->getDocument()->isPerformingTransaction() && + !_pcLink && docInfo && filePath.size() && objectName.size() && + (!docInfo->pcDoc || docInfo->pcDoc->testStatus(Document::PartialDoc))) + { + auto path = docInfo->getDocPath(filePath.c_str(),owner->getDocument(),false); + if(path.size()) + App::GetApplication().openDocument(path.c_str()); + } +} + +//************************************************************************** +// PropertyXLinkSub +//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +TYPESYSTEM_SOURCE(App::PropertyXLinkSub , App::PropertyXLink) + +PropertyXLinkSub::PropertyXLinkSub(bool allowPartial, PropertyLinkBase *parent) + :PropertyXLink(allowPartial,parent) +{ + +} + +PropertyXLinkSub::~PropertyXLinkSub() { +} + +void PropertyXLinkSub::setValue(App::DocumentObject *link, + const std::vector &subs, std::vector &&shadows) +{ + _setValue(link,std::vector(subs),std::move(shadows)); +} + +void PropertyXLinkSub::setValue(App::DocumentObject *link, + std::vector &&subs, std::vector &&shadows) +{ + _setValue(link,std::move(subs),std::move(shadows)); +} + +void PropertyXLinkSub::setSubValues(std::vector &&subs, + std::vector &&shadows) +{ + aboutToSetValue(); + _setSubValues(std::move(subs),std::move(shadows)); + hasSetValue(); +} + +PropertyXLink *PropertyXLinkSub::createInstance() const{ + return new PropertyXLinkSub(); +} + +bool PropertyXLinkSub::upgrade(Base::XMLReader &reader, const char *typeName) { + if(strcmp(typeName, PropertyLinkSubGlobal::getClassTypeId().getName())==0 || + strcmp(typeName, PropertyLinkSub::getClassTypeId().getName())==0 || + strcmp(typeName, PropertyLinkSubChild::getClassTypeId().getName())==0) + { + App::PropertyLinkSub linkProp; + linkProp.setContainer(getContainer()); + linkProp.Restore(reader); + setValue(linkProp.getValue(),linkProp.getSubValues()); + return true; + } + return PropertyXLink::upgrade(reader,typeName); +} + +PyObject *PropertyXLinkSub::getPyObject(void) +{ + Py::Tuple tup(2); + Py::List list(static_cast(_SubList.size())); + if (_pcLink) + tup[0] = Py::asObject(_pcLink->getPyObject()); + else { + tup[0] = Py::None(); + if(_SubList.empty()) + Py_Return; + } + for(unsigned int i = 0;i<_SubList.size(); i++) + list[i] = Py::String(_SubList[i]); + tup[1] = list; + return Py::new_reference_to(tup); +} + +void PropertyXLinkSub::setPyObject(PyObject *value) { + if(PySequence_Check(value)) { + Py::Sequence seq(value); + if(seq.size()!=2) + throw Base::ValueError("Expect input sequence of size 2"); + std::vector subs; + Py::Object pyObj(seq[0].ptr()); + Py::Object pySub(seq[1].ptr()); + if(pyObj.isNone()) { + setValue(0); + return; + } else if(!PyObject_TypeCheck(pyObj.ptr(), &DocumentObjectPy::Type)) + throw Base::TypeError("Expect the first element to be of 'DocumentObject'"); + if(pySub.isString()) + subs.push_back(pySub.as_string()); + else if(pySub.isSequence()) { + Py::Sequence seq(pySub); + subs.reserve(seq.size()); + for(size_t i=0;i(pyObj.ptr())->getDocumentObjectPtr(), std::move(subs)); + } else if(PyObject_TypeCheck(value, &(DocumentObjectPy::Type))) { + setValue(static_cast(value)->getDocumentObjectPtr()); + } else if (Py_None == value) { + setValue(0); + } else { + throw Base::TypeError("type must be 'DocumentObject', 'None', or '(DocumentObject, SubName)' or " + "'DocumentObject, [SubName..])"); + } +} + +//************************************************************************** +// PropertyXLinkSubList +//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +TYPESYSTEM_SOURCE(App::PropertyXLinkSubList , App::PropertyLinkBase) + +//************************************************************************** +// Construction/Destruction + + +PropertyXLinkSubList::PropertyXLinkSubList() +{ + _pcScope = LinkScope::Global; +} + +PropertyXLinkSubList::~PropertyXLinkSubList() +{ +} + +int PropertyXLinkSubList::getSize(void) const +{ + return static_cast(_Links.size()); +} + +void PropertyXLinkSubList::setValue(DocumentObject* lValue,const char* SubName) +{ + std::map > values; + if(lValue) { + auto &subs = values[lValue]; + if(SubName) + subs.emplace_back(SubName); + } + setValues(std::move(values)); +} + +void PropertyXLinkSubList::setValues( + const std::vector& lValue, + const std::vector& lSubNames) +{ +#define CHECK_SUB_SIZE(_l,_r) do{\ + if(_l.size()!=_r.size())\ + FC_THROWM(Base::ValueError, "object and subname size mismatch");\ + }while(0) + CHECK_SUB_SIZE(lValue,lSubNames); + std::map > values; + int i=0; + for(auto &obj : lValue) { + const char *sub = lSubNames[i++]; + if(sub) + values[obj].emplace_back(sub); + } + setValues(std::move(values)); +} + +void PropertyXLinkSubList::setValues(const std::vector& lValue, + const std::vector& lSubNames) +{ + CHECK_SUB_SIZE(lValue,lSubNames); + std::map > values; + int i=0; + for(auto &obj : lValue) + values[obj].push_back(lSubNames[i++]); + setValues(std::move(values)); +} + +void PropertyXLinkSubList::setSubListValues(const std::vector &svalues) { + std::map > values; + for(auto &v : svalues) { + auto &s = values[v.first]; + s.reserve(s.size()+v.second.size()); + s.insert(s.end(),v.second.begin(),v.second.end()); + } + setValues(std::move(values)); +} + +void PropertyXLinkSubList::setValues( + const std::map > &values) +{ + setValues(std::map >(values)); +} + +void PropertyXLinkSubList::setValues( + std::map > &&values) +{ + for(auto &v : values) { + if(!v.first || !v.first->getNameInDocument()) + FC_THROWM(Base::ValueError,"invalid document object"); + } + + aboutToSetValue(); + + for(auto it=_Links.begin(),itNext=it;it!=_Links.end();it=itNext) { + ++itNext; + auto iter = values.find(it->getValue()); + if(iter == values.end()) { + _Links.erase(it); + continue; + } + it->setSubValues(std::move(iter->second)); + values.erase(iter); + } + + for(auto &v : values) { + _Links.emplace_back(testFlag(LinkAllowPartial),this); + _Links.back().setValue(v.first,std::move(v.second)); + } + hasSetValue(); +} + +void PropertyXLinkSubList::addValue(App::DocumentObject *obj, + const std::vector &subs, bool reset) +{ + addValue(obj,std::vector(subs),reset); +} + +void PropertyXLinkSubList::addValue(App::DocumentObject *obj, + std::vector &&subs, bool reset) { + + if(!obj || !obj->getNameInDocument()) + FC_THROWM(Base::ValueError,"invalid document object"); + + for(auto &l : _Links) { + if(l.getValue() == obj) { + auto s = l.getSubValues(); + if(s.empty() || reset) + l.setSubValues(std::move(subs)); + else { + s.reserve(s.size()+subs.size()); + std::move(subs.begin(),subs.end(),std::back_inserter(s)); + l.setSubValues(std::move(s)); + } + return; + } + } + aboutToSetValue(); + _Links.emplace_back(testFlag(LinkAllowPartial),this); + _Links.back().setValue(obj,std::move(subs)); + hasSetValue(); +} + +void PropertyXLinkSubList::setValue(DocumentObject* lValue, const std::vector &SubList) +{ + std::map > values; + if(lValue) + values[lValue] = SubList; + setValues(std::move(values)); +} + +const string PropertyXLinkSubList::getPyReprString() const +{ + if (_Links.empty()) + return std::string("None"); + std::ostringstream ss; + ss << '['; + for(auto &link : _Links) { + auto obj = link.getValue(); + if(!obj || !obj->getNameInDocument()) + continue; + ss << "(App.getDocument('" << obj->getDocument()->getName() + << "').getObject('" << obj->getNameInDocument() << "'), ("; + const auto &subs = link.getSubValues(); + if(subs.empty()) + ss << "''"; + else{ + for(auto &sub : subs) + ss << "'" << sub << "',"; + } + ss << ")), "; + } + ss << ']'; + return ss.str(); +} + +DocumentObject *PropertyXLinkSubList::getValue() const +{ + if(_Links.size()) + return _Links.begin()->getValue(); + return 0; +} + +int PropertyXLinkSubList::removeValue(App::DocumentObject *lValue) +{ + int ret = 0; + auto it = std::find_if(_Links.begin(),_Links.end(), + [=](const PropertyXLinkSub &l){return l.getValue()==lValue;}); + if(it != _Links.end()) { + ret = (int)it->getSubValues().size(); + if(!ret) + ret = 1; + _Links.erase(it); + } + return ret; +} + +PyObject *PropertyXLinkSubList::getPyObject(void) +{ + Py::List list; + for(auto &link : _Links) { + auto obj = link.getValue(); + if(!obj || !obj->getNameInDocument()) + continue; + Py::Tuple tup(2); + tup[0] = Py::asObject(obj->getPyObject()); + + const auto &subs = link.getSubValues(); + Py::Tuple items(subs.size()); + for (std::size_t j = 0; j < subs.size(); j++) { + items[j] = Py::String(subs[j]); + } + tup[1] = items; + list.append(tup); + } + return Py::new_reference_to(list); +} + +void PropertyXLinkSubList::setPyObject(PyObject *value) +{ + try { //try PropertyLinkSub syntax + PropertyLinkSub dummy; + dummy.setAllowExternal(true); + dummy.setPyObject(value); + this->setValue(dummy.getValue(), dummy.getSubValues()); + return; + } + catch (Base::Exception&) {} + + if (!PyTuple_Check(value) && !PyList_Check(value)) + throw Base::TypeError("Invalid type. Accepts (DocumentObject, (subname...)) or sequence of such type."); + Py::Sequence seq(value); + std::map > values; + try { + for(size_t i=0;i" << endl; + writer.incInd(); + for(auto &l : _Links) + l.Save(writer); + writer.decInd(); + writer.Stream() << writer.ind() << "" << endl ; +} + +void PropertyXLinkSubList::Restore(Base::XMLReader &reader) +{ + reader.readElement("XLinkSubList"); + setFlag(LinkAllowPartial, + reader.hasAttribute("partial") && + reader.getAttributeAsInteger("partial")); + int count = reader.getAttributeAsInteger("count"); + _Links.clear(); + for(int i=0;i &nameMap) const +{ + std::unique_ptr copy; + auto it = _Links.begin(); + for(;it!=_Links.end();++it) { + copy.reset(it->CopyOnImportExternal(nameMap)); + if(copy) break; + } + if(!copy) + return 0; + std::unique_ptr p(new PropertyXLinkSubList); + for(auto iter=_Links.begin();iter!=it;++iter) { + p->_Links.emplace_back(); + iter->copyTo(p->_Links.back()); + } + p->_Links.emplace_back(); + static_cast(*copy).copyTo(p->_Links.back()); + for(++it;it!=_Links.end();++it) { + p->_Links.emplace_back(); + copy.reset(it->CopyOnImportExternal(nameMap)); + if(copy) + static_cast(*copy).copyTo(p->_Links.back()); + else + it->copyTo(p->_Links.back()); + } + return p.release(); +} + +Property *PropertyXLinkSubList::CopyOnLabelChange(App::DocumentObject *obj, + const std::string &ref, const char *newLabel) const +{ + std::unique_ptr copy; + auto it = _Links.begin(); + for(;it!=_Links.end();++it) { + copy.reset(it->CopyOnLabelChange(obj,ref,newLabel)); + if(copy) break; + } + if(!copy) + return 0; + std::unique_ptr p(new PropertyXLinkSubList); + for(auto iter=_Links.begin();iter!=it;++iter) { + p->_Links.emplace_back(); + iter->copyTo(p->_Links.back()); + } + p->_Links.emplace_back(); + static_cast(*copy).copyTo(p->_Links.back()); + for(++it;it!=_Links.end();++it) { + p->_Links.emplace_back(); + copy.reset(it->CopyOnLabelChange(obj,ref,newLabel)); + if(copy) + static_cast(*copy).copyTo(p->_Links.back()); + else + it->copyTo(p->_Links.back()); + } + return p.release(); +} + +Property *PropertyXLinkSubList::CopyOnLinkReplace(const App::DocumentObject *parent, + App::DocumentObject *oldObj, App::DocumentObject *newObj) const +{ + std::unique_ptr copy; + PropertyXLinkSub *copied = 0; + std::set subs; + auto it = _Links.begin(); + for(;it!=_Links.end();++it) { + copy.reset(it->CopyOnLinkReplace(parent,oldObj,newObj)); + if(copy) { + copied = static_cast(copy.get()); + if(copied->getValue() == newObj) { + for(auto &sub : copied->getSubValues()) + subs.insert(sub); + } + break; + } + } + if(!copy) + return 0; + std::unique_ptr p(new PropertyXLinkSubList); + for(auto iter=_Links.begin();iter!=it;++iter) { + if(iter->getValue()==newObj && copied->getValue()==newObj) { + // merge subnames in case new object already exists + for(auto &sub : iter->getSubValues()) { + if(subs.insert(sub).second) + copied->_SubList.push_back(sub); + } + } else { + p->_Links.emplace_back(); + iter->copyTo(p->_Links.back()); + } + } + p->_Links.emplace_back(); + copied->copyTo(p->_Links.back()); + copied = &p->_Links.back(); + for(++it;it!=_Links.end();++it) { + if((it->getValue()==newObj||it->getValue()==oldObj) + && copied->getValue()==newObj) + { + // merge subnames in case new object already exists + for(auto &sub : it->getSubValues()) { + if(subs.insert(sub).second) + copied->_SubList.push_back(sub); + } + continue; + } + p->_Links.emplace_back(); + copy.reset(it->CopyOnLinkReplace(parent,oldObj,newObj)); + if(copy) + static_cast(*copy).copyTo(p->_Links.back()); + else + it->copyTo(p->_Links.back()); + } + return p.release(); +} + +Property *PropertyXLinkSubList::Copy(void) const +{ + PropertyXLinkSubList *p = new PropertyXLinkSubList(); + for(auto &l : _Links) { + p->_Links.emplace_back(testFlag(LinkAllowPartial),p); + l.copyTo(p->_Links.back()); + } + return p; +} + +void PropertyXLinkSubList::Paste(const Property &from) +{ + if(!from.isDerivedFrom(PropertyXLinkSubList::getClassTypeId())) + throw Base::TypeError("Incompatible proeprty to paste to"); + + aboutToSetValue(); + _Links.clear(); + for(auto &l : static_cast(from)._Links) { + _Links.emplace_back(testFlag(LinkAllowPartial),this); + _Links.back().Paste(l); + } + hasSetValue(); +} + +unsigned int PropertyXLinkSubList::getMemSize (void) const +{ + unsigned int size=0; + for(auto &l : _Links) + size += l.getMemSize(); + return size; +} + +const std::vector &PropertyXLinkSubList::getSubValues(App::DocumentObject *obj) const { + for(auto &l : _Links) { + if(l.getValue() == obj) + return l.getSubValues(); + } + FC_THROWM(Base::RuntimeError, "object not found"); +} + +std::vector PropertyXLinkSubList::getSubValues(App::DocumentObject *obj, bool newStyle) const { + for(auto &l : _Links) { + if(l.getValue() == obj) + return l.getSubValues(newStyle); + } + return {}; +} + +void PropertyXLinkSubList::getLinks(std::vector &objs, + bool all, std::vector *subs, bool newStyle) const +{ + if(all||_pcScope!=LinkScope::Hidden) { + if(!subs) { + objs.reserve(objs.size()+_Links.size()); + for(auto &l : _Links) { + auto obj = l.getValue(); + if(obj && obj->getNameInDocument()) + objs.push_back(obj); + } + return; + } + size_t count=0; + for(auto &l : _Links) { + auto obj = l.getValue(); + if(obj && obj->getNameInDocument()) + count += l.getSubValues().size(); + } + objs.reserve(objs.size()+count); + subs->reserve(subs->size()+count); + for(auto &l : _Links) { + auto obj = l.getValue(); + if(obj && obj->getNameInDocument()) { + for(auto &sub : l.getSubValues(newStyle)) { + objs.push_back(obj); + subs->push_back(std::move(sub)); + } + } + } + } +} + +void PropertyXLinkSubList::breakLink(App::DocumentObject *obj, bool clear) { + if(clear && getContainer()==obj) { + setValue(0); + return; + } + bool touched = false; + for(auto &l : _Links) { + if(l.getValue() == obj) { + if(!touched) { + touched = true; + aboutToSetValue(); + } + l.setValue(0); + } + } + if(touched) + hasSetValue(); +} + +bool PropertyXLinkSubList::adjustLink(const std::set &inList) { + if (_pcScope==LinkScope::Hidden) + return false; + std::map > values; + bool touched = false; + int count=0; + for(auto &l : _Links) { + auto obj = l.getValue(); + if(!obj || !obj->getNameInDocument()) { + ++count; + continue; + } + if(inList.count(obj) && adjustLinkSubs(this,inList,obj,l._SubList,&values)) + touched = true; + } + if(touched) { + decltype(_Links) tmp; + if(count) { + // XLink allows detached state, i.e. with closed external document. So + // we need to preserve empty link + for(auto it=_Links.begin(),itNext=it;it!=_Links.end();it=itNext) { + ++itNext; + if(!it->getValue()) + tmp.splice(tmp.end(),_Links,it); + } + } + setValues(std::move(values)); + _Links.splice(_Links.end(),tmp); + } + return touched; +} + +int PropertyXLinkSubList::checkRestore(std::string *msg) const { + for(auto &l : _Links) { + int res; + if((res = l.checkRestore(msg))) + return res; + } + return 0; +} + +bool PropertyXLinkSubList::upgrade(Base::XMLReader &reader, const char *typeName) { + if(strcmp(typeName, PropertyLinkSubListGlobal::getClassTypeId().getName())==0 || + strcmp(typeName, PropertyLinkSubList::getClassTypeId().getName())==0 || + strcmp(typeName, PropertyLinkSubListChild::getClassTypeId().getName())==0) + { + PropertyLinkSubList linkProp; + linkProp.setContainer(getContainer()); + linkProp.Restore(reader); + std::map > values; + const auto &objs = linkProp.getValues(); + const auto &subs = linkProp.getSubValues(); + assert(objs.size() == subs.size()); + for(size_t i=0;igetValue(); + if(!obj) + continue; + if(info.docName.size()) { + if(info.docName != obj->getDocument()->getName()) + _DocMap[info.docName] = obj->getDocument()->getName(); + if(info.docLabel != obj->getDocument()->Label.getValue()) + _DocMap[App::quote(info.docLabel)] = obj->getDocument()->Label.getValue(); + } + if(_Deps.insert(obj).second) + _XLinks[obj->getFullName()] = std::move(info.xlink); + } + _XLinkRestores.reset(); +} + +void PropertyXLinkContainer::breakLink(App::DocumentObject *obj, bool clear) { + if(!obj || !obj->getNameInDocument()) + return; + auto owner = dynamic_cast(getContainer()); + if(!owner || !owner->getNameInDocument()) + return; + if(!clear || obj!=owner) { + if(!_Deps.erase(obj)) + return; + aboutToSetValue(); + onBreakLink(obj); + if(obj->getDocument() == owner->getDocument()) + obj->_removeBackLink(owner); + else + _XLinks.erase(obj->getFullName()); + hasSetValue(); + return; + } + if(obj!=owner) + return; + for(auto obj : _Deps) { + if(!obj || !obj->getNameInDocument()) + continue; + onBreakLink(obj); + if(obj->getDocument()==owner->getDocument()) + obj->_removeBackLink(owner); + } + _XLinks.clear(); + _Deps.clear(); +} + +int PropertyXLinkContainer::checkRestore(std::string *msg) const { + if(_LinkRestored) + return 1; + for(auto &v : _XLinks) { + int res = v.second->checkRestore(msg); + if(res) + return res; + } + return 0; +} + +void PropertyXLinkContainer::Save (Base::Writer &writer) const { + + writer.Stream() << writer.ind() << " docSet; + auto owner = Base::freecad_dynamic_cast(getContainer()); + if(owner && !owner->isExporting()) { + // Document name and label can change on restore, we shall record the + // current document name and label and pair it with the associated + // xlink, so that we can restore them correctly. + int i=-1; + for(auto &v : _XLinks) { + ++i; + auto obj = v.second->getValue(); + if(obj && obj->getDocument()) + docSet.insert(std::make_pair(obj->getDocument(),i)); + } + + if(docSet.size()) + writer.Stream() << "\" docs=\"" << docSet.size(); + } + + writer.Stream() << "\">" << std::endl; + writer.incInd(); + + for(auto &v : docSet) { + writer.Stream() << writer.ind() << "getName() + << "\" label=\"" << encodeAttribute(v.first->Label.getValue()) + << "\" index=\"" << v.second << "\"/>" << std::endl; + } + + for(auto &v : _XLinks) + v.second->Save(writer); + writer.decInd(); + + writer.Stream() << writer.ind() << "" << std::endl; +} + +void PropertyXLinkContainer::Restore(Base::XMLReader &reader) { + reader.readElement("XLinks"); + auto count = reader.getAttributeAsUnsigned("count"); + _XLinkRestores.reset(new std::vector(count)); + + if(reader.hasAttribute("docs")) { + auto docCount = reader.getAttributeAsUnsigned("docs"); + _DocMap.clear(); + std::vector docs(count*2); + for(unsigned i=0;i=count) { + FC_ERR(propertyName(this) << " invalid document map entry"); + continue; + } + auto &info = _XLinkRestores->at(index); + info.docName = reader.getAttribute("name"); + info.docLabel = reader.getAttribute("label"); + } + } + + for(auto &info : *_XLinkRestores) { + info.xlink.reset(createXLink()); + info.xlink->Restore(reader); + } + reader.readEndElement("XLinks"); +} + +void PropertyXLinkContainer::aboutToSetChildValue(App::Property &prop) { + auto xlink = dynamic_cast(&prop); + if(xlink && xlink->testFlag(LinkDetached)) { + if(_Deps.erase(const_cast(xlink->getValue()))) + onBreakLink(xlink->getValue()); + } +} + +void PropertyXLinkContainer::onBreakLink(DocumentObject *) { +} + +PropertyXLink *PropertyXLinkContainer::createXLink() { + return new PropertyXLink(false,this); +} + +bool PropertyXLinkContainer::isLinkedToDocument(const App::Document &doc) const { + auto iter = _XLinks.lower_bound(doc.getName()); + if(iter != _XLinks.end()) { + size_t len = strlen(doc.getName()); + return iter->first.size()>len + && iter->first[len] == '#' + && boost::starts_with(iter->first,doc.getName()); + } + return false; +} + +void PropertyXLinkContainer::updateDeps(std::set &&newDeps) { + auto owner = Base::freecad_dynamic_cast(getContainer()); + if(!owner || !owner->getNameInDocument()) + return; + newDeps.erase(owner); + + for(auto obj : newDeps) { + if(obj && obj->getNameInDocument()) { + auto it = _Deps.find(obj); + if(it != _Deps.end()) { + _Deps.erase(it); + continue; + } + if(owner->getDocument()!=obj->getDocument()) { + auto &xlink = _XLinks[obj->getFullName()]; + if(!xlink) { + xlink.reset(createXLink()); + xlink->setValue(obj); + } + } +#ifndef USE_OLD_DAG + else + obj->_addBackLink(owner); +#endif + onAddDep(obj); + } + } + for(auto obj : _Deps) { + if(!obj || !obj->getNameInDocument()) + continue; + if(obj->getDocument()==owner->getDocument()) { +#ifndef USE_OLD_DAG + obj->_removeBackLink(owner); +#endif + }else + _XLinks.erase(obj->getFullName()); + onRemoveDep(obj); + } + _Deps = std::move(newDeps); + + _LinkRestored = testFlag(LinkRestoring); + + if(!_LinkRestored && !testFlag(LinkDetached)) { + for(auto it=_XLinks.begin(),itNext=it;it!=_XLinks.end();it=itNext) { + ++itNext; + if(!it->second->getValue()) + _XLinks.erase(it); + } + } +} + +void PropertyXLinkContainer::clearDeps() { + auto owner = dynamic_cast(getContainer()); + if(!owner || !owner->getNameInDocument()) + return; + for(auto obj : _Deps) { + if(obj && obj->getNameInDocument() && obj->getDocument()==owner->getDocument()) + obj->_removeBackLink(owner); + } + _Deps.clear(); + _XLinks.clear(); + _LinkRestored = false; +} + +void PropertyXLinkContainer::getLinks(std::vector &objs, + bool, std::vector * /*subs*/, bool /*newStyle*/) const +{ + objs.insert(objs.end(),_Deps.begin(),_Deps.end()); +} + diff --git a/src/App/PropertyLinks.h b/src/App/PropertyLinks.h index 7b2ef25fcb06..2bd5a73abac8 100644 --- a/src/App/PropertyLinks.h +++ b/src/App/PropertyLinks.h @@ -28,7 +28,11 @@ #include +#include +#include #include +#include +#include #include "Property.h" namespace Base { @@ -38,6 +42,12 @@ class Writer; namespace App { class DocumentObject; +class Document; + +class DocInfo; +typedef std::shared_ptr DocInfoPtr; + +class PropertyXLink; /** * @brief Defines different scopes for which a link can be valid @@ -45,11 +55,13 @@ class DocumentObject; * Local: links are valid only within the same GeoFeatureGroup as the linkowner is in or in none. * Child: links are valid within the same or any sub GeoFeatureGroup * Global: all possible links are valid + * Hidden: links are not included in dependency calculation */ enum class LinkScope { Local, Child, - Global + Global, + Hidden, }; /** @@ -87,6 +99,468 @@ class AppExport ScopedLink { LinkScope _pcScope = LinkScope::Local; }; +/// Parent class of all link type properties +class AppExport PropertyLinkBase : public Property, public ScopedLink +{ + TYPESYSTEM_HEADER(); +public: + typedef std::pair ShadowSub; + + PropertyLinkBase(); + virtual ~PropertyLinkBase(); + + friend class DocInfo; + + /** Link type property interface APIs + * These APIs are moved here so that any type of property can have the + * property link behavior, e.g. the PropertyExpressionEngine + */ + //@{ + + /** Called to update the element reference of this link property + * + * @sa _updateElementReference() + */ + virtual void updateElementReference(App::DocumentObject *feature, + bool reverse=false, bool notify=false) + { + (void)feature; + (void)reverse; + (void)notify; + } + + /// Clear internal element reference registration + void unregisterElementReference(); + + /** Register label reference for future object relabel update + * + * @param labels: labels to be registered + * @param reset: if ture, then calls unregisterLabelReference() before + * registering + */ + void registerLabelReferences(std::vector &&labels, bool reset=true); + + /** Check subnames for label registeration + * + * @param subs: subname references + * @param reset: if ture, then calls unregisterLabelReference() before + * registering + * + * Check the give subname references and extract any label reference + * inside (by calling getLabelReferences()), and register them. + */ + void checkLabelReferences(const std::vector &subs, bool reset=true); + + /// Clear internal label references registration + void unregisterLabelReferences(); + + /// Test if the element reference has changed after restore + virtual bool referenceChanged() const { + return false; + } + + /** Test if the link is restored unchanged + * + * @param msg: optional error message + * + * @return For external linked object, return 2 in case the link is + * missing, and 1 if the time stamp has changed. + */ + virtual int checkRestore(std::string *msg=0) const { + (void)msg; + return 0; + } + + /** Obtain the linked objects + * + * @param objs: hold the returned linked objects on output + * @param all: if true, then return all the linked object regardless of + * this LinkScope. If false, then return only if the LinkScope + * is not hidden. + * @param sub: if given, then return subname references. + * @param newStyle: whether to return new or old style subname reference + */ + virtual void getLinks(std::vector &objs, + bool all=false, std::vector *subs=0, bool newStyle=true) const = 0; + + /** Called to reset this link property + * + * @param obj: reset link property if it is linked to this object + * @param clear: if true, then also reset property if the owner of this proeprty is \a obj + * + * @sa breakLinks() + */ + virtual void breakLink(App::DocumentObject *obj, bool clear) = 0; + + /** Called to adjust the link to avoid potential cyclic dependency + * + * @param inList: recursive in-list of the would-be parent + * + * @return Return whether the link has been adjusted + * + * This function tries to correct the link to avoid any (sub)object inside + * in-list. If the adjustment is impossible, exception will be raised + */ + virtual bool adjustLink(const std::set &inList) = 0; + + /** Return a copy of the property if the link replacement affects this property + * + * @param owner: the parent object whose link property is to be replace. + * Note that The parent may not be the container of this + * property. Link sub property can use this opportunity to + * adjust its relative links. + * @param oldObj: object to be replaced + * @param newObj: object to replace with + * + * @return Return a copy of the property that is adjusted for the link + * replacement operation. + */ + virtual Property *CopyOnLinkReplace(const App::DocumentObject *parent, + App::DocumentObject *oldObj, App::DocumentObject *newObj) const = 0; + + /** Return a copy of the property if any changes caused by importing external linked object + * + * @param nameMap: a map from the original external object name to the + * imported new object name + * + * @return Returns a copy of the property with the updated link reference if + * affected. The copy will later be assgiend to this property by calling its + * Paste(). + */ + virtual Property *CopyOnImportExternal(const std::map &nameMap) const { + (void)nameMap; + return 0; + } + + /** Update object label reference in this property + * + * @param obj: the object owner of the changing label + * @param ref: subname reference to old label + * @param newLabel: the future new label + * + * @return Returns a copy of the property if its link reference is affected. + * The copy will later be assgiend to this property by calling its Paste(). + */ + virtual Property *CopyOnLabelChange(App::DocumentObject *obj, + const std::string &ref, const char *newLabel) const + { + (void)obj; + (void)ref; + (void)newLabel; + return 0; + } + + /// Helper function to return all linked objects of this property + std::vector linkedObjects(bool all=false) const { + std::vector ret; + getLinks(ret,all); + return ret; + } + + /// Helper function to return linked objects using an std::inserter + template + void getLinkedObjects(T &inserter, bool all=false) const { + std::vector ret; + getLinks(ret,all); + std::copy(ret.begin(),ret.end(),inserter); + } + + /// Helper function to return a map of linked object and its subname references + void getLinkedElements(std::map > &elements, + bool newStyle=true, bool all=true) const + { + std::vector ret; + std::vector subs; + getLinks(ret,all,&subs,newStyle); + assert(ret.size()==subs.size()); + int i=0; + for(auto obj : ret) + elements[obj].push_back(subs[i++]); + } + + /// Helper function to return a map of linked object and its subname references + std::map > + linkedElements(bool newStyle=true, bool all=true) const + { + std::map > ret; + getLinkedElements(ret,newStyle,all); + return ret; + } + //@} + + /** Enable/disable temporary holding external object without throwing exception + * + * Warning, non-PropertyXLink related property does not have internal + * tracking of external objects, therefore the link will not by auto broken + * when external document is closed. Only use this for temporary case, or + * if you handle signalDeleteDocument yourself, or use one of the + * ProeprtyXLink related property. + */ + void setAllowExternal(bool allow); + + /// Helper functions + //@{ + + /** Helper function to check and replace a link + * + * @param owner: the owner of the current property + * @param obj: the current linked object + * @param parent: the parent of the changing link property, may or may not + * be equal to \c owner + * @param oldObj: the object to be replaced + * @param newObj: the object to replace with + * @param sub: optional the current subname reference + * + * @return Returns a pair(obj,subname). If no replacement is found, + * pair.first will be NULL + * + * Say a group has one of its child object replaced with another. Any + * existing link sub reference that refer to the original child object + * through the group will be broken. This helper function is used to check + * and correct any link sub reference. + */ + static std::pair tryReplaceLink( + const App::PropertyContainer *owner, App::DocumentObject *obj, + const App::DocumentObject *parent, App::DocumentObject *oldObj, + App::DocumentObject *newObj, const char *sub=0); + + /** Helper function to check and replace a link with multiple subname refereces + * + * @param owner: the owner of the current property + * @param obj: the current linked object + * @param parent: the parent of the changing link property, may or may not + * be equal to \c owner + * @param oldObj: the object to be replaced + * @param newObj: the object to replace with + * @param subs: the current subname references + * + * @return Returns the a pair(obj,subs). If no replacement is found, + * pair.first will be NULL + * @sa tryReplaceLink() + */ + static std::pair > + tryReplaceLinkSubs( const App::PropertyContainer *owner, + App::DocumentObject *obj, + const App::DocumentObject *parent, + App::DocumentObject *oldObj, + App::DocumentObject *newObj, + const std::vector &subs); + + /// Update all element references in all link properties of \a feature + static void updateElementReferences(DocumentObject *feature, bool reverse=false); + + + /** Helper function for update individual element reference + * + * @param feature: if given, than only update element reference belonging + * to this feature. If not, then update geometry element + * references. + * @param sub: the subname reference to be updated. + * @param shadow: a pair of new and old style element references to be updated. + * @param reverse: if true, then use the old style, i.e. non-mapped element + * reference to query for the new style, i.e. mapped + * element reference when update. If false, then the other + * way around. + * @param notify: if true, call aboutToSetValue() before change + * + * This helper function is to be called by each link property in the event of + * geometry element reference change due to geometry model changes. + */ + bool _updateElementReference(App::DocumentObject *feature, + App::DocumentObject *obj, std::string &sub, ShadowSub &shadow, + bool reverse, bool notify=false); + + /** Helper function to register geometry element reference + * + * @param obj: the linked object + * @param sub: the subname reference + * @param shadow: a pair of new and old style element references to be updated. + * + * Search for any geometry element reference inside the subname, and + * register for future update in case of geometry model update. + */ + void _registerElementReference(App::DocumentObject *obj, std::string &sub, ShadowSub &shadow); + + /** Helper function for breaking link properties + * + * @param link: reset link property if it is linked to this object + * @param objs: the objects to check for the link properties + * @param clear: if ture, then also reset property if the owner of the link property is \a link + * + * App::Document::breakDependency() calls this function to break the link property + */ + static void breakLinks(App::DocumentObject *link, const std::vector &objs, bool clear); + + /** Helper function for link import operation + * + * @param obj: the linked object + * @param sub: subname reference + * @param doc: importing document + * @param nameMap: a name map from source object to its imported counter part + * + * @return Return a changed subname reference, or empty string if no change. + * + * Link import operation will go through all link property and imports all + * externally linked object. After import, the link property must be + * changed to point to the newly imported objects, which should happen inside + * the API CopyOnImportExternal(). This function helps to rewrite subname + * reference to point to the correct sub objects that are imported. + */ + static std::string tryImportSubName(const App::DocumentObject *obj, const char *sub, + const App::Document *doc, const std::map &nameMap); + + /** Helper function for link import operation + * + * @param doc: owner document of the imported objects + * @param obj: the linked object + * @param nameMap: a name map from source object to its imported counter part + * + * @return Return the imported object if found, or the input \c obj if no change. + * @sa tryImportSubNames + * + * This function searches for the name map and tries to find the imported + * object from the given source object. + */ + static App::DocumentObject *tryImport(const App::Document *doc, const App::DocumentObject *obj, + const std::map &nameMap); + + /** Helper function to export a subname reference + * + * @param output: output subname if the subname is modified + * @param obj: linked object + * @param sub: input subname reference + * @param first_obj: if true, then the first object referenced in subname + * is obtained by searching the owner document of obj, + * otherwise the subname search among obj's sub-objects. + * + * @return Return output.c_str() if the subname is modified for exporting + * otherwise, return the input subname + * + * @sa importSubName(), restoreLabelReference() + * + * The function go through the input subname reference and changes any sub + * object references inside for exporting. If the sub object is referenced + * by its internal object name, then the reference is changed from + * 'objName' to 'objName@docName'. If referenced by label, then it will be + * changed to 'objName@docName@' instead. importSubName() and + * restoreLabelReference() can be used together to restore the reference + * during import. + */ + static const char *exportSubName(std::string &output, + const App::DocumentObject *obj, const char *subname, bool first_obj=false); + + /** Helper function to import a subname reference + * + * @param reader: the import reader + * @param sub: input subname reference + * @param restoreLabel: output indicate whether post process is required + * after restore. + * + * @sa exportSubName(), restoreLabelReference() + * + * @return return either an updated subname reference or the input + * reference if no change. If restoreLabel is set to true on output, it + * means there are some label reference changes that must be corrected + * after restore, by calling restoreLabelReference() in property's + * afterRestore(). + */ + static std::string importSubName(Base::XMLReader &reader, const char *sub, bool &restoreLabel); + + /** Helper function to restore label references during import + * + * @param obj: linked object + * @param sub: subname reference + * @param shadow: optional shadow subname reference + * + * @sa exportSubName(), importSubName() + * + * When exporting and importing (i.e. copy and paste) objects into the same + * document, the new object must be renamed, both the internal name and the + * label. Therefore, the link reference of the new objects must be + * corrected accordingly. The basic idea is that when exporting object, all + * object name references are changed to 'objName@docName', and label + * references are changed to 'objName@docName@'. During import, + * MergeDocument will maintain a map from objName@docName to object's new + * name. Object name reference can be restored on spot by consulting the + * map, while label reference will be restored later in property's + * afterRestore() function, which calls this function to do the string + * parsing. + */ + static void restoreLabelReference(const App::DocumentObject *obj, std::string &sub, ShadowSub *shadow=0); + + /** Helper function to extract labels from a subname reference + * + * @param labels: output vector of extracted labels + * @param subname: subname reference + * + * @sa registerLabelReferences() + * + * This function is used to extrac label from subname reference for + * registering of label changes. + */ + static void getLabelReferences(std::vector &labels, const char *subname); + + /** Helper function to collect changed property when an object re-label + * + * @param obj: the object that owns the label + * @param newLabel: the new label + * + * @return return a map from the affected property to a copy of it with + * updated subname references + */ + static std::vector > > updateLabelReferences( + App::DocumentObject *obj, const char *newLabel); + + /** Helper function to update subname reference on label change + * + * @param linked: linked object + * @param subname: subname reference + * @param obj: the object that owns the label + * @param ref: label reference in the format of '$.', which is + * the format used in subname reference for label reference. + * This parameter is provided for easy search of label + * reference. + * @param newLabel: new label + * + * @return Returns an updated subname reference, or empty string if no change. + * + * This function helps to update subname reference on label change. It is + * usually called inside CopyOnLabelChange(), the API for handling label + * change, which is called just before label change. In other word, when + * called, the sub object can still be reached using the original label + * references, but not the new labels. + */ + static std::string updateLabelReference(const App::DocumentObject *linked, const char *subname, + App::DocumentObject *obj, const std::string &ref, const char *newLabel); + //@} + + enum LinkFlags { + LinkAllowExternal, + LinkDetached, + LinkRestoring, + LinkAllowPartial, + LinkRestoreLabel, + }; + inline bool testFlag(int flag) const { + return _Flags.test((std::size_t)flag); + } + + virtual void setAllowPartial(bool enable) { (void)enable; } + +protected: + virtual void hasSetValue() override; + +protected: + std::bitset<32> _Flags; + inline void setFlag(int flag, bool value=true) { + _Flags.set((std::size_t)flag,value); + } + +private: + std::set _LabelRefs; + std::set _ElementRefs; +}; + /** The general Link Property * Main Purpose of this property is to Link Objects and Features in a document. Like all links this * property is scope aware, meaning it does define which objects are allowed to be linked depending @@ -95,7 +569,7 @@ class AppExport ScopedLink { * @note Links that are invalid in respect to the scope of this property is set to are not rejected. * They are only detected to be invalid and prevent the feature from recomputing. */ -class AppExport PropertyLink : public Property, public ScopedLink +class AppExport PropertyLink : public PropertyLinkBase { TYPESYSTEM_HEADER(); @@ -112,9 +586,11 @@ class AppExport PropertyLink : public Property, public ScopedLink */ virtual ~PropertyLink(); + void resetLink(); + /** Sets the property */ - void setValue(App::DocumentObject *); + virtual void setValue(App::DocumentObject *); /** This method returns the linked DocumentObject */ @@ -146,6 +622,15 @@ class AppExport PropertyLink : public Property, public ScopedLink virtual const char* getEditorName(void) const { return "Gui::PropertyEditor::PropertyLinkItem"; } + virtual void getLinks(std::vector &objs, + bool all=false, std::vector *subs=0, bool newStyle=true) const override; + + virtual void breakLink(App::DocumentObject *obj, bool clear) override; + + virtual bool adjustLink(const std::set &inList) override; + + virtual Property *CopyOnLinkReplace(const App::DocumentObject *parent, + App::DocumentObject *oldObj, App::DocumentObject *newObj) const override; protected: App::DocumentObject *_pcLink; }; @@ -168,9 +653,30 @@ class AppExport PropertyLinkGlobal : public PropertyLink PropertyLinkGlobal() {_pcScope = LinkScope::Global;}; }; -class AppExport PropertyLinkList : public PropertyLists, public ScopedLink +/** The general Link Property that are hidden from dependency checking + */ +class AppExport PropertyLinkHidden : public PropertyLink +{ + TYPESYSTEM_HEADER(); +public: + PropertyLinkHidden() {_pcScope = LinkScope::Hidden;}; +}; + + +class AppExport PropertyLinkListBase: public PropertyLinkBase, public PropertyListsBase +{ + TYPESYSTEM_HEADER(); +public: + virtual void setPyObject(PyObject *obj) override { + _setPyObject(obj); + } +}; + +class AppExport PropertyLinkList : + public PropertyListsT, PropertyLinkListBase> { TYPESYSTEM_HEADER(); + typedef PropertyListsT,PropertyLinkListBase> inherited; public: /** @@ -186,29 +692,15 @@ class AppExport PropertyLinkList : public PropertyLists, public ScopedLink virtual ~PropertyLinkList(); virtual void setSize(int newSize); - virtual int getSize(void) const; + virtual void setSize(int newSize, const_reference def); /** Sets the property - */ - void setValue(DocumentObject*); - void setValues(const std::vector&); + */ + void setValues(const std::vector&) override; - /// index operator - DocumentObject* operator[] (const int idx) const { - return _lValueList.operator[] (idx); - } - - - void set1Value(const int idx, DocumentObject* value) { - _lValueList.operator[] (idx) = value; - } - - const std::vector &getValues(void) const { - return _lValueList; - } + void set1Value(int idx, DocumentObject * const &value) override; virtual PyObject *getPyObject(void); - virtual void setPyObject(PyObject *); virtual void Save(Base::Writer &writer) const; virtual void Restore(Base::XMLReader &reader); @@ -220,8 +712,27 @@ class AppExport PropertyLinkList : public PropertyLists, public ScopedLink virtual const char* getEditorName(void) const { return "Gui::PropertyEditor::PropertyLinkListItem"; } -private: - std::vector _lValueList; + virtual void getLinks(std::vector &objs, + bool all=false, std::vector *subs=0, bool newStyle=true) const override; + + virtual void breakLink(App::DocumentObject *obj, bool clear) override; + + virtual bool adjustLink(const std::set &inList) override; + + virtual Property *CopyOnLinkReplace(const App::DocumentObject *parent, + App::DocumentObject *oldObj, App::DocumentObject *newObj) const override; + + DocumentObject *find(const std::string &, int *pindex=0) const; + DocumentObject *find(const char *sub, int *pindex=0) const { + if(!sub) return 0; + return find(std::string(sub),pindex); + } + +protected: + DocumentObject *getPyValue(PyObject *item) const override; + +protected: + mutable std::map _nameMap; }; /** The general Link Property with Child scope @@ -242,13 +753,24 @@ class AppExport PropertyLinkListGlobal : public PropertyLinkList PropertyLinkListGlobal() {_pcScope = LinkScope::Global;}; }; +/** The general Link Property that are hidden from dependency checking + */ +class AppExport PropertyLinkListHidden : public PropertyLinkList +{ + TYPESYSTEM_HEADER(); +public: + PropertyLinkListHidden() {_pcScope = LinkScope::Hidden;}; +}; + +class PropertyXLinkSub; + /** the Link Property with sub elements * This property links an object and a defined sequence of * sub elements. These subelements (like Edges of a Shape) * are stored as names, which can be resolved by the * ComplexGeoDataType interface to concrete sub objects. */ -class AppExport PropertyLinkSub : public Property, public ScopedLink +class AppExport PropertyLinkSub : public PropertyLinkBase { TYPESYSTEM_HEADER(); @@ -265,9 +787,15 @@ class AppExport PropertyLinkSub : public Property, public ScopedLink */ virtual ~PropertyLinkSub(); + virtual void afterRestore() override; + virtual void onContainerRestored() override; + /** Sets the property */ - void setValue(App::DocumentObject *,const std::vector &SubList=std::vector()); + void setValue(App::DocumentObject *,const std::vector &SubList, + std::vector &&ShadowSubList={}); + void setValue(App::DocumentObject *,std::vector &&SubList={}, + std::vector &&ShadowSubList={}); /** This method returns the linked DocumentObject */ @@ -276,8 +804,15 @@ class AppExport PropertyLinkSub : public Property, public ScopedLink /// return the list of sub elements const std::vector& getSubValues(void) const; + /// return the list of sub elements with mapped names + const std::vector &getShadowSubs() const { + return _ShadowSubList; + } + + std::vector getSubValues(bool newStyle) const; + /// return the list of sub elements starts with a special string - std::vector getSubValuesStartsWith(const char*) const; + std::vector getSubValuesStartsWith(const char*, bool newStyle=false) const; /** Returns the link type checked */ @@ -299,13 +834,37 @@ class AppExport PropertyLinkSub : public Property, public ScopedLink virtual Property *Copy(void) const; virtual void Paste(const Property &from); + /// Return a copy of the property if any changes caused by importing external object + virtual Property *CopyOnImportExternal(const std::map &nameMap) const override; + + virtual Property *CopyOnLabelChange(App::DocumentObject *obj, + const std::string &ref, const char *newLabel) const override; + + virtual Property *CopyOnLinkReplace(const App::DocumentObject *parent, + App::DocumentObject *oldObj, App::DocumentObject *newObj) const override; + virtual unsigned int getMemSize (void) const{ return sizeof(App::DocumentObject *); } + virtual void updateElementReference( + DocumentObject *feature,bool reverse=false, bool notify=false) override; + + virtual bool referenceChanged() const override; + + virtual void getLinks(std::vector &objs, + bool all=false, std::vector *subs=0, bool newStyle=true) const override; + + virtual void breakLink(App::DocumentObject *obj, bool clear) override; + + virtual bool adjustLink(const std::set &inList) override; + protected: App::DocumentObject* _pcLinkSub; std::vector _cSubList; + std::vector _ShadowSubList; + std::vector _mapped; + bool _restoreLabel; }; /** The general Link Property with Child scope @@ -326,7 +885,16 @@ class AppExport PropertyLinkSubGlobal : public PropertyLinkSub PropertyLinkSubGlobal() {_pcScope = LinkScope::Global;}; }; -class AppExport PropertyLinkSubList : public PropertyLists, public ScopedLink +/** The general Link Property that are hidden from dependency checking + */ +class AppExport PropertyLinkSubHidden : public PropertyLinkSub +{ + TYPESYSTEM_HEADER(); +public: + PropertyLinkSubHidden() {_pcScope = LinkScope::Hidden;}; +}; + +class AppExport PropertyLinkSubList : public PropertyLinkBase { TYPESYSTEM_HEADER(); @@ -344,15 +912,21 @@ class AppExport PropertyLinkSubList : public PropertyLists, public ScopedLink */ virtual ~PropertyLinkSubList(); - virtual void setSize(int newSize); - virtual int getSize(void) const; + virtual void afterRestore() override; + virtual void onContainerRestored() override; + + int getSize(void) const; + void setSize(int newSize); /** Sets the property. * setValue(0, whatever) clears the property */ void setValue(DocumentObject*,const char*); void setValues(const std::vector&,const std::vector&); - void setValues(const std::vector&,const std::vector&); + void setValues(const std::vector&,const std::vector&, + std::vector &&ShadowSubList={}); + void setValues(std::vector&&, std::vector &&subs, + std::vector &&ShadowSubList={}); /** * @brief setValue: PropertyLinkSub-compatible overload @@ -378,6 +952,12 @@ class AppExport PropertyLinkSubList : public PropertyLists, public ScopedLink return _lSubList; } + std::vector getSubValues(bool newStyle) const; + + const std::vector &getShadowSubs() const { + return _ShadowSubList; + } + /** * @brief Removes all occurrences of \a lValue in the property * together with its sub-elements and returns the number of entries removed. @@ -385,7 +965,7 @@ class AppExport PropertyLinkSubList : public PropertyLists, public ScopedLink int removeValue(App::DocumentObject *lValue); void setSubListValues(const std::vector&); - std::vector getSubListValues() const; + std::vector getSubListValues(bool newStyle=false) const; virtual PyObject *getPyObject(void); virtual void setPyObject(PyObject *); @@ -396,12 +976,36 @@ class AppExport PropertyLinkSubList : public PropertyLists, public ScopedLink virtual Property *Copy(void) const; virtual void Paste(const Property &from); + /// Return a copy of the property if any changes caused by importing external object + virtual Property *CopyOnImportExternal(const std::map &nameMap) const override; + + virtual Property *CopyOnLabelChange(App::DocumentObject *obj, + const std::string &ref, const char *newLabel) const override; + + virtual Property *CopyOnLinkReplace(const App::DocumentObject *parent, + App::DocumentObject *oldObj, App::DocumentObject *newObj) const override; + virtual unsigned int getMemSize (void) const; + virtual void updateElementReference( + DocumentObject *feature,bool reverse=false, bool notify=false) override; + + virtual bool referenceChanged() const override; + + virtual void getLinks(std::vector &objs, + bool all=false, std::vector *subs=0, bool newStyle=true) const override; + + virtual void breakLink(App::DocumentObject *obj, bool clear) override; + + virtual bool adjustLink(const std::set &inList) override; + private: //FIXME: Do not make two independent lists because this will lead to some inconsistencies! std::vector _lValueList; std::vector _lSubList; + std::vector _ShadowSubList; + std::vector _mapped; + bool _restoreLabel; }; /** The general Link Property with Child scope @@ -422,6 +1026,312 @@ class AppExport PropertyLinkSubListGlobal : public PropertyLinkSubList PropertyLinkSubListGlobal() {_pcScope = LinkScope::Global;}; }; +/** The general Link Property that are hidden from dependency checking + */ +class AppExport PropertyLinkSubListHidden : public PropertyLinkSubList +{ + TYPESYSTEM_HEADER(); +public: + PropertyLinkSubListHidden() {_pcScope = LinkScope::Hidden;}; +}; + +class PropertyXLinkSubList; + +/** Link to an (sub)object in the same or different document + */ +class AppExport PropertyXLink : public PropertyLinkGlobal +{ + TYPESYSTEM_HEADER(); + +public: + PropertyXLink(bool allowPartial=false, PropertyLinkBase *parent=0); + + virtual ~PropertyXLink(); + + PropertyLinkBase *parent() const { return parentProp; } + + virtual void afterRestore() override; + virtual void onContainerRestored() override; + + void setValue(App::DocumentObject *) override; + void setValue(App::DocumentObject *, const char *subname); + + const char *getSubName(bool newStyle=true) const; + void setSubName(const char *subname); + + bool hasSubName() const {return !_SubList.empty();} + + App::Document *getDocument() const; + const char *getDocumentPath() const; + const char *getObjectName() const; + + virtual int checkRestore(std::string *msg=0) const override; + + virtual void Save (Base::Writer &writer) const; + virtual void Restore(Base::XMLReader &reader); + + virtual Property *Copy(void) const; + virtual void Paste(const Property &from); + + /// Return a copy of the property if any changes caused by importing external object + virtual Property *CopyOnImportExternal(const std::map &nameMap) const override; + + virtual Property *CopyOnLabelChange(App::DocumentObject *obj, + const std::string &ref, const char *newLabel) const override; + + virtual Property *CopyOnLinkReplace(const App::DocumentObject *parent, + App::DocumentObject *oldObj, App::DocumentObject *newObj) const override; + + virtual PyObject *getPyObject(void); + virtual void setPyObject(PyObject *); + + friend class DocInfo; + + static bool supportXLink(const App::Property *prop); + static bool hasXLink(const App::Document *doc); + static bool hasXLink(const std::vector &objs, std::vector *unsaved=0); + static std::map > getDocumentOutList(App::Document *doc=0); + static std::map > getDocumentInList(App::Document *doc=0); + + virtual void updateElementReference( + DocumentObject *feature,bool reverse=false, bool notify=false) override; + + virtual bool referenceChanged() const override; + + virtual void getLinks(std::vector &objs, + bool all=false, std::vector *subs=0, bool newStyle=true) const override; + + virtual bool adjustLink(const std::set &inList) override; + + // The following APIs are provided to be compatible with PropertyLinkSub. + // Note that although PropertyXLink is capable of holding multiple subnames, + // there no public APIs allowing user to set more that one subname. Multiple + // subname adding API is published in PropertyXLinkSub. + + const std::vector& getSubValues(void) const { + return _SubList; + } + const std::vector &getShadowSubs() const { + return _ShadowSubList; + } + std::vector getSubValues(bool newStyle) const; + std::vector getSubValuesStartsWith(const char*, bool newStyle=false) const; + + virtual void setAllowPartial(bool enable) override; + +protected: + void unlink(); + void detach(); + + void restoreLink(App::DocumentObject *); + + void _setSubValues(std::vector &&SubList, + std::vector &&ShadowSubList = {}); + + void _setValue(std::string &&filePath, std::string &&objectName, std::vector &&SubList, + std::vector &&ShadowSubList = {}); + + void _setValue(App::DocumentObject *,std::vector &&SubList, + std::vector &&ShadowSubList = {}); + + virtual PropertyXLink *createInstance() const; + + virtual bool upgrade(Base::XMLReader &reader, const char *typeName); + + void copyTo(PropertyXLink &other, App::DocumentObject *linked=0, std::vector *subs=0) const; + + virtual void aboutToSetValue() override; + + virtual void hasSetValue() override; + + friend class PropertyXLinkSubList; + +protected: + DocInfoPtr docInfo; + std::string filePath; + std::string docName; + std::string objectName; + std::string stamp; + std::vector _SubList; + std::vector _ShadowSubList; + std::vector _mapped; + PropertyLinkBase *parentProp; +}; + + +/** Link to one or more (sub)object from the same or different document + */ +class AppExport PropertyXLinkSub: public PropertyXLink { + TYPESYSTEM_HEADER(); + +public: + PropertyXLinkSub(bool allowPartial=false, PropertyLinkBase *parent=0); + + virtual ~PropertyXLinkSub(); + + void setValue(App::DocumentObject *,const std::vector &SubList, + std::vector &&ShadowSubList={}); + + void setValue(App::DocumentObject *,std::vector &&SubList={}, + std::vector &&ShadowSubList={}); + + void setSubValues(std::vector &&SubList, + std::vector &&ShadowSubList={}); + + virtual bool upgrade(Base::XMLReader &reader, const char *typeName) override; + + virtual PyObject *getPyObject(void); + virtual void setPyObject(PyObject *); + +protected: + virtual PropertyXLink *createInstance() const; +}; + + +/** Link to one or more (sub)object(s) of one or more object(s) from the same or different document + */ +class AppExport PropertyXLinkSubList: public PropertyLinkBase { + TYPESYSTEM_HEADER(); + +public: + PropertyXLinkSubList(); + virtual ~PropertyXLinkSubList(); + + virtual void afterRestore() override; + virtual void onContainerRestored() override; + + int getSize(void) const; + + /** Sets the property. + * setValue(0, whatever) clears the property + */ + void setValue(DocumentObject*,const char*); + void setValues(const std::vector&,const std::vector&); + void setValues(const std::vector&,const std::vector&); + void setValues(std::map > &&); + void setValues(const std::map > &); + + void addValue(App::DocumentObject *obj, const std::vector &SubList={}, bool reset=false); + void addValue(App::DocumentObject *obj, std::vector &&SubList={}, bool reset=false); + + /** + * @brief setValue: PropertyLinkSub-compatible overload + * @param SubList + */ + void setValue(App::DocumentObject *lValue, const std::vector &SubList=std::vector()); + + std::vector getValues(void); + + const std::string getPyReprString() const; + + DocumentObject* getValue() const; + + const std::vector &getSubValues(App::DocumentObject *obj) const; + + std::vector getSubValues(App::DocumentObject *obj, bool newStyle) const; + + const std::vector &getShadowSubs(App::DocumentObject *obj) const; + + /** + * @brief Removes all occurrences of \a lValue in the property + * together with its sub-elements and returns the number of entries removed. + */ + int removeValue(App::DocumentObject *lValue); + + void setSubListValues(const std::vector&); + + const std::list &getSubListValues() const { + return _Links; + } + + virtual PyObject *getPyObject(void); + virtual void setPyObject(PyObject *); + + virtual void Save (Base::Writer &writer) const; + virtual void Restore(Base::XMLReader &reader); + + virtual Property *Copy(void) const; + virtual void Paste(const Property &from); + + virtual Property *CopyOnImportExternal(const std::map &nameMap) const override; + + virtual Property *CopyOnLabelChange(App::DocumentObject *obj, + const std::string &ref, const char *newLabel) const override; + + virtual Property *CopyOnLinkReplace(const App::DocumentObject *parent, + App::DocumentObject *oldObj, App::DocumentObject *newObj) const override; + + virtual unsigned int getMemSize (void) const; + + virtual void updateElementReference( + DocumentObject *feature,bool reverse=false, bool notify=false) override; + + virtual bool referenceChanged() const override; + + virtual void getLinks(std::vector &objs, + bool all=false, std::vector *subs=0, bool newStyle=true) const override; + + virtual void breakLink(App::DocumentObject *obj, bool clear) override; + + virtual bool adjustLink(const std::set &inList) override; + + bool upgrade(Base::XMLReader &reader, const char *typeName); + + virtual int checkRestore(std::string *msg=0) const override; + + virtual void setAllowPartial(bool enable) override; + + virtual void hasSetChildValue(Property &) override; + virtual void aboutToSetChildValue(Property &) override; + +protected: + std::list _Links; +}; + +/** Abstract property that can link to multiple external objects + * + * @sa See PropertyExpressionEngine for example usage + */ +class AppExport PropertyXLinkContainer : public PropertyLinkBase { + TYPESYSTEM_HEADER(); +public: + PropertyXLinkContainer(); + ~PropertyXLinkContainer(); + + virtual void afterRestore() override; + virtual int checkRestore(std::string *msg=0) const override; + virtual void Save (Base::Writer &writer) const override; + virtual void Restore(Base::XMLReader &reader) override; + virtual void breakLink(App::DocumentObject *obj, bool clear) override; + virtual void getLinks(std::vector &objs, + bool all=false, std::vector *subs=0, bool newStyle=true) const override; + + bool isLinkedToDocument(const App::Document &doc) const; + +protected: + virtual void aboutToSetChildValue(App::Property &prop) override; + virtual PropertyXLink *createXLink(); + virtual void onBreakLink(App::DocumentObject *obj); + virtual void onAddDep(App::DocumentObject *) {} + virtual void onRemoveDep(App::DocumentObject *) {} + void updateDeps(std::set &&newDeps); + void clearDeps(); + +protected: + std::set _Deps; + std::map > _XLinks; + std::map _DocMap; + bool _LinkRestored; + +private: + struct RestoreInfo { + std::unique_ptr xlink; + std::string docName; + std::string docLabel; + }; + std::unique_ptr > _XLinkRestores; +}; + } // namespace App