diff --git a/src/App/PropertyContainer.h b/src/App/PropertyContainer.h index 621a59bf2f..646834378f 100644 --- a/src/App/PropertyContainer.h +++ b/src/App/PropertyContainer.h @@ -73,13 +73,30 @@ enum PropertyType struct AppExport PropertyData { + + /// @brief Struct to hold the property specification. struct PropertySpec { + /// The name of the property. const char * Name; + /// The group name of the property. const char * Group; + /// The documentation string of the property. const char * Docu; - short Offset, Type; + /// The offset of the property in the container. + short Offset; + /// The type of the property. + short Type; + /** + * @brief Construct a PropertySpec. + * + * @param[in] name The name of the property. + * @param[in] group The group name of the property. + * @param[in] doc The documentation string of the property. + * @param[in] offset The offset of the property in the container. + * @param[in] type The type of the property. + */ 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) {} @@ -89,15 +106,37 @@ struct AppExport PropertyData //be able to return the offset to a property from the accepted containers. This allows you to use //one function implementation for multiple container types without losing all type safety by //accepting void* + /** + * @brief Struct that represents the base for offset calculation. + * + * This struct is used to calculate the offset of a property in a container. + * It can be constructed from either a PropertyContainer or an Extension. + */ struct OffsetBase { + /** + * @brief Construct an OffsetBase from a PropertyContainer. + * + * @param[in] container The PropertyContainer to construct from. + */ // Lint wants these marked explicit, but they are currently used implicitly in enough // places that I don't wnt to fix it. Instead we disable the Lint message. // NOLINTNEXTLINE(runtime/explicit) OffsetBase(const App::PropertyContainer* container) : m_container(container) {} + /** + * @brief Construct an OffsetBase from an Extension. + * + * @param[in] container The Extension to construct from. + */ // NOLINTNEXTLINE(runtime/explicit) OffsetBase(const App::Extension* container) : m_container(container) {} + /** + * @brief Get the offset to a property. + * + * @param[in] prop The property to get the offset to. + * @return The offset to the property relative to the base offset. + */ short int getOffsetTo(const App::Property* prop) const { auto *pt = (const char*)prop; auto *base = (const char *)m_container; @@ -105,6 +144,11 @@ struct AppExport PropertyData return -1; return (short) (pt-base); } + /** + * @brief Get the base offset in bytes. + * + * @return The base offset in bytes. + */ char* getOffset() const {return (char*) m_container;} private: @@ -112,11 +156,15 @@ struct AppExport PropertyData }; // clang-format off - // 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 + + /** + * @brief A multi index container for holding the property spec. + * + * The multi index has 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< @@ -133,15 +181,52 @@ struct AppExport PropertyData > propertyData; // clang-format on + /// Whether the property data is merged with the parent. mutable bool parentMerged = false; + /// The parent property data. const PropertyData* parentPropertyData; - void addProperty(OffsetBase offsetBase,const char* PropName, Property *Prop, const char* PropertyGroup= nullptr, PropertyType = Prop_None, const char* PropertyDocu= nullptr ); + /** + * @brief Add a property to the property data. + * + * @param[in] offsetBase The base offset to offset the property. + * @param[in] PropName The name of the property. + * @param[in] Prop The property to add. + * @param[in] PropertyGroup The group name of the property. + * @param[in] Type The type of the property. + * @param[in] PropertyDocu The documentation string of the property. + */ + void addProperty(OffsetBase offsetBase,const char* PropName, Property *Prop, const char* PropertyGroup= nullptr, PropertyType Type=Prop_None, const char* PropertyDocu=nullptr ); + /** + * @brief Find a property by its name. + * + * @param[in] offsetBase The base offset for the property. + * @param[in] PropName The name of the property to find. + * @return The property specification if found; `nullptr` otherwise. + */ const PropertySpec *findProperty(OffsetBase offsetBase,const char* PropName) const; + + /** + * @brief Find a property by its pointer. + * + * @param[in] offsetBase The base offset for the property. + * @param[in] prop The property to find. + * @return The property specification if found; `nullptr` otherwise. + */ const PropertySpec *findProperty(OffsetBase offsetBase,const Property* prop) const; + /** + * @name PropertyData accessors + * + * @details These methods are used to access the property data based on: + * - the offset base, + * - either: + * - a property pointer, or + * - a property name. + * @{ + */ 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; @@ -149,15 +234,70 @@ struct AppExport PropertyData 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; + /// @} + /** + * @brief Get a property by its name. + * + * @param[in] offsetBase The base offset for the property. + * @param[in] name The name of the property to find. + * @return The property if found; `nullptr` otherwise. + */ Property *getPropertyByName(OffsetBase offsetBase,const char* name) const; + + /** + * @brief Get a map of properties. + * + * @param[in] offsetBase The base offset for the property. + * @param[out] Map A map of property names to properties. + */ void getPropertyMap(OffsetBase offsetBase,std::map &Map) const; + + /** + * @brief Get a list of properties. + * + * @param[in] offsetBase The base offset for the property. + * @param[out] List A vector of properties. + */ void getPropertyList(OffsetBase offsetBase,std::vector &List) const; + + /** + * @brief Get a list of properties with their names. + * + * @param[in] offsetBase The base offset for the property. + * @param[out] List A vector of pairs, where each pair contains the name and + * the property. + */ void getPropertyNamedList(OffsetBase offsetBase, std::vector > &List) const; - /// See PropertyContainer::visitProperties for semantics + + /** + * @brief Visit each property in the PropertyData struct. + * + * @param[in] offsetBase The base offset for the property. + * @param[in] visitor The function to apply to each property. + * + * @see PropertyContainer::visitProperties() + */ void visitProperties(OffsetBase offsetBase, const std::function& visitor) const; + /** + * @brief Merge the PropertyData structs. + * + * Merge two PropertyData structs. If `other` is `nullptr`, merge with the + * parent PropertyData. + * + * @param[in] other (Optional) The other PropertyData to merge with; + */ void merge(PropertyData *other=nullptr) const; + + /** + * @brief Split the PropertyData structs. + * + * This method splits the PropertyData structs. It is used to + * restore the parent PropertyData after a merge. + * + * @param[in] other The other PropertyData to split with; this can be the parent PropertyData. + */ void split(PropertyData *other); }; @@ -251,7 +391,7 @@ public: * * The list may contain duplicates and aliases. * - * @param[out] List A vector of pairs, where each pair contains the name and + * @param[out] list A vector of pairs, where each pair contains the name and * the property. */ virtual void getPropertyNamedList(std::vector > &list) const; diff --git a/src/App/core-app.dox b/src/App/core-app.dox index 7350244663..d7a7536b14 100644 --- a/src/App/core-app.dox +++ b/src/App/core-app.dox @@ -62,17 +62,28 @@ * * @section propframe_intro 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 @ref - * App::PropertyContainer "PropertyContainer" class and can be used by all - * derived classes. In particular, there are two classes that inherit from - * @ref App::PropertyContainer "PropertyContainer" which are @ref App::Document + * The property framework introduces an intricate system to access properties + * of objects. It provides the ability to: + * 1. define properties in a class and access them by name, + * 2. add properties to a class at runtime and access them by name, and + * 3. access properties of a class by name without knowing the class type. + * + * The first two points are similar to what the dynamic reflection framework of + * C# or Java offer. The third point allows FreeCAD to have App::Property as a + * generic interface to access properties. This is similar to the way that + * Python allows to access properties of a class by name. + * + * This ability is introduced by the @ref App::PropertyContainer + * "PropertyContainer" class and can be used by all derived classes. In + * particular, there are two classes that inherit from @ref + * App::PropertyContainer "PropertyContainer" which are @ref App::Document * "Document" and @ref App::DocumentObject "DocumentObject". These two classes * serve different purposes but are both able to hold properties. @ref * App::PropertyContainer "PropertyContainer" contains the shared logic to do * so. * + * @section propframe_static_dynamic_props Static/Dynamic Properties + * * In C++, it is possible to define properties as part of the class. These can * be considered "static" properties but this term is typically not used within * FreeCAD. Properties created in a class cannot be removed from a @ref @@ -81,13 +92,65 @@ * However, it is also possible to add properties to a @ref * App::PropertyContainer "PropertyContainer" at runtime. These properties are * called "dynamic properties" and these properties can be freely added and - * removed by users. + * removed by users. In Python, all properties are dynamic properties. * - * In Python, all properties are dynamic properties. This makes it difficult - * to understand whether properties are part of a Python class and are - * necessary for the functioning of the class, or whether a user has added - * these properties. Therefore, it is possible to indicate that a property is - * "locked": + * @section propframe_mechanisms Mechanisms + * + * The macros `PROPERTY_HEADER` and `PROPERTY_SOURCE` (and variants) are used + * to define a private static PropertyData member in the class. This data + * structure contains a multi index that maps 1) the property name to the + * property specifications @ref App::PropertyData::PropertySpec "PropertySpec", + * allowing access of properties by name, and 2) maps the offset of a property + * with respect to its container, allowing access to property specifications. + * + * The static function @ref App::PropertyContainer::getPropertyDataPtr() + * "PropertyContainer::getPropertyDataPtr()" is used to access the class-wide + * static @ref App::PropertyData "PropertyData" structure shared by all + * instances of the class. The virtual function @ref + * App::PropertyContainer::getPropertyData() + * "PropertyContainer::getPropertyData()" allows access to the class-level + * static PropertyData based on the dynamic type of the object, even when + * downcasted. This allows for example a @ref App::PropertyInteger + * "PropertyInteger*" downcasted to @ref App::Property "Property*" to access + * all its properties. + * + * Since the @ref App::PropertyData "PropertyData" structure is static in the + * class and maintains static information of properties, such as the group and + * documentation, we need to be able to access a specific instance given a + * property name. This is achieved by looking up the @ref + * App::PropertyData::PropertySpec "PropertySpec" in the index based on the + * property name. The property specification stores an offset to the property + * address given an @ref App::PropertyData::OffsetBase "OffsetBase" which wraps + * the address of a @ref App::PropertyContainer "PropertyContainer". See @ref + * App::PropertyData::findProperty() "PropertyData::findProperty()" and @ref + * App::PropertyData::OffsetBase::getOffsetTo() "OffsetBase::getOffsetTo()". + * The offset of the property relative to the offset base gives us the address + * of the property in the instance. Note that different values for properties + * across property containers are stored in the @ref App::Property "Property" instances. + * + * The reverse is also needed: Given a property in a property container, it is + * possible to compute the offset relative to the base of the property + * container. The index in @ref App::PropertyData "PropertyData" allows us to + * acquire the property spec and provides us with the static data associated + * with the property. + * + * Dynamic properties are stored in their own @ref App::DynamicProperty + * "DynamicProperty" container. It can be added with the function @ref + * App::PropertyContainer::addDynamicProperty() + * "PropertyContainer::addDynamicProperty()" and removed with @ref + * App::PropertyContainer::removeDynamicProperty() + * "PropertyContainer::removeDynamicProperty()". The rest of the interface of + * dynamic properties is virtually the same. In general, in FreeCAD, it is + * difficult for users to know whether a property is dynamic or static and they + * typically do not need to be concerned with this distinction. + * + * @section Locking Dynamic Properties + * + * Since in Python all properties are dynamic properties, it is difficult to + * understand whether properties are part of a Python class and are necessary + * for the functioning of the class, or whether a user has added these + * properties. Therefore, it is possible to indicate that a property is + * "locked": * * @code * prop->setStatus(Property::LockDynamic, true); @@ -107,13 +170,12 @@ * * @section Examples * - * Here some little examples how to use it: + * Here some little examples how to use the property framework: * * @code - * // search in PropertyList - * Property *prop = _pcFeature->getPropertyByName(attr); - * if(prop) - * { + * // Acquire a property by name and return the value as a Python object. + * Property *prop = feature->getPropertyByName(nameProperty); + * if (prop) { * return prop->getPyObject(); * } * @endcode @@ -121,18 +183,19 @@ * or: * * @code + * // Restore properties from a reader. * void PropertyContainer::Restore(Base::Reader &reader) * { * reader.readElement("Properties"); * int Cnt = reader.getAttributeAsInteger("Count"); * - * for(int i=0 ;iRestore(reader); + * } * * reader.readEndElement("Property"); * }