Skip to content

Commit

Permalink
App: Property related API changes
Browse files Browse the repository at this point in the history
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
  • Loading branch information
realthunder committed Jul 16, 2019
1 parent 8a22216 commit 819c807
Show file tree
Hide file tree
Showing 25 changed files with 1,505 additions and 1,573 deletions.
10 changes: 9 additions & 1 deletion src/App/Application.cpp
Expand Up @@ -423,7 +423,7 @@ Document* Application::newDocument(const char * Name, const char * UserName)
}

// create the FreeCAD document
std::unique_ptr<Document> newDoc(new Document());
std::unique_ptr<Document> newDoc(new Document(name.c_str()));

// add the document to the internal list
DocMap[name] = newDoc.release(); // now owned by the Application
Expand All @@ -448,6 +448,8 @@ Document* Application::newDocument(const char * Name, const char * UserName)
_pActiveDoc->signalAbortTransaction.connect(boost::bind(&App::Application::slotAbortTransaction, this, _1));
_pActiveDoc->signalStartSave.connect(boost::bind(&App::Application::slotStartSaveDocument, this, _1, _2));
_pActiveDoc->signalFinishSave.connect(boost::bind(&App::Application::slotFinishSaveDocument, this, _1, _2));
_pActiveDoc->signalChangePropertyEditor.connect(
boost::bind(&App::Application::slotChangePropertyEditor, this, _1, _2));

// make sure that the active document is set in case no GUI is up
{
Expand Down Expand Up @@ -1131,6 +1133,11 @@ void Application::slotFinishSaveDocument(const App::Document& doc, const std::st
this->signalFinishSaveDocument(doc, filename);
}

void Application::slotChangePropertyEditor(const App::Document &doc, const App::Property &prop)
{
this->signalChangePropertyEditor(doc,prop);
}

//**************************************************************************
// Init, Destruct and singleton

Expand Down Expand Up @@ -1410,6 +1417,7 @@ void Application::initTypes(void)
App ::PropertyIntegerSet ::init();
App ::PropertyMap ::init();
App ::PropertyString ::init();
App ::PropertyPersistentObject ::init();
App ::PropertyUUID ::init();
App ::PropertyFont ::init();
App ::PropertyStringList ::init();
Expand Down
3 changes: 2 additions & 1 deletion src/App/Application.h
Expand Up @@ -184,7 +184,7 @@ class AppExport Application
/// signal on about removing a dynamic property
boost::signals2::signal<void (const App::Property&)> signalRemoveDynamicProperty;
/// signal on about changing the editor mode of a property
boost::signals2::signal<void (const App::Property&)> signalChangePropertyEditor;
boost::signals2::signal<void (const App::Document&, const App::Property&)> signalChangePropertyEditor;
//@}


Expand Down Expand Up @@ -342,6 +342,7 @@ class AppExport Application
void slotAbortTransaction(const App::Document&);
void slotStartSaveDocument(const App::Document&, const std::string&);
void slotFinishSaveDocument(const App::Document&, const std::string&);
void slotChangePropertyEditor(const App::Document&, const App::Property &);
//@}

private:
Expand Down
29 changes: 20 additions & 9 deletions src/App/Document.cpp
Expand Up @@ -931,15 +931,20 @@ bool Document::redo(void)
return false;
}

