App: fix property ordering problem when undo/redo (#3255)
* Part: fix Placement/Shape onChanged() handling * App: fix property ordering problem when undo/redo See https://tracker.freecadweb.org/view.php?id=4265#c14271 * Gui: fix undo/redo signaling Make sure to signal after all properties has been restored
This commit is contained in:
@@ -36,8 +36,10 @@ using Base::Writer;
|
||||
#include <Base/Reader.h>
|
||||
using Base::XMLReader;
|
||||
#include <Base/Console.h>
|
||||
#include <Base/Tools.h>
|
||||
#include "Transactions.h"
|
||||
#include "Property.h"
|
||||
#include "Application.h"
|
||||
#include "Document.h"
|
||||
#include "DocumentObject.h"
|
||||
|
||||
@@ -163,6 +165,133 @@ void Transaction::addOrRemoveProperty(TransactionalObject *Obj,
|
||||
//**************************************************************************
|
||||
// separator for other implementation aspects
|
||||
|
||||
static int _TransactionActive;
|
||||
static bool _FlushingProps;
|
||||
|
||||
// Hold all changed property when transactions are applied. The mapped value is
|
||||
// index for remembering the order. Although the property change order within
|
||||
// the same object is not kept, but there is some ordering of changes between
|
||||
// different objects
|
||||
static std::unordered_map<Property*, int> _PendingProps;
|
||||
static int _PendingPropIndex;
|
||||
|
||||
TransactionGuard::TransactionGuard(bool undo)
|
||||
:undo(undo)
|
||||
{
|
||||
if(_FlushingProps) {
|
||||
FC_ERR("Recursive transaction");
|
||||
return;
|
||||
}
|
||||
++_TransactionActive;
|
||||
}
|
||||
|
||||
TransactionGuard::~TransactionGuard()
|
||||
{
|
||||
if(_FlushingProps)
|
||||
return;
|
||||
|
||||
if(--_TransactionActive)
|
||||
return;
|
||||
|
||||
Base::StateLocker locker(_FlushingProps);
|
||||
|
||||
std::vector<std::pair<Property*,int> > props;
|
||||
props.reserve(_PendingProps.size());
|
||||
props.insert(props.end(),_PendingProps.begin(),_PendingProps.end());
|
||||
std::sort(props.begin(), props.end(),
|
||||
[](const std::pair<Property*,int> &a, const std::pair<Property*,int> &b) {
|
||||
return a.second < b.second;
|
||||
});
|
||||
|
||||
std::vector<App::Document*> docs;
|
||||
std::set<App::Document*> docSet;
|
||||
for(auto &v : props) {
|
||||
auto container = v.first->getContainer();
|
||||
if (!container) continue;
|
||||
auto doc = container->getOwnerDocument();
|
||||
if (doc && docSet.insert(doc).second)
|
||||
docs.push_back(doc);
|
||||
}
|
||||
|
||||
std::string errMsg;
|
||||
for(auto &v : props) {
|
||||
auto prop = v.first;
|
||||
// double check if the property exists, because it may be removed
|
||||
// while we are looping.
|
||||
if(_PendingProps.count(prop)) {
|
||||
try {
|
||||
FC_LOG("transaction touch " << prop->getFullName());
|
||||
prop->touch();
|
||||
}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 finishing transaction " << errMsg);
|
||||
errMsg.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
_PendingProps.clear();
|
||||
_PendingPropIndex = 0;
|
||||
|
||||
try {
|
||||
if(undo) {
|
||||
for (auto doc : docs)
|
||||
doc->signalUndo(*doc);
|
||||
GetApplication().signalUndo();
|
||||
} else {
|
||||
for (auto doc : docs)
|
||||
doc->signalRedo(*doc);
|
||||
GetApplication().signalRedo();
|
||||
}
|
||||
} catch(Base::Exception &e) {
|
||||
e.ReportException();
|
||||
errMsg = e.what();
|
||||
} catch(...) {
|
||||
errMsg = "Unknown exception";
|
||||
}
|
||||
if (errMsg.size()) {
|
||||
FC_ERR("Exception on " << (undo?"undo: ":"redo: ") << errMsg);
|
||||
errMsg.clear();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
bool Transaction::isApplying(Property *prop)
|
||||
{
|
||||
if (_TransactionActive) {
|
||||
if(prop)
|
||||
_PendingProps.insert(std::make_pair(prop, _PendingPropIndex++));
|
||||
return true;
|
||||
}
|
||||
|
||||
// If we are flushing the property changes, then
|
||||
// Document::isPerformingTransaction() shall still report true. That's why
|
||||
// we return true here if prop is not given.
|
||||
if (!prop)
|
||||
return _FlushingProps;
|
||||
|
||||
// Property::hasSetValue/touch() also call us (with a given prop) to see if
|
||||
// it shall notify its container about the change. Now, it is debatable if
|
||||
// we shall allow further propagation of the change notification, because
|
||||
// optimally speaking, no recomputation shall be performed while undo/redo.
|
||||
// We can stop the chain of notification after informing change of the
|
||||
// given property. This, however, may cause problem for those not
|
||||
// 'optimally' coded objects. So we didn't do that, but only remove the
|
||||
// soon to be notified property here.
|
||||
_PendingProps.erase(prop);
|
||||
return false;
|
||||
}
|
||||
|
||||
void Transaction::removePendingProperty(Property *prop)
|
||||
{
|
||||
_PendingProps.erase(prop);
|
||||
}
|
||||
|
||||
void Transaction::apply(Document &Doc, bool forward)
|
||||
{
|
||||
@@ -175,6 +304,7 @@ void Transaction::apply(Document &Doc, bool forward)
|
||||
info.second->applyNew(Doc, const_cast<TransactionalObject*>(info.first));
|
||||
for(auto &info : index)
|
||||
info.second->applyChn(Doc, const_cast<TransactionalObject*>(info.first), forward);
|
||||
|
||||
}catch(Base::Exception &e) {
|
||||
e.ReportException();
|
||||
errMsg = e.what();
|
||||
|
||||
Reference in New Issue
Block a user