Files
create/src/App/Property.h
Zheng, Lei 94c228973d App: API changes for document recompute/save/restore/import/export
This patch adds support of recomputation with external linked object,
as well as external document auto loading and partial loading.

Application:

* Modified new/openDocument()/signalNewDocument to choose whether to
  signal GUI for creating a view for the document. This makes it possible
  to suppress view creation when opening external documents.

* New API openDocuments() which does the actual job of loading the
  document together with any external dependencies. There are afew
  extra arguments to allow setting FileName property differently from
  the actual file path, which are required when auto loading
  dependencies during document recovery (with future patch to
  Gui::DocumentRecovery)

* openDocumentPrivate() is an internal helper for opening individual
  document.

* New signalStart/FinishOpenDocument to be signaled before and after
  opening a document. There may be multiple depending documents actually
  opened in between these two signals.

* New signalBeforeRecomputeDocument signaled before recompute a
  document.

* New API addPendingDocument() for use by external capable link
  properties' to queue up external documents.

* isRestoring/isClosingAll(), for convenience status reporting.

Document:

* signalFinishImport/RestoreObjects, new signal triggered after imported
  or restored all input objects

* signalBeforeRecompute, signaled before start recomputing this document

* Modified signalRecomputed with additional recomputed objects, this is
  to make it more efficient for Gui::TreeWidget to check recomputation
  result.

* signalSkipRecompute, signal to inform which objects are skipped
  during recomputation because of their owner document SkipRecompute
  setting.

* restore/save/read/writeObjects() modified to suport partial
  loading. See [here](https://git.io/fj6PY) for more information.

* afterRestore(), internal function called to finish restore. The
  function is separated from restore() because there is quite a few
  critical steps needed to fully restore a document with external
  linking. See [here](https://git.io/fj6P4) for more information.

* DocumentP::_RecomputeLog is modified to store more accurate object
  recomputation error, including those happened during restore/import.

* isExporting(), new API for checking if an object is exporting.
  External linking properties will use this function to decide how to
  export.

* copyObject(), modified to support external linking objects, and
  accepts multiple input objects.

* moveObject(), modified to support arbitary object moves. The original
  implementation may cause crash if undo/redo is enabled. Furthermore,
  because the original information fakes the object's deletion to break
  its dependency, it does not work for objects that may auto delete their
  children when being deleted. The new implementation copy the object,
  and than paste it to the other document. It then deletes the input
  objects from the original document. In case of recursive move, it only
  deletes the depending object if it has an empty in list.

* importLinks(), new API to import any external object linked by the
  input objects into this document. It will auto correct all link
  references after importing.

* getDependencyList/_rebuildDependencyList(), these two APIs are unified
  and implemented by an internal function _buildDependencyList() with a
  new algorithm to handle external objects. The returned dependency list
  will now include objects from external documents. In case of cyclic
  dependencies, getDpendencyList() will report the actual objects
  involved in dependency loops.

* mustExecute(), new API to check if there are any object requires
  recomputation. This function will call _buildDependencyList() and
  check for external objects as well.

* addRecomputeObject(), new API for marking changes during document
  restore. It only marks the object but does not actually recompute
  them for performance reason. One use case is for geo feature to
  request for recomputation to generate geometry topological names.

* recompute(), support partial, external, and inverse dependency
  recomputation. Improve error handling during recomputation.
  See [here](https://git.io/fj6PO) for more information.

* _recomputeFeature(), suppoert user abort.

* getDependentDocuments/getInList(), new API to obtain an optional
  dependency sorted list of depending documents.

DocumentObject:

* Add various ObjectStatus flags

* isExporting/getExportName(), return a safe name for exporting, in the
  form of <ObjName>@<DocName>, which is guarrenteed to be unique.
  Various link property will save linked object using this name if the
  the linked object is exported together with the owner object, see
  [PropertyLinkBase::restoreLabelReference()](https://git.io/fj6XO)
  for more information.

* recomputeFeature(), add option to recompute this object together with
  all its dependent objects.

* canLoadPartial(), new API for [partial document loading](https://git.io/fj6PY).

MergeDocuments:

* Move object name mapping logic to various link properties. See

Base::Sequencer:

* Add new API checkAbort() for checking user abort.
2019-08-17 14:52:10 +02:00

543 lines
20 KiB
C++

/***************************************************************************
* Copyright (c) Jürgen Riegel (juergen.riegel@web.de) 2002 *
* *
* 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
// Std. configurations
#include <Base/Exception.h>
#include <Base/Persistence.h>
#ifndef BOOST_105400
#include <boost/any.hpp>
#else
#include <boost_any_1_55.hpp>
#endif
#include <string>
#include <bitset>
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();
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, // 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.
// 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();
virtual ~Property();
/** 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
*/
virtual unsigned int getMemSize (void) const {
// 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
const char* getName(void) const;
std::string getFullName() const;
/// Get the class name of the associated property editor item
virtual const char* getEditorName(void) const { return ""; }
/// Get the type of the property in the container
short getType(void) const;
/// Get the group of this property
const char* getGroup(void) const;
/// Get the documentation of this property
const char* getDocumentation(void) 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(void) 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 begining 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 extenal
* 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 properies 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(void) const {
return StatusBits.test(Touched);
}
/// Reset this property touched
inline void purgeTouched(void) {
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(void) 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 &) {}
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(void);
/// Gets called by all setValue() methods before the value has changed
virtual void aboutToSetValue(void);
/// Verify a path for the current property
virtual void verifyPath(const App::ObjectIdentifier & p) const;
private:
// forbidden
Property(const Property&);
Property& operator = (const Property&);
// Sync status with Property_Type
void syncType(unsigned type);
private:
PropertyContainer *father;
const char *myName;
};
/** 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 destroied.
*
* 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() : signalCounter(0), hasChanged(false) { }
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().
*/
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 proeprty 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 */
};
private:
int signalCounter; /**< Counter for invoking transaction start/stop */
bool hasChanged;
};
/** 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(void) 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();
public:
virtual 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:
typedef typename ListT::const_reference const_reference;
typedef ListT list_type;
typedef ParentT parent_type;
typedef typename AtomicPropertyChangeInterface<
PropertyListsT<T,ListT,ParentT> >::AtomicPropertyChange atomic_change;
friend atomic_change;
virtual void setSize(int newSize, const_reference def) {
_lValueList.resize(newSize,def);
}
virtual void setSize(int newSize) override {
_lValueList.resize(newSize);
}
virtual int getSize(void) 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(void) const{return _lValueList;}
// alias to getValues
const ListT &getValue(void) const{return getValues();}
const_reference operator[] (int idx) const {return _lValueList[idx];}
virtual 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
{
ListT values;
// old version boost::dynamic_bitset don't have reserve(). What a shame!
// values.reserve(vals.size());
for(auto item : vals)
values.push_back(getPyValue(item));
if(indices.empty())
setValues(values);
else {
atomic_change guard(*this);
assert(values.size()==indices.size());
for(int i=0,count=values.size();i<count;++i)
set1Value(indices[i],values[i]);
guard.tryInvoke();
}
}
virtual T getPyValue(PyObject *item) const = 0;
protected:
ListT _lValueList;
};
} // namespace App
#endif // APP_PROPERTY_H