void Document::removePropertyOfObject(TransactionalObject* obj, const char* name)
void Document::addOrRemovePropertyOfObject(TransactionalObject* obj, Property *prop, bool add)
{
Property* prop = obj->getDynamicPropertyByName(name);
if (prop) {
if (d->activeUndoTransaction)
d->activeUndoTransaction->removeProperty(obj, prop);
for (auto it : mUndoTransactions)
it->removeProperty(obj, prop);
if (!prop || !obj)
return;
if(d->iUndoMode && !isPerformingTransaction() && !d->activeUndoTransaction) {
if(!testStatus(Restoring) || testStatus(Importing)) {
int tid=0;
const char *name = GetApplication().getActiveTransaction(&tid);
if(name && tid>0)
_openTransaction(name,tid);
}
}
if (d->activeUndoTransaction)
d->activeUndoTransaction->addOrRemoveProperty(obj, prop, add);
}

bool Document::isPerformingTransaction() const
Expand Down Expand Up @@ -1193,7 +1198,8 @@ void Document::setTransactionMode(int iMode)
//--------------------------------------------------------------------------
// constructor
//--------------------------------------------------------------------------
Document::Document(void)
Document::Document(const char *name)
: myName(name)
{
// Remark: In a constructor we should never increment a Python object as we cannot be sure
// if the Python interpreter gets a reference of it. E.g. if we increment but Python don't
Expand Down Expand Up @@ -1948,7 +1954,12 @@ bool Document::isSaved() const
*/
const char* Document::getName() const
{
return GetApplication().getDocumentName(this);
// return GetApplication().getDocumentName(this);
return myName.c_str();
}

std::string Document::getFullName() const {
return myName;
}

/// Remove all modifications. After this call The document becomes valid again.
Expand Down
11 changes: 8 additions & 3 deletions src/App/Document.h
Expand Up @@ -169,6 +169,7 @@ class AppExport Document : public App::PropertyContainer
boost::signals2::signal<void (const App::Document&)> signalCommitTransaction;
// signal an aborted transaction
boost::signals2::signal<void (const App::Document&)> signalAbortTransaction;
boost::signals2::signal<void (const App::Document&,const App::Property&)> signalChangePropertyEditor;
//@}

/** @name File handling of the document */
Expand Down Expand Up @@ -340,8 +341,8 @@ class AppExport Document : public App::PropertyContainer
bool redo() ;
/// returns true if the document is in an Transaction phase, e.g. currently performing a redo/undo or rollback
bool isPerformingTransaction() const;
/// \internal remove property from a transactional object with name \a name
void removePropertyOfObject(TransactionalObject*, const char*);
/// \internal add or remove property from a transactional object
void addOrRemovePropertyOfObject(TransactionalObject*, Property *prop, bool add);
//@}

/** @name dependency stuff */
Expand Down Expand Up @@ -389,6 +390,8 @@ class AppExport Document : public App::PropertyContainer

virtual PyObject *getPyObject(void);

virtual std::string getFullName() const override;

friend class Application;
/// because of transaction handling
friend class TransactionalObject;
Expand All @@ -401,7 +404,7 @@ class AppExport Document : public App::PropertyContainer

protected:
/// Construction
Document(void);
Document(const char *name = "");

void _removeObject(DocumentObject* pcObject);
void _addObject(DocumentObject* pcObject, const char* pObjectName);
Expand Down Expand Up @@ -438,6 +441,8 @@ class AppExport Document : public App::PropertyContainer
// pointer to the python class
Py::Object DocumentPythonObject;
struct DocumentP* d;

std::string myName;
};

template<typename T>
Expand Down
63 changes: 46 additions & 17 deletions src/App/DocumentObject.cpp
Expand Up @@ -525,27 +525,44 @@ void DocumentObject::setDocument(App::Document* doc)
onSettingDocument();
}

