App: Property related API changes

Property:

* Extended property status bitset. Mirror most of PropertyType and
  allow dynamic change property type.

* Cache property name and type to improve performance

* Centralize property status change signalling

* Change aboutToSetValue()/hasSetValue() to virtual

* Add new API getFullName() to obtain full quanlified name of the property

AtomicPropertyChangeInterface:

* Allow calling aboutToSetValue()/hasSetValue() when actually changed

PropertyLists:

* Refactor implementation by an abstract class PropertyListBase and a
  template class PropertyListsT, to allow better code reuse.
  PropertyListT is derived from AtomicPropertyChangeInterface to allow
  more efficient change on individual elements.

* All list type property now accept setting python value as a dictionary
  with index as key to set individual element of a list.

* Add touch list for more efficient handling of value changes. The list
  contains the index of changed value. And empty touch list should be
  treated as the entire list is changed. PropertyContainerPy expose this
  functionality with getPropertyTouchList().

PropertyPersistentObject:

* New property to allow dynamic creation of any FreeCAD object derived
  from Base::Persistence, and use it as a property.

DynamicProperty:

* Use boost multi_index_container for efficient property lookup while
  keeping order.

* Modify to be allowed to use in PropertyContainer directly

PropertyContainer:

* Use boost multi_index_container for efficient property lookup while
  keeping order.

* Allow adding/removing dynamic property on all property container

* Modify Save/Restore() to persist property status, and better handle
  transient property which can now be dynamically enabled/disabled per
  object.

* Add new API getFullName() to obtain full quanlified name of the property.
  Implemented by Document, DocumentObject, and also
  ViewProviderDocumentObject if future patch

DocumentObject and FeaturePython are modified to accommondate the
dynamic property changes.

Removed get/setCustomAttribute() implementation from DocumentObjectPy,
and rely on PropertyContainerPy for the implementation, because of the
additional dynamic property support in property container.

Gui::ViewProviderDocumentObject, which is derived from
PropertyContainer, is also modified accordingly
This commit is contained in:
Zheng, Lei
2019-06-28 10:16:42 +08:00
committed by wmayer
parent ff1d1cd341
commit f4205130ae
25 changed files with 1511 additions and 1579 deletions

View File

