From 4dea0df06c4d088f71849229dfbbb6e38aeeac26 Mon Sep 17 00:00:00 2001 From: Abdullah Tahiri Date: Mon, 13 Feb 2023 16:55:41 +0100 Subject: [PATCH] App: Extension/ExtensionContainer - handle property change ========================================================== Currently changes of name or type of properties in a property container are handled by: void PropertyContainer::handleChangedPropertyName(Base::XMLReader &reader, const char * TypeName, const char *PropName) void PropertyContainer::changedPropertyType(Base::XMLReader &reader, const char * TypeName, Property * prop) There is no mechanism for handling property changes by extensions. Sometimes the solution is to explicitly call the extension from the container. However, this is a breach of the SRP, as the container should not be in a position to decide whether the extension needs or not handle property changes. The handling code of the container changes for two different reasons, for adapting the container to a property change of its own, and for adapting that of a property of the extension. Illustrating it with an example, following inheritance, it goes like this: PropertyContainer => ExtensionContainer => TransactionalObject => ViewProvider App::Extension => ViewProviderExtension The extension is currently not notified by the ExtensionContainer that a property needs handling. So a change in a property of a ViewProviderExtension needs code at the ViewProvider it was added to. This commit provides a mechanism in ExtensionContainer to call the extensions so that they can handle property changes. This functions: virtual bool extensionHandleChangedPropertyName(Base::XMLReader &reader, const char * TypeName, const char *PropName); virtual bool extensionHandleChangedPropertyType(Base::XMLReader &reader, const char * TypeName, Property * prop); Containers should always call the base class for any unhandled property change. If a sub-class container of ExtensionContainer handles property changes itself, but not the ones of the extensions, this call to the base class ultimately ensures that if the property was not handled by the container hierarchy, any extension is given an opportunity to handle it. Some examples: * A container handles the extension property change or its own: void ContainerSubClass::handleChangedPropertyType(...) { if (prop == &PropertyOfExt) { } else if (prop == &PropertyOfCont) { } else { ContainerBaseClass::handleChangedPropertyType(...); } } * A container and the extension handle their own: void ContainerSubClass::handleChangedPropertyType(...) { if (prop == &PropertyOfCont) { } else { // This will call ExtensionContainer::handleChangedPropertyType ContainerBaseClass::handleChangedPropertyType(...); } } bool ExtensionSubClass::extensionHandleChangedPropertyType(...) { if (prop == &PropertyOfCont) { return true; } return false; } --- src/App/Extension.cpp | 408 +++++----- src/App/Extension.h | 651 ++++++++-------- src/App/ExtensionContainer.cpp | 864 +++++++++++---------- src/App/ExtensionContainer.h | 423 ++++++----- src/App/PropertyContainer.cpp | 1299 ++++++++++++++++---------------- src/App/PropertyContainer.h | 686 ++++++++--------- 6 files changed, 2200 insertions(+), 2131 deletions(-) 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