void DocumentObject::onAboutToRemoveProperty(const char* name)
bool DocumentObject::removeDynamicProperty(const char* name)
{
if (_pDoc) {
_pDoc->removePropertyOfObject(this, name);
if (!_pDoc)
return false;

Property* prop = getDynamicPropertyByName(name);
if (prop) {
auto expressions = ExpressionEngine.getExpressions();
std::vector<App::ObjectIdentifier> removeExpr;
Property* prop = getDynamicPropertyByName(name);
if(!prop || prop->testStatus(App::Property::LockDynamic))
return false;

for (auto it : expressions) {
if (it.first.getProperty() == prop) {
removeExpr.push_back(it.first);
}
}
if(prop->isDerivedFrom(PropertyLinkBase::getClassTypeId()))
clearOutListCache();

for (auto it : removeExpr) {
ExpressionEngine.setValue(it, boost::shared_ptr<Expression>());
}
_pDoc->addOrRemovePropertyOfObject(this, prop, false);

auto expressions = ExpressionEngine.getExpressions();
std::vector<App::ObjectIdentifier> removeExpr;

for (auto it : expressions) {
if (it.first.getProperty() == prop) {
removeExpr.push_back(it.first);
}
}

for (auto it : removeExpr) {
ExpressionEngine.setValue(it, boost::shared_ptr<Expression>());
}

return TransactionalObject::removeDynamicProperty(name);
}

App::Property* DocumentObject::addDynamicProperty(
const char* type, const char* name, const char* group, const char* doc,
short attr, bool ro, bool hidden)
{
auto prop = TransactionalObject::addDynamicProperty(type,name,group,doc,attr,ro,hidden);
if(prop && _pDoc)
_pDoc->addOrRemovePropertyOfObject(this, prop, true);
return prop;
}

void DocumentObject::onBeforeChange(const Property* prop)
Expand Down Expand Up @@ -576,8 +593,14 @@ void DocumentObject::onChanged(const Property* prop)
_pDoc->signalRelabelObject(*this);

// set object touched if it is an input property
if (!(prop->getType() & Prop_Output)) {
StatusBits.set(ObjectStatus::Touch);
if (!testStatus(ObjectStatus::NoTouch)
&& !(prop->getType() & Prop_Output)
&& !prop->testStatus(Property::Output))
{
if(!StatusBits.test(ObjectStatus::Touch)) {
FC_TRACE("touch '" << getFullName() << "' on change of '" << prop->getName() << "'");
StatusBits.set(ObjectStatus::Touch);
}
// must execute on document recompute
if (!(prop->getType() & Prop_NoRecompute))
StatusBits.set(ObjectStatus::Enforce);
Expand Down Expand Up @@ -1004,3 +1027,9 @@ bool DocumentObject::redirectSubName(std::ostringstream &, DocumentObject *, Doc
return false;
}

void DocumentObject::onPropertyStatusChanged(const Property &prop, unsigned long oldStatus) {
(void)oldStatus;
if(!Document::isAnyRestoring() && getNameInDocument() && getDocument())
getDocument()->signalChangePropertyEditor(*getDocument(),prop);
}

14 changes: 11 additions & 3 deletions src/App/DocumentObject.h
Expand Up @@ -115,7 +115,7 @@ class AppExport DocumentObject: public App::TransactionalObject
/// Return the object ID that is unique within its owner document
long getID() const {return _Id;}
/// Return the object full name of the form DocName#ObjName
std::string getFullName() const;
virtual std::string getFullName() const override;
virtual bool isAttachedToDocument() const;
virtual const char* detachFromDocument();
/// gets the document in which this Object is handled
Expand Down Expand Up @@ -365,6 +365,13 @@ class AppExport DocumentObject: public App::TransactionalObject
return _pcViewProviderName.c_str();
}

virtual bool removeDynamicProperty(const char* prop) override;

virtual App::Property* addDynamicProperty(
const char* type, const char* name=0,
const char* group=0, const char* doc=0,
short attr=0, bool ro=false, bool hidden=false) override;

/** Resolve the last document object referenced in the subname
*
* @param subname: dot separated subname
Expand Down Expand Up @@ -438,8 +445,6 @@ class AppExport DocumentObject: public App::TransactionalObject
void resetError(void){StatusBits.reset(ObjectStatus::Error);}
void setDocument(App::Document* doc);

/// \internal get called when removing a property of name \a prop
void onAboutToRemoveProperty(const char* prop);
/// get called before the value is changed
virtual void onBeforeChange(const Property* prop);
/// get called by the container when a property was changed
Expand All @@ -453,6 +458,9 @@ class AppExport DocumentObject: public App::TransactionalObject
/// get called when object is going to be removed from the document
virtual void unsetupObject();

/// get called when a property status has changed
virtual void onPropertyStatusChanged(const Property &prop, unsigned long oldStatus) override;

/// python object of this class and all descendent
protected: // attributes
Py::Object PythonObject;
Expand Down
29 changes: 26 additions & 3 deletions src/App/DocumentObjectPyImp.cpp
Expand Up @@ -672,20 +672,39 @@ PyObject* DocumentObjectPy::getPathsByOutList(PyObject *args)

PyObject *DocumentObjectPy::getCustomAttributes(const char* attr) const
{
// Dynamic proeprty is now directly supported in PropertyContainer. So we
// can comment out here and let PropertyContainerPy handle it.
#if 1
(void)attr;
#else
// search for dynamic property
Property* prop = getDocumentObjectPtr()->getDynamicPropertyByName(attr);
if (prop)
return prop->getPyObject();
else
#endif
return 0;
}

int DocumentObjectPy::setCustomAttributes(const char* attr, PyObject *obj)
{
// The following code is practically the same as in PropertyContainerPy,
// especially since now dynamic proeprty is directly supported in
// PropertyContainer. So we can comment out here and let PropertyContainerPy
// handle it.
#if 1
(void)attr;
(void)obj;
#else
// explicitly search for dynamic property
try {
Property* prop = getDocumentObjectPtr()->getDynamicPropertyByName(attr);
if (prop) {
if(prop->testStatus(Property::Immutable)) {
std::stringstream s;
s << "'DocumentObject' attribute '" << attr << "' is read-only";
throw Py::AttributeError(s.str());
}
prop->setPyObject(obj);
return 1;
}
Expand All @@ -700,7 +719,9 @@ int DocumentObjectPy::setCustomAttributes(const char* attr, PyObject *obj)
s << "Attribute (Name: " << attr << ") error: '" << exc.what() << "' ";
throw Py::AttributeError(s.str());
}
catch (...) {
catch (Py::AttributeError &) {
throw;
}catch (...) {
std::stringstream s;
s << "Unknown error in attribute " << attr;
throw Py::AttributeError(s.str());
Expand All @@ -710,8 +731,9 @@ int DocumentObjectPy::setCustomAttributes(const char* attr, PyObject *obj)
Property *prop = getDocumentObjectPtr()->getPropertyByName(attr);
if (prop) {
// Read-only attributes must not be set over its Python interface
short Type = getDocumentObjectPtr()->getPropertyType(prop);
if (Type & Prop_ReadOnly) {
if(prop->testStatus(Property::Immutable) ||
(getDocumentObjectPtr()->getPropertyType(prop) & Prop_ReadOnly))
{
std::stringstream s;
s << "'DocumentObject' attribute '" << attr << "' is read-only";
throw Py::AttributeError(s.str());
Expand All @@ -727,6 +749,7 @@ int DocumentObjectPy::setCustomAttributes(const char* attr, PyObject *obj)
}
return 1;
}
#endif

return 0;
}
Expand Down

0 comments on commit 819c807

Please sign in to comment.