diff --git a/src/App/Extension.cpp b/src/App/Extension.cpp index 9c38fbfd98..c3dea0f5e7 100644 --- a/src/App/Extension.cpp +++ b/src/App/Extension.cpp @@ -1,195 +1,213 @@ -/*************************************************************************** - * Copyright (c) 2016 Stefan Tröger * - * * - * 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 -#endif - -#include - -#include "Extension.h" -#include "ExtensionContainer.h" -#include "ExtensionPython.h" -#include - - -/* 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(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 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; -} +/*************************************************************************** + * Copyright (c) 2016 Stefan Tröger * + * * + * 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 +#endif + +#include + +#include "Extension.h" +#include "ExtensionContainer.h" +#include "ExtensionPython.h" +#include + + +/* 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(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 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; +} diff --git a/src/App/Extension.h b/src/App/Extension.h index ea6d8b4fe1..79171b2071 100644 --- a/src/App/Extension.h +++ b/src/App/Extension.h @@ -1,323 +1,328 @@ -/*************************************************************************** - * Copyright (c) 2016 Stefan Tröger * - * * - * 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 - -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; - * @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 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>; - * @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 &Map) const; - /// get all properties of the class (including properties of the parent) - virtual void extensionGetPropertyList(std::vector &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(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(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 * + * * + * 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 + +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; + * @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 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>; + * @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 &Map) const; + /// get all properties of the class (including properties of the parent) + virtual void extensionGetPropertyList(std::vector &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(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(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 diff --git a/src/App/ExtensionContainer.cpp b/src/App/ExtensionContainer.cpp index 855cbfa73d..30022e809b 100644 --- a/src/App/ExtensionContainer.cpp +++ b/src/App/ExtensionContainer.cpp @@ -1,418 +1,446 @@ -/*************************************************************************** - * Copyright (c) 2016 Stefan Tröger * - * * - * 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 -#include -#include -#include - -#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 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() << "" << std::endl; - for(const auto& entry : _extensions) { - - auto ext = entry.second; - writer.incInd(); // indentation for 'Extension name' - writer.Stream() << writer.ind() << "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() << "" << std::endl; - writer.decInd(); // indentation for 'Extension name' - } - writer.Stream() << writer.ind() << "" << 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(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 * + * * + * 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 +#include +#include +#include + +#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 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() << "" << std::endl; + for(const auto& entry : _extensions) { + + auto ext = entry.second; + writer.incInd(); // indentation for 'Extension name' + writer.Stream() << writer.ind() << "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() << "" << std::endl; + writer.decInd(); // indentation for 'Extension name' + } + writer.Stream() << writer.ind() << "" << 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(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); +} diff --git a/src/App/ExtensionContainer.h b/src/App/ExtensionContainer.h index a190932976..d3661c1dcf 100644 --- a/src/App/ExtensionContainer.h +++ b/src/App/ExtensionContainer.h @@ -1,203 +1,220 @@ -/*************************************************************************** - * Copyright (c) 2016 Stefan Tröger * - * * - * 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(); - * 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::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 - ExtensionT* getExtensionByType(bool no_except=false, bool derived=true) const { - return static_cast(getExtension(ExtensionT::getExtensionClassTypeId(),derived,no_except)); - } - - //get all extensions which have the given base class - std::vector getExtensionsDerivedFrom(Base::Type type) const; - template - std::vector getExtensionsDerivedFromType() const { - std::vector typevec; - for(const auto& entry : _extensions) { - if(entry.first.isDerivedFrom(ExtensionT::getExtensionClassTypeId())) - typevec.push_back(static_cast(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 &Map) const override; - /// get all properties of the class (including properties of the parent) - void getPropertyList(std::vector &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 _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 * + * * + * 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(); + * 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::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 + ExtensionT* getExtensionByType(bool no_except=false, bool derived=true) const { + return static_cast(getExtension(ExtensionT::getExtensionClassTypeId(),derived,no_except)); + } + + //get all extensions which have the given base class + std::vector getExtensionsDerivedFrom(Base::Type type) const; + template + std::vector getExtensionsDerivedFromType() const { + std::vector typevec; + for(const auto& entry : _extensions) { + if(entry.first.isDerivedFrom(ExtensionT::getExtensionClassTypeId())) + typevec.push_back(static_cast(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 &Map) const override; + /// get all properties of the class (including properties of the parent) + void getPropertyList(std::vector &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 _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 diff --git a/src/App/PropertyContainer.cpp b/src/App/PropertyContainer.cpp index 3e3416e637..09e9120c69 100644 --- a/src/App/PropertyContainer.cpp +++ b/src/App/PropertyContainer.cpp @@ -1,649 +1,650 @@ -/*************************************************************************** - * Copyright (c) 2002 Jürgen Riegel * - * * - * 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 -#include -#include -#include - -#include "Property.h" -#include "PropertyContainer.h" - - -FC_LOG_LEVEL_INIT("App",true,true) - -using namespace App; -using namespace Base; -using namespace std; - -TYPESYSTEM_SOURCE(App::PropertyContainer,Base::Persistence) - - -//************************************************************************** -// Construction/Destruction - -// Here's the implementation! Description should take place in the header file! -PropertyContainer::PropertyContainer() -{ - propertyData.parentPropertyData = nullptr; -} - -PropertyContainer::~PropertyContainer() = default; - -unsigned int PropertyContainer::getMemSize () const -{ - std::map Map; - getPropertyMap(Map); - std::map::const_iterator It; - unsigned int size = 0; - for (It = Map.begin(); It != Map.end();++It) - size += It->second->getMemSize(); - return size; -} - - -App::Property* PropertyContainer::addDynamicProperty( - const char* type, const char* name, const char* group, const char* doc, - short attr, bool ro, bool hidden) -{ - return dynamicProps.addDynamicProperty(*this,type,name,group,doc,attr,ro,hidden); -} - -Property *PropertyContainer::getPropertyByName(const char* name) const -{ - auto prop = dynamicProps.getDynamicPropertyByName(name); - if(prop) - return prop; - return getPropertyData().getPropertyByName(this,name); -} - -void PropertyContainer::getPropertyMap(std::map &Map) const -{ - dynamicProps.getPropertyMap(Map); - getPropertyData().getPropertyMap(this,Map); -} - -void PropertyContainer::getPropertyList(std::vector &List) const -{ - dynamicProps.getPropertyList(List); - getPropertyData().getPropertyList(this,List); -} - -void PropertyContainer::getPropertyNamedList(std::vector > &List) const -{ - dynamicProps.getPropertyNamedList(List); - getPropertyData().getPropertyNamedList(this,List); -} - -void PropertyContainer::setPropertyStatus(unsigned char bit,bool value) -{ - std::vector List; - getPropertyList(List); - for(std::vector::const_iterator it=List.begin();it!=List.end();++it) - (**it).StatusBits.set(bit,value); -} - -short PropertyContainer::getPropertyType(const Property* prop) const -{ - return prop?prop->getType():0; -} - -short PropertyContainer::getPropertyType(const char *name) const -{ - return getPropertyType(getPropertyByName(name)); -} - -const char* PropertyContainer::getPropertyGroup(const Property* prop) const -{ - auto group = dynamicProps.getPropertyGroup(prop); - if(group) - return group; - return getPropertyData().getGroup(this,prop); -} - -const char* PropertyContainer::getPropertyGroup(const char *name) const -{ - auto group = dynamicProps.getPropertyGroup(name); - if(group) - return group; - return getPropertyData().getGroup(this,name); -} - -const char* PropertyContainer::getPropertyDocumentation(const Property* prop) const -{ - auto doc = dynamicProps.getPropertyDocumentation(prop); - if(doc) - return doc; - return getPropertyData().getDocumentation(this,prop); -} - -const char* PropertyContainer::getPropertyDocumentation(const char *name) const -{ - auto doc = dynamicProps.getPropertyDocumentation(name); - if(doc) - return doc; - return getPropertyData().getDocumentation(this,name); -} - -bool PropertyContainer::isReadOnly(const Property* prop) const -{ - return (getPropertyType(prop) & Prop_ReadOnly) == Prop_ReadOnly; -} - -bool PropertyContainer::isReadOnly(const char *name) const -{ - return (getPropertyType(name) & Prop_ReadOnly) == Prop_ReadOnly; -} - -bool PropertyContainer::isHidden(const Property* prop) const -{ - return (getPropertyType(prop) & Prop_Hidden) == Prop_Hidden; -} - -bool PropertyContainer::isHidden(const char *name) const -{ - return (getPropertyType(name) & Prop_Hidden) == Prop_Hidden; -} - -const char* PropertyContainer::getPropertyName(const Property* prop)const -{ - auto res = dynamicProps.getPropertyName(prop); - if(!res) - res = getPropertyData().getName(this,prop); - return res; -} - -const PropertyData * PropertyContainer::getPropertyDataPtr(){return &propertyData;} -const PropertyData & PropertyContainer::getPropertyData() const{return propertyData;} - -/** - * @brief PropertyContainer::handleChangedPropertyName is called during restore to possibly - * fix reading of older versions of this property container. This method is typically called - * if the property on file has changed its name in more recent versions. - * - * The default implementation does nothing. - * - * @param reader The XML stream to read from. - * @param TypeName Name of property type on file. - * @param PropName Name of property on file that does not exist in the container anymore. - */ - -void PropertyContainer::handleChangedPropertyName(Base::XMLReader &reader, const char * TypeName, const char *PropName) -{ - (void)reader; - (void)TypeName; - (void)PropName; -} - -/** - * @brief PropertyContainer::handleChangedPropertyType is called during restore to possibly - * fix reading of older versions of the property container. This method is typically called - * if the property on file has changed its type in more recent versions. - * - * The default implementation does nothing. - * - * @param reader The XML stream to read from. - * @param TypeName Name of property type on file. - * @param prop Pointer to property to restore. Its type differs from TypeName. - */ - -void PropertyContainer::handleChangedPropertyType(XMLReader &reader, const char *TypeName, Property *prop) -{ - (void)reader; - (void)TypeName; - (void)prop; -} - -PropertyData PropertyContainer::propertyData; - -void PropertyContainer::Save (Base::Writer &writer) const -{ - std::map Map; - getPropertyMap(Map); - - std::vector transients; - for(auto it=Map.begin();it!=Map.end();) { - auto prop = it->second; - if(prop->testStatus(Property::PropNoPersist)) { - it = Map.erase(it); - continue; - } - if(!prop->testStatus(Property::PropDynamic) - && (prop->testStatus(Property::Transient) || - getPropertyType(prop) & Prop_Transient)) - { - transients.push_back(prop); - it = Map.erase(it); - }else - ++it; - } - - writer.incInd(); // indentation for 'Properties Count' - writer.Stream() << writer.ind() << "" << endl; - - // First store transient properties to persist their status value. We use - // a new element named "_Property" so that the save file can be opened by - // older versions of FC. - writer.incInd(); - for(auto prop : transients) { - writer.Stream() << writer.ind() << "<_Property name=\"" << prop->getName() - << "\" type=\"" << prop->getTypeId().getName() - << "\" status=\"" << prop->getStatus() << "\"/>" << std::endl; - } - writer.decInd(); - - // Now store normal properties - for (auto it = Map.begin(); it != Map.end(); ++it) - { - writer.incInd(); // indentation for 'Property name' - writer.Stream() << writer.ind() << "first << "\" type=\"" - << it->second->getTypeId().getName(); - - dynamicProps.save(it->second,writer); - - auto status = it->second->getStatus(); - if(status) - writer.Stream() << "\" status=\"" << status; - writer.Stream() << "\">"; - - if(it->second->testStatus(Property::Transient) - || it->second->getType() & Prop_Transient) - { - writer.decInd(); - writer.Stream() << "" << std::endl; - continue; - } - - writer.Stream() << std::endl; - - writer.incInd(); // indentation for the actual property - - try { - // We must make sure to handle all exceptions accordingly so that - // the project file doesn't get invalidated. In the error case this - // means to proceed instead of aborting the write operation. - it->second->Save(writer); - } - catch (const Base::Exception &e) { - Base::Console().Error("%s\n", e.what()); - } - catch (const std::exception &e) { - Base::Console().Error("%s\n", e.what()); - } - catch (const char* e) { - Base::Console().Error("%s\n", e); - } -#ifndef FC_DEBUG - catch (...) { - Base::Console().Error("PropertyContainer::Save: Unknown C++ exception thrown. Try to continue...\n"); - } -#endif - writer.decInd(); // indentation for the actual property - writer.Stream() << writer.ind() << "" << endl; - writer.decInd(); // indentation for 'Property name' - } - writer.Stream() << writer.ind() << "" << endl; - writer.decInd(); // indentation for 'Properties Count' -} - -void PropertyContainer::Restore(Base::XMLReader &reader) -{ - reader.clearPartialRestoreProperty(); - reader.readElement("Properties"); - int Cnt = reader.getAttributeAsInteger("Count"); - - int transientCount = 0; - if(reader.hasAttribute("TransientCount")) - transientCount = reader.getAttributeAsUnsigned("TransientCount"); - - for (int i=0;igetName() << "'"); - if(prop && reader.hasAttribute("status")) - prop->setStatusValue(reader.getAttributeAsUnsigned("status")); - } - - for (int i=0 ;igetContainer() != this) - prop = dynamicProps.restore(*this,PropName.c_str(),TypeName.c_str(),reader); - - decltype(Property::StatusBits) status; - if(reader.hasAttribute("status")) { - status = decltype(status)(reader.getAttributeAsUnsigned("status")); - if(prop) - prop->setStatusValue(status.to_ulong()); - } - // name and type match - if (prop && strcmp(prop->getTypeId().getName(), TypeName.c_str()) == 0) { - if (!prop->testStatus(Property::Transient) - && !status.test(Property::Transient) - && !status.test(Property::PropTransient) - && !prop->testStatus(Property::PropTransient)) - { - FC_TRACE("restore property '" << prop->getName() << "'"); - prop->Restore(reader); - }else - FC_TRACE("skip transient '" << prop->getName() << "'"); - } - // name matches but not the type - else if (prop) { - handleChangedPropertyType(reader, TypeName.c_str(), prop); - } - // name doesn't match, the sub-class then has to know - // if the property has been renamed or removed - else { - handleChangedPropertyName(reader, TypeName.c_str(), PropName.c_str()); - } - - if (reader.testStatus(Base::XMLReader::ReaderStatus::PartialRestoreInProperty)) { - Base::Console().Error("Property %s of type %s was subject to a partial restore.\n",PropName.c_str(),TypeName.c_str()); - reader.clearPartialRestoreProperty(); - } - } - catch (const Base::XMLParseException&) { - throw; // re-throw - } - catch (const Base::RestoreError &) { - reader.setPartialRestore(true); - reader.clearPartialRestoreProperty(); - Base::Console().Error("Property %s of type %s was subject to a partial restore.\n",PropName.c_str(),TypeName.c_str()); - } - catch (const Base::Exception &e) { - Base::Console().Error("%s\n", e.what()); - } - catch (const std::exception &e) { - Base::Console().Error("%s\n", e.what()); - } - catch (const char* e) { - Base::Console().Error("%s\n", e); - } -#ifndef FC_DEBUG - catch (...) { - Base::Console().Error("PropertyContainer::Restore: Unknown C++ exception thrown\n"); - } -#endif - reader.readEndElement("Property"); - } - reader.readEndElement("Properties"); -} - -void PropertyContainer::onPropertyStatusChanged(const Property &prop, unsigned long oldStatus) -{ - (void)prop; - (void)oldStatus; -} - -void PropertyData::addProperty(OffsetBase offsetBase,const char* PropName, Property *Prop, const char* PropertyGroup , PropertyType Type, const char* PropertyDocu) -{ -#ifdef FC_DEBUG - if(!parentMerged) -#endif - { - short offset = offsetBase.getOffsetTo(Prop); - if(offset < 0) - throw Base::RuntimeError("Invalid static property"); - auto &index = propertyData.get<1>(); - auto it = index.find(PropName); - if(it == index.end()) { - if(parentMerged) - throw Base::RuntimeError("Cannot add static property"); - index.emplace(PropName, PropertyGroup, PropertyDocu, offset, Type); - } else{ -#ifdef FC_DEBUG - if(it->Offset != offset) { - FC_ERR("Duplicate property '" << PropName << "'"); - } -#endif - } - } - - Prop->syncType(Type); - Prop->myName = PropName; -} - -void PropertyData::merge(PropertyData *other) const { - if(!other) - other = const_cast(parentPropertyData); - if(other == parentPropertyData) { - if(parentMerged) - return; - parentMerged = true; - } - if(other) { - other->merge(); - auto &index = propertyData.get<0>(); - for(const auto &spec : other->propertyData.get<0>()) - index.push_back(spec); - } -} - -void PropertyData::split(PropertyData *other) { - if(other == parentPropertyData) { - if(!parentMerged) - return; - parentMerged = false; - } - if(other) { - auto &index = propertyData.get<2>(); - for(const auto &spec : other->propertyData.get<0>()) - index.erase(spec.Offset); - } -} - -const PropertyData::PropertySpec *PropertyData::findProperty(OffsetBase offsetBase,const char* PropName) const -{ - (void)offsetBase; - merge(); - auto &index = propertyData.get<1>(); - auto it = index.find(PropName); - if(it != index.end()) - return &(*it); - return nullptr; -} - -const PropertyData::PropertySpec *PropertyData::findProperty(OffsetBase offsetBase,const Property* prop) const -{ - merge(); - int diff = offsetBase.getOffsetTo(prop); - if(diff<0) - return nullptr; - - auto &index = propertyData.get<2>(); - auto it = index.find(diff); - if(it!=index.end()) - return &(*it); - - return nullptr; -} - -const char* PropertyData::getName(OffsetBase offsetBase,const Property* prop) const -{ - const PropertyData::PropertySpec* Spec = findProperty(offsetBase,prop); - - if(Spec) - return Spec->Name; - else - return nullptr; -} - -short PropertyData::getType(OffsetBase offsetBase,const Property* prop) const -{ - const PropertyData::PropertySpec* Spec = findProperty(offsetBase,prop); - - if(Spec) - return Spec->Type; - else - return 0; -} - -short PropertyData::getType(OffsetBase offsetBase,const char* name) const -{ - const PropertyData::PropertySpec* Spec = findProperty(offsetBase,name); - - if(Spec) - return Spec->Type; - else - return 0; -} - -const char* PropertyData::getGroup(OffsetBase offsetBase,const Property* prop) const -{ - const PropertyData::PropertySpec* Spec = findProperty(offsetBase,prop); - - if(Spec) - return Spec->Group; - else - return nullptr; -} - -const char* PropertyData::getGroup(OffsetBase offsetBase,const char* name) const -{ - const PropertyData::PropertySpec* Spec = findProperty(offsetBase,name); - - if(Spec) - return Spec->Group; - else - return nullptr; -} - -const char* PropertyData::getDocumentation(OffsetBase offsetBase,const Property* prop) const -{ - const PropertyData::PropertySpec* Spec = findProperty(offsetBase,prop); - - if(Spec) - return Spec->Docu; - else - return nullptr; -} - -const char* PropertyData::getDocumentation(OffsetBase offsetBase,const char* name) const -{ - const PropertyData::PropertySpec* Spec = findProperty(offsetBase,name); - - if(Spec) - return Spec->Docu; - else - return nullptr; -} - -Property *PropertyData::getPropertyByName(OffsetBase offsetBase,const char* name) const -{ - const PropertyData::PropertySpec* Spec = findProperty(offsetBase,name); - - if(Spec) - return reinterpret_cast(Spec->Offset + offsetBase.getOffset()); - else - return nullptr; -} - -void PropertyData::getPropertyMap(OffsetBase offsetBase,std::map &Map) const -{ - merge(); - for(auto &spec : propertyData.get<0>()) - Map[spec.Name] = reinterpret_cast(spec.Offset + offsetBase.getOffset()); -} - -void PropertyData::getPropertyList(OffsetBase offsetBase,std::vector &List) const -{ - merge(); - size_t base = List.size(); - List.reserve(base+propertyData.size()); - for (auto &spec : propertyData.get<0>()) - List.push_back(reinterpret_cast(spec.Offset + offsetBase.getOffset())); -} - -void PropertyData::getPropertyNamedList(OffsetBase offsetBase, - std::vector > &List) const -{ - merge(); - size_t base = List.size(); - List.reserve(base+propertyData.size()); - for (auto &spec : propertyData.get<0>()) { - auto prop = reinterpret_cast(spec.Offset + offsetBase.getOffset()); - List.emplace_back(prop->getName(),prop); - } -} - - - -/** \defgroup PropFrame Property framework - \ingroup APP - \brief System to access object properties -\section Introduction -The property framework introduces the ability to access attributes (member variables) of a class by name without -knowing the class type. It's like the reflection mechanism of Java or C#. -This ability is introduced by the App::PropertyContainer class and can be used by all derived classes. - -This makes it possible in the first place to make an automatic mapping to python (e.g. in App::FeaturePy) and -abstract editing properties in Gui::PropertyEditor. - -\section Examples - -Here some little examples how to use it: - -\code -// search in PropertyList -Property *prop = _pcFeature->getPropertyByName(attr); -if(prop) -{ - return prop->getPyObject(); -} -\endcode - -or: - -\code -void PropertyContainer::Restore(Base::Reader &reader) -{ - reader.readElement("Properties"); - int Cnt = reader.getAttributeAsInteger("Count"); - - for(int i=0 ;iRestore(reader); - - reader.readEndElement("Property"); - } - reader.readEndElement("Properties"); -} -\endcode - -*/ +/*************************************************************************** + * Copyright (c) 2002 Jürgen Riegel * + * * + * 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 +#include +#include +#include + +#include "Property.h" +#include "PropertyContainer.h" + + +FC_LOG_LEVEL_INIT("App",true,true) + +using namespace App; +using namespace Base; +using namespace std; + +TYPESYSTEM_SOURCE(App::PropertyContainer,Base::Persistence) + + +//************************************************************************** +// Construction/Destruction + +// Here's the implementation! Description should take place in the header file! +PropertyContainer::PropertyContainer() +{ + propertyData.parentPropertyData = nullptr; +} + +PropertyContainer::~PropertyContainer() = default; + +unsigned int PropertyContainer::getMemSize () const +{ + std::map Map; + getPropertyMap(Map); + std::map::const_iterator It; + unsigned int size = 0; + for (It = Map.begin(); It != Map.end();++It) + size += It->second->getMemSize(); + return size; +} + + +App::Property* PropertyContainer::addDynamicProperty( + const char* type, const char* name, const char* group, const char* doc, + short attr, bool ro, bool hidden) +{ + return dynamicProps.addDynamicProperty(*this,type,name,group,doc,attr,ro,hidden); +} + +Property *PropertyContainer::getPropertyByName(const char* name) const +{ + auto prop = dynamicProps.getDynamicPropertyByName(name); + if(prop) + return prop; + return getPropertyData().getPropertyByName(this,name); +} + +void PropertyContainer::getPropertyMap(std::map &Map) const +{ + dynamicProps.getPropertyMap(Map); + getPropertyData().getPropertyMap(this,Map); +} + +void PropertyContainer::getPropertyList(std::vector &List) const +{ + dynamicProps.getPropertyList(List); + getPropertyData().getPropertyList(this,List); +} + +void PropertyContainer::getPropertyNamedList(std::vector > &List) const +{ + dynamicProps.getPropertyNamedList(List); + getPropertyData().getPropertyNamedList(this,List); +} + +void PropertyContainer::setPropertyStatus(unsigned char bit,bool value) +{ + std::vector List; + getPropertyList(List); + for(std::vector::const_iterator it=List.begin();it!=List.end();++it) + (**it).StatusBits.set(bit,value); +} + +short PropertyContainer::getPropertyType(const Property* prop) const +{ + return prop?prop->getType():0; +} + +short PropertyContainer::getPropertyType(const char *name) const +{ + return getPropertyType(getPropertyByName(name)); +} + +const char* PropertyContainer::getPropertyGroup(const Property* prop) const +{ + auto group = dynamicProps.getPropertyGroup(prop); + if(group) + return group; + return getPropertyData().getGroup(this,prop); +} + +const char* PropertyContainer::getPropertyGroup(const char *name) const +{ + auto group = dynamicProps.getPropertyGroup(name); + if(group) + return group; + return getPropertyData().getGroup(this,name); +} + +const char* PropertyContainer::getPropertyDocumentation(const Property* prop) const +{ + auto doc = dynamicProps.getPropertyDocumentation(prop); + if(doc) + return doc; + return getPropertyData().getDocumentation(this,prop); +} + +const char* PropertyContainer::getPropertyDocumentation(const char *name) const +{ + auto doc = dynamicProps.getPropertyDocumentation(name); + if(doc) + return doc; + return getPropertyData().getDocumentation(this,name); +} + +bool PropertyContainer::isReadOnly(const Property* prop) const +{ + return (getPropertyType(prop) & Prop_ReadOnly) == Prop_ReadOnly; +} + +bool PropertyContainer::isReadOnly(const char *name) const +{ + return (getPropertyType(name) & Prop_ReadOnly) == Prop_ReadOnly; +} + +bool PropertyContainer::isHidden(const Property* prop) const +{ + return (getPropertyType(prop) & Prop_Hidden) == Prop_Hidden; +} + +bool PropertyContainer::isHidden(const char *name) const +{ + return (getPropertyType(name) & Prop_Hidden) == Prop_Hidden; +} + +const char* PropertyContainer::getPropertyName(const Property* prop)const +{ + auto res = dynamicProps.getPropertyName(prop); + if(!res) + res = getPropertyData().getName(this,prop); + return res; +} + +const PropertyData * PropertyContainer::getPropertyDataPtr(){return &propertyData;} +const PropertyData & PropertyContainer::getPropertyData() const{return propertyData;} + + +/** + * @brief PropertyContainer::handleChangedPropertyName is called during restore to possibly + * fix reading of older versions of this property container. This method is typically called + * if the property on file has changed its name in more recent versions. + * + * The default implementation does nothing. + * + * @param reader The XML stream to read from. + * @param TypeName Name of property type on file. + * @param PropName Name of property on file that does not exist in the container anymore. + */ + +void PropertyContainer::handleChangedPropertyName(Base::XMLReader &reader, const char * TypeName, const char *PropName) +{ + (void)reader; + (void)TypeName; + (void)PropName; +} + +/** + * @brief PropertyContainer::handleChangedPropertyType is called during restore to possibly + * fix reading of older versions of the property container. This method is typically called + * if the property on file has changed its type in more recent versions. + * + * The default implementation does nothing. + * + * @param reader The XML stream to read from. + * @param TypeName Name of property type on file. + * @param prop Pointer to property to restore. Its type differs from TypeName. + */ + +void PropertyContainer::handleChangedPropertyType(XMLReader &reader, const char *TypeName, Property *prop) +{ + (void)reader; + (void)TypeName; + (void)prop; +} + +PropertyData PropertyContainer::propertyData; + +void PropertyContainer::Save (Base::Writer &writer) const +{ + std::map Map; + getPropertyMap(Map); + + std::vector transients; + for(auto it=Map.begin();it!=Map.end();) { + auto prop = it->second; + if(prop->testStatus(Property::PropNoPersist)) { + it = Map.erase(it); + continue; + } + if(!prop->testStatus(Property::PropDynamic) + && (prop->testStatus(Property::Transient) || + getPropertyType(prop) & Prop_Transient)) + { + transients.push_back(prop); + it = Map.erase(it); + }else + ++it; + } + + writer.incInd(); // indentation for 'Properties Count' + writer.Stream() << writer.ind() << "" << endl; + + // First store transient properties to persist their status value. We use + // a new element named "_Property" so that the save file can be opened by + // older versions of FC. + writer.incInd(); + for(auto prop : transients) { + writer.Stream() << writer.ind() << "<_Property name=\"" << prop->getName() + << "\" type=\"" << prop->getTypeId().getName() + << "\" status=\"" << prop->getStatus() << "\"/>" << std::endl; + } + writer.decInd(); + + // Now store normal properties + for (auto it = Map.begin(); it != Map.end(); ++it) + { + writer.incInd(); // indentation for 'Property name' + writer.Stream() << writer.ind() << "first << "\" type=\"" + << it->second->getTypeId().getName(); + + dynamicProps.save(it->second,writer); + + auto status = it->second->getStatus(); + if(status) + writer.Stream() << "\" status=\"" << status; + writer.Stream() << "\">"; + + if(it->second->testStatus(Property::Transient) + || it->second->getType() & Prop_Transient) + { + writer.decInd(); + writer.Stream() << "" << std::endl; + continue; + } + + writer.Stream() << std::endl; + + writer.incInd(); // indentation for the actual property + + try { + // We must make sure to handle all exceptions accordingly so that + // the project file doesn't get invalidated. In the error case this + // means to proceed instead of aborting the write operation. + it->second->Save(writer); + } + catch (const Base::Exception &e) { + Base::Console().Error("%s\n", e.what()); + } + catch (const std::exception &e) { + Base::Console().Error("%s\n", e.what()); + } + catch (const char* e) { + Base::Console().Error("%s\n", e); + } +#ifndef FC_DEBUG + catch (...) { + Base::Console().Error("PropertyContainer::Save: Unknown C++ exception thrown. Try to continue...\n"); + } +#endif + writer.decInd(); // indentation for the actual property + writer.Stream() << writer.ind() << "" << endl; + writer.decInd(); // indentation for 'Property name' + } + writer.Stream() << writer.ind() << "" << endl; + writer.decInd(); // indentation for 'Properties Count' +} + +void PropertyContainer::Restore(Base::XMLReader &reader) +{ + reader.clearPartialRestoreProperty(); + reader.readElement("Properties"); + int Cnt = reader.getAttributeAsInteger("Count"); + + int transientCount = 0; + if(reader.hasAttribute("TransientCount")) + transientCount = reader.getAttributeAsUnsigned("TransientCount"); + + for (int i=0;igetName() << "'"); + if(prop && reader.hasAttribute("status")) + prop->setStatusValue(reader.getAttributeAsUnsigned("status")); + } + + for (int i=0 ;igetContainer() != this) + prop = dynamicProps.restore(*this,PropName.c_str(),TypeName.c_str(),reader); + + decltype(Property::StatusBits) status; + if(reader.hasAttribute("status")) { + status = decltype(status)(reader.getAttributeAsUnsigned("status")); + if(prop) + prop->setStatusValue(status.to_ulong()); + } + // name and type match + if (prop && strcmp(prop->getTypeId().getName(), TypeName.c_str()) == 0) { + if (!prop->testStatus(Property::Transient) + && !status.test(Property::Transient) + && !status.test(Property::PropTransient) + && !prop->testStatus(Property::PropTransient)) + { + FC_TRACE("restore property '" << prop->getName() << "'"); + prop->Restore(reader); + }else + FC_TRACE("skip transient '" << prop->getName() << "'"); + } + // name matches but not the type + else if (prop) { + handleChangedPropertyType(reader, TypeName.c_str(), prop); + } + // name doesn't match, the sub-class then has to know + // if the property has been renamed or removed + else { + handleChangedPropertyName(reader, TypeName.c_str(), PropName.c_str()); + } + + if (reader.testStatus(Base::XMLReader::ReaderStatus::PartialRestoreInProperty)) { + Base::Console().Error("Property %s of type %s was subject to a partial restore.\n",PropName.c_str(),TypeName.c_str()); + reader.clearPartialRestoreProperty(); + } + } + catch (const Base::XMLParseException&) { + throw; // re-throw + } + catch (const Base::RestoreError &) { + reader.setPartialRestore(true); + reader.clearPartialRestoreProperty(); + Base::Console().Error("Property %s of type %s was subject to a partial restore.\n",PropName.c_str(),TypeName.c_str()); + } + catch (const Base::Exception &e) { + Base::Console().Error("%s\n", e.what()); + } + catch (const std::exception &e) { + Base::Console().Error("%s\n", e.what()); + } + catch (const char* e) { + Base::Console().Error("%s\n", e); + } +#ifndef FC_DEBUG + catch (...) { + Base::Console().Error("PropertyContainer::Restore: Unknown C++ exception thrown\n"); + } +#endif + reader.readEndElement("Property"); + } + reader.readEndElement("Properties"); +} + +void PropertyContainer::onPropertyStatusChanged(const Property &prop, unsigned long oldStatus) +{ + (void)prop; + (void)oldStatus; +} + +void PropertyData::addProperty(OffsetBase offsetBase,const char* PropName, Property *Prop, const char* PropertyGroup , PropertyType Type, const char* PropertyDocu) +{ +#ifdef FC_DEBUG + if(!parentMerged) +#endif + { + short offset = offsetBase.getOffsetTo(Prop); + if(offset < 0) + throw Base::RuntimeError("Invalid static property"); + auto &index = propertyData.get<1>(); + auto it = index.find(PropName); + if(it == index.end()) { + if(parentMerged) + throw Base::RuntimeError("Cannot add static property"); + index.emplace(PropName, PropertyGroup, PropertyDocu, offset, Type); + } else{ +#ifdef FC_DEBUG + if(it->Offset != offset) { + FC_ERR("Duplicate property '" << PropName << "'"); + } +#endif + } + } + + Prop->syncType(Type); + Prop->myName = PropName; +} + +void PropertyData::merge(PropertyData *other) const { + if(!other) + other = const_cast(parentPropertyData); + if(other == parentPropertyData) { + if(parentMerged) + return; + parentMerged = true; + } + if(other) { + other->merge(); + auto &index = propertyData.get<0>(); + for(const auto &spec : other->propertyData.get<0>()) + index.push_back(spec); + } +} + +void PropertyData::split(PropertyData *other) { + if(other == parentPropertyData) { + if(!parentMerged) + return; + parentMerged = false; + } + if(other) { + auto &index = propertyData.get<2>(); + for(const auto &spec : other->propertyData.get<0>()) + index.erase(spec.Offset); + } +} + +const PropertyData::PropertySpec *PropertyData::findProperty(OffsetBase offsetBase,const char* PropName) const +{ + (void)offsetBase; + merge(); + auto &index = propertyData.get<1>(); + auto it = index.find(PropName); + if(it != index.end()) + return &(*it); + return nullptr; +} + +const PropertyData::PropertySpec *PropertyData::findProperty(OffsetBase offsetBase,const Property* prop) const +{ + merge(); + int diff = offsetBase.getOffsetTo(prop); + if(diff<0) + return nullptr; + + auto &index = propertyData.get<2>(); + auto it = index.find(diff); + if(it!=index.end()) + return &(*it); + + return nullptr; +} + +const char* PropertyData::getName(OffsetBase offsetBase,const Property* prop) const +{ + const PropertyData::PropertySpec* Spec = findProperty(offsetBase,prop); + + if(Spec) + return Spec->Name; + else + return nullptr; +} + +short PropertyData::getType(OffsetBase offsetBase,const Property* prop) const +{ + const PropertyData::PropertySpec* Spec = findProperty(offsetBase,prop); + + if(Spec) + return Spec->Type; + else + return 0; +} + +short PropertyData::getType(OffsetBase offsetBase,const char* name) const +{ + const PropertyData::PropertySpec* Spec = findProperty(offsetBase,name); + + if(Spec) + return Spec->Type; + else + return 0; +} + +const char* PropertyData::getGroup(OffsetBase offsetBase,const Property* prop) const +{ + const PropertyData::PropertySpec* Spec = findProperty(offsetBase,prop); + + if(Spec) + return Spec->Group; + else + return nullptr; +} + +const char* PropertyData::getGroup(OffsetBase offsetBase,const char* name) const +{ + const PropertyData::PropertySpec* Spec = findProperty(offsetBase,name); + + if(Spec) + return Spec->Group; + else + return nullptr; +} + +const char* PropertyData::getDocumentation(OffsetBase offsetBase,const Property* prop) const +{ + const PropertyData::PropertySpec* Spec = findProperty(offsetBase,prop); + + if(Spec) + return Spec->Docu; + else + return nullptr; +} + +const char* PropertyData::getDocumentation(OffsetBase offsetBase,const char* name) const +{ + const PropertyData::PropertySpec* Spec = findProperty(offsetBase,name); + + if(Spec) + return Spec->Docu; + else + return nullptr; +} + +Property *PropertyData::getPropertyByName(OffsetBase offsetBase,const char* name) const +{ + const PropertyData::PropertySpec* Spec = findProperty(offsetBase,name); + + if(Spec) + return reinterpret_cast(Spec->Offset + offsetBase.getOffset()); + else + return nullptr; +} + +void PropertyData::getPropertyMap(OffsetBase offsetBase,std::map &Map) const +{ + merge(); + for(auto &spec : propertyData.get<0>()) + Map[spec.Name] = reinterpret_cast(spec.Offset + offsetBase.getOffset()); +} + +void PropertyData::getPropertyList(OffsetBase offsetBase,std::vector &List) const +{ + merge(); + size_t base = List.size(); + List.reserve(base+propertyData.size()); + for (auto &spec : propertyData.get<0>()) + List.push_back(reinterpret_cast(spec.Offset + offsetBase.getOffset())); +} + +void PropertyData::getPropertyNamedList(OffsetBase offsetBase, + std::vector > &List) const +{ + merge(); + size_t base = List.size(); + List.reserve(base+propertyData.size()); + for (auto &spec : propertyData.get<0>()) { + auto prop = reinterpret_cast(spec.Offset + offsetBase.getOffset()); + List.emplace_back(prop->getName(),prop); + } +} + + + +/** \defgroup PropFrame Property framework + \ingroup APP + \brief System to access object properties +\section Introduction +The property framework introduces the ability to access attributes (member variables) of a class by name without +knowing the class type. It's like the reflection mechanism of Java or C#. +This ability is introduced by the App::PropertyContainer class and can be used by all derived classes. + +This makes it possible in the first place to make an automatic mapping to python (e.g. in App::FeaturePy) and +abstract editing properties in Gui::PropertyEditor. + +\section Examples + +Here some little examples how to use it: + +\code +// search in PropertyList +Property *prop = _pcFeature->getPropertyByName(attr); +if(prop) +{ + return prop->getPyObject(); +} +\endcode + +or: + +\code +void PropertyContainer::Restore(Base::Reader &reader) +{ + reader.readElement("Properties"); + int Cnt = reader.getAttributeAsInteger("Count"); + + for(int i=0 ;iRestore(reader); + + reader.readEndElement("Property"); + } + reader.readEndElement("Properties"); +} +\endcode + +*/ diff --git a/src/App/PropertyContainer.h b/src/App/PropertyContainer.h index e2ecac07fa..c86b03e9a4 100644 --- a/src/App/PropertyContainer.h +++ b/src/App/PropertyContainer.h @@ -1,343 +1,343 @@ -/*************************************************************************** - * Copyright (c) 2005 Jürgen Riegel * - * * - * 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 -#include -#include - -#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(ptbase+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, - CStringHasher, - CStringHasher - >, - bmi::hashed_unique< - bmi::member - > - > - > 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 &Map) const; - void getPropertyList(OffsetBase offsetBase,std::vector &List) const; - void getPropertyNamedList(OffsetBase offsetBase, std::vector > &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 &Map) const; - /// get all properties of the class (including properties of the parent) - virtual void getPropertyList(std::vector &List) const; - /// get all properties with their names, may contain duplicates and aliases - virtual void getPropertyNamedList(std::vector > &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 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(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(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 * + * * + * 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 +#include +#include + +#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(ptbase+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, + CStringHasher, + CStringHasher + >, + bmi::hashed_unique< + bmi::member + > + > + > 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 &Map) const; + void getPropertyList(OffsetBase offsetBase,std::vector &List) const; + void getPropertyNamedList(OffsetBase offsetBase, std::vector > &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 &Map) const; + /// get all properties of the class (including properties of the parent) + virtual void getPropertyList(std::vector &List) const; + /// get all properties with their names, may contain duplicates and aliases + virtual void getPropertyNamedList(std::vector > &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 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(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(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