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 {
|
||||
|
||||
136
src/App/Link.h
136
src/App/Link.h
@@ -24,7 +24,8 @@
|
||||
#define APP_LINK_H
|
||||
|
||||
#include <unordered_set>
|
||||
|
||||
#include <Base/Parameter.h>
|
||||
#include <Base/Bitmask.h>
|
||||
#include "DocumentObject.h"
|
||||
#include "DocumentObjectExtension.h"
|
||||
#include "FeaturePython.h"
|
||||
@@ -89,6 +90,10 @@ public:
|
||||
(LinkTransform, bool, App::PropertyBool, false, \
|
||||
"Set to false to override linked object's placement", ##__VA_ARGS__)
|
||||
|
||||
#define LINK_PARAM_CLAIM_CHILD(...) \
|
||||
(LinkClaimChild, bool, App::PropertyBool, false, \
|
||||
"Claim the linked object as a child", ##__VA_ARGS__)
|
||||
|
||||
#define LINK_PARAM_COPY_ON_CHANGE(...) \
|
||||
(LinkCopyOnChange, long, App::PropertyEnumeration, ((long)0), \
|
||||
"Disabled: disable copy on change\n"\
|
||||
@@ -97,6 +102,16 @@ public:
|
||||
" the link will try to sync any change of the original linked object back to the copy.",\
|
||||
##__VA_ARGS__)
|
||||
|
||||
#define LINK_PARAM_COPY_ON_CHANGE_SOURCE(...) \
|
||||
(LinkCopyOnChangeSource, App::DocumentObject*, App::PropertyLink, 0, "The copy on change source object", ##__VA_ARGS__)
|
||||
|
||||
#define LINK_PARAM_COPY_ON_CHANGE_GROUP(...) \
|
||||
(LinkCopyOnChangeGroup, App::DocumentObject*, App::PropertyLink, 0, \
|
||||
"Linked to a internal group object for holding on change copies", ##__VA_ARGS__)
|
||||
|
||||
#define LINK_PARAM_COPY_ON_CHANGE_TOUCHED(...) \
|
||||
(LinkCopyOnChangeTouched, bool, App::PropertyBool, 0, "Indicating the copy on change source object has been changed", ##__VA_ARGS__)
|
||||
|
||||
#define LINK_PARAM_SCALE(...) \
|
||||
(Scale, double, App::PropertyFloat, 1.0, "Scale factor", ##__VA_ARGS__)
|
||||
|
||||
@@ -151,6 +166,7 @@ public:
|
||||
LINK_PARAM(PLACEMENT)\
|
||||
LINK_PARAM(LINK_PLACEMENT)\
|
||||
LINK_PARAM(OBJECT)\
|
||||
LINK_PARAM(CLAIM_CHILD)\
|
||||
LINK_PARAM(TRANSFORM)\
|
||||
LINK_PARAM(SCALE)\
|
||||
LINK_PARAM(SCALE_VECTOR)\
|
||||
@@ -164,6 +180,9 @@ public:
|
||||
LINK_PARAM(LINK_EXECUTE)\
|
||||
LINK_PARAM(COLORED_ELEMENTS)\
|
||||
LINK_PARAM(COPY_ON_CHANGE)\
|
||||
LINK_PARAM(COPY_ON_CHANGE_SOURCE)\
|
||||
LINK_PARAM(COPY_ON_CHANGE_GROUP)\
|
||||
LINK_PARAM(COPY_ON_CHANGE_TOUCHED)\
|
||||
|
||||
enum PropIndex {
|
||||
#define LINK_PINDEX_DEFINE(_1,_2,_param) LINK_PINDEX(_param),
|
||||
@@ -201,6 +220,13 @@ public:
|
||||
typedef std::map<std::string, PropInfo> PropInfoMap;
|
||||
virtual const PropInfoMap &getPropertyInfoMap() const;
|
||||
|
||||
enum LinkCopyOnChangeType {
|
||||
CopyOnChangeDisabled = 0,
|
||||
CopyOnChangeEnabled = 1,
|
||||
CopyOnChangeOwned = 2,
|
||||
CopyOnChangeTracking = 3
|
||||
};
|
||||
|
||||
#define LINK_PROP_GET(_1,_2,_param) \
|
||||
LINK_PTYPE(_param) BOOST_PP_SEQ_CAT((get)(LINK_PNAME(_param))(Value)) () const {\
|
||||
auto prop = props[LINK_PINDEX(_param)];\
|
||||
@@ -300,15 +326,46 @@ public:
|
||||
|
||||
static bool isCopyOnChangeProperty(App::DocumentObject *obj, const Property &prop);
|
||||
|
||||
void syncCopyOnChange();
|
||||
|
||||
/** Options used in setOnChangeCopyObject()
|
||||
* Multiple options can be combined by bitwise or operator
|
||||
*/
|
||||
enum class OnChangeCopyOptions {
|
||||
/// If set, then exclude the input from object list to copy on change, or else, include the input object.
|
||||
Exclude = 1,
|
||||
/// If set , then apply the setting to all links to the input object, or else, apply only to this link.
|
||||
ApplyAll = 2,
|
||||
};
|
||||
|
||||
/** Include or exclude object from list of objects to copy on change
|
||||
* @param obj: input object
|
||||
* @param options: control options. @sa OnChangeCopyOptions.
|
||||
*/
|
||||
void setOnChangeCopyObject(App::DocumentObject *obj, OnChangeCopyOptions options);
|
||||
|
||||
std::vector<App::DocumentObject *> getOnChangeCopyObjects(
|
||||
std::vector<App::DocumentObject *> *excludes = nullptr,
|
||||
App::DocumentObject *src = nullptr);
|
||||
|
||||
bool isLinkedToConfigurableObject() const;
|
||||
|
||||
void monitorOnChangeCopyObjects(const std::vector<App::DocumentObject*> &objs);
|
||||
|
||||
/// Check if the linked object is a copy on change
|
||||
bool isLinkMutated() const;
|
||||
|
||||
protected:
|
||||
void _handleChangedPropertyName(Base::XMLReader &reader,
|
||||
const char * TypeName, const char *PropName);
|
||||
void parseSubName() const;
|
||||
void update(App::DocumentObject *parent, const Property *prop);
|
||||
void checkCopyOnChange(App::DocumentObject *parent, const App::Property &prop);
|
||||
void setupCopyOnChange(App::DocumentObject *parent);
|
||||
void setupCopyOnChange(App::DocumentObject *parent, bool checkSource = false);
|
||||
App::DocumentObject *makeCopyOnChange();
|
||||
void syncElementList();
|
||||
void detachElement(App::DocumentObject *obj);
|
||||
void detachElements();
|
||||
void checkGeoElementMap(const App::DocumentObject *obj,
|
||||
const App::DocumentObject *linked, PyObject **pyObj, const char *postfix) const;
|
||||
void updateGroup();
|
||||
@@ -329,10 +386,14 @@ protected:
|
||||
mutable bool enableLabelCache;
|
||||
bool hasOldSubElement;
|
||||
|
||||
mutable bool checkingProperty = false;
|
||||
|
||||
std::vector<boost::signals2::scoped_connection> copyOnChangeConns;
|
||||
std::vector<boost::signals2::scoped_connection> copyOnChangeSrcConns;
|
||||
bool hasCopyOnChange;
|
||||
|
||||
mutable bool checkingProperty = false;
|
||||
bool pauseCopyOnChange = false;
|
||||
|
||||
boost::signals2::scoped_connection connCopyOnChangeSource;
|
||||
};
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
@@ -460,6 +521,7 @@ public:
|
||||
|
||||
#define LINK_PARAMS_LINK \
|
||||
LINK_PARAM_EXT_TYPE(OBJECT, App::PropertyXLink)\
|
||||
LINK_PARAM_EXT(CLAIM_CHILD)\
|
||||
LINK_PARAM_EXT(TRANSFORM)\
|
||||
LINK_PARAM_EXT(LINK_PLACEMENT)\
|
||||
LINK_PARAM_EXT(PLACEMENT)\
|
||||
@@ -468,6 +530,9 @@ public:
|
||||
LINK_PARAM_EXT(LINK_EXECUTE)\
|
||||
LINK_PARAM_EXT_ATYPE(COLORED_ELEMENTS,App::Prop_Hidden)\
|
||||
LINK_PARAM_EXT(COPY_ON_CHANGE)\
|
||||
LINK_PARAM_EXT_TYPE(COPY_ON_CHANGE_SOURCE, App::PropertyXLink)\
|
||||
LINK_PARAM_EXT(COPY_ON_CHANGE_GROUP)\
|
||||
LINK_PARAM_EXT(COPY_ON_CHANGE_TOUCHED)\
|
||||
|
||||
LINK_PROPS_DEFINE(LINK_PARAMS_LINK)
|
||||
|
||||
@@ -508,6 +573,9 @@ public:
|
||||
LINK_PARAM_EXT(LINK_PLACEMENT)\
|
||||
LINK_PARAM_EXT(PLACEMENT)\
|
||||
LINK_PARAM_EXT(COPY_ON_CHANGE)\
|
||||
LINK_PARAM_EXT_TYPE(COPY_ON_CHANGE_SOURCE, App::PropertyXLink)\
|
||||
LINK_PARAM_EXT(COPY_ON_CHANGE_GROUP)\
|
||||
LINK_PARAM_EXT(COPY_ON_CHANGE_TOUCHED)\
|
||||
|
||||
// defines the actual properties
|
||||
LINK_PROPS_DEFINE(LINK_PARAMS_ELEMENT)
|
||||
@@ -566,6 +634,66 @@ typedef App::FeaturePythonT<LinkGroup> LinkGroupPython;
|
||||
|
||||
} //namespace App
|
||||
|
||||
ENABLE_BITMASK_OPERATORS(App::Link::OnChangeCopyOptions)
|
||||
|
||||
/*[[[cog
|
||||
import LinkParams
|
||||
LinkParams.declare()
|
||||
]]]*/
|
||||
|
||||
namespace App {
|
||||
/** Convenient class to obtain App::Link related parameters
|
||||
|
||||
* The parameters are under group "User parameter:BaseApp/Preferences/Link"
|
||||
*
|
||||
* This class is auto generated by LinkParams.py. Modify that file
|
||||
* instead of this one, if you want to add any parameter. You need
|
||||
* to install Cog Python package for code generation:
|
||||
* @code
|
||||
* pip install cogapp
|
||||
* @endcode
|
||||
*
|
||||
* Once modified, you can regenerate the header and the source file,
|
||||
* @code
|
||||
* python3 -m cogapp -r Link.h Link.cpp
|
||||
* @endcode
|
||||
*
|
||||
* You can add a new parameter by adding lines in LinkParams.py. Available
|
||||
* parameter types are 'Int, UInt, String, Bool, Float'. For example, to add
|
||||
* a new Int type parameter,
|
||||
* @code
|
||||
* ParamInt(parameter_name, default_value, documentation, on_change=False)
|
||||
* @endcode
|
||||
*
|
||||
* If there is special handling on parameter change, pass in on_change=True.
|
||||
* And you need to provide a function implementation in Link.cpp with
|
||||
* the following signature.
|
||||
* @code
|
||||
* void LinkParams:on<parameter_name>Changed()
|
||||
* @endcode
|
||||
*/
|
||||
class AppExport LinkParams {
|
||||
public:
|
||||
static ParameterGrp::handle getHandle();
|
||||
|
||||
//@{
|
||||
/** Accessor for parameter CopyOnChangeApplyToAll
|
||||
*
|
||||
* Stores the last user choice of whether to apply CopyOnChange setup to all link
|
||||
* that links to the same configurable object
|
||||
*/
|
||||
static const bool & getCopyOnChangeApplyToAll();
|
||||
static const bool & defaultCopyOnChangeApplyToAll();
|
||||
static void removeCopyOnChangeApplyToAll();
|
||||
static void setCopyOnChangeApplyToAll(const bool &v);
|
||||
static const char *docCopyOnChangeApplyToAll();
|
||||
//@}
|
||||
|
||||
};
|
||||
|
||||
} // namespace App
|
||||
//[[[end]]]
|
||||
|
||||
|
||||
#if defined(__clang__)
|
||||
# pragma clang diagnostic pop
|
||||
|
||||
30
src/App/LinkParams.py
Normal file
30
src/App/LinkParams.py
Normal file
@@ -0,0 +1,30 @@
|
||||
import sys
|
||||
from os import sys, path
|
||||
|
||||
# Actual code generation is done in Base/param_utils.py.
|
||||
|
||||
# The following code is to import param_util.py without needing __init__.py in Base directory
|
||||
sys.path.append(path.join(path.dirname(path.dirname(path.abspath(__file__))), 'Base'))
|
||||
import params_utils
|
||||
|
||||
from params_utils import ParamBool, ParamInt, ParamString, ParamUInt, ParamFloat
|
||||
|
||||
NameSpace = 'App'
|
||||
ClassName = 'LinkParams'
|
||||
ParamPath = 'User parameter:BaseApp/Preferences/Link'
|
||||
ClassDoc = 'Convenient class to obtain App::Link related parameters'
|
||||
HeaderFile = 'Link.h'
|
||||
SourceFile = 'Link.cpp'
|
||||
|
||||
Params = [
|
||||
ParamBool('CopyOnChangeApplyToAll', True, '''\
|
||||
Stores the last user choice of whether to apply CopyOnChange setup to all link
|
||||
that links to the same configurable object'''),
|
||||
]
|
||||
|
||||
def declare():
|
||||
params_utils.declare_begin(sys.modules[__name__], header=False)
|
||||
params_utils.declare_end(sys.modules[__name__])
|
||||
|
||||
def define():
|
||||
params_utils.define(sys.modules[__name__], header=False)
|
||||
@@ -45,6 +45,7 @@
|
||||
#include <QMenu>
|
||||
#endif
|
||||
|
||||
#include <boost/range.hpp>
|
||||
#include <App/ComplexGeoData.h>
|
||||
#include <App/Document.h>
|
||||
#include <Base/BoundBoxPy.h>
|
||||
@@ -67,12 +68,15 @@
|
||||
#include "ViewParams.h"
|
||||
#include "ViewProviderGeometryObject.h"
|
||||
|
||||
#include "ActionFunction.h"
|
||||
#include "Command.h"
|
||||
|
||||
FC_LOG_LEVEL_INIT("App::Link", true, true)
|
||||
|
||||
using namespace Gui;
|
||||
using namespace Base;
|
||||
|
||||
typedef boost::iterator_range<const char*> CharRange;
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
static inline bool appendPathSafe(SoPath *path, SoNode *node) {
|
||||
@@ -218,6 +222,10 @@ public:
|
||||
return pcLinked->getObject()->getNameInDocument();
|
||||
}
|
||||
|
||||
const char *getLinkedLabel() const {
|
||||
return pcLinked->getObject()->Label.getValue();
|
||||
}
|
||||
|
||||
const char *getLinkedNameSafe() const {
|
||||
if(isLinked())
|
||||
return getLinkedName();
|
||||
@@ -965,7 +973,7 @@ void LinkView::renderDoubleSide(bool enable) {
|
||||
if(enable) {
|
||||
if(!pcShapeHints) {
|
||||
pcShapeHints = new SoShapeHints;
|
||||
pcShapeHints->vertexOrdering = SoShapeHints::UNKNOWN_ORDERING;
|
||||
pcShapeHints->vertexOrdering = SoShapeHints::CLOCKWISE;
|
||||
pcShapeHints->shapeType = SoShapeHints::UNKNOWN_SHAPE_TYPE;
|
||||
pcLinkRoot->insertChild(pcShapeHints,0);
|
||||
}
|
||||
@@ -1485,14 +1493,53 @@ bool LinkView::linkGetDetailPath(const char *subname, SoFullPath *path, SoDetail
|
||||
if(!subname || *subname==0) return true;
|
||||
auto len = path->getLength();
|
||||
if(nodeArray.empty()) {
|
||||
appendPath(path,pcLinkRoot);
|
||||
}else{
|
||||
int idx = App::LinkBaseExtension::getArrayIndex(subname,&subname);
|
||||
if(!appendPathSafe(path,pcLinkRoot))
|
||||
return false;
|
||||
} else {
|
||||
int idx = -1;
|
||||
if (subname[0]>='0' && subname[0]<='9') {
|
||||
idx = App::LinkBaseExtension::getArrayIndex(subname,&subname);
|
||||
} else {
|
||||
while(1) {
|
||||
const char *dot = strchr(subname,'.');
|
||||
if(!dot)
|
||||
break;
|
||||
int i = 0;
|
||||
if (subname[0] == '$') {
|
||||
CharRange name(subname+1,dot);
|
||||
for(auto &info : nodeArray) {
|
||||
if(info->isLinked() && boost::equals(name,info->linkInfo->getLinkedLabel())) {
|
||||
idx = i;
|
||||
break;
|
||||
}
|
||||
++i;
|
||||
}
|
||||
} else {
|
||||
CharRange name(subname,dot);
|
||||
for(auto &info : nodeArray) {
|
||||
if(info->isLinked() && boost::equals(name,info->linkInfo->getLinkedName())) {
|
||||
idx = i;
|
||||
break;
|
||||
}
|
||||
++i;
|
||||
}
|
||||
}
|
||||
|
||||
if(idx<0)
|
||||
return false;
|
||||
|
||||
subname = dot+1;
|
||||
if(!subname[0] || nodeArray[idx]->isGroup==0)
|
||||
break;
|
||||
idx = -1;
|
||||
}
|
||||
}
|
||||
|
||||
if(idx<0 || idx>=(int)nodeArray.size())
|
||||
return false;
|
||||
|
||||
auto &info = *nodeArray[idx];
|
||||
appendPath(path,pcLinkRoot);
|
||||
if(!appendPathSafe(path,pcLinkRoot))
|
||||
return false;
|
||||
if(info.groupIndex>=0 && !getGroupHierarchy(info.groupIndex,path))
|
||||
return false;
|
||||
appendPath(path,info.pcSwitch);
|
||||
@@ -1561,7 +1608,7 @@ void LinkView::unlink(LinkInfoPtr info) {
|
||||
else {
|
||||
for(auto &info : nodeArray) {
|
||||
int idx;
|
||||
if(!info->isLinked() &&
|
||||
if(info->isLinked() &&
|
||||
(idx=info->pcRoot->findChild(pcLinkedRoot))>=0)
|
||||
info->pcRoot->removeChild(idx);
|
||||
}
|
||||
@@ -1792,6 +1839,10 @@ void ViewProviderLink::updateData(const App::Property *prop) {
|
||||
return inherited::updateData(prop);
|
||||
}
|
||||
|
||||
static inline bool canScale(const Base::Vector3d &v) {
|
||||
return fabs(v.x)>1e-7 && fabs(v.y)>1e-7 && fabs(v.z)>1e-7;
|
||||
}
|
||||
|
||||
void ViewProviderLink::updateDataPrivate(App::LinkBaseExtension *ext, const App::Property *prop) {
|
||||
if(!prop) return;
|
||||
if(prop == &ext->_ChildCache) {
|
||||
@@ -1807,8 +1858,10 @@ void ViewProviderLink::updateDataPrivate(App::LinkBaseExtension *ext, const App:
|
||||
}else if(prop==ext->getScaleProperty() || prop==ext->getScaleVectorProperty()) {
|
||||
if(!prop->testStatus(App::Property::User3)) {
|
||||
const auto &v = ext->getScaleVector();
|
||||
pcTransform->scaleFactor.setValue(v.x,v.y,v.z);
|
||||
linkView->renderDoubleSide(v.x*v.y*v.z < 0);
|
||||
if(canScale(v))
|
||||
pcTransform->scaleFactor.setValue(v.x,v.y,v.z);
|
||||
SbMatrix matrix = convert(ext->getTransform(false));
|
||||
linkView->renderDoubleSide(matrix.det3() < 0);
|
||||
}
|
||||
}else if(prop == ext->getPlacementProperty() || prop == ext->getLinkPlacementProperty()) {
|
||||
auto propLinkPlacement = ext->getLinkPlacementProperty();
|
||||
@@ -1816,8 +1869,19 @@ void ViewProviderLink::updateDataPrivate(App::LinkBaseExtension *ext, const App:
|
||||
const auto &pla = static_cast<const App::PropertyPlacement*>(prop)->getValue();
|
||||
ViewProviderGeometryObject::updateTransform(pla, pcTransform);
|
||||
const auto &v = ext->getScaleVector();
|
||||
pcTransform->scaleFactor.setValue(v.x,v.y,v.z);
|
||||
linkView->renderDoubleSide(v.x*v.y*v.z < 0);
|
||||
if(canScale(v))
|
||||
pcTransform->scaleFactor.setValue(v.x,v.y,v.z);
|
||||
SbMatrix matrix = convert(ext->getTransform(false));
|
||||
linkView->renderDoubleSide(matrix.det3() < 0);
|
||||
}
|
||||
}else if(prop == ext->getLinkCopyOnChangeGroupProperty()) {
|
||||
if (auto group = ext->getLinkCopyOnChangeGroupValue()) {
|
||||
auto vp = Base::freecad_dynamic_cast<ViewProviderDocumentObject>(
|
||||
Application::Instance->getViewProvider(group));
|
||||
if (vp) {
|
||||
vp->hide();
|
||||
vp->ShowInTree.setValue(false);
|
||||
}
|
||||
}
|
||||
}else if(prop == ext->getLinkedObjectProperty()) {
|
||||
|
||||
@@ -1918,9 +1982,9 @@ void ViewProviderLink::updateDataPrivate(App::LinkBaseExtension *ext, const App:
|
||||
if(touched.empty()) {
|
||||
for(int i=0;i<linkView->getSize();++i) {
|
||||
Base::Matrix4D mat;
|
||||
if(propPlacements->getSize()>i)
|
||||
if(propPlacements && propPlacements->getSize()>i)
|
||||
mat = (*propPlacements)[i].toMatrix();
|
||||
if(propScales && propScales->getSize()>i) {
|
||||
if(propScales && propScales->getSize()>i && canScale((*propScales)[i])) {
|
||||
Base::Matrix4D s;
|
||||
s.scale((*propScales)[i]);
|
||||
mat *= s;
|
||||
@@ -1932,9 +1996,9 @@ void ViewProviderLink::updateDataPrivate(App::LinkBaseExtension *ext, const App:
|
||||
if(i<0 || i>=linkView->getSize())
|
||||
continue;
|
||||
Base::Matrix4D mat;
|
||||
if(propPlacements->getSize()>i)
|
||||
if(propPlacements && propPlacements->getSize()>i)
|
||||
mat = (*propPlacements)[i].toMatrix();
|
||||
if(propScales && propScales->getSize()>i) {
|
||||
if(propScales && propScales->getSize()>i && canScale((*propScales)[i])) {
|
||||
Base::Matrix4D s;
|
||||
s.scale((*propScales)[i]);
|
||||
mat *= s;
|
||||
@@ -2076,24 +2140,33 @@ ViewProvider *ViewProviderLink::getLinkedView(
|
||||
|
||||
std::vector<App::DocumentObject*> ViewProviderLink::claimChildren(void) const {
|
||||
auto ext = getLinkExtension();
|
||||
std::vector<App::DocumentObject*> ret;
|
||||
|
||||
if(ext && !ext->_getShowElementValue() && ext->_getElementCountValue()) {
|
||||
// in array mode without element objects, we'd better not show the
|
||||
// linked object's children to avoid inconsistent behavior on selection.
|
||||
// We claim the linked object instead
|
||||
std::vector<App::DocumentObject*> ret;
|
||||
if(ext) {
|
||||
auto obj = ext->getTrueLinkedObject(true);
|
||||
auto obj = ext->getLinkedObjectValue();
|
||||
if(obj) ret.push_back(obj);
|
||||
}
|
||||
return ret;
|
||||
} else if(hasElements(ext) || isGroup(ext))
|
||||
return ext->getElementListValue();
|
||||
if(!hasSubName) {
|
||||
} else if(hasElements(ext) || isGroup(ext)) {
|
||||
ret = ext->getElementListValue();
|
||||
if (ext->_getElementCountValue()
|
||||
&& ext->getLinkClaimChildValue()
|
||||
&& ext->getLinkedObjectValue())
|
||||
ret.insert(ret.begin(), ext->getLinkedObjectValue());
|
||||
} else if(!hasSubName) {
|
||||
auto linked = getLinkedView(true);
|
||||
if(linked)
|
||||
return linked->claimChildren();
|
||||
if(linked) {
|
||||
ret = linked->claimChildren();
|
||||
if (ext->getLinkClaimChildValue() && ext->getLinkedObjectValue())
|
||||
ret.insert(ret.begin(), ext->getLinkedObjectValue());
|
||||
}
|
||||
}
|
||||
return std::vector<App::DocumentObject*>();
|
||||
if (ext && ext->getLinkCopyOnChangeGroupValue())
|
||||
ret.insert(ret.begin(), ext->getLinkCopyOnChangeGroupValue());
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool ViewProviderLink::canDragObject(App::DocumentObject* obj) const {
|
||||
@@ -2279,12 +2352,29 @@ bool ViewProviderLink::getDetailPath(
|
||||
return false;
|
||||
}
|
||||
std::string _subname;
|
||||
if(subname && subname[0] &&
|
||||
(isGroup(ext,true) || hasElements(ext) || ext->getElementCountValue())) {
|
||||
int index = ext->getElementIndex(subname,&subname);
|
||||
if(index>=0) {
|
||||
_subname = std::to_string(index)+'.'+subname;
|
||||
subname = _subname.c_str();
|
||||
if(subname && subname[0]) {
|
||||
if (auto linked = ext->getLinkedObjectValue()) {
|
||||
if (const char *dot = strchr(subname,'.')) {
|
||||
if(subname[0]=='$') {
|
||||
CharRange sub(subname+1, dot);
|
||||
if (!boost::equals(sub, linked->Label.getValue()))
|
||||
dot = nullptr;
|
||||
} else {
|
||||
CharRange sub(subname, dot);
|
||||
if (!boost::equals(sub, linked->getNameInDocument()))
|
||||
dot = nullptr;
|
||||
}
|
||||
if (dot && linked->getSubObject(dot+1))
|
||||
subname = dot+1;
|
||||
}
|
||||
}
|
||||
|
||||
if (isGroup(ext,true) || hasElements(ext) || ext->getElementCountValue()) {
|
||||
int index = ext->getElementIndex(subname,&subname);
|
||||
if(index>=0) {
|
||||
_subname = std::to_string(index)+'.'+subname;
|
||||
subname = _subname.c_str();
|
||||
}
|
||||
}
|
||||
}
|
||||
if(linkView->linkGetDetailPath(subname,pPath,det))
|
||||
@@ -2295,7 +2385,27 @@ bool ViewProviderLink::getDetailPath(
|
||||
|
||||
bool ViewProviderLink::onDelete(const std::vector<std::string> &) {
|
||||
auto element = freecad_dynamic_cast<App::LinkElement>(getObject());
|
||||
return !element || element->canDelete();
|
||||
if (element && !element->canDelete())
|
||||
return false;
|
||||
auto ext = getLinkExtension();
|
||||
if (ext->isLinkMutated()) {
|
||||
auto linked = ext->getLinkedObjectValue();
|
||||
auto doc = ext->getContainer()->getDocument();
|
||||
if (linked->getDocument() == doc) {
|
||||
std::deque<std::string> objs;
|
||||
for (auto obj : ext->getOnChangeCopyObjects(nullptr, linked)) {
|
||||
if (obj->getDocument() == doc) {
|
||||
// getOnChangeCopyObjects() returns object in depending
|
||||
// order. So we delete it in reverse to avoid error
|
||||
// reported by some parent object failing to find child
|
||||
objs.emplace_front(obj->getNameInDocument());
|
||||
}
|
||||
}
|
||||
for (auto &name : objs)
|
||||
doc->removeObject(name.c_str());
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ViewProviderLink::canDelete(App::DocumentObject *obj) const {
|
||||
@@ -2334,12 +2444,122 @@ void ViewProviderLink::setupContextMenu(QMenu* menu, QObject* receiver, const ch
|
||||
if (!ext)
|
||||
return;
|
||||
|
||||
_setupContextMenu(ext, menu, receiver, member);
|
||||
Gui::ActionFunction* func = nullptr;
|
||||
if (ext->isLinkedToConfigurableObject()) {
|
||||
if (ext->getLinkCopyOnChangeValue() == 0) {
|
||||
auto submenu = menu->addMenu(QObject::tr("Copy on change"));
|
||||
auto act = submenu->addAction(QObject::tr("Enable"));
|
||||
act->setToolTip(QObject::tr(
|
||||
"Enable auto copy of linked object when its configuration is changed"));
|
||||
act->setData(-1);
|
||||
if (!func) func = new Gui::ActionFunction(menu);
|
||||
func->trigger(act, [ext](){
|
||||
try {
|
||||
App::AutoTransaction guard("Enable Link copy on change");
|
||||
ext->getLinkCopyOnChangeProperty()->setValue(1);
|
||||
Command::updateActive();
|
||||
} catch (Base::Exception &e) {
|
||||
e.ReportException();
|
||||
}
|
||||
});
|
||||
act = submenu->addAction(QObject::tr("Tracking"));
|
||||
act->setToolTip(QObject::tr(
|
||||
"Copy the linked object when its configuration is changed.\n"
|
||||
"Also auto redo the copy if the original linked object is changed.\n"));
|
||||
act->setData(-1);
|
||||
func->trigger(act, [ext](){
|
||||
try {
|
||||
App::AutoTransaction guard("Enable Link tracking");
|
||||
ext->getLinkCopyOnChangeProperty()->setValue(3);
|
||||
Command::updateActive();
|
||||
} catch (Base::Exception &e) {
|
||||
e.ReportException();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (ext->getLinkCopyOnChangeValue() != 2
|
||||
&& ext->getLinkCopyOnChangeValue() != 0) {
|
||||
QAction *act = menu->addAction(
|
||||
QObject::tr("Disable copy on change"));
|
||||
act->setData(-1);
|
||||
if (!func) func = new Gui::ActionFunction(menu);
|
||||
func->trigger(act, [ext](){
|
||||
try {
|
||||
App::AutoTransaction guard("Disable copy on change");
|
||||
ext->getLinkCopyOnChangeProperty()->setValue((long)0);
|
||||
Command::updateActive();
|
||||
} catch (Base::Exception &e) {
|
||||
e.ReportException();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (ext->isLinkMutated()) {
|
||||
QAction* act = menu->addAction(QObject::tr("Rerefresh configurable object"));
|
||||
act->setToolTip(QObject::tr(
|
||||
"Synchronize the original configurable source object by\n"
|
||||
"creating a new deep copy. Note that any changes made to\n"
|
||||
"the current copy will be lost.\n"));
|
||||
act->setData(-1);
|
||||
if (!func) func = new Gui::ActionFunction(menu);
|
||||
func->trigger(act, [ext](){
|
||||
try {
|
||||
App::AutoTransaction guard("Link refresh");
|
||||
ext->syncCopyOnChange();
|
||||
Command::updateActive();
|
||||
} catch (Base::Exception &e) {
|
||||
e.ReportException();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void ViewProviderLink::_setupContextMenu(
|
||||
App::LinkBaseExtension *ext, QMenu* menu, QObject* receiver, const char* member)
|
||||
{
|
||||
if(linkEdit(ext)) {
|
||||
linkView->getLinkedView()->setupContextMenu(menu,receiver,member);
|
||||
} else if(ext->getPlacementProperty() || ext->getLinkPlacementProperty()) {
|
||||
QIcon iconObject = mergeGreyableOverlayIcons(Gui::BitmapFactory().pixmap("Std_TransformManip.svg"));
|
||||
QAction* act = menu->addAction(iconObject, QObject::tr("Transform"), receiver, member);
|
||||
act->setData(QVariant((int)ViewProvider::Transform));
|
||||
if (auto linkvp = Base::freecad_dynamic_cast<ViewProviderLink>(linkView->getLinkedView()))
|
||||
linkvp->_setupContextMenu(ext, menu, receiver, member);
|
||||
else
|
||||
linkView->getLinkedView()->setupContextMenu(menu,receiver,member);
|
||||
}
|
||||
|
||||
if(ext->getLinkedObjectProperty()
|
||||
&& ext->_getShowElementProperty()
|
||||
&& ext->_getElementCountValue() > 1)
|
||||
{
|
||||
auto action = menu->addAction(QObject::tr("Toggle array elements"), [ext] {
|
||||
try {
|
||||
App::AutoTransaction guard(QT_TRANSLATE_NOOP("Command", "Toggle array elements"));
|
||||
ext->getShowElementProperty()->setValue(!ext->getShowElementValue());
|
||||
Command::updateActive();
|
||||
} catch (Base::Exception &e) {
|
||||
e.ReportException();
|
||||
}
|
||||
});
|
||||
action->setToolTip(QObject::tr(
|
||||
"Change whether show each link array element as individual objects"));
|
||||
}
|
||||
|
||||
if((ext->getPlacementProperty() && !ext->getPlacementProperty()->isReadOnly())
|
||||
|| (ext->getLinkPlacementProperty() && !ext->getLinkPlacementProperty()->isReadOnly()))
|
||||
{
|
||||
bool found = false;
|
||||
for(auto action : menu->actions()) {
|
||||
if(action->data().toInt() == ViewProvider::Transform) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
QIcon iconObject = mergeGreyableOverlayIcons(Gui::BitmapFactory().pixmap("Std_TransformManip.svg"));
|
||||
QAction* act = menu->addAction(iconObject, QObject::tr("Transform"), receiver, member);
|
||||
act->setToolTip(QObject::tr("Transform at the origin of the placement"));
|
||||
act->setData(QVariant((int)ViewProvider::Transform));
|
||||
}
|
||||
}
|
||||
|
||||
if(ext->getColoredElementsProperty()) {
|
||||
|
||||
@@ -273,6 +273,7 @@ protected:
|
||||
void setEditViewer(View3DInventorViewer*, int ModNum) override;
|
||||
void unsetEditViewer(View3DInventorViewer*) override;
|
||||
bool linkEdit(const App::LinkBaseExtension *ext=nullptr) const;
|
||||
void _setupContextMenu(App::LinkBaseExtension *ext, QMenu*, QObject*, const char*);
|
||||
|
||||
enum LinkType {
|
||||
LinkTypeNone,
|
||||
|
||||
Reference in New Issue
Block a user