diff --git a/src/App/Application.cpp b/src/App/Application.cpp index 871d0b1cb4..c5a69897a2 100644 --- a/src/App/Application.cpp +++ b/src/App/Application.cpp @@ -251,6 +251,7 @@ init_freecad_module(void) Application::Application(std::map &mConfig) : _mConfig(mConfig), _pActiveDoc(0), _objCount(-1) + , _activeTransactionID(0), _activeTransactionGuard(0), _activeTransactionTmpName(false) { //_hApp = new ApplicationOCC; mpcPramManager["System parameter"] = _pcSysParamMngr; @@ -648,6 +649,161 @@ void Application::setActiveDocument(const char *Name) } } +AutoTransaction::AutoTransaction(const char *name, bool tmpName) { + auto &app = GetApplication(); + if(name && app._activeTransactionGuard>=0) { + if(!app.getActiveTransaction() + || (!tmpName && app._activeTransactionTmpName)) + { + FC_LOG("auto transaction '" << name << "', " << tmpName); + tid = app.setActiveTransaction(name); + app._activeTransactionTmpName = tmpName; + } + } + // We use negative transaction guard to disable auto transaction from here + // and any stack below. This is to support user setting active transaction + // before having any existing AutoTransaction on stack, or 'persist' + // transaction that can out live AutoTransaction. + if(app._activeTransactionGuard<0) + --app._activeTransactionGuard; + else if(tid || app._activeTransactionGuard>0) + ++app._activeTransactionGuard; + else if(app.getActiveTransaction()) { + FC_LOG("auto transaction disabled because of '" << app._activeTransactionName << "'"); + --app._activeTransactionGuard; + } else + ++app._activeTransactionGuard; + FC_TRACE("construct auto Transaction " << app._activeTransactionGuard); +} + +AutoTransaction::~AutoTransaction() { + auto &app = GetApplication(); + FC_TRACE("before destruct auto Transaction " << app._activeTransactionGuard); + if(app._activeTransactionGuard<0) + ++app._activeTransactionGuard; + else if(!app._activeTransactionGuard) { +#ifdef FC_DEBUG + FC_ERR("Transaction guard error"); +#endif + } else if(--app._activeTransactionGuard == 0) { + try { + // We don't call close() here, because close() only closes + // transaction that we opened during construction time. However, + // when _activeTransactionGuard reaches zero here, we are supposed + // to close any transaction opened. + app.closeActiveTransaction(); + } catch(Base::Exception &e) { + e.ReportException(); + } catch(...) + {} + } + FC_TRACE("destruct auto Transaction " << app._activeTransactionGuard); +} + +void AutoTransaction::close(bool abort) { + if(tid || abort) { + GetApplication().closeActiveTransaction(abort,abort?0:tid); + tid = 0; + } +} + +void AutoTransaction::setEnable(bool enable) { + auto &app = GetApplication(); + if(!app._activeTransactionGuard) + return; + if((enable && app._activeTransactionGuard>0) + || (!enable && app._activeTransactionGuard<0)) + return; + app._activeTransactionGuard = -app._activeTransactionGuard; + FC_TRACE("toggle auto Transaction " << app._activeTransactionGuard); + if(!enable && app._activeTransactionTmpName) { + bool close = true; + for(auto &v : app.DocMap) { + if(v.second->hasPendingTransaction()) { + close = false; + break; + } + } + if(close) + app.closeActiveTransaction(); + } +} + +int Application::setActiveTransaction(const char *name, bool persist) { + if(!name || !name[0]) + name = "Command"; + + if(_activeTransactionGuard>0 && getActiveTransaction()) { + if(_activeTransactionTmpName) { + FC_LOG("transaction rename to '" << name << "'"); + for(auto &v : DocMap) + v.second->renameTransaction(name,_activeTransactionID); + }else + return 0; + }else{ + FC_LOG("set active transaction '" << name << "'"); + _activeTransactionID = 0; + for(auto &v : DocMap) + v.second->_commitTransaction(); + _activeTransactionID = Transaction::getNewID(); + } + _activeTransactionTmpName = false; + _activeTransactionName = name; + if(persist) + AutoTransaction::setEnable(false); + return _activeTransactionID; +} + +const char *Application::getActiveTransaction(int *id) const { + int tid = 0; + if(Transaction::getLastID() == _activeTransactionID) + tid = _activeTransactionID; + if(id) *id = tid; + return tid?_activeTransactionName.c_str():0; +} + +void Application::closeActiveTransaction(bool abort, int id) { + if(!id) id = _activeTransactionID; + if(!id) return; + + if(_activeTransactionGuard>0 && !abort) { + FC_LOG("ignore close transaction"); + return; + } + + FC_LOG("close transaction '" << _activeTransactionName << "' " << abort); + _activeTransactionID = 0; + + TransactionSignaller siganller(abort,false); + for(auto &v : DocMap) { + if(v.second->getTransactionID(true) != id) + continue; + if(abort) + v.second->_abortTransaction(); + else + v.second->_commitTransaction(); + } +} + +static int _TransSignalCount; +static bool _TransSignalled; +Application::TransactionSignaller::TransactionSignaller(bool abort, bool signal) + :abort(abort) +{ + ++_TransSignalCount; + if(signal && !_TransSignalled) { + _TransSignalled = true; + GetApplication().signalBeforeCloseTransaction(abort); + } +} + +Application::TransactionSignaller::~TransactionSignaller() { + if(--_TransSignalCount == 0 && _TransSignalled) { + _TransSignalled = false; + GetApplication().signalCloseTransaction(abort); + } +} + const char* Application::getHomePath(void) const { return _mConfig["AppHomePath"].c_str(); diff --git a/src/App/Application.h b/src/App/Application.h index b38e0daa7e..255fb0c541 100644 --- a/src/App/Application.h +++ b/src/App/Application.h @@ -48,6 +48,7 @@ class Document; class DocumentObject; class ApplicationObserver; class Property; +class AutoTransaction; enum GetLinkOption { /// Get all links (both directly and in directly) linked to the given object @@ -105,6 +106,37 @@ public: void setActiveDocument(const char *Name); /// close all documents (without saving) void closeAllDocuments(void); + /** @name Application-wide trandaction setting */ + //@{ + /** Setup a pending application-wide active transaction + * + * @param name: new transaction name + * @param persist: by default, if the calling code is inside any invokation + * of a command, it will be auto closed once all command within the current + * stack exists. To disable auto closing, set persist=true + * + * @return The new transaction ID. + * + * Call this function to setup an application-wide transaction. All current + * pending transactions of opening documents will be commited first. + * However, no new transaction is created by this call. Any subsequent + * changes in any current opening document will auto create a transaction + * with the given name and ID. If more than one document is changed, the + * transactions will share the same ID, and will be undo/redo together. + */ + int setActiveTransaction(const char *name, bool persist=false); + /// Return the current active transaction name and ID + const char *getActiveTransaction(int *tid=0) const; + /** Commit/abort current active transactions + * + * @param abort: whether to abort or commit the transactions + * + * Bsides calling this function directly, it will be called by automatically + * if 1) any new transaction is created with a different ID, or 2) any + * transaction with the current active transaction ID is either commited or + * aborted + */ + void closeActiveTransaction(bool abort=false, int id=0); //@} /** @name Signals of the Application */ @@ -133,8 +165,16 @@ public: boost::signals2::signal signalFinishSaveDocument; /// signal on undo in document boost::signals2::signal signalUndoDocument; + /// signal on application wide undo + boost::signals2::signal signalUndo; /// signal on redo in document boost::signals2::signal signalRedoDocument; + /// signal on application wide redo + boost::signals2::signal signalRedo; + /// signal before close/abort active transaction + boost::signals2::signal signalBeforeCloseTransaction; + /// signal after close/abort active transaction + boost::signals2::signal signalCloseTransaction; /// signal on show hidden items boost::signals2::signal signalShowHidden; //@} @@ -345,6 +385,15 @@ protected: void slotChangePropertyEditor(const App::Document&, const App::Property &); //@} + /// Helper class for App::Document to signal on close/abort transaction + class AppExport TransactionSignaller { + public: + TransactionSignaller(bool abort,bool signal); + ~TransactionSignaller(); + private: + bool abort; + }; + private: /// Constructor Application(std::map &mConfig); @@ -397,6 +446,9 @@ private: static PyObject *sCheckLinkDepth (PyObject *self,PyObject *args); static PyObject *sGetLinksTo (PyObject *self,PyObject *args); + static PyObject *sSetActiveTransaction (PyObject *self,PyObject *args); + static PyObject *sGetActiveTransaction (PyObject *self,PyObject *args); + static PyObject *sCloseActiveTransaction(PyObject *self,PyObject *args); static PyMethodDef Methods[]; friend class ApplicationObserver; @@ -448,6 +500,13 @@ private: // for estimate max link depth int _objCount; + friend class AutoTransaction; + + std::string _activeTransactionName; + int _activeTransactionID; + int _activeTransactionGuard; + bool _activeTransactionTmpName; + static Base::ConsoleObserverStd *_pConsoleObserverStd; static Base::ConsoleObserverFile *_pConsoleObserverFile; }; @@ -457,6 +516,60 @@ inline App::Application &GetApplication(void){ return *App::Application::_pcSingleton; } +/// Helper class to manager transaction (i.e. undo/redo) +class AppExport AutoTransaction { +private: + /// Private new operator to prevent heap allocation + void* operator new(size_t size); + +public: + /** Construtor + * + * @param name: optional new transaction name on construction + * @param tmpName: if true and a new transaction is setup, the name given is + * considered as temperary, and subsequent construction of this class (or + * calling Application::setActiveTransaction()) can override the transaction + * name. + * + * The constructor increments an internal counter + * (Application::_activeTransactionGuard). The counter prevents any new + * active transaction being setup. It also prevents close (i.e. commits) the + * current active transaction until it reaches zero. It does not have any + * effect on aborting transaction, though. + */ + AutoTransaction(const char *name=0, bool tmpName=false); + + /** Destructor + * + * This destructor decrease an internal counter + * (Application::_activeTransactionGuard), and will commit any current + * active transaction when the counter reaches zero. + */ + ~AutoTransaction(); + + /** Close or abort the transaction + * + * This function can be used to explicitly close (i.e. commit) the + * transaction, if the current transaction ID matches the one created inside + * the constructor. For aborting, it will abort any current transaction + */ + void close(bool abort=false); + + /** Enable/Disable any AutoTransaction instance in the current stack + * + * Once disabled, any empty temperary named transaction is closed. If there + * are non-empty or non-temperary named active transaction, it will not be + * auto closed. + * + * This function may be used in, for example, Gui::Document::setEdit() to + * allow a transaction live past any command scope. + */ + static void setEnable(bool enable); + +private: + int tid = 0; +}; + } // namespace App diff --git a/src/App/ApplicationPy.cpp b/src/App/ApplicationPy.cpp index 5fcaa6a2e1..89cef1be04 100644 --- a/src/App/ApplicationPy.cpp +++ b/src/App/ApplicationPy.cpp @@ -151,6 +151,19 @@ PyMethodDef Application::Methods[] = { "getLinksTo(obj,options=0,maxCount=0) -- return the objects linked to 'obj'\n\n" "options: 1: recursive, 2: check link array. Options can combine.\n" "maxCount: to limit the number of links returned\n"}, + {"setActiveTransaction", (PyCFunction) Application::sSetActiveTransaction, METH_VARARGS, + "setActiveTransaction(name, persist=False) -- setup active transaction with the given name\n\n" + "name: the transaction name\n" + "persist(False): by default, if the calling code is inside any invokation of a command, it\n" + " will be auto closed once all command within the current stack exists. To\n" + " disable auto closing, set persist=True\n" + "Returns the transaction ID for the active transaction. An application-wide\n" + "active transaction causes any document changes to open a transaction with\n" + "the given name and ID."}, + {"getActiveTransaction", (PyCFunction) Application::sGetActiveTransaction, METH_VARARGS, + "getActiveTransaction() -> (name,id) return the current active transaction name and ID"}, + {"closeActiveTransaction", (PyCFunction) Application::sCloseActiveTransaction, METH_VARARGS, + "closeActiveTransaction(abort=False) -- commit or abort current active transaction"}, {NULL, NULL, 0, NULL} /* Sentinel */ }; @@ -806,3 +819,46 @@ PyObject *Application::sGetLinksTo(PyObject * /*self*/, PyObject *args) }PY_CATCH; } +PyObject *Application::sSetActiveTransaction(PyObject * /*self*/, PyObject *args) +{ + char *name; + PyObject *persist = Py_False; + if (!PyArg_ParseTuple(args, "s|O", &name,&persist)) + return 0; + + PY_TRY { + Py::Int ret(GetApplication().setActiveTransaction(name,PyObject_IsTrue(persist))); + return Py::new_reference_to(ret); + }PY_CATCH; +} + +PyObject *Application::sGetActiveTransaction(PyObject * /*self*/, PyObject *args) +{ + if (!PyArg_ParseTuple(args, "")) + return 0; + + PY_TRY { + int id = 0; + const char *name = GetApplication().getActiveTransaction(&id); + if(!name || id<=0) + Py_Return; + Py::Tuple ret(2); + ret.setItem(0,Py::String(name)); + ret.setItem(1,Py::Int(id)); + return Py::new_reference_to(ret); + }PY_CATCH; +} + +PyObject *Application::sCloseActiveTransaction(PyObject * /*self*/, PyObject *args) +{ + PyObject *abort = Py_False; + int id = 0; + if (!PyArg_ParseTuple(args, "|Oi", &abort,&id)) + return 0; + + PY_TRY { + GetApplication().closeActiveTransaction(PyObject_IsTrue(abort),id); + Py_Return; + } PY_CATCH; +} + diff --git a/src/App/Document.cpp b/src/App/Document.cpp index 134201f731..5b5d07b01a 100644 --- a/src/App/Document.cpp +++ b/src/App/Document.cpp @@ -121,6 +121,10 @@ recompute path. Also, it enables more complicated dependencies beyond trees. #include "GeoFeatureGroupExtension.h" #include "Origin.h" #include "OriginGroupExtension.h" +#include "Link.h" +#include "GeoFeature.h" + +FC_LOG_LEVEL_INIT("App", true, true, true); using Base::Console; using Base::streq; @@ -152,6 +156,7 @@ typedef std::vector Path; namespace App { +static bool _IsRelabeling; // Pimpl class struct DocumentP { @@ -881,26 +886,38 @@ bool Document::checkOnCycle(void) return false; } -bool Document::undo(void) +bool Document::undo(int id) { if (d->iUndoMode) { + if(id) { + auto it = mUndoMap.find(id); + if(it == mUndoMap.end()) + return false; + if(it->second != d->activeUndoTransaction) { + while(mUndoTransactions.size() && mUndoTransactions.back()!=it->second) + undo(0); + } + } + if (d->activeUndoTransaction) - commitTransaction(); - else if (mUndoTransactions.empty()) + _commitTransaction(true); + if (mUndoTransactions.empty()) return false; // redo - d->activeUndoTransaction = new Transaction(); + d->activeUndoTransaction = new Transaction(mUndoTransactions.back()->getID()); d->activeUndoTransaction->Name = mUndoTransactions.back()->Name; - d->undoing = true; + + Base::FlagToggler flag(d->undoing); // applying the undo mUndoTransactions.back()->apply(*this,false); - d->undoing = false; // save the redo + mRedoMap[d->activeUndoTransaction->getID()] = d->activeUndoTransaction; mRedoTransactions.push_back(d->activeUndoTransaction); d->activeUndoTransaction = 0; + mUndoMap.erase(mUndoTransactions.back()->getID()); delete mUndoTransactions.back(); mUndoTransactions.pop_back(); @@ -911,25 +928,35 @@ bool Document::undo(void) return false; } -bool Document::redo(void) +bool Document::redo(int id) { if (d->iUndoMode) { + if(id) { + auto it = mRedoMap.find(id); + if(it == mRedoMap.end()) + return false; + while(mRedoTransactions.size() && mRedoTransactions.back()!=it->second) + redo(0); + } + if (d->activeUndoTransaction) - commitTransaction(); + _commitTransaction(true); assert(mRedoTransactions.size()!=0); // undo - d->activeUndoTransaction = new Transaction(); + d->activeUndoTransaction = new Transaction(mRedoTransactions.back()->getID()); d->activeUndoTransaction->Name = mRedoTransactions.back()->Name; // do the redo - d->undoing = true; + Base::FlagToggler flag(d->undoing); mRedoTransactions.back()->apply(*this,true); - d->undoing = false; + + mUndoMap[d->activeUndoTransaction->getID()] = d->activeUndoTransaction; mUndoTransactions.push_back(d->activeUndoTransaction); d->activeUndoTransaction = 0; + mRedoMap.erase(mRedoTransactions.back()->getID()); delete mRedoTransactions.back(); mRedoTransactions.pop_back(); @@ -979,34 +1006,100 @@ std::vector Document::getAvailableRedoNames() const return vList; } -void Document::openTransaction(const char* name) +void Document::openTransaction(const char* name) { + if(isPerformingTransaction()) { + FC_WARN("Cannot open transaction while transacting"); + return; + } + + GetApplication().setActiveTransaction(name?name:""); +} + +int Document::_openTransaction(const char* name, int id) { + if(isPerformingTransaction()) { + FC_WARN("Cannot open transaction while transacting"); + return 0; + } + if (d->iUndoMode) { + if(id && mUndoMap.find(id)!=mUndoMap.end()) + throw Base::RuntimeError("invalid transaction id"); if (d->activeUndoTransaction) - commitTransaction(); + _commitTransaction(true); _clearRedos(); - d->activeUndoTransaction = new Transaction(); - if (name) - d->activeUndoTransaction->Name = name; - else - d->activeUndoTransaction->Name = ""; + d->activeUndoTransaction = new Transaction(id); + if (!name) + name = ""; + d->activeUndoTransaction->Name = name; + mUndoMap[d->activeUndoTransaction->getID()] = d->activeUndoTransaction; + id = d->activeUndoTransaction->getID(); - signalOpenTransaction(*this, d->activeUndoTransaction->Name); + signalOpenTransaction(*this, name); + + auto &app = GetApplication(); + auto activeDoc = app.getActiveDocument(); + if(activeDoc && + activeDoc!=this && + !activeDoc->hasPendingTransaction()) + { + std::string aname("-> "); + aname += d->activeUndoTransaction->Name; + FC_LOG("auto transaction " << getName() << " -> " << activeDoc->getName()); + activeDoc->_openTransaction(aname.c_str(),id); + } + return id; + } + return 0; +} + +void Document::renameTransaction(const char *name, int id) { + if(name && d->activeUndoTransaction && d->activeUndoTransaction->getID()==id) { + if(boost::starts_with(d->activeUndoTransaction->Name, "-> ")) + d->activeUndoTransaction->Name.resize(3); + else + d->activeUndoTransaction->Name.clear(); + d->activeUndoTransaction->Name += name; } } -void Document::_checkTransaction(DocumentObject* pcObject) +void Document::_checkTransaction(DocumentObject* pcDelObj, const Property *What, int line) { // if the undo is active but no transaction open, open one! - if (d->iUndoMode) { + if (d->iUndoMode && !isPerformingTransaction()) { if (!d->activeUndoTransaction) { + if(!testStatus(Restoring) || testStatus(Importing)) { + int tid=0; + const char *name = GetApplication().getActiveTransaction(&tid); + if(name && tid>0) { + bool ignore = false; + if(What) { + auto parent = What->getContainer(); + auto parentObj = Base::freecad_dynamic_cast(parent); + if(!parentObj || What->testStatus(Property::NoModify)) + ignore = true; + } + if(FC_LOG_INSTANCE.isEnabled(FC_LOGLEVEL_LOG)) { + if(What) + FC_LOG((ignore?"ignore":"auto") << " transaction (" + << line << ") '" << What->getFullName()); + else + FC_LOG((ignore?"ignore":"auto") <<" transaction (" + << line << ") '" << name << "' in " << getName()); + } + if(!ignore) + _openTransaction(name,tid); + return; + } + } + if(!pcDelObj) return; // When the object is going to be deleted we have to check if it has already been added to // the undo transactions std::list::iterator it; for (it = mUndoTransactions.begin(); it != mUndoTransactions.end(); ++it) { - if ((*it)->hasObject(pcObject)) { - openTransaction(); + if ((*it)->hasObject(pcDelObj)) { + _openTransaction("Delete"); break; } } @@ -1016,35 +1109,79 @@ void Document::_checkTransaction(DocumentObject* pcObject) void Document::_clearRedos() { + if(isPerformingTransaction()) { + FC_ERR("Cannot clear redo while transacting"); + return; + } + + mRedoMap.clear(); while (!mRedoTransactions.empty()) { delete mRedoTransactions.back(); mRedoTransactions.pop_back(); } } -void Document::commitTransaction() +void Document::commitTransaction() { + if(isPerformingTransaction()) { + FC_WARN("Cannot commit transaction while transacting"); + return; + } + + if (d->activeUndoTransaction) + GetApplication().closeActiveTransaction(false,d->activeUndoTransaction->getID()); +} + +void Document::_commitTransaction(bool notify) { + if(isPerformingTransaction()) { + FC_WARN("Cannot commit transaction while transacting"); + return; + } + if (d->activeUndoTransaction) { + Application::TransactionSignaller signaller(false,true); + int id = d->activeUndoTransaction->getID(); mUndoTransactions.push_back(d->activeUndoTransaction); d->activeUndoTransaction = 0; // check the stack for the limits if(mUndoTransactions.size() > d->UndoMaxStackSize){ + mUndoMap.erase(mUndoTransactions.front()->getID()); delete mUndoTransactions.front(); mUndoTransactions.pop_front(); } signalCommitTransaction(*this); + + if(notify) + GetApplication().closeActiveTransaction(false,id); } } -void Document::abortTransaction() +void Document::abortTransaction() { + if(isPerformingTransaction()) { + FC_WARN("Cannot abort transaction while transacting"); + return; + } + if (d->activeUndoTransaction) + GetApplication().closeActiveTransaction(true,d->activeUndoTransaction->getID()); +} + +void Document::_abortTransaction() { + if(isPerformingTransaction()) { + FC_WARN("Cannot abort transaction while transacting"); + return; + } + if (d->activeUndoTransaction) { - d->rollback = true; - // applying the so far made changes - d->activeUndoTransaction->apply(*this,false); - d->rollback = false; + Application::TransactionSignaller signaller(true,true); + { + Base::FlagToggler flag(d->rollback); + // applying the so far made changes + d->activeUndoTransaction->apply(*this,false); + } // destroy the undo + mUndoMap.erase(d->activeUndoTransaction->getID()); delete d->activeUndoTransaction; d->activeUndoTransaction = 0; signalAbortTransaction(*this); @@ -1059,6 +1196,26 @@ bool Document::hasPendingTransaction() const return false; } +int Document::getTransactionID(bool undo, unsigned pos) const { + if(undo) { + if(d->activeUndoTransaction) { + if(pos == 0) + return d->activeUndoTransaction->getID(); + --pos; + } + if(pos>=mUndoTransactions.size()) + return 0; + auto rit = mUndoTransactions.rbegin(); + for(;pos;++rit,--pos); + return (*rit)->getID(); + } + if(pos>=mRedoTransactions.size()) + return 0; + auto rit = mRedoTransactions.rbegin(); + for(;pos;++rit,--pos); + return (*rit)->getID(); +} + bool Document::isTransactionEmpty() const { if (d->activeUndoTransaction) { @@ -1070,8 +1227,15 @@ bool Document::isTransactionEmpty() const void Document::clearUndos() { + if(isPerformingTransaction()) { + FC_ERR("Cannot clear undos while transacting"); + return; + } + if (d->activeUndoTransaction) - commitTransaction(); + _commitTransaction(true); + + mUndoMap.clear(); // When cleaning up the undo stack we must delete the transactions from front // to back because a document object can appear in several transactions but @@ -1091,16 +1255,40 @@ void Document::clearUndos() _clearRedos(); } -int Document::getAvailableUndos() const +int Document::getAvailableUndos(int id) const { + if(id) { + auto it = mUndoMap.find(id); + if(it == mUndoMap.end()) + return 0; + int i = 0; + if(d->activeUndoTransaction) { + ++i; + if(d->activeUndoTransaction->getID()==id) + return i; + } + auto rit = mUndoTransactions.rbegin(); + for(;rit!=mUndoTransactions.rend()&&*rit!=it->second;++rit,++i); + assert(rit!=mUndoTransactions.rend()); + return i+1; + } if (d->activeUndoTransaction) return static_cast(mUndoTransactions.size() + 1); else return static_cast(mUndoTransactions.size()); } -int Document::getAvailableRedos() const +int Document::getAvailableRedos(int id) const { + if(id) { + auto it = mRedoMap.find(id); + if(it == mRedoMap.end()) + return 0; + int i = 0; + for(auto rit=mRedoTransactions.rbegin();*rit!=it->second;++rit,++i); + assert(i<(int)mRedoTransactions.size()); + return i+1; + } return static_cast(mRedoTransactions.size()); } @@ -1139,6 +1327,8 @@ unsigned int Document::getMaxUndoStackSize(void)const void Document::onBeforeChange(const Property* prop) { + if(prop == &Label) + oldLabel = Label.getValue(); signalBeforeChange(*this, *prop); } @@ -1148,6 +1338,7 @@ void Document::onChanged(const Property* prop) // the Name property is a label for display purposes if (prop == &Label) { + Base::FlagToggler<> flag(_IsRelabeling); App::GetApplication().signalRelabelDocument(*this); } else if(prop == &ShowHidden) { App::GetApplication().signalShowHidden(*this); @@ -1189,9 +1380,11 @@ void Document::onBeforeChangeProperty(const TransactionalObject *Who, const Prop { if(Who->isDerivedFrom(App::DocumentObject::getClassTypeId())) signalBeforeChangeObject(*static_cast(Who), *What); - - if (d->activeUndoTransaction && !d->rollback) - d->activeUndoTransaction->addObjectChange(Who,What); + if(!d->rollback && !_IsRelabeling) { + _checkTransaction(0,What,__LINE__); + if (d->activeUndoTransaction) + d->activeUndoTransaction->addObjectChange(Who,What); + } } void Document::onChangedProperty(const DocumentObject *Who, const Property *What) @@ -2736,6 +2929,7 @@ DocumentObject * Document::addObject(const char* sType, const char* pObjectName, // do no transactions if we do a rollback! if (!d->rollback) { // Undo stuff + _checkTransaction(0,0,__LINE__); if (d->activeUndoTransaction) d->activeUndoTransaction->addObjectDel(pcObject); } @@ -2822,6 +3016,7 @@ std::vector Document::addObjects(const char* sType, const std: // do no transactions if we do a rollback! if (!d->rollback) { // Undo stuff + _checkTransaction(0,0,__LINE__); if (d->activeUndoTransaction) { d->activeUndoTransaction->addObjectDel(pcObject); } @@ -2897,6 +3092,7 @@ void Document::addObject(DocumentObject* pcObject, const char* pObjectName) // do no transactions if we do a rollback! if (!d->rollback) { // Undo stuff + _checkTransaction(0,0,__LINE__); if (d->activeUndoTransaction) d->activeUndoTransaction->addObjectDel(pcObject); } @@ -2952,6 +3148,7 @@ void Document::_addObject(DocumentObject* pcObject, const char* pObjectName) // do no transactions if we do a rollback! if (!d->rollback) { // Undo stuff + _checkTransaction(0,0,__LINE__); if (d->activeUndoTransaction) d->activeUndoTransaction->addObjectDel(pcObject); } @@ -2980,7 +3177,7 @@ void Document::removeObject(const char* sName) if (pos == d->objectMap.end()) return; - _checkTransaction(pos->second); + _checkTransaction(pos->second,0,__LINE__); if (d->activeObject == pos->second) d->activeObject = 0; @@ -3056,7 +3253,7 @@ void Document::removeObject(const char* sName) void Document::_removeObject(DocumentObject* pcObject) { // TODO Refactoring: share code with Document::removeObject() (2015-09-01, Fat-Zer) - _checkTransaction(pcObject); + _checkTransaction(pcObject,0,__LINE__); auto pos = d->objectMap.find(pcObject->getNameInDocument()); diff --git a/src/App/Document.h b/src/App/Document.h index a24a389ca2..e7b256d7a8 100644 --- a/src/App/Document.h +++ b/src/App/Document.h @@ -298,7 +298,22 @@ public: //@} - /** @name methods for the UNDO REDO and Transaction handling */ + /** @name methods for the UNDO REDO and Transaction handling + * + * Introduce a new concept of transaction ID. Each transaction must be + * unique inside the document. Multiple transactions from different + * documents can be grouped together with the same transaction ID. + * + * When undo, Gui component can query getAvailableUndo(id) to see if it is + * possible to undo with a given ID. If there more than one undo + * transactions, meaning that there are other transactions before the given + * ID. The Gui component shall ask user if he wants to undo multiple steps. + * And if the user agrees, call undo(id) to unroll all transaction before + * and including the the one with the give ID. Same apllies for redo. + * + * The new transaction ID describe here is fully backward compatible. + * Calling the APIs with a default id=0 gives the original behavior. + */ //@{ /// switch the level of Undo/Redo void setUndoMode(int iMode); @@ -306,14 +321,25 @@ public: int getUndoMode(void) const; /// switch the transaction mode void setTransactionMode(int iMode); - /// Open a new command Undo/Redo, an UTF-8 name can be specified + /** Open a new command Undo/Redo, an UTF-8 name can be specified + * + * @param name: transaction name + * + * This function calls App::Application::setActiveTransaction(name) instead + * to setup a potential transaction which will only be created if there is + * actual changes. + */ void openTransaction(const char* name=0); - // Commit the Command transaction. Do nothing If there is no Command transaction open. + /// Rename the current transaction if the id matches + void renameTransaction(const char *name, int id); + /// Commit the Command transaction. Do nothing If there is no Command transaction open. void commitTransaction(); /// Abort the actually running transaction. void abortTransaction(); /// Check if a transaction is open bool hasPendingTransaction() const; + /// Return the undo/redo transaction ID starting from the back + int getTransactionID(bool undo, unsigned pos=0) const; /// Check if a transaction is open and its list is empty. /// If no transaction is open true is returned. bool isTransactionEmpty() const; @@ -328,17 +354,17 @@ public: /// Remove all stored Undos and Redos void clearUndos(); /// Returns the number of stored Undos. If greater than 0 Undo will be effective. - int getAvailableUndos() const; + int getAvailableUndos(int id=0) const; /// Returns a list of the Undo names std::vector getAvailableUndoNames() const; /// Will UNDO one step, returns False if no undo was done (Undos == 0). - bool undo(); + bool undo(int id=0); /// Returns the number of stored Redos. If greater than 0 Redo will be effective. - int getAvailableRedos() const; + int getAvailableRedos(int id=0) const; /// Returns a list of the Redo names. std::vector getAvailableRedoNames() const; /// Will REDO one step, returns False if no redo was done (Redos == 0). - bool redo() ; + bool redo(int id=0) ; /// returns true if the document is in an Transaction phase, e.g. currently performing a redo/undo or rollback bool isPerformingTransaction() const; /// \internal add or remove property from a transactional object @@ -409,7 +435,7 @@ protected: void _removeObject(DocumentObject* pcObject); void _addObject(DocumentObject* pcObject, const char* pObjectName); /// checks if a valid transaction is open - void _checkTransaction(DocumentObject* pcObject); + void _checkTransaction(DocumentObject* pcDelObj, const Property *What, int line); void breakDependency(DocumentObject* pcObject, bool clear); std::vector readObjects(Base::XMLReader& reader); void writeObjects(const std::vector&, Base::Writer &writer) const; @@ -430,18 +456,34 @@ protected: void _rebuildDependencyList(void); std::string getTransientDirectoryName(const std::string& uuid, const std::string& filename) const; + /** Open a new command Undo/Redo, an UTF-8 name can be specified + * + * @param name: transaction name + * @param id: transaction ID, if 0 then the ID is auto generated. + * + * @return: Return the ID of the new transaction. + * + * This function creates an actual transaction regardless of Application + * AutoTransaction setting. + */ + int _openTransaction(const char* name=0, int id=0); + /// Internally called by App::Application to commit the Command transaction. + void _commitTransaction(bool notify=false); + /// Internally called by App::Application to abort the running transaction. + void _abortTransaction(); private: // # Data Member of the document +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ std::list mUndoTransactions; + std::map mUndoMap; std::list mRedoTransactions; - // recompute log - std::vector _RecomputeLog; + std::map mRedoMap; // pointer to the python class Py::Object DocumentPythonObject; struct DocumentP* d; + std::string oldLabel; std::string myName; }; diff --git a/src/App/DocumentPy.xml b/src/App/DocumentPy.xml index eea4e29568..055740b4e8 100644 --- a/src/App/DocumentPy.xml +++ b/src/App/DocumentPy.xml @@ -50,7 +50,14 @@ - Open a new Undo/Redo transaction. + openTransaction(name) - Open a new Undo/Redo transaction. + +This function no long creates a new transaction, but calls +FreeCAD.setActiveTransaction(name) instead, which will auto creates a +transaction with the given name when any change happed in any opened document. +If more than one document is changed, all newly created transactions will have +the same internal ID and will be undo/redo together. + @@ -232,6 +239,18 @@ Both parameters are optional. + + + Check if there is a pending transaction + + + + + + Indicate whether the document is undoing/redoing + + + diff --git a/src/App/DocumentPyImp.cpp b/src/App/DocumentPyImp.cpp index a3212c7c11..255d10188e 100644 --- a/src/App/DocumentPyImp.cpp +++ b/src/App/DocumentPyImp.cpp @@ -373,6 +373,10 @@ PyObject* DocumentPy::commitTransaction(PyObject * args) Py_Return; } +Py::Boolean DocumentPy::getHasPendingTransaction() const { + return Py::Boolean(getDocumentPtr()->hasPendingTransaction()); +} + PyObject* DocumentPy::undo(PyObject * args) { if (!PyArg_ParseTuple(args, "")) // convert args: Python->C @@ -723,5 +727,9 @@ PyObject* DocumentPy::getLinksTo(PyObject *args) ret.setItem(i++,Py::Object(o->getPyObject(),true)); return Py::new_reference_to(ret); }PY_CATCH +Py::Boolean DocumentPy::getTransacting() const { + return Py::Boolean(getDocumentPtr()->isPerformingTransaction()); +} + } diff --git a/src/App/TransactionalObject.cpp b/src/App/TransactionalObject.cpp index a5729edd5b..036802327f 100644 --- a/src/App/TransactionalObject.cpp +++ b/src/App/TransactionalObject.cpp @@ -61,18 +61,3 @@ void TransactionalObject::onBeforeChangeProperty(Document *doc, const Property * doc->onBeforeChangeProperty(this, prop); } -App::Property* TransactionalObject::addDynamicProperty(const char*, const char*, - const char*, const char*, - short, bool, bool) -{ - std::stringstream str; - str << "Type " << this->getTypeId().getName() << " cannot dynamically add properties"; - throw Base::RuntimeError(str.str()); -} - -bool TransactionalObject::removeDynamicProperty(const char*) -{ - std::stringstream str; - str << "Type " << this->getTypeId().getName() << " cannot dynamically remove properties"; - throw Base::RuntimeError(str.str()); -} diff --git a/src/App/TransactionalObject.h b/src/App/TransactionalObject.h index 50e0747ac5..bd860835e3 100644 --- a/src/App/TransactionalObject.h +++ b/src/App/TransactionalObject.h @@ -44,13 +44,6 @@ public: virtual ~TransactionalObject(); virtual bool isAttachedToDocument() const; virtual const char* detachFromDocument(); - - virtual App::Property* addDynamicProperty( - const char*, const char* = 0, - const char* = 0, const char* = 0, - short = 0, bool = false, bool = false); - virtual bool removeDynamicProperty(const char*); - protected: void onBeforeChangeProperty(Document *doc, const Property *prop); }; diff --git a/src/App/Transactions.cpp b/src/App/Transactions.cpp index d147a8fcff..9c1af58038 100644 --- a/src/App/Transactions.cpp +++ b/src/App/Transactions.cpp @@ -27,16 +27,21 @@ # include #endif +#include + /// Here the FreeCAD includes sorted by Base,App,Gui...... #include using Base::Writer; #include using Base::XMLReader; +#include #include "Transactions.h" #include "Property.h" #include "Document.h" #include "DocumentObject.h" +FC_LOG_LEVEL_INIT("App",true,true); + using namespace App; using namespace std; @@ -45,14 +50,10 @@ TYPESYSTEM_SOURCE(App::Transaction, Base::Persistence) //************************************************************************** // Construction/Destruction -Transaction::Transaction() - : iPos(0) -{ -} - -Transaction::Transaction(int pos) - : iPos(pos) +Transaction::Transaction(int id) { + if(!id) id = getNewID(); + transID = id; } /** @@ -61,8 +62,8 @@ Transaction::Transaction(int pos) */ Transaction::~Transaction() { - TransactionList::iterator It; - for (It= _Objects.begin();It!=_Objects.end();++It) { + auto &index = _Objects.get<0>(); + for (auto It= index.begin();It!=index.end();++It) { if (It->second->status == TransactionObject::New) { // If an object has been removed from the document the transaction // status is 'New'. The 'pcNameInDocument' member serves as criterion @@ -95,6 +96,19 @@ Transaction::~Transaction() } } +static std::atomic _TransactionID; + +int Transaction::getNewID() { + int id = ++_TransactionID; + if(id) return id; + // wrap around? really? + return ++_TransactionID; +} + +int Transaction::getLastID() { + return _TransactionID; +} + unsigned int Transaction::getMemSize (void) const { return 0; @@ -110,34 +124,39 @@ void Transaction::Restore(Base::XMLReader &/*reader*/) assert(0); } +int Transaction::getID(void) const +{ + return transID; +} + bool Transaction::isEmpty() const { return _Objects.empty(); } -int Transaction::getPos(void) const -{ - return iPos; -} - bool Transaction::hasObject(const TransactionalObject *Obj) const { - TransactionList::const_iterator it; - for (it = _Objects.begin(); it != _Objects.end(); ++it) { - if (it->first == Obj) - return true; - } - - return false; + return !!_Objects.get<1>().count(Obj); } -void Transaction::removeProperty(TransactionalObject *Obj, - const Property* pcProp) +void Transaction::addOrRemoveProperty(TransactionalObject *Obj, + const Property* pcProp, bool add) { - for (auto it : _Objects) { - if (it.first == Obj) - it.second->removeProperty(pcProp); + auto &index = _Objects.get<1>(); + auto pos = index.find(Obj); + + TransactionObject *To; + + if (pos != index.end()) { + To = pos->second; } + else { + To = TransactionFactory::instance().createTransaction(Obj->getTypeId()); + To->status = TransactionObject::Chn; + index.emplace(Obj,To); + } + + To->addOrRemoveProperty(pcProp,add); } //************************************************************************** @@ -146,93 +165,90 @@ void Transaction::removeProperty(TransactionalObject *Obj, void Transaction::apply(Document &Doc, bool forward) { - TransactionList::iterator It; - //for (It= _Objects.begin();It!=_Objects.end();++It) - // It->second->apply(Doc,const_cast(It->first)); - for (It= _Objects.begin();It!=_Objects.end();++It) - It->second->applyDel(Doc, const_cast(It->first)); - for (It= _Objects.begin();It!=_Objects.end();++It) - It->second->applyNew(Doc, const_cast(It->first)); - for (It= _Objects.begin();It!=_Objects.end();++It) - It->second->applyChn(Doc, const_cast(It->first), forward); + std::string errMsg; + try { + auto &index = _Objects.get<0>(); + for(auto &info : index) + info.second->applyDel(Doc, const_cast(info.first)); + for(auto &info : index) + info.second->applyNew(Doc, const_cast(info.first)); + for(auto &info : index) + info.second->applyChn(Doc, const_cast(info.first), forward); + }catch(Base::Exception &e) { + e.ReportException(); + errMsg = e.what(); + }catch(std::exception &e) { + errMsg = e.what(); + }catch(...) { + errMsg = "Unknown exception"; + } + if(errMsg.size()) { + FC_ERR("Exception on " << (forward?"redo":"undo") << " '" + << Name << "':" << errMsg); + } } void Transaction::addObjectNew(TransactionalObject *Obj) { - TransactionList::iterator pos = _Objects.end(); - for (TransactionList::iterator it = _Objects.begin(); it != _Objects.end(); ++it) { - if (it->first == Obj) { - pos = it; - break; - } - } - - if (pos != _Objects.end()) { + auto &index = _Objects.get<1>(); + auto pos = index.find(Obj); + if (pos != index.end()) { if (pos->second->status == TransactionObject::Del) { delete pos->second; delete pos->first; - _Objects.erase(pos); + index.erase(pos); } else { pos->second->status = TransactionObject::New; pos->second->_NameInDocument = Obj->detachFromDocument(); // move item at the end to make sure the order of removal is kept - _Objects.splice(_Objects.end(), _Objects, pos); + auto &seq = _Objects.get<0>(); + seq.relocate(seq.end(),_Objects.project<0>(pos)); } } else { TransactionObject *To = TransactionFactory::instance().createTransaction(Obj->getTypeId()); To->status = TransactionObject::New; To->_NameInDocument = Obj->detachFromDocument(); - _Objects.push_back(std::make_pair(Obj, To)); + index.emplace(Obj,To); } } void Transaction::addObjectDel(const TransactionalObject *Obj) { - TransactionList::iterator pos = _Objects.end(); - for (TransactionList::iterator it = _Objects.begin(); it != _Objects.end(); ++it) { - if (it->first == Obj) { - pos = it; - break; - } - } + auto &index = _Objects.get<1>(); + auto pos = index.find(Obj); // is it created in this transaction ? - if (pos != _Objects.end() && pos->second->status == TransactionObject::New) { + if (pos != index.end() && pos->second->status == TransactionObject::New) { // remove completely from transaction delete pos->second; - _Objects.erase(pos); + index.erase(pos); } - else if (pos != _Objects.end() && pos->second->status == TransactionObject::Chn) { + else if (pos != index.end() && pos->second->status == TransactionObject::Chn) { pos->second->status = TransactionObject::Del; } else { TransactionObject *To = TransactionFactory::instance().createTransaction(Obj->getTypeId()); - _Objects.push_back(std::make_pair(Obj, To)); To->status = TransactionObject::Del; + index.emplace(Obj,To); } } void Transaction::addObjectChange(const TransactionalObject *Obj, const Property *Prop) { - TransactionList::iterator pos = _Objects.end(); - for (TransactionList::iterator it = _Objects.begin(); it != _Objects.end(); ++it) { - if (it->first == Obj) { - pos = it; - break; - } - } + auto &index = _Objects.get<1>(); + auto pos = index.find(Obj); TransactionObject *To; - if (pos != _Objects.end()) { + if (pos != index.end()) { To = pos->second; } else { To = TransactionFactory::instance().createTransaction(Obj->getTypeId()); - _Objects.push_back(std::make_pair(Obj, To)); To->status = TransactionObject::Chn; + index.emplace(Obj,To); } To->setProperty(Prop); @@ -264,9 +280,8 @@ TransactionObject::TransactionObject() */ TransactionObject::~TransactionObject() { - std::map::const_iterator It; - for (It=_PropChangeMap.begin();It!=_PropChangeMap.end();++It) - delete It->second; + for(auto &v : _PropChangeMap) + delete v.second.property; } void TransactionObject::applyDel(Document & /*Doc*/, TransactionalObject * /*pcObj*/) @@ -277,38 +292,97 @@ void TransactionObject::applyNew(Document & /*Doc*/, TransactionalObject * /*pcO { } -void TransactionObject::applyChn(Document & /*Doc*/, TransactionalObject * /*pcObj*/, bool Forward) +void TransactionObject::applyChn(Document & /*Doc*/, TransactionalObject *pcObj, bool Forward) { if (status == New || status == Chn) { - // apply changes if any - if (!Forward) { - std::map::const_reverse_iterator It; - std::map::const_reverse_iterator rendIt = _PropChangeMap.rend(); - for (It = _PropChangeMap.rbegin(); It != rendIt; ++It) - const_cast(It->first)->Paste(*(It->second)); - } - else { - std::map::const_iterator It; - std::map::const_iterator endIt = _PropChangeMap.end(); - for (It = _PropChangeMap.begin(); It != endIt; ++It) - const_cast(It->first)->Paste(*(It->second)); + // Property change order is not preserved, as it is recursive in nature + for(auto &v : _PropChangeMap) { + auto &data = v.second; + auto prop = const_cast(v.first); + + if(!data.property) { + // here means we are undoing/redoing and property add operation + pcObj->removeDynamicProperty(v.second.name.c_str()); + continue; + } + + // getPropertyName() is specially coded to be safe even if prop has + // been destroies. We must prepare for the case where user removed + // a dynamic property but does not recordered as transaction. + auto name = pcObj->getPropertyName(prop); + if(!name) { + // Here means the original property is not found, probably removed + if(v.second.name.empty()) { + // not a dynamic property, nothing to do + continue; + } + + // It is possible for the dynamic property to be removed and + // restored. But since restoring property is actually creating + // a new property, the property key inside redo stack will not + // match. So we search by name first. + prop = pcObj->getDynamicPropertyByName(v.second.name.c_str()); + if(!prop) { + // Still not found, re-create the property + prop = pcObj->addDynamicProperty( + data.property->getTypeId().getName(), + v.second.name.c_str(), data.group.c_str(), data.doc.c_str(), + data.attr, data.readonly, data.hidden); + if(!prop) + continue; + prop->setStatusValue(data.property->getStatus()); + } + } + // Because we now allow undo/redo dynamic property adding/removing, + // we have to enforce property type checking before calling Copy/Paste. + if(data.property->getTypeId() != prop->getTypeId()) { + FC_WARN("Cannot " << (Forward?"redo":"undo") + << " change of property " << prop->getName() + << " because of type change: " + << data.property->getTypeId().getName() + << " -> " << prop->getTypeId().getName()); + continue; + } + prop->Paste(*data.property); } } } void TransactionObject::setProperty(const Property* pcProp) { - std::map::iterator pos = _PropChangeMap.find(pcProp); - if (pos == _PropChangeMap.end()) - _PropChangeMap[pcProp] = pcProp->Copy(); + auto &data = _PropChangeMap[pcProp]; + if(!data.property && data.name.empty()) { + data = pcProp->getContainer()->getDynamicPropertyData(pcProp); + data.property = pcProp->Copy(); + data.property->setStatusValue(pcProp->getStatus()); + } } -void TransactionObject::removeProperty(const Property* pcProp) +void TransactionObject::addOrRemoveProperty(const Property* pcProp, bool add) { - std::map::iterator pos = _PropChangeMap.find(pcProp); - if (pos != _PropChangeMap.end()) { - delete pos->second; - _PropChangeMap.erase(pos); + (void)add; + if(!pcProp || !pcProp->getContainer()) + return; + + auto &data = _PropChangeMap[pcProp]; + if(data.name.size()) { + if(!add && !data.property) { + // this means add and remove the same property inside a single + // transaction, so they cancel each other out. + _PropChangeMap.erase(pcProp); + } + return; + } + if(data.property) { + delete data.property; + data.property = 0; + } + data = pcProp->getContainer()->getDynamicPropertyData(pcProp); + if(add) + data.property = 0; + else { + data.property = pcProp->Copy(); + data.property->setStatusValue(pcProp->getStatus()); } } diff --git a/src/App/Transactions.h b/src/App/Transactions.h index 3f135504e1..7d0e619a77 100644 --- a/src/App/Transactions.h +++ b/src/App/Transactions.h @@ -26,6 +26,7 @@ #include #include +#include namespace App { @@ -44,11 +45,16 @@ class AppExport Transaction : public Base::Persistence TYPESYSTEM_HEADER(); public: + /** Construction + * + * @param id: transaction id. If zero, then it will be generated + * automatically as a monotonically increasing index across the entire + * application. User can pass in a transaction id to group multiple + * transactions from different document, so that they can be undo/redo + * together. + */ + Transaction(int id = 0); /// Construction - Transaction(); - /// Construction - Transaction(int pos); - /// Destruction virtual ~Transaction(); /// apply the content to the document @@ -62,22 +68,35 @@ public: /// This method is used to restore properties from an XML document. virtual void Restore(Base::XMLReader &reader); + /// Return the transaction ID + int getID(void) const; + + /// Generate a new unique transaction ID + static int getNewID(void); + static int getLastID(void); + /// Returns true if the transaction list is empty; otherwise returns false. bool isEmpty() const; - /// get the position in the transaction history - int getPos(void) const; /// check if this object is used in a transaction bool hasObject(const TransactionalObject *Obj) const; - void removeProperty(TransactionalObject *Obj, const Property* pcProp); + void addOrRemoveProperty(TransactionalObject *Obj, const Property* pcProp, bool add); void addObjectNew(TransactionalObject *Obj); void addObjectDel(const TransactionalObject *Obj); void addObjectChange(const TransactionalObject *Obj, const Property *Prop); private: - int iPos; - typedef std::list > TransactionList; - TransactionList _Objects; + int transID; + typedef std::pair Info; + bmi::multi_index_container< + Info, + bmi::indexed_by< + bmi::sequenced<>, + bmi::hashed_unique< + bmi::member + > + > + > _Objects; }; /** Represents an entry for an object in a Transaction @@ -97,7 +116,7 @@ public: virtual void applyChn(Document &Doc, TransactionalObject *pcObj, bool Forward); void setProperty(const Property* pcProp); - void removeProperty(const Property* pcProp); + void addOrRemoveProperty(const Property* pcProp, bool add); virtual unsigned int getMemSize (void) const; virtual void Save (Base::Writer &writer) const; @@ -108,7 +127,7 @@ public: protected: enum Status {New,Del,Chn} status; - std::map _PropChangeMap; + std::unordered_map _PropChangeMap; std::string _NameInDocument; };