diff --git a/src/App/AutoTransaction.cpp b/src/App/AutoTransaction.cpp index 82ccc8cec9..6094ef2070 100644 --- a/src/App/AutoTransaction.cpp +++ b/src/App/AutoTransaction.cpp @@ -21,7 +21,9 @@ ****************************************************************************/ #include "PreCompiled.h" + #include +#include #include "Application.h" #include "Transactions.h" #include "Document.h" @@ -31,6 +33,9 @@ FC_LOG_LEVEL_INIT("App",true,true) using namespace App; +static int _TransactionLock; +static int _TransactionClosed; + AutoTransaction::AutoTransaction(const char *name, bool tmpName) { auto &app = GetApplication(); if(name && app._activeTransactionGuard>=0) { @@ -125,7 +130,11 @@ int Application::setActiveTransaction(const char *name, bool persist) { AutoTransaction::setEnable(false); return 0; } - }else{ + } else if (_TransactionLock) { + if (FC_LOG_INSTANCE.isEnabled(FC_LOGLEVEL_LOG)) + FC_WARN("Transaction locked, ignore new transaction '" << name << "'"); + return 0; + } else { FC_LOG("set active transaction '" << name << "'"); _activeTransactionID = 0; for(auto &v : DocMap) @@ -156,10 +165,17 @@ void Application::closeActiveTransaction(bool abort, int id) { return; } + if(_TransactionLock) { + if(_TransactionClosed >= 0) + _TransactionLock = abort?-1:1; + FC_LOG("pending " << (abort?"abort":"close") << " transaction"); + return; + } + FC_LOG("close transaction '" << _activeTransactionName << "' " << abort); _activeTransactionID = 0; - TransactionSignaller siganller(abort,false); + TransactionSignaller signaller(abort,false); for(auto &v : DocMap) { if(v.second->getTransactionID(true) != id) continue; @@ -170,3 +186,57 @@ void Application::closeActiveTransaction(bool abort, int id) { } } +//////////////////////////////////////////////////////////////////////// + +TransactionLocker::TransactionLocker(bool lock) + :active(lock) +{ + if(lock) + ++_TransactionLock; +} + +TransactionLocker::~TransactionLocker() +{ + if(active) { + try { + activate(false); + return; + } catch (Base::Exception &e) { + e.ReportException(); + } catch (Py::Exception &) { + Base::PyException e; + e.ReportException(); + } catch (std::exception &e) { + FC_ERR(e.what()); + } catch (...) { + } + FC_ERR("Exception when unlocking transaction"); + } +} + +void TransactionLocker::activate(bool enable) +{ + if(active == enable) + return; + + active = enable; + if(active) { + ++_TransactionLock; + return; + } + + if(--_TransactionLock != 0) + return; + + if(_TransactionClosed) { + bool abort = (_TransactionClosed<0); + _TransactionClosed = 0; + GetApplication().closeActiveTransaction(abort); + } +} + +bool TransactionLocker::isLocked() { + return _TransactionLock > 0; +} + + diff --git a/src/App/AutoTransaction.h b/src/App/AutoTransaction.h index f22fc123a4..e792b41b89 100644 --- a/src/App/AutoTransaction.h +++ b/src/App/AutoTransaction.h @@ -25,6 +25,8 @@ namespace App { +class Application; + /// Helper class to manager transaction (i.e. undo/redo) class AppExport AutoTransaction { private: @@ -79,6 +81,51 @@ private: int tid = 0; }; + +/** Helper class to lock a transaction from being closed or aborted. + * + * The helper class is used to protect some critical transaction from being + * closed prematurely, e.g. when deleting some object. + */ +class AppExport TransactionLocker { +public: + + /** Constructor + * @param lock: whether to activate the lock + */ + TransactionLocker(bool lock=true); + + /** Destructor + * Unlock the transaction is this locker is active + */ + ~TransactionLocker(); + + /** Activate or deactivate this locker + * @param enable: whether to activate the locker + * + * An internal counter is used to support recursive locker. When activated, + * the current active transaction cannot be closed or aborted. But the + * closing call (Application::closeActiveTransaction()) will be remembered, + * and performed when the internal lock counter reaches zero. + */ + void activate(bool enable); + + /// Check if the locker is active + bool isActive() const {return active;} + + /// Check if transaction is being locked + static bool isLocked(); + + friend class Application; + +private: + /// Private new operator to prevent heap allocation + void* operator new(size_t size); + +private: + bool active; +}; + } // namespace App #endif // APP_AUTOTRANSACTION_H diff --git a/src/App/Document.cpp b/src/App/Document.cpp index cf54a7924e..f916e05fc1 100644 --- a/src/App/Document.cpp +++ b/src/App/Document.cpp @@ -91,6 +91,7 @@ recompute path. Also, it enables more complicated dependencies beyond trees. #include #include +#include "AutoTransaction.h" #include "Document.h" #include "Application.h" #include "DocumentObject.h" @@ -3764,6 +3765,8 @@ void Document::removeObject(const char* sName) return; } + TransactionLocker tlock; + _checkTransaction(pos->second,0,__LINE__); #if 0 @@ -3865,6 +3868,8 @@ void Document::_removeObject(DocumentObject* pcObject) return; } + TransactionLocker tlock; + // TODO Refactoring: share code with Document::removeObject() (2015-09-01, Fat-Zer) _checkTransaction(pcObject,0,__LINE__); diff --git a/src/Gui/CommandDoc.cpp b/src/Gui/CommandDoc.cpp index e091e32835..566e88da89 100644 --- a/src/Gui/CommandDoc.cpp +++ b/src/Gui/CommandDoc.cpp @@ -1130,6 +1130,9 @@ void StdCmdDelete::activated(int iMsg) commitCommand(); return; } + + App::TransactionLocker tlock; + Gui::getMainWindow()->setUpdatesEnabled(false); auto editDoc = Application::Instance->editDocument(); ViewProviderDocumentObject *vpedit = 0;