Doc: Improve App::ExtensionContainer documentation
This commit is contained in:
committed by
Chris Hennes
parent
412cea78f9
commit
e9d298a94f
@@ -74,15 +74,15 @@ void ExtensionContainer::registerExtension(Base::Type extension, Extension* ext)
|
||||
_extensions[extension] = ext;
|
||||
}
|
||||
|
||||
bool ExtensionContainer::hasExtension(Base::Type t, bool derived) const
|
||||
bool ExtensionContainer::hasExtension(Base::Type type, bool derived) const
|
||||
{
|
||||
|
||||
// check for the exact type
|
||||
bool found = _extensions.find(t) != _extensions.end();
|
||||
bool found = _extensions.find(type) != _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)) {
|
||||
if (entry.first.isDerivedFrom(type)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,33 +35,41 @@ namespace App
|
||||
{
|
||||
|
||||
class Extension;
|
||||
|
||||
/**
|
||||
* @brief Container which can hold extensions
|
||||
* @brief A container that 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 provides a way to extend the functionality of
|
||||
* objects in FreeCAD despite Python's limitations with multiple inheritance
|
||||
* (see below for an explanation). Extensions are FreeCAD objects that act
|
||||
* like regular objects in the sense that they have properties and class
|
||||
* methods to define their functionality. However, extensions are not exposed
|
||||
* as individual usable entities but are used to extend other objects. An
|
||||
* extended object obtains all the properties and methods of the extension.
|
||||
*
|
||||
* The concept of extensions allows one 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
|
||||
* As such, it is like C++ multiple inheritance, which is indeed used to
|
||||
* achieve this on C++ side, but it provides a few important additional
|
||||
* functionalities as well:
|
||||
*
|
||||
* - 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 since in FreeCAD, all
|
||||
* functionality should be easily accessible from both Python and C++. To
|
||||
* ensure this -- as already noted -- extensions can be added to an object from
|
||||
* Python.
|
||||
*
|
||||
* However, this means that it is not clear from the C++ object type whether 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 the 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 whether an extension exists and then to get the extension
|
||||
* object. This interface always works the same, no matter if added from
|
||||
* Python or C++.
|
||||
*
|
||||
* The interoperability with python is highly important, as in FreeCAD all functionality should be
|
||||
* as easily accessible from python as from c++. To ensure this, and as already noted, extensions
|
||||
* can be added to a object from python. However, this means that it is not clear from the c++
|
||||
* object type if an extension was added or not. If added from c++ it becomes clear in the type due
|
||||
* to the use of multiple inheritance. If added from python it is a runtime extension and not
|
||||
* visible from type. Hence querying existing extensions of an object and accessing its methods
|
||||
* works not by type casting but by the interface provided in ExtensionContainer. The default
|
||||
* workflow is to query if an extension exists and then get the extension object. No matter if added
|
||||
* from python or c++ this interface works always the same.
|
||||
* @code
|
||||
* if (object->hasExtension(GroupExtension::getClassTypeId())) {
|
||||
* App::GroupExtension* group = object->getExtensionByType<GroupExtension>();
|
||||
@@ -69,33 +77,43 @@ class Extension;
|
||||
* }
|
||||
* @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.
|
||||
* 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 in addition to the
|
||||
* multiple inheritance when adding extensions from C++.
|
||||
*
|
||||
* 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
|
||||
* 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);
|
||||
* @code{.cpp}
|
||||
* 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) {
|
||||
*
|
||||
* 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.
|
||||
* 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)__:
|
||||
@@ -103,15 +121,28 @@ class Extension;
|
||||
* 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.
|
||||
* 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 queried if the method
|
||||
* to override exists in this proxy before calling it.
|
||||
*
|
||||
* For information on howto create extension see the documentation of Extension
|
||||
* For information on how to create extension see the documentation of
|
||||
* Extension.
|
||||
*
|
||||
* @section{sec_limitations_python_multiple_inheritance Limitations of Python}
|
||||
*
|
||||
* Without this extension system, it would be challenging to use extending
|
||||
* functionality in FreeCAD. Although C++ supports multiple inheritance, it is
|
||||
* not possible to use it in FreeCAD because it should be possible to expose
|
||||
* all objects to Python.
|
||||
*
|
||||
* However, using multiple parent classes in Python is currently not possible
|
||||
* with the default object approach. Moreover, it is basically impossible to
|
||||
* handle multiple inheritance in the C-API for Python extensions.
|
||||
*/
|
||||
class AppExport ExtensionContainer: public App::PropertyContainer
|
||||
{
|
||||
@@ -119,21 +150,96 @@ class AppExport ExtensionContainer: public App::PropertyContainer
|
||||
TYPESYSTEM_HEADER_WITH_OVERRIDE();
|
||||
|
||||
public:
|
||||
/// A type alias for iterating extensions.
|
||||
using ExtensionIterator = std::map<Base::Type, App::Extension*>::iterator;
|
||||
|
||||
/// Construct an extension container.
|
||||
ExtensionContainer();
|
||||
/// Destruct an extension container.
|
||||
~ExtensionContainer() override;
|
||||
|
||||
/**
|
||||
* @brief Register an extension.
|
||||
*
|
||||
* @param[in] extension The type of the extension to register.
|
||||
* @param[in] ext The extension to register.
|
||||
*/
|
||||
void registerExtension(Base::Type extension, App::Extension* ext);
|
||||
// returns first of type (or derived from if set to true) and throws otherwise
|
||||
bool hasExtension(Base::Type, bool derived = true) const;
|
||||
// this version does not check derived classes
|
||||
|
||||
/**
|
||||
* @brief Whether this container has an extension of the given type.
|
||||
*
|
||||
* This version checks for derived types. It returns the first extension
|
||||
* of the given type or the first extension that is derived from the type
|
||||
* if @p derived is true.
|
||||
*
|
||||
* @param[in] type The type of the extension to check for.
|
||||
* @param[in] derived (Optionally) Whether to check for derived types, true by default.
|
||||
*
|
||||
* @return True if the container has the (derived) extension, false otherwise.
|
||||
*/
|
||||
bool hasExtension(Base::Type type, bool derived = true) const;
|
||||
|
||||
/**
|
||||
* @brief Whether this container has an extension of the given type.
|
||||
*
|
||||
* This version checks for derived types. It returns the first extension
|
||||
* of the given type. Note that this function does not check for derived
|
||||
* types.
|
||||
*
|
||||
* @param[in] name The name of the extension to check for.
|
||||
* @return True if the container has the extension, false otherwise.
|
||||
*/
|
||||
bool hasExtension(const std::string& name) const;
|
||||
|
||||
/**
|
||||
* @brief Whether this container has extensions.
|
||||
*
|
||||
* @return True if the container has extensions, false otherwise.
|
||||
*/
|
||||
bool hasExtensions() const;
|
||||
|
||||
/**
|
||||
* @brief Get the extension of the given type.
|
||||
*
|
||||
* This version checks for derived types. It returns the first extension
|
||||
* of the given type or the first extension that is derived from the type
|
||||
* if @p derived is true. If @p no_except is true, it returns nullptr,
|
||||
*otherwise it throws an exception if no extension of the given type is
|
||||
* found.
|
||||
*
|
||||
* @param[in] type The type of the extension to get.
|
||||
* @param[in] derived (Optionally) Whether to check for derived types, true by default.
|
||||
* @param[in] no_except (Optionally) Whether to throw an exception if no extension is found,
|
||||
* false by default.
|
||||
*
|
||||
* @return The extension of the given type or `nullptr` if not found.
|
||||
* @throws Base::TypeError if no extension of the given type is found and
|
||||
* @p no_except is false.
|
||||
*/
|
||||
App::Extension* getExtension(Base::Type, bool derived = true, bool no_except = false) const;
|
||||
// this version does not check derived classes
|
||||
|
||||
/**
|
||||
* @brief Get the extension with the given name.
|
||||
*
|
||||
* This version does not check for derived types.
|
||||
*
|
||||
* @param[in] name The name of the extension to get.
|
||||
*
|
||||
* @return The extension with the given name or `nullptr` if not found.
|
||||
*/
|
||||
App::Extension* getExtension(const std::string& name) const;
|
||||
// this version checks for derived types and doesn't throw
|
||||
|
||||
/**
|
||||
* @brief Get the extension of the given type.
|
||||
*
|
||||
* This version checks for derived types. It returns the first extension
|
||||
* of the given type or the first extension that is derived from the type.
|
||||
* It doesn't throw an exception but returns `nullptr` if not found.
|
||||
*
|
||||
* @tparam ExtensionT The type of the extension to get.
|
||||
* @return The extension of the given type or `nullptr` if not found.
|
||||
*/
|
||||
template<typename ExtensionT>
|
||||
ExtensionT* getExtension() const
|
||||
{
|
||||
@@ -141,7 +247,24 @@ public:
|
||||
getExtension(ExtensionT::getExtensionClassTypeId(), true, true));
|
||||
}
|
||||
|
||||
// returns first of type (or derived from) and throws otherwise
|
||||
/**
|
||||
* @brief Get the extension with the given type.
|
||||
*
|
||||
* This version checks for derived types if @p derived is true. It returns the first
|
||||
* extension of the given type or the first extension that is derived from the type
|
||||
* if @p derived is true. If not found, it returns `nullptr` if @p no_except is true,
|
||||
* otherwise it throws an exception.
|
||||
*
|
||||
* @tparam ExtensionT The type of the extension to get.
|
||||
*
|
||||
* @param[in] no_except (Optionally) Whether to throw an exception if no extension is found,
|
||||
* false by default.
|
||||
* @param[in] derived (Optionally) Whether to check for derived types, true by default.
|
||||
*
|
||||
* @return The extension of the given type or `nullptr` if not found if @p no_except is true.
|
||||
* @throws Base::TypeError if no extension of the given type is found and
|
||||
* @p no_except is false.
|
||||
*/
|
||||
template<typename ExtensionT>
|
||||
ExtensionT* getExtensionByType(bool no_except = false, bool derived = true) const
|
||||
{
|
||||
@@ -149,8 +272,20 @@ public:
|
||||
getExtension(ExtensionT::getExtensionClassTypeId(), derived, no_except));
|
||||
}
|
||||
|
||||
// get all extensions which have the given base class
|
||||
/**
|
||||
* @brief Get all extensions with the given type as base class.
|
||||
*
|
||||
* @param[in] type The type of the extension to get.
|
||||
* @return A vector of extensions of the given type.
|
||||
*/
|
||||
std::vector<Extension*> getExtensionsDerivedFrom(Base::Type type) const;
|
||||
|
||||
/**
|
||||
* @brief Get all extensions with the given type as base class.
|
||||
*
|
||||
* @tparam ExtensionT The type of the extension to get.
|
||||
* @return A vector of extensions of the given type.
|
||||
*/
|
||||
template<typename ExtensionT>
|
||||
std::vector<ExtensionT*> getExtensionsDerivedFromType() const
|
||||
{
|
||||
@@ -163,78 +298,138 @@ public:
|
||||
return typevec;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get the begin iterator for the extensions.
|
||||
*
|
||||
* @return The begin iterator for the extensions.
|
||||
*/
|
||||
ExtensionIterator extensionBegin()
|
||||
{
|
||||
return _extensions.begin();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get the end iterator for the extensions.
|
||||
*
|
||||
* @return The end iterator for the extensions.
|
||||
*/
|
||||
ExtensionIterator extensionEnd()
|
||||
{
|
||||
return _extensions.end();
|
||||
}
|
||||
|
||||
|
||||
/** @name Access properties */
|
||||
//@{
|
||||
/// find a property by its name
|
||||
/** @name Access properties
|
||||
*
|
||||
* @{
|
||||
*/
|
||||
|
||||
Property* getPropertyByName(const char* name) const override;
|
||||
/// find a property by its name, dynamic cased to specified type
|
||||
|
||||
/**
|
||||
* @brief Find a property by its name and cast it to the specified type.
|
||||
*
|
||||
* This method finds a property by its name and casts it to the specified
|
||||
* type with a `freecad_cast`.
|
||||
*
|
||||
* @tparam T The property type to which the property is cast.
|
||||
* @param[in] name The name of the property to find.
|
||||
* @return The property if found cast to the specified type, or `nullptr` if not found.
|
||||
*/
|
||||
template<typename T>
|
||||
T* getPropertyByName(const char* name) const {
|
||||
return freecad_cast<T*>(this->getPropertyByName(name));
|
||||
}
|
||||
/// get the name of a property
|
||||
|
||||
const char* getPropertyName(const Property* prop) const override;
|
||||
/// get all properties of the class (including properties of the parent)
|
||||
|
||||
void getPropertyMap(std::map<std::string, Property*>& Map) const override;
|
||||
/// See PropertyContainer::visitProperties for semantics
|
||||
|
||||
void visitProperties(const std::function<void(Property*)>& visitor) const override;
|
||||
/// get all properties of the class (including properties of the parent)
|
||||
|
||||
void getPropertyList(std::vector<Property*>& List) const override;
|
||||
|
||||
/// get the Type of a Property
|
||||
short getPropertyType(const Property* prop) const override;
|
||||
/// get the Type of a named Property
|
||||
|
||||
short getPropertyType(const char* name) const override;
|
||||
/// get the Group of a Property
|
||||
|
||||
const char* getPropertyGroup(const Property* prop) const override;
|
||||
/// get the Group of a named Property
|
||||
|
||||
const char* getPropertyGroup(const char* name) const override;
|
||||
/// get the Group of a Property
|
||||
|
||||
const char* getPropertyDocumentation(const Property* prop) const override;
|
||||
/// get the Group of a named Property
|
||||
|
||||
const char* getPropertyDocumentation(const char* name) const override;
|
||||
//@}
|
||||
///@}
|
||||
|
||||
void onChanged(const Property*) override;
|
||||
|
||||
void Save(Base::Writer& writer) const override;
|
||||
|
||||
void Restore(Base::XMLReader& reader) override;
|
||||
|
||||
// those methods save/restore the dynamic extensions without handling properties, which is
|
||||
// something done by the default Save/Restore methods.
|
||||
/**
|
||||
* @brief Save the extensions to the given writer.
|
||||
*
|
||||
* This method saves the dynamic extensions to the given writer without
|
||||
* handling properties, which is something done by the default Save/Restore
|
||||
* methods.
|
||||
*
|
||||
* @param[in,out] writer The writer to save the extensions to.
|
||||
*/
|
||||
void saveExtensions(Base::Writer& writer) const;
|
||||
|
||||
/**
|
||||
* @brief Restore the extensions from the given reader.
|
||||
*
|
||||
* This method restores the dynamic extensions from the given reader
|
||||
* without handling properties, which is something done by the default
|
||||
* Save/Restore methods.
|
||||
*
|
||||
* @param[in,out] reader The reader to restore the extensions from.
|
||||
*/
|
||||
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.
|
||||
/**
|
||||
* @brief Handle a changed property name during restore.
|
||||
*
|
||||
* Extensions get their extensionHandleChangedPropertyName() called.
|
||||
* This method 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.
|
||||
*
|
||||
* If no extension handles the request, then the containers handleChangedPropertyName() is
|
||||
* Extensions get their Extension::extensionHandleChangedPropertyName()
|
||||
* called.
|
||||
*
|
||||
* If no extension handles the request, then
|
||||
* PropertyContainer::handleChangedPropertyName() is called.
|
||||
*
|
||||
* @param[in,out] reader The reader to restore the extensions from.
|
||||
* @param[in] TypeName The name of the type of the property to handle.
|
||||
* @param[in] PropName The name of the property to handle.
|
||||
*/
|
||||
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.
|
||||
|
||||
/**
|
||||
* @brief Handle a changed property type during restore.
|
||||
*
|
||||
* Extensions get their extensionHandleChangedPropertyType() called.
|
||||
* This method 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.
|
||||
*
|
||||
* If no extension handles the request, then the containers handleChangedPropertyType() is
|
||||
* called.
|
||||
* Extensions get their Extension::extensionHandleChangedPropertyType() called.
|
||||
*
|
||||
* If no extension handles the request, then
|
||||
* PropertyContainer::handleChangedPropertyType() is called.
|
||||
*
|
||||
* @param[in,out] reader The reader to restore the extensions from.
|
||||
* @param[in] TypeName The name of the type of the property to handle.
|
||||
* @param[in] prop The property that needs to be restored. Its type differs from `TypeName`.
|
||||
*/
|
||||
void handleChangedPropertyType(Base::XMLReader& reader,
|
||||
const char* TypeName,
|
||||
|
||||
Reference in New Issue
Block a user