From 4f29e81d0ccc2b72f372c197020d49545dae93a1 Mon Sep 17 00:00:00 2001 From: "Zheng, Lei" Date: Wed, 1 Jan 2020 18:19:24 +0800 Subject: [PATCH] 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. --- src/App/Link.cpp | 206 +++++++++++++++++++++- src/App/Link.h | 22 ++- src/App/Property.cpp | 3 +- src/App/Property.h | 1 + src/Gui/propertyeditor/PropertyEditor.cpp | 3 + 5 files changed, 231 insertions(+), 4 deletions(-) diff --git a/src/App/Link.cpp b/src/App/Link.cpp index 3e67cbe16c..c845d3b247 100644 --- a/src/App/Link.cpp +++ b/src/App/Link.cpp @@ -28,6 +28,7 @@ #include #include +#include #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(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 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 *copyOnChangeConns, bool checkExisting) +{ + if(!parent || !linked) + return false; + + bool res = false; + + std::unordered_map newProps; + std::vector 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 pcopy(prop->Copy()); + Base::ObjectStatusLocker 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 pcopy(prop.Copy()); + // temperoray set Output to prevent touching + p->setStatus(Property::Output, true); + // temperoray block copy on change + Base::ObjectStatusLocker 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 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 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()) diff --git a/src/App/Link.h b/src/App/Link.h index 221f5b4068..0a38bf8a41 100644 --- a/src/App/Link.h +++ b/src/App/Link.h @@ -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 *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 myLabelCache; // for label based subname lookup mutable bool enableLabelCache; - bool hasOldSubElement; mutable bool checkingProperty = false; + + std::vector 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) diff --git a/src/App/Property.cpp b/src/App/Property.cpp index 04b08190b1..1c7bdce0d4 100644 --- a/src/App/Property.cpp +++ b/src/App/Property.cpp @@ -256,7 +256,8 @@ void Property::setStatusValue(unsigned long status) { |(1<isChecked())