6022 lines
198 KiB
C++
6022 lines
198 KiB
C++
// SPDX-License-Identifier: LGPL-2.1-or-later
|
|
|
|
/***************************************************************************
|
|
* 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 <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 <Base/Tools.h>
|
|
|
|
#include "PropertyLinks.h"
|
|
#include "Application.h"
|
|
#include "Document.h"
|
|
#include "DocumentObject.h"
|
|
#include "DocumentObjectPy.h"
|
|
#include "DocumentObserver.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)
|
|
|
|
// clang-format off
|
|
static std::unordered_map<std::string, std::set<PropertyLinkBase*>> _LabelMap;
|
|
static std::unordered_map<App::DocumentObject*, std::unordered_set<PropertyLinkBase*>> _ElementRefMap;
|
|
// clang-format on
|
|
|
|
PropertyLinkBase::PropertyLinkBase() = default;
|
|
|
|
PropertyLinkBase::~PropertyLinkBase()
|
|
{
|
|
unregisterLabelReferences();
|
|
unregisterElementReference();
|
|
}
|
|
|
|
void PropertyLinkBase::setAllowExternal(bool allow)
|
|
{
|
|
setFlag(LinkAllowExternal, allow);
|
|
}
|
|
|
|
void PropertyLinkBase::setSilentRestore(bool allow)
|
|
{
|
|
setFlag(LinkSilentRestore, allow);
|
|
}
|
|
|
|
void PropertyLinkBase::setReturnNewElement(bool enable)
|
|
{
|
|
setFlag(LinkNewElement, enable);
|
|
}
|
|
|
|
void PropertyLinkBase::hasSetValue()
|
|
{
|
|
auto owner = freecad_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>()
|
|
|| 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()
|
|
{
|
|
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();
|
|
}
|
|
|
|
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,
|
|
const 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 = freecad_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)
|
|
{
|
|
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());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void PropertyLinkBase::updateAllElementReferences(bool reverse)
|
|
{
|
|
for (auto reference : _ElementRefMap) {
|
|
for (auto prop : reference.second) {
|
|
if (prop->getContainer()) {
|
|
try {
|
|
prop->updateElementReference(reference.first, 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());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void PropertyLinkBase::_registerElementReference(App::DocumentObject* obj,
|
|
std::string& sub,
|
|
ShadowSub& shadow)
|
|
{
|
|
if (!obj || !obj->getNameInDocument() || sub.empty()) {
|
|
return;
|
|
}
|
|
if (shadow.newName.empty()) {
|
|
_updateElementReference(nullptr, obj, sub, shadow, false);
|
|
return;
|
|
}
|
|
GeoFeature* geo = nullptr;
|
|
const char* element = nullptr;
|
|
ShadowSub elementName;
|
|
GeoFeature::resolveElement(obj,
|
|
sub.c_str(),
|
|
elementName,
|
|
true,
|
|
GeoFeature::ElementNameType::Export,
|
|
nullptr,
|
|
&element,
|
|
&geo);
|
|
if (!geo || !element || !element[0]) {
|
|
return;
|
|
}
|
|
|
|
if (_ElementRefs.insert(geo).second) {
|
|
_ElementRefMap[geo].insert(this);
|
|
}
|
|
}
|
|
|
|
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->oldName.size() >= count) {
|
|
shadow->oldName = newSub + (shadow->oldName.c_str() + count);
|
|
}
|
|
if (shadow && shadow->newName.size() >= count) {
|
|
shadow->newName = newSub + (shadow->newName.c_str() + count);
|
|
}
|
|
subname = newSub + sub;
|
|
}
|
|
|
|
bool PropertyLinkBase::_updateElementReference(DocumentObject* feature,
|
|
App::DocumentObject* obj,
|
|
std::string& sub,
|
|
ShadowSub& shadow,
|
|
bool reverse,
|
|
bool notify)
|
|
{
|
|
if (!obj || !obj->getNameInDocument()) {
|
|
return false;
|
|
}
|
|
ShadowSub elementName;
|
|
const char* subname;
|
|
if (shadow.newName.size()) {
|
|
subname = shadow.newName.c_str();
|
|
}
|
|
else if (shadow.oldName.size()) {
|
|
subname = shadow.oldName.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.oldName.size()) {
|
|
shadow.oldName.swap(elementName.oldName);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
if (_ElementRefs.insert(geo).second) {
|
|
_ElementRefMap[geo].insert(this);
|
|
}
|
|
|
|
if (!reverse) {
|
|
if (elementName.newName.empty()) {
|
|
shadow.oldName.swap(elementName.oldName);
|
|
return false;
|
|
}
|
|
if (shadow == elementName) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
bool missing = GeoFeature::hasMissingElement(elementName.oldName.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.oldName.c_str());
|
|
if (!Data::hasMissingElement(oldElement)) {
|
|
auto names = geo->searchElementCache(oldElement);
|
|
if (names.empty()) {
|
|
// try floating point tolerance
|
|
names = geo->searchElementCache(oldElement, Data::SearchOptions());
|
|
}
|
|
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.newName.size() ? shadow.newName : shadow.oldName;
|
|
const auto& newName =
|
|
elementName.newName.size() ? elementName.newName : elementName.oldName;
|
|
if (oldName != newName) {
|
|
FC_LOG(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.newName.size() ? elementName.newName : elementName.oldName));
|
|
shadow.oldName.swap(elementName.oldName);
|
|
}
|
|
else {
|
|
FC_TRACE(propertyName(this) << " element reference shadow update " << ret->getFullName()
|
|
<< " " << shadow.newName << " -> " << elementName.newName);
|
|
shadow.swap(elementName);
|
|
if (shadow.newName.size() && Data::hasMappedElementName(sub.c_str())) {
|
|
updateSub(shadow.newName);
|
|
}
|
|
}
|
|
|
|
if (reverse) {
|
|
if (shadow.newName.size() && Data::hasMappedElementName(sub.c_str())) {
|
|
updateSub(shadow.newName);
|
|
}
|
|
else {
|
|
updateSub(shadow.oldName);
|
|
}
|
|
return true;
|
|
}
|
|
if (missing) {
|
|
if (sub != shadow.newName) {
|
|
updateSub(shadow.oldName);
|
|
}
|
|
return true;
|
|
}
|
|
auto pos2 = shadow.newName.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.newName[pos2]) != 0) {
|
|
FC_LOG("element reference update " << sub << " -> " << shadow.newName);
|
|
std::string newSub(sub);
|
|
newSub.replace(pos, sub.size() - pos, &shadow.newName[pos2]);
|
|
updateSub(newSub);
|
|
}
|
|
}
|
|
else if (sub != shadow.oldName) {
|
|
FC_LOG("element reference update " << sub << " -> " << shadow.oldName);
|
|
updateSub(shadow.oldName);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
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;
|
|
}
|
|
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);
|
|
}
|
|
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;
|
|
}
|
|
else if (sobj == newObj) {
|
|
return tryReplaceLink(owner, obj, parent, newObj, oldObj, subname);
|
|
}
|
|
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
|
|
// 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>()) {
|
|
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);
|
|
}
|
|
}
|
|
}
|
|
_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();
|
|
|
|
// 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);
|
|
}
|
|
}
|
|
}
|
|
|
|
_pcLink = lValue;
|
|
hasSetValue();
|
|
}
|
|
|
|
App::DocumentObject* PropertyLink::getValue() const
|
|
{
|
|
return _pcLink;
|
|
}
|
|
|
|
App::DocumentObject* PropertyLink::getValue(Base::Type t) const
|
|
{
|
|
return (_pcLink && _pcLink->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<const char*>("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>()) {
|
|
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::getLinksTo(std::vector<App::ObjectIdentifier>& identifiers,
|
|
App::DocumentObject* obj,
|
|
const char* subname,
|
|
bool all) const
|
|
{
|
|
(void)subname;
|
|
if (!all && _pcScope == LinkScope::Hidden) {
|
|
return; // Don't get hidden links unless all is specified.
|
|
}
|
|
if (obj && _pcLink == obj) {
|
|
identifiers.emplace_back(*this);
|
|
}
|
|
}
|
|
|
|
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
|
|
|
|
// maintain the back link in the DocumentObject class
|
|
if (_pcScope != LinkScope::Hidden && !_lValueList.empty() && getContainer()
|
|
&& getContainer()->isDerivedFrom<App::DocumentObject>()) {
|
|
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);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
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());
|
|
|
|
if (_pcScope != LinkScope::Hidden) {
|
|
obj->_removeBackLink(static_cast<DocumentObject*>(getContainer()));
|
|
}
|
|
}
|
|
_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();
|
|
|
|
if (getContainer() && getContainer()->isDerivedFrom<App::DocumentObject>()) {
|
|
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()));
|
|
}
|
|
}
|
|
}
|
|
|
|
inherited::set1Value(idx, value);
|
|
}
|
|
|
|
void PropertyLinkList::setValues(const std::vector<DocumentObject*>& value)
|
|
{
|
|
if (value.size() == 1 && !value[0]) {
|
|
// one null element means clear, as backward compatibility for old code
|
|
setValues(std::vector<DocumentObject*>());
|
|
return;
|
|
}
|
|
|
|
auto parent = freecad_cast<App::DocumentObject*>(getContainer());
|
|
for (auto obj : value) {
|
|
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();
|
|
|
|
// 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 : value) {
|
|
if (obj) {
|
|
obj->_addBackLink(parent);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
inherited::setValues(value);
|
|
}
|
|
|
|
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.getAttribute<long>("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<const char*>("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>()) {
|
|
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 char* name, int* pindex) const
|
|
{
|
|
const int DONT_MAP_UNDER = 10;
|
|
if (!name) {
|
|
return nullptr;
|
|
}
|
|
if (_lValueList.size() <= DONT_MAP_UNDER) {
|
|
int index = -1;
|
|
for (auto obj : _lValueList) {
|
|
++index;
|
|
if (obj && obj->getNameInDocument() && boost::equals(name, obj->getNameInDocument())) {
|
|
if (pindex) {
|
|
*pindex = index;
|
|
}
|
|
return obj;
|
|
}
|
|
}
|
|
return nullptr;
|
|
}
|
|
// We're using a map. Do we need to (re)create it?
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
// Now lookup up in that map
|
|
auto it = _nameMap.find(name);
|
|
if (it == _nameMap.end()) {
|
|
return nullptr;
|
|
}
|
|
if (pindex) {
|
|
*pindex = it->second;
|
|
}
|
|
return _lValueList[it->second];
|
|
}
|
|
|
|
DocumentObject* PropertyLinkList::findUsingMap(const std::string& name, int* pindex) const
|
|
{
|
|
if (_nameMap.size() == _lValueList.size()) {
|
|
auto it = _nameMap.find(name);
|
|
if (it == _nameMap.end()) {
|
|
return nullptr;
|
|
}
|
|
if (pindex) {
|
|
*pindex = it->second;
|
|
}
|
|
return _lValueList[it->second];
|
|
}
|
|
return find(name.c_str(), pindex);
|
|
}
|
|
|
|
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::getLinksTo(std::vector<App::ObjectIdentifier>& identifiers,
|
|
App::DocumentObject* obj,
|
|
const char* subname,
|
|
bool all) const
|
|
{
|
|
(void)subname;
|
|
if (!obj || (!all && _pcScope == LinkScope::Hidden)) {
|
|
return;
|
|
}
|
|
int i = -1;
|
|
for (auto docObj : _lValueList) {
|
|
++i;
|
|
if (docObj == obj) {
|
|
identifiers.emplace_back(*this, i);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
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
|
|
|
|
if (_pcLinkSub && getContainer()
|
|
&& getContainer()->isDerivedFrom<App::DocumentObject>()) {
|
|
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);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
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 = freecad_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();
|
|
|
|
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);
|
|
}
|
|
}
|
|
}
|
|
|
|
_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.oldName.empty()) {
|
|
return shadow.oldName;
|
|
}
|
|
}
|
|
else if (!shadow.newName.empty()) {
|
|
if (Data::hasMissingElement(shadow.oldName.c_str())) {
|
|
auto pos = shadow.newName.rfind('.');
|
|
if (pos != std::string::npos) {
|
|
tmp = shadow.newName.substr(0, pos + 1);
|
|
tmp += shadow.oldName;
|
|
return tmp;
|
|
}
|
|
}
|
|
return shadow.newName;
|
|
}
|
|
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
|
|
{
|
|
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;
|
|
}
|
|
|
|
App::DocumentObject* PropertyLinkSub::getValue(Base::Type t) const
|
|
{
|
|
return (_pcLinkSub && _pcLinkSub->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());
|
|
int i = 0;
|
|
for (auto& sub : getSubValues(testFlag(LinkNewElement))) {
|
|
list[i++] = Py::String(sub);
|
|
}
|
|
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 = freecad_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].newName.empty()) {
|
|
subs[idx] = shadows[idx].newName;
|
|
}
|
|
}
|
|
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 {};
|
|
}
|
|
|
|
void PropertyLinkBase::_getLinksTo(std::vector<App::ObjectIdentifier>& identifiers,
|
|
App::DocumentObject* obj,
|
|
const char* subname,
|
|
const std::vector<std::string>& subs,
|
|
const std::vector<PropertyLinkBase::ShadowSub>& shadows) const
|
|
{
|
|
if (!subname) {
|
|
identifiers.emplace_back(*this);
|
|
return;
|
|
}
|
|
App::SubObjectT objT(obj, subname);
|
|
auto subObject = objT.getSubObject();
|
|
auto subElement = objT.getOldElementName();
|
|
|
|
int i = -1;
|
|
for (const auto& sub : subs) {
|
|
++i;
|
|
if (sub == subname) {
|
|
identifiers.emplace_back(*this);
|
|
return;
|
|
}
|
|
if (!subObject) {
|
|
continue;
|
|
}
|
|
// After above, there is a subobject and the subname doesn't match our current entry
|
|
App::SubObjectT sobjT(obj, sub.c_str());
|
|
if (sobjT.getSubObject() == subObject && sobjT.getOldElementName() == subElement) {
|
|
identifiers.emplace_back(*this);
|
|
return;
|
|
}
|
|
// And the oldElementName ( short, I.E. "Edge5" ) doesn't match.
|
|
if (i < (int)shadows.size()) {
|
|
const auto& [shadowNewName, shadowOldName] = shadows[i];
|
|
if (shadowNewName == subname || shadowOldName == subname) {
|
|
identifiers.emplace_back(*this);
|
|
return;
|
|
}
|
|
if (!subObject) {
|
|
continue;
|
|
}
|
|
App::SubObjectT shadowobjT(obj,
|
|
shadowNewName.empty() ? shadowOldName.c_str()
|
|
: shadowNewName.c_str());
|
|
if (shadowobjT.getSubObject() == subObject
|
|
&& shadowobjT.getOldElementName() == subElement) {
|
|
identifiers.emplace_back(*this);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#define ATTR_SHADOWED "shadowed"
|
|
#define ATTR_SHADOW "shadow"
|
|
#define ATTR_MAPPED "mapped"
|
|
|
|
#define IGNORE_SHADOW false
|
|
|
|
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 = freecad_cast<DocumentObject*>(getContainer());
|
|
bool exporting = owner && owner->isExporting();
|
|
for (unsigned int i = 0; i < _cSubList.size(); i++) {
|
|
const auto& shadow = _ShadowSubList[i];
|
|
// shadow.oldName 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.oldName.empty() ? _cSubList[i] : shadow.oldName;
|
|
writer.Stream() << writer.ind() << "<Sub value=\"";
|
|
if (exporting) {
|
|
std::string exportName;
|
|
writer.Stream() << encodeAttribute(exportSubName(exportName, _pcLinkSub, sub.c_str()));
|
|
if (!shadow.oldName.empty() && shadow.newName == _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.newName.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.newName);
|
|
}
|
|
}
|
|
}
|
|
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<const char*>("value"));
|
|
int count = reader.getAttribute<long>("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].oldName = importSubName(reader, reader.getAttribute<const char*>("value"), restoreLabel);
|
|
if (reader.hasAttribute(ATTR_SHADOWED) && !IGNORE_SHADOW) {
|
|
values[i] = shadows[i].newName =
|
|
importSubName(reader, reader.getAttribute<const char*>(ATTR_SHADOWED), restoreLabel);
|
|
}
|
|
else {
|
|
values[i] = shadows[i].oldName;
|
|
if (reader.hasAttribute(ATTR_SHADOW) && !IGNORE_SHADOW) {
|
|
shadows[i].newName =
|
|
importSubName(reader, reader.getAttribute<const char*>(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(), 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;
|
|
p->_ShadowSubList = _ShadowSubList;
|
|
return p;
|
|
}
|
|
|
|
void PropertyLinkSub::Paste(const Property& from)
|
|
{
|
|
if (!from.isDerivedFrom<PropertyLinkSub>()) {
|
|
throw Base::TypeError("Incompatible property to paste to");
|
|
}
|
|
auto& link = static_cast<const PropertyLinkSub&>(from);
|
|
setValue(link._pcLinkSub, link._cSubList, std::vector<ShadowSub>(link._ShadowSubList));
|
|
}
|
|
|
|
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()) {
|
|
// we use to run this method everytime the program needed to access the sub-elements in
|
|
// a property link, but it caused multiple issues (#23441 and #23402) so it has been
|
|
// commented out.
|
|
// updateElementReferences(_pcLinkSub);
|
|
objs.push_back(_pcLinkSub);
|
|
if (subs) {
|
|
*subs = getSubValues(newStyle);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void PropertyLinkSub::getLinksTo(std::vector<App::ObjectIdentifier>& identifiers,
|
|
App::DocumentObject* obj,
|
|
const char* subname,
|
|
bool all) const
|
|
{
|
|
if (all || _pcScope != LinkScope::Hidden) {
|
|
if (obj && obj == _pcLinkSub) {
|
|
_getLinksTo(identifiers, obj, subname, _cSubList, _ShadowSubList);
|
|
}
|
|
}
|
|
}
|
|
|
|
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.contains(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.contains(_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
|
|
|
|
// maintain backlinks
|
|
if (!_lValueList.empty() && getContainer()
|
|
&& getContainer()->isDerivedFrom<App::DocumentObject>()) {
|
|
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);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
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 = freecad_cast<App::DocumentObject*>(getContainer());
|
|
verifyObject(lValue, parent);
|
|
|
|
// 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);
|
|
}
|
|
}
|
|
}
|
|
|
|
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 = freecad_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");
|
|
}
|
|
|
|
// 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);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
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 = freecad_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");
|
|
}
|
|
|
|
// 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);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
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);
|
|
|
|
// 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);
|
|
}
|
|
}
|
|
}
|
|
|
|
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 = freecad_cast<App::DocumentObject*>(getContainer());
|
|
verifyObject(obj, parent);
|
|
|
|
// 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);
|
|
}
|
|
}
|
|
}
|
|
|
|
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;
|
|
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);
|
|
}
|
|
}
|
|
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].newName.empty()) {
|
|
sub = _ShadowSubList[i].newName;
|
|
}
|
|
else if (!newStyle && !_ShadowSubList[i].oldName.empty()) {
|
|
sub = _ShadowSubList[i].oldName;
|
|
}
|
|
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_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].newName.empty()) {
|
|
_lSubList[idx] = _ShadowSubList[idx].newName;
|
|
}
|
|
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 = freecad_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.oldName 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.oldName.empty() ? _lSubList[i] : shadow.oldName;
|
|
|
|
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.oldName.empty() && _lSubList[i] == shadow.newName) {
|
|
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.newName.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.newName);
|
|
}
|
|
}
|
|
}
|
|
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.getAttribute<long>("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 = freecad_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<const char*>("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.oldName = importSubName(reader, reader.getAttribute<const char*>("sub"), restoreLabel);
|
|
if (reader.hasAttribute(ATTR_SHADOWED) && !IGNORE_SHADOW) {
|
|
shadow.newName =
|
|
importSubName(reader, reader.getAttribute<const char*>(ATTR_SHADOWED), restoreLabel);
|
|
SubNames.push_back(shadow.newName);
|
|
}
|
|
else {
|
|
SubNames.push_back(shadow.oldName);
|
|
if (reader.hasAttribute(ATTR_SHADOW) && !IGNORE_SHADOW) {
|
|
shadow.newName =
|
|
importSubName(reader, reader.getAttribute<const char*>(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;
|
|
p->_ShadowSubList = _ShadowSubList;
|
|
return p;
|
|
}
|
|
|
|
void PropertyLinkSubList::Paste(const Property& from)
|
|
{
|
|
if (!from.isDerivedFrom<PropertyLinkSubList>()) {
|
|
throw Base::TypeError("Incompatible property to paste to");
|
|
}
|
|
auto& link = static_cast<const PropertyLinkSubList&>(from);
|
|
setValues(link._lValueList, link._lSubList, std::vector<ShadowSub>(link._ShadowSubList));
|
|
}
|
|
|
|
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()) {
|
|
// we use to run this method everytime the program needed to access the sub-elements in
|
|
// a property link, but it caused multiple issues (#23441 and #23402) so it has been
|
|
// commented out.
|
|
// updateElementReferences(obj);
|
|
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::getLinksTo(std::vector<App::ObjectIdentifier>& identifiers,
|
|
App::DocumentObject* obj,
|
|
const char* subname,
|
|
bool all) const
|
|
{
|
|
if (!obj || (!all && _pcScope == LinkScope::Hidden)) {
|
|
return;
|
|
}
|
|
App::SubObjectT objT(obj, subname);
|
|
auto subObject = objT.getSubObject();
|
|
auto subElement = objT.getOldElementName();
|
|
|
|
int i = -1;
|
|
for (const auto& docObj : _lValueList) {
|
|
++i;
|
|
if (docObj != obj) {
|
|
continue;
|
|
}
|
|
// If we don't specify a subname we looking for all; or if the subname is in our
|
|
// property, add this entry to our result
|
|
if (!subname || (i < (int)_lSubList.size() && subname == _lSubList[i])) {
|
|
identifiers.emplace_back(*this, i);
|
|
continue;
|
|
}
|
|
// If we couldn't find any subobjects or this object's index is in our list, ignore it
|
|
if (!subObject || i < (int)_lSubList.size()) {
|
|
continue;
|
|
}
|
|
App::SubObjectT sobjT(obj, _lSubList[i].c_str());
|
|
if (sobjT.getSubObject() == subObject && sobjT.getOldElementName() == subElement) {
|
|
identifiers.emplace_back(*this);
|
|
continue;
|
|
}
|
|
if (i < (int)_ShadowSubList.size()) {
|
|
const auto& shadow = _ShadowSubList[i];
|
|
App::SubObjectT sobjT(obj,
|
|
shadow.newName.empty() ? shadow.oldName.c_str()
|
|
: shadow.newName.c_str());
|
|
if (sobjT.getSubObject() == subObject && sobjT.getOldElementName() == subElement) {
|
|
identifiers.emplace_back(*this);
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
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.contains(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.contains(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 = freecad_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 = freecad_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 = freecad_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 = freecad_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()
|
|
{
|
|
try {
|
|
unlink();
|
|
} catch (std::bad_weak_ptr &) {
|
|
FC_WARN("Bad pointer exception caught when destroying PropertyXLink\n");
|
|
}
|
|
}
|
|
|
|
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 (!Base::Tools::isNullOrEmpty(subname)) {
|
|
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);
|
|
onContainerRestored(); // re-register element references
|
|
}
|
|
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 (!Base::Tools::isNullOrEmpty(subname)) {
|
|
subs.emplace_back(subname);
|
|
}
|
|
setValue(lValue, std::move(subs));
|
|
}
|
|
|
|
void PropertyXLink::restoreLink(App::DocumentObject* lValue)
|
|
{
|
|
assert(!_pcLink && lValue && docInfo);
|
|
|
|
auto owner = freecad_cast<DocumentObject*>(getContainer());
|
|
if (!owner || !owner->isAttachedToDocument()) {
|
|
throw Base::RuntimeError("invalid container");
|
|
}
|
|
|
|
bool touched = owner->isTouched();
|
|
setFlag(LinkDetached, false);
|
|
setFlag(LinkRestoring);
|
|
aboutToSetValue();
|
|
|
|
if (!owner->testStatus(ObjectStatus::Destroy) && _pcScope != LinkScope::Hidden) {
|
|
lValue->_addBackLink(owner);
|
|
}
|
|
|
|
_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");
|
|
}
|
|
|
|
auto owner = freecad_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);
|
|
|
|
if (!owner->testStatus(ObjectStatus::Destroy) && _pcScope != LinkScope::Hidden) {
|
|
if (_pcLink) {
|
|
_pcLink->_removeBackLink(owner);
|
|
}
|
|
if (lValue) {
|
|
lValue->_addBackLink(owner);
|
|
}
|
|
}
|
|
|
|
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 = freecad_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();
|
|
|
|
if (_pcLink && !owner->testStatus(ObjectStatus::Destroy) && _pcScope != LinkScope::Hidden) {
|
|
_pcLink->_removeBackLink(owner);
|
|
}
|
|
|
|
_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(LinkSilentRestore)) {
|
|
return 0;
|
|
}
|
|
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 (!Base::Tools::isNullOrEmpty(docPath)) {
|
|
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.oldName.empty() ? subName : shadowSub.oldName;
|
|
if (exporting) {
|
|
std::string exportName;
|
|
writer.Stream() << "\" sub=\""
|
|
<< encodeAttribute(exportSubName(exportName, _pcLink, sub.c_str()));
|
|
if (!shadowSub.oldName.empty() && shadowSub.newName == 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.newName.empty()) {
|
|
writer.Stream()
|
|
<< "\" " ATTR_SHADOW "=\"" << encodeAttribute(shadowSub.newName);
|
|
}
|
|
}
|
|
}
|
|
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.oldName 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.oldName.empty() ? _SubList[i] : shadow.oldName;
|
|
writer.Stream() << writer.ind() << "<Sub value=\"";
|
|
if (exporting) {
|
|
std::string exportName;
|
|
writer.Stream() << encodeAttribute(exportSubName(exportName, _pcLink, sub.c_str()));
|
|
if (!shadow.oldName.empty() && shadow.newName == _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.newName.empty()) {
|
|
writer.Stream()
|
|
<< "\" " ATTR_SHADOW "=\"" << encodeAttribute(shadow.newName);
|
|
}
|
|
}
|
|
}
|
|
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<const char*>("stamp");
|
|
}
|
|
if (reader.hasAttribute("file")) {
|
|
file = reader.getAttribute<const char*>("file");
|
|
}
|
|
|
|
setFlag(LinkAllowPartial,
|
|
reader.hasAttribute("partial") && reader.getAttribute<bool>("partial"));
|
|
std::string name;
|
|
if (file.empty()) {
|
|
name = reader.getName(reader.getAttribute<const char*>("name"));
|
|
}
|
|
else {
|
|
name = reader.getAttribute<const char*>("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.oldName = importSubName(reader, reader.getAttribute<const char*>("sub"), restoreLabel);
|
|
if (reader.hasAttribute(ATTR_SHADOWED) && !IGNORE_SHADOW) {
|
|
subname = shadow.newName =
|
|
importSubName(reader, reader.getAttribute<const char*>(ATTR_SHADOWED), restoreLabel);
|
|
}
|
|
else {
|
|
subname = shadow.oldName;
|
|
if (reader.hasAttribute(ATTR_SHADOW) && !IGNORE_SHADOW) {
|
|
shadow.newName =
|
|
importSubName(reader, reader.getAttribute<const char*>(ATTR_SHADOW), restoreLabel);
|
|
}
|
|
}
|
|
}
|
|
else if (reader.hasAttribute("count")) {
|
|
int count = reader.getAttribute<long>("count");
|
|
subs.resize(count);
|
|
shadows.resize(count);
|
|
for (int i = 0; i < count; i++) {
|
|
reader.readElement("Sub");
|
|
shadows[i].oldName = importSubName(reader, reader.getAttribute<const char*>("value"), restoreLabel);
|
|
if (reader.hasAttribute(ATTR_SHADOWED) && !IGNORE_SHADOW) {
|
|
subs[i] = shadows[i].newName =
|
|
importSubName(reader, reader.getAttribute<const char*>(ATTR_SHADOWED), restoreLabel);
|
|
}
|
|
else {
|
|
subs[i] = shadows[i].oldName;
|
|
if (reader.hasAttribute(ATTR_SHADOW) && !IGNORE_SHADOW) {
|
|
shadows[i].newName =
|
|
importSubName(reader, reader.getAttribute<const char*>(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 = freecad_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;
|
|
other._ShadowSubList = _ShadowSubList;
|
|
}
|
|
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>()) {
|
|
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;
|
|
}
|
|
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));
|
|
}
|
|
setFlag(LinkAllowPartial, other.testFlag(LinkAllowPartial));
|
|
}
|
|
|
|
bool PropertyXLink::supportXLink(const App::Property* prop)
|
|
{
|
|
return prop->isDerivedFrom<PropertyXLink>()
|
|
|| prop->isDerivedFrom<PropertyXLinkSubList>()
|
|
|| prop->isDerivedFrom<PropertyXLinkContainer>();
|
|
}
|
|
|
|
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) || v.second->links.empty()) {
|
|
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()) {
|
|
// we use to run this method everytime the program needed to access the sub-elements in
|
|
// a property link, but it caused multiple issues (#23441 and #23402) so it has been
|
|
// commented out.
|
|
// updateElementReferences(_pcLink, false);
|
|
objs.push_back(_pcLink);
|
|
if (subs && _SubList.size() == _ShadowSubList.size()) {
|
|
*subs = getSubValues(newStyle);
|
|
}
|
|
}
|
|
}
|
|
|
|
void PropertyXLink::getLinksTo(std::vector<App::ObjectIdentifier>& identifiers,
|
|
App::DocumentObject* obj,
|
|
const char* subname,
|
|
bool all) const
|
|
{
|
|
if (all || _pcScope != LinkScope::Hidden) {
|
|
if (obj && obj == _pcLink) {
|
|
_getLinksTo(identifiers, obj, subname, _SubList, _ShadowSubList);
|
|
}
|
|
}
|
|
}
|
|
|
|
bool PropertyXLink::adjustLink(const std::set<App::DocumentObject*>& inList)
|
|
{
|
|
if (_pcScope == LinkScope::Hidden) {
|
|
return false;
|
|
}
|
|
if (!_pcLink || !_pcLink->isAttachedToDocument() || !inList.contains(_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.getAttribute<bool>("partial"));
|
|
int count = reader.getAttribute<long>("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>()) {
|
|
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()) {
|
|
// we use to run this method everytime the program needed to access the sub-elements in
|
|
// a property link, but it caused multiple issues (#23441 and #23402) so it has been
|
|
// commented out.
|
|
// updateElementReferences(obj);
|
|
|
|
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));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Same algorithm as _getLinksTo above, but returns all matches
|
|
void PropertyXLinkSubList::_getLinksToList(
|
|
std::vector<App::ObjectIdentifier>& identifiers,
|
|
App::DocumentObject* obj,
|
|
const char* subname,
|
|
const std::vector<std::string>& subs,
|
|
const std::vector<PropertyLinkBase::ShadowSub>& shadows) const
|
|
{
|
|
if (!subname) {
|
|
identifiers.emplace_back(*this);
|
|
return;
|
|
}
|
|
App::SubObjectT objT(obj, subname);
|
|
auto subObject = objT.getSubObject();
|
|
auto subElement = objT.getOldElementName();
|
|
|
|
int i = -1;
|
|
for (const auto& sub : subs) {
|
|
++i;
|
|
if (sub == subname) {
|
|
identifiers.emplace_back(*this, i);
|
|
continue;
|
|
}
|
|
if (!subObject) {
|
|
continue;
|
|
}
|
|
// There is a subobject and the subname doesn't match our current entry
|
|
App::SubObjectT sobjT(obj, sub.c_str());
|
|
if (sobjT.getSubObject() == subObject && sobjT.getOldElementName() == subElement) {
|
|
identifiers.emplace_back(*this, i);
|
|
continue;
|
|
}
|
|
// The oldElementName ( short, I.E. "Edge5" ) doesn't match.
|
|
if (i < (int)shadows.size()) {
|
|
const auto& [shadowNewName, shadowOldName] = shadows[i];
|
|
if (shadowNewName == subname || shadowOldName == subname) {
|
|
identifiers.emplace_back(*this, i);
|
|
continue;
|
|
}
|
|
if (!subObject) {
|
|
continue;
|
|
}
|
|
App::SubObjectT shadowobjT(obj,
|
|
shadowNewName.empty() ? shadowOldName.c_str()
|
|
: shadowNewName.c_str());
|
|
if (shadowobjT.getSubObject() == subObject
|
|
&& shadowobjT.getOldElementName() == subElement) {
|
|
identifiers.emplace_back(*this, i);
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void PropertyXLinkSubList::getLinksTo(std::vector<App::ObjectIdentifier>& identifiers,
|
|
App::DocumentObject* obj,
|
|
const char* subname,
|
|
bool all) const
|
|
{
|
|
if (!all && _pcScope != LinkScope::Hidden) {
|
|
return;
|
|
}
|
|
for (auto& l : _Links) {
|
|
if (obj && obj == l._pcLink) {
|
|
_getLinksToList(identifiers, obj, subname, l._SubList, l._ShadowSubList);
|
|
}
|
|
}
|
|
}
|
|
|
|
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.contains(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);
|
|
onAddDep(obj);
|
|
}
|
|
}
|
|
_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);
|
|
onRemoveDep(obj);
|
|
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 = freecad_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.getAttribute<unsigned long>("count");
|
|
_XLinkRestores = std::make_unique<std::vector<RestoreInfo>>(count);
|
|
|
|
if (reader.hasAttribute("hidden")) {
|
|
std::istringstream iss(reader.getAttribute<const char*>("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.getAttribute<unsigned long>("docs");
|
|
_DocMap.clear();
|
|
for (unsigned i = 0; i < docCount; ++i) {
|
|
reader.readElement("DocMap");
|
|
auto index = reader.getAttribute<unsigned long>("index");
|
|
if (index >= count) {
|
|
FC_ERR(propertyName(this) << " invalid document map entry");
|
|
continue;
|
|
}
|
|
auto& info = _XLinkRestores->at(index);
|
|
info.docName = reader.getAttribute<const char*>("name");
|
|
info.docLabel = reader.getAttribute<const char*>("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)) {
|
|
auto obj = const_cast<App::DocumentObject*>(xlink->getValue());
|
|
if (_Deps.erase(obj)) {
|
|
_onBreakLink(xlink->getValue());
|
|
onRemoveDep(obj);
|
|
}
|
|
}
|
|
}
|
|
|
|
void PropertyXLinkContainer::onBreakLink(DocumentObject*)
|
|
{}
|
|
|
|
void PropertyXLinkContainer::_onBreakLink(DocumentObject* obj)
|
|
{
|
|
try {
|
|
onBreakLink(obj);
|
|
}
|
|
catch (Base::Exception& e) {
|
|
e.reportException();
|
|
FC_ERR("Exception on breaking link property " << getFullName());
|
|
}
|
|
catch (std::exception& e) {
|
|
FC_ERR("Exception on breaking link property " << getFullName() << ": " << e.what());
|
|
}
|
|
catch (...) {
|
|
FC_ERR("Exception on breaking link property " << getFullName());
|
|
}
|
|
}
|
|
|
|
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 = freecad_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;
|
|
}
|
|
|
|
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);
|
|
}
|
|
}
|
|
}
|
|
|
|
_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);
|
|
}
|
|
}
|
|
}
|