Files
create/src/App/PropertyLinks.cpp
2024-07-10 10:29:03 +02:00

5114 lines
174 KiB
C++

/***************************************************************************
* Copyright (c) 2002 Jürgen Riegel <juergen.riegel@web.de> *
* *
* This file is part of the FreeCAD CAx development system. *
* *
* This library is free software; you can redistribute it and/or *
* modify it under the terms of the GNU Library General Public *
* License as published by the Free Software Foundation; either *
* version 2 of the License, or (at your option) any later version. *
* *
* This library is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU Library General Public License for more details. *
* *
* You should have received a copy of the GNU Library General Public *
* License along with this library; see the file COPYING.LIB. If not, *
* write to the Free Software Foundation, Inc., 59 Temple Place, *
* Suite 330, Boston, MA 02111-1307, USA *
* *
***************************************************************************/
#include "PreCompiled.h"
#include <QDir>
#include <QFileInfo>
#include <boost/algorithm/string/predicate.hpp>
#include <Base/Console.h>
#include <Base/Exception.h>
#include <Base/Reader.h>
#include <Base/Writer.h>
#include "PropertyLinks.h"
#include "Application.h"
#include "Document.h"
#include "DocumentObject.h"
#include "DocumentObjectPy.h"
#include "ObjectIdentifier.h"
#include "ElementNamingUtils.h"
#include "GeoFeature.h"
FC_LOG_LEVEL_INIT("PropertyLinks",true,true)
using namespace App;
using namespace Base;
using namespace std;
namespace sp = std::placeholders;
//**************************************************************************
//**************************************************************************
// PropertyLinkBase
//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
TYPESYSTEM_SOURCE_ABSTRACT(App::PropertyLinkBase , App::Property)
static std::unordered_map<std::string, std::set<PropertyLinkBase*> > _LabelMap;
static std::unordered_map<App::DocumentObject *, std::unordered_set<PropertyLinkBase*> > _ElementRefMap;
PropertyLinkBase::PropertyLinkBase() = default;
PropertyLinkBase::~PropertyLinkBase() {
unregisterLabelReferences();
unregisterElementReference();
}
void PropertyLinkBase::setAllowExternal(bool allow) {
setFlag(LinkAllowExternal,allow);
}
void PropertyLinkBase::hasSetValue() {
auto owner = dynamic_cast<DocumentObject*>(getContainer());
if(owner)
owner->clearOutListCache();
Property::hasSetValue();
}
bool PropertyLinkBase::isSame(const Property &other) const
{
if(&other == this)
return true;
if(other.isDerivedFrom(PropertyLinkBase::getClassTypeId())
|| getScope() != static_cast<const PropertyLinkBase*>(&other)->getScope())
return false;
static std::vector<App::DocumentObject*> ret;
static std::vector<std::string> subs;
static std::vector<App::DocumentObject*> ret2;
static std::vector<std::string> subs2;
ret.clear();
subs.clear();
ret2.clear();
subs2.clear();
getLinks(ret,true,&subs,false);
static_cast<const PropertyLinkBase *>(&other)->getLinks(ret2,true,&subs2,true);
return ret==ret2 && subs==subs2;
}
void PropertyLinkBase::unregisterElementReference() {
#ifdef FC_USE_TNP_FIX
for (auto obj : _ElementRefs) {
auto it = _ElementRefMap.find(obj);
if (it != _ElementRefMap.end()) {
it->second.erase(this);
if (it->second.empty()) {
_ElementRefMap.erase(it);
}
}
}
_ElementRefs.clear();
#endif
}
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<std::string> &subs,const char *subname) {
const char *dot;
for (; (subname = strchr(subname, '$')) != nullptr; subname = dot + 1) {
++subname;
dot = strchr(subname,'.');
if(!dot) break;
subs.emplace_back(subname,dot-subname);
}
}
void PropertyLinkBase::registerLabelReferences(std::vector<std::string> &&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<std::string> &subs, bool reset) {
if(reset)
unregisterLabelReferences();
std::vector<std::string> 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->isAttachedToDocument() || !parent || !parent->isAttachedToDocument())
return {};
// Because the label is allowed to be the same across different
// hierarchies, we have to search for all occurrences, 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())) != nullptr); 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::vector<std::pair<Property*, std::unique_ptr<Property> > >
PropertyLinkBase::updateLabelReferences(App::DocumentObject *obj, const char *newLabel)
{
std::vector<std::pair<Property*,std::unique_ptr<Property> > > ret;
if(!obj || !obj->isAttachedToDocument())
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<PropertyLinkBase*> 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<Property> 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 {};
if(!prop->getContainer() || !prop->hasName()) {
auto xlink = Base::freecad_dynamic_cast<const PropertyXLink>(prop);
if(xlink)
return propertyName(xlink->parent());
}
return prop->getFullName();
}
const std::unordered_set<PropertyLinkBase*>&
PropertyLinkBase::getElementReferences(DocumentObject* feature)
{
static std::unordered_set<PropertyLinkBase*> none;
auto it = _ElementRefMap.find(feature);
if (it == _ElementRefMap.end()) {
return none;
}
return it->second;
}
void PropertyLinkBase::updateElementReferences(DocumentObject *feature, bool reverse) {
#ifdef FC_USE_TNP_FIX
if (!feature || !feature->getNameInDocument()) {
return;
}
auto it = _ElementRefMap.find(feature);
if (it == _ElementRefMap.end()) {
return;
}
std::vector<PropertyLinkBase*> props;
props.reserve(it->second.size());
props.insert(props.end(), it->second.begin(), it->second.end());
for (auto prop : props) {
if (prop->getContainer()) {
try {
prop->updateElementReference(feature, reverse, true);
}
catch (Base::Exception& e) {
e.ReportException();
FC_ERR("Failed to update element reference of " << propertyName(prop));
}
catch (std::exception& e) {
FC_ERR("Failed to update element reference of " << propertyName(prop) << ": "
<< e.what());
}
}
}
#else
(void)feature;
(void)reverse;
#endif
}
void PropertyLinkBase::_registerElementReference(App::DocumentObject *obj, std::string &sub, ShadowSub &shadow)
{
#ifdef FC_USE_TNP_FIX
if (!obj || !obj->getNameInDocument() || sub.empty()) {
return;
}
if (shadow.first.empty()) {
_updateElementReference(0, obj, sub, shadow, false);
return;
}
GeoFeature* geo = nullptr;
const char* element = nullptr;
std::pair<std::string, std::string> elementName;
GeoFeature::resolveElement(obj,
sub.c_str(),
elementName,
true,
GeoFeature::ElementNameType::Export,
0,
&element,
&geo);
if (!geo || !element || !element[0]) {
return;
}
if (_ElementRefs.insert(geo).second) {
_ElementRefMap[geo].insert(this);
}
#else
(void)obj;
(void)sub;
(void)shadow;
#endif
}
class StringGuard {
public:
explicit 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 label 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)
{
#ifdef FC_USE_TNP_FIX
if (!obj || !obj->getNameInDocument()) {
return false;
}
ShadowSub elementName;
const char* subname;
if (shadow.first.size()) {
subname = shadow.first.c_str();
}
else if (shadow.second.size()) {
subname = shadow.second.c_str();
}
else {
subname = sub.c_str();
}
GeoFeature* geo = nullptr;
const char* element = nullptr;
auto ret = GeoFeature::resolveElement(obj,
subname,
elementName,
true,
GeoFeature::ElementNameType::Export,
feature,
&element,
&geo);
if (!ret || !geo || !element || !element[0]) {
if (elementName.second.size()) {
shadow.second.swap(elementName.second);
}
return false;
}
if (_ElementRefs.insert(geo).second) {
_ElementRefMap[geo].insert(this);
}
if (!reverse) {
if (elementName.first.empty()) {
shadow.second.swap(elementName.second);
return false;
}
if (shadow == elementName) {
return false;
}
}
bool missing = GeoFeature::hasMissingElement(elementName.second.c_str());
if (feature == geo && (missing || reverse)) {
// If the referenced element is missing, or we are generating element
// map for the first time, or we are re-generating the element map due
// to version change, i.e. 'reverse', try search by geometry first
const char* oldElement = Data::findElementName(shadow.second.c_str());
if (!Data::hasMissingElement(oldElement)) {
const auto& names = geo->searchElementCache(oldElement);
// std::vector<std::string> names; // searchElementCache isn't implemented.
if (names.size()) {
missing = false;
std::string newsub(subname, strlen(subname) - strlen(element));
newsub += names.front();
GeoFeature::resolveElement(obj,
newsub.c_str(),
elementName,
true,
GeoFeature::ElementNameType::Export,
feature);
const auto& oldName = shadow.first.size() ? shadow.first : shadow.second;
const auto& newName =
elementName.first.size() ? elementName.first : elementName.second;
if (oldName != newName) {
FC_WARN(propertyName(this)
<< " auto change element reference " << ret->getFullName() << " "
<< oldName << " -> " << newName);
}
}
}
}
if (notify) {
aboutToSetValue();
}
auto updateSub = [&](const std::string& newSub) {
if (sub != newSub) {
// signalUpdateElementReference(sub, newSub);
sub = newSub;
}
};
if (missing) {
FC_WARN(propertyName(this)
<< " missing element reference " << ret->getFullName() << " "
<< (elementName.first.size() ? elementName.first : elementName.second));
shadow.second.swap(elementName.second);
}
else {
FC_TRACE(propertyName(this) << " element reference shadow update " << ret->getFullName()
<< " " << shadow.first << " -> " << elementName.first);
shadow.swap(elementName);
if (shadow.first.size() && Data::hasMappedElementName(sub.c_str())) {
updateSub(shadow.first);
}
}
if (reverse) {
if (shadow.first.size() && Data::hasMappedElementName(sub.c_str())) {
updateSub(shadow.first);
}
else {
updateSub(shadow.second);
}
return true;
}
if (missing) {
if (sub != shadow.first) {
updateSub(shadow.second);
}
return true;
}
auto pos2 = shadow.first.rfind('.');
if (pos2 == std::string::npos) {
return true;
}
++pos2;
auto pos = sub.rfind('.');
if (pos == std::string::npos) {
pos = 0;
}
else {
++pos;
}
if (pos == pos2) {
if (sub.compare(pos, sub.size() - pos, &shadow.first[pos2]) != 0) {
FC_LOG("element reference update " << sub << " -> " << shadow.first);
std::string newSub(sub);
newSub.replace(pos, sub.size() - pos, &shadow.first[pos2]);
updateSub(newSub);
}
}
else if (sub != shadow.second) {
FC_LOG("element reference update " << sub << " -> " << shadow.second);
updateSub(shadow.second);
}
return true;
#else
(void)feature;
(void)obj;
(void)reverse;
(void)notify;
shadow.second = sub;
return false;
#endif
}
std::pair<DocumentObject*, std::string>
PropertyLinkBase::tryReplaceLink(const PropertyContainer* owner,
DocumentObject* obj,
const DocumentObject* parent,
DocumentObject* oldObj,
DocumentObject* newObj,
const char* subname)
{
std::pair<DocumentObject*, std::string> res;
res.first = 0;
if (!obj) {
return res;
}
if (oldObj == obj) {
if (owner == parent) {
res.first = newObj;
if (subname) {
res.second = subname;
}
return res;
}
return res;
#ifdef FC_USE_TNP_FIX
}
else if (newObj == obj) {
// This means the new object is already sub-object of this parent
// (consider a case of swapping the tool and base object of the Cut
// feature). We'll swap the old and new object.
return tryReplaceLink(owner, obj, parent, newObj, oldObj, subname);
#endif
}
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];
if (c == '.') {
continue;
}
sub[pos] = 0;
auto sobj = obj->getSubObject(sub.c_str());
sub[pos] = c;
if (!sobj) {
break;
}
if (sobj == oldObj) {
if (prev == parent) {
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;
#ifdef FC_USE_TNP_FIX
}
else if (sobj == newObj) {
return tryReplaceLink(owner, obj, parent, newObj, oldObj, subname);
#endif
}
else if (prev == parent) {
break;
}
prev = sobj;
prevPos = pos;
}
return res;
}
std::pair<DocumentObject*,std::vector<std::string> >
PropertyLinkBase::tryReplaceLinkSubs(const PropertyContainer *owner,
DocumentObject *obj, const DocumentObject *parent, DocumentObject *oldObj,
DocumentObject *newObj, const std::vector<std::string> &subs)
{
std::pair<DocumentObject*,std::vector<std::string> > res;
res.first = 0;
if (!obj)
return res;
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::PropertyLinkBase)
TYPESYSTEM_SOURCE(App::PropertyLinkChild , App::PropertyLink)
TYPESYSTEM_SOURCE(App::PropertyLinkGlobal , App::PropertyLink)
TYPESYSTEM_SOURCE(App::PropertyLinkHidden , App::PropertyLink)
//**************************************************************************
// Construction/Destruction
PropertyLink::PropertyLink() = default;
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 (_pcScope!=LinkScope::Hidden &&
_pcLink &&
getContainer() &&
getContainer()->isDerivedFrom(App::DocumentObject::getClassTypeId()))
{
App::DocumentObject* parent = static_cast<DocumentObject*>(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 (_pcLink)
_pcLink->_removeBackLink(parent);
}
}
#endif
_pcLink = nullptr;
}
void PropertyLink::setValue(App::DocumentObject * lValue)
{
auto parent = dynamic_cast<App::DocumentObject*>(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 (_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)) {
if (_pcLink)
_pcLink->_removeBackLink(parent);
if (lValue)
lValue->_addBackLink(parent);
}
}
#endif
_pcLink=lValue;
hasSetValue();
}
App::DocumentObject * PropertyLink::getValue() const
{
return _pcLink;
}
App::DocumentObject * PropertyLink::getValue(Base::Type t) const
{
return (_pcLink && _pcLink->getTypeId().isDerivedFrom(t)) ? _pcLink : nullptr;
}
PyObject *PropertyLink::getPyObject()
{
if (_pcLink)
return _pcLink->getPyObject();
else
Py_Return;
}
void PropertyLink::setPyObject(PyObject *value)
{
Base::PyTypeCheck(&value, &DocumentObjectPy::Type);
if (value) {
DocumentObjectPy *pcObject = static_cast<DocumentObjectPy*>(value);
setValue(pcObject->getDocumentObjectPtr());
}
else {
setValue(nullptr);
}
}
void PropertyLink::Save (Base::Writer &writer) const
{
writer.Stream() << writer.ind() << "<Link value=\"" << (_pcLink?_pcLink->getExportName():"") <<"\"/>" << std::endl;
}
void PropertyLink::Restore(Base::XMLReader &reader)
{
// read my element
reader.readElement("Link");
// get the value of my attribute
std::string name = reader.getName(reader.getAttribute("value"));
// Property not in a DocumentObject!
assert(getContainer()->isDerivedFrom<App::DocumentObject>() );
if (!name.empty()) {
DocumentObject* parent = static_cast<DocumentObject*>(getContainer());
App::Document* document = parent->getDocument();
DocumentObject* object = document ? document->getObject(name.c_str()) : nullptr;
if (!object) {
if (reader.isVerbose()) {
Base::Console().Warning("Lost link to '%s' while loading, maybe "
"an object was not loaded correctly\n",name.c_str());
}
}
else if (parent == object) {
if (reader.isVerbose()) {
Base::Console().Warning("Object '%s' links to itself, nullify it\n",name.c_str());
}
object = nullptr;
}
setValue(object);
}
else {
setValue(nullptr);
}
}
Property *PropertyLink::Copy() const
{
PropertyLink *p= new PropertyLink();
p->_pcLink = _pcLink;
return p;
}
void PropertyLink::Paste(const Property &from)
{
if (!from.isDerivedFrom(PropertyLink::getClassTypeId()))
throw Base::TypeError("Incompatible property to paste to");
setValue(static_cast<const PropertyLink&>(from)._pcLink);
}
void PropertyLink::getLinks(std::vector<App::DocumentObject *> &objs,
bool all, std::vector<std::string> *subs, bool newStyle) const
{
(void)newStyle;
(void)subs;
if((all||_pcScope!=LinkScope::Hidden) && _pcLink && _pcLink->isAttachedToDocument())
objs.push_back(_pcLink);
}
void PropertyLink::breakLink(App::DocumentObject *obj, bool clear) {
if(_pcLink == obj || (clear && getContainer()==obj))
setValue(nullptr);
}
bool PropertyLink::adjustLink(const std::set<App::DocumentObject*> &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 nullptr;
}
//**************************************************************************
// PropertyLinkList
//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
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
PropertyLinkList::PropertyLinkList() = default;
PropertyLinkList::~PropertyLinkList()
{
//in case this property gety dynamically removed
#ifndef USE_OLD_DAG
//maintain the back link in the DocumentObject class
if (_pcScope!=LinkScope::Hidden &&
!_lValueList.empty() &&
getContainer() &&
getContainer()->isDerivedFrom(App::DocumentObject::getClassTypeId()))
{
App::DocumentObject* parent = static_cast<DocumentObject*>(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);
}
}
}
#endif
}
void PropertyLinkList::setSize(int newSize)
{
for(int i=newSize;i<(int)_lValueList.size();++i) {
auto obj = _lValueList[i];
if (!obj || !obj->isAttachedToDocument())
continue;
_nameMap.erase(obj->getNameInDocument());
#ifndef USE_OLD_DAG
if (_pcScope!=LinkScope::Hidden)
obj->_removeBackLink(static_cast<DocumentObject*>(getContainer()));
#endif
}
_lValueList.resize(newSize);
}
void PropertyLinkList::setSize(int newSize, const_reference def) {
auto oldSize = getSize();
setSize(newSize);
for(auto i=oldSize;i<newSize;++i)
_lValueList[i] = def;
}
void PropertyLinkList::set1Value(int idx, DocumentObject* const &value) {
DocumentObject *obj = nullptr;
if(idx>=0 && idx<(int)_lValueList.size()) {
obj = _lValueList[idx];
if(obj == value)
return;
}
if(!value || !value->isAttachedToDocument())
throw Base::ValueError("invalid document object");
_nameMap.clear();
#ifndef USE_OLD_DAG
if (getContainer() && getContainer()->isDerivedFrom(App::DocumentObject::getClassTypeId())) {
App::DocumentObject* parent = static_cast<DocumentObject*>(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) && _pcScope!=LinkScope::Hidden) {
if(obj)
obj->_removeBackLink(static_cast<DocumentObject*>(getContainer()));
if(value)
value->_addBackLink(static_cast<DocumentObject*>(getContainer()));
}
}
#endif
inherited::set1Value(idx,value);
}
void PropertyLinkList::setValues(const std::vector<DocumentObject*>& lValue) {
if(lValue.size()==1 && !lValue[0]) {
// one null element means clear, as backward compatibility for old code
setValues(std::vector<DocumentObject*>());
return;
}
auto parent = Base::freecad_dynamic_cast<App::DocumentObject>(getContainer());
for(auto obj : lValue) {
if(!obj || !obj->isAttachedToDocument())
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();
#ifndef USE_OLD_DAG
//maintain the back link in the DocumentObject class
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) && _pcScope!=LinkScope::Hidden) {
for(auto *obj : _lValueList) {
if (obj)
obj->_removeBackLink(parent);
}
for(auto *obj : lValue) {
if (obj)
obj->_addBackLink(parent);
}
}
}
#endif
inherited::setValues(lValue);
}
PyObject *PropertyLinkList::getPyObject()
{
int count = getSize();
#if 0//FIXME: Should switch to tuple
Py::Tuple sequence(count);
#else
Py::List sequence(count);
#endif
for (int i = 0; i<count; i++) {
auto obj = _lValueList[i];
if(obj && obj->isAttachedToDocument())
sequence.setItem(i, Py::asObject(_lValueList[i]->getPyObject()));
else
sequence.setItem(i, Py::None());
}
return Py::new_reference_to(sequence);
}
DocumentObject *PropertyLinkList::getPyValue(PyObject *item) const
{
Base::PyTypeCheck(&item, &DocumentObjectPy::Type);
return item ? static_cast<DocumentObjectPy*>(item)->getDocumentObjectPtr() : nullptr;
}
void PropertyLinkList::Save(Base::Writer &writer) const
{
writer.Stream() << writer.ind() << "<LinkList count=\"" << getSize() << "\">" << endl;
writer.incInd();
for (int i = 0; i<getSize(); i++) {
DocumentObject* obj = _lValueList[i];
if (obj)
writer.Stream() << writer.ind() << "<Link value=\"" << obj->getExportName() << "\"/>" << endl;
else
writer.Stream() << writer.ind() << "<Link value=\"\"/>" << endl;
}
writer.decInd();
writer.Stream() << writer.ind() << "</LinkList>" << endl;
}
void PropertyLinkList::Restore(Base::XMLReader &reader)
{
// read my element
reader.readElement("LinkList");
// get the value of my attribute
int count = reader.getAttributeAsInteger("count");
App::PropertyContainer* container = getContainer();
if (!container)
throw Base::RuntimeError("Property is not part of a container");
if (!container->isDerivedFrom<App::DocumentObject>()) {
std::stringstream str;
str << "Container is not a document object ("
<< container->getTypeId().getName() << ")";
throw Base::TypeError(str.str());
}
std::vector<DocumentObject*> values;
values.reserve(count);
for (int i = 0; i < count; i++) {
reader.readElement("Link");
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.
// Property not in an object!
DocumentObject* father = static_cast<DocumentObject*>(getContainer());
App::Document* document = father->getDocument();
DocumentObject* child = document ? document->getObject(name.c_str()) : nullptr;
if (child)
values.push_back(child);
else if (reader.isVerbose())
FC_WARN("Lost link to " << (document?document->getName():"") << " " << name
<< " while loading, maybe an object was not loaded correctly");
}
reader.readEndElement("LinkList");
// assignment
setValues(values);
}
Property *PropertyLinkList::CopyOnLinkReplace(const App::DocumentObject *parent,
App::DocumentObject *oldObj, App::DocumentObject *newObj) const
{
std::vector<DocumentObject*> 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 insert 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 nullptr;
auto p= new PropertyLinkList();
p->_lValueList = std::move(links);
return p;
}
Property *PropertyLinkList::Copy() const
{
PropertyLinkList *p = new PropertyLinkList();
p->_lValueList = _lValueList;
return p;
}
void PropertyLinkList::Paste(const Property &from)
{
if(!from.isDerivedFrom(PropertyLinkList::getClassTypeId()))
throw Base::TypeError("Incompatible property to paste to");
setValues(static_cast<const PropertyLinkList&>(from)._lValueList);
}
unsigned int PropertyLinkList::getMemSize() const
{
return static_cast<unsigned int>(_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->isAttachedToDocument())
_nameMap[obj->getNameInDocument()] = i;
}
}
auto it = _nameMap.find(name);
if(it == _nameMap.end())
return nullptr;
if(pindex) *pindex = it->second;
return _lValueList[it->second];
}
void PropertyLinkList::getLinks(std::vector<App::DocumentObject *> &objs,
bool all, std::vector<std::string> *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->isAttachedToDocument())
objs.push_back(obj);
}
}
}
void PropertyLinkList::breakLink(App::DocumentObject *obj, bool clear) {
if(clear && getContainer()==obj) {
setValues({});
return;
}
std::vector<App::DocumentObject*> 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<App::DocumentObject*> &inList) {
(void)inList;
return false;
}
//**************************************************************************
// PropertyLinkSub
//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
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
PropertyLinkSub::PropertyLinkSub() = default;
PropertyLinkSub::~PropertyLinkSub()
{
//in case this property is dynamically removed
#ifndef USE_OLD_DAG
if (_pcLinkSub && getContainer() && getContainer()->isDerivedFrom(App::DocumentObject::getClassTypeId())) {
App::DocumentObject* parent = static_cast<DocumentObject*>(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) && _pcScope!=LinkScope::Hidden) {
if (_pcLinkSub)
_pcLinkSub->_removeBackLink(parent);
}
}
#endif
}
void PropertyLinkSub::setSyncSubObject(bool enable)
{
_Flags.set((std::size_t)LinkSyncSubObject, enable);
}
void PropertyLinkSub::setValue(App::DocumentObject * lValue,
const std::vector<std::string> &SubList, std::vector<ShadowSub> &&shadows)
{
setValue(lValue,std::vector<std::string>(SubList),std::move(shadows));
}
void PropertyLinkSub::setValue(App::DocumentObject * lValue,
std::vector<std::string> &&subs, std::vector<ShadowSub> &&shadows)
{
auto parent = Base::freecad_dynamic_cast<App::DocumentObject>(getContainer());
if (lValue) {
if (!lValue->isAttachedToDocument())
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 (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) && _pcScope != LinkScope::Hidden) {
if (_pcLinkSub)
_pcLinkSub->_removeBackLink(parent);
if (lValue)
lValue->_addBackLink(parent);
}
}
#endif
_pcLinkSub = lValue;
_cSubList = std::move(subs);
if (shadows.size() == _cSubList.size()) {
_ShadowSubList = std::move(shadows);
onContainerRestored(); // re-register element references
} else
updateElementReference(nullptr);
checkLabelReferences(_cSubList);
hasSetValue();
}
App::DocumentObject * PropertyLinkSub::getValue() const
{
return _pcLinkSub;
}
const std::vector<std::string>& PropertyLinkSub::getSubValues() const
{
return _cSubList;
}
static inline const std::string &getSubNameWithStyle(const std::string &subName,
const PropertyLinkBase::ShadowSub &shadow, bool newStyle, std::string &tmp)
{
if(!newStyle) {
if(!shadow.second.empty())
return shadow.second;
}else if(!shadow.first.empty()) {
#ifdef FC_USE_TNP_FIX
if (Data::hasMissingElement(shadow.second.c_str())) {
auto pos = shadow.first.rfind('.');
if (pos != std::string::npos) {
tmp = shadow.first.substr(0, pos+1);
tmp += shadow.second;
return tmp;
}
}
#else
(void) tmp;
#endif
return shadow.first;
}
return subName;
}
std::vector<std::string> PropertyLinkSub::getSubValues(bool newStyle) const {
assert(_cSubList.size() == _ShadowSubList.size());
std::vector<std::string> ret;
ret.reserve(_cSubList.size());
std::string tmp;
for(size_t i=0;i<_ShadowSubList.size();++i)
ret.push_back(getSubNameWithStyle(_cSubList[i],_ShadowSubList[i],newStyle,tmp));
return ret;
}
std::vector<std::string> PropertyLinkSub::getSubValuesStartsWith(const char* starter, bool newStyle) const
{
#ifdef FC_USE_TNP_FIX
assert(_cSubList.size() == _ShadowSubList.size());
std::vector<std::string> ret;
std::string tmp;
for(size_t i=0;i<_ShadowSubList.size();++i) {
const auto &sub = getSubNameWithStyle(_cSubList[i],_ShadowSubList[i],newStyle,tmp);
auto element = Data::findElementName(sub.c_str());
if(element && boost::starts_with(element,starter))
ret.emplace_back(element);
}
return ret;
#else
(void)newStyle;
std::vector<std::string> temp;
for(const auto & it : _cSubList) {
if(strncmp(starter, it.c_str(), strlen(starter)) == 0) {
temp.push_back(it);
}
}
return temp;
#endif
}
App::DocumentObject * PropertyLinkSub::getValue(Base::Type t) const
{
return (_pcLinkSub && _pcLinkSub->getTypeId().isDerivedFrom(t)) ? _pcLinkSub : nullptr;
}
PyObject *PropertyLinkSub::getPyObject()
{
Py::Tuple tup(2);
Py::List list(static_cast<int>(_cSubList.size()));
if (_pcLinkSub) {
tup[0] = Py::asObject(_pcLinkSub->getPyObject());
#ifdef FC_USE_TNP_FIX
int i = 0;
for (auto &sub : getSubValues(true))
list[i++] = Py::String(sub);
#else
for(unsigned int i = 0;i<_cSubList.size(); i++)
list[i] = Py::String(_cSubList[i]);
#endif
tup[1] = list;
return Py::new_reference_to(tup);
}
else {
return Py::new_reference_to(Py::None());
}
}
void PropertyLinkSub::setPyObject(PyObject *value)
{
if (PyObject_TypeCheck(value, &(DocumentObjectPy::Type))) {
DocumentObjectPy *pcObject = static_cast<DocumentObjectPy*>(value);
setValue(pcObject->getDocumentObjectPtr());
}
else if (PyTuple_Check(value) || PyList_Check(value)) {
Py::Sequence seq(value);
if(seq.size() == 0)
setValue(nullptr);
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 = static_cast<DocumentObjectPy*>(seq[0].ptr());
static const char *errMsg = "type of second element in tuple must be str or sequence of str";
PropertyString propString;
if (seq[1].isString()) {
std::vector<std::string> vals;
propString.setPyObject(seq[1].ptr());
vals.emplace_back(propString.getValue());
setValue(pcObj->getDocumentObjectPtr(),std::move(vals));
}
else if (seq[1].isSequence()) {
Py::Sequence list(seq[1]);
std::vector<std::string> vals(list.size());
unsigned int i=0;
for (Py::Sequence::iterator it = list.begin();it!=list.end();++it,++i) {
if(!(*it).isString())
throw Base::TypeError(errMsg);
propString.setPyObject((*it).ptr());
vals[i] = propString.getValue();
}
setValue(pcObj->getDocumentObjectPtr(),std::move(vals));
}
else {
throw Base::TypeError(errMsg);
}
}
else {
std::string error = std::string("type of first element in tuple must be 'DocumentObject', not ");
error += seq[0].ptr()->ob_type->tp_name;
throw Base::TypeError(error);
}
}
else if(Py_None == value) {
setValue(nullptr);
}
else {
std::string error = std::string("type must be 'DocumentObject', 'NoneType' or ('DocumentObject',['String',]) not ");
error += value->ob_type->tp_name;
throw Base::TypeError(error);
}
}
static bool updateLinkReference(App::PropertyLinkBase *prop,
App::DocumentObject *feature, bool reverse, bool notify,
App::DocumentObject *link, std::vector<std::string> &subs, std::vector<int> &mapped,
std::vector<PropertyLinkBase::ShadowSub> &shadows)
{
if(!feature) {
shadows.clear();
prop->unregisterElementReference();
}
shadows.resize(subs.size());
if(!link || !link->isAttachedToDocument())
return false;
auto owner = dynamic_cast<DocumentObject*>(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.empty())
subs[idx] = shadows[idx].first;
}
mapped.clear();
if(owner && feature)
owner->onUpdateElementReference(prop);
return true;
}
void PropertyLinkSub::afterRestore() {
_ShadowSubList.resize(_cSubList.size());
if(!testFlag(LinkRestoreLabel) ||!_pcLinkSub || !_pcLinkSub->isAttachedToDocument())
return;
setFlag(LinkRestoreLabel,false);
for(std::size_t i=0;i<_cSubList.size();++i)
restoreLabelReference(_pcLinkSub,_cSubList[i],&_ShadowSubList[i]);
}
void PropertyLinkSub::onContainerRestored() {
unregisterElementReference();
if(!_pcLinkSub || !_pcLinkSub->isAttachedToDocument())
return;
for(std::size_t i=0;i<_cSubList.size();++i)
_registerElementReference(_pcLinkSub,_cSubList[i],_ShadowSubList[i]);
}
void PropertyLinkSub::updateElementReference(DocumentObject *feature, bool reverse, bool notify) {
if(!updateLinkReference(this,feature,reverse,notify,_pcLinkSub,_cSubList,_mapped,_ShadowSubList))
return;
if(notify)
hasSetValue();
}
bool PropertyLinkSub::referenceChanged() const {
return !_mapped.empty();
}
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;
}
str << reader.getName(std::string(sub,count).c_str()) << tail;
}
str << sub;
return str.str();
}
const char *PropertyLinkBase::exportSubName(std::string &output,
const App::DocumentObject *obj, const char *sub, bool first_obj)
{
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;hash<dot && *hash!='#';++hash) {}
App::Document *doc = nullptr;
if(*hash == '#')
doc = GetApplication().getDocument(std::string(sub,hash-sub).c_str());
else {
hash = nullptr;
if(obj && obj->isAttachedToDocument())
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->isAttachedToDocument())
return res;
if(hash) {
if(!obj->isExporting())
str << doc->getName() << '#';
sub = hash+1;
}
}else if(!obj || !obj->isAttachedToDocument())
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->isAttachedToDocument()) {
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<std::string,std::string> &nameMap)
{
if(doc && obj && obj->isAttachedToDocument()) {
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<DocumentObject*>(obj);
}
std::string PropertyLinkBase::tryImportSubName(const App::DocumentObject *obj, const char *_subname,
const App::Document *doc, const std::map<std::string,std::string> &nameMap)
{
if(!doc || !obj || !obj->isAttachedToDocument())
return {};
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 label reference " << obj->getFullName() << '.' << subname);
return {};
}
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 {};
}
#define ATTR_SHADOWED "shadowed"
#define ATTR_SHADOW "shadow"
#define ATTR_MAPPED "mapped"
#ifdef FC_USE_TNP_FIX
#define IGNORE_SHADOW false
#else
// We do not have topo naming yet, ignore shadow sub for now
#define IGNORE_SHADOW true
#endif
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->isAttachedToDocument())
internal_name = _pcLinkSub->getExportName();
writer.Stream() << writer.ind() << "<LinkSub value=\""
<< internal_name <<"\" count=\"" << _cSubList.size();
writer.Stream() << "\">" << std::endl;
writer.incInd();
auto owner = dynamic_cast<DocumentObject*>(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' whenever possible.
const auto &sub = shadow.second.empty()?_cSubList[i]:shadow.second;
writer.Stream() << writer.ind() << "<Sub value=\"";
if(exporting) {
std::string exportName;
writer.Stream() << encodeAttribute(exportSubName(exportName,_pcLinkSub,sub.c_str()));
if(!shadow.second.empty() && shadow.first == _cSubList[i])
writer.Stream() << "\" " ATTR_MAPPED "=\"1";
} else {
writer.Stream() << encodeAttribute(sub);
if(!_cSubList[i].empty()) {
if(sub!=_cSubList[i]) {
// Stores the actual value that is shadowed. For new version FC,
// we will restore this shadowed value instead.
writer.Stream() << "\" " ATTR_SHADOWED "=\"" << encodeAttribute(_cSubList[i]);
}else if(!shadow.first.empty()){
// Here means the user set value is old style element name.
// We shall then store the shadow somewhere else.
writer.Stream() << "\" " ATTR_SHADOW "=\"" << encodeAttribute(shadow.first);
}
}
}
writer.Stream()<<"\"/>" << endl;
}
writer.decInd();
writer.Stream() << writer.ind() << "</LinkSub>" << 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()->isDerivedFrom<App::DocumentObject>() );
App::Document* document = static_cast<DocumentObject*>(getContainer())->getDocument();
DocumentObject *pcObject = nullptr;
if (!name.empty()) {
pcObject = document ? document->getObject(name.c_str()) : nullptr;
if (!pcObject) {
if (reader.isVerbose()) {
FC_WARN("Lost link to " << name
<< " while loading, maybe an object was not loaded correctly");
}
}
}
std::vector<int> mapped;
std::vector<std::string> values(count);
std::vector<ShadowSub> 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(nullptr);
}
}
template<class Func, class... Args >
std::vector<std::string> updateLinkSubs(const App::DocumentObject *obj,
const std::vector<std::string> &subs, Func *f, Args&&... args )
{
if(!obj || !obj->isAttachedToDocument())
return {};
std::vector<std::string> 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>(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.empty())
res.push_back(sub);
}
return res;
}
Property *PropertyLinkSub::CopyOnImportExternal(
const std::map<std::string,std::string> &nameMap) const
{
auto owner = dynamic_cast<const DocumentObject*>(getContainer());
if(!owner || !owner->getDocument())
return nullptr;
if(!_pcLinkSub || !_pcLinkSub->isAttachedToDocument())
return nullptr;
auto subs = updateLinkSubs(_pcLinkSub,_cSubList,
&tryImportSubName,owner->getDocument(),nameMap);
auto linked = tryImport(owner->getDocument(),_pcLinkSub,nameMap);
if(subs.empty() && linked==_pcLinkSub)
return nullptr;
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<const DocumentObject*>(getContainer());
if(!owner || !owner->getDocument())
return nullptr;
if(!_pcLinkSub || !_pcLinkSub->isAttachedToDocument())
return nullptr;
auto subs = updateLinkSubs(_pcLinkSub,_cSubList,&updateLabelReference,obj,ref,newLabel);
if(subs.empty())
return nullptr;
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 nullptr;
}
Property *PropertyLinkSub::Copy() const
{
PropertyLinkSub *p= new PropertyLinkSub();
p->_pcLinkSub = _pcLinkSub;
p->_cSubList = _cSubList;
#ifdef FC_USE_TNP_FIX
p->_ShadowSubList = _ShadowSubList;
#endif
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<const PropertyLinkSub&>(from);
#ifdef FC_USE_TNP_FIX
setValue(link._pcLinkSub, link._cSubList,
std::vector<ShadowSub>(link._ShadowSubList));
#else
setValue(link._pcLinkSub, link._cSubList);
#endif
}
void PropertyLinkSub::getLinks(std::vector<App::DocumentObject *> &objs,
bool all, std::vector<std::string> *subs, bool newStyle) const
{
if(all||_pcScope!=LinkScope::Hidden) {
if(_pcLinkSub && _pcLinkSub->isAttachedToDocument()) {
objs.push_back(_pcLinkSub);
if(subs)
*subs = getSubValues(newStyle);
}
}
}
void PropertyLinkSub::breakLink(App::DocumentObject *obj, bool clear) {
if(obj == _pcLinkSub || (clear && getContainer()==obj))
setValue(nullptr);
}
static App::DocumentObject *adjustLinkSubs(App::PropertyLinkBase *prop,
const std::set<App::DocumentObject*> &inList,
App::DocumentObject *link, std::vector<std::string> &subs,
std::map<App::DocumentObject *, std::vector<std::string> > *links=nullptr)
{
App::DocumentObject *newLink = nullptr;
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 nullptr;
}
return newLink;
}
bool PropertyLinkSub::adjustLink(const std::set<App::DocumentObject*> &inList) {
if (_pcScope==LinkScope::Hidden)
return false;
if(!_pcLinkSub || !_pcLinkSub->isAttachedToDocument() || !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::PropertyLinkBase)
TYPESYSTEM_SOURCE(App::PropertyLinkSubListChild, App::PropertyLinkSubList)
TYPESYSTEM_SOURCE(App::PropertyLinkSubListGlobal, App::PropertyLinkSubList)
TYPESYSTEM_SOURCE(App::PropertyLinkSubListHidden, App::PropertyLinkSubList)
//**************************************************************************
// Construction/Destruction
PropertyLinkSubList::PropertyLinkSubList() = default;
PropertyLinkSubList::~PropertyLinkSubList()
{
//in case this property is dynamically removed
#ifndef USE_OLD_DAG
//maintain backlinks
if (!_lValueList.empty() && getContainer() && getContainer()->isDerivedFrom(App::DocumentObject::getClassTypeId())) {
App::DocumentObject* parent = static_cast<DocumentObject*>(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) && _pcScope!=LinkScope::Hidden) {
for(auto *obj : _lValueList) {
if (obj)
obj->_removeBackLink(parent);
}
}
}
#endif
}
void PropertyLinkSubList::setSyncSubObject(bool enable)
{
_Flags.set((std::size_t)LinkSyncSubObject, enable);
}
void PropertyLinkSubList::verifyObject(App::DocumentObject* obj, App::DocumentObject* parent)
{
if (obj) {
if (!obj->isAttachedToDocument())
throw Base::ValueError("PropertyLinkSubList: invalid document object");
if (!testFlag(LinkAllowExternal) && parent && parent->getDocument() != obj->getDocument())
throw Base::ValueError("PropertyLinkSubList does not support external object");
}
}
void PropertyLinkSubList::setSize(int newSize)
{
_lValueList.resize(newSize);
_lSubList .resize(newSize);
_ShadowSubList.resize(newSize);
}
int PropertyLinkSubList::getSize() const
{
return static_cast<int>(_lValueList.size());
}
void PropertyLinkSubList::setValue(DocumentObject* lValue,const char* SubName)
{
auto parent = Base::freecad_dynamic_cast<App::DocumentObject>(getContainer());
verifyObject(lValue, parent);
#ifndef USE_OLD_DAG
//maintain backlinks
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) && _pcScope!=LinkScope::Hidden) {
for(auto *obj : _lValueList) {
if (obj)
obj->_removeBackLink(parent);
}
if (lValue)
lValue->_addBackLink(parent);
}
}
#endif
if (lValue) {
aboutToSetValue();
_lValueList.resize(1);
_lValueList[0]=lValue;
_lSubList.resize(1);
_lSubList[0]=SubName;
}
else {
aboutToSetValue();
_lValueList.clear();
_lSubList.clear();
}
updateElementReference(nullptr);
checkLabelReferences(_lSubList);
hasSetValue();
}
void PropertyLinkSubList::setValues(const std::vector<DocumentObject*>& lValue,const std::vector<const char*>& lSubNames)
{
auto parent = Base::freecad_dynamic_cast<App::DocumentObject>(getContainer());
for(auto obj : lValue) {
verifyObject(obj, parent);
}
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(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) && _pcScope!=LinkScope::Hidden) {
//_lValueList can contain items multiple times, but we trust the document
//object to ensure that this works
for(auto *obj : _lValueList) {
if (obj)
obj->_removeBackLink(parent);
}
//maintain backlinks. lValue can contain items multiple times, but we trust the document
//object to ensure that the backlink is only added once
for(auto *obj : lValue) {
if (obj)
obj->_addBackLink(parent);
}
}
}
#endif
aboutToSetValue();
_lValueList = lValue;
_lSubList.resize(lSubNames.size());
int i = 0;
for (std::vector<const char*>::const_iterator it = lSubNames.begin();it!=lSubNames.end();++it,++i) {
if (*it)
_lSubList[i] = *it;
}
updateElementReference(nullptr);
checkLabelReferences(_lSubList);
hasSetValue();
}
void PropertyLinkSubList::setValues(const std::vector<DocumentObject*>& lValue,
const std::vector<std::string>& lSubNames, std::vector<ShadowSub> &&ShadowSubList)
{
setValues(std::vector<DocumentObject*>(lValue),
std::vector<std::string>(lSubNames),std::move(ShadowSubList));
}
void PropertyLinkSubList::setValues(std::vector<DocumentObject*>&& lValue,
std::vector<std::string>&& lSubNames, std::vector<ShadowSub> &&ShadowSubList)
{
auto parent = Base::freecad_dynamic_cast<App::DocumentObject>(getContainer());
for(auto obj : lValue) {
verifyObject(obj, parent);
}
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(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) && _pcScope!=LinkScope::Hidden) {
//_lValueList can contain items multiple times, but we trust the document
//object to ensure that this works
for(auto *obj : _lValueList) {
if (obj)
obj->_removeBackLink(parent);
}
//maintain backlinks. lValue can contain items multiple times, but we trust the document
//object to ensure that the backlink is only added once
for(auto *obj : lValue) {
if (obj)
obj->_addBackLink(parent);
}
}
}
#endif
aboutToSetValue();
_lValueList = std::move(lValue);
_lSubList = std::move(lSubNames);
if(ShadowSubList.size()==_lSubList.size()) {
_ShadowSubList = std::move(ShadowSubList);
onContainerRestored(); // re-register element references
} else
updateElementReference(nullptr);
checkLabelReferences(_lSubList);
hasSetValue();
}
void PropertyLinkSubList::setValue(DocumentObject* lValue, const std::vector<std::string> &SubList)
{
auto parent = dynamic_cast<App::DocumentObject*>(getContainer());
verifyObject(lValue, parent);
#ifndef USE_OLD_DAG
//maintain backlinks.
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) && _pcScope!=LinkScope::Hidden) {
//_lValueList can contain items multiple times, but we trust the document
//object to ensure that this works
for(auto *obj : _lValueList) {
if (obj)
obj->_removeBackLink(parent);
}
//maintain backlinks. lValue can contain items multiple times, but we trust the document
//object to ensure that the backlink is only added once
if (lValue)
lValue->_addBackLink(parent);
}
}
#endif
aboutToSetValue();
std::size_t size = SubList.size();
this->_lValueList.clear();
this->_lSubList.clear();
if (size == 0) {
if (lValue) {
this->_lValueList.push_back(lValue);
this->_lSubList.emplace_back();
}
}
else {
this->_lSubList = SubList;
this->_lValueList.insert(this->_lValueList.begin(), size, lValue);
}
updateElementReference(nullptr);
checkLabelReferences(_lSubList);
hasSetValue();
}
void PropertyLinkSubList::addValue(App::DocumentObject *obj, const std::vector<std::string> &subs, bool reset)
{
auto parent = Base::freecad_dynamic_cast<App::DocumentObject>(getContainer());
verifyObject(obj, parent);
#ifndef USE_OLD_DAG
//maintain backlinks.
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) && _pcScope != LinkScope::Hidden) {
//_lValueList can contain items multiple times, but we trust the document
//object to ensure that this works
if (reset) {
for(auto* value : _lValueList) {
if (value && value == obj)
value->_removeBackLink(parent);
}
}
//maintain backlinks. lValue can contain items multiple times, but we trust the document
//object to ensure that the backlink is only added once
if (obj)
obj->_addBackLink(parent);
}
}
#endif
std::vector<DocumentObject*> valueList;
std::vector<std::string> subList;
if (reset) {
for (std::size_t i=0; i<_lValueList.size(); i++) {
if (_lValueList[i] != obj) {
valueList.push_back(_lValueList[i]);
subList.push_back(_lSubList[i]);
}
}
}
else {
valueList = _lValueList;
subList = _lSubList;
}
std::size_t size = subs.size();
if (size == 0) {
if (obj) {
valueList.push_back(obj);
subList.emplace_back();
}
}
else if (obj) {
subList.insert(subList.end(), subs.begin(), subs.end());
valueList.insert(valueList.end(), size, obj);
}
aboutToSetValue();
_lValueList = valueList;
_lSubList = subList;
updateElementReference(nullptr);
checkLabelReferences(_lSubList);
hasSetValue();
}
const string PropertyLinkSubList::getPyReprString() const
{
assert(this->_lValueList.size() == this->_lSubList.size());
if (this->_lValueList.empty())
return std::string("None");
std::stringstream strm;
strm << "[";
for (std::size_t i = 0; i < this->_lSubList.size(); i++) {
if (i>0)
strm << ",(";
else
strm << "(";
App::DocumentObject* obj = this->_lValueList[i];
if (obj) {
strm << "App.getDocument('" << obj->getDocument()->getName()
<< "').getObject('" << obj->getNameInDocument() << "')";
} else {
strm << "None";
}
strm << ",";
strm << "'" << this->_lSubList[i] << "'";
strm << ")";
}
strm << "]";
return strm.str();
}
DocumentObject *PropertyLinkSubList::getValue() const
{
App::DocumentObject* ret = nullptr;
//FIXME: cache this to avoid iterating each time, to improve speed
for (auto i : this->_lValueList) {
if (!ret)
ret = i;
if (ret != i)
return nullptr;
}
return ret;
}
int PropertyLinkSubList::removeValue(App::DocumentObject *lValue)
{
assert(this->_lValueList.size() == this->_lSubList.size());
std::size_t num = std::count(this->_lValueList.begin(), this->_lValueList.end(), lValue);
if (num == 0)
return 0;
std::vector<DocumentObject*> links;
std::vector<std::string> subs;
links.reserve(this->_lValueList.size() - num);
subs.reserve(this->_lSubList.size() - num);
for (std::size_t i=0; i<this->_lValueList.size(); ++i) {
if (this->_lValueList[i] != lValue) {
links.push_back(this->_lValueList[i]);
subs.push_back(this->_lSubList[i]);
}
}
setValues(links, subs);
return static_cast<int>(num);
}
void PropertyLinkSubList::setSubListValues(const std::vector<PropertyLinkSubList::SubSet>& values)
{
std::vector<DocumentObject*> links;
std::vector<std::string> subs;
#ifdef FC_USE_TNP_FIX
for (std::vector<PropertyLinkSubList::SubSet>::const_iterator it = values.begin(); it != values.end(); ++it) {
if (it->second.empty()) {
links.push_back(it->first);
subs.emplace_back();
continue;
}
for (std::vector<std::string>::const_iterator jt = it->second.begin(); jt != it->second.end(); ++jt) {
links.push_back(it->first);
subs.push_back(*jt);
}
}
#else
for (const auto & value : values) {
for (const auto& jt : value.second) {
links.push_back(value.first);
subs.push_back(jt);
}
}
#endif
setValues(links, subs);
}
std::vector<PropertyLinkSubList::SubSet> PropertyLinkSubList::getSubListValues(bool newStyle) const
{
std::vector<PropertyLinkSubList::SubSet> 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;
if(newStyle && !_ShadowSubList[i].first.empty())
sub = _ShadowSubList[i].first;
else if(!newStyle && !_ShadowSubList[i].second.empty())
sub = _ShadowSubList[i].second;
else
sub = _lSubList[i];
if (values.empty() || values.back().first != link){
//new object started, start a new subset.
values.emplace_back(link, std::vector<std::string>());
}
values.back().second.push_back(sub);
}
return values;
}
PyObject *PropertyLinkSubList::getPyObject()
{
std::vector<SubSet> subLists = getSubListValues();
std::size_t count = subLists.size();
#if 0//FIXME: Should switch to tuple
Py::Tuple sequence(count);
#else
Py::List sequence(count);
#endif
for (std::size_t i = 0; i<count; i++) {
Py::Tuple tup(2);
tup[0] = Py::asObject(subLists[i].first->getPyObject());
const std::vector<std::string>& sub = subLists[i].second;
Py::Tuple items(sub.size());
for (std::size_t j = 0; j < sub.size(); j++) {
items[j] = Py::String(sub[j]);
}
tup[1] = items;
sequence[i] = tup;
}
return Py::new_reference_to(sequence);
}
void PropertyLinkSubList::setPyObject(PyObject *value)
{
try { //try PropertyLinkSub syntax
PropertyLinkSub dummy;
dummy.setPyObject(value);
this->setValue(dummy.getValue(), dummy.getSubValues());
return;
}
catch (...) {}
try {
// try PropertyLinkList syntax
PropertyLinkList dummy;
dummy.setPyObject(value);
const auto &values = dummy.getValues();
std::vector<std::string> subs(values.size());
this->setValues(values,subs);
return;
}catch(...) {}
static const char *errMsg =
"Expects sequence of items of type DocObj, (DocObj,SubName), or (DocObj, (SubName,...))";
if (!PyTuple_Check(value) && !PyList_Check(value))
throw Base::TypeError(errMsg);
Py::Sequence list(value);
Py::Sequence::size_type size = list.size();
std::vector<DocumentObject*> values;
values.reserve(size);
std::vector<std::string> SubNames;
SubNames.reserve(size);
for (Py::Sequence::size_type i=0; i<size; i++) {
Py::Object item = list[i];
if ((item.isTuple() || item.isSequence()) && PySequence_Size(*item)==2) {
Py::Sequence seq(item);
if (PyObject_TypeCheck(seq[0].ptr(), &(DocumentObjectPy::Type))){
auto obj = static_cast<DocumentObjectPy*>(seq[0].ptr())->getDocumentObjectPtr();
PropertyString propString;
if (seq[1].isString()) {
values.push_back(obj);
propString.setPyObject(seq[1].ptr());
SubNames.emplace_back(propString.getValue());
} else if (seq[1].isSequence()) {
Py::Sequence list(seq[1]);
for (Py::Sequence::iterator it = list.begin(); it != list.end(); ++it) {
if(!(*it).isString())
throw Base::TypeError(errMsg);
values.push_back(obj);
propString.setPyObject((*it).ptr());
SubNames.emplace_back(propString.getValue());
}
} else
throw Base::TypeError(errMsg);
}
} else if (PyObject_TypeCheck(*item, &(DocumentObjectPy::Type))) {
DocumentObjectPy *pcObj;
pcObj = static_cast<DocumentObjectPy*>(*item);
values.push_back(pcObj->getDocumentObjectPtr());
SubNames.emplace_back();
} else
throw Base::TypeError(errMsg);
}
setValues(values,SubNames);
}
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<DocumentObject>(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<int> mapped;
mapped.reserve(_mapped.size());
for(int idx : _mapped) {
if(idx<(int)_lSubList.size()) {
if(!_ShadowSubList[idx].first.empty())
_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
{
assert(_lSubList.size() == _ShadowSubList.size());
int count = 0;
for(auto obj : _lValueList) {
if(obj && obj->isAttachedToDocument())
++count;
}
writer.Stream() << writer.ind() << "<LinkSubList count=\"" << count <<"\">" << endl;
writer.incInd();
auto owner = dynamic_cast<DocumentObject*>(getContainer());
bool exporting = owner && owner->isExporting();
for (int i = 0; i < getSize(); i++) {
auto obj = _lValueList[i];
if(!obj || !obj->isAttachedToDocument())
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' whenever possible.
const auto &sub = shadow.second.empty()?_lSubList[i]:shadow.second;
writer.Stream() << writer.ind() << "<Link obj=\"" << obj->getExportName() << "\" sub=\"";
if(exporting) {
std::string exportName;
writer.Stream() << encodeAttribute(exportSubName(exportName,obj,sub.c_str()));
if(!shadow.second.empty() && _lSubList[i]==shadow.first)
writer.Stream() << "\" " ATTR_MAPPED "=\"1";
} else {
writer.Stream() << encodeAttribute(sub);
if(!_lSubList[i].empty()) {
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 "=\"" << encodeAttribute(_lSubList[i]);
}else if(!shadow.first.empty()) {
// Here means the user set value is old style element name.
// We shall then store the shadow somewhere else.
writer.Stream() << "\" " ATTR_SHADOW "=\"" << encodeAttribute(shadow.first);
}
}
}
writer.Stream() << "\"/>" << endl;
}
writer.decInd();
writer.Stream() << writer.ind() << "</LinkSubList>" << endl ;
}
void PropertyLinkSubList::Restore(Base::XMLReader &reader)
{
// read my element
reader.readElement("LinkSubList");
// get the value of my attribute
int count = reader.getAttributeAsInteger("count");
std::vector<DocumentObject*> values;
values.reserve(count);
std::vector<std::string> SubNames;
SubNames.reserve(count);
std::vector<ShadowSub> shadows;
shadows.reserve(count);
DocumentObject* father = dynamic_cast<DocumentObject*>(getContainer());
App::Document* document = father ? father->getDocument() : nullptr;
std::vector<int> mapped;
bool restoreLabel=false;
for (int i = 0; i < count; i++) {
reader.readElement("Link");
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* child = document ? document->getObject(name.c_str()) : nullptr;
if (child) {
values.push_back(child);
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());
}
setFlag(LinkRestoreLabel,restoreLabel);
reader.readEndElement("LinkSubList");
// assignment
setValues(values,SubNames,std::move(shadows));
_mapped.swap(mapped);
}
bool PropertyLinkSubList::upgrade(Base::XMLReader &reader, const char *typeName)
{
Base::Type type = Base::Type::fromName(typeName);
if (type.isDerivedFrom(PropertyLink::getClassTypeId())) {
PropertyLink prop;
prop.setContainer(getContainer());
prop.Restore(reader);
setValue(prop.getValue());
return true;
}
else if (type.isDerivedFrom(PropertyLinkList::getClassTypeId())) {
PropertyLinkList prop;
prop.setContainer(getContainer());
prop.Restore(reader);
std::vector<std::string> subnames;
subnames.resize(prop.getSize());
setValues(prop.getValues(), subnames);
return true;
}
else if (type.isDerivedFrom(PropertyLinkSub::getClassTypeId())) {
PropertyLinkSub prop;
prop.setContainer(getContainer());
prop.Restore(reader);
setValue(prop.getValue(), prop.getSubValues());
return true;
}
return false;
}
Property *PropertyLinkSubList::CopyOnImportExternal(
const std::map<std::string,std::string> &nameMap) const
{
auto owner = dynamic_cast<const DocumentObject*>(getContainer());
if(!owner || !owner->getDocument() || _lValueList.size()!=_lSubList.size())
return nullptr;
std::vector<App::DocumentObject *> values;
std::vector<std::string> 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->isAttachedToDocument()) {
if(!values.empty()) {
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.empty()) {
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.empty()) {
values.push_back(linked);
subs.push_back(sub);
}
}
if(values.empty())
return nullptr;
std::unique_ptr<PropertyLinkSubList> 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<const DocumentObject*>(getContainer());
if(!owner || !owner->getDocument())
return nullptr;
std::vector<App::DocumentObject *> values;
std::vector<std::string> 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->isAttachedToDocument()) {
if(!values.empty()) {
values.push_back(value);
subs.push_back(sub);
}
continue;
}
auto new_sub = updateLabelReference(value,sub.c_str(),obj,ref,newLabel);
if(!new_sub.empty()) {
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.empty()) {
values.push_back(value);
subs.push_back(sub);
}
}
if(values.empty())
return nullptr;
std::unique_ptr<PropertyLinkSubList> 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<App::DocumentObject *> values;
std::vector<std::string> subs;
auto itSub = _lSubList.begin();
std::vector<size_t> positions;
for(auto itValue=_lValueList.begin();itValue!=_lValueList.end();++itValue,++itSub) {
auto value = *itValue;
const auto &sub = *itSub;
if(!value || !value->isAttachedToDocument()) {
if(!values.empty()) {
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.empty()) {
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 nullptr;
std::unique_ptr<PropertyLinkSubList> p(new PropertyLinkSubList);
p->_lValueList = std::move(values);
p->_lSubList = std::move(subs);
return p.release();
}
Property *PropertyLinkSubList::Copy() const
{
PropertyLinkSubList *p = new PropertyLinkSubList();
p->_lValueList = _lValueList;
p->_lSubList = _lSubList;
#ifdef FC_USE_TNP_FIX
p->_ShadowSubList = _ShadowSubList;
#endif
return p;
}
void PropertyLinkSubList::Paste(const Property &from)
{
if(!from.isDerivedFrom(PropertyLinkSubList::getClassTypeId()))
throw Base::TypeError("Incompatible property to paste to");
auto &link = static_cast<const PropertyLinkSubList&>(from);
#ifdef FC_USE_TNP_FIX
setValues(link._lValueList, link._lSubList,
std::vector<ShadowSub>(link._ShadowSubList));
#else
setValues(link._lValueList, link._lSubList);
#endif
}
unsigned int PropertyLinkSubList::getMemSize () const
{
unsigned int size = static_cast<unsigned int>(_lValueList.size() * sizeof(App::DocumentObject *));
for(int i = 0;i<getSize(); i++)
size += _lSubList[i].size();
return size;
}
std::vector<std::string> PropertyLinkSubList::getSubValues(bool newStyle) const {
assert(_lSubList.size() == _ShadowSubList.size());
std::vector<std::string> ret;
ret.reserve(_ShadowSubList.size());
std::string tmp;
for(size_t i=0;i<_ShadowSubList.size();++i)
ret.push_back(getSubNameWithStyle(_lSubList[i],_ShadowSubList[i],newStyle,tmp));
return ret;
}
void PropertyLinkSubList::getLinks(std::vector<App::DocumentObject *> &objs,
bool all, std::vector<std::string> *subs, bool newStyle) const
{
if(all||_pcScope!=LinkScope::Hidden) {
objs.reserve(objs.size()+_lValueList.size());
for(auto obj : _lValueList) {
if(obj && obj->isAttachedToDocument())
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<DocumentObject*> values;
std::vector<std::string> 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<App::DocumentObject*> &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->isAttachedToDocument() || !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 absolute path.
// Because of possible symbolic links, multiple entry may refer to the same
// file. We used to rely on QFileInfo::canonicalFilePath to resolve it, but
// has now been changed to simply use the absoluteFilePath(), and rely on user
// to be aware of possible duplicated file location. The reason being that
// some user (especially Linux user) use symlink to organize file tree.
using DocInfoMap = std::map<QString, DocInfoPtr>;
DocInfoMap _DocInfoMap;
class App::DocInfo :
public std::enable_shared_from_this<App::DocInfo>
{
public:
using Connection = boost::signals2::scoped_connection;
Connection connFinishRestoreDocument;
Connection connPendingReloadDocument;
Connection connDeleteDocument;
Connection connSaveDocument;
Connection connDeletedObject;
DocInfoMap::iterator myPos;
std::string myPath;
App::Document *pcDoc{nullptr};
std::set<PropertyXLink*> links;
static std::string getDocPath(
const char *filename, App::Document *pDoc, bool relative, QString *fullPath = nullptr)
{
bool absolute;
// The path could be an URI, in that case
// TODO: build a far much more resilient approach to test for an URI
QString path = QString::fromUtf8(filename);
if (path.startsWith(QLatin1String("https://"))) {
// We do have an URI
if (fullPath)
*fullPath = path;
return std::string(filename);
}
// make sure the filename is absolute path
path = QDir::cleanPath(path);
if((absolute = QFileInfo(path).isAbsolute())) {
if(fullPath)
*fullPath = path;
if(!relative)
return std::string(path.toUtf8().constData());
}
const char *docPath = pDoc->getFileName();
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->getFileName()) == fullpath) {
info->attach(doc);
break;
}
}
}
}
} else {
info = std::make_shared<DocInfo>();
auto ret = _DocInfoMap.insert(std::make_pair(path,info));
info->init(ret.first,objName,l);
}
if(info->pcDoc) {
// make sure to attach only external object
auto owner = Base::freecad_dynamic_cast<DocumentObject>(l->getContainer());
if(owner && owner->getDocument() == info->pcDoc)
return info;
}
info->links.insert(l);
return info;
}
static QString getFullPath(const char *p) {
QString path = QString::fromUtf8(p);
if (path.isEmpty())
return path;
if (path.startsWith(QLatin1String("https://")))
return path;
else {
return QFileInfo(path).absoluteFilePath();
}
}
QString getFullPath() const {
QString path = myPos->first;
if (path.startsWith(QLatin1String("https://")))
return path;
else {
return QFileInfo(myPos->first).absoluteFilePath();
}
}
const char *filePath() const {
return myPath.c_str();
}
void deinit() {
FC_LOG("deinit " << (pcDoc?pcDoc->getName():filePath()));
assert(links.empty());
connFinishRestoreDocument.disconnect();
connPendingReloadDocument.disconnect();
connDeleteDocument.disconnect();
connSaveDocument.disconnect();
connDeletedObject.disconnect();
auto me = shared_from_this();
_DocInfoMap.erase(myPos);
myPos = _DocInfoMap.end();
myPath.clear();
pcDoc = nullptr;
}
void init(DocInfoMap::iterator pos, const char *objName, PropertyXLink *l) {
myPos = pos;
myPath = myPos->first.toUtf8().constData();
App::Application &app = App::GetApplication();
//NOLINTBEGIN
connFinishRestoreDocument = app.signalFinishRestoreDocument.connect(
std::bind(&DocInfo::slotFinishRestoreDocument,this,sp::_1));
connPendingReloadDocument = app.signalPendingReloadDocument.connect(
std::bind(&DocInfo::slotFinishRestoreDocument,this,sp::_1));
connDeleteDocument = app.signalDeleteDocument.connect(
std::bind(&DocInfo::slotDeleteDocument,this,sp::_1));
connSaveDocument = app.signalSaveDocument.connect(
std::bind(&DocInfo::slotSaveDocument,this,sp::_1));
//NOLINTEND
QString fullpath(getFullPath());
if(fullpath.isEmpty())
FC_ERR("document not found " << filePath());
else{
for(App::Document *doc : App::GetApplication().getDocuments()) {
if(getFullPath(doc->getFileName()) == fullpath) {
if(doc->testStatus(App::Document::PartialDoc) && !doc->getObject(objName))
break;
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->getFileName());
std::map<App::PropertyLinkBase*,std::vector<App::PropertyXLink*> > 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)
link->restoreLink(obj);
else if (doc->testStatus(App::Document::PartialDoc)) {
App::GetApplication().addPendingDocument(
doc->FileName.getValue(),
link->objectName.c_str(),
false);
FC_WARN("reloading partial document '" << doc->FileName.getValue()
<< "' due to object " << link->objectName);
} else
FC_WARN("object '" << link->objectName << "' not found in document '"
<< doc->getName() << "'");
}
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)
link->restoreLink(obj);
else if (doc->testStatus(App::Document::PartialDoc)) {
App::GetApplication().addPendingDocument(
doc->FileName.getValue(),
link->objectName.c_str(),
false);
FC_WARN("reloading partial document '" << doc->FileName.getValue()
<< "' due to object " << link->objectName);
} else
FC_WARN("object '" << link->objectName << "' not found in document '"
<< doc->getName() << "'");
}
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();
}
}
static void restoreDocument(const App::Document &doc) {
auto it = _DocInfoMap.find(getFullPath(doc.FileName.getValue()));
if(it==_DocInfoMap.end())
return;
it->second->slotFinishRestoreDocument(doc);
}
void slotFinishRestoreDocument(const App::Document &doc) {
if(pcDoc)
return;
QString fullpath(getFullPath());
if(!fullpath.isEmpty() && getFullPath(doc.getFileName())==fullpath)
attach(const_cast<App::Document*>(&doc));
}
void slotSaveDocument(const App::Document &doc) {
if(!pcDoc) {
slotFinishRestoreDocument(doc);
return;
}
if(&doc!=pcDoc)
return;
QFileInfo info(myPos->first);
QString path(info.absoluteFilePath());
const char *filename = doc.getFileName();
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(docPath,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<PropertyXLink *> tmp;
tmp.swap(links);
for(auto link : tmp) {
auto owner = static_cast<DocumentObject*>(link->getContainer());
// adjust file path for each PropertyXLink
DocInfo::get(filename,owner->getDocument(),link,link->objectName.c_str());
}
}
// time stamp changed, touch the linking document.
std::set<Document*> docs;
for(auto link : links) {
auto linkdoc = static_cast<DocumentObject*>(link->getContainer())->getDocument();
auto ret = docs.insert(linkdoc);
if(ret.second) {
// This will signal the Gui::Document to call setModified();
FC_LOG("touch document " << linkdoc->getName()
<< " on time stamp change of " << link->getFullName());
linkdoc->Comment.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<DocumentObject*>(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<App::PropertyLinkBase*,std::vector<App::PropertyXLink*> > parentLinks;
for(auto link : links) {
link->setFlag(PropertyLinkBase::LinkDetached);
if(link->parentProp)
parentLinks[link->parentProp].push_back(link);
else
parentLinks[nullptr].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 = nullptr;
}
bool hasXLink(const App::Document *doc) const{
for(auto link : links) {
auto obj = dynamic_cast<DocumentObject*>(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<PropertyLinkBase*> 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<App::DocumentObject*> &objs, bool clear)
{
std::vector<Property*> props;
for(auto obj : objs) {
props.clear();
obj->getPropertyList(props);
for(auto prop : props) {
auto linkProp = dynamic_cast<PropertyLinkBase*>(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);
setSyncSubObject(true);
if(parent)
setContainer(parent->getContainer());
}
PropertyXLink::~PropertyXLink() {
unlink();
}
void PropertyXLink::setSyncSubObject(bool enable)
{
_Flags.set((std::size_t)LinkSyncSubObject, enable);
}
void PropertyXLink::unlink() {
if(docInfo) {
docInfo->remove(this);
docInfo.reset();
}
objectName.clear();
resetLink();
}
void PropertyXLink::detach() {
if(docInfo && _pcLink) {
aboutToSetValue();
resetLink();
updateElementReference(nullptr);
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<std::string> subs;
if(subname && subname[0])
subs.emplace_back(subname);
aboutToSetValue();
setSubValues(std::move(subs));
hasSetValue();
}
void PropertyXLink::setSubValues(std::vector<std::string> &&subs,
std::vector<ShadowSub> &&shadows)
{
_SubList = std::move(subs);
_ShadowSubList.clear();
if(shadows.size() == _SubList.size()) {
_ShadowSubList = std::move(shadows);
#ifdef FC_USE_TNP_FIX
onContainerRestored(); // re-register element references
#endif
} else
updateElementReference(nullptr);
checkLabelReferences(_SubList);
}
void PropertyXLink::setValue(App::DocumentObject * lValue) {
setValue(lValue,nullptr);
}
void PropertyXLink::setValue(App::DocumentObject * lValue, const char *subname)
{
std::vector<std::string> 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<DocumentObject*>(getContainer());
if(!owner || !owner->isAttachedToDocument())
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(nullptr);
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<std::string> &&subs, std::vector<ShadowSub> &&shadows)
{
if(_pcLink==lValue && _SubList==subs)
return;
if(lValue && (!lValue->isAttachedToDocument() || !lValue->getDocument())) {
throw Base::ValueError("Invalid object");
return;
}
auto owner = dynamic_cast<DocumentObject*>(getContainer());
if(!owner || !owner->isAttachedToDocument())
throw Base::RuntimeError("invalid container");
if(lValue == owner)
throw Base::ValueError("self linking");
aboutToSetValue();
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()->getFileName();
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);
#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;
}
if(!docInfo)
filePath.clear();
_pcLink=lValue;
if(docInfo && docInfo->pcDoc)
stamp=docInfo->pcDoc->LastModifiedDate.getValue();
objectName = name;
setSubValues(std::move(subs),std::move(shadows));
hasSetValue();
}
void PropertyXLink::setValue(std::string &&filename, std::string &&name,
std::vector<std::string> &&subs, std::vector<ShadowSub> &&shadows)
{
if(name.empty()) {
setValue(nullptr,std::move(subs),std::move(shadows));
return;
}
auto owner = dynamic_cast<DocumentObject*>(getContainer());
if(!owner || !owner->isAttachedToDocument())
throw Base::RuntimeError("invalid container");
DocumentObject *pObject=nullptr;
DocInfoPtr info;
if(!filename.empty()) {
owner->getDocument()->signalLinkXsetValue(filename);
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 = nullptr;
if(docInfo!=info) {
unlink();
docInfo = info;
}
if(!docInfo)
filePath.clear();
if(docInfo && docInfo->pcDoc)
stamp=docInfo->pcDoc->LastModifiedDate.getValue();
objectName = std::move(name);
setSubValues(std::move(subs),std::move(shadows));
hasSetValue();
}
void PropertyXLink::setValue(App::DocumentObject *link,
const std::vector<std::string> &subs, std::vector<ShadowSub> &&shadows)
{
setValue(link,std::vector<std::string>(subs),std::move(shadows));
}
App::Document *PropertyXLink::getDocument() const {
return docInfo?docInfo->pcDoc:nullptr;
}
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.empty()) {
// this condition means linked object not found
if(msg) {
std::ostringstream ss;
ss << "Link not restored" << std::endl;
ss << "Object: " << objectName;
if(!filePath.empty())
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.empty())
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->isAttachedToDocument())
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->isAttachedToDocument())
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<const DocumentObject *>(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() << "<XLink name=\"" << _pcLink->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 absolute file path for easy transition
// into document at different directory
if(docInfo)
_path = docInfo->filePath();
else {
auto pDoc = owner->getDocument();
const char *docPath = pDoc->getFileName();
if(docPath && docPath[0]) {
if(!filePath.empty())
_path = DocInfo::getDocPath(filePath.c_str(),pDoc,false);
else
_path = docPath;
}else
FC_WARN("PropertyXLink export without saving the document");
}
if(!_path.empty())
path = _path.c_str();
}
writer.Stream() << writer.ind()
<< "<XLink file=\"" << encodeAttribute(path)
<< "\" stamp=\"" << (docInfo&&docInfo->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=\"" <<
encodeAttribute(exportSubName(exportName,_pcLink,sub.c_str()));
if(!shadowSub.second.empty() && shadowSub.first==subName)
writer.Stream() << "\" " ATTR_MAPPED "=\"1";
}else{
writer.Stream() << "\" sub=\"" << encodeAttribute(sub);
if(!sub.empty()) {
if(sub!=subName)
writer.Stream() << "\" " ATTR_SHADOWED "=\"" << encodeAttribute(subName);
else if(!shadowSub.first.empty())
writer.Stream() << "\" " ATTR_SHADOW "=\"" << encodeAttribute(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' whenever possible.
const auto &sub = shadow.second.empty()?_SubList[i]:shadow.second;
writer.Stream() << writer.ind() << "<Sub value=\"";
if(exporting) {
std::string exportName;
writer.Stream() << encodeAttribute(exportSubName(exportName,_pcLink,sub.c_str()));
if(!shadow.second.empty() && shadow.first == _SubList[i])
writer.Stream() << "\" " ATTR_MAPPED "=\"1";
} else {
writer.Stream() << encodeAttribute(sub);
if(!_SubList[i].empty()) {
if(sub!=_SubList[i])
writer.Stream() << "\" " ATTR_SHADOWED "=\"" << encodeAttribute(_SubList[i]);
else if(!shadow.first.empty())
writer.Stream() << "\" " ATTR_SHADOW "=\"" << encodeAttribute(shadow.first);
}
}
writer.Stream()<<"\"/>" << endl;
}
writer.decInd();
writer.Stream() << writer.ind() << "</XLink>" << endl ;
}
}
void PropertyXLink::Restore(Base::XMLReader &reader)
{
// read my element
reader.readElement("XLink");
std::string stampAttr,file;
if(reader.hasAttribute("stamp"))
stampAttr = 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()->isDerivedFrom<App::DocumentObject>());
DocumentObject *object = nullptr;
if(!name.empty() && file.empty()) {
DocumentObject* parent = static_cast<DocumentObject*>(getContainer());
Document *document = parent->getDocument();
object = document ? document->getObject(name.c_str()) : nullptr;
if(!object) {
if(reader.isVerbose()) {
FC_WARN("Lost link to '" << name << "' while loading, maybe "
"an object was not loaded correctly");
}
}
}
std::vector<std::string> subs;
std::vector<ShadowSub> shadows;
std::vector<int> 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(nullptr);
return;
}
if(!file.empty() || (!object && !name.empty())) {
this->stamp = stampAttr;
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<std::string,std::string> &nameMap) const
{
auto owner = Base::freecad_dynamic_cast<const DocumentObject>(getContainer());
if(!owner || !owner->getDocument() || !_pcLink || !_pcLink->isAttachedToDocument())
return nullptr;
auto subs = updateLinkSubs(_pcLink,_SubList,
&tryImportSubName,owner->getDocument(),nameMap);
auto linked = tryImport(owner->getDocument(),_pcLink,nameMap);
if(subs.empty() && linked==_pcLink)
return nullptr;
std::unique_ptr<PropertyXLink> p(new PropertyXLink);
copyTo(*p,linked,&subs);
return p.release();
}
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 nullptr;
std::unique_ptr<PropertyXLink> p(new PropertyXLink);
copyTo(*p,res.first,&res.second);
return p.release();
}
Property *PropertyXLink::CopyOnLabelChange(App::DocumentObject *obj,
const std::string &ref, const char *newLabel) const
{
auto owner = dynamic_cast<const DocumentObject*>(getContainer());
if(!owner || !owner->getDocument() || !_pcLink || !_pcLink->isAttachedToDocument())
return nullptr;
auto subs = updateLinkSubs(_pcLink,_SubList,&updateLabelReference,obj,ref,newLabel);
if(subs.empty())
return nullptr;
std::unique_ptr<PropertyXLink> p(new PropertyXLink);
copyTo(*p,_pcLink,&subs);
return p.release();
}
void PropertyXLink::copyTo(PropertyXLink &other,
DocumentObject *linked, std::vector<std::string> *subs) const
{
if(!linked)
linked = _pcLink;
if(linked && linked->isAttachedToDocument()) {
other.docName = linked->getDocument()->getName();
other.objectName = linked->getNameInDocument();
other.docInfo.reset();
other.filePath.clear();
}else{
other.objectName = objectName;
other.docName.clear();
other.docInfo = docInfo;
other.filePath = filePath;
}
if(subs)
other._SubList = std::move(*subs);
else {
other._SubList = _SubList;
#ifdef FC_USE_TNP_FIX
other._ShadowSubList = _ShadowSubList;
#endif
}
other._Flags = _Flags;
}
Property *PropertyXLink::Copy() const
{
std::unique_ptr<PropertyXLink> p(new PropertyXLink);
copyTo(*p);
return p.release();
}
void PropertyXLink::Paste(const Property &from)
{
if(!from.isDerivedFrom(PropertyXLink::getClassTypeId()))
throw Base::TypeError("Incompatible property to paste to");
const auto &other = static_cast<const PropertyXLink&>(from);
if(!other.docName.empty()) {
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;
}
#ifdef FC_USE_TNP_FIX
setValue(obj,std::vector<std::string>(other._SubList),
std::vector<ShadowSub>(other._ShadowSubList));
} else
setValue(std::string(other.filePath),std::string(other.objectName),
std::vector<std::string>(other._SubList),
std::vector<ShadowSub>(other._ShadowSubList));
#else
setValue(obj,std::vector<std::string>(other._SubList));
} else
setValue(std::string(other.filePath),std::string(other.objectName),
std::vector<std::string>(other._SubList));
#endif
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<App::DocumentObject*> &objs, std::vector<App::Document*> *unsaved)
{
std::set<App::Document*> docs;
bool ret = false;
for(auto o : objs) {
if(o && o->isAttachedToDocument() && 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;
}
void PropertyXLink::restoreDocument(const App::Document &doc) {
DocInfo::restoreDocument(doc);
}
std::map<App::Document*,std::set<App::Document*> >
PropertyXLink::getDocumentOutList(App::Document *doc) {
std::map<App::Document*,std::set<App::Document*> > ret;
for(auto &v : _DocInfoMap) {
for(auto link : v.second->links) {
if(!v.second->pcDoc
|| link->getScope() == LinkScope::Hidden
|| link->testStatus(Property::PropTransient)
|| link->testStatus(Property::Transient)
|| link->testStatus(Property::PropNoPersist))
continue;
auto obj = dynamic_cast<App::DocumentObject*>(link->getContainer());
if(!obj || !obj->isAttachedToDocument() || !obj->getDocument())
continue;
if(doc && obj->getDocument()!=doc)
continue;
ret[obj->getDocument()].insert(v.second->pcDoc);
}
}
return ret;
}
std::map<App::Document*,std::set<App::Document*> >
PropertyXLink::getDocumentInList(App::Document *doc) {
std::map<App::Document*,std::set<App::Document*> > 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) {
if(link->getScope() == LinkScope::Hidden
|| link->testStatus(Property::PropTransient)
|| link->testStatus(Property::Transient)
|| link->testStatus(Property::PropNoPersist))
continue;
auto obj = dynamic_cast<App::DocumentObject*>(link->getContainer());
if(obj && obj->isAttachedToDocument() && obj->getDocument())
docs.insert(obj->getDocument());
}
}
return ret;
}
PyObject *PropertyXLink::getPyObject()
{
if(!_pcLink)
Py_Return;
const auto &subs = getSubValues(false);
if(subs.empty())
return _pcLink->getPyObject();
Py::Tuple ret(2);
ret.setItem(0,Py::Object(_pcLink->getPyObject(),true));
PropertyString propString;
if (subs.size() == 1) {
propString.setValue(subs.front());
ret.setItem(1,Py::asObject(propString.getPyObject()));
} else {
Py::List list(subs.size());
int i = 0;
for (auto &sub : subs) {
propString.setValue(sub);
list[i++] = Py::asObject(propString.getPyObject());
}
ret.setItem(1, list);
}
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::vector<std::string> subs;
Py::Object pyObj(seq[0].ptr());
Py::Object pySub(seq[1].ptr());
if(pyObj.isNone()) {
setValue(nullptr);
return;
} else if(!PyObject_TypeCheck(pyObj.ptr(), &DocumentObjectPy::Type))
throw Base::TypeError("Expect the first element to be of 'DocumentObject'");
PropertyString propString;
if(pySub.isString()) {
propString.setPyObject(pySub.ptr());
subs.push_back(propString.getStrValue());
} else if (pySub.isSequence()) {
Py::Sequence seq(pySub);
subs.reserve(seq.size());
for(Py_ssize_t i=0;i<seq.size();++i) {
Py::Object sub(seq[i]);
if(!sub.isString())
throw Base::TypeError("Expect only string inside second argument");
propString.setPyObject(sub.ptr());
subs.push_back(propString.getStrValue());
}
}else
throw Base::TypeError("Expect the second element to be a string or sequence of string");
setValue(static_cast<DocumentObjectPy*>(pyObj.ptr())->getDocumentObjectPtr(), std::move(subs));
} else if(PyObject_TypeCheck(value, &(DocumentObjectPy::Type))) {
setValue(static_cast<DocumentObjectPy*>(value)->getDocumentObjectPtr());
} else if (Py_None == value) {
setValue(nullptr);
} else {
throw Base::TypeError("type must be 'DocumentObject', 'None', or '(DocumentObject, SubName)' or "
"'DocumentObject, [SubName..])");
}
}
const char *PropertyXLink::getSubName(bool newStyle) const {
if(_SubList.empty() || _ShadowSubList.empty())
return "";
return getSubNameWithStyle(_SubList[0],_ShadowSubList[0],newStyle,tmpShadow).c_str();
}
void PropertyXLink::getLinks(std::vector<App::DocumentObject *> &objs,
bool all, std::vector<std::string> *subs, bool newStyle) const
{
if((all||_pcScope!=LinkScope::Hidden) && _pcLink && _pcLink->isAttachedToDocument()) {
objs.push_back(_pcLink);
if(subs && _SubList.size()==_ShadowSubList.size())
*subs = getSubValues(newStyle);
}
}
bool PropertyXLink::adjustLink(const std::set<App::DocumentObject*> &inList) {
if (_pcScope==LinkScope::Hidden)
return false;
if(!_pcLink || !_pcLink->isAttachedToDocument() || !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<std::string> PropertyXLink::getSubValues(bool newStyle) const {
assert(_SubList.size() == _ShadowSubList.size());
std::vector<std::string> ret;
ret.reserve(_SubList.size());
std::string tmp;
for(size_t i=0;i<_ShadowSubList.size();++i)
ret.push_back(getSubNameWithStyle(_SubList[i],_ShadowSubList[i],newStyle,tmp));
return ret;
}
std::vector<std::string> PropertyXLink::getSubValuesStartsWith(const char* starter, bool newStyle) const
{
(void)newStyle;
std::vector<std::string> temp;
for(const auto & it : _SubList) {
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<const DocumentObject*>(getContainer());
if(!owner)
return;
if(!App::GetApplication().isRestoring() &&
!owner->getDocument()->isPerformingTransaction() &&
!_pcLink && docInfo && !filePath.empty() && !objectName.empty() &&
(!docInfo->pcDoc || docInfo->pcDoc->testStatus(Document::PartialDoc)))
{
auto path = docInfo->getDocPath(filePath.c_str(),owner->getDocument(),false);
if(!path.empty())
App::GetApplication().openDocument(path.c_str());
}
}
//**************************************************************************
// PropertyXLinkSub
//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
TYPESYSTEM_SOURCE(App::PropertyXLinkSub , App::PropertyXLink)
TYPESYSTEM_SOURCE(App::PropertyXLinkSubHidden, App::PropertyXLinkSub)
PropertyXLinkSub::PropertyXLinkSub(bool allowPartial, PropertyLinkBase *parent)
:PropertyXLink(allowPartial,parent)
{
}
PropertyXLinkSub::~PropertyXLinkSub() = default;
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()
{
if(!_pcLink)
Py_Return;
Py::Tuple ret(2);
ret.setItem(0,Py::Object(_pcLink->getPyObject(),true));
const auto &subs = getSubValues(false);
Py::List list(subs.size());
int i = 0;
PropertyString propString;
for (auto &sub : subs) {
propString.setValue(sub);
list[i++] = Py::asObject(propString.getPyObject());
}
ret.setItem(1, list);
return Py::new_reference_to(ret);
}
//**************************************************************************
// PropertyXLinkSubList
//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
TYPESYSTEM_SOURCE(App::PropertyXLinkSubList , App::PropertyLinkBase)
//**************************************************************************
// Construction/Destruction
PropertyXLinkSubList::PropertyXLinkSubList()
{
_pcScope = LinkScope::Global;
setSyncSubObject(true);
}
PropertyXLinkSubList::~PropertyXLinkSubList() = default;
void PropertyXLinkSubList::setSyncSubObject(bool enable)
{
_Flags.set((std::size_t)LinkSyncSubObject, enable);
}
int PropertyXLinkSubList::getSize() const
{
return static_cast<int>(_Links.size());
}
void PropertyXLinkSubList::setValue(DocumentObject* lValue,const char* SubName)
{
std::map<DocumentObject*,std::vector<std::string> > values;
if(lValue) {
auto &subs = values[lValue];
if(SubName)
subs.emplace_back(SubName);
}
setValues(std::move(values));
}
void PropertyXLinkSubList::setValues(
const std::vector<DocumentObject*>& lValue,
const std::vector<const char*>& 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<DocumentObject*,std::vector<std::string> > 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<DocumentObject*>& lValue,
const std::vector<std::string>& lSubNames)
{
CHECK_SUB_SIZE(lValue,lSubNames);
std::map<DocumentObject*,std::vector<std::string> > values;
int i=0;
for(auto &obj : lValue)
values[obj].push_back(lSubNames[i++]);
setValues(std::move(values));
}
void PropertyXLinkSubList::setSubListValues(const std::vector<PropertyLinkSubList::SubSet> &svalues) {
std::map<DocumentObject*,std::vector<std::string> > 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<App::DocumentObject*,std::vector<std::string> > &values)
{
setValues(std::map<App::DocumentObject*,std::vector<std::string> >(values));
}
void PropertyXLinkSubList::setValues(
std::map<App::DocumentObject*,std::vector<std::string> > &&values)
{
for(auto &v : values) {
if(!v.first || !v.first->isAttachedToDocument())
FC_THROWM(Base::ValueError,"invalid document object");
}
atomic_change guard(*this);
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));
}
guard.tryInvoke();
}
void PropertyXLinkSubList::addValue(App::DocumentObject *obj,
const std::vector<std::string> &subs, bool reset)
{
addValue(obj,std::vector<std::string>(subs),reset);
}
void PropertyXLinkSubList::addValue(App::DocumentObject *obj,
std::vector<std::string> &&subs, bool reset) {
if(!obj || !obj->isAttachedToDocument())
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;
}
}
atomic_change guard(*this);
_Links.emplace_back(testFlag(LinkAllowPartial),this);
_Links.back().setValue(obj,std::move(subs));
guard.tryInvoke();
}
void PropertyXLinkSubList::setValue(DocumentObject *lValue, const std::vector<std::string> &SubList)
{
std::map<DocumentObject *, std::vector<std::string> > values;
if(lValue)
values[lValue] = SubList;
setValues(std::move(values));
}
void PropertyXLinkSubList::setValues(const std::vector<DocumentObject*> &values) {
atomic_change guard(*this);
_Links.clear();
for(auto obj : values) {
_Links.emplace_back(testFlag(LinkAllowPartial),this);
_Links.back().setValue(obj);
}
guard.tryInvoke();
}
void PropertyXLinkSubList::set1Value(int idx,
DocumentObject *value,
const std::vector<std::string> &SubList)
{
if(idx < -1 || idx > getSize())
throw Base::RuntimeError("index out of bound");
if(idx < 0 || idx+1 == getSize()) {
if(SubList.empty()) {
addValue(value,SubList);
return;
}
atomic_change guard(*this);
_Links.emplace_back(testFlag(LinkAllowPartial),this);
_Links.back().setValue(value);
guard.tryInvoke();
return;
}
auto it = _Links.begin();
for(;idx;--idx)
++it;
it->setValue(value,SubList);
}
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->isAttachedToDocument())
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.empty())
return _Links.begin()->getValue();
return nullptr;
}
int PropertyXLinkSubList::removeValue(App::DocumentObject *lValue)
{
atomic_change guard(*this,false);
int ret = 0;
for(auto it=_Links.begin();it!=_Links.end();) {
if(it->getValue() != lValue)
++it;
else {
guard.aboutToChange();
it = _Links.erase(it);
++ret;
}
}
guard.tryInvoke();
return ret;
}
PyObject *PropertyXLinkSubList::getPyObject()
{
Py::List list;
for(auto &link : _Links) {
auto obj = link.getValue();
if(!obj || !obj->isAttachedToDocument())
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<DocumentObject*, std::vector<std::string> > values;
try {
for(Py_ssize_t i=0;i<seq.size();++i) {
PropertyLinkSub link;
link.setAllowExternal(true);
link.setPyObject(seq[i].ptr());
const auto &subs = link.getSubValues();
auto &s = values[link.getValue()];
s.reserve(s.size()+subs.size());
s.insert(s.end(),subs.begin(),subs.end());
}
}
catch(Base::Exception&){
throw Base::TypeError("Invalid type inside sequence. Must be type of (DocumentObject, (subname...))");
}
setValues(std::move(values));
}
void PropertyXLinkSubList::afterRestore() {
for(auto &l : _Links)
l.afterRestore();
}
void PropertyXLinkSubList::onContainerRestored() {
for(auto &l : _Links)
l.onContainerRestored();
}
void PropertyXLinkSubList::updateElementReference(DocumentObject *feature, bool reverse,bool notify) {
for(auto &l : _Links)
l.updateElementReference(feature,reverse,notify);
}
bool PropertyXLinkSubList::referenceChanged() const{
for(auto &l : _Links) {
if(l.referenceChanged())
return true;
}
return false;
}
void PropertyXLinkSubList::Save (Base::Writer &writer) const
{
writer.Stream() << writer.ind() << "<XLinkSubList count=\"" << _Links.size();
if(testFlag(LinkAllowPartial))
writer.Stream() << "\" partial=\"1";
writer.Stream() <<"\">" << endl;
writer.incInd();
for(auto &l : _Links)
l.Save(writer);
writer.decInd();
writer.Stream() << writer.ind() << "</XLinkSubList>" << endl ;
}
void PropertyXLinkSubList::Restore(Base::XMLReader &reader)
{
reader.readElement("XLinkSubList");
setFlag(LinkAllowPartial,
reader.hasAttribute("partial") &&
reader.getAttributeAsInteger("partial"));
int count = reader.getAttributeAsInteger("count");
atomic_change guard(*this,false);
_Links.clear();
for(int i=0;i<count;++i) {
_Links.emplace_back(false,this);
_Links.back().Restore(reader);
}
reader.readEndElement("XLinkSubList");
guard.tryInvoke();
}
Property *PropertyXLinkSubList::CopyOnImportExternal(
const std::map<std::string,std::string> &nameMap) const
{
std::unique_ptr<Property> copy;
auto it = _Links.begin();
for(;it!=_Links.end();++it) {
copy.reset(it->CopyOnImportExternal(nameMap));
if(copy) break;
}
if(!copy)
return nullptr;
std::unique_ptr<PropertyXLinkSubList> 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<PropertyXLinkSub&>(*copy).copyTo(p->_Links.back());
for(++it;it!=_Links.end();++it) {
p->_Links.emplace_back();
copy.reset(it->CopyOnImportExternal(nameMap));
if(copy)
static_cast<PropertyXLinkSub&>(*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<Property> copy;
auto it = _Links.begin();
for(;it!=_Links.end();++it) {
copy.reset(it->CopyOnLabelChange(obj,ref,newLabel));
if(copy) break;
}
if(!copy)
return nullptr;
std::unique_ptr<PropertyXLinkSubList> 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<PropertyXLinkSub&>(*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<PropertyXLinkSub&>(*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<Property> copy;
PropertyXLinkSub *copied = nullptr;
std::set<std::string> subs;
auto it = _Links.begin();
for(;it!=_Links.end();++it) {
copy.reset(it->CopyOnLinkReplace(parent,oldObj,newObj));
if(copy) {
copied = static_cast<PropertyXLinkSub*>(copy.get());
if(copied->getValue() == newObj) {
for(auto &sub : copied->getSubValues())
subs.insert(sub);
}
break;
}
}
if(!copy)
return nullptr;
std::unique_ptr<PropertyXLinkSubList> 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<PropertyXLinkSub&>(*copy).copyTo(p->_Links.back());
else
it->copyTo(p->_Links.back());
}
return p.release();
}
Property *PropertyXLinkSubList::Copy() 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 property to paste to");
aboutToSetValue();
_Links.clear();
for(auto &l : static_cast<const PropertyXLinkSubList&>(from)._Links) {
_Links.emplace_back(testFlag(LinkAllowPartial),this);
_Links.back().Paste(l);
}
hasSetValue();
}
unsigned int PropertyXLinkSubList::getMemSize () const
{
unsigned int size=0;
for(auto &l : _Links)
size += l.getMemSize();
return size;
}
const std::vector<std::string> &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<std::string> 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<App::DocumentObject *> &objs,
bool all, std::vector<std::string> *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->isAttachedToDocument())
objs.push_back(obj);
}
return;
}
size_t count=0;
for(auto &l : _Links) {
auto obj = l.getValue();
if(obj && obj->isAttachedToDocument())
count += std::max((int)l.getSubValues().size(), 1);
}
if(!count) {
objs.reserve(objs.size()+_Links.size());
for(auto &l : _Links) {
auto obj = l.getValue();
if(obj && obj->isAttachedToDocument())
objs.push_back(obj);
}
return;
}
objs.reserve(objs.size()+count);
subs->reserve(subs->size()+count);
for(auto &l : _Links) {
auto obj = l.getValue();
if(obj && obj->isAttachedToDocument()) {
auto subnames = l.getSubValues(newStyle);
if (subnames.empty())
subnames.emplace_back("");
for(auto &sub : subnames) {
objs.push_back(obj);
subs->push_back(std::move(sub));
}
}
}
}
}
void PropertyXLinkSubList::breakLink(App::DocumentObject *obj, bool clear) {
if(clear && getContainer()==obj) {
setValue(nullptr);
return;
}
atomic_change guard(*this,false);
for(auto &l : _Links) {
if(l.getValue() == obj) {
guard.aboutToChange();
l.setValue(nullptr);
}
}
guard.tryInvoke();
}
bool PropertyXLinkSubList::adjustLink(const std::set<App::DocumentObject*> &inList) {
if (_pcScope==LinkScope::Hidden)
return false;
std::map<App::DocumentObject*,std::vector<std::string> > values;
bool touched = false;
int count=0;
for(auto &l : _Links) {
auto obj = l.getValue();
if(!obj || !obj->isAttachedToDocument()) {
++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, PropertyLinkListGlobal::getClassTypeId().getName())==0 ||
strcmp(typeName, PropertyLinkList::getClassTypeId().getName())==0 ||
strcmp(typeName, PropertyLinkListChild::getClassTypeId().getName())==0)
{
PropertyLinkList linkProp;
linkProp.setContainer(getContainer());
linkProp.Restore(reader);
setValues(linkProp.getValues());
return true;
} else 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<DocumentObject *, std::vector<std::string> > values;
const auto &objs = linkProp.getValues();
const auto &subs = linkProp.getSubValues();
assert(objs.size() == subs.size());
for(size_t i=0;i<objs.size();++i)
values[objs[i]].push_back(subs[i]);
setValues(std::move(values));
return true;
}
_Links.clear();
_Links.emplace_back(testFlag(LinkAllowPartial),this);
if(!_Links.back().upgrade(reader,typeName)) {
_Links.clear();
return false;
}
return true;
}
void PropertyXLinkSubList::setAllowPartial(bool enable) {
setFlag(LinkAllowPartial,enable);
for(auto &l : _Links)
l.setAllowPartial(enable);
}
void PropertyXLinkSubList::hasSetChildValue(Property &) {
if(!signalCounter)
hasSetValue();
}
void PropertyXLinkSubList::aboutToSetChildValue(Property &) {
if(!signalCounter || !hasChanged) {
aboutToSetValue();
if(signalCounter)
hasChanged = true;
}
}
std::vector<App::DocumentObject*> PropertyXLinkSubList::getValues() const
{
std::vector<DocumentObject*> xLinks;
getLinks(xLinks);
return(xLinks);
}
//**************************************************************************
// PropertyXLinkList
//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
TYPESYSTEM_SOURCE(App::PropertyXLinkList , App::PropertyXLinkSubList)
//**************************************************************************
// Construction/Destruction
PropertyXLinkList::PropertyXLinkList() = default;
PropertyXLinkList::~PropertyXLinkList() = default;
PyObject *PropertyXLinkList::getPyObject()
{
for(auto &link : _Links) {
auto obj = link.getValue();
if(!obj || !obj->isAttachedToDocument())
continue;
if(link.hasSubName())
return PropertyXLinkSubList::getPyObject();
}
Py::List list;
for(auto &link : _Links) {
auto obj = link.getValue();
if(!obj || !obj->isAttachedToDocument())
continue;
list.append(Py::asObject(obj->getPyObject()));
}
return Py::new_reference_to(list);
}
void PropertyXLinkList::setPyObject(PyObject *value)
{
try { //try PropertyLinkList syntax
PropertyLinkList dummy;
dummy.setAllowExternal(true);
dummy.setPyObject(value);
this->setValues(dummy.getValues());
return;
}
catch (Base::Exception&) {}
PropertyXLinkSubList::setPyObject(value);
}
//**************************************************************************
// PropertyXLinkContainer
//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
TYPESYSTEM_SOURCE_ABSTRACT(App::PropertyXLinkContainer , App::PropertyLinkBase)
PropertyXLinkContainer::PropertyXLinkContainer() {
_pcScope = LinkScope::Global;
_LinkRestored = false;
}
PropertyXLinkContainer::~PropertyXLinkContainer() = default;
void PropertyXLinkContainer::afterRestore() {
_DocMap.clear();
if(!_XLinkRestores)
return;
_Deps.clear();
for(auto &info : *_XLinkRestores) {
auto obj = info.xlink->getValue();
if(!obj)
continue;
if(!info.docName.empty()) {
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(std::make_pair(obj,info.xlink->getScope()==LinkScope::Hidden)).second)
_XLinks[obj->getFullName()] = std::move(info.xlink);
}
_XLinkRestores.reset();
}
void PropertyXLinkContainer::breakLink(App::DocumentObject *obj, bool clear) {
if(!obj || !obj->isAttachedToDocument())
return;
auto owner = dynamic_cast<App::DocumentObject*>(getContainer());
if(!owner || !owner->isAttachedToDocument())
return;
if(!clear || obj!=owner) {
auto it = _Deps.find(obj);
if(it == _Deps.end())
return;
aboutToSetValue();
onBreakLink(obj);
if (obj->getDocument() != owner->getDocument())
_XLinks.erase(obj->getFullName());
else if (!it->second)
obj->_removeBackLink(owner);
_Deps.erase(it);
hasSetValue();
return;
}
if(obj!=owner)
return;
for(auto &v : _Deps) {
auto key = v.first;
if(!key || !key->isAttachedToDocument())
continue;
onBreakLink(key);
if(!v.second && key->getDocument()==owner->getDocument())
key->_removeBackLink(owner);
}
_XLinks.clear();
_Deps.clear();
}
int PropertyXLinkContainer::checkRestore(std::string *msg) const {
if(_LinkRestored) {
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() << "<XLinks count=\"" << _XLinks.size();
std::map<App::Document*,int> docSet;
auto owner = Base::freecad_dynamic_cast<App::DocumentObject>(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.empty())
writer.Stream() << "\" docs=\"" << docSet.size();
}
std::ostringstream ss;
int hidden = 0;
int i=-1;
for(auto &v : _XLinks) {
++i;
if(v.second->getScope() == LinkScope::Hidden) {
ss << i << ' ';
++hidden;
}
}
if(hidden)
writer.Stream() << "\" hidden=\"" << ss.str();
writer.Stream() << "\">" << std::endl;
writer.incInd();
for(auto &v : docSet) {
writer.Stream() << writer.ind() << "<DocMap "
<< "name=\"" << v.first->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() << "</XLinks>" << std::endl;
}
void PropertyXLinkContainer::Restore(Base::XMLReader &reader) {
reader.readElement("XLinks");
auto count = reader.getAttributeAsUnsigned("count");
_XLinkRestores = std::make_unique<std::vector<RestoreInfo>>(count);
if(reader.hasAttribute("hidden")) {
std::istringstream iss(reader.getAttribute("hidden"));
int index;
while(iss >> index) {
if(index>=0 && index<static_cast<int>(count))
_XLinkRestores->at(index).hidden = true;
}
}
if(reader.hasAttribute("docs")) {
auto docCount = reader.getAttributeAsUnsigned("docs");
_DocMap.clear();
for(unsigned i=0;i<docCount;++i) {
reader.readElement("DocMap");
auto index = reader.getAttributeAsUnsigned("index");
if(index>=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());
if(info.hidden)
info.xlink->setScope(LinkScope::Hidden);
info.xlink->Restore(reader);
}
reader.readEndElement("XLinks");
}
void PropertyXLinkContainer::aboutToSetChildValue(App::Property &prop) {
auto xlink = dynamic_cast<App::PropertyXLink*>(&prop);
if(xlink && xlink->testFlag(LinkDetached)) {
if(_Deps.erase(const_cast<App::DocumentObject*>(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::map<DocumentObject*,bool> &&newDeps) {
auto owner = Base::freecad_dynamic_cast<App::DocumentObject>(getContainer());
if(!owner || !owner->isAttachedToDocument())
return;
newDeps.erase(owner);
for(auto &v : newDeps) {
auto obj = v.first;
if(obj && obj->isAttachedToDocument()) {
auto it = _Deps.find(obj);
if(it != _Deps.end()) {
if(v.second != it->second) {
if(v.second)
obj->_removeBackLink(owner);
else
obj->_addBackLink(owner);
}
_Deps.erase(it);
continue;
}
if(owner->getDocument()!=obj->getDocument()) {
auto &xlink = _XLinks[obj->getFullName()];
if(!xlink) {
xlink.reset(createXLink());
xlink->setValue(obj);
}
xlink->setScope(v.second?LinkScope::Hidden:LinkScope::Global);
}
else if(!v.second)
obj->_addBackLink(owner);
onAddDep(obj);
}
}
for(auto &v : _Deps) {
auto obj = v.first;
if(!obj || !obj->isAttachedToDocument())
continue;
if(obj->getDocument()==owner->getDocument()) {
if(!v.second)
obj->_removeBackLink(owner);
}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<App::DocumentObject*>(getContainer());
if(!owner || !owner->isAttachedToDocument())
return;
#ifndef USE_OLD_DAG
if (!owner->testStatus(ObjectStatus::Destroy)) {
for(auto &v : _Deps) {
auto obj = v.first;
if(!v.second && obj && obj->isAttachedToDocument() && obj->getDocument()==owner->getDocument())
obj->_removeBackLink(owner);
}
}
#endif
_Deps.clear();
_XLinks.clear();
_LinkRestored = false;
}
void PropertyXLinkContainer::getLinks(std::vector<App::DocumentObject *> &objs,
bool all, std::vector<std::string> * /*subs*/, bool /*newStyle*/) const
{
for(auto &v : _Deps) {
if(all || !v.second)
objs.push_back(v.first);
}
}