App/Gui: support Link refresh on change of originally linked configurable object
This commit is contained in:
677
src/App/Link.cpp
677
src/App/Link.cpp
@@ -22,14 +22,17 @@
|
||||
|
||||
#include "PreCompiled.h"
|
||||
|
||||
#include <boost/range.hpp>
|
||||
#include <boost/algorithm/string/predicate.hpp>
|
||||
#include <Base/Tools.h>
|
||||
#include <Base/Uuid.h>
|
||||
|
||||
#include "Application.h"
|
||||
#include "ComplexGeoData.h"
|
||||
#include "ComplexGeoDataPy.h"
|
||||
#include "Document.h"
|
||||
#include "GroupExtension.h"
|
||||
#include "DocumentObserver.h"
|
||||
#include "GeoFeatureGroupExtension.h"
|
||||
#include "Link.h"
|
||||
#include "LinkBaseExtensionPy.h"
|
||||
|
||||
@@ -45,6 +48,99 @@ using namespace App;
|
||||
using namespace Base;
|
||||
namespace bp = boost::placeholders;
|
||||
|
||||
typedef boost::iterator_range<const char*> CharRange;
|
||||
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/*[[[cog
|
||||
import LinkParams
|
||||
LinkParams.define()
|
||||
]]]*/
|
||||
|
||||
// Auto generated code. See class document of LinkParams.
|
||||
class LinkParamsP: public ParameterGrp::ObserverType {
|
||||
public:
|
||||
// Auto generated code. See class document of LinkParams.
|
||||
ParameterGrp::handle handle;
|
||||
|
||||
// Auto generated code. See class document of LinkParams.
|
||||
std::unordered_map<const char *,void(*)(LinkParamsP*),App::CStringHasher,App::CStringHasher> funcs;
|
||||
|
||||
bool CopyOnChangeApplyToAll; // Auto generated code. See class document of LinkParams.
|
||||
|
||||
// Auto generated code. See class document of LinkParams.
|
||||
LinkParamsP() {
|
||||
handle = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/Link");
|
||||
handle->Attach(this);
|
||||
|
||||
CopyOnChangeApplyToAll = handle->GetBool("CopyOnChangeApplyToAll", true);
|
||||
funcs["CopyOnChangeApplyToAll"] = &LinkParamsP::updateCopyOnChangeApplyToAll;
|
||||
}
|
||||
|
||||
// Auto generated code. See class document of LinkParams.
|
||||
~LinkParamsP() {
|
||||
}
|
||||
|
||||
// Auto generated code. See class document of LinkParams.
|
||||
void OnChange(Base::Subject<const char*> &, const char* sReason) {
|
||||
if(!sReason)
|
||||
return;
|
||||
auto it = funcs.find(sReason);
|
||||
if(it == funcs.end())
|
||||
return;
|
||||
it->second(this);
|
||||
}
|
||||
|
||||
|
||||
// Auto generated code. See class document of LinkParams.
|
||||
static void updateCopyOnChangeApplyToAll(LinkParamsP *self) {
|
||||
self->CopyOnChangeApplyToAll = self->handle->GetBool("CopyOnChangeApplyToAll", true);
|
||||
}
|
||||
};
|
||||
|
||||
// Auto generated code. See class document of LinkParams.
|
||||
LinkParamsP *instance() {
|
||||
static LinkParamsP *inst = new LinkParamsP;
|
||||
return inst;
|
||||
}
|
||||
|
||||
// Auto generated code. See class document of LinkParams.
|
||||
ParameterGrp::handle LinkParams::getHandle() {
|
||||
return instance()->handle;
|
||||
}
|
||||
|
||||
// Auto generated code. See class document of LinkParams.
|
||||
const char *LinkParams::docCopyOnChangeApplyToAll() {
|
||||
return QT_TRANSLATE_NOOP("LinkParams",
|
||||
"Stores the last user choice of whether to apply CopyOnChange setup to all link\n"
|
||||
"that links to the same configurable object");
|
||||
}
|
||||
|
||||
// Auto generated code. See class document of LinkParams.
|
||||
const bool & LinkParams::getCopyOnChangeApplyToAll() {
|
||||
return instance()->CopyOnChangeApplyToAll;
|
||||
}
|
||||
|
||||
// Auto generated code. See class document of LinkParams.
|
||||
const bool & LinkParams::defaultCopyOnChangeApplyToAll() {
|
||||
const static bool def = true;
|
||||
return def;
|
||||
}
|
||||
|
||||
// Auto generated code. See class document of LinkParams.
|
||||
void LinkParams::setCopyOnChangeApplyToAll(const bool &v) {
|
||||
instance()->handle->SetBool("CopyOnChangeApplyToAll",v);
|
||||
instance()->CopyOnChangeApplyToAll = v;
|
||||
}
|
||||
|
||||
// Auto generated code. See class document of LinkParams.
|
||||
void LinkParams::removeCopyOnChangeApplyToAll() {
|
||||
instance()->handle->RemoveBool("CopyOnChangeApplyToAll");
|
||||
}
|
||||
//[[[end]]]
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
EXTENSION_PROPERTY_SOURCE(App::LinkBaseExtension, App::DocumentObjectExtension)
|
||||
|
||||
LinkBaseExtension::LinkBaseExtension(void)
|
||||
@@ -130,18 +226,23 @@ void LinkBaseExtension::setProperty(int idx, Property *prop) {
|
||||
switch(idx) {
|
||||
case PropLinkMode: {
|
||||
static const char *linkModeEnums[] = {"None","Auto Delete","Auto Link","Auto Unlink",nullptr};
|
||||
auto propLinkMode = freecad_dynamic_cast<PropertyEnumeration>(prop);
|
||||
auto propLinkMode = static_cast<PropertyEnumeration*>(prop);
|
||||
if(!propLinkMode->getEnums())
|
||||
propLinkMode->setEnums(linkModeEnums);
|
||||
break;
|
||||
}
|
||||
case PropLinkCopyOnChange: {
|
||||
static const char *enums[] = {"Disabled","Enabled","Owned",nullptr};
|
||||
auto propEnum = freecad_dynamic_cast<PropertyEnumeration>(prop);
|
||||
static const char *enums[] = {"Disabled","Enabled","Owned","Tracking",nullptr};
|
||||
auto propEnum = static_cast<PropertyEnumeration*>(prop);
|
||||
if(!propEnum->getEnums())
|
||||
propEnum->setEnums(enums);
|
||||
break;
|
||||
}
|
||||
case PropLinkCopyOnChangeTouched:
|
||||
case PropLinkCopyOnChangeSource:
|
||||
case PropLinkCopyOnChangeGroup:
|
||||
prop->setStatus(Property::Hidden, true);
|
||||
break;
|
||||
case PropLinkTransform:
|
||||
case PropLinkPlacement:
|
||||
case PropPlacement:
|
||||
@@ -155,6 +256,7 @@ void LinkBaseExtension::setProperty(int idx, Property *prop) {
|
||||
}
|
||||
break;
|
||||
case PropElementList:
|
||||
getElementListProperty()->setScope(LinkScope::Global);
|
||||
getElementListProperty()->setStatus(Property::Hidden,true);
|
||||
// fall through
|
||||
case PropLinkedObject:
|
||||
@@ -187,33 +289,35 @@ void LinkBaseExtension::setProperty(int idx, Property *prop) {
|
||||
static const char _GroupPrefix[] = "Configuration (";
|
||||
|
||||
App::DocumentObjectExecReturn *LinkBaseExtension::extensionExecute(void) {
|
||||
// The actual value of LinkRecompouted is not important, just to notify view
|
||||
// The actual value of LinkTouched is not important, just to notify view
|
||||
// provider that the link (in fact, its dependents, i.e. linked ones) have
|
||||
// recomputed.
|
||||
_LinkTouched.touch();
|
||||
|
||||
if(getLinkedObjectProperty()) {
|
||||
DocumentObject *linked = getTrueLinkedObject(true);
|
||||
if(!linked)
|
||||
return new App::DocumentObjectExecReturn("Link broken");
|
||||
if(!linked) {
|
||||
std::ostringstream ss;
|
||||
ss << "Link broken!";
|
||||
auto xlink = Base::freecad_dynamic_cast<PropertyXLink>(
|
||||
getLinkedObjectProperty());
|
||||
if (xlink) {
|
||||
const char *objname = xlink->getObjectName();
|
||||
if (objname && objname[0])
|
||||
ss << "\nObject: " << objname;
|
||||
const char *filename = xlink->getFilePath();
|
||||
if (filename && filename[0])
|
||||
ss << "\nFile: " << filename;
|
||||
}
|
||||
return new App::DocumentObjectExecReturn(ss.str().c_str());
|
||||
}
|
||||
|
||||
App::DocumentObject *container = getContainer();
|
||||
setupCopyOnChange(container);
|
||||
|
||||
if(hasCopyOnChange && getLinkCopyOnChangeValue()==0) {
|
||||
hasCopyOnChange = false;
|
||||
std::vector<Property*> props;
|
||||
container->getPropertyList(props);
|
||||
for(auto prop : props) {
|
||||
if(isCopyOnChangeProperty(container, *prop)) {
|
||||
try {
|
||||
container->removeDynamicProperty(prop->getName());
|
||||
} catch (Base::Exception &e) {
|
||||
e.ReportException();
|
||||
} catch (...) {
|
||||
}
|
||||
}
|
||||
}
|
||||
auto source = getLinkCopyOnChangeSourceValue();
|
||||
if (source && getLinkCopyOnChangeValue() == CopyOnChangeTracking
|
||||
&& getLinkCopyOnChangeTouchedValue())
|
||||
{
|
||||
syncCopyOnChange();
|
||||
}
|
||||
|
||||
PropertyPythonObject *proxy = nullptr;
|
||||
@@ -266,6 +370,25 @@ App::DocumentObjectExecReturn *LinkBaseExtension::extensionExecute(void) {
|
||||
return new App::DocumentObjectExecReturn(errMsg);
|
||||
}
|
||||
}
|
||||
|
||||
auto parent = getContainer();
|
||||
setupCopyOnChange(parent);
|
||||
|
||||
if(hasCopyOnChange && getLinkCopyOnChangeValue()==CopyOnChangeDisabled) {
|
||||
hasCopyOnChange = false;
|
||||
std::vector<Property*> props;
|
||||
parent->getPropertyList(props);
|
||||
for(auto prop : props) {
|
||||
if(isCopyOnChangeProperty(parent, *prop)) {
|
||||
try {
|
||||
parent->removeDynamicProperty(prop->getName());
|
||||
} catch (Base::Exception &e) {
|
||||
e.ReportException();
|
||||
} catch (...) {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return inherited::extensionExecute();
|
||||
}
|
||||
@@ -276,6 +399,273 @@ short LinkBaseExtension::extensionMustExecute(void) {
|
||||
return link->mustExecute();
|
||||
}
|
||||
|
||||
std::vector<App::DocumentObject*>
|
||||
LinkBaseExtension::getOnChangeCopyObjects(
|
||||
std::vector<App::DocumentObject *> *excludes,
|
||||
App::DocumentObject *src)
|
||||
{
|
||||
auto parent = getContainer();
|
||||
if (!src)
|
||||
src = getLinkCopyOnChangeSourceValue();
|
||||
if (!src || getLinkCopyOnChangeValue() == CopyOnChangeDisabled)
|
||||
return {};
|
||||
|
||||
auto res = Document::getDependencyList({src}, Document::DepSort);
|
||||
for (auto it=res.begin(); it!=res.end();) {
|
||||
auto obj = *it;
|
||||
if (obj == src) {
|
||||
++it;
|
||||
continue;
|
||||
}
|
||||
auto prop = Base::freecad_dynamic_cast<PropertyMap>(
|
||||
obj->getPropertyByName("_CopyOnChangeControl"));
|
||||
static std::map<std::string, std::string> dummy;
|
||||
const auto & map = prop && prop->getContainer()==obj ? prop->getValues() : dummy;
|
||||
const char *v = "";
|
||||
if (src->getDocument() != obj->getDocument())
|
||||
v = "-";
|
||||
auto iter = map.find("*");
|
||||
if (iter != map.end())
|
||||
v = iter->second.c_str();
|
||||
else if ((iter = map.find(parent->getNameInDocument())) != map.end())
|
||||
v = iter->second.c_str();
|
||||
if (boost::equals(v, "-")) {
|
||||
if (excludes)
|
||||
excludes->push_back(obj);
|
||||
else {
|
||||
it = res.erase(it);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
++it;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
void LinkBaseExtension::setOnChangeCopyObject(
|
||||
App::DocumentObject *obj, OnChangeCopyOptions options)
|
||||
{
|
||||
auto parent = getContainer();
|
||||
Base::Flags<OnChangeCopyOptions> flags(options);
|
||||
bool exclude = flags.testFlag(OnChangeCopyOptions::Exclude);
|
||||
bool external = parent->getDocument() != obj->getDocument();
|
||||
auto prop = Base::freecad_dynamic_cast<PropertyMap>(
|
||||
obj->getPropertyByName("_CopyOnChangeControl"));
|
||||
|
||||
if (external == exclude && !prop)
|
||||
return;
|
||||
|
||||
prop = static_cast<PropertyMap*>(
|
||||
obj->addDynamicProperty("App::PropertyMap", "_CopyOnChangeControl"));
|
||||
if (!prop) {
|
||||
FC_ERR("Failed to setup copy on change object " << obj->getFullName());
|
||||
return;
|
||||
}
|
||||
|
||||
const char *key = flags.testFlag(OnChangeCopyOptions::ApplyAll) ? "*" : parent->getNameInDocument();
|
||||
if (external)
|
||||
prop->setValue(key, exclude ? nullptr : "+");
|
||||
else
|
||||
prop->setValue(key, exclude ? "-" : nullptr);
|
||||
}
|
||||
|
||||
// The purpose of this function is to synchronize the mutated copy to the
|
||||
// original linked CopyOnChange object. It will make a new copy if any of the
|
||||
// non-CopyOnChange property of the original object has changed.
|
||||
void LinkBaseExtension::syncCopyOnChange()
|
||||
{
|
||||
if (!getLinkCopyOnChangeValue())
|
||||
return;
|
||||
auto linkProp = getLinkedObjectProperty();
|
||||
auto srcProp = getLinkCopyOnChangeSourceProperty();
|
||||
auto srcTouched = getLinkCopyOnChangeTouchedProperty();
|
||||
if (!linkProp
|
||||
|| !srcProp
|
||||
|| !srcTouched
|
||||
|| !srcProp->getValue()
|
||||
|| !linkProp->getValue()
|
||||
|| srcProp->getValue() == linkProp->getValue())
|
||||
return;
|
||||
|
||||
auto parent = getContainer();
|
||||
|
||||
auto linked = linkProp->getValue();
|
||||
|
||||
std::vector<App::DocumentObjectT> oldObjs;
|
||||
std::vector<App::DocumentObject*> objs;
|
||||
|
||||
// CopyOnChangeGroup is a hidden dynamic property for holding a LinkGroup
|
||||
// for holding the mutated copy of the original linked object and all its
|
||||
// dependencies.
|
||||
LinkGroup *copyOnChangeGroup = nullptr;
|
||||
if (auto prop = getLinkCopyOnChangeGroupProperty()) {
|
||||
copyOnChangeGroup = Base::freecad_dynamic_cast<LinkGroup>(prop->getValue());
|
||||
if (!copyOnChangeGroup) {
|
||||
// Create the LinkGroup if not exist
|
||||
auto group = new LinkGroup;
|
||||
group->LinkMode.setValue(LinkModeAutoDelete);
|
||||
parent->getDocument()->addObject(group, "CopyOnChangeGroup");
|
||||
prop->setValue(group);
|
||||
} else {
|
||||
// If it exists, then obtain all copied objects. Note that we stores
|
||||
// the dynamic property _SourceUUID in oldObjs if possible, in order
|
||||
// to match the possible new copy later.
|
||||
objs = copyOnChangeGroup->ElementList.getValues();
|
||||
for (auto obj : objs) {
|
||||
if (!obj->getNameInDocument())
|
||||
continue;
|
||||
auto prop = Base::freecad_dynamic_cast<PropertyUUID>(
|
||||
obj->getPropertyByName("_SourceUUID"));
|
||||
if (prop && prop->getContainer() == obj)
|
||||
oldObjs.emplace_back(prop);
|
||||
else
|
||||
oldObjs.emplace_back(obj);
|
||||
}
|
||||
std::sort(objs.begin(), objs.end());
|
||||
}
|
||||
}
|
||||
|
||||
// Obtain the original linked object and its dependency in depending order.
|
||||
// The last being the original linked object.
|
||||
auto srcObjs = getOnChangeCopyObjects();
|
||||
// Refresh signal connection to monitor changes
|
||||
monitorOnChangeCopyObjects(srcObjs);
|
||||
|
||||
// Copy the objects. Document::export/importObjects() (called by
|
||||
// copyObject()) will generate a _ObjectUUID for each source object and
|
||||
// match it with a _SourceUUID in the copy.
|
||||
auto copiedObjs = parent->getDocument()->copyObject(srcObjs);
|
||||
if(copiedObjs.empty())
|
||||
return;
|
||||
|
||||
// copyObject() will return copy in order of the same order of the input,
|
||||
// so the last object will be the copy of the original linked object
|
||||
auto newLinked = copiedObjs.back();
|
||||
|
||||
// We are coping from the original linked object and we've already mutated
|
||||
// it, so we need to copy all CopyOnChange properties from the mutated
|
||||
// object to the new copy.
|
||||
std::vector<App::Property*> propList;
|
||||
linked->getPropertyList(propList);
|
||||
for (auto prop : propList) {
|
||||
if(!prop->testStatus(Property::CopyOnChange)
|
||||
|| prop->getContainer()!=linked)
|
||||
continue;
|
||||
auto p = newLinked->getPropertyByName(prop->getName());
|
||||
if (p && p->getTypeId() == prop->getTypeId()) {
|
||||
std::unique_ptr<Property> pCopy(prop->Copy());
|
||||
p->Paste(*pCopy);
|
||||
}
|
||||
}
|
||||
|
||||
if (copyOnChangeGroup) {
|
||||
// The order of the copied objects is in dependency order (because of
|
||||
// getOnChangeCopyObjects()). We reverse it here so that we can later
|
||||
// on delete it in reverse order to avoid error (because some parent
|
||||
// objects may want to delete their own children).
|
||||
std::reverse(copiedObjs.begin(), copiedObjs.end());
|
||||
copyOnChangeGroup->ElementList.setValues(copiedObjs);
|
||||
}
|
||||
|
||||
// Create a map to find the corresponding replacement of the new copies to
|
||||
// the mutated object. The reason for doing so is that we are copying from
|
||||
// the original linked object and its dependency, not the mutated objects
|
||||
// which are old copies. There could be arbitary changes in the originals
|
||||
// which may add or remove or change dependending orders, while the
|
||||
// replacement happen between the new and old copies.
|
||||
|
||||
std::map<Base::Uuid, App::DocumentObjectT> newObjs;
|
||||
for (auto obj : copiedObjs) {
|
||||
auto prop = Base::freecad_dynamic_cast<PropertyUUID>(
|
||||
obj->getPropertyByName("_SourceUUID"));
|
||||
if (prop)
|
||||
newObjs.insert(std::make_pair(prop->getValue(), obj));
|
||||
}
|
||||
|
||||
std::vector<std::pair<App::DocumentObject*, App::DocumentObject*> > replacements;
|
||||
for (const auto &objT : oldObjs) {
|
||||
auto prop = Base::freecad_dynamic_cast<PropertyUUID>(objT.getProperty());
|
||||
if (!prop)
|
||||
continue;
|
||||
auto it = newObjs.find(prop->getValue());
|
||||
if (it == newObjs.end())
|
||||
continue;
|
||||
auto oldObj = objT.getObject();
|
||||
auto newObj = it->second.getObject();
|
||||
if (oldObj && newObj)
|
||||
replacements.emplace_back(oldObj, newObj);
|
||||
}
|
||||
|
||||
std::vector<std::pair<App::DocumentObjectT, std::unique_ptr<App::Property> > > propChanges;
|
||||
if (!replacements.empty()) {
|
||||
std::sort(copiedObjs.begin(), copiedObjs.end());
|
||||
|
||||
// Global search for links affected by the replacement. We accumulate
|
||||
// the changes in propChanges without applying, in order to avoid any
|
||||
// side effect of changing while searching.
|
||||
for(auto doc : App::GetApplication().getDocuments()) {
|
||||
for(auto o : doc->getObjects()) {
|
||||
if (o == parent
|
||||
|| std::binary_search(objs.begin(), objs.end(), o)
|
||||
|| std::binary_search(copiedObjs.begin(), copiedObjs.end(), o))
|
||||
continue;
|
||||
propList.clear();
|
||||
o->getPropertyList(propList);
|
||||
for(auto prop : propList) {
|
||||
if (prop->getContainer() != o)
|
||||
continue;
|
||||
auto linkProp = Base::freecad_dynamic_cast<App::PropertyLinkBase>(prop);
|
||||
if(!linkProp)
|
||||
continue;
|
||||
for (const auto &v : replacements) {
|
||||
std::unique_ptr<App::Property> copy(
|
||||
linkProp->CopyOnLinkReplace(parent,v.first,v.second));
|
||||
if(!copy)
|
||||
continue;
|
||||
propChanges.emplace_back(App::DocumentObjectT(prop),std::move(copy));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Base::StateLocker guard(pauseCopyOnChange);
|
||||
linkProp->setValue(newLinked);
|
||||
newLinked->Visibility.setValue(false);
|
||||
srcTouched->setValue(false);
|
||||
|
||||
// Apply the global link changes.
|
||||
for(const auto &v : propChanges) {
|
||||
auto prop = v.first.getProperty();
|
||||
if(prop)
|
||||
prop->Paste(*v.second.get());
|
||||
}
|
||||
|
||||
// Finally, remove all old copies. oldObjs stores type of DocumentObjectT
|
||||
// which stores the object name. Before removing, we need to make sure the
|
||||
// object exists, and it is not some new object with the same name, by
|
||||
// checking objs which stores DocumentObject pointer.
|
||||
for (const auto &objT : oldObjs) {
|
||||
auto obj = objT.getObject();
|
||||
if (obj && std::binary_search(objs.begin(), objs.end(), obj))
|
||||
obj->getDocument()->removeObject(obj->getNameInDocument());
|
||||
}
|
||||
}
|
||||
|
||||
bool LinkBaseExtension::isLinkedToConfigurableObject() const
|
||||
{
|
||||
if (auto linked = getLinkedObjectValue()) {
|
||||
std::vector<App::Property*> propList;
|
||||
linked->getPropertyList(propList);
|
||||
for (auto prop : propList) {
|
||||
if(prop->testStatus(Property::CopyOnChange)
|
||||
&& prop->getContainer()==linked)
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool LinkBaseExtension::isCopyOnChangeProperty(DocumentObject *obj, const App::Property &prop) {
|
||||
if(obj!=prop.getContainer() || !prop.testStatus(App::Property::PropDynamic))
|
||||
return false;
|
||||
@@ -283,14 +673,30 @@ bool LinkBaseExtension::isCopyOnChangeProperty(DocumentObject *obj, const App::P
|
||||
return group && boost::starts_with(group,_GroupPrefix);
|
||||
}
|
||||
|
||||
void LinkBaseExtension::setupCopyOnChange(DocumentObject *parent) {
|
||||
void LinkBaseExtension::setupCopyOnChange(DocumentObject *parent, bool checkSource) {
|
||||
copyOnChangeConns.clear();
|
||||
copyOnChangeSrcConns.clear();
|
||||
|
||||
auto linked = getTrueLinkedObject(false);
|
||||
if(!linked || getLinkCopyOnChangeValue()==0)
|
||||
if(!linked || getLinkCopyOnChangeValue()==CopyOnChangeDisabled)
|
||||
return;
|
||||
|
||||
if (checkSource && !pauseCopyOnChange) {
|
||||
PropertyLink *source = getLinkCopyOnChangeSourceProperty();
|
||||
if (source) {
|
||||
source->setValue(linked);
|
||||
if (auto touched = getLinkCopyOnChangeTouchedProperty())
|
||||
touched->setValue(false);
|
||||
}
|
||||
}
|
||||
|
||||
hasCopyOnChange = setupCopyOnChange(parent,linked,©OnChangeConns,hasCopyOnChange);
|
||||
if (hasCopyOnChange && getLinkCopyOnChangeValue() == CopyOnChangeOwned
|
||||
&& getLinkedObjectValue()
|
||||
&& getLinkedObjectValue() == getLinkCopyOnChangeSourceValue())
|
||||
{
|
||||
makeCopyOnChange();
|
||||
}
|
||||
}
|
||||
|
||||
bool LinkBaseExtension::setupCopyOnChange(DocumentObject *parent, DocumentObject *linked,
|
||||
@@ -371,7 +777,7 @@ bool LinkBaseExtension::setupCopyOnChange(DocumentObject *parent, DocumentObject
|
||||
if(!copyOnChangeConns)
|
||||
return res;
|
||||
|
||||
for(auto &v : newProps) {
|
||||
for(const auto &v : newProps) {
|
||||
// sync configuration properties
|
||||
copyOnChangeConns->push_back(v.second->signalChanged.connect([parent](const Property &prop) {
|
||||
if(!prop.testStatus(Property::CopyOnChange))
|
||||
@@ -396,15 +802,19 @@ bool LinkBaseExtension::setupCopyOnChange(DocumentObject *parent, DocumentObject
|
||||
void LinkBaseExtension::checkCopyOnChange(
|
||||
App::DocumentObject *parent, const App::Property &prop)
|
||||
{
|
||||
if(parent->getDocument()->isPerformingTransaction())
|
||||
if(!parent || !parent->getDocument()
|
||||
|| parent->getDocument()->isPerformingTransaction())
|
||||
return;
|
||||
|
||||
auto linked = getTrueLinkedObject(false);
|
||||
if(!linked || getLinkCopyOnChangeValue()==0
|
||||
auto linked = getLinkedObjectValue();
|
||||
if(!linked || getLinkCopyOnChangeValue()==CopyOnChangeDisabled
|
||||
|| !isCopyOnChangeProperty(parent,prop))
|
||||
return;
|
||||
|
||||
if(getLinkCopyOnChangeValue()==2) {
|
||||
if(getLinkCopyOnChangeValue() == CopyOnChangeOwned ||
|
||||
(getLinkCopyOnChangeValue() == CopyOnChangeTracking
|
||||
&& linked != getLinkCopyOnChangeSourceValue()))
|
||||
{
|
||||
auto p = linked->getPropertyByName(prop.getName());
|
||||
if(p && p->getTypeId()==prop.getTypeId()) {
|
||||
std::unique_ptr<Property> pcopy(prop.Copy());
|
||||
@@ -418,21 +828,80 @@ void LinkBaseExtension::checkCopyOnChange(
|
||||
if(!linkedProp || linkedProp->getTypeId()!=prop.getTypeId() || linkedProp->isSame(prop))
|
||||
return;
|
||||
|
||||
auto objs = parent->getDocument()->copyObject({linked},true);
|
||||
auto copied = makeCopyOnChange();
|
||||
if (copied) {
|
||||
linkedProp = copied->getPropertyByName(prop.getName());
|
||||
if(linkedProp && linkedProp->getTypeId()==prop.getTypeId()) {
|
||||
std::unique_ptr<Property> pcopy(prop.Copy());
|
||||
if(pcopy)
|
||||
linkedProp->Paste(*pcopy);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
App::DocumentObject *LinkBaseExtension::makeCopyOnChange() {
|
||||
auto linked = getLinkedObjectValue();
|
||||
if (pauseCopyOnChange || !linked)
|
||||
return nullptr;
|
||||
auto parent = getContainer();
|
||||
auto srcobjs = getOnChangeCopyObjects(nullptr, linked);
|
||||
for (auto obj : srcobjs) {
|
||||
if (obj->testStatus(App::PartialObject)) {
|
||||
FC_THROWM(Base::RuntimeError, "Cannot copy partial loaded object: "
|
||||
<< obj->getFullName());
|
||||
}
|
||||
}
|
||||
auto objs = parent->getDocument()->copyObject(srcobjs);
|
||||
if(objs.empty())
|
||||
return;
|
||||
return nullptr;
|
||||
|
||||
monitorOnChangeCopyObjects(srcobjs);
|
||||
|
||||
linked = objs.back();
|
||||
linked->Visibility.setValue(false);
|
||||
linkedProp = linked->getPropertyByName(prop.getName());
|
||||
if(linkedProp && linkedProp->getTypeId()==prop.getTypeId()) {
|
||||
std::unique_ptr<Property> pcopy(prop.Copy());
|
||||
if(pcopy)
|
||||
linkedProp->Paste(*pcopy);
|
||||
}
|
||||
getLinkCopyOnChangeProperty()->setValue((long)0);
|
||||
|
||||
Base::StateLocker guard(pauseCopyOnChange);
|
||||
getLinkedObjectProperty()->setValue(linked);
|
||||
getLinkCopyOnChangeProperty()->setValue(2);
|
||||
if (getLinkCopyOnChangeValue() == CopyOnChangeEnabled)
|
||||
getLinkCopyOnChangeProperty()->setValue(CopyOnChangeOwned);
|
||||
|
||||
if (auto prop = getLinkCopyOnChangeGroupProperty()) {
|
||||
if (auto obj = prop->getValue()) {
|
||||
if (obj->getNameInDocument() && obj->getDocument())
|
||||
obj->getDocument()->removeObject(obj->getNameInDocument());
|
||||
}
|
||||
auto group = new LinkGroup;
|
||||
group->LinkMode.setValue(LinkModeAutoDelete);
|
||||
getContainer()->getDocument()->addObject(group, "CopyOnChangeGroup");
|
||||
prop->setValue(group);
|
||||
|
||||
// The order of the copied objects is in dependency order (because of
|
||||
// getOnChangeCopyObjects()). We reverse it here so that we can later
|
||||
// on delete it in reverse order to avoid error (because some parent
|
||||
// objects may want to delete their own children).
|
||||
std::reverse(objs.begin(), objs.end());
|
||||
group->ElementList.setValues(objs);
|
||||
}
|
||||
|
||||
return linked;
|
||||
}
|
||||
|
||||
void LinkBaseExtension::monitorOnChangeCopyObjects(
|
||||
const std::vector<App::DocumentObject*> &objs)
|
||||
{
|
||||
copyOnChangeSrcConns.clear();
|
||||
if (getLinkCopyOnChangeValue() == CopyOnChangeDisabled)
|
||||
return;
|
||||
for(auto obj : objs) {
|
||||
obj->setStatus(App::ObjectStatus::TouchOnColorChange, true);
|
||||
copyOnChangeSrcConns.push_back(obj->signalChanged.connect(
|
||||
[this](const DocumentObject &, const Property &) {
|
||||
if (auto prop = this->getLinkCopyOnChangeTouchedProperty()) {
|
||||
if (this->getLinkCopyOnChangeValue() != CopyOnChangeDisabled)
|
||||
prop->setValue(true);
|
||||
}
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
App::GroupExtension *LinkBaseExtension::linkedPlainGroup() const {
|
||||
@@ -492,6 +961,8 @@ bool LinkBaseExtension::extensionHasChildElement() const {
|
||||
if(_getElementListValue().size()
|
||||
|| (_getElementCountValue() && _getShowElementValue()))
|
||||
return true;
|
||||
if (getLinkClaimChildValue())
|
||||
return false;
|
||||
DocumentObject *linked = getTrueLinkedObject(false);
|
||||
if(linked) {
|
||||
if(linked->hasChildElement())
|
||||
@@ -560,7 +1031,8 @@ DocumentObject *LinkBaseExtension::getContainer(){
|
||||
}
|
||||
|
||||
DocumentObject *LinkBaseExtension::getLink(int depth) const{
|
||||
GetApplication().checkLinkDepth(depth,false);
|
||||
if (!GetApplication().checkLinkDepth(depth,true))
|
||||
return nullptr;
|
||||
if(getLinkedObjectProperty())
|
||||
return getLinkedObjectValue();
|
||||
return nullptr;
|
||||
@@ -628,15 +1100,18 @@ int LinkBaseExtension::getElementIndex(const char *subname, const char **psubnam
|
||||
// redirect that reference to the first array element
|
||||
auto linked = getTrueLinkedObject(false);
|
||||
if(!linked || !linked->getNameInDocument()) return -1;
|
||||
std::string sub(subname,dot-subname);
|
||||
if(subname[0]=='$') {
|
||||
if(strcmp(sub.c_str()+1,linked->Label.getValue())==0)
|
||||
CharRange sub(subname+1, dot);
|
||||
if (boost::equals(sub, linked->Label.getValue()))
|
||||
idx = 0;
|
||||
}else if(sub==linked->getNameInDocument())
|
||||
idx = 0;
|
||||
} else {
|
||||
CharRange sub(subname, dot);
|
||||
if (boost::equals(sub, linked->getNameInDocument()))
|
||||
idx = 0;
|
||||
}
|
||||
if(idx<0) {
|
||||
// Lastly, try to get sub object directly from the linked object
|
||||
auto sobj = linked->getSubObject(sub.c_str());
|
||||
auto sobj = linked->getSubObject(std::string(subname, dot-subname+1).c_str());
|
||||
if(!sobj)
|
||||
return -1;
|
||||
if(psubname)
|
||||
@@ -849,8 +1324,44 @@ bool LinkBaseExtension::extensionGetSubObject(DocumentObject *&ret, const char *
|
||||
return true;
|
||||
|
||||
Base::Matrix4D matNext;
|
||||
if(mat) matNext = *mat;
|
||||
ret = linked->getSubObject(subname,pyObj,mat?&matNext:nullptr,false,depth+1);
|
||||
|
||||
// Because of the addition of LinkClaimChild, the linked object may be
|
||||
// claimed as the first child. Regardless of the current value of
|
||||
// LinkClaimChild, we must accept sub-object path that contains the linked
|
||||
// object, because other link property may store such reference.
|
||||
if (const char* dot=strchr(subname,'.')) {
|
||||
auto group = getLinkCopyOnChangeGroupValue();
|
||||
if (subname[0] == '$') {
|
||||
CharRange sub(subname+1,dot);
|
||||
if (group && boost::equals(sub, group->Label.getValue()))
|
||||
linked = group;
|
||||
else if(!boost::equals(sub, linked->Label.getValue()))
|
||||
dot = nullptr;
|
||||
} else {
|
||||
CharRange sub(subname,dot);
|
||||
if (group && boost::equals(sub, group->getNameInDocument()))
|
||||
linked = group;
|
||||
else if (!boost::equals(sub, linked->getNameInDocument()))
|
||||
dot = nullptr;
|
||||
}
|
||||
if (dot) {
|
||||
// Because of external linked object, It is possible for and
|
||||
// child object to have the exact same internal name or label
|
||||
// as the parent object. To resolve this potential ambiguity,
|
||||
// try assuming the current subname is referring to the parent
|
||||
// (i.e. the linked object), and if it fails, try again below.
|
||||
if(mat) matNext = *mat;
|
||||
ret = linked->getSubObject(dot+1,pyObj,mat?&matNext:nullptr,false,depth+1);
|
||||
if (ret && dot[1])
|
||||
subname = dot+1;
|
||||
}
|
||||
}
|
||||
|
||||
if (!ret) {
|
||||
if(mat) matNext = *mat;
|
||||
ret = linked->getSubObject(subname,pyObj,mat?&matNext:nullptr,false,depth+1);
|
||||
}
|
||||
|
||||
std::string postfix;
|
||||
if(ret) {
|
||||
// do not resolve the link if we are the last referenced object
|
||||
@@ -892,10 +1403,11 @@ void LinkBaseExtension::checkGeoElementMap(const App::DocumentObject *obj,
|
||||
void LinkBaseExtension::onExtendedUnsetupObject() {
|
||||
if(!getElementListProperty())
|
||||
return;
|
||||
auto objs = getElementListValue();
|
||||
getElementListProperty()->setValue();
|
||||
for(auto obj : objs)
|
||||
detachElement(obj);
|
||||
detachElements();
|
||||
if (auto obj = getLinkCopyOnChangeGroupValue()) {
|
||||
if(obj->getNameInDocument() && !obj->isRemoving())
|
||||
obj->getDocument()->removeObject(obj->getNameInDocument());
|
||||
}
|
||||
}
|
||||
|
||||
DocumentObject *LinkBaseExtension::getTrueLinkedObject(
|
||||
@@ -1281,13 +1793,32 @@ void LinkBaseExtension::update(App::DocumentObject *parent, const Property *prop
|
||||
parseSubName();
|
||||
syncElementList();
|
||||
|
||||
if(getLinkCopyOnChangeValue()==2)
|
||||
getLinkCopyOnChangeProperty()->setValue(1);
|
||||
if(getLinkCopyOnChangeValue()==CopyOnChangeOwned
|
||||
&& !pauseCopyOnChange
|
||||
&& !parent->getDocument()->isPerformingTransaction())
|
||||
getLinkCopyOnChangeProperty()->setValue(CopyOnChangeEnabled);
|
||||
else
|
||||
setupCopyOnChange(parent);
|
||||
setupCopyOnChange(parent, true);
|
||||
|
||||
}else if(prop == getLinkCopyOnChangeProperty()) {
|
||||
setupCopyOnChange(parent);
|
||||
setupCopyOnChange(parent, getLinkCopyOnChangeSourceValue() == nullptr);
|
||||
} else if (prop == getLinkCopyOnChangeSourceProperty()) {
|
||||
if (auto source = getLinkCopyOnChangeSourceValue()) {
|
||||
this->connCopyOnChangeSource = source->signalChanged.connect(
|
||||
[this](const DocumentObject & obj, const Property &prop) {
|
||||
auto src = getLinkCopyOnChangeSourceValue();
|
||||
if (src != &obj || getLinkCopyOnChangeValue()==CopyOnChangeDisabled)
|
||||
return;
|
||||
if (App::Document::isAnyRestoring()
|
||||
|| obj.testStatus(ObjectStatus::NoTouch)
|
||||
|| (prop.getType() & Prop_Output)
|
||||
|| prop.testStatus(Property::Output))
|
||||
return;
|
||||
if (auto propTouch = getLinkCopyOnChangeTouchedProperty())
|
||||
propTouch->setValue(true);
|
||||
});
|
||||
} else
|
||||
this->connCopyOnChangeSource.disconnect();
|
||||
|
||||
}else if(prop == getLinkTransformProperty()) {
|
||||
auto linkPlacement = getLinkPlacementProperty();
|
||||
@@ -1396,7 +1927,7 @@ void LinkBaseExtension::onExtendedDocumentRestored() {
|
||||
sub.resize(element - sub.c_str());
|
||||
}
|
||||
std::vector<std::string> subs;
|
||||
for(auto &s : subset)
|
||||
for(const auto &s : subset)
|
||||
subs.push_back(sub + s);
|
||||
xlink->setSubValues(std::move(subs));
|
||||
}
|
||||
@@ -1409,8 +1940,14 @@ void LinkBaseExtension::onExtendedDocumentRestored() {
|
||||
getScaleVectorProperty()->setValue(s,s,s);
|
||||
}
|
||||
update(parent,getVisibilityListProperty());
|
||||
update(parent,getLinkedObjectProperty());
|
||||
if (auto prop = getLinkedObjectProperty()) {
|
||||
Base::StateLocker guard(pauseCopyOnChange);
|
||||
update(parent,prop);
|
||||
}
|
||||
update(parent,getLinkCopyOnChangeSourceProperty());
|
||||
update(parent,getElementListProperty());
|
||||
if (getLinkCopyOnChangeValue() != CopyOnChangeDisabled)
|
||||
monitorOnChangeCopyObjects(getOnChangeCopyObjects());
|
||||
}
|
||||
|
||||
void LinkBaseExtension::_handleChangedPropertyName(
|
||||
@@ -1515,11 +2052,7 @@ void LinkBaseExtension::setLink(int index, DocumentObject *obj,
|
||||
|
||||
if(obj || !getElementListProperty())
|
||||
LINK_THROW(Base::RuntimeError,"No PropertyLink or PropertyLinkList configured");
|
||||
|
||||
auto objs = getElementListValue();
|
||||
getElementListProperty()->setValue();
|
||||
for(auto thisObj : objs)
|
||||
detachElement(thisObj);
|
||||
detachElements();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1554,6 +2087,16 @@ void LinkBaseExtension::setLink(int index, DocumentObject *obj,
|
||||
xlink->setValue(obj,std::move(subs));
|
||||
}
|
||||
|
||||
void LinkBaseExtension::detachElements()
|
||||
{
|
||||
std::vector<App::DocumentObjectT> objs;
|
||||
for (auto obj : getElementListValue())
|
||||
objs.push_back(obj);
|
||||
getElementListProperty()->setValue();
|
||||
for(const auto &objT : objs)
|
||||
detachElement(objT.getObject());
|
||||
}
|
||||
|
||||
void LinkBaseExtension::detachElement(DocumentObject *obj) {
|
||||
if(!obj || !obj->getNameInDocument() || obj->isRemoving())
|
||||
return;
|
||||
@@ -1642,6 +2185,14 @@ Property *LinkBaseExtension::extensionGetPropertyByName(const char* name) const
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool LinkBaseExtension::isLinkMutated() const
|
||||
{
|
||||
return getLinkCopyOnChangeValue() != CopyOnChangeDisabled
|
||||
&& getLinkedObjectValue()
|
||||
&& (!getLinkCopyOnChangeSourceValue()
|
||||
|| (getLinkedObjectValue() != getLinkCopyOnChangeSourceValue()));
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
namespace App {
|
||||
|
||||
Reference in New Issue
Block a user