@@ -35,10 +35,13 @@
#include <Base/Console.h>
#include <Base/Exception.h>
#include "Application.h"
#include "Property.h"
#include "PropertyContainer.h"
#include "PropertyLinks.h"
FC_LOG_LEVEL_INIT("App",true,true)
using namespace App;
using namespace Base;
using namespace std;
@@ -71,18 +74,30 @@ unsigned int PropertyContainer::getMemSize (void) const
return size;
}
App::Property* PropertyContainer::addDynamicProperty(
const char* type, const char* name, const char* group, const char* doc,
short attr, bool ro, bool hidden)
{
return dynamicProps.addDynamicProperty(*this,type,name,group,doc,attr,ro,hidden);
}
Property *PropertyContainer::getPropertyByName(const char* name) const
{
auto prop = dynamicProps.getDynamicPropertyByName(name);
if(prop) return prop;
return getPropertyData().getPropertyByName(this,name);
}
void PropertyContainer::getPropertyMap(std::map<std::string,Property*> &Map) const
{
dynamicProps.getPropertyMap(Map);
getPropertyData().getPropertyMap(this,Map);
}
void PropertyContainer::getPropertyList(std::vector<Property*> &List) const
{
dynamicProps.getPropertyList(List);
getPropertyData().getPropertyList(this,List);
}
@@ -96,31 +111,39 @@ void PropertyContainer::setPropertyStatus(unsigned char bit,bool value)
short PropertyContainer::getPropertyType(const Property* prop) const
{
return getPropertyData().getType(this,prop);
return prop?prop->getType():0;
}
short PropertyContainer::getPropertyType(const char *name) const
{
return getPropertyData().getType(this,name);
return getPropertyType(getPropertyByName(name));
}
const char* PropertyContainer::getPropertyGroup(const Property* prop) const
{
auto group = dynamicProps.getPropertyGroup(prop);
if(group) return group;
return getPropertyData().getGroup(this,prop);
}
const char* PropertyContainer::getPropertyGroup(const char *name) const
{
auto group = dynamicProps.getPropertyGroup(name);
if(group) return group;
return getPropertyData().getGroup(this,name);
}
const char* PropertyContainer::getPropertyDocumentation(const Property* prop) const
{
auto doc = dynamicProps.getPropertyDocumentation(prop);
if(doc) return doc;
return getPropertyData().getDocumentation(this,prop);
}
const char* PropertyContainer::getPropertyDocumentation(const char *name) const
{
auto doc = dynamicProps.getPropertyDocumentation(name);
if(doc) return doc;
return getPropertyData().getDocumentation(this,name);
}
@@ -146,10 +169,12 @@ bool PropertyContainer::isHidden(const char *name) const
const char* PropertyContainer::getPropertyName(const Property* prop)const
{
return getPropertyData().getName(this,prop);
auto res = dynamicProps.getPropertyName(prop);
if(!res)
res = getPropertyData().getName(this,prop);
return res;
}
const PropertyData * PropertyContainer::getPropertyDataPtr(void){return &propertyData;}
const PropertyData & PropertyContainer::getPropertyData(void) const{return propertyData;}
@@ -193,66 +218,82 @@ void PropertyContainer::handleChangedPropertyType(XMLReader &reader, const char
PropertyData PropertyContainer::propertyData;
/**
* Binary function to query the flags for use with generic STL functions.
*/
template <class TCLASS>
class PropertyAttribute : public std::binary_function<TCLASS, typename App::PropertyType, bool>
{
public:
PropertyAttribute(const PropertyContainer* c) : cont(c) {}
bool operator () (const TCLASS& prop, typename App::PropertyType attr) const
{ return (cont->getPropertyType(prop.second) & attr) == attr; }
private:
const PropertyContainer* cont;
};
void PropertyContainer::Save (Base::Writer &writer) const
void PropertyContainer::Save (Base::Writer &writer) const
{
std::map<std::string,Property*> Map;
getPropertyMap(Map);
// ignore the properties we won't store
size_t ct = std::count_if(Map.begin(), Map.end(), std::bind2nd(PropertyAttribute
<std::pair<std::string,Property*> >(this), Prop_Transient));
size_t size = Map.size() - ct;
std::vector<Property*> transients;
for(auto it=Map.begin();it!=Map.end();) {
auto prop = it->second;
if(prop->testStatus(Property::PropNoPersist)) {
it = Map.erase(it);
continue;
}
if(!prop->testStatus(Property::PropDynamic)
&& (prop->testStatus(Property::Transient) ||
getPropertyType(prop) & Prop_Transient))
{
transients.push_back(prop);
it = Map.erase(it);
}else
++it;
}
writer.incInd(); // indentation for 'Properties Count'
writer.Stream() << writer.ind() << "<Properties Count=\"" << size << "\">" << endl;
std::map<std::string,Property*>::iterator it;
for (it = Map.begin(); it != Map.end(); ++it)
writer.Stream() << writer.ind() << "<Properties Count=\"" << Map.size()
<< "\" TransientCount=\"" << transients.size() << "\">" << endl;
// First store transient properties to persisit their status value. We use
// a new element named "_Property" so that the save file can be opened by
// older version FC.
writer.incInd();
for(auto prop : transients) {
writer.Stream() << writer.ind() << "<_Property name=\"" << prop->getName()
<< "\" type=\"" << prop->getTypeId().getName()
<< "\" status=\"" << prop->getStatus() << "\"/>" << std::endl;
}
writer.decInd();
// Now store normal properties
for (auto it = Map.begin(); it != Map.end(); ++it)
{
// Don't write transient properties
if (!(getPropertyType(it->second) & Prop_Transient))
{
writer.incInd(); // indentation for 'Property name'
writer.Stream() << writer.ind() << "<Property name=\"" << it->first << "\" type=\""
<< it->second->getTypeId().getName() << "\">" << endl;;
writer.incInd(); // indentation for the actual property
try {
// We must make sure to handle all exceptions accordingly so that
// the project file doesn't get invalidated. In the error case this
// means to proceed instead of aborting the write operation.
it->second->Save(writer);
}
catch (const Base::Exception &e) {
Base::Console().Error("%s\n", e.what());
}
catch (const std::exception &e) {
Base::Console().Error("%s\n", e.what());
}
catch (const char* e) {
Base::Console().Error("%s\n", e);
}
#ifndef FC_DEBUG
catch (...) {
Base::Console().Error("PropertyContainer::Save: Unknown C++ exception thrown. Try to continue...\n");
}
#endif
writer.decInd(); // indentation for the actual property
writer.Stream() << writer.ind() << "</Property>" << endl;
writer.decInd(); // indentation for 'Property name'
writer.incInd(); // indentation for 'Property name'
writer.Stream() << writer.ind() << "<Property name=\"" << it->first << "\" type=\""
<< it->second->getTypeId().getName();
dynamicProps.save(it->second,writer);
auto status = it->second->getStatus();
if(status)
writer.Stream() << "\" status=\"" << status;
writer.Stream() << "\">" << std::endl;
writer.incInd(); // indentation for the actual property
try {
// We must make sure to handle all exceptions accordingly so that
// the project file doesn't get invalidated. In the error case this
// means to proceed instead of aborting the write operation.
it->second->Save(writer);
}
catch (const Base::Exception &e) {
Base::Console().Error("%s\n", e.what());
}
catch (const std::exception &e) {
Base::Console().Error("%s\n", e.what());
}
catch (const char* e) {
Base::Console().Error("%s\n", e);
}
#ifndef FC_DEBUG
catch (...) {
Base::Console().Error("PropertyContainer::Save: Unknown C++ exception thrown. Try to continue...\n");
}
#endif
writer.decInd(); // indentation for the actual property
writer.Stream() << writer.ind() << "</Property>" << endl;
writer.decInd(); // indentation for 'Property name'
}
writer.Stream() << writer.ind() << "</Properties>" << endl;
writer.decInd(); // indentation for 'Properties Count'
@@ -264,11 +305,33 @@ void PropertyContainer::Restore(Base::XMLReader &reader)
reader.readElement("Properties");
int Cnt = reader.getAttributeAsInteger("Count");
int transientCount = 0;
if(reader.hasAttribute("TransientCount"))
transientCount = reader.getAttributeAsUnsigned("TransientCount");
for (int i=0;i<transientCount; ++i) {
reader.readElement("_Property");
Property* prop = getPropertyByName(reader.getAttribute("name"));
if(prop)
FC_TRACE("restore transient '" << prop->getName() << "'");
if(prop && reader.hasAttribute("status"))
prop->setStatusValue(reader.getAttributeAsUnsigned("status"));
}
for (int i=0 ;i<Cnt ;i++) {
reader.readElement("Property");
std::string PropName = reader.getAttribute("name");
std::string TypeName = reader.getAttribute("type");
Property* prop = getPropertyByName(PropName.c_str());
auto prop = dynamicProps.restore(*this,PropName.c_str(),TypeName.c_str(),reader);
if(!prop)
prop = getPropertyByName(PropName.c_str());
decltype(Property::StatusBits) status;
if(reader.hasAttribute("status")) {
status = decltype(status)(reader.getAttributeAsUnsigned("status"));
if(prop)
prop->setStatusValue(status.to_ulong());
}
// NOTE: We must also check the type of the current property because a
// subclass of PropertyContainer might change the type of a property but
// not its name. In this case we would force to read-in a wrong property
@@ -276,7 +339,15 @@ void PropertyContainer::Restore(Base::XMLReader &reader)
try {
// name and type match
if (prop && strcmp(prop->getTypeId().getName(), TypeName.c_str()) == 0) {
prop->Restore(reader);
if (!prop->testStatus(Property::Transient)
&& !status.test(Property::Transient)
&& !status.test(Property::PropTransient)
&& !(getPropertyType(prop) & Prop_Transient))
{
FC_TRACE("restore proeprty '" << prop->getName() << "'");
prop->Restore(reader);
}else
FC_TRACE("skip transient '" << prop->getName() << "'");
}
// name matches but not the type
else if (prop) {
@@ -315,58 +386,98 @@ void PropertyContainer::Restore(Base::XMLReader &reader)
Base::Console().Error("PropertyContainer::Restore: Unknown C++ exception thrown\n");
}
#endif
reader.readEndElement("Property");
}
reader.readEndElement("Properties");
}
void PropertyContainer::onPropertyStatusChanged(const Property &prop, unsigned long oldStatus)
{
(void)prop;
(void)oldStatus;
}
void PropertyData::addProperty(OffsetBase offsetBase,const char* PropName, Property *Prop, const char* PropertyGroup , PropertyType Type, const char* PropertyDocu)
{
bool IsIn = false;
for (vector<PropertySpec>::const_iterator It = propertyData.begin(); It != propertyData.end(); ++It)
if(strcmp(It->Name,PropName)==0)
IsIn = true;
#ifdef FC_DEBUG
if(!parentMerged)
#endif
{
short offset = offsetBase.getOffsetTo(Prop);
if(offset < 0)
throw Base::RuntimeError("Invalid static property");
auto &index = propertyData.get<1>();
auto it = index.find(PropName);
if(it == index.end()) {
if(parentMerged)
throw Base::RuntimeError("Cannot add static property");
index.emplace(PropName, PropertyGroup, PropertyDocu, offset, Type);
} else{
#ifdef FC_DEBUG
if(it->Offset != offset) {
FC_ERR("Duplicate property '" << PropName << "'");
}
#endif
}
}
if( !IsIn )
{
PropertySpec temp;
temp.Name = PropName;
temp.Offset = offsetBase.getOffsetTo(Prop);
assert(temp.Offset>=0);
temp.Group = PropertyGroup;
temp.Type = Type;
temp.Docu = PropertyDocu;
propertyData.push_back(temp);
}
Prop->syncType(Type);
Prop->myName = PropName;
}
void PropertyData::merge(PropertyData *other) const {
if(!other)
other = const_cast<PropertyData*>(parentPropertyData);
if(other == parentPropertyData) {
if(parentMerged)
return;
parentMerged = true;
}
if(other) {
other->merge();
auto &index = propertyData.get<0>();
for(const auto &spec : other->propertyData.get<0>())
index.push_back(spec);
}
}
void PropertyData::split(PropertyData *other) {
if(other == parentPropertyData) {
if(!parentMerged)
return;
parentMerged = false;
}
if(other) {
auto &index = propertyData.get<2>();
for(const auto &spec : other->propertyData.get<0>())
index.erase(spec.Offset);
}
}
const PropertyData::PropertySpec *PropertyData::findProperty(OffsetBase offsetBase,const char* PropName) const
{
for (vector<PropertyData::PropertySpec>::const_iterator It = propertyData.begin(); It != propertyData.end(); ++It)
if(strcmp(It->Name,PropName)==0)
return &(*It);
if(parentPropertyData)
return parentPropertyData->findProperty(offsetBase,PropName);
return 0;
(void)offsetBase;
merge();
auto &index = propertyData.get<1>();
auto it = index.find(PropName);
if(it != index.end())
return &(*it);
return 0;
}
const PropertyData::PropertySpec *PropertyData::findProperty(OffsetBase offsetBase,const Property* prop) const
{
const int diff = offsetBase.getOffsetTo(prop);
if(diff<0)
return 0;
merge();
int diff = offsetBase.getOffsetTo(prop);
if(diff<0)
return 0;
for (vector<PropertyData::PropertySpec>::const_iterator It = propertyData.begin(); It != propertyData.end(); ++It)
if(diff == It->Offset)
return &(*It);
if(parentPropertyData)
return parentPropertyData->findProperty(offsetBase,prop);
return 0;
auto &index = propertyData.get<2>();
auto it = index.find(diff);
if(it!=index.end())
return &(*it);
return 0;
}
const char* PropertyData::getName(OffsetBase offsetBase,const Property* prop) const
@@ -377,17 +488,6 @@ const char* PropertyData::getName(OffsetBase offsetBase,const Property* prop) co
return Spec->Name;
else
return 0;
/*
for(std::map<std::string,PropertySpec>::const_iterator pos = propertyData.begin();pos != propertyData.end();++pos)
if(pos->second.Offset == diff)
return pos->first.c_str();
if(parentPropertyData)
return parentPropertyData->getName(container,prop);
return 0;
*/
}
short PropertyData::getType(OffsetBase offsetBase,const Property* prop) const
@@ -398,19 +498,6 @@ short PropertyData::getType(OffsetBase offsetBase,const Property* prop) const
return Spec->Type;
else
return 0;
/*
const int diff = (int) ((char*)prop - (char*)container);
for(std::map<std::string,PropertySpec>::const_iterator pos = propertyData.begin();pos != propertyData.end();++pos)
if(pos->second.Offset == diff)
return pos->second.Type;
if(parentPropertyData)
return parentPropertyData->getType(container,prop);
return 0;
*/
}
short PropertyData::getType(OffsetBase offsetBase,const char* name) const
@@ -431,19 +518,6 @@ const char* PropertyData::getGroup(OffsetBase offsetBase,const Property* prop) c
return Spec->Group;
else
return 0;
/*
const int diff = (int) ((char*)prop - (char*)container);
for(std::map<std::string,PropertySpec>::const_iterator pos = propertyData.begin();pos != propertyData.end();++pos)
if(pos->second.Offset == diff)
return pos->second.Group;
if(parentPropertyData)
return parentPropertyData->getGroup(container,prop);
return 0;
*/
}
const char* PropertyData::getGroup(OffsetBase offsetBase,const char* name) const
@@ -476,8 +550,6 @@ const char* PropertyData::getDocumentation(OffsetBase offsetBase,const char* nam
return 0;
}
Property *PropertyData::getPropertyByName(OffsetBase offsetBase,const char* name) const
{
const PropertyData::PropertySpec* Spec = findProperty(offsetBase,name);
@@ -486,53 +558,22 @@ Property *PropertyData::getPropertyByName(OffsetBase offsetBase,const char* name
return (Property *) (Spec->Offset + offsetBase.getOffset());
else
return 0;
/*
std::map<std::string,PropertySpec>::const_iterator pos = propertyData.find(name);
if(pos != propertyData.end())
{
// calculate propterty by offset
return (Property *) (pos->second.Offset + (char *)container);
}else{
if(parentPropertyData)
return parentPropertyData->getPropertyByName(container,name);
else
return 0;
}*/
}
void PropertyData::getPropertyMap(OffsetBase offsetBase,std::map<std::string,Property*> &Map) const
{
for (vector<PropertyData::PropertySpec>::const_iterator It = propertyData.begin(); It != propertyData.end(); ++It)
Map[It->Name] = (Property *) (It->Offset + offsetBase.getOffset());
/*
std::map<std::string,PropertySpec>::const_iterator pos;
for(pos = propertyData.begin();pos != propertyData.end();++pos)
{
Map[pos->first] = (Property *) (pos->second.Offset + (char *)container);
}
*/
if(parentPropertyData)
parentPropertyData->getPropertyMap(offsetBase,Map);
merge();
for(auto &spec : propertyData.get<0>())
Map[spec.Name] = (Property *) (spec.Offset + offsetBase.getOffset());
}
void PropertyData::getPropertyList(OffsetBase offsetBase,std::vector<Property*> &List) const
{
for (vector<PropertyData::PropertySpec>::const_iterator It = propertyData.begin(); It != propertyData.end(); ++It)
List.push_back((Property *) (It->Offset + offsetBase.getOffset()) );
/* std::map<std::string,PropertySpec>::const_iterator pos;
for(pos = propertyData.begin();pos != propertyData.end();++pos)
{
List.push_back((Property *) (pos->second.Offset + (char *)container) );
}*/
if(parentPropertyData)
parentPropertyData->getPropertyList(offsetBase,List);
merge();
size_t base = List.size();
List.reserve(base+propertyData.size());
for (auto &spec : propertyData.get<0>())
List.push_back((Property *) (spec.Offset + offsetBase.getOffset()));
}