App: support CopyOnChange property in Link
New property status bit 'CopyOnChange' is added for any document object to publish any property as a 'Configuration' option. When Link is linked to any object with such property, it will duplicate those properties and added it Link itself as dynamic properties. If the user changes any of these dynamic properties, the Link will auto copy the linked to object and apply the new configuration to it. The Link has a new property 'LinkCopyOnChange' to allow user to enable/disable this feature. Spreadsheet's 'Configuration Table' feature will publish its configuration property with 'CopyOnChange'. Currently, once the linked object is copied, it will be independent with the original object. There is no mechanism to auto sync changes back to the copy.
This commit is contained in:
206
src/App/Link.cpp
206
src/App/Link.cpp
@@ -28,6 +28,7 @@
|
||||
|
||||
#include <boost/algorithm/string/predicate.hpp>
|
||||
#include <boost/preprocessor/stringize.hpp>
|
||||
#include <Base/Tools.h>
|
||||
#include "Application.h"
|
||||
#include "Document.h"
|
||||
#include "GroupExtension.h"
|
||||
@@ -53,7 +54,7 @@ namespace bp = boost::placeholders;
|
||||
EXTENSION_PROPERTY_SOURCE(App::LinkBaseExtension, App::DocumentObjectExtension)
|
||||
|
||||
LinkBaseExtension::LinkBaseExtension(void)
|
||||
:enableLabelCache(false),hasOldSubElement(false)
|
||||
:enableLabelCache(false),hasOldSubElement(false),hasCopyOnChange(true)
|
||||
{
|
||||
initExtensionType(LinkBaseExtension::getExtensionClassTypeId());
|
||||
EXTENSION_ADD_PROPERTY_TYPE(_LinkTouched, (false), " Link",
|
||||
@@ -139,7 +140,15 @@ void LinkBaseExtension::setProperty(int idx, Property *prop) {
|
||||
if(!propLinkMode->getEnums())
|
||||
propLinkMode->setEnums(linkModeEnums);
|
||||
break;
|
||||
} case PropLinkTransform:
|
||||
}
|
||||
case PropLinkCopyOnChange: {
|
||||
static const char *enums[] = {"Disabled","Enabled","Owned",0};
|
||||
auto propEnum = freecad_dynamic_cast<PropertyEnumeration>(prop);
|
||||
if(!propEnum->getEnums())
|
||||
propEnum->setEnums(enums);
|
||||
break;
|
||||
}
|
||||
case PropLinkTransform:
|
||||
case PropLinkPlacement:
|
||||
case PropPlacement:
|
||||
if(getLinkTransformProperty() &&
|
||||
@@ -181,6 +190,8 @@ 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
|
||||
// provider that the link (in fact, its dependents, i.e. linked ones) have
|
||||
@@ -193,6 +204,24 @@ App::DocumentObjectExecReturn *LinkBaseExtension::extensionExecute(void) {
|
||||
return new App::DocumentObjectExecReturn("Link broken");
|
||||
|
||||
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 (...) {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
PropertyPythonObject *proxy = 0;
|
||||
if(getLinkExecuteProperty()
|
||||
&& !boost::iequals(getLinkExecuteValue(), "none")
|
||||
@@ -253,6 +282,165 @@ short LinkBaseExtension::extensionMustExecute(void) {
|
||||
return link->mustExecute();
|
||||
}
|
||||
|
||||
bool LinkBaseExtension::isCopyOnChangeProperty(DocumentObject *obj, const App::Property &prop) {
|
||||
if(obj!=prop.getContainer() || !prop.testStatus(App::Property::PropDynamic))
|
||||
return false;
|
||||
auto group = prop.getGroup();
|
||||
return group && boost::starts_with(group,_GroupPrefix);
|
||||
}
|
||||
|
||||
void LinkBaseExtension::setupCopyOnChange(DocumentObject *parent) {
|
||||
copyOnChangeConns.clear();
|
||||
|
||||
auto linked = getTrueLinkedObject(false);
|
||||
if(!linked || getLinkCopyOnChangeValue()==0)
|
||||
return;
|
||||
|
||||
hasCopyOnChange = setupCopyOnChange(parent,linked,©OnChangeConns,hasCopyOnChange);
|
||||
}
|
||||
|
||||
bool LinkBaseExtension::setupCopyOnChange(DocumentObject *parent, DocumentObject *linked,
|
||||
std::vector<boost::signals2::scoped_connection> *copyOnChangeConns, bool checkExisting)
|
||||
{
|
||||
if(!parent || !linked)
|
||||
return false;
|
||||
|
||||
bool res = false;
|
||||
|
||||
std::unordered_map<Property*, Property*> newProps;
|
||||
std::vector<Property*> props;
|
||||
linked->getPropertyList(props);
|
||||
for(auto prop : props) {
|
||||
if(!prop->testStatus(Property::CopyOnChange)
|
||||
|| prop->getContainer()!=linked)
|
||||
continue;
|
||||
|
||||
res = true;
|
||||
|
||||
const char* linkedGroupName = prop->getGroup();
|
||||
if(!linkedGroupName || !linkedGroupName[0])
|
||||
linkedGroupName = "Base";
|
||||
|
||||
std::string groupName;
|
||||
groupName = _GroupPrefix;
|
||||
if(boost::starts_with(linkedGroupName,_GroupPrefix))
|
||||
groupName += linkedGroupName + sizeof(_GroupPrefix)-1;
|
||||
else {
|
||||
groupName += linkedGroupName;
|
||||
groupName += ")";
|
||||
}
|
||||
|
||||
auto p = parent->getPropertyByName(prop->getName());
|
||||
if(p) {
|
||||
if(p->getContainer()!=parent)
|
||||
p = 0;
|
||||
else {
|
||||
const char* otherGroupName = p->getGroup();
|
||||
if(!otherGroupName || !boost::starts_with(otherGroupName, _GroupPrefix)) {
|
||||
FC_WARN(p->getFullName() << " shadows another CopyOnChange property "
|
||||
<< prop->getFullName());
|
||||
continue;
|
||||
}
|
||||
if(p->getTypeId() != prop->getTypeId() || groupName != otherGroupName) {
|
||||
parent->removeDynamicProperty(p->getName());
|
||||
p = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
if(!p) {
|
||||
p = parent->addDynamicProperty(prop->getTypeId().getName(),
|
||||
prop->getName(), groupName.c_str(), prop->getDocumentation());
|
||||
std::unique_ptr<Property> pcopy(prop->Copy());
|
||||
Base::ObjectStatusLocker<Property::Status,Property> guard(Property::User3, p);
|
||||
if(pcopy) {
|
||||
p->Paste(*pcopy);
|
||||
}
|
||||
p->setStatusValue(prop->getStatus());
|
||||
}
|
||||
newProps[p] = prop;
|
||||
}
|
||||
|
||||
if(checkExisting) {
|
||||
props.clear();
|
||||
parent->getPropertyList(props);
|
||||
for(auto prop : props) {
|
||||
if(prop->getContainer()!=parent)
|
||||
continue;
|
||||
auto gname = prop->getGroup();
|
||||
if(!gname || !boost::starts_with(gname, _GroupPrefix))
|
||||
continue;
|
||||
if(!newProps.count(prop))
|
||||
parent->removeDynamicProperty(prop->getName());
|
||||
}
|
||||
}
|
||||
|
||||
if(!copyOnChangeConns)
|
||||
return res;
|
||||
|
||||
for(auto &v : newProps) {
|
||||
// sync configuration properties
|
||||
copyOnChangeConns->push_back(v.second->signalChanged.connect([parent](const Property &prop) {
|
||||
if(!prop.testStatus(Property::CopyOnChange))
|
||||
return;
|
||||
auto p = parent->getPropertyByName(prop.getName());
|
||||
if(p && p->getTypeId()==prop.getTypeId()) {
|
||||
std::unique_ptr<Property> pcopy(prop.Copy());
|
||||
// temperoray set Output to prevent touching
|
||||
p->setStatus(Property::Output, true);
|
||||
// temperoray block copy on change
|
||||
Base::ObjectStatusLocker<Property::Status,Property> guard(Property::User3, p);
|
||||
if(pcopy)
|
||||
p->Paste(*pcopy);
|
||||
p->setStatusValue(prop.getStatus());
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
void LinkBaseExtension::checkCopyOnChange(
|
||||
App::DocumentObject *parent, const App::Property &prop)
|
||||
{
|
||||
if(parent->getDocument()->isPerformingTransaction())
|
||||
return;
|
||||
|
||||
auto linked = getTrueLinkedObject(false);
|
||||
if(!linked || getLinkCopyOnChangeValue()==0
|
||||
|| !isCopyOnChangeProperty(parent,prop))
|
||||
return;
|
||||
|
||||
if(getLinkCopyOnChangeValue()==2) {
|
||||
auto p = linked->getPropertyByName(prop.getName());
|
||||
if(p && p->getTypeId()==prop.getTypeId()) {
|
||||
std::unique_ptr<Property> pcopy(prop.Copy());
|
||||
if(pcopy)
|
||||
p->Paste(*pcopy);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
auto linkedProp = linked->getPropertyByName(prop.getName());
|
||||
if(!linkedProp || linkedProp->getTypeId()!=prop.getTypeId() || linkedProp->isSame(prop))
|
||||
return;
|
||||
|
||||
auto objs = parent->getDocument()->copyObject({linked},true);
|
||||
if(objs.empty())
|
||||
return;
|
||||
|
||||
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);
|
||||
getLinkedObjectProperty()->setValue(linked);
|
||||
getLinkCopyOnChangeProperty()->setValue(2);
|
||||
}
|
||||
|
||||
App::GroupExtension *LinkBaseExtension::linkedPlainGroup() const {
|
||||
if(mySubElements.size() && mySubElements[0].size())
|
||||
return 0;
|
||||
@@ -1098,6 +1286,15 @@ void LinkBaseExtension::update(App::DocumentObject *parent, const Property *prop
|
||||
_ChildCache.setValue();
|
||||
parseSubName();
|
||||
syncElementList();
|
||||
|
||||
if(getLinkCopyOnChangeValue()==2)
|
||||
getLinkCopyOnChangeProperty()->setValue(1);
|
||||
else
|
||||
setupCopyOnChange(parent);
|
||||
|
||||
}else if(prop == getLinkCopyOnChangeProperty()) {
|
||||
setupCopyOnChange(parent);
|
||||
|
||||
}else if(prop == getLinkTransformProperty()) {
|
||||
auto linkPlacement = getLinkPlacementProperty();
|
||||
auto placement = getPlacementProperty();
|
||||
@@ -1107,6 +1304,9 @@ void LinkBaseExtension::update(App::DocumentObject *parent, const Property *prop
|
||||
linkPlacement->setStatus(Property::Hidden,!transform);
|
||||
}
|
||||
syncElementList();
|
||||
|
||||
} else {
|
||||
checkCopyOnChange(parent, *prop);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1156,6 +1356,8 @@ void LinkBaseExtension::syncElementList() {
|
||||
|
||||
element->LinkedObject.setStatus(Property::Hidden,link!=0);
|
||||
element->LinkedObject.setStatus(Property::Immutable,link!=0);
|
||||
if(element->LinkCopyOnChange.getValue()==2)
|
||||
continue;
|
||||
if(xlink) {
|
||||
if(element->LinkedObject.getValue()!=xlink->getValue() ||
|
||||
element->LinkedObject.getSubValues()!=xlink->getSubValues())
|
||||
|
||||
@@ -96,6 +96,14 @@ public:
|
||||
(LinkTransform, bool, App::PropertyBool, false, \
|
||||
"Set to false to override linked object's placement", ##__VA_ARGS__)
|
||||
|
||||
#define LINK_PARAM_COPY_ON_CHANGE(...) \
|
||||
(LinkCopyOnChange, long, App::PropertyEnumeration, ((long)0), \
|
||||
"Disabled: disable copy on change\n"\
|
||||
"Enabled: enable copy linked object on change of any of its propert marked as CopyOnChange\n"\
|
||||
"Owned: indicate the linked object has been copied and is own owned by the link. And the\n"\
|
||||
" the link will try to sync any change of the original linked object back to the copy.",\
|
||||
##__VA_ARGS__)
|
||||
|
||||
#define LINK_PARAM_SCALE(...) \
|
||||
(Scale, double, App::PropertyFloat, 1.0, "Scale factor", ##__VA_ARGS__)
|
||||
|
||||
@@ -162,6 +170,7 @@ public:
|
||||
LINK_PARAM(MODE)\
|
||||
LINK_PARAM(LINK_EXECUTE)\
|
||||
LINK_PARAM(COLORED_ELEMENTS)\
|
||||
LINK_PARAM(COPY_ON_CHANGE)\
|
||||
|
||||
enum PropIndex {
|
||||
#define LINK_PINDEX_DEFINE(_1,_2,_param) LINK_PINDEX(_param),
|
||||
@@ -293,11 +302,18 @@ public:
|
||||
|
||||
void cacheChildLabel(int enable=-1) const;
|
||||
|
||||
static bool setupCopyOnChange(App::DocumentObject *obj, App::DocumentObject *linked,
|
||||
std::vector<boost::signals2::scoped_connection> *copyOnChangeConns, bool checkExisting);
|
||||
|
||||
static bool isCopyOnChangeProperty(App::DocumentObject *obj, const Property &prop);
|
||||
|
||||
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 syncElementList();
|
||||
void detachElement(App::DocumentObject *obj);
|
||||
void checkGeoElementMap(const App::DocumentObject *obj,
|
||||
@@ -318,10 +334,12 @@ protected:
|
||||
|
||||
mutable std::unordered_map<std::string,int> myLabelCache; // for label based subname lookup
|
||||
mutable bool enableLabelCache;
|
||||
|
||||
bool hasOldSubElement;
|
||||
|
||||
mutable bool checkingProperty = false;
|
||||
|
||||
std::vector<boost::signals2::scoped_connection> copyOnChangeConns;
|
||||
bool hasCopyOnChange;
|
||||
};
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
@@ -456,6 +474,7 @@ public:
|
||||
LINK_PARAM_EXT_TYPE(COUNT,App::PropertyIntegerConstraint)\
|
||||
LINK_PARAM_EXT(LINK_EXECUTE)\
|
||||
LINK_PARAM_EXT_ATYPE(COLORED_ELEMENTS,App::Prop_Hidden)\
|
||||
LINK_PARAM_EXT(COPY_ON_CHANGE)\
|
||||
|
||||
LINK_PROPS_DEFINE(LINK_PARAMS_LINK)
|
||||
|
||||
@@ -495,6 +514,7 @@ public:
|
||||
LINK_PARAM_EXT(TRANSFORM) \
|
||||
LINK_PARAM_EXT(LINK_PLACEMENT)\
|
||||
LINK_PARAM_EXT(PLACEMENT)\
|
||||
LINK_PARAM_EXT(COPY_ON_CHANGE)\
|
||||
|
||||
// defines the actual properties
|
||||
LINK_PROPS_DEFINE(LINK_PARAMS_ELEMENT)
|
||||
|
||||
@@ -256,7 +256,8 @@ void Property::setStatusValue(unsigned long status) {
|
||||
|(1<<PropReadOnly)
|
||||
|(1<<PropTransient)
|
||||
|(1<<PropOutput)
|
||||
|(1<<PropHidden);
|
||||
|(1<<PropHidden)
|
||||
|(1<<Busy);
|
||||
|
||||
status &= ~mask;
|
||||
status |= StatusBits.to_ulong() & mask;
|
||||
|
||||
@@ -77,6 +77,7 @@ public:
|
||||
EvalOnRestore = 14, // In case of expression binding, evaluate the
|
||||
// expression on restore and touch the object on value change.
|
||||
Busy = 15, // internal use to avoid recursive signaling
|
||||
CopyOnChange = 16, // for Link to copy the linked object on change of the property with this flag
|
||||
|
||||
// The following bits are corresponding to PropertyType set when the
|
||||
// property added. These types are meant to be static, and cannot be
|
||||
|
||||
@@ -507,6 +507,7 @@ enum MenuAction {
|
||||
MA_Hidden,
|
||||
MA_Touched,
|
||||
MA_EvalOnRestore,
|
||||
MA_CopyOnChange,
|
||||
};
|
||||
|
||||
void PropertyEditor::contextMenuEvent(QContextMenuEvent *) {
|
||||
@@ -612,6 +613,7 @@ void PropertyEditor::contextMenuEvent(QContextMenuEvent *) {
|
||||
ACTION_SETUP(Transient);
|
||||
_ACTION_SETUP(Touched);
|
||||
_ACTION_SETUP(EvalOnRestore);
|
||||
_ACTION_SETUP(CopyOnChange);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -638,6 +640,7 @@ void PropertyEditor::contextMenuEvent(QContextMenuEvent *) {
|
||||
ACTION_CHECK(Output);
|
||||
ACTION_CHECK(Hidden);
|
||||
ACTION_CHECK(EvalOnRestore);
|
||||
ACTION_CHECK(CopyOnChange);
|
||||
case MA_Touched:
|
||||
for(auto prop : props) {
|
||||
if(action->isChecked())
|
||||
|
||||
Reference in New Issue
Block a user