/*************************************************************************** * Copyright (c) Stefan Tröger (stefantroeger@gmx.net) 2016 * * * * 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 "PropertyPythonObject.h" #include "ExtensionContainer.h" #include "Base/Interpreter.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()); * } * typedef ExtensionPythonT MyExtensionPython; * @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 trival, 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 * typedef ExtensionPythonT> MyExtensionPython; * @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(void); /** @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=0); //@} virtual void extensionOnChanged(const Property* p) {(void)(p);} friend class App::ExtensionContainer; protected: void initExtensionType(Base::Type type); bool m_isPythonExtension = false; Py::Object ExtensionPythonObject; private: Base::Type m_extensionType; App::ExtensionContainer* m_base = nullptr; }; // Property define #define EXTENSION_ADD_PROPERTY(_prop_, _defaultval_) \ do { \ this->_prop_.setValue _defaultval_;\ propertyData.addProperty(static_cast(this), #_prop_, &this->_prop_); \ } while (0) #define EXTENSION_ADD_PROPERTY_TYPE(_prop_, _defaultval_, _group_,_type_,_Docu_) \ do { \ this->_prop_.setValue _defaultval_;\ propertyData.addProperty(static_cast(this), #_prop_, &this->_prop_, (_group_),(_type_),(_Docu_)); \ } while (0) /** * Generic Python extension class which allows every extension derived * class to behave as a Python extension -- simply by subclassing. */ template class ExtensionPythonT : public ExtensionT { EXTENSION_PROPERTY_HEADER(App::ExtensionPythonT); public: typedef ExtensionT Inherited; ExtensionPythonT() { ExtensionT::m_isPythonExtension = true; ExtensionT::initExtensionType(ExtensionPythonT::getExtensionClassTypeId()); EXTENSION_ADD_PROPERTY(ExtensionProxy,(Py::Object())); } virtual ~ExtensionPythonT() { } PropertyPythonObject ExtensionProxy; }; typedef ExtensionPythonT ExtensionPython; // Helper macros to define python extensions #define EXTENSION_PROXY_FIRST(function) \ Base::PyGILStateLocker lock;\ Py::Object result;\ try {\ Property* proxy = this->extensionGetPropertyByName("ExtensionProxy");\ if (proxy && proxy->getTypeId() == PropertyPythonObject::getClassTypeId()) {\ Py::Object feature = static_cast(proxy)->getValue();\ if (feature.hasAttr(std::string("function"))) {\ if (feature.hasAttr("__object__")) {\ Py::Callable method(feature.getAttr(std::string("function"))); #define EXTENSION_PROXY_SECOND(function)\ result = method.apply(args);\ }\ else {\ Py::Callable method(feature.getAttr(std::string("function"))); #define EXTENSION_PROXY_THIRD()\ result = method.apply(args);\ }\ }\ }\ }\ catch (Py::Exception&) {\ Base::PyException e;\ e.ReportException();\ } #define EXTENSION_PROXY_NOARG(function)\ EXTENSION_PROXY_FIRST(function) \ Py::Tuple args;\ EXTENSION_PROXY_SECOND(function) \ Py::Tuple args(1);\ args.setItem(0, Py::Object(this->getExtensionPyObject(), true));\ EXTENSION_PROXY_THIRD() #define EXTENSION_PROXY_ONEARG(function, arg)\ EXTENSION_PROXY_FIRST(function) \ Py::Tuple args;\ args.setItem(0, arg); \ EXTENSION_PROXY_SECOND(function) \ Py::Tuple args(2);\ args.setItem(0, Py::Object(this->getExtensionPyObject(), true));\ args.setItem(1, arg); \ EXTENSION_PROXY_THIRD() #define EXTENSION_PYTHON_OVERRIDE_VOID_NOARGS(function)\ virtual void function() override {\ EXTENSION_PROXY_NOARGS(function)\ }; #define EXTENSION_PYTHON_OVERRIDE_OBJECT_NOARGS(function)\ virtual PyObject* function() override {\ EXTENSION_PROXY_NOARGS(function)\ return res.ptr();\ }; } //App #endif // APP_EXTENSION_H