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:
Zheng Lei
2022-02-21 19:29:01 +08:00
committed by GitHub
parent b42462ba14
commit c3178343db
14 changed files with 250 additions and 68 deletions

View File

@@ -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();