Files
create/src/App/Property.h
Roy-043 bcd7373068 Solve merge conflict
OK, there was indeed a merge conflict due to reformatting.
2024-12-02 11:35:28 -05:00

663 lines
23 KiB
C++

/***************************************************************************
* Copyright (c) 2002 Jürgen Riegel <juergen.riegel@web.de> *
* *
* 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_PROPERTY_H
#define APP_PROPERTY_H
#include <Base/Exception.h>
#include <Base/Persistence.h>
#include <boost/any.hpp>
#include <boost/signals2.hpp>
#include <bitset>
#include <string>
#include <FCGlobal.h>
#include "ElementNamingUtils.h"
namespace Py
{
class Object;
}
namespace App
{
class PropertyContainer;
class ObjectIdentifier;
/** Base class of all properties
* This is the father of all properties. Properties are objects which are used
* in the document tree to parametrize e.g. features and their graphical output.
* They are also used to gain access from the scripting facility.
* /par
* This abstract base class defines all methods shared by all
* possible properties. It is also possible to define user properties
* and use them in the framework...
*/
class AppExport Property: public Base::Persistence
{
TYPESYSTEM_HEADER_WITH_OVERRIDE();
public:
enum Status
{
Touched = 0, // touched property
Immutable = 1, // can't modify property
ReadOnly = 2, // for property editor
Hidden = 3, // for property editor
Transient = 4, // for property container save
MaterialEdit = 5, // to turn ON PropertyMaterial edit
NoMaterialListEdit = 6, // to turn OFF PropertyMaterialList edit
Output = 7, // same effect as Prop_Output
LockDynamic = 8, // prevent being removed from dynamic property
NoModify = 9, // prevent causing Gui::Document::setModified()
PartialTrigger = 10, // allow change in partial doc
NoRecompute = 11, // don't touch owner for recompute on property change
Single = 12, // for save/load of floating point numbers
Ordered = 13, // for PropertyLists whether the order of the elements is
// relevant for the container using it
EvalOnRestore = 14, // In case of expression binding, evaluate the
// expression on restore and touch the object on value change.
Busy = 15, // internal use to avoid recursive signaling
CopyOnChange =
16, // for Link to copy the linked object on change of the property with this flag
UserEdit = 17, // cause property editor to create button for user defined editing
// The following bits are corresponding to PropertyType set when the
// property added. These types are meant to be static, and cannot be
// changed in runtime. It is mirrored here to save the linear search
// required in PropertyContainer::getPropertyType()
//
PropStaticBegin = 21,
PropDynamic = 21, // indicating the property is dynamically added
PropNoPersist = 22, // corresponding to Prop_NoPersist
PropNoRecompute = 23, // corresponding to Prop_NoRecompute
PropReadOnly = 24, // corresponding to Prop_ReadOnly
PropTransient = 25, // corresponding to Prop_Transient
PropHidden = 26, // corresponding to Prop_Hidden
PropOutput = 27, // corresponding to Prop_Output
PropStaticEnd = 28,
User1 = 28, // user-defined status
User2 = 29, // user-defined status
User3 = 30, // user-defined status
User4 = 31 // user-defined status
};
Property();
~Property() override;
/// For safe deleting of a dynamic property
static void destroy(Property* p);
/** This method is used to get the size of objects
* It is not meant to have the exact size, it is more or less an estimation
* which runs fast! Is it two bytes or a GB?
* This method is defined in Base::Persistence
* @see Base::Persistence
*/
unsigned int getMemSize() const override
{
// you have to implement this method in all property classes!
return sizeof(father) + sizeof(StatusBits);
}
/** Get the name of this property in the belonging container
* With \ref hasName() it can be checked beforehand if a valid name is set.
* @note If no name is set this function returns an empty string, i.e. "".
*/
const char* getName() const;
/** Check if the property has a name set.
* If no name is set then \ref getName() will return an empty string
*/
bool hasName() const;
/** Check if the passed name is valid.
* If \a name is null or an empty string it's considered invalid,
* and valid otherwise.
*/
static bool isValidName(const char* name);
std::string getFullName() const;
/// Get the class name of the associated property editor item
virtual const char* getEditorName() const
{
return "";
}
/// Get the type of the property in the container
short getType() const;
/// Get the group of this property
const char* getGroup() const;
/// Get the documentation of this property
const char* getDocumentation() const;
/// Is called by the framework to set the father (container)
void setContainer(PropertyContainer* Father);
/// Get a pointer to the PropertyContainer derived class the property belongs to
PropertyContainer* getContainer() const
{
return father;
}
/// Set value of property
virtual void setPathValue(const App::ObjectIdentifier& path, const boost::any& value);
/// Get value of property
virtual const boost::any getPathValue(const App::ObjectIdentifier& path) const;
/// Get Python value of property
virtual bool getPyPathValue(const App::ObjectIdentifier&, Py::Object&) const
{
return false;
}
/// Convert p to a canonical representation of it
virtual App::ObjectIdentifier canonicalPath(const App::ObjectIdentifier& p) const;
/// Get valid paths for this property; used by auto completer
virtual void getPaths(std::vector<App::ObjectIdentifier>& paths) const;
/** Called at the beginning of Document::afterRestore()
*
* This function is called without dependency sorting, because some
* types of link property can only reconstructs the linking information
* inside this function.
*
* One example use case of this function is PropertyLinkSub that uses
* afterRestore() to parse and restore subname references, which may
* contain sub-object reference from external document, and there will be
* special mapping required during object import.
*
* Another example is PropertyExpressionEngine which only parse the
* restored expression in afterRestore(). The reason, in addition to
* subname mapping like PropertyLinkSub, is that it can handle document
* name adjustment as well. It internally relies on PropertyXLink to store
* the external document path for external linking. When the external
* document is restored, its internal name may change due to name conflict
* with existing documents. PropertyExpressionEngine can now auto adjust
* external references without any problem.
*/
virtual void afterRestore()
{}
/** Called before calling DocumentObject::onDocumentRestored()
*
* This function is called after finished calling Property::afterRestore()
* of all properties of objects. By then, the object dependency information
* is assumed ready. So, unlike Property::afterRestore(), this function is
* called on objects with dependency order.
*/
virtual void onContainerRestored()
{}
/** Property status handling
*/
//@{
/// Set the property touched
void touch();
/// Test if this property is touched
inline bool isTouched() const
{
return StatusBits.test(Touched);
}
/// Reset this property touched
inline void purgeTouched()
{
StatusBits.reset(Touched);
}
/// return the status bits
inline unsigned long getStatus() const
{
return StatusBits.to_ulong();
}
inline bool testStatus(Status pos) const
{
return StatusBits.test(static_cast<size_t>(pos));
}
void setStatus(Status pos, bool on);
void setStatusValue(unsigned long status);
/// Sets property editable/grayed out in property editor
void setReadOnly(bool readOnly);
inline bool isReadOnly() const
{
return testStatus(App::Property::ReadOnly);
}
/// Sets precision of properties using floating point
/// numbers to single, the default is double.
void setSinglePrecision(bool single)
{
setStatus(App::Property::Single, single);
}
/// Gets precision of properties using floating point numbers
inline bool isSinglePrecision() const
{
return testStatus(App::Property::Single);
}
//@}
/// Returns a new copy of the property (mainly for Undo/Redo and transactions)
virtual Property* Copy() const = 0;
/// Paste the value from the property (mainly for Undo/Redo and transactions)
virtual void Paste(const Property& from) = 0;
/// Called when a child property has changed value
virtual void hasSetChildValue(Property&)
{}
/// Called before a child property changing value
virtual void aboutToSetChildValue(Property&)
{}
/// Compare if this property has the same content as the given one
virtual bool isSame(const Property& other) const;
/** Return a unique ID for the property
*
* The ID of a property is generated from a monotonically increasing
* internal counter. The intention of the ID is to be used as a key for
* mapping, instead of using the raw pointer. Because, it is possible for
* the runtime memory allocator to reuse just deleted memory, which will
* cause hard to debug problem if use pointer as key.
*/
int64_t getID() const
{
return _id;
}
virtual void beforeSave() const
{}
friend class PropertyContainer;
friend struct PropertyData;
friend class DynamicProperty;
protected:
/** Status bits of the property
* The first 8 bits are used for the base system the rest can be used in
* descendent classes to mark special statuses on the objects.
* The bits and their meaning are listed below:
* 0 - object is marked as 'touched'
* 1 - object is marked as 'immutable'
* 2 - object is marked as 'read-only' (for property editor)
* 3 - object is marked as 'hidden' (for property editor)
*/
std::bitset<32> StatusBits;
protected:
/// Gets called by all setValue() methods after the value has changed
virtual void hasSetValue();
/// Gets called by all setValue() methods before the value has changed
virtual void aboutToSetValue();
/// Verify a path for the current property
virtual void verifyPath(const App::ObjectIdentifier& p) const;
/// Return a file name suitable for saving this property
std::string getFileName(const char* postfix = 0, const char* prefix = 0) const;
public:
// forbidden
Property(const Property&) = delete;
Property& operator=(const Property&) = delete;
private:
// Sync status with Property_Type
void syncType(unsigned type);
private:
PropertyContainer* father {nullptr};
const char* myName {nullptr};
int64_t _id;
public:
boost::signals2::signal<void(const App::Property&)> signalChanged;
};
/** A template class that is used to inhibit multiple nested calls to aboutToSetValue/hasSetValue
* for properties.
*
* A template class that is used to inhibit multiple nested calls to
* aboutToSetValue/hasSetValue for properties, and only invoke it on change and
* last time it is needed. This is useful in cases where you want to change multiple
* values in a property "atomically", using possibly multiple primitive functions
* that normally would trigger aboutToSetValue/hasSetValue calls on their own.
*
* To use, inherit privately from the AtomicPropertyChangeInterface class, using
* your class name as the template argument. In all cases where you normally would
* call aboutToSetValue/hasSetValue before and after a change, create an
* AtomicPropertyChange object. The default constructor assume you are about to
* change the property and will call property's aboutToSetValue() if the
* property has not been marked as changed before by any other
* AtomicPropertyChange instances in current call stack. You can pass 'false'
* as the a second argument to the constructor, and manually call
* AtomicPropertyChange::aboutToChange() before actual change, this enables you
* to prevent unnecessary property copy for undo/redo where there is actual
* changes. AtomicPropertyChange will guaranetee calling hasSetValue() when the
* last instance in the current call stack is destroyed.
*
* One thing to take note is that, because C++ does not allow throwing
* exception in destructor, any exception thrown when calling property's
* hasSetValue() will be caught and swallowed. To allow exception propagation,
* you can manually call AtomicPropertyChange::tryInvoke(). If the condition is
* satisfied, it will call hasSetValue() that allows exception propagation.
*/
template<class P>
class AtomicPropertyChangeInterface
{
protected:
AtomicPropertyChangeInterface() = default;
public:
class AtomicPropertyChange
{
public:
/** Constructor
*
* @param prop: the property
* @param markChange: If true, marks the property as changed if it
* hasn't been marked before, and calls its
* aboutToSetValue().
*/
explicit AtomicPropertyChange(P& prop, bool markChange = true)
: mProp(prop)
{
mProp.signalCounter++;
if (markChange) {
aboutToChange();
}
}
/** Mark the property as changed
*
* It will mark the property as changed only if it has been marked
* before, and only then will it call the property's aboutToSetValue().
*/
void aboutToChange()
{
if (!mProp.hasChanged) {
mProp.hasChanged = true;
mProp.aboutToSetValue();
}
}
/** Destructor
*
* If the property is marked as changed, and this is the last instance
* of the class in current call stack, it will call property's
* hasSetValue()
*/
~AtomicPropertyChange()
{
// Signal counter == 1? meaning we are the last one. Invoke
// hasSetValue() before decrease counter to prevent recursive call
// triggered by another AtomicPropertyChange created inside
// hasSetValue(), as it has now been changed to a virtual function.
if (mProp.signalCounter == 1 && mProp.hasChanged) {
// Must make sure to not throw in a destructor
try {
mProp.hasSetValue();
}
catch (Base::Exception& e) {
e.ReportException();
}
catch (...) {
}
mProp.hasChanged = false;
}
if (mProp.signalCounter > 0) {
mProp.signalCounter--;
}
}
/** Check and invoke property's hasSetValue()
*
* Check if this is the last instance and the property has been marked
* as changed. If so, invoke property's hasSetValue().
*/
// Destructor cannot throw. So we provide this function to allow error
// propagation.
void tryInvoke()
{
if (mProp.signalCounter == 1 && mProp.hasChanged) {
mProp.hasSetValue();
if (mProp.signalCounter > 0) {
--mProp.signalCounter;
}
mProp.hasChanged = false;
}
}
private:
P& mProp; /**< Referenced to property we work on */
};
protected:
int signalCounter {0}; /**< Counter for invoking transaction start/stop */
bool hasChanged {false};
};
/** Helper class to construct list like properties
*
* This class is not derived from Property so that we can have more that one
* base class for list like properties, e.g. see PropertyList, and
* PropertyLinkListBase
*/
class AppExport PropertyListsBase
{
public:
virtual void setSize(int newSize) = 0;
virtual int getSize() const = 0;
const std::set<int>& getTouchList() const
{
return _touchList;
}
void clearTouchList()
{
_touchList.clear();
}
protected:
virtual void setPyValues(const std::vector<PyObject*>& vals, const std::vector<int>& indices)
{
(void)vals;
(void)indices;
throw Base::NotImplementedError("not implemented");
}
void _setPyObject(PyObject*);
protected:
std::set<int> _touchList;
};
/** Base class of all property lists.
* The PropertyLists class is the base class for properties which can contain
* multiple values, not only a single value.
* All property types which may contain more than one value inherits this class.
*/
class AppExport PropertyLists: public Property, public PropertyListsBase
{
TYPESYSTEM_HEADER_WITH_OVERRIDE();
public:
void setPyObject(PyObject* obj) override
{
_setPyObject(obj);
}
// if the order of the elements in the list relevant?
// if yes, certain operations, like restoring must make sure that the
// order is kept despite errors.
inline void setOrderRelevant(bool on)
{
this->setStatus(Status::Ordered, on);
}
inline bool isOrderRelevant() const
{
return this->testStatus(Status::Ordered);
}
};
/** Helper class to implement PropertyLists */
template<class T, class ListT = std::vector<T>, class ParentT = PropertyLists>
class PropertyListsT: public ParentT,
public AtomicPropertyChangeInterface<PropertyListsT<T, ListT, ParentT>>
{
public:
using const_reference = typename ListT::const_reference;
using list_type = ListT;
using parent_type = ParentT;
using atomic_change = typename AtomicPropertyChangeInterface<
PropertyListsT<T, ListT, ParentT>>::AtomicPropertyChange;
friend atomic_change;
virtual void setSize(int newSize, const_reference def)
{
_lValueList.resize(newSize, def);
}
void setSize(int newSize) override
{
_lValueList.resize(newSize);
}
int getSize() const override
{
return static_cast<int>(_lValueList.size());
}
void setValue(const_reference value)
{
ListT vals;
vals.resize(1, value);
setValues(vals);
}
virtual void setValues(const ListT& newValues = ListT())
{
atomic_change guard(*this);
this->_touchList.clear();
this->_lValueList = newValues;
guard.tryInvoke();
}
void setValue(const ListT& newValues = ListT())
{
setValues(newValues);
}
const ListT& getValues() const
{
return _lValueList;
}
// alias to getValues
const ListT& getValue() const
{
return getValues();
}
const_reference operator[](int idx) const
{
return _lValueList[idx];
}
bool isSame(const Property& other) const override
{
if (&other == this) {
return true;
}
return this->getTypeId() == other.getTypeId()
&& this->getValue() == static_cast<decltype(this)>(&other)->getValue();
}
void setPyObject(PyObject* value) override
{
try {
setValue(getPyValue(value));
return;
}
catch (...) {
}
parent_type::setPyObject(value);
}
virtual void set1Value(int index, const_reference value)
{
int size = getSize();
if (index < -1 || index > size) {
throw Base::RuntimeError("index out of bound");
}
atomic_change guard(*this);
if (index == -1 || index == size) {
index = size;
setSize(index + 1, value);
}
else {
_lValueList[index] = value;
}
this->_touchList.insert(index);
guard.tryInvoke();
}
protected:
void setPyValues(const std::vector<PyObject*>& vals, const std::vector<int>& indices) override
{
if (indices.empty()) {
ListT values {};
values.reserve(vals.size());
for (auto* valsContent : vals) {
values.push_back(getPyValue(valsContent));
}
setValues(std::move(values));
return;
}
assert(vals.size() == indices.size());
atomic_change guard(*this);
int i {0};
for (auto index : indices) {
set1Value(index, getPyValue(vals[i]));
i++;
}
guard.tryInvoke();
}
virtual T getPyValue(PyObject* item) const = 0;
protected:
ListT _lValueList;
};
} // namespace App
#endif // APP_PROPERTY_H