"Professional CMake" book suggest the following: "Targets should build successfully with or without compiler support for precompiled headers. It should be considered an optimization, not a requirement. In particular, do not explicitly include a precompile header (e.g. stdafx.h) in the source code, let CMake force-include an automatically generated precompile header on the compiler command line instead. This is more portable across the major compilers and is likely to be easier to maintain. It will also avoid warnings being generated from certain code checking tools like iwyu (include what you use)." Therefore, removed the "#include <PreCompiled.h>" from sources, also there is no need for the "#ifdef _PreComp_" anymore
579 lines
20 KiB
C++
579 lines
20 KiB
C++
/***************************************************************************
|
|
* Copyright (c) 2011 Jürgen Riegel <juergen.riegel@web.de> *
|
|
* Copyright (c) 2011 Werner Mayer <wmayer[at]users.sourceforge.net> *
|
|
* *
|
|
* 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 *
|
|
* *
|
|
***************************************************************************/
|
|
|
|
#include <cassert>
|
|
|
|
#include <atomic>
|
|
#include <Base/Console.h>
|
|
#include <Base/Reader.h>
|
|
#include <Base/Writer.h>
|
|
|
|
#include "Transactions.h"
|
|
#include "Document.h"
|
|
#include "DocumentObject.h"
|
|
#include "Property.h"
|
|
|
|
|
|
FC_LOG_LEVEL_INIT("App", true, true)
|
|
|
|
using namespace App;
|
|
using namespace std;
|
|
|
|
TYPESYSTEM_SOURCE(App::Transaction, Base::Persistence)
|
|
|
|
//**************************************************************************
|
|
// Construction/Destruction
|
|
|
|
Transaction::Transaction(int id)
|
|
{
|
|
if (!id) {
|
|
id = getNewID();
|
|
}
|
|
transID = id;
|
|
}
|
|
|
|
/**
|
|
* A destructor.
|
|
* A more elaborate description of the destructor.
|
|
*/
|
|
Transaction::~Transaction()
|
|
{
|
|
auto& index = _Objects.get<0>();
|
|
for (const auto& It : index) {
|
|
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
|
|
// to check whether the object is part of the document or not.
|
|
// Note, it's possible that the transaction status is 'New' while the
|
|
// object is (again) part of the document. This usually happens when
|
|
// a previous removal is undone.
|
|
// Thus, if the object has been removed, i.e. the status is 'New' and
|
|
// is still not part of the document the object must be destroyed not
|
|
// to cause a memory leak. This usually is the case when the removal
|
|
// of an object is not undone or when an addition is undone.
|
|
|
|
if (!It.first->isAttachedToDocument()) {
|
|
if (It.first->isDerivedFrom<DocumentObject>()) {
|
|
// #0003323: Crash when clearing transaction list
|
|
// It can happen that when clearing the transaction list several objects
|
|
// are destroyed with dependencies which can lead to dangling pointers.
|
|
// When setting the 'Destroy' flag of an object the destructors of link
|
|
// properties don't ry to remove backlinks, i.e. they don't try to access
|
|
// possible dangling pointers.
|
|
// An alternative solution is to call breakDependency inside
|
|
// Document::_removeObject. Make this change in v0.18.
|
|
const DocumentObject* obj = static_cast<const DocumentObject*>(It.first);
|
|
const_cast<DocumentObject*>(obj)->setStatus(ObjectStatus::Destroy, true);
|
|
}
|
|
delete It.first;
|
|
}
|
|
}
|
|
delete It.second;
|
|
}
|
|
}
|
|
|
|
static std::atomic<int> _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() const
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
void Transaction::Save(Base::Writer& /*writer*/) const
|
|
{
|
|
assert(0);
|
|
}
|
|
|
|
void Transaction::Restore(Base::XMLReader& /*reader*/)
|
|
{
|
|
assert(0);
|
|
}
|
|
|
|
int Transaction::getID() const
|
|
{
|
|
return transID;
|
|
}
|
|
|
|
bool Transaction::isEmpty() const
|
|
{
|
|
return _Objects.empty();
|
|
}
|
|
|
|
bool Transaction::hasObject(const TransactionalObject* Obj) const
|
|
{
|
|
#if BOOST_VERSION < 107500
|
|
return !!_Objects.get<1>().count(Obj);
|
|
#else
|
|
return !!_Objects.get<1>().contains(Obj);
|
|
#endif
|
|
}
|
|
|
|
void Transaction::changeProperty(TransactionalObject* Obj,
|
|
std::function<void(TransactionObject* to)> changeFunc)
|
|
{
|
|
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);
|
|
}
|
|
|
|
changeFunc(To);
|
|
}
|
|
|
|
void Transaction::renameProperty(TransactionalObject* Obj, const Property* pcProp, const char* oldName)
|
|
{
|
|
changeProperty(Obj, [pcProp, oldName](TransactionObject* to) {
|
|
to->renameProperty(pcProp, oldName);
|
|
});
|
|
}
|
|
|
|
void Transaction::addOrRemoveProperty(TransactionalObject* Obj, const Property* pcProp, bool add)
|
|
{
|
|
changeProperty(Obj, [pcProp, add](TransactionObject* to) {
|
|
to->addOrRemoveProperty(pcProp, add);
|
|
});
|
|
}
|
|
|
|
//**************************************************************************
|
|
// separator for other implementation aspects
|
|
|
|
|
|
void Transaction::apply(Document& Doc, bool forward)
|
|
{
|
|
std::string errMsg;
|
|
try {
|
|
auto& index = _Objects.get<0>();
|
|
for (auto& info : index) {
|
|
info.second->applyDel(Doc, const_cast<TransactionalObject*>(info.first));
|
|
}
|
|
for (auto& info : index) {
|
|
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();
|
|
}
|
|
catch (std::exception& e) {
|
|
errMsg = e.what();
|
|
}
|
|
catch (...) {
|
|
errMsg = "Unknown exception";
|
|
}
|
|
if (!errMsg.empty()) {
|
|
FC_ERR("Exception on " << (forward ? "redo" : "undo") << " '" << Name << "':" << errMsg);
|
|
}
|
|
}
|
|
|
|
void Transaction::addObjectNew(TransactionalObject* Obj)
|
|
{
|
|
auto& index = _Objects.get<1>();
|
|
auto pos = index.find(Obj);
|
|
if (pos != index.end()) {
|
|
if (pos->second->status == TransactionObject::Del) {
|
|
// first remove the item from the container before deleting it
|
|
auto second = pos->second;
|
|
auto first = pos->first;
|
|
index.erase(pos);
|
|
delete second;
|
|
delete first;
|
|
}
|
|
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
|
|
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();
|
|
index.emplace(Obj, To);
|
|
}
|
|
}
|
|
|
|
void Transaction::addObjectDel(const TransactionalObject* Obj)
|
|
{
|
|
auto& index = _Objects.get<1>();
|
|
auto pos = index.find(Obj);
|
|
|
|
// is it created in this transaction ?
|
|
if (pos != index.end() && pos->second->status == TransactionObject::New) {
|
|
// remove completely from transaction
|
|
delete pos->second;
|
|
index.erase(pos);
|
|
}
|
|
else if (pos != index.end() && pos->second->status == TransactionObject::Chn) {
|
|
pos->second->status = TransactionObject::Del;
|
|
}
|
|
else {
|
|
TransactionObject* To = TransactionFactory::instance().createTransaction(Obj->getTypeId());
|
|
To->status = TransactionObject::Del;
|
|
index.emplace(Obj, To);
|
|
}
|
|
}
|
|
|
|
void Transaction::addObjectChange(const TransactionalObject* Obj, const Property* Prop)
|
|
{
|
|
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->setProperty(Prop);
|
|
}
|
|
|
|
|
|
//**************************************************************************
|
|
//**************************************************************************
|
|
// TransactionObject
|
|
//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
|
|
|
TYPESYSTEM_SOURCE_ABSTRACT(App::TransactionObject, Base::Persistence)
|
|
|
|
//**************************************************************************
|
|
// Construction/Destruction
|
|
|
|
/**
|
|
* A constructor.
|
|
* A more elaborate description of the constructor.
|
|
*/
|
|
TransactionObject::TransactionObject() = default;
|
|
|
|
/**
|
|
* A destructor.
|
|
* A more elaborate description of the destructor.
|
|
*/
|
|
TransactionObject::~TransactionObject()
|
|
{
|
|
for (auto& v : _PropChangeMap) {
|
|
auto& data = v.second;
|
|
// If nameOrig is used, it means it is a transaction of a rename
|
|
// operation. This operation does not interact with v.second.property,
|
|
// so it should not be deleted in that case.
|
|
if (data.nameOrig.empty()) {
|
|
delete v.second.property;
|
|
}
|
|
}
|
|
}
|
|
|
|
void TransactionObject::applyDel(Document& /*Doc*/, TransactionalObject* /*pcObj*/)
|
|
{}
|
|
|
|
void TransactionObject::applyNew(Document& /*Doc*/, TransactionalObject* /*pcObj*/)
|
|
{}
|
|
|
|
void TransactionObject::applyChn(Document& /*Doc*/, TransactionalObject* pcObj, bool /* Forward */)
|
|
{
|
|
if (status == New || status == Chn) {
|
|
// Property change order is not preserved, as it is recursive in nature
|
|
for (auto& v : _PropChangeMap) {
|
|
auto& data = v.second;
|
|
auto prop = const_cast<Property*>(data.propertyOrig);
|
|
|
|
if (!data.nameOrig.empty()) {
|
|
// This means we are undoing/redoing a rename operation
|
|
Property* currentProp = pcObj->getDynamicPropertyByName(data.name.c_str());
|
|
if (currentProp) {
|
|
pcObj->renameDynamicProperty(currentProp, data.nameOrig.c_str());
|
|
}
|
|
continue;
|
|
}
|
|
|
|
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 || (!data.name.empty() && data.name != name)
|
|
|| data.propertyType != prop->getTypeId()) {
|
|
// Here means the original property is not found, probably removed
|
|
if (data.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(data.name.c_str());
|
|
if (!prop) {
|
|
// Still not found, re-create the property
|
|
prop = pcObj->addDynamicProperty(data.propertyType.getName(),
|
|
data.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());
|
|
}
|
|
}
|
|
|
|
// Many properties do not bother implement Copy() and accepts
|
|
// derived types just fine in Paste(). So we do not enforce type
|
|
// matching here. But instead, strengthen type checking in all
|
|
// Paste() implementation.
|
|
//
|
|
// if(data.propertyType != prop->getTypeId()) {
|
|
// FC_WARN("Cannot " << (Forward?"redo":"undo")
|
|
// << " change of property " << prop->getName()
|
|
// << " because of type change: "
|
|
// << data.propertyType.getName()
|
|
// << " -> " << prop->getTypeId().getName());
|
|
// continue;
|
|
// }
|
|
try {
|
|
prop->Paste(*data.property);
|
|
}
|
|
catch (Base::Exception& e) {
|
|
e.reportException();
|
|
FC_ERR("exception while restoring " << prop->getFullName() << ": " << e.what());
|
|
}
|
|
catch (std::exception& e) {
|
|
FC_ERR("exception while restoring " << prop->getFullName() << ": " << e.what());
|
|
}
|
|
catch (...) {
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void TransactionObject::setProperty(const Property* pcProp)
|
|
{
|
|
auto& data = _PropChangeMap[pcProp->getID()];
|
|
if (!data.property && data.name.empty()) {
|
|
static_cast<DynamicProperty::PropData&>(data) =
|
|
pcProp->getContainer()->getDynamicPropertyData(pcProp);
|
|
data.propertyOrig = pcProp;
|
|
data.property = pcProp->Copy();
|
|
data.propertyType = pcProp->getTypeId();
|
|
data.property->setStatusValue(pcProp->getStatus());
|
|
}
|
|
}
|
|
|
|
void TransactionObject::renameProperty(const Property* pcProp, const char* oldName)
|
|
{
|
|
if (!pcProp || !pcProp->getContainer()) {
|
|
return;
|
|
}
|
|
|
|
auto& data = _PropChangeMap[pcProp->getID()];
|
|
|
|
if (data.name.empty()) {
|
|
static_cast<DynamicProperty::PropData&>(data) =
|
|
pcProp->getContainer()->getDynamicPropertyData(pcProp);
|
|
}
|
|
data.nameOrig = oldName;
|
|
}
|
|
|
|
void TransactionObject::addOrRemoveProperty(const Property* pcProp, bool add)
|
|
{
|
|
(void)add;
|
|
if (!pcProp || !pcProp->getContainer()) {
|
|
return;
|
|
}
|
|
|
|
auto& data = _PropChangeMap[pcProp->getID()];
|
|
if (!data.name.empty()) {
|
|
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->getID());
|
|
}
|
|
return;
|
|
}
|
|
if (data.property) {
|
|
delete data.property;
|
|
data.property = nullptr;
|
|
}
|
|
data.propertyOrig = pcProp;
|
|
static_cast<DynamicProperty::PropData&>(data) =
|
|
pcProp->getContainer()->getDynamicPropertyData(pcProp);
|
|
if (add) {
|
|
data.property = nullptr;
|
|
}
|
|
else {
|
|
data.property = pcProp->Copy();
|
|
data.propertyType = pcProp->getTypeId();
|
|
data.property->setStatusValue(pcProp->getStatus());
|
|
}
|
|
}
|
|
|
|
unsigned int TransactionObject::getMemSize() const
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
void TransactionObject::Save(Base::Writer& /*writer*/) const
|
|
{
|
|
assert(0);
|
|
}
|
|
|
|
void TransactionObject::Restore(Base::XMLReader& /*reader*/)
|
|
{
|
|
assert(0);
|
|
}
|
|
|
|
//**************************************************************************
|
|
//**************************************************************************
|
|
// TransactionDocumentObject
|
|
//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
|
|
|
TYPESYSTEM_SOURCE_ABSTRACT(App::TransactionDocumentObject, App::TransactionObject)
|
|
|
|
//**************************************************************************
|
|
// Construction/Destruction
|
|
|
|
/**
|
|
* A constructor.
|
|
* A more elaborate description of the constructor.
|
|
*/
|
|
TransactionDocumentObject::TransactionDocumentObject() = default;
|
|
|
|
/**
|
|
* A destructor.
|
|
* A more elaborate description of the destructor.
|
|
*/
|
|
TransactionDocumentObject::~TransactionDocumentObject() = default;
|
|
|
|
void TransactionDocumentObject::applyDel(Document& Doc, TransactionalObject* pcObj)
|
|
{
|
|
if (status == Del) {
|
|
DocumentObject* obj = static_cast<DocumentObject*>(pcObj);
|
|
|
|
// Make sure the backlinks of all linked objects are updated. As the links of the removed
|
|
// object are never set to [] they also do not remove the backlink. But as they are
|
|
// not in the document anymore we need to remove them anyway to ensure a correct graph
|
|
auto list = obj->getOutList();
|
|
for (auto link : list) {
|
|
link->_removeBackLink(obj);
|
|
}
|
|
|
|
// simply filling in the saved object
|
|
Doc._removeObject(obj);
|
|
}
|
|
}
|
|
|
|
void TransactionDocumentObject::applyNew(Document& Doc, TransactionalObject* pcObj)
|
|
{
|
|
if (status == New) {
|
|
DocumentObject* obj = static_cast<DocumentObject*>(pcObj);
|
|
Doc._addObject(obj, _NameInDocument.c_str());
|
|
|
|
// make sure the backlinks of all linked objects are updated
|
|
auto list = obj->getOutList();
|
|
for (auto link : list) {
|
|
link->_addBackLink(obj);
|
|
}
|
|
}
|
|
}
|
|
|
|
//**************************************************************************
|
|
//**************************************************************************
|
|
// TransactionFactory
|
|
//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
|
|
|
App::TransactionFactory* App::TransactionFactory::self = nullptr;
|
|
|
|
TransactionFactory& TransactionFactory::instance()
|
|
{
|
|
if (!self) {
|
|
self = new TransactionFactory;
|
|
}
|
|
return *self;
|
|
}
|
|
|
|
void TransactionFactory::destruct()
|
|
{
|
|
delete self;
|
|
self = nullptr;
|
|
}
|
|
|
|
void TransactionFactory::addProducer(const Base::Type& type, Base::AbstractProducer* producer)
|
|
{
|
|
producers[type] = producer;
|
|
}
|
|
|
|
/**
|
|
* Creates a transaction object for the given type id.
|
|
*/
|
|
TransactionObject* TransactionFactory::createTransaction(const Base::Type& type) const
|
|
{
|
|
std::map<Base::Type, Base::AbstractProducer*>::const_iterator it;
|
|
for (it = producers.begin(); it != producers.end(); ++it) {
|
|
if (type.isDerivedFrom(it->first)) {
|
|
return static_cast<TransactionObject*>(it->second->Produce());
|
|
}
|
|
}
|
|
|
|
assert(0);
|
|
return nullptr;
|
|
}
|