App: Extension/ExtensionContainer - handle property change

==========================================================

Currently changes of name or type of properties in a property container are handled by:
void PropertyContainer::handleChangedPropertyName(Base::XMLReader &reader, const char * TypeName, const char *PropName)
void PropertyContainer::changedPropertyType(Base::XMLReader &reader, const char * TypeName, Property * prop)

There is no mechanism for handling property changes by extensions. Sometimes the solution is to explicitly call the extension
from the container. However, this is a breach of the SRP, as the container should not be in a position to decide whether the
extension needs or not handle property changes. The handling code of the container changes for two different reasons, for
adapting the container to a property change of its own, and for adapting that of a property of the extension.

Illustrating it with an example, following inheritance, it goes like this:
PropertyContainer => ExtensionContainer => TransactionalObject => ViewProvider
App::Extension => ViewProviderExtension

The extension is currently not notified by the ExtensionContainer that a property needs handling. So a change in a property of
a ViewProviderExtension needs code at the ViewProvider it was added to.

This commit provides a mechanism in ExtensionContainer to call the extensions so that they can handle property changes. This
functions:

  virtual bool extensionHandleChangedPropertyName(Base::XMLReader &reader, const char * TypeName, const char *PropName);
  virtual bool extensionHandleChangedPropertyType(Base::XMLReader &reader, const char * TypeName, Property * prop);

Containers should always call the base class for any unhandled property change. If a sub-class container of ExtensionContainer
handles property changes itself, but not the ones of the extensions, this call to the base class ultimately ensures that if the
property was not handled by the container hierarchy, any extension is given an opportunity to handle it.

Some examples:

* A container handles the extension property change or its own:

void ContainerSubClass::handleChangedPropertyType(...)
{
    if (prop == &PropertyOfExt) {

    }
    else if (prop == &PropertyOfCont) {

    }
    else {
        ContainerBaseClass::handleChangedPropertyType(...);
    }
}

* A container and the extension handle their own:

void ContainerSubClass::handleChangedPropertyType(...)
{
    if (prop == &PropertyOfCont) {

    }
    else {
        // This will call ExtensionContainer::handleChangedPropertyType
        ContainerBaseClass::handleChangedPropertyType(...);
    }
}

bool ExtensionSubClass::extensionHandleChangedPropertyType(...)
{
    if (prop == &PropertyOfCont) {

        return true;
    }
    return false;
}
This commit is contained in:
Abdullah Tahiri
2023-02-13 16:55:41 +01:00
committed by abdullahtahiriyo
parent f1428994b7
commit 4dea0df06c
6 changed files with 2200 additions and 2131 deletions

View File

@@ -1,195 +1,213 @@
/***************************************************************************
* Copyright (c) 2016 Stefan Tröger <stefantroeger@gmx.net> *
* *
* This file is part of the FreeCAD CAx development system. *
* *
* This library is free software; you can redistribute it and/or *
* modify it under the terms of the GNU Library General Public *
* License as published by the Free Software Foundation; either *
* version 2 of the License, or (at your option) any later version. *
* *
* This library is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU Library General Public License for more details. *
* *
* You should have received a copy of the GNU Library General Public *
* License along with this library; see the file COPYING.LIB. If not, *
* write to the Free Software Foundation, Inc., 59 Temple Place, *
* Suite 330, Boston, MA 02111-1307, USA *
* *
***************************************************************************/
#include "PreCompiled.h"
#ifndef _PreComp_
# include <cassert>
#endif
#include <Base/PyObjectBase.h>
#include "Extension.h"
#include "ExtensionContainer.h"
#include "ExtensionPython.h"
#include <ExtensionPy.h>
/* We do not use a standard property macro for type initiation. The reason is that we have the first
* PropertyData in the extension chain, there is no parent property data.
*/
EXTENSION_TYPESYSTEM_SOURCE_P(App::Extension)
const App::PropertyData * App::Extension::extensionGetPropertyDataPtr(){return &propertyData;}
const App::PropertyData & App::Extension::extensionGetPropertyData() const{return propertyData;}
App::PropertyData App::Extension::propertyData;
void App::Extension::init(){
assert(Extension::classTypeId == Base::Type::badType() && "don't init() twice!");
/* Set up entry in the type system. */
Extension::classTypeId = Base::Type::createType(Base::Type::badType(), "App::Extension",
Extension::create);
}
using namespace App;
Extension::Extension()
{
}
Extension::~Extension()
{
if (!ExtensionPythonObject.is(Py::_None())){
// Remark: The API of Py::Object has been changed to set whether the wrapper owns the passed
// Python object or not. In the constructor we forced the wrapper to own the object so we need
// not to dec'ref the Python object any more.
// But we must still invalidate the Python object because it need not to be
// destructed right now because the interpreter can own several references to it.
Base::PyObjectBase* obj = static_cast<Base::PyObjectBase*>(ExtensionPythonObject.ptr());
// Call before decrementing the reference counter, otherwise a heap error can occur
obj->setInvalid();
}
}
void Extension::initExtensionType(Base::Type type) {
m_extensionType = type;
if (m_extensionType.isBad())
throw Base::RuntimeError("Extension: Extension type not set");
}
void Extension::initExtension(ExtensionContainer* obj) {
if (m_extensionType.isBad())
throw Base::RuntimeError("Extension: Extension type not set");
//all properties are initialised without PropertyContainer father. Now that we know it we can
//finally finish the property initialisation
std::vector<Property*> list;
extensionGetPropertyData().getPropertyList(this, list);
for(Property* prop : list)
prop->setContainer(obj);
m_base = obj;
m_base->registerExtension( m_extensionType, this );
}
PyObject* Extension::getExtensionPyObject() {
if (ExtensionPythonObject.is(Py::_None())){
// ref counter is set to 1
auto grp = new ExtensionPy(this);
ExtensionPythonObject = Py::Object(grp,true);
}
return Py::new_reference_to(ExtensionPythonObject);
}
std::string Extension::name() const {
if (m_extensionType.isBad())
throw Base::RuntimeError("Extension::name: Extension type not set");
std::string temp(m_extensionType.getName());
std::string::size_type pos = temp.find_last_of(':');
if (pos != std::string::npos)
return temp.substr(pos+1);
else
return std::string();
}
Property* Extension::extensionGetPropertyByName(const char* name) const {
return extensionGetPropertyData().getPropertyByName(this, name);
}
short int Extension::extensionGetPropertyType(const Property* prop) const {
return extensionGetPropertyData().getType(this, prop);
}
short int Extension::extensionGetPropertyType(const char* name) const {
return extensionGetPropertyData().getType(this, name);
}
const char* Extension::extensionGetPropertyName(const Property* prop) const {
return extensionGetPropertyData().getName(this,prop);
}
const char* Extension::extensionGetPropertyGroup(const Property* prop) const {
return extensionGetPropertyData().getGroup(this,prop);
}
const char* Extension::extensionGetPropertyGroup(const char* name) const {
return extensionGetPropertyData().getGroup(this,name);
}
const char* Extension::extensionGetPropertyDocumentation(const Property* prop) const {
return extensionGetPropertyData().getDocumentation(this, prop);
}
const char* Extension::extensionGetPropertyDocumentation(const char* name) const {
return extensionGetPropertyData().getDocumentation(this, name);
}
void Extension::extensionGetPropertyList(std::vector< Property* >& List) const {
extensionGetPropertyData().getPropertyList(this, List);
}
void Extension::extensionGetPropertyMap(std::map< std::string, Property* >& Map) const {
extensionGetPropertyData().getPropertyMap(this, Map);
}
void Extension::initExtensionSubclass(Base::Type& toInit, const char* ClassName, const char* ParentName,
Base::Type::instantiationMethod method) {
// don't init twice!
assert(toInit == Base::Type::badType());
// get the parent class
Base::Type parentType(Base::Type::fromName(ParentName));
// forgot init parent!
assert(parentType != Base::Type::badType() );
// create the new type
toInit = Base::Type::createType(parentType, ClassName, method);
}
namespace App {
EXTENSION_PROPERTY_SOURCE_TEMPLATE(App::ExtensionPython, App::ExtensionPython::Inherited)
// explicit template instantiation
template class AppExport ExtensionPythonT<Extension>;
}
/***************************************************************************
* Copyright (c) 2016 Stefan Tröger <stefantroeger@gmx.net> *
* *
* This file is part of the FreeCAD CAx development system. *
* *
* This library is free software; you can redistribute it and/or *
* modify it under the terms of the GNU Library General Public *
* License as published by the Free Software Foundation; either *
* version 2 of the License, or (at your option) any later version. *
* *
* This library is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU Library General Public License for more details. *
* *
* You should have received a copy of the GNU Library General Public *
* License along with this library; see the file COPYING.LIB. If not, *
* write to the Free Software Foundation, Inc., 59 Temple Place, *
* Suite 330, Boston, MA 02111-1307, USA *
* *
***************************************************************************/
#include "PreCompiled.h"
#ifndef _PreComp_
# include <cassert>
#endif
#include <Base/PyObjectBase.h>
#include "Extension.h"
#include "ExtensionContainer.h"
#include "ExtensionPython.h"
#include <ExtensionPy.h>
/* We do not use a standard property macro for type initiation. The reason is that we have the first
* PropertyData in the extension chain, there is no parent property data.
*/
EXTENSION_TYPESYSTEM_SOURCE_P(App::Extension)
const App::PropertyData * App::Extension::extensionGetPropertyDataPtr(){return &propertyData;}
const App::PropertyData & App::Extension::extensionGetPropertyData() const{return propertyData;}
App::PropertyData App::Extension::propertyData;
void App::Extension::init(){
assert(Extension::classTypeId == Base::Type::badType() && "don't init() twice!");
/* Set up entry in the type system. */
Extension::classTypeId = Base::Type::createType(Base::Type::badType(), "App::Extension",
Extension::create);
}
using namespace App;
Extension::Extension()
{
}
Extension::~Extension()
{
if (!ExtensionPythonObject.is(Py::_None())){
// Remark: The API of Py::Object has been changed to set whether the wrapper owns the passed
// Python object or not. In the constructor we forced the wrapper to own the object so we need
// not to dec'ref the Python object any more.
// But we must still invalidate the Python object because it need not to be
// destructed right now because the interpreter can own several references to it.
Base::PyObjectBase* obj = static_cast<Base::PyObjectBase*>(ExtensionPythonObject.ptr());
// Call before decrementing the reference counter, otherwise a heap error can occur
obj->setInvalid();
}
}
void Extension::initExtensionType(Base::Type type) {
m_extensionType = type;
if (m_extensionType.isBad())
throw Base::RuntimeError("Extension: Extension type not set");
}
void Extension::initExtension(ExtensionContainer* obj) {
if (m_extensionType.isBad())
throw Base::RuntimeError("Extension: Extension type not set");
//all properties are initialised without PropertyContainer father. Now that we know it we can
//finally finish the property initialisation
std::vector<Property*> list;
extensionGetPropertyData().getPropertyList(this, list);
for(Property* prop : list)
prop->setContainer(obj);
m_base = obj;
m_base->registerExtension( m_extensionType, this );
}
PyObject* Extension::getExtensionPyObject() {
if (ExtensionPythonObject.is(Py::_None())){
// ref counter is set to 1
auto grp = new ExtensionPy(this);
ExtensionPythonObject = Py::Object(grp,true);
}
return Py::new_reference_to(ExtensionPythonObject);
}
std::string Extension::name() const {
if (m_extensionType.isBad())
throw Base::RuntimeError("Extension::name: Extension type not set");
std::string temp(m_extensionType.getName());
std::string::size_type pos = temp.find_last_of(':');
if (pos != std::string::npos)
return temp.substr(pos+1);
else
return std::string();
}
Property* Extension::extensionGetPropertyByName(const char* name) const {
return extensionGetPropertyData().getPropertyByName(this, name);
}
short int Extension::extensionGetPropertyType(const Property* prop) const {
return extensionGetPropertyData().getType(this, prop);
}
short int Extension::extensionGetPropertyType(const char* name) const {
return extensionGetPropertyData().getType(this, name);
}
const char* Extension::extensionGetPropertyName(const Property* prop) const {
return extensionGetPropertyData().getName(this,prop);
}
const char* Extension::extensionGetPropertyGroup(const Property* prop) const {
return extensionGetPropertyData().getGroup(this,prop);
}
const char* Extension::extensionGetPropertyGroup(const char* name) const {
return extensionGetPropertyData().getGroup(this,name);
}
const char* Extension::extensionGetPropertyDocumentation(const Property* prop) const {
return extensionGetPropertyData().getDocumentation(this, prop);
}
const char* Extension::extensionGetPropertyDocumentation(const char* name) const {
return extensionGetPropertyData().getDocumentation(this, name);
}
void Extension::extensionGetPropertyList(std::vector< Property* >& List) const {
extensionGetPropertyData().getPropertyList(this, List);
}
void Extension::extensionGetPropertyMap(std::map< std::string, Property* >& Map) const {
extensionGetPropertyData().getPropertyMap(this, Map);
}
void Extension::initExtensionSubclass(Base::Type& toInit, const char* ClassName, const char* ParentName,
Base::Type::instantiationMethod method) {
// don't init twice!
assert(toInit == Base::Type::badType());
// get the parent class
Base::Type parentType(Base::Type::fromName(ParentName));
// forgot init parent!
assert(parentType != Base::Type::badType() );
// create the new type
toInit = Base::Type::createType(parentType, ClassName, method);
}
bool Extension::extensionHandleChangedPropertyName(Base::XMLReader &reader, const char * TypeName, const char *PropName)
{
(void) reader;
(void) TypeName;
(void) PropName;
return false;
};
bool Extension::extensionHandleChangedPropertyType(Base::XMLReader &reader, const char * TypeName, Property * prop)
{
(void) reader;
(void) TypeName;
(void) prop;
return false;
};
namespace App {
EXTENSION_PROPERTY_SOURCE_TEMPLATE(App::ExtensionPython, App::ExtensionPython::Inherited)
// explicit template instantiation
template class AppExport ExtensionPythonT<Extension>;
}

View File

@@ -1,323 +1,328 @@
/***************************************************************************
* Copyright (c) 2016 Stefan Tröger <stefantroeger@gmx.net> *
* *
* This file is part of the FreeCAD CAx development system. *
* *
* This library is free software; you can redistribute it and/or *
* modify it under the terms of the GNU Library General Public *
* License as published by the Free Software Foundation; either *
* version 2 of the License, or (at your option) any later version. *
* *
* This library is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU Library General Public License for more details. *
* *
* You should have received a copy of the GNU Library General Public *
* License along with this library; see the file COPYING.LIB. If not, *
* write to the Free Software Foundation, Inc., 59 Temple Place, *
* Suite 330, Boston, MA 02111-1307, USA *
* *
***************************************************************************/
#ifndef APP_EXTENSION_H
#define APP_EXTENSION_H
#include "PropertyContainer.h"
#include <Base/SmartPtrPy.h>
namespace App {
class ExtensionContainer;
/// define Extension types
#define EXTENSION_TYPESYSTEM_HEADER() \
public: \
static Base::Type getExtensionClassTypeId(void); \
virtual Base::Type getExtensionTypeId(void) const; \
static void init(void);\
static void *create(void);\
private: \
static Base::Type classTypeId
/// Like EXTENSION_TYPESYSTEM_HEADER, with getExtensionTypeId declared override
#define EXTENSION_TYPESYSTEM_HEADER_WITH_OVERRIDE() \
public: \
static Base::Type getExtensionClassTypeId(void); \
virtual Base::Type getExtensionTypeId(void) const override; \
static void init(void);\
static void *create(void);\
private: \
static Base::Type classTypeId
/// define to implement a subclass of Base::BaseClass
#define EXTENSION_TYPESYSTEM_SOURCE_P(_class_) \
Base::Type _class_::getExtensionClassTypeId(void) { return _class_::classTypeId; } \
Base::Type _class_::getExtensionTypeId(void) const { return _class_::classTypeId; } \
Base::Type _class_::classTypeId = Base::Type::badType(); \
void * _class_::create(void){\
return new _class_ ();\
}
/// define to implement a subclass of Base::BaseClass
#define EXTENSION_TYPESYSTEM_SOURCE_ABSTRACT_P(_class_) \
Base::Type _class_::getExtensionClassTypeId(void) { return _class_::classTypeId; } \
Base::Type _class_::getExtensionTypeId(void) const { return _class_::classTypeId; } \
Base::Type _class_::classTypeId = Base::Type::badType(); \
void * _class_::create(void){return 0;}
/// define to implement a subclass of Base::BaseClass
#define EXTENSION_TYPESYSTEM_SOURCE(_class_, _parentclass_) \
EXTENSION_TYPESYSTEM_SOURCE_P(_class_)\
void _class_::init(void){\
initExtensionSubclass(_class_::classTypeId, #_class_ , #_parentclass_, &(_class_::create) ); \
}
#define EXTENSION_TYPESYSTEM_SOURCE_TEMPLATE(_class_) \
template<> Base::Type _class_::classTypeId = Base::Type::badType(); \
template<> Base::Type _class_::getExtensionClassTypeId(void) { return _class_::classTypeId; } \
template<> Base::Type _class_::getExtensionTypeId(void) const { return _class_::classTypeId; } \
template<> void * _class_::create(void){\
return new _class_ ();\
}
// init property stuff
#define EXTENSION_PROPERTY_HEADER(_class_) \
EXTENSION_TYPESYSTEM_HEADER(); \
protected: \
static const App::PropertyData * extensionGetPropertyDataPtr(void); \
virtual const App::PropertyData &extensionGetPropertyData(void) const; \
private: \
static App::PropertyData propertyData
/// Like EXTENSION_PROPERTY_HEADER, but with override declarations.
#define EXTENSION_PROPERTY_HEADER_WITH_OVERRIDE(_class_) \
EXTENSION_TYPESYSTEM_HEADER_WITH_OVERRIDE(); \
protected: \
static const App::PropertyData * extensionGetPropertyDataPtr(void); \
virtual const App::PropertyData &extensionGetPropertyData(void) const override; \
private: \
static App::PropertyData propertyData
#define EXTENSION_PROPERTY_SOURCE(_class_, _parentclass_) \
EXTENSION_TYPESYSTEM_SOURCE_P(_class_)\
const App::PropertyData * _class_::extensionGetPropertyDataPtr(void){return &propertyData;} \
const App::PropertyData & _class_::extensionGetPropertyData(void) const{return propertyData;} \
App::PropertyData _class_::propertyData; \
void _class_::init(void){\
initExtensionSubclass(_class_::classTypeId, #_class_ , #_parentclass_, &(_class_::create) ); \
_class_::propertyData.parentPropertyData = _parentclass_::extensionGetPropertyDataPtr();\
}
#define EXTENSION_PROPERTY_SOURCE_TEMPLATE(_class_, _parentclass_) \
EXTENSION_TYPESYSTEM_SOURCE_TEMPLATE(_class_)\
template<> App::PropertyData _class_::propertyData = App::PropertyData(); \
template<> const App::PropertyData * _class_::extensionGetPropertyDataPtr(void){return &propertyData;} \
template<> const App::PropertyData & _class_::extensionGetPropertyData(void) const{return propertyData;} \
template<> void _class_::init(void){\
initExtensionSubclass(_class_::classTypeId, #_class_ , #_parentclass_, &(_class_::create) ); \
_class_::propertyData.parentPropertyData = _parentclass_::extensionGetPropertyDataPtr();\
}
/**
* @brief Base class for all extension that can be added to a DocumentObject
*
* For general documentation on why extension system exists and how to use it see the ExtensionContainer
* documentation. Following is a description howto create custom extensions.
*
* Extensions are like every other FreeCAD object and based on properties. All information storage
* and persistence should be achieved by use of those. Additional any number of methods can be
* added to provide functionality around the properties. There are 3 small difference to normal objects:
* 1. They must be derived from Extension class
* 2. Properties must be handled with special extension macros
* 3. Extensions must be initialised
* This works as simple as
* @code
* class MyExtension : public Extension {
* EXTENSION_PROPERTY_HEADER(MyExtension);
* PropertyInt MyProp;
* virtual bool overridableMethod(DocumentObject* obj) {};
* };
*
* EXTENSION_PROPERTY_SOURCE(App::MyExtension, App::Extension)
* MyExtension::MyExtension() {
*
* EXTENSION_ADD_PROPERTY(MyProp, (0)) *
* initExtension(MyExtension::getExtensionClassTypeId());
* }
* using MyExtensionPython = ExtensionPythonT<MyExtension>;
* @endcode
*
* The special python extension type created above is important, as only those python extensions
* can be added to an object from python. It does not work to add the c++ version directly there.
*
* Note that every method of the extension becomes part of the extended object when added from c++.
* This means one should carefully design the API and make only necessary methods public or protected.
* Every internal method should be private.
*
* The automatic availability of methods in the class does not hold for the python interface, only
* for c++ classes. This is like every where else in FreeCAD, there is no automatic creation of python
* API from c++ classes. Hence the extension creator must also create a custom python object of its
* extension, which works exactly like the normal FreeCAD python object workflow. There is nothing
* special at all for extension python objects, the normal xml + imp.cpp approach is used. It must
* only be taken care that the objects father is the correct extension base class. Of course also
* make sure your extension returns the correct python object in its "getPyObject" call.
* Every method you create in the extensions python will be later added to an extended object. This
* happens automatically for both, c++ and python extension, if "getPyObject" returns the correct
* python object. No extra work needs to be done.
*
* A special case that needs to be handled for extensions is the possibility of overridden methods.
* Often it is desired to customise extension behaviour by allowing the user to override methods
* provided by the extension. On c++ side this is trivial, such methods are simply marked as "virtual"
* and can than be overridden in any derived class. This is more involved for the python interface and
* here special care needs to be taken.
*
* As already seen above one needs to create a special ExtensionPythonT<> object for extension from
* python. This is done exactly for the purpose of allowing to have overridable methods. The
* ExtensionPythonT wrapper adds a proxy property which holds a PyObject which itself will contain
* the implementations for the overridden methods. This design is equal to the ObjectPythonT<> design
* of normal document objects.
* As this wrapper inherits the c++ extension class it can also override the virtual functions the
* user designed to be overridden. What it should do at a call of the virtual method is to check if
* this method is implemented in the proxy object and if so call it, and if not call the normal
* c++ version. It is the extensions creators responsibility to implement this check and call behaviour
* for every overridable method.
* This is done by creating a custom wrapper just like ExtensionPythonT<> and overriding all virtual
* methods.
* @code
* template<typename ExtensionT> class MyExtensionPythonT : public ExtensionT {
* public:
*
* MyExtensionPythonT() {}
* virtual ~MyExtensionPythonT() {}
*
* virtual bool overridableMethod(DocumentObject* obj) override {
* Py::Object pyobj = Py::asObject(obj->getPyObject());
* EXTENSION_PROXY_ONEARG(allowObject, pyobj);
*
* if(result.isNone())
* ExtensionT::allowObject(obj);
*
* if(result.isBoolean())
* return result.isTrue();
*
* return false;
* };
* };
* @endcode
* @Note As seen in the code there are multiple helper macros to ease the repetitive work of querying
* and calling methods of the proxy object. See the macro documentation for how to use them.
*
* To ensure that your wrapper is used when a extension is created from python the extension type must
* be exposed as follows:
* @code
* using MyExtensionPython = ExtensionPythonT<MyExtensionPythonT<MyExtension>>;
* @endcode
*
* This boilerplate is absolutely necessary to allow overridable methods in python and it is the
* extension creator's responsibility to ensure full implementation.
*
*/
class AppExport Extension
{
//The cass does not have properties itself, but it is important to provide the property access
//functions. see cpp file for details
EXTENSION_PROPERTY_HEADER(App::Extension);
public:
Extension();
virtual ~Extension();
virtual void initExtension(App::ExtensionContainer* obj);
App::ExtensionContainer* getExtendedContainer() {return m_base;}
const App::ExtensionContainer* getExtendedContainer() const {return m_base;}
//get extension name without namespace
std::string name() const;
bool isPythonExtension() {return m_isPythonExtension;}
virtual PyObject* getExtensionPyObject();
/** @name Access properties */
//@{
/// find a property by its name
virtual Property *extensionGetPropertyByName(const char* name) const;
/// get the name of a property
virtual const char* extensionGetPropertyName(const Property* prop) const;
/// get all properties of the class (including properties of the parent)
virtual void extensionGetPropertyMap(std::map<std::string,Property*> &Map) const;
/// get all properties of the class (including properties of the parent)
virtual void extensionGetPropertyList(std::vector<Property*> &List) const;
/// get the Type of a Property
virtual short extensionGetPropertyType(const Property* prop) const;
/// get the Type of a named Property
virtual short extensionGetPropertyType(const char *name) const;
/// get the Group of a Property
virtual const char* extensionGetPropertyGroup(const Property* prop) const;
/// get the Group of a named Property
virtual const char* extensionGetPropertyGroup(const char *name) const;
/// get the Group of a Property
virtual const char* extensionGetPropertyDocumentation(const Property* prop) const;
/// get the Group of a named Property
virtual const char* extensionGetPropertyDocumentation(const char *name) const;
//@}
/** @name Persistence */
//@{
virtual void extensionSave(Base::Writer&) const {}
virtual void extensionRestore(Base::XMLReader&) {}
//@}
/** @name TypeHandling */
//@{
bool extensionIsDerivedFrom(const Base::Type type) const {return getExtensionTypeId().isDerivedFrom(type);}
protected:
static void initExtensionSubclass(Base::Type &toInit,const char* ClassName, const char *ParentName,
Base::Type::instantiationMethod method=nullptr);
//@}
virtual void extensionOnChanged(const Property* p) {(void)(p);}
friend class App::ExtensionContainer;
protected:
void initExtensionType(Base::Type type);
bool m_isPythonExtension = false;
Py::SmartPtr ExtensionPythonObject;
private:
Base::Type m_extensionType;
App::ExtensionContainer* m_base = nullptr;
};
// Property define
#define _EXTENSION_ADD_PROPERTY(_name, _prop_, _defaultval_) \
do { \
this->_prop_.setValue _defaultval_;\
propertyData.addProperty(static_cast<App::Extension*>(this), _name, &this->_prop_); \
} while (0)
#define EXTENSION_ADD_PROPERTY(_prop_, _defaultval_) \
_EXTENSION_ADD_PROPERTY(#_prop_, _prop_, _defaultval_)
#define _EXTENSION_ADD_PROPERTY_TYPE(_name, _prop_, _defaultval_, _group_,_type_,_Docu_) \
do { \
this->_prop_.setValue _defaultval_;\
propertyData.addProperty(static_cast<App::Extension*>(this), _name, &this->_prop_, (_group_),(_type_),(_Docu_)); \
} while (0)
#define EXTENSION_ADD_PROPERTY_TYPE(_prop_, _defaultval_, _group_,_type_,_Docu_) \
_EXTENSION_ADD_PROPERTY_TYPE(#_prop_, _prop_, _defaultval_, _group_,_type_,_Docu_)
} //App
#endif // APP_EXTENSION_H
/***************************************************************************
* Copyright (c) 2016 Stefan Tröger <stefantroeger@gmx.net> *
* *
* This file is part of the FreeCAD CAx development system. *
* *
* This library is free software; you can redistribute it and/or *
* modify it under the terms of the GNU Library General Public *
* License as published by the Free Software Foundation; either *
* version 2 of the License, or (at your option) any later version. *
* *
* This library is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU Library General Public License for more details. *
* *
* You should have received a copy of the GNU Library General Public *
* License along with this library; see the file COPYING.LIB. If not, *
* write to the Free Software Foundation, Inc., 59 Temple Place, *
* Suite 330, Boston, MA 02111-1307, USA *
* *
***************************************************************************/
#ifndef APP_EXTENSION_H
#define APP_EXTENSION_H
#include "PropertyContainer.h"
#include <Base/SmartPtrPy.h>
namespace App {
class ExtensionContainer;
/// define Extension types
#define EXTENSION_TYPESYSTEM_HEADER() \
public: \
static Base::Type getExtensionClassTypeId(void); \
virtual Base::Type getExtensionTypeId(void) const; \
static void init(void);\
static void *create(void);\
private: \
static Base::Type classTypeId
/// Like EXTENSION_TYPESYSTEM_HEADER, with getExtensionTypeId declared override
#define EXTENSION_TYPESYSTEM_HEADER_WITH_OVERRIDE() \
public: \
static Base::Type getExtensionClassTypeId(void); \
virtual Base::Type getExtensionTypeId(void) const override; \
static void init(void);\
static void *create(void);\
private: \
static Base::Type classTypeId
/// define to implement a subclass of Base::BaseClass
#define EXTENSION_TYPESYSTEM_SOURCE_P(_class_) \
Base::Type _class_::getExtensionClassTypeId(void) { return _class_::classTypeId; } \
Base::Type _class_::getExtensionTypeId(void) const { return _class_::classTypeId; } \
Base::Type _class_::classTypeId = Base::Type::badType(); \
void * _class_::create(void){\
return new _class_ ();\
}
/// define to implement a subclass of Base::BaseClass
#define EXTENSION_TYPESYSTEM_SOURCE_ABSTRACT_P(_class_) \
Base::Type _class_::getExtensionClassTypeId(void) { return _class_::classTypeId; } \
Base::Type _class_::getExtensionTypeId(void) const { return _class_::classTypeId; } \
Base::Type _class_::classTypeId = Base::Type::badType(); \
void * _class_::create(void){return 0;}
/// define to implement a subclass of Base::BaseClass
#define EXTENSION_TYPESYSTEM_SOURCE(_class_, _parentclass_) \
EXTENSION_TYPESYSTEM_SOURCE_P(_class_)\
void _class_::init(void){\
initExtensionSubclass(_class_::classTypeId, #_class_ , #_parentclass_, &(_class_::create) ); \
}
#define EXTENSION_TYPESYSTEM_SOURCE_TEMPLATE(_class_) \
template<> Base::Type _class_::classTypeId = Base::Type::badType(); \
template<> Base::Type _class_::getExtensionClassTypeId(void) { return _class_::classTypeId; } \
template<> Base::Type _class_::getExtensionTypeId(void) const { return _class_::classTypeId; } \
template<> void * _class_::create(void){\
return new _class_ ();\
}
// init property stuff
#define EXTENSION_PROPERTY_HEADER(_class_) \
EXTENSION_TYPESYSTEM_HEADER(); \
protected: \
static const App::PropertyData * extensionGetPropertyDataPtr(void); \
virtual const App::PropertyData &extensionGetPropertyData(void) const; \
private: \
static App::PropertyData propertyData
/// Like EXTENSION_PROPERTY_HEADER, but with override declarations.
#define EXTENSION_PROPERTY_HEADER_WITH_OVERRIDE(_class_) \
EXTENSION_TYPESYSTEM_HEADER_WITH_OVERRIDE(); \
protected: \
static const App::PropertyData * extensionGetPropertyDataPtr(void); \
virtual const App::PropertyData &extensionGetPropertyData(void) const override; \
private: \
static App::PropertyData propertyData
#define EXTENSION_PROPERTY_SOURCE(_class_, _parentclass_) \
EXTENSION_TYPESYSTEM_SOURCE_P(_class_)\
const App::PropertyData * _class_::extensionGetPropertyDataPtr(void){return &propertyData;} \
const App::PropertyData & _class_::extensionGetPropertyData(void) const{return propertyData;} \
App::PropertyData _class_::propertyData; \
void _class_::init(void){\
initExtensionSubclass(_class_::classTypeId, #_class_ , #_parentclass_, &(_class_::create) ); \
_class_::propertyData.parentPropertyData = _parentclass_::extensionGetPropertyDataPtr();\
}
#define EXTENSION_PROPERTY_SOURCE_TEMPLATE(_class_, _parentclass_) \
EXTENSION_TYPESYSTEM_SOURCE_TEMPLATE(_class_)\
template<> App::PropertyData _class_::propertyData = App::PropertyData(); \
template<> const App::PropertyData * _class_::extensionGetPropertyDataPtr(void){return &propertyData;} \
template<> const App::PropertyData & _class_::extensionGetPropertyData(void) const{return propertyData;} \
template<> void _class_::init(void){\
initExtensionSubclass(_class_::classTypeId, #_class_ , #_parentclass_, &(_class_::create) ); \
_class_::propertyData.parentPropertyData = _parentclass_::extensionGetPropertyDataPtr();\
}
/**
* @brief Base class for all extension that can be added to a DocumentObject
*
* For general documentation on why extension system exists and how to use it see the ExtensionContainer
* documentation. Following is a description howto create custom extensions.
*
* Extensions are like every other FreeCAD object and based on properties. All information storage
* and persistence should be achieved by use of those. Additional any number of methods can be
* added to provide functionality around the properties. There are 3 small difference to normal objects:
* 1. They must be derived from Extension class
* 2. Properties must be handled with special extension macros
* 3. Extensions must be initialised
* This works as simple as
* @code
* class MyExtension : public Extension {
* EXTENSION_PROPERTY_HEADER(MyExtension);
* PropertyInt MyProp;
* virtual bool overridableMethod(DocumentObject* obj) {};
* };
*
* EXTENSION_PROPERTY_SOURCE(App::MyExtension, App::Extension)
* MyExtension::MyExtension() {
*
* EXTENSION_ADD_PROPERTY(MyProp, (0)) *
* initExtension(MyExtension::getExtensionClassTypeId());
* }
* using MyExtensionPython = ExtensionPythonT<MyExtension>;
* @endcode
*
* The special python extension type created above is important, as only those python extensions
* can be added to an object from python. It does not work to add the c++ version directly there.
*
* Note that every method of the extension becomes part of the extended object when added from c++.
* This means one should carefully design the API and make only necessary methods public or protected.
* Every internal method should be private.
*
* The automatic availability of methods in the class does not hold for the python interface, only
* for c++ classes. This is like every where else in FreeCAD, there is no automatic creation of python
* API from c++ classes. Hence the extension creator must also create a custom python object of its
* extension, which works exactly like the normal FreeCAD python object workflow. There is nothing
* special at all for extension python objects, the normal xml + imp.cpp approach is used. It must
* only be taken care that the objects father is the correct extension base class. Of course also
* make sure your extension returns the correct python object in its "getPyObject" call.
* Every method you create in the extensions python will be later added to an extended object. This
* happens automatically for both, c++ and python extension, if "getPyObject" returns the correct
* python object. No extra work needs to be done.
*
* A special case that needs to be handled for extensions is the possibility of overridden methods.
* Often it is desired to customise extension behaviour by allowing the user to override methods
* provided by the extension. On c++ side this is trivial, such methods are simply marked as "virtual"
* and can than be overridden in any derived class. This is more involved for the python interface and
* here special care needs to be taken.
*
* As already seen above one needs to create a special ExtensionPythonT<> object for extension from
* python. This is done exactly for the purpose of allowing to have overridable methods. The
* ExtensionPythonT wrapper adds a proxy property which holds a PyObject which itself will contain
* the implementations for the overridden methods. This design is equal to the ObjectPythonT<> design
* of normal document objects.
* As this wrapper inherits the c++ extension class it can also override the virtual functions the
* user designed to be overridden. What it should do at a call of the virtual method is to check if
* this method is implemented in the proxy object and if so call it, and if not call the normal
* c++ version. It is the extensions creators responsibility to implement this check and call behaviour
* for every overridable method.
* This is done by creating a custom wrapper just like ExtensionPythonT<> and overriding all virtual
* methods.
* @code
* template<typename ExtensionT> class MyExtensionPythonT : public ExtensionT {
* public:
*
* MyExtensionPythonT() {}
* virtual ~MyExtensionPythonT() {}
*
* virtual bool overridableMethod(DocumentObject* obj) override {
* Py::Object pyobj = Py::asObject(obj->getPyObject());
* EXTENSION_PROXY_ONEARG(allowObject, pyobj);
*
* if(result.isNone())
* ExtensionT::allowObject(obj);
*
* if(result.isBoolean())
* return result.isTrue();
*
* return false;
* };
* };
* @endcode
* @Note As seen in the code there are multiple helper macros to ease the repetitive work of querying
* and calling methods of the proxy object. See the macro documentation for how to use them.
*
* To ensure that your wrapper is used when a extension is created from python the extension type must
* be exposed as follows:
* @code
* using MyExtensionPython = ExtensionPythonT<MyExtensionPythonT<MyExtension>>;
* @endcode
*
* This boilerplate is absolutely necessary to allow overridable methods in python and it is the
* extension creator's responsibility to ensure full implementation.
*
*/
class AppExport Extension
{
//The cass does not have properties itself, but it is important to provide the property access
//functions. see cpp file for details
EXTENSION_PROPERTY_HEADER(App::Extension);
public:
Extension();
virtual ~Extension();
virtual void initExtension(App::ExtensionContainer* obj);
App::ExtensionContainer* getExtendedContainer() {return m_base;}
const App::ExtensionContainer* getExtendedContainer() const {return m_base;}
//get extension name without namespace
std::string name() const;
bool isPythonExtension() {return m_isPythonExtension;}
virtual PyObject* getExtensionPyObject();
/** @name Access properties */
//@{
/// find a property by its name
virtual Property *extensionGetPropertyByName(const char* name) const;
/// get the name of a property
virtual const char* extensionGetPropertyName(const Property* prop) const;
/// get all properties of the class (including properties of the parent)
virtual void extensionGetPropertyMap(std::map<std::string,Property*> &Map) const;
/// get all properties of the class (including properties of the parent)
virtual void extensionGetPropertyList(std::vector<Property*> &List) const;
/// get the Type of a Property
virtual short extensionGetPropertyType(const Property* prop) const;
/// get the Type of a named Property
virtual short extensionGetPropertyType(const char *name) const;
/// get the Group of a Property
virtual const char* extensionGetPropertyGroup(const Property* prop) const;
/// get the Group of a named Property
virtual const char* extensionGetPropertyGroup(const char *name) const;
/// get the Group of a Property
virtual const char* extensionGetPropertyDocumentation(const Property* prop) const;
/// get the Group of a named Property
virtual const char* extensionGetPropertyDocumentation(const char *name) const;
//@}
/** @name Persistence */
//@{
virtual void extensionSave(Base::Writer&) const {}
virtual void extensionRestore(Base::XMLReader&) {}
//@}
/** @name TypeHandling */
//@{
bool extensionIsDerivedFrom(const Base::Type type) const {return getExtensionTypeId().isDerivedFrom(type);}
protected:
static void initExtensionSubclass(Base::Type &toInit,const char* ClassName, const char *ParentName,
Base::Type::instantiationMethod method=nullptr);
//@}
virtual void extensionOnChanged(const Property* p) {(void)(p);}
/// returns true if the property name change was handled by the extension.
virtual bool extensionHandleChangedPropertyName(Base::XMLReader &reader, const char * TypeName, const char *PropName);
/// returns true if the property type change was handled by the extension.
virtual bool extensionHandleChangedPropertyType(Base::XMLReader &reader, const char * TypeName, Property * prop);
friend class App::ExtensionContainer;
protected:
void initExtensionType(Base::Type type);
bool m_isPythonExtension = false;
Py::SmartPtr ExtensionPythonObject;
private:
Base::Type m_extensionType;
App::ExtensionContainer* m_base = nullptr;
};
// Property define
#define _EXTENSION_ADD_PROPERTY(_name, _prop_, _defaultval_) \
do { \
this->_prop_.setValue _defaultval_;\
propertyData.addProperty(static_cast<App::Extension*>(this), _name, &this->_prop_); \
} while (0)
#define EXTENSION_ADD_PROPERTY(_prop_, _defaultval_) \
_EXTENSION_ADD_PROPERTY(#_prop_, _prop_, _defaultval_)
#define _EXTENSION_ADD_PROPERTY_TYPE(_name, _prop_, _defaultval_, _group_,_type_,_Docu_) \
do { \
this->_prop_.setValue _defaultval_;\
propertyData.addProperty(static_cast<App::Extension*>(this), _name, &this->_prop_, (_group_),(_type_),(_Docu_)); \
} while (0)
#define EXTENSION_ADD_PROPERTY_TYPE(_prop_, _defaultval_, _group_,_type_,_Docu_) \
_EXTENSION_ADD_PROPERTY_TYPE(#_prop_, _prop_, _defaultval_, _group_,_type_,_Docu_)
} //App
#endif // APP_EXTENSION_H

View File

@@ -1,418 +1,446 @@
/***************************************************************************
* Copyright (c) 2016 Stefan Tröger <stefantroeger@gmx.net> *
* *
* This file is part of the FreeCAD CAx development system. *
* *
* This library is free software; you can redistribute it and/or *
* modify it under the terms of the GNU Library General Public *
* License as published by the Free Software Foundation; either *
* version 2 of the License, or (at your option) any later version. *
* *
* This library is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU Library General Public License for more details. *
* *
* You should have received a copy of the GNU Library General Public *
* License along with this library; see the file COPYING.LIB. If not, *
* write to the Free Software Foundation, Inc., 59 Temple Place, *
* Suite 330, Boston, MA 02111-1307, USA *
* *
***************************************************************************/
#include "PreCompiled.h"
#include <Base/Console.h>
#include <Base/Exception.h>
#include <Base/Reader.h>
#include <Base/Writer.h>
#include "Extension.h"
#include "ExtensionContainer.h"
using namespace App;
TYPESYSTEM_SOURCE(App::ExtensionContainer, App::PropertyContainer)
ExtensionContainer::ExtensionContainer() = default;
ExtensionContainer::~ExtensionContainer() {
//we need to delete all dynamically added extensions
for(const auto& entry : _extensions) {
if(entry.second->isPythonExtension())
delete entry.second;
}
}
void ExtensionContainer::registerExtension(Base::Type extension, Extension* ext) {
if(ext->getExtendedContainer() != this)
throw Base::ValueError("ExtensionContainer::registerExtension: Extension has not this as base object");
//no duplicate extensions (including base classes)
if(hasExtension(extension)) {
for(const auto& entry : _extensions) {
if(entry.first == extension || entry.first.isDerivedFrom(extension)) {
_extensions.erase(entry.first);
break;
}
}
}
_extensions[extension] = ext;
}
bool ExtensionContainer::hasExtension(Base::Type t, bool derived) const {
//check for the exact type
bool found = _extensions.find(t) != _extensions.end();
if(!found && derived) {
//and for types derived from it, as they can be cast to the extension
for(const auto& entry : _extensions) {
if(entry.first.isDerivedFrom(t))
return true;
}
return false;
}
return found;
}
bool ExtensionContainer::hasExtension(const std::string& name) const {
//and for types derived from it, as they can be cast to the extension
for(const auto& entry : _extensions) {
if(entry.second->name() == name)
return true;
}
return false;
}
Extension* ExtensionContainer::getExtension(Base::Type t, bool derived, bool no_except) const {
auto result = _extensions.find(t);
if((result == _extensions.end()) && derived) {
//we need to check for derived types
for(const auto& entry : _extensions) {
if(entry.first.isDerivedFrom(t))
return entry.second;
}
if(no_except)
return nullptr;
//if we arrive here we don't have anything matching
throw Base::TypeError("ExtensionContainer::getExtension: No extension of given type available");
}
else if (result != _extensions.end()) {
return result->second;
}
else {
if(no_except)
return nullptr;
//if we arrive here we don't have anything matching
throw Base::TypeError("ExtensionContainer::getExtension: No extension of given type available");
}
}
bool ExtensionContainer::hasExtensions() const {
return !_extensions.empty();
}
Extension* ExtensionContainer::getExtension(const std::string& name) const {
//and for types derived from it, as they can be cast to the extension
for(const auto& entry : _extensions) {
if(entry.second->name() == name)
return entry.second;
}
return nullptr;
}
std::vector< Extension* > ExtensionContainer::getExtensionsDerivedFrom(Base::Type type) const {
std::vector<Extension*> vec;
//and for types derived from it, as they can be cast to the extension
for(const auto& entry : _extensions) {
if(entry.first.isDerivedFrom(type))
vec.push_back(entry.second);
}
return vec;
}
void ExtensionContainer::getPropertyList(std::vector< Property* >& List) const {
App::PropertyContainer::getPropertyList(List);
for(const auto& entry : _extensions)
entry.second->extensionGetPropertyList(List);
}
void ExtensionContainer::getPropertyMap(std::map< std::string, Property* >& Map) const {
App::PropertyContainer::getPropertyMap(Map);
for(const auto& entry : _extensions)
entry.second->extensionGetPropertyMap(Map);
}
Property* ExtensionContainer::getPropertyByName(const char* name) const {
auto prop = App::PropertyContainer::getPropertyByName(name);
if(prop)
return prop;
for(const auto& entry : _extensions) {
auto prop = entry.second->extensionGetPropertyByName(name);
if(prop)
return prop;
}
return nullptr;
}
short int ExtensionContainer::getPropertyType(const Property* prop) const {
short int res = App::PropertyContainer::getPropertyType(prop);
if(res != 0)
return res;
for(const auto& entry : _extensions) {
res = entry.second->extensionGetPropertyType(prop);
if(res != 0)
return res;
}
return 0;
}
short int ExtensionContainer::getPropertyType(const char* name) const {
short int res = App::PropertyContainer::getPropertyType(name);
if(res != 0)
return res;
for(const auto& entry : _extensions) {
res = entry.second->extensionGetPropertyType(name);
if(res != 0)
return res;
}
return 0;
}
const char* ExtensionContainer::getPropertyName(const Property* prop) const {
const char* res = App::PropertyContainer::getPropertyName(prop);
if (res)
return res;
for (const auto& entry : _extensions) {
res = entry.second->extensionGetPropertyName(prop);
if (res)
return res;
}
return nullptr;
}
const char* ExtensionContainer::getPropertyGroup(const Property* prop) const {
const char* res = App::PropertyContainer::getPropertyGroup(prop);
if (res)
return res;
for (const auto& entry : _extensions) {
res = entry.second->extensionGetPropertyGroup(prop);
if (res)
return res;
}
return nullptr;
}
const char* ExtensionContainer::getPropertyGroup(const char* name) const {
const char* res = App::PropertyContainer::getPropertyGroup(name);
if (res)
return res;
for (const auto& entry : _extensions) {
res = entry.second->extensionGetPropertyGroup(name);
if (res)
return res;
}
return nullptr;
}
const char* ExtensionContainer::getPropertyDocumentation(const Property* prop) const {
const char* res = App::PropertyContainer::getPropertyDocumentation(prop);
if (res)
return res;
for (const auto& entry : _extensions) {
res = entry.second->extensionGetPropertyDocumentation(prop);
if (res)
return res;
}
return nullptr;
}
const char* ExtensionContainer::getPropertyDocumentation(const char* name) const {
const char* res = App::PropertyContainer::getPropertyDocumentation(name);
if (res)
return res;
for(const auto& entry : _extensions) {
res = entry.second->extensionGetPropertyDocumentation(name);
if (res)
return res;
}
return nullptr;
}
void ExtensionContainer::onChanged(const Property* prop) {
//inform all extensions about changed property. This includes all properties from the
//extended object (this) as well as all extension properties
for(const auto& entry : _extensions)
entry.second->extensionOnChanged(prop);
App::PropertyContainer::onChanged(prop);
}
void ExtensionContainer::Save(Base::Writer& writer) const {
//Note: save extensions must be called first to ensure that the extension element is always the
// very first inside the object element. This is needed since extension element works together with
// an object attribute, and if another element would be read first the object attributes would be
// cleared.
saveExtensions(writer);
App::PropertyContainer::Save(writer);
}
void ExtensionContainer::Restore(Base::XMLReader& reader) {
//restore dynamic extensions.
//Note 1: The extension element must be read first, before all other object elements. That is
// needed as the element works together with an object element attribute, which would be
// cleared if another attribute is read first
//Note 2: This must happen before the py object of this container is used, as only in the
// pyobject constructor the extension methods are added to the container.
restoreExtensions(reader);
App::PropertyContainer::Restore(reader);
}
void ExtensionContainer::saveExtensions(Base::Writer& writer) const {
//we don't save anything if there are no dynamic extensions
if(!hasExtensions())
return;
//save dynamic extensions
writer.incInd(); // indentation for 'Extensions'
writer.Stream() << writer.ind() << "<Extensions Count=\"" << _extensions.size() << "\">" << std::endl;
for(const auto& entry : _extensions) {
auto ext = entry.second;
writer.incInd(); // indentation for 'Extension name'
writer.Stream() << writer.ind() << "<Extension"
<< " type=\"" << ext->getExtensionTypeId().getName() <<"\""
<< " name=\"" << ext->name() << "\">" << std::endl;
writer.incInd(); // indentation for the actual Extension
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.
ext->extensionSave(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("ExtensionContainer::Save: Unknown C++ exception thrown. Try to continue...\n");
}
#endif
writer.decInd(); // indentation for the actual extension
writer.Stream() << writer.ind() << "</Extension>" << std::endl;
writer.decInd(); // indentation for 'Extension name'
}
writer.Stream() << writer.ind() << "</Extensions>" << std::endl;
writer.decInd();
}
void ExtensionContainer::restoreExtensions(Base::XMLReader& reader) {
//Dynamic extensions are optional (also because they are introduced late into the document format)
//and hence it is possible that the element does not exist. As we cannot check for the existence of
//an element a object attribute is set if extensions are available. Here we check that
//attribute, and only if it exists the extensions element will be available.
if(!reader.hasAttribute("Extensions"))
return;
reader.readElement("Extensions");
int Cnt = reader.getAttributeAsInteger("Count");
for (int i=0 ;i<Cnt ;i++) {
reader.readElement("Extension");
const char* Type = reader.getAttribute("type");
const char* Name = reader.getAttribute("name");
try {
App::Extension* ext = getExtension(Name);
if(!ext) {
//get the extension type asked for
Base::Type extension = Base::Type::fromName(Type);
if (extension.isBad() || !extension.isDerivedFrom(App::Extension::getExtensionClassTypeId())) {
std::stringstream str;
str << "No extension found of type '" << Type << "'" << std::ends;
throw Base::TypeError(str.str());
}
//register the extension
ext = static_cast<App::Extension*>(extension.createInstance());
//check if this really is a python extension!
if (!ext->isPythonExtension()) {
delete ext;
std::stringstream str;
str << "Extension is not a python addable version: '" << Type << "'" << std::ends;
throw Base::TypeError(str.str());
}
ext->initExtension(this);
}
if (ext && strcmp(ext->getExtensionTypeId().getName(), Type) == 0)
ext->extensionRestore(reader);
}
catch (const Base::XMLParseException&) {
throw; // re-throw
}
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("ExtensionContainer::Restore: Unknown C++ exception thrown\n");
}
#endif
reader.readEndElement("Extension");
}
reader.readEndElement("Extensions");
}
/***************************************************************************
* Copyright (c) 2016 Stefan Tröger <stefantroeger@gmx.net> *
* *
* This file is part of the FreeCAD CAx development system. *
* *
* This library is free software; you can redistribute it and/or *
* modify it under the terms of the GNU Library General Public *
* License as published by the Free Software Foundation; either *
* version 2 of the License, or (at your option) any later version. *
* *
* This library is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU Library General Public License for more details. *
* *
* You should have received a copy of the GNU Library General Public *
* License along with this library; see the file COPYING.LIB. If not, *
* write to the Free Software Foundation, Inc., 59 Temple Place, *
* Suite 330, Boston, MA 02111-1307, USA *
* *
***************************************************************************/
#include "PreCompiled.h"
#include <Base/Console.h>
#include <Base/Exception.h>
#include <Base/Reader.h>
#include <Base/Writer.h>
#include "Extension.h"
#include "ExtensionContainer.h"
using namespace App;
TYPESYSTEM_SOURCE(App::ExtensionContainer, App::PropertyContainer)
ExtensionContainer::ExtensionContainer() = default;
ExtensionContainer::~ExtensionContainer() {
//we need to delete all dynamically added extensions
for(const auto& entry : _extensions) {
if(entry.second->isPythonExtension())
delete entry.second;
}
}
void ExtensionContainer::registerExtension(Base::Type extension, Extension* ext) {
if(ext->getExtendedContainer() != this)
throw Base::ValueError("ExtensionContainer::registerExtension: Extension has not this as base object");
//no duplicate extensions (including base classes)
if(hasExtension(extension)) {
for(const auto& entry : _extensions) {
if(entry.first == extension || entry.first.isDerivedFrom(extension)) {
_extensions.erase(entry.first);
break;
}
}
}
_extensions[extension] = ext;
}
bool ExtensionContainer::hasExtension(Base::Type t, bool derived) const {
//check for the exact type
bool found = _extensions.find(t) != _extensions.end();
if(!found && derived) {
//and for types derived from it, as they can be cast to the extension
for(const auto& entry : _extensions) {
if(entry.first.isDerivedFrom(t))
return true;
}
return false;
}
return found;
}
bool ExtensionContainer::hasExtension(const std::string& name) const {
//and for types derived from it, as they can be cast to the extension
for(const auto& entry : _extensions) {
if(entry.second->name() == name)
return true;
}
return false;
}
Extension* ExtensionContainer::getExtension(Base::Type t, bool derived, bool no_except) const {
auto result = _extensions.find(t);
if((result == _extensions.end()) && derived) {
//we need to check for derived types
for(const auto& entry : _extensions) {
if(entry.first.isDerivedFrom(t))
return entry.second;
}
if(no_except)
return nullptr;
//if we arrive here we don't have anything matching
throw Base::TypeError("ExtensionContainer::getExtension: No extension of given type available");
}
else if (result != _extensions.end()) {
return result->second;
}
else {
if(no_except)
return nullptr;
//if we arrive here we don't have anything matching
throw Base::TypeError("ExtensionContainer::getExtension: No extension of given type available");
}
}
bool ExtensionContainer::hasExtensions() const {
return !_extensions.empty();
}
Extension* ExtensionContainer::getExtension(const std::string& name) const {
//and for types derived from it, as they can be cast to the extension
for(const auto& entry : _extensions) {
if(entry.second->name() == name)
return entry.second;
}
return nullptr;
}
std::vector< Extension* > ExtensionContainer::getExtensionsDerivedFrom(Base::Type type) const {
std::vector<Extension*> vec;
//and for types derived from it, as they can be cast to the extension
for(const auto& entry : _extensions) {
if(entry.first.isDerivedFrom(type))
vec.push_back(entry.second);
}
return vec;
}
void ExtensionContainer::getPropertyList(std::vector< Property* >& List) const {
App::PropertyContainer::getPropertyList(List);
for(const auto& entry : _extensions)
entry.second->extensionGetPropertyList(List);
}
void ExtensionContainer::getPropertyMap(std::map< std::string, Property* >& Map) const {
App::PropertyContainer::getPropertyMap(Map);
for(const auto& entry : _extensions)
entry.second->extensionGetPropertyMap(Map);
}
Property* ExtensionContainer::getPropertyByName(const char* name) const {
auto prop = App::PropertyContainer::getPropertyByName(name);
if(prop)
return prop;
for(const auto& entry : _extensions) {
auto prop = entry.second->extensionGetPropertyByName(name);
if(prop)
return prop;
}
return nullptr;
}
short int ExtensionContainer::getPropertyType(const Property* prop) const {
short int res = App::PropertyContainer::getPropertyType(prop);
if(res != 0)
return res;
for(const auto& entry : _extensions) {
res = entry.second->extensionGetPropertyType(prop);
if(res != 0)
return res;
}
return 0;
}
short int ExtensionContainer::getPropertyType(const char* name) const {
short int res = App::PropertyContainer::getPropertyType(name);
if(res != 0)
return res;
for(const auto& entry : _extensions) {
res = entry.second->extensionGetPropertyType(name);
if(res != 0)
return res;
}
return 0;
}
const char* ExtensionContainer::getPropertyName(const Property* prop) const {
const char* res = App::PropertyContainer::getPropertyName(prop);
if (res)
return res;
for (const auto& entry : _extensions) {
res = entry.second->extensionGetPropertyName(prop);
if (res)
return res;
}
return nullptr;
}
const char* ExtensionContainer::getPropertyGroup(const Property* prop) const {
const char* res = App::PropertyContainer::getPropertyGroup(prop);
if (res)
return res;
for (const auto& entry : _extensions) {
res = entry.second->extensionGetPropertyGroup(prop);
if (res)
return res;
}
return nullptr;
}
const char* ExtensionContainer::getPropertyGroup(const char* name) const {
const char* res = App::PropertyContainer::getPropertyGroup(name);
if (res)
return res;
for (const auto& entry : _extensions) {
res = entry.second->extensionGetPropertyGroup(name);
if (res)
return res;
}
return nullptr;
}
const char* ExtensionContainer::getPropertyDocumentation(const Property* prop) const {
const char* res = App::PropertyContainer::getPropertyDocumentation(prop);
if (res)
return res;
for (const auto& entry : _extensions) {
res = entry.second->extensionGetPropertyDocumentation(prop);
if (res)
return res;
}
return nullptr;
}
const char* ExtensionContainer::getPropertyDocumentation(const char* name) const {
const char* res = App::PropertyContainer::getPropertyDocumentation(name);
if (res)
return res;
for(const auto& entry : _extensions) {
res = entry.second->extensionGetPropertyDocumentation(name);
if (res)
return res;
}
return nullptr;
}
void ExtensionContainer::onChanged(const Property* prop) {
//inform all extensions about changed property. This includes all properties from the
//extended object (this) as well as all extension properties
for(const auto& entry : _extensions)
entry.second->extensionOnChanged(prop);
App::PropertyContainer::onChanged(prop);
}
void ExtensionContainer::Save(Base::Writer& writer) const {
//Note: save extensions must be called first to ensure that the extension element is always the
// very first inside the object element. This is needed since extension element works together with
// an object attribute, and if another element would be read first the object attributes would be
// cleared.
saveExtensions(writer);
App::PropertyContainer::Save(writer);
}
void ExtensionContainer::Restore(Base::XMLReader& reader) {
//restore dynamic extensions.
//Note 1: The extension element must be read first, before all other object elements. That is
// needed as the element works together with an object element attribute, which would be
// cleared if another attribute is read first
//Note 2: This must happen before the py object of this container is used, as only in the
// pyobject constructor the extension methods are added to the container.
restoreExtensions(reader);
App::PropertyContainer::Restore(reader);
}
void ExtensionContainer::saveExtensions(Base::Writer& writer) const {
//we don't save anything if there are no dynamic extensions
if(!hasExtensions())
return;
//save dynamic extensions
writer.incInd(); // indentation for 'Extensions'
writer.Stream() << writer.ind() << "<Extensions Count=\"" << _extensions.size() << "\">" << std::endl;
for(const auto& entry : _extensions) {
auto ext = entry.second;
writer.incInd(); // indentation for 'Extension name'
writer.Stream() << writer.ind() << "<Extension"
<< " type=\"" << ext->getExtensionTypeId().getName() <<"\""
<< " name=\"" << ext->name() << "\">" << std::endl;
writer.incInd(); // indentation for the actual Extension
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.
ext->extensionSave(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("ExtensionContainer::Save: Unknown C++ exception thrown. Try to continue...\n");
}
#endif
writer.decInd(); // indentation for the actual extension
writer.Stream() << writer.ind() << "</Extension>" << std::endl;
writer.decInd(); // indentation for 'Extension name'
}
writer.Stream() << writer.ind() << "</Extensions>" << std::endl;
writer.decInd();
}
void ExtensionContainer::restoreExtensions(Base::XMLReader& reader) {
//Dynamic extensions are optional (also because they are introduced late into the document format)
//and hence it is possible that the element does not exist. As we cannot check for the existence of
//an element a object attribute is set if extensions are available. Here we check that
//attribute, and only if it exists the extensions element will be available.
if(!reader.hasAttribute("Extensions"))
return;
reader.readElement("Extensions");
int Cnt = reader.getAttributeAsInteger("Count");
for (int i=0 ;i<Cnt ;i++) {
reader.readElement("Extension");
const char* Type = reader.getAttribute("type");
const char* Name = reader.getAttribute("name");
try {
App::Extension* ext = getExtension(Name);
if(!ext) {
//get the extension type asked for
Base::Type extension = Base::Type::fromName(Type);
if (extension.isBad() || !extension.isDerivedFrom(App::Extension::getExtensionClassTypeId())) {
std::stringstream str;
str << "No extension found of type '" << Type << "'" << std::ends;
throw Base::TypeError(str.str());
}
//register the extension
ext = static_cast<App::Extension*>(extension.createInstance());
//check if this really is a python extension!
if (!ext->isPythonExtension()) {
delete ext;
std::stringstream str;
str << "Extension is not a python addable version: '" << Type << "'" << std::ends;
throw Base::TypeError(str.str());
}
ext->initExtension(this);
}
if (ext && strcmp(ext->getExtensionTypeId().getName(), Type) == 0)
ext->extensionRestore(reader);
}
catch (const Base::XMLParseException&) {
throw; // re-throw
}
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("ExtensionContainer::Restore: Unknown C++ exception thrown\n");
}
#endif
reader.readEndElement("Extension");
}
reader.readEndElement("Extensions");
}
void ExtensionContainer::handleChangedPropertyName(Base::XMLReader &reader, const char * TypeName, const char *PropName)
{
//inform all extensions about changed property name. This includes all properties from the
//extended object (this) as well as all extension properties
for(const auto& entry : _extensions) {
bool handled = entry.second->extensionHandleChangedPropertyName(reader, TypeName, PropName);
if(handled)
return; // one property change needs only be handled once
}
PropertyContainer::handleChangedPropertyName(reader, TypeName, PropName);
}
void ExtensionContainer::handleChangedPropertyType(Base::XMLReader &reader, const char * TypeName, Property * prop)
{
//inform all extensions about changed property type. This includes all properties from the
//extended object (this) as well as all extension properties
for(const auto& entry : _extensions) {
bool handled = entry.second->extensionHandleChangedPropertyType(reader, TypeName, prop);
if(handled)
return; // one property change needs only be handled once
}
PropertyContainer::handleChangedPropertyType(reader, TypeName, prop);
}

View File

@@ -1,203 +1,220 @@
/***************************************************************************
* Copyright (c) 2016 Stefan Tröger <stefantroeger@gmx.net> *
* *
* This file is part of the FreeCAD CAx development system. *
* *
* This library is free software; you can redistribute it and/or *
* modify it under the terms of the GNU Library General Public *
* License as published by the Free Software Foundation; either *
* version 2 of the License, or (at your option) any later version. *
* *
* This library is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU Library General Public License for more details. *
* *
* You should have received a copy of the GNU Library General Public *
* License along with this library; see the file COPYING.LIB. If not, *
* write to the Free Software Foundation, Inc., 59 Temple Place, *
* Suite 330, Boston, MA 02111-1307, USA *
* *
***************************************************************************/
#ifndef APP_EXTENSIONCONTAINER_H
#define APP_EXTENSIONCONTAINER_H
#include "PropertyContainer.h"
namespace App {
class Extension;
/**
* @brief Container which can hold extensions
*
* In FreeCAD normally inheritance is a chain, it is not possible to use multiple inheritance.
* The reason for this is that all objects need to be exposed to python, and it is basically
* impossible to handle multiple inheritance in the C-API for python extensions. Also using multiple
* parent classes in python is currently not possible with the default object approach.
*
* The concept of extensions allow to circumvent those problems. Extensions are FreeCAD objects
* which work like normal objects in the sense that they use properties and class methods to define
* their functionality. However, they are not exposed as individual usable entities but are used to
* extend other objects. A extended object gets all the properties and methods of the extension.
* Therefore it is like c++ multiple inheritance, which is indeed used to achieve this on c++ side,
* but provides a few important additional functionalities:
* - Property persistence is handled, save and restore work out of the box
* - The objects python API gets extended too with the extension python API
* - Extensions can be added from c++ and python, even from both together
*
* The interoperability with python is highly important, as in FreeCAD all functionality should be
* as easily accessible from python as from c++. To ensure this, and as already noted, extensions can
* be added to a object from python. However, this means that it is not clear from the c++ object type
* if an extension was added or not. If added from c++ it becomes clear in the type due to the use of
* multiple inheritance. If added from python it is a runtime extension and not visible from type.
* Hence querying existing extensions of an object and accessing its methods works not by type
* casting but by the interface provided in ExtensionContainer. The default workflow is to query if
* an extension exists and then get the extension object. No matter if added from python or c++ this
* interface works always the same.
* @code
* if (object->hasExtension(GroupExtension::getClassTypeId())) {
* App::GroupExtension* group = object->getExtensionByType<GroupExtension>();
* group->hasObject(...);
* }
* @endcode
*
* To add a extension to an object, it must comply to a single restriction: it must be derived
* from ExtensionContainer. This is important to allow adding extensions from python and also to
* access the universal extension API. As DocumentObject itself derives from ExtensionContainer this
* should be the case automatically in most circumstances.
*
* Note that two small boilerplate changes are needed next to the multiple inheritance when adding
* extensions from c++.
* 1. It must be ensured that the property and type registration is aware of the extensions by using
* special macros.
* 2. The extensions need to be initialised in the constructor
*
* Here is a working example:
* @code
* class AppExport Part : public App::DocumentObject, public App::FirstExtension, public App::SecondExtension {
* PROPERTY_HEADER_WITH_EXTENSIONS(App::Part);
* };
* PROPERTY_SOURCE_WITH_EXTENSIONS(App::Part, App::DocumentObject)
* Part::Part(void) {
* FirstExtension::initExtension(this);
* SecondExtension::initExtension(this);
* }
* @endcode
*
* From python adding an extension is easier, it must be simply registered to a document object
* at object initialisation like done with properties. Note that the special python extension objects
* need to be added, not the c++ objects. Normally the only difference in name is the additional
* "Python" at the end of the extension name.
* @code{.py}
* class Test():
* __init(self)__:
* registerExtension("App::FirstExtensionPython", self)
* registerExtension("App::SecondExtensionPython", self)
* @endcode
*
* Extensions can provide methods that should be overridden by the extended object for customisation
* of the extension behaviour. In c++ this is as simple as overriding the provided virtual functions.
* In python a class method must be provided which has the same name as the method to override. This
* method must not necessarily be in the object that is extended, it must be in the object which is
* provided to the "registerExtension" call as second argument. This second argument is used as a
* proxy and enqueired if the method to override exists in this proxy before calling it.
*
* For information on howto create extension see the documentation of Extension
*/
class AppExport ExtensionContainer : public App::PropertyContainer
{
TYPESYSTEM_HEADER_WITH_OVERRIDE();
public:
using ExtensionIterator = std::map<Base::Type, App::Extension*>::iterator;
ExtensionContainer();
~ExtensionContainer() override;
void registerExtension(Base::Type extension, App::Extension* ext);
bool hasExtension(Base::Type, bool derived=true) const; //returns first of type (or derived from if set to true) and throws otherwise
bool hasExtension(const std::string& name) const; //this version does not check derived classes
bool hasExtensions() const;
App::Extension* getExtension(Base::Type, bool derived = true, bool no_except=false) const;
App::Extension* getExtension(const std::string& name) const; //this version does not check derived classes
//returns first of type (or derived from) and throws otherwise
template<typename ExtensionT>
ExtensionT* getExtensionByType(bool no_except=false, bool derived=true) const {
return static_cast<ExtensionT*>(getExtension(ExtensionT::getExtensionClassTypeId(),derived,no_except));
}
//get all extensions which have the given base class
std::vector<Extension*> getExtensionsDerivedFrom(Base::Type type) const;
template<typename ExtensionT>
std::vector<ExtensionT*> getExtensionsDerivedFromType() const {
std::vector<ExtensionT*> typevec;
for(const auto& entry : _extensions) {
if(entry.first.isDerivedFrom(ExtensionT::getExtensionClassTypeId()))
typevec.push_back(static_cast<ExtensionT*>(entry.second));
}
return typevec;
}
ExtensionIterator extensionBegin() {return _extensions.begin();}
ExtensionIterator extensionEnd() {return _extensions.end();}
/** @name Access properties */
//@{
/// find a property by its name
Property *getPropertyByName(const char* name) const override;
/// get the name of a property
const char* getPropertyName(const Property* prop) const override;
/// get all properties of the class (including properties of the parent)
void getPropertyMap(std::map<std::string,Property*> &Map) const override;
/// get all properties of the class (including properties of the parent)
void getPropertyList(std::vector<Property*> &List) const override;
/// get the Type of a Property
short getPropertyType(const Property* prop) const override;
/// get the Type of a named Property
short getPropertyType(const char *name) const override;
/// get the Group of a Property
const char* getPropertyGroup(const Property* prop) const override;
/// get the Group of a named Property
const char* getPropertyGroup(const char *name) const override;
/// get the Group of a Property
const char* getPropertyDocumentation(const Property* prop) const override;
/// get the Group of a named Property
const char* getPropertyDocumentation(const char *name) const override;
//@}
void onChanged(const Property*) override;
void Save(Base::Writer& writer) const override;
void Restore(Base::XMLReader& reader) override;
//those methods save/restore the dynamic extensions without handling properties, which is something
//done by the default Save/Restore methods.
void saveExtensions(Base::Writer& writer) const;
void restoreExtensions(Base::XMLReader& reader);
private:
//stored extensions
std::map<Base::Type, App::Extension*> _extensions;
};
#define PROPERTY_HEADER_WITH_EXTENSIONS(_class_) \
PROPERTY_HEADER_WITH_OVERRIDE(_class)
/// We make sure that the PropertyData of the container is not connected to the one of the extension
#define PROPERTY_SOURCE_WITH_EXTENSIONS(_class_, _parentclass_) \
PROPERTY_SOURCE(_class_, _parentclass_)
#define PROPERTY_SOURCE_ABSTRACT_WITH_EXTENSIONS(_class_, _parentclass_) \
PROPERTY_SOURCE_ABSTRACT(_class_, _parentclass_)
} //App
#endif // APP_EXTENSIONCONTAINER_H
/***************************************************************************
* Copyright (c) 2016 Stefan Tröger <stefantroeger@gmx.net> *
* *
* This file is part of the FreeCAD CAx development system. *
* *
* This library is free software; you can redistribute it and/or *
* modify it under the terms of the GNU Library General Public *
* License as published by the Free Software Foundation; either *
* version 2 of the License, or (at your option) any later version. *
* *
* This library is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU Library General Public License for more details. *
* *
* You should have received a copy of the GNU Library General Public *
* License along with this library; see the file COPYING.LIB. If not, *
* write to the Free Software Foundation, Inc., 59 Temple Place, *
* Suite 330, Boston, MA 02111-1307, USA *
* *
***************************************************************************/
#ifndef APP_EXTENSIONCONTAINER_H
#define APP_EXTENSIONCONTAINER_H
#include "PropertyContainer.h"
namespace App {
class Extension;
/**
* @brief Container which can hold extensions
*
* In FreeCAD normally inheritance is a chain, it is not possible to use multiple inheritance.
* The reason for this is that all objects need to be exposed to python, and it is basically
* impossible to handle multiple inheritance in the C-API for python extensions. Also using multiple
* parent classes in python is currently not possible with the default object approach.
*
* The concept of extensions allow to circumvent those problems. Extensions are FreeCAD objects
* which work like normal objects in the sense that they use properties and class methods to define
* their functionality. However, they are not exposed as individual usable entities but are used to
* extend other objects. A extended object gets all the properties and methods of the extension.
* Therefore it is like c++ multiple inheritance, which is indeed used to achieve this on c++ side,
* but provides a few important additional functionalities:
* - Property persistence is handled, save and restore work out of the box
* - The objects python API gets extended too with the extension python API
* - Extensions can be added from c++ and python, even from both together
*
* The interoperability with python is highly important, as in FreeCAD all functionality should be
* as easily accessible from python as from c++. To ensure this, and as already noted, extensions can
* be added to a object from python. However, this means that it is not clear from the c++ object type
* if an extension was added or not. If added from c++ it becomes clear in the type due to the use of
* multiple inheritance. If added from python it is a runtime extension and not visible from type.
* Hence querying existing extensions of an object and accessing its methods works not by type
* casting but by the interface provided in ExtensionContainer. The default workflow is to query if
* an extension exists and then get the extension object. No matter if added from python or c++ this
* interface works always the same.
* @code
* if (object->hasExtension(GroupExtension::getClassTypeId())) {
* App::GroupExtension* group = object->getExtensionByType<GroupExtension>();
* group->hasObject(...);
* }
* @endcode
*
* To add a extension to an object, it must comply to a single restriction: it must be derived
* from ExtensionContainer. This is important to allow adding extensions from python and also to
* access the universal extension API. As DocumentObject itself derives from ExtensionContainer this
* should be the case automatically in most circumstances.
*
* Note that two small boilerplate changes are needed next to the multiple inheritance when adding
* extensions from c++.
* 1. It must be ensured that the property and type registration is aware of the extensions by using
* special macros.
* 2. The extensions need to be initialised in the constructor
*
* Here is a working example:
* @code
* class AppExport Part : public App::DocumentObject, public App::FirstExtension, public App::SecondExtension {
* PROPERTY_HEADER_WITH_EXTENSIONS(App::Part);
* };
* PROPERTY_SOURCE_WITH_EXTENSIONS(App::Part, App::DocumentObject)
* Part::Part(void) {
* FirstExtension::initExtension(this);
* SecondExtension::initExtension(this);
* }
* @endcode
*
* From python adding an extension is easier, it must be simply registered to a document object
* at object initialisation like done with properties. Note that the special python extension objects
* need to be added, not the c++ objects. Normally the only difference in name is the additional
* "Python" at the end of the extension name.
* @code{.py}
* class Test():
* __init(self)__:
* registerExtension("App::FirstExtensionPython", self)
* registerExtension("App::SecondExtensionPython", self)
* @endcode
*
* Extensions can provide methods that should be overridden by the extended object for customisation
* of the extension behaviour. In c++ this is as simple as overriding the provided virtual functions.
* In python a class method must be provided which has the same name as the method to override. This
* method must not necessarily be in the object that is extended, it must be in the object which is
* provided to the "registerExtension" call as second argument. This second argument is used as a
* proxy and enqueired if the method to override exists in this proxy before calling it.
*
* For information on howto create extension see the documentation of Extension
*/
class AppExport ExtensionContainer : public App::PropertyContainer
{
TYPESYSTEM_HEADER_WITH_OVERRIDE();
public:
using ExtensionIterator = std::map<Base::Type, App::Extension*>::iterator;
ExtensionContainer();
~ExtensionContainer() override;
void registerExtension(Base::Type extension, App::Extension* ext);
bool hasExtension(Base::Type, bool derived=true) const; //returns first of type (or derived from if set to true) and throws otherwise
bool hasExtension(const std::string& name) const; //this version does not check derived classes
bool hasExtensions() const;
App::Extension* getExtension(Base::Type, bool derived = true, bool no_except=false) const;
App::Extension* getExtension(const std::string& name) const; //this version does not check derived classes
//returns first of type (or derived from) and throws otherwise
template<typename ExtensionT>
ExtensionT* getExtensionByType(bool no_except=false, bool derived=true) const {
return static_cast<ExtensionT*>(getExtension(ExtensionT::getExtensionClassTypeId(),derived,no_except));
}
//get all extensions which have the given base class
std::vector<Extension*> getExtensionsDerivedFrom(Base::Type type) const;
template<typename ExtensionT>
std::vector<ExtensionT*> getExtensionsDerivedFromType() const {
std::vector<ExtensionT*> typevec;
for(const auto& entry : _extensions) {
if(entry.first.isDerivedFrom(ExtensionT::getExtensionClassTypeId()))
typevec.push_back(static_cast<ExtensionT*>(entry.second));
}
return typevec;
}
ExtensionIterator extensionBegin() {return _extensions.begin();}
ExtensionIterator extensionEnd() {return _extensions.end();}
/** @name Access properties */
//@{
/// find a property by its name
Property *getPropertyByName(const char* name) const override;
/// get the name of a property
const char* getPropertyName(const Property* prop) const override;
/// get all properties of the class (including properties of the parent)
void getPropertyMap(std::map<std::string,Property*> &Map) const override;
/// get all properties of the class (including properties of the parent)
void getPropertyList(std::vector<Property*> &List) const override;
/// get the Type of a Property
short getPropertyType(const Property* prop) const override;
/// get the Type of a named Property
short getPropertyType(const char *name) const override;
/// get the Group of a Property
const char* getPropertyGroup(const Property* prop) const override;
/// get the Group of a named Property
const char* getPropertyGroup(const char *name) const override;
/// get the Group of a Property
const char* getPropertyDocumentation(const Property* prop) const override;
/// get the Group of a named Property
const char* getPropertyDocumentation(const char *name) const override;
//@}
void onChanged(const Property*) override;
void Save(Base::Writer& writer) const override;
void Restore(Base::XMLReader& reader) override;
//those methods save/restore the dynamic extensions without handling properties, which is something
//done by the default Save/Restore methods.
void saveExtensions(Base::Writer& writer) const;
void restoreExtensions(Base::XMLReader& reader);
/** Extends the rules for handling property name changed, so that extensions are given an opportunity to handle it.
* If an extension handles a change, neither the rest of the extensions, nor the container itself get to handle it.
*
* Extensions get their extensionHandleChangedPropertyName() called.
*
* If no extension handles the request, then the containers handleChangedPropertyName() is called.
*/
virtual void handleChangedPropertyName(Base::XMLReader &reader, const char * TypeName, const char *PropName) override;
/** Extends the rules for handling property type changed, so that extensions are given an opportunity to handle it.
* If an extension handles a change, neither the rest of the extensions, nor the container itself get to handle it.
*
* Extensions get their extensionHandleChangedPropertyType() called.
*
* If no extension handles the request, then the containers handleChangedPropertyType() is called.
*/
virtual void handleChangedPropertyType(Base::XMLReader &reader, const char * TypeName, Property * prop) override;
private:
//stored extensions
std::map<Base::Type, App::Extension*> _extensions;
};
#define PROPERTY_HEADER_WITH_EXTENSIONS(_class_) \
PROPERTY_HEADER_WITH_OVERRIDE(_class)
/// We make sure that the PropertyData of the container is not connected to the one of the extension
#define PROPERTY_SOURCE_WITH_EXTENSIONS(_class_, _parentclass_) \
PROPERTY_SOURCE(_class_, _parentclass_)
#define PROPERTY_SOURCE_ABSTRACT_WITH_EXTENSIONS(_class_, _parentclass_) \
PROPERTY_SOURCE_ABSTRACT(_class_, _parentclass_)
} //App
#endif // APP_EXTENSIONCONTAINER_H

File diff suppressed because it is too large Load Diff

View File

@@ -1,343 +1,343 @@
/***************************************************************************
* Copyright (c) 2005 Jürgen Riegel <juergen.riegel@web.de> *
* *
* This file is part of the FreeCAD CAx development system. *
* *
* This library is free software; you can redistribute it and/or *
* modify it under the terms of the GNU Library General Public *
* License as published by the Free Software Foundation; either *
* version 2 of the License, or (at your option) any later version. *
* *
* This library is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU Library General Public License for more details. *
* *
* You should have received a copy of the GNU Library General Public *
* License along with this library; see the file COPYING.LIB. If not, *
* write to the Free Software Foundation, Inc., 59 Temple Place, *
* Suite 330, Boston, MA 02111-1307, USA *
* *
***************************************************************************/
#ifndef APP_PROPERTYCONTAINER_H
#define APP_PROPERTYCONTAINER_H
#include <map>
#include <cstring>
#include <Base/Persistence.h>
#include "DynamicProperty.h"
namespace Base {
class Writer;
}
namespace App
{
class Property;
class PropertyContainer;
class DocumentObject;
class Extension;
enum PropertyType
{
Prop_None = 0, /*!< No special property type */
Prop_ReadOnly = 1, /*!< Property is read-only in the editor */
Prop_Transient = 2, /*!< Property content won't be saved to file, but still saves name, type and status */
Prop_Hidden = 4, /*!< Property won't appear in the editor */
Prop_Output = 8, /*!< Modified property doesn't touch its parent container */
Prop_NoRecompute = 16,/*!< Modified property doesn't touch its container for recompute */
Prop_NoPersist = 32,/*!< Property won't be saved to file at all */
};
struct AppExport PropertyData
{
struct PropertySpec
{
const char * Name;
const char * Group;
const char * Docu;
short Offset, Type;
inline PropertySpec(const char *name, const char *group, const char *doc, short offset, short type)
:Name(name),Group(group),Docu(doc),Offset(offset),Type(type)
{}
};
//purpose of this struct is to be constructible from all acceptable container types and to
//be able to return the offset to a property from the accepted containers. This allows to use
//one function implementation for multiple container types without losing all type safety by
//accepting void*
struct OffsetBase
{
OffsetBase(const App::PropertyContainer* container) : m_container(container) {}//explicit bombs
OffsetBase(const App::Extension* container) : m_container(container) {}//explicit bombs
short int getOffsetTo(const App::Property* prop) const {
auto *pt = (const char*)prop;
auto *base = (const char *)m_container;
if(pt<base || pt>base+SHRT_MAX)
return -1;
return (short) (pt-base);
}
char* getOffset() const {return (char*) m_container;}
private:
const void* m_container;
};
// A multi index container for holding the property spec, with the following
// index,
// * a sequence, to preserve creation order
// * hash index on property name
// * hash index on property pointer offset
mutable bmi::multi_index_container<
PropertySpec,
bmi::indexed_by<
bmi::sequenced<>,
bmi::hashed_unique<
bmi::member<PropertySpec, const char*, &PropertySpec::Name>,
CStringHasher,
CStringHasher
>,
bmi::hashed_unique<
bmi::member<PropertySpec, short, &PropertySpec::Offset>
>
>
> propertyData;
mutable bool parentMerged = false;
const PropertyData* parentPropertyData;
void addProperty(OffsetBase offsetBase,const char* PropName, Property *Prop, const char* PropertyGroup= nullptr, PropertyType = Prop_None, const char* PropertyDocu= nullptr );
const PropertySpec *findProperty(OffsetBase offsetBase,const char* PropName) const;
const PropertySpec *findProperty(OffsetBase offsetBase,const Property* prop) const;
const char* getName (OffsetBase offsetBase,const Property* prop) const;
short getType (OffsetBase offsetBase,const Property* prop) const;
short getType (OffsetBase offsetBase,const char* name) const;
const char* getGroup (OffsetBase offsetBase,const char* name) const;
const char* getGroup (OffsetBase offsetBase,const Property* prop) const;
const char* getDocumentation(OffsetBase offsetBase,const char* name) const;
const char* getDocumentation(OffsetBase offsetBase,const Property* prop) const;
Property *getPropertyByName(OffsetBase offsetBase,const char* name) const;
void getPropertyMap(OffsetBase offsetBase,std::map<std::string,Property*> &Map) const;
void getPropertyList(OffsetBase offsetBase,std::vector<Property*> &List) const;
void getPropertyNamedList(OffsetBase offsetBase, std::vector<std::pair<const char*,Property*> > &List) const;
void merge(PropertyData *other=nullptr) const;
void split(PropertyData *other);
};
/** Base class of all classes with properties
*/
class AppExport PropertyContainer: public Base::Persistence
{
TYPESYSTEM_HEADER_WITH_OVERRIDE();
public:
/**
* A constructor.
* A more elaborate description of the constructor.
*/
PropertyContainer();
/**
* A destructor.
* A more elaborate description of the destructor.
*/
~PropertyContainer() override;
unsigned int getMemSize () const override;
virtual std::string getFullName() const {return std::string();}
/// find a property by its name
virtual Property *getPropertyByName(const char* name) const;
/// get the name of a property
virtual const char* getPropertyName(const Property* prop) const;
/// get all properties of the class (including properties of the parent)
virtual void getPropertyMap(std::map<std::string,Property*> &Map) const;
/// get all properties of the class (including properties of the parent)
virtual void getPropertyList(std::vector<Property*> &List) const;
/// get all properties with their names, may contain duplicates and aliases
virtual void getPropertyNamedList(std::vector<std::pair<const char*,Property*> > &List) const;
/// set the Status bit of all properties at once
void setPropertyStatus(unsigned char bit,bool value);
/// get the Type of a Property
virtual short getPropertyType(const Property* prop) const;
/// get the Type of a named Property
virtual short getPropertyType(const char *name) const;
/// get the Group of a Property
virtual const char* getPropertyGroup(const Property* prop) const;
/// get the Group of a named Property
virtual const char* getPropertyGroup(const char *name) const;
/// get the Group of a Property
virtual const char* getPropertyDocumentation(const Property* prop) const;
/// get the Group of a named Property
virtual const char* getPropertyDocumentation(const char *name) const;
/// check if the property is read-only
bool isReadOnly(const Property* prop) const;
/// check if the named property is read-only
bool isReadOnly(const char *name) const;
/// check if the property is hidden
bool isHidden(const Property* prop) const;
/// check if the named property is hidden
bool isHidden(const char *name) const;
virtual App::Property* addDynamicProperty(
const char* type, const char* name=nullptr,
const char* group=nullptr, const char* doc=nullptr,
short attr=0, bool ro=false, bool hidden=false);
DynamicProperty::PropData getDynamicPropertyData(const Property* prop) const {
return dynamicProps.getDynamicPropertyData(prop);
}
bool changeDynamicProperty(const Property *prop, const char *group, const char *doc) {
return dynamicProps.changeDynamicProperty(prop,group,doc);
}
virtual bool removeDynamicProperty(const char* name) {
return dynamicProps.removeDynamicProperty(name);
}
virtual std::vector<std::string> getDynamicPropertyNames() const {
return dynamicProps.getDynamicPropertyNames();
}
virtual App::Property *getDynamicPropertyByName(const char* name) const {
return dynamicProps.getDynamicPropertyByName(name);
}
virtual void onPropertyStatusChanged(const Property &prop, unsigned long oldStatus);
void Save (Base::Writer &writer) const override;
void Restore(Base::XMLReader &reader) override;
virtual void editProperty(const char * /*propName*/) {}
const char *getPropertyPrefix() const {
return _propertyPrefix.c_str();
}
void setPropertyPrefix(const char *prefix) {
_propertyPrefix = prefix;
}
friend class Property;
friend class DynamicProperty;
protected:
/// get called by the container when a property has changed
virtual void onChanged(const Property* /*prop*/){}
/// get called before the value is changed
virtual void onBeforeChange(const Property* /*prop*/){}
//void hasChanged(Property* prop);
static const PropertyData * getPropertyDataPtr();
virtual const PropertyData& getPropertyData() const;
virtual void handleChangedPropertyName(Base::XMLReader &reader, const char * TypeName, const char *PropName);
virtual void handleChangedPropertyType(Base::XMLReader &reader, const char * TypeName, Property * prop);
private:
// forbidden
PropertyContainer(const PropertyContainer&);
PropertyContainer& operator = (const PropertyContainer&);
protected:
DynamicProperty dynamicProps;
private:
std::string _propertyPrefix;
static PropertyData propertyData;
};
/// Property define
#define _ADD_PROPERTY(_name,_prop_, _defaultval_) \
do { \
this->_prop_.setValue _defaultval_;\
this->_prop_.setContainer(this); \
propertyData.addProperty(static_cast<App::PropertyContainer*>(this), _name, &this->_prop_); \
} while (0)
#define ADD_PROPERTY(_prop_, _defaultval_) \
_ADD_PROPERTY(#_prop_, _prop_, _defaultval_)
#define _ADD_PROPERTY_TYPE(_name,_prop_, _defaultval_, _group_,_type_,_Docu_) \
do { \
this->_prop_.setValue _defaultval_;\
this->_prop_.setContainer(this); \
propertyData.addProperty(static_cast<App::PropertyContainer*>(this), _name, &this->_prop_, (_group_),(_type_),(_Docu_)); \
} while (0)
#define ADD_PROPERTY_TYPE(_prop_, _defaultval_, _group_,_type_,_Docu_) \
_ADD_PROPERTY_TYPE(#_prop_,_prop_,_defaultval_,_group_,_type_,_Docu_)
#define PROPERTY_HEADER(_class_) \
TYPESYSTEM_HEADER(); \
protected: \
static const App::PropertyData * getPropertyDataPtr(void); \
virtual const App::PropertyData &getPropertyData(void) const; \
private: \
static App::PropertyData propertyData
/// Like PROPERTY_HEADER, but with overridden methods declared as such
#define PROPERTY_HEADER_WITH_OVERRIDE(_class_) \
TYPESYSTEM_HEADER_WITH_OVERRIDE(); \
protected: \
static const App::PropertyData * getPropertyDataPtr(void); \
virtual const App::PropertyData &getPropertyData(void) const override; \
private: \
static App::PropertyData propertyData
///
#define PROPERTY_SOURCE(_class_, _parentclass_) \
TYPESYSTEM_SOURCE_P(_class_)\
const App::PropertyData * _class_::getPropertyDataPtr(void){return &propertyData;} \
const App::PropertyData & _class_::getPropertyData(void) const{return propertyData;} \
App::PropertyData _class_::propertyData; \
void _class_::init(void){\
initSubclass(_class_::classTypeId, #_class_ , #_parentclass_, &(_class_::create) ); \
_class_::propertyData.parentPropertyData = _parentclass_::getPropertyDataPtr(); \
}
#define PROPERTY_SOURCE_ABSTRACT(_class_, _parentclass_) \
TYPESYSTEM_SOURCE_ABSTRACT_P(_class_)\
const App::PropertyData * _class_::getPropertyDataPtr(void){return &propertyData;} \
const App::PropertyData & _class_::getPropertyData(void) const{return propertyData;} \
App::PropertyData _class_::propertyData; \
void _class_::init(void){\
initSubclass(_class_::classTypeId, #_class_ , #_parentclass_, &(_class_::create) ); \
_class_::propertyData.parentPropertyData = _parentclass_::getPropertyDataPtr(); \
}
#define TYPESYSTEM_SOURCE_TEMPLATE(_class_) \
template<> Base::Type _class_::classTypeId = Base::Type::badType(); \
template<> Base::Type _class_::getClassTypeId(void) { return _class_::classTypeId; } \
template<> Base::Type _class_::getTypeId(void) const { return _class_::classTypeId; } \
template<> void * _class_::create(void){\
return new _class_ ();\
}
#define PROPERTY_SOURCE_TEMPLATE(_class_, _parentclass_) \
TYPESYSTEM_SOURCE_TEMPLATE(_class_)\
template<> App::PropertyData _class_::propertyData = App::PropertyData(); \
template<> const App::PropertyData * _class_::getPropertyDataPtr(void){return &propertyData;} \
template<> const App::PropertyData & _class_::getPropertyData(void) const{return propertyData;} \
template<> void _class_::init(void){\
initSubclass(_class_::classTypeId, #_class_ , #_parentclass_, &(_class_::create) ); \
_class_::propertyData.parentPropertyData = _parentclass_::getPropertyDataPtr(); \
}
} // namespace App
#endif // APP_PROPERTYCONTAINER_H
/***************************************************************************
* Copyright (c) 2005 Jürgen Riegel <juergen.riegel@web.de> *
* *
* This file is part of the FreeCAD CAx development system. *
* *
* This library is free software; you can redistribute it and/or *
* modify it under the terms of the GNU Library General Public *
* License as published by the Free Software Foundation; either *
* version 2 of the License, or (at your option) any later version. *
* *
* This library is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU Library General Public License for more details. *
* *
* You should have received a copy of the GNU Library General Public *
* License along with this library; see the file COPYING.LIB. If not, *
* write to the Free Software Foundation, Inc., 59 Temple Place, *
* Suite 330, Boston, MA 02111-1307, USA *
* *
***************************************************************************/
#ifndef APP_PROPERTYCONTAINER_H
#define APP_PROPERTYCONTAINER_H
#include <map>
#include <cstring>
#include <Base/Persistence.h>
#include "DynamicProperty.h"
namespace Base {
class Writer;
}
namespace App
{
class Property;
class PropertyContainer;
class DocumentObject;
class Extension;
enum PropertyType
{
Prop_None = 0, /*!< No special property type */
Prop_ReadOnly = 1, /*!< Property is read-only in the editor */
Prop_Transient = 2, /*!< Property content won't be saved to file, but still saves name, type and status */
Prop_Hidden = 4, /*!< Property won't appear in the editor */
Prop_Output = 8, /*!< Modified property doesn't touch its parent container */
Prop_NoRecompute = 16,/*!< Modified property doesn't touch its container for recompute */
Prop_NoPersist = 32,/*!< Property won't be saved to file at all */
};
struct AppExport PropertyData
{
struct PropertySpec
{
const char * Name;
const char * Group;
const char * Docu;
short Offset, Type;
inline PropertySpec(const char *name, const char *group, const char *doc, short offset, short type)
:Name(name),Group(group),Docu(doc),Offset(offset),Type(type)
{}
};
//purpose of this struct is to be constructible from all acceptable container types and to
//be able to return the offset to a property from the accepted containers. This allows to use
//one function implementation for multiple container types without losing all type safety by
//accepting void*
struct OffsetBase
{
OffsetBase(const App::PropertyContainer* container) : m_container(container) {}//explicit bombs
OffsetBase(const App::Extension* container) : m_container(container) {}//explicit bombs
short int getOffsetTo(const App::Property* prop) const {
auto *pt = (const char*)prop;
auto *base = (const char *)m_container;
if(pt<base || pt>base+SHRT_MAX)
return -1;
return (short) (pt-base);
}
char* getOffset() const {return (char*) m_container;}
private:
const void* m_container;
};
// A multi index container for holding the property spec, with the following
// index,
// * a sequence, to preserve creation order
// * hash index on property name
// * hash index on property pointer offset
mutable bmi::multi_index_container<
PropertySpec,
bmi::indexed_by<
bmi::sequenced<>,
bmi::hashed_unique<
bmi::member<PropertySpec, const char*, &PropertySpec::Name>,
CStringHasher,
CStringHasher
>,
bmi::hashed_unique<
bmi::member<PropertySpec, short, &PropertySpec::Offset>
>
>
> propertyData;
mutable bool parentMerged = false;
const PropertyData* parentPropertyData;
void addProperty(OffsetBase offsetBase,const char* PropName, Property *Prop, const char* PropertyGroup= nullptr, PropertyType = Prop_None, const char* PropertyDocu= nullptr );
const PropertySpec *findProperty(OffsetBase offsetBase,const char* PropName) const;
const PropertySpec *findProperty(OffsetBase offsetBase,const Property* prop) const;
const char* getName (OffsetBase offsetBase,const Property* prop) const;
short getType (OffsetBase offsetBase,const Property* prop) const;
short getType (OffsetBase offsetBase,const char* name) const;
const char* getGroup (OffsetBase offsetBase,const char* name) const;
const char* getGroup (OffsetBase offsetBase,const Property* prop) const;
const char* getDocumentation(OffsetBase offsetBase,const char* name) const;
const char* getDocumentation(OffsetBase offsetBase,const Property* prop) const;
Property *getPropertyByName(OffsetBase offsetBase,const char* name) const;
void getPropertyMap(OffsetBase offsetBase,std::map<std::string,Property*> &Map) const;
void getPropertyList(OffsetBase offsetBase,std::vector<Property*> &List) const;
void getPropertyNamedList(OffsetBase offsetBase, std::vector<std::pair<const char*,Property*> > &List) const;
void merge(PropertyData *other=nullptr) const;
void split(PropertyData *other);
};
/** Base class of all classes with properties
*/
class AppExport PropertyContainer: public Base::Persistence
{
TYPESYSTEM_HEADER_WITH_OVERRIDE();
public:
/**
* A constructor.
* A more elaborate description of the constructor.
*/
PropertyContainer();
/**
* A destructor.
* A more elaborate description of the destructor.
*/
~PropertyContainer() override;
unsigned int getMemSize () const override;
virtual std::string getFullName() const {return std::string();}
/// find a property by its name
virtual Property *getPropertyByName(const char* name) const;
/// get the name of a property
virtual const char* getPropertyName(const Property* prop) const;
/// get all properties of the class (including properties of the parent)
virtual void getPropertyMap(std::map<std::string,Property*> &Map) const;
/// get all properties of the class (including properties of the parent)
virtual void getPropertyList(std::vector<Property*> &List) const;
/// get all properties with their names, may contain duplicates and aliases
virtual void getPropertyNamedList(std::vector<std::pair<const char*,Property*> > &List) const;
/// set the Status bit of all properties at once
void setPropertyStatus(unsigned char bit,bool value);
/// get the Type of a Property
virtual short getPropertyType(const Property* prop) const;
/// get the Type of a named Property
virtual short getPropertyType(const char *name) const;
/// get the Group of a Property
virtual const char* getPropertyGroup(const Property* prop) const;
/// get the Group of a named Property
virtual const char* getPropertyGroup(const char *name) const;
/// get the Group of a Property
virtual const char* getPropertyDocumentation(const Property* prop) const;
/// get the Group of a named Property
virtual const char* getPropertyDocumentation(const char *name) const;
/// check if the property is read-only
bool isReadOnly(const Property* prop) const;
/// check if the named property is read-only
bool isReadOnly(const char *name) const;
/// check if the property is hidden
bool isHidden(const Property* prop) const;
/// check if the named property is hidden
bool isHidden(const char *name) const;
virtual App::Property* addDynamicProperty(
const char* type, const char* name=nullptr,
const char* group=nullptr, const char* doc=nullptr,
short attr=0, bool ro=false, bool hidden=false);
DynamicProperty::PropData getDynamicPropertyData(const Property* prop) const {
return dynamicProps.getDynamicPropertyData(prop);
}
bool changeDynamicProperty(const Property *prop, const char *group, const char *doc) {
return dynamicProps.changeDynamicProperty(prop,group,doc);
}
virtual bool removeDynamicProperty(const char* name) {
return dynamicProps.removeDynamicProperty(name);
}
virtual std::vector<std::string> getDynamicPropertyNames() const {
return dynamicProps.getDynamicPropertyNames();
}
virtual App::Property *getDynamicPropertyByName(const char* name) const {
return dynamicProps.getDynamicPropertyByName(name);
}
virtual void onPropertyStatusChanged(const Property &prop, unsigned long oldStatus);
void Save (Base::Writer &writer) const override;
void Restore(Base::XMLReader &reader) override;
virtual void editProperty(const char * /*propName*/) {}
const char *getPropertyPrefix() const {
return _propertyPrefix.c_str();
}
void setPropertyPrefix(const char *prefix) {
_propertyPrefix = prefix;
}
friend class Property;
friend class DynamicProperty;
protected:
/// get called by the container when a property has changed
virtual void onChanged(const Property* /*prop*/){}
/// get called before the value is changed
virtual void onBeforeChange(const Property* /*prop*/){}
//void hasChanged(Property* prop);
static const PropertyData * getPropertyDataPtr();
virtual const PropertyData& getPropertyData() const;
virtual void handleChangedPropertyName(Base::XMLReader &reader, const char * TypeName, const char *PropName);
virtual void handleChangedPropertyType(Base::XMLReader &reader, const char * TypeName, Property * prop);
private:
// forbidden
PropertyContainer(const PropertyContainer&);
PropertyContainer& operator = (const PropertyContainer&);
protected:
DynamicProperty dynamicProps;
private:
std::string _propertyPrefix;
static PropertyData propertyData;
};
/// Property define
#define _ADD_PROPERTY(_name,_prop_, _defaultval_) \
do { \
this->_prop_.setValue _defaultval_;\
this->_prop_.setContainer(this); \
propertyData.addProperty(static_cast<App::PropertyContainer*>(this), _name, &this->_prop_); \
} while (0)
#define ADD_PROPERTY(_prop_, _defaultval_) \
_ADD_PROPERTY(#_prop_, _prop_, _defaultval_)
#define _ADD_PROPERTY_TYPE(_name,_prop_, _defaultval_, _group_,_type_,_Docu_) \
do { \
this->_prop_.setValue _defaultval_;\
this->_prop_.setContainer(this); \
propertyData.addProperty(static_cast<App::PropertyContainer*>(this), _name, &this->_prop_, (_group_),(_type_),(_Docu_)); \
} while (0)
#define ADD_PROPERTY_TYPE(_prop_, _defaultval_, _group_,_type_,_Docu_) \
_ADD_PROPERTY_TYPE(#_prop_,_prop_,_defaultval_,_group_,_type_,_Docu_)
#define PROPERTY_HEADER(_class_) \
TYPESYSTEM_HEADER(); \
protected: \
static const App::PropertyData * getPropertyDataPtr(void); \
virtual const App::PropertyData &getPropertyData(void) const; \
private: \
static App::PropertyData propertyData
/// Like PROPERTY_HEADER, but with overridden methods declared as such
#define PROPERTY_HEADER_WITH_OVERRIDE(_class_) \
TYPESYSTEM_HEADER_WITH_OVERRIDE(); \
protected: \
static const App::PropertyData * getPropertyDataPtr(void); \
virtual const App::PropertyData &getPropertyData(void) const override; \
private: \
static App::PropertyData propertyData
///
#define PROPERTY_SOURCE(_class_, _parentclass_) \
TYPESYSTEM_SOURCE_P(_class_)\
const App::PropertyData * _class_::getPropertyDataPtr(void){return &propertyData;} \
const App::PropertyData & _class_::getPropertyData(void) const{return propertyData;} \
App::PropertyData _class_::propertyData; \
void _class_::init(void){\
initSubclass(_class_::classTypeId, #_class_ , #_parentclass_, &(_class_::create) ); \
_class_::propertyData.parentPropertyData = _parentclass_::getPropertyDataPtr(); \
}
#define PROPERTY_SOURCE_ABSTRACT(_class_, _parentclass_) \
TYPESYSTEM_SOURCE_ABSTRACT_P(_class_)\
const App::PropertyData * _class_::getPropertyDataPtr(void){return &propertyData;} \
const App::PropertyData & _class_::getPropertyData(void) const{return propertyData;} \
App::PropertyData _class_::propertyData; \
void _class_::init(void){\
initSubclass(_class_::classTypeId, #_class_ , #_parentclass_, &(_class_::create) ); \
_class_::propertyData.parentPropertyData = _parentclass_::getPropertyDataPtr(); \
}
#define TYPESYSTEM_SOURCE_TEMPLATE(_class_) \
template<> Base::Type _class_::classTypeId = Base::Type::badType(); \
template<> Base::Type _class_::getClassTypeId(void) { return _class_::classTypeId; } \
template<> Base::Type _class_::getTypeId(void) const { return _class_::classTypeId; } \
template<> void * _class_::create(void){\
return new _class_ ();\
}
#define PROPERTY_SOURCE_TEMPLATE(_class_, _parentclass_) \
TYPESYSTEM_SOURCE_TEMPLATE(_class_)\
template<> App::PropertyData _class_::propertyData = App::PropertyData(); \
template<> const App::PropertyData * _class_::getPropertyDataPtr(void){return &propertyData;} \
template<> const App::PropertyData & _class_::getPropertyData(void) const{return propertyData;} \
template<> void _class_::init(void){\
initSubclass(_class_::classTypeId, #_class_ , #_parentclass_, &(_class_::create) ); \
_class_::propertyData.parentPropertyData = _parentclass_::getPropertyDataPtr(); \
}
} // namespace App
#endif // APP_PROPERTYCONTAINER_H