Revert "Address the poor performance of the existing unique-name generation (#17944)"

This reverts commit 83202d8ad6.

# Conflicts:
#	src/Base/Tools.cpp
#	src/Base/Tools.h
This commit is contained in:
Benjamin Nauck
2024-12-16 14:30:49 +01:00
committed by Yorik van Havre
parent 844d88fb7a
commit a2c980f7d6
28 changed files with 430 additions and 716 deletions

View File

@@ -450,16 +450,41 @@ void Application::renameDocument(const char *OldName, const char *NewName)
throw Base::RuntimeError("Renaming document internal name is no longer allowed!");
}
Document* Application::newDocument(const char * proposedName, const char * proposedLabel, bool createView, bool tempDoc)
Document* Application::newDocument(const char * Name, const char * UserName, bool createView, bool tempDoc)
{
std::string name;
bool useDefaultName = (!proposedName || proposedName[0] == '\0');
// get a valid name anyway!
if (useDefaultName) {
proposedName = "Unnamed";
}
auto getNameAndLabel = [this](const char * Name, const char * UserName) -> std::tuple<std::string, std::string> {
bool defaultName = (!Name || Name[0] == '\0');
name = getUniqueDocumentName(proposedName, tempDoc);
// get a valid name anyway!
if (defaultName) {
Name = "Unnamed";
}
std::string userName;
if (UserName && UserName[0] != '\0') {
userName = UserName;
}
else {
userName = defaultName ? QObject::tr("Unnamed").toStdString() : Name;
std::vector<std::string> names;
names.reserve(DocMap.size());
for (const auto& pos : DocMap) {
names.emplace_back(pos.second->Label.getValue());
}
if (!names.empty()) {
userName = Base::Tools::getUniqueName(userName, names);
}
}
return std::make_tuple(std::string(Name), userName);
};
auto tuple = getNameAndLabel(Name, UserName);
std::string name = std::get<0>(tuple);
std::string userName = std::get<1>(tuple);
name = getUniqueDocumentName(name.c_str(), tempDoc);
// return the temporary document if it exists
if (tempDoc) {
@@ -468,65 +493,53 @@ Document* Application::newDocument(const char * proposedName, const char * propo
return it->second;
}
// Determine the document's Label
std::string label;
if (proposedLabel && proposedLabel[0] != '\0') {
label = proposedLabel;
}
else {
label = useDefaultName ? QObject::tr("Unnamed").toStdString() : proposedName;
if (!DocMap.empty()) {
// The assumption here is that there are not many documents and
// documents are rarely created so the cost
// of building this manager each time is inconsequential
Base::UniqueNameManager names;
for (const auto& pos : DocMap) {
names.addExactName(pos.second->Label.getValue());
}
label = names.makeUniqueName(label);
}
}
// create the FreeCAD document
Document* doc = new Document(name.c_str());
doc->setStatus(Document::TempDoc, tempDoc);
std::unique_ptr<Document> newDoc(new Document(name.c_str()));
newDoc->setStatus(Document::TempDoc, tempDoc);
auto oldActiveDoc = _pActiveDoc;
auto doc = newDoc.release(); // now owned by the Application
// add the document to the internal list
DocMap[name] = doc;
_pActiveDoc = doc;
//NOLINTBEGIN
// clang-format off
// connect the signals to the application for the new document
doc->signalBeforeChange.connect(std::bind(&App::Application::slotBeforeChangeDocument, this, sp::_1, sp::_2));
doc->signalChanged.connect(std::bind(&App::Application::slotChangedDocument, this, sp::_1, sp::_2));
doc->signalNewObject.connect(std::bind(&App::Application::slotNewObject, this, sp::_1));
doc->signalDeletedObject.connect(std::bind(&App::Application::slotDeletedObject, this, sp::_1));
doc->signalBeforeChangeObject.connect(std::bind(&App::Application::slotBeforeChangeObject, this, sp::_1, sp::_2));
doc->signalChangedObject.connect(std::bind(&App::Application::slotChangedObject, this, sp::_1, sp::_2));
doc->signalRelabelObject.connect(std::bind(&App::Application::slotRelabelObject, this, sp::_1));
doc->signalActivatedObject.connect(std::bind(&App::Application::slotActivatedObject, this, sp::_1));
doc->signalUndo.connect(std::bind(&App::Application::slotUndoDocument, this, sp::_1));
doc->signalRedo.connect(std::bind(&App::Application::slotRedoDocument, this, sp::_1));
doc->signalRecomputedObject.connect(std::bind(&App::Application::slotRecomputedObject, this, sp::_1));
doc->signalRecomputed.connect(std::bind(&App::Application::slotRecomputed, this, sp::_1));
doc->signalBeforeRecompute.connect(std::bind(&App::Application::slotBeforeRecompute, this, sp::_1));
doc->signalOpenTransaction.connect(std::bind(&App::Application::slotOpenTransaction, this, sp::_1, sp::_2));
doc->signalCommitTransaction.connect(std::bind(&App::Application::slotCommitTransaction, this, sp::_1));
doc->signalAbortTransaction.connect(std::bind(&App::Application::slotAbortTransaction, this, sp::_1));
doc->signalStartSave.connect(std::bind(&App::Application::slotStartSaveDocument, this, sp::_1, sp::_2));
doc->signalFinishSave.connect(std::bind(&App::Application::slotFinishSaveDocument, this, sp::_1, sp::_2));
doc->signalChangePropertyEditor.connect(std::bind(&App::Application::slotChangePropertyEditor, this, sp::_1, sp::_2));
_pActiveDoc->signalBeforeChange.connect(std::bind(&App::Application::slotBeforeChangeDocument, this, sp::_1, sp::_2));
_pActiveDoc->signalChanged.connect(std::bind(&App::Application::slotChangedDocument, this, sp::_1, sp::_2));
_pActiveDoc->signalNewObject.connect(std::bind(&App::Application::slotNewObject, this, sp::_1));
_pActiveDoc->signalDeletedObject.connect(std::bind(&App::Application::slotDeletedObject, this, sp::_1));
_pActiveDoc->signalBeforeChangeObject.connect(std::bind(&App::Application::slotBeforeChangeObject, this, sp::_1, sp::_2));
_pActiveDoc->signalChangedObject.connect(std::bind(&App::Application::slotChangedObject, this, sp::_1, sp::_2));
_pActiveDoc->signalRelabelObject.connect(std::bind(&App::Application::slotRelabelObject, this, sp::_1));
_pActiveDoc->signalActivatedObject.connect(std::bind(&App::Application::slotActivatedObject, this, sp::_1));
_pActiveDoc->signalUndo.connect(std::bind(&App::Application::slotUndoDocument, this, sp::_1));
_pActiveDoc->signalRedo.connect(std::bind(&App::Application::slotRedoDocument, this, sp::_1));
_pActiveDoc->signalRecomputedObject.connect(std::bind(&App::Application::slotRecomputedObject, this, sp::_1));
_pActiveDoc->signalRecomputed.connect(std::bind(&App::Application::slotRecomputed, this, sp::_1));
_pActiveDoc->signalBeforeRecompute.connect(std::bind(&App::Application::slotBeforeRecompute, this, sp::_1));
_pActiveDoc->signalOpenTransaction.connect(std::bind(&App::Application::slotOpenTransaction, this, sp::_1, sp::_2));
_pActiveDoc->signalCommitTransaction.connect(std::bind(&App::Application::slotCommitTransaction, this, sp::_1));
_pActiveDoc->signalAbortTransaction.connect(std::bind(&App::Application::slotAbortTransaction, this, sp::_1));
_pActiveDoc->signalStartSave.connect(std::bind(&App::Application::slotStartSaveDocument, this, sp::_1, sp::_2));
_pActiveDoc->signalFinishSave.connect(std::bind(&App::Application::slotFinishSaveDocument, this, sp::_1, sp::_2));
_pActiveDoc->signalChangePropertyEditor.connect(std::bind(&App::Application::slotChangePropertyEditor, this, sp::_1, sp::_2));
// clang-format on
//NOLINTEND
// (temporarily) make this the active document for the upcoming notifications.
// Signal NewDocument rather than ActiveDocument
auto oldActiveDoc = _pActiveDoc;
setActiveDocumentNoSignal(doc);
signalNewDocument(*doc, createView);
// make sure that the active document is set in case no GUI is up
{
Base::PyGILStateLocker lock;
Py::Object active(_pActiveDoc->getPyObject(), true);
Py::Module("FreeCAD").setAttr(std::string("ActiveDocument"), active);
}
doc->Label.setValue(label);
signalNewDocument(*_pActiveDoc, createView);
// set the UserName after notifying all observers
_pActiveDoc->Label.setValue(userName);
// set the old document active again if the new is temporary
if (tempDoc && oldActiveDoc)
@@ -616,17 +629,13 @@ std::string Application::getUniqueDocumentName(const char *Name, bool tempDoc) c
return CleanName;
}
else {
// The assumption here is that there are not many documents and
// documents are rarely created so the cost
// of building this manager each time is inconsequential
Base::UniqueNameManager names;
for (const auto& pos : DocMap) {
if (!tempDoc || !pos.second->testStatus(Document::TempDoc)) {
names.addExactName(pos.first);
}
std::vector<std::string> names;
names.reserve(DocMap.size());
for (pos = DocMap.begin(); pos != DocMap.end(); ++pos) {
if (!tempDoc || !pos->second->testStatus(Document::TempDoc))
names.push_back(pos->first);
}
return names.makeUniqueName(CleanName);
return Base::Tools::getUniqueName(CleanName, names);
}
}
@@ -1044,14 +1053,6 @@ Document* Application::getActiveDocument() const
}
void Application::setActiveDocument(Document* pDoc)
{
setActiveDocumentNoSignal(pDoc);
if (pDoc)
signalActiveDocument(*pDoc);
}
void Application::setActiveDocumentNoSignal(Document* pDoc)
{
_pActiveDoc = pDoc;
@@ -1059,15 +1060,18 @@ void Application::setActiveDocumentNoSignal(Document* pDoc)
if (pDoc) {
Base::PyGILStateLocker lock;
Py::Object active(pDoc->getPyObject(), true);
Py::Module("FreeCAD").setAttr(std::string("ActiveDocument"), active);
Py::Module("FreeCAD").setAttr(std::string("ActiveDocument"),active);
}
else {
Base::PyGILStateLocker lock;
Py::Module("FreeCAD").setAttr(std::string("ActiveDocument"), Py::None());
Py::Module("FreeCAD").setAttr(std::string("ActiveDocument"),Py::None());
}
if (pDoc)
signalActiveDocument(*pDoc);
}
void Application::setActiveDocument(const char* Name)
void Application::setActiveDocument(const char *Name)
{
// If no active document is set, resort to a default.
if (*Name == '\0') {

View File

@@ -160,7 +160,7 @@ public:
std::vector<App::Document*> getDocuments() const;
/// Set the active document
void setActiveDocument(App::Document* pDoc);
void setActiveDocument(const char* Name);
void setActiveDocument(const char *Name);
/// close all documents (without saving)
void closeAllDocuments();
/// Add pending document to open together with the current opening document
@@ -505,8 +505,6 @@ private:
static void cleanupUnits();
void setActiveDocumentNoSignal(App::Document* pDoc);
/** @name member for parameter */
//@{
static Base::Reference<ParameterManager> _pcSysParamMngr;

View File

@@ -645,11 +645,8 @@ void Document::clearDocument()
setStatus(Document::PartialDoc, false);
d->clearRecomputeLog();
d->objectLabelCounts.clear();
d->objectLabelManager.clear();
d->objectArray.clear();
d->objectMap.clear();
d->objectNameManager.clear();
d->objectIdMap.clear();
d->lastObjectId = 0;
}
@@ -2253,59 +2250,6 @@ bool Document::saveToFile(const char* filename) const
return true;
}
void Document::registerLabel(const std::string& newLabel)
{
if (newLabel.empty()) {
return;
}
if (!d->objectLabelManager.containsName(newLabel)) {
// First occurrence of label. We make no entry in objectLabelCounts when the count is one.
d->objectLabelManager.addExactName(newLabel);
}
else {
auto it = d->objectLabelCounts.find(newLabel);
if (it != d->objectLabelCounts.end()) {
// There is a count already greater then one, so increment it
it->second++;
}
else {
// There is no count entry, which implies one, so register a count of two
d->objectLabelCounts.insert(std::pair(newLabel, 2));
}
}
}
void Document::unregisterLabel(const std::string& oldLabel)
{
if (oldLabel.empty()) {
return;
}
auto it = d->objectLabelCounts.find(oldLabel);
if (it == d->objectLabelCounts.end()) {
// Missing count implies a count of one, or an unregistered name
d->objectLabelManager.removeExactName(oldLabel);
return;
}
if (--it->second == 1) {
// Decremented to one, remove the count entry
d->objectLabelCounts.erase(it);
}
}
bool Document::containsLabel(const std::string& label)
{
return d->objectLabelManager.containsName(label);
}
std::string Document::makeUniqueLabel(const std::string& modelLabel)
{
if (modelLabel.empty()) {
return std::string();
}
return d->objectLabelManager.makeUniqueName(modelLabel, 3);
}
bool Document::isAnyRestoring()
{
return globalIsRestoring;
@@ -2332,10 +2276,7 @@ void Document::restore(const char* filename,
setStatus(Document::PartialDoc, false);
d->clearRecomputeLog();
d->objectLabelCounts.clear();
d->objectLabelManager.clear();
d->objectArray.clear();
d->objectNameManager.clear();
d->objectMap.clear();
d->objectIdMap.clear();
d->lastObjectId = 0;
@@ -3644,7 +3585,6 @@ DocumentObject* Document::addObject(const char* sType,
// insert in the name map
d->objectMap[ObjectName] = pcObject;
d->objectNameManager.addExactName(ObjectName);
// generate object id and add to id map;
pcObject->_Id = ++d->lastObjectId;
d->objectIdMap[pcObject->_Id] = pcObject;
@@ -3653,8 +3593,6 @@ DocumentObject* Document::addObject(const char* sType,
pcObject->pcNameInDocument = &(d->objectMap.find(ObjectName)->first);
// insert in the vector
d->objectArray.push_back(pcObject);
// Register the current Label even though it is (probably) about to change
registerLabel(pcObject->Label.getStrValue());
// If we are restoring, don't set the Label object now; it will be restored later. This is to
// avoid potential duplicate label conflicts later.
@@ -3715,6 +3653,13 @@ Document::addObjects(const char* sType, const std::vector<std::string>& objectNa
return objects;
}
// get all existing object names
std::vector<std::string> reservedNames;
reservedNames.reserve(d->objectMap.size());
for (const auto& pos : d->objectMap) {
reservedNames.push_back(pos.first);
}
for (auto it = objects.begin(); it != objects.end(); ++it) {
auto index = std::distance(objects.begin(), it);
App::DocumentObject* pcObject = *it;
@@ -3729,19 +3674,29 @@ Document::addObjects(const char* sType, const std::vector<std::string>& objectNa
}
}
// get unique name. We don't use getUniqueObjectName because it takes a char* not a std::string
// get unique name
std::string ObjectName = objectNames[index];
if (ObjectName.empty()) {
ObjectName = sType;
}
ObjectName = Base::Tools::getIdentifier(ObjectName);
if (d->objectNameManager.containsName(ObjectName)) {
ObjectName = d->objectNameManager.makeUniqueName(ObjectName, 3);
if (d->objectMap.find(ObjectName) != d->objectMap.end()) {
// remove also trailing digits from clean name which is to avoid to create lengthy names
// like 'Box001001'
if (!testStatus(KeepTrailingDigits)) {
std::string::size_type index = ObjectName.find_last_not_of("0123456789");
if (index + 1 < ObjectName.size()) {
ObjectName = ObjectName.substr(0, index + 1);
}
}
ObjectName = Base::Tools::getUniqueName(ObjectName, reservedNames, 3);
}
reservedNames.push_back(ObjectName);
// insert in the name map
d->objectMap[ObjectName] = pcObject;
d->objectNameManager.addExactName(ObjectName);
// generate object id and add to id map;
pcObject->_Id = ++d->lastObjectId;
d->objectIdMap[pcObject->_Id] = pcObject;
@@ -3750,8 +3705,6 @@ Document::addObjects(const char* sType, const std::vector<std::string>& objectNa
pcObject->pcNameInDocument = &(d->objectMap.find(ObjectName)->first);
// insert in the vector
d->objectArray.push_back(pcObject);
// Register the current Label even though it is about to change
registerLabel(pcObject->Label.getStrValue());
pcObject->Label.setValue(ObjectName);
@@ -3812,7 +3765,6 @@ void Document::addObject(DocumentObject* pcObject, const char* pObjectName)
// insert in the name map
d->objectMap[ObjectName] = pcObject;
d->objectNameManager.addExactName(ObjectName);
// generate object id and add to id map;
if (!pcObject->_Id) {
pcObject->_Id = ++d->lastObjectId;
@@ -3823,8 +3775,6 @@ void Document::addObject(DocumentObject* pcObject, const char* pObjectName)
pcObject->pcNameInDocument = &(d->objectMap.find(ObjectName)->first);
// insert in the vector
d->objectArray.push_back(pcObject);
// Register the current Label even though it is about to change
registerLabel(pcObject->Label.getStrValue());
pcObject->Label.setValue(ObjectName);
@@ -3848,14 +3798,12 @@ void Document::_addObject(DocumentObject* pcObject, const char* pObjectName)
{
std::string ObjectName = getUniqueObjectName(pObjectName);
d->objectMap[ObjectName] = pcObject;
d->objectNameManager.addExactName(ObjectName);
// generate object id and add to id map;
if (!pcObject->_Id) {
pcObject->_Id = ++d->lastObjectId;
}
d->objectIdMap[pcObject->_Id] = pcObject;
d->objectArray.push_back(pcObject);
registerLabel(pcObject->Label.getStrValue());
// cache the pointer to the name string in the Object (for performance of
// DocumentObject::getNameInDocument())
pcObject->pcNameInDocument = &(d->objectMap.find(ObjectName)->first);
@@ -3884,15 +3832,6 @@ void Document::_addObject(DocumentObject* pcObject, const char* pObjectName)
signalActivatedObject(*pcObject);
}
bool Document::containsObject(const DocumentObject* pcObject) const
{
// We could look for the object in objectMap (keyed by object name),
// or search in objectArray (a O(n) vector search) but looking by Id
// in objectIdMap would be fastest.
auto found = d->objectIdMap.find(pcObject->getID());
return found != d->objectIdMap.end() && found->second == pcObject;
}
/// Remove an object out of the document
void Document::removeObject(const char* sName)
{
@@ -3980,7 +3919,6 @@ void Document::removeObject(const char* sName)
}
}
unregisterLabel(pos->second->Label.getStrValue());
for (std::vector<DocumentObject*>::iterator obj = d->objectArray.begin();
obj != d->objectArray.end();
++obj) {
@@ -3994,7 +3932,6 @@ void Document::removeObject(const char* sName)
if (tobedestroyed) {
tobedestroyed->pcNameInDocument = nullptr;
}
d->objectNameManager.removeExactName(pos->first);
d->objectMap.erase(pos);
}
@@ -4064,8 +4001,6 @@ void Document::_removeObject(DocumentObject* pcObject)
// remove from map
pcObject->setStatus(ObjectStatus::Remove, false); // Unset the bit to be on the safe side
d->objectIdMap.erase(pcObject->_Id);
d->objectNameManager.removeExactName(pos->first);
unregisterLabel(pos->second->Label.getStrValue());
d->objectMap.erase(pos);
for (std::vector<DocumentObject*>::iterator it = d->objectArray.begin();
@@ -4342,29 +4277,50 @@ const char* Document::getObjectName(DocumentObject* pFeat) const
return nullptr;
}
std::string Document::getUniqueObjectName(const char* proposedName) const
std::string Document::getUniqueObjectName(const char* Name) const
{
if (!proposedName || *proposedName == '\0') {
if (!Name || *Name == '\0') {
return {};
}
std::string cleanName = Base::Tools::getIdentifier(proposedName);
std::string CleanName = Base::Tools::getIdentifier(Name);
if (!d->objectNameManager.containsName(cleanName)) {
// Not in use yet, name is OK
return cleanName;
// name in use?
auto pos = d->objectMap.find(CleanName);
if (pos == d->objectMap.end()) {
// if not, name is OK
return CleanName;
}
else {
// remove also trailing digits from clean name which is to avoid to create lengthy names
// like 'Box001001'
if (!testStatus(KeepTrailingDigits)) {
std::string::size_type index = CleanName.find_last_not_of("0123456789");
if (index + 1 < CleanName.size()) {
CleanName = CleanName.substr(0, index + 1);
}
}
std::vector<std::string> names;
names.reserve(d->objectMap.size());
for (pos = d->objectMap.begin(); pos != d->objectMap.end(); ++pos) {
names.push_back(pos->first);
}
return Base::Tools::getUniqueName(CleanName, names, 3);
}
return d->objectNameManager.makeUniqueName(cleanName, 3);
}
std::tuple<uint, uint>
Document::decomposeName(const std::string& name, std::string& baseName, std::string& nameExtension)
std::string Document::getStandardObjectName(const char* Name, int d) const
{
return d->objectNameManager.decomposeName(name, baseName, nameExtension);
}
std::vector<App::DocumentObject*> mm = getObjects();
std::vector<std::string> labels;
labels.reserve(mm.size());
std::string Document::getStandardObjectLabel(const char* modelName, int digitCount) const
{
return d->objectLabelManager.makeUniqueName(modelName, digitCount);
for (auto it : mm) {
std::string label = it->Label.getValue();
labels.push_back(label);
}
return Base::Tools::getUniqueName(Name, labels, d);
}
std::vector<DocumentObject*> Document::getDependingObjects() const

View File

@@ -287,10 +287,6 @@ public:
*/
void addObject(DocumentObject*, const char* pObjectName = nullptr);
/// returns whether this is actually contains the DocumentObject.
/// Testing the DocumentObject's pDoc pointer is not sufficient because the object
/// removeObject and _removeObject leave _pDoc unchanged
bool containsObject(const DocumentObject*) const;
/** Copy objects from another document to this document
*
@@ -322,14 +318,11 @@ public:
/// Returns true if the DocumentObject is contained in this document
bool isIn(const DocumentObject* pFeat) const;
/// Returns a Name of an Object or 0
const char *getObjectName(DocumentObject* pFeat) const;
/// Returns a Name for a new Object or empty if proposedName is null or empty.
std::string getUniqueObjectName(const char* proposedName) const;
/// Returns a name different from any of the Labels of any objects in this document, based on the given modelName.
std::string getStandardObjectLabel(const char* modelName, int d) const;
/// Break an object Name or Label into its base parts, returning tuple(digitCount, digitsValue)
std::tuple<uint, uint>
decomposeName(const std::string& name, std::string& baseName, std::string& nameExtension);
const char* getObjectName(DocumentObject* pFeat) const;
/// Returns a Name of an Object or 0
std::string getUniqueObjectName(const char* Name) const;
/// Returns a name of the form prefix_number. d specifies the number of digits.
std::string getStandardObjectName(const char* Name, int d) const;
/// Returns a list of document's objects including the dependencies
std::vector<DocumentObject*> getDependingObjects() const;
/// Returns a list of all Objects
@@ -574,11 +567,6 @@ public:
/// Indicate if there is any document restoring/importing
static bool isAnyRestoring();
void registerLabel(const std ::string& newLabel);
void unregisterLabel(const std::string& oldLabel);
bool containsLabel(const std::string& label);
std::string makeUniqueLabel(const std::string& modelLabel);
friend class Application;
/// because of transaction handling
friend class TransactionalObject;

View File

@@ -814,74 +814,6 @@ void DocumentObject::onBeforeChange(const Property* prop)
signalBeforeChange(*this, *prop);
}
std::vector<std::pair<Property*, std::unique_ptr<Property>>>
DocumentObject::onProposedLabelChange(std::string& newLabel)
{
// Note that this work can't be done in onBeforeChangeLabel because FeaturePython overrides this
// method and does not initially base-call it.
// We re only called if the new label differs from the old one, and our code to check duplicates
// may not work if this is not the case.
std::string oldLabel = Label.getStrValue();
assert(newLabel != oldLabel);
std::string label;
if (!isAttachedToDocument()
|| (getDocument()->testStatus(App::Document::Restoring)
&& !getDocument()->testStatus(App::Document::Importing))
|| getDocument()->isPerformingTransaction()) {
return {};
}
static ParameterGrp::handle _hPGrp;
if (!_hPGrp) {
_hPGrp = GetApplication().GetUserParameter().GetGroup("BaseApp");
_hPGrp = _hPGrp->GetGroup("Preferences")->GetGroup("Document");
}
App::Document* doc = getDocument();
if (doc && newLabel.size() > 0 && !_hPGrp->GetBool("DuplicateLabels") && !allowDuplicateLabel()
&& doc->containsLabel(newLabel)) {
// We must ensure the Label is unique in the document (well, sort of...).
// If the base name of the proposed label equals the base name of the object Name, we use
// the (uniquefied) object Name, which could actually be identical to another object's Label,
// but probably isn't.
// Otherwise we generate a unique Label using newLabel as a prototype name. In doing so,
// we must also act as if the current value of the property is not an existing Label entry.
std::string objName = getNameInDocument();
std::string objBaseName;
std::string objSuffix;
doc->decomposeName(objName, objBaseName, objSuffix);
std::string newBaseName;
std::string newSuffix;
doc->decomposeName(newLabel, newBaseName, newSuffix);
if (newBaseName == objBaseName && newSuffix == objSuffix) {
newLabel = objName;
}
else {
// We deregister the old label so it does not interfere with making the new label,
// and re-register it after. This is probably a bit less efficient that having a special
// make-unique-label-as-if-this-one-did-not-exist method, but such a method would be a real
// ugly wart.
doc->unregisterLabel(oldLabel);
newLabel = doc->makeUniqueLabel(newLabel);
doc->registerLabel(oldLabel);
}
}
// Despite our efforts to make a unique label, onBeforeLabelChange can change it.
onBeforeChangeLabel(newLabel);
if (oldLabel == newLabel || getDocument()->testStatus(App::Document::Restoring)) {
// Don't update label reference if we are restoring or if the label is unchanged.
// When importing (which also counts as restoring), it is possible the
// new object changes its label. However, we cannot update label
// references here, because object being restored is not based on
// dependency order. It can only be done in afterRestore().
//
// See PropertyLinkBase::restoreLabelReference() for more details.
return {};
}
return PropertyLinkBase::updateLabelReferences(this, newLabel.c_str());
}
void DocumentObject::onEarlyChange(const Property* prop)
{
if (GetApplication().isClosingAll()) {
@@ -904,11 +836,6 @@ void DocumentObject::onEarlyChange(const Property* prop)
/// get called by the container when a Property was changed
void DocumentObject::onChanged(const Property* prop)
{
if (prop == &Label && _pDoc && _pDoc->containsObject(this) && oldLabel != Label.getStrValue()) {
_pDoc->unregisterLabel(oldLabel);
_pDoc->registerLabel(Label.getStrValue());
}
if (isFreezed() && prop != &Visibility) {
return;
}

View File

@@ -513,14 +513,7 @@ public:
{
return false;
}
/// Handle Label changes, including forcing unique label values,
/// signalling OnBeforeLabelChange, and arranging to update linked references,
/// on the assumption that after returning the label will indeed be changed to
/// the (altered) value of newLabel.
/// Returns a vector of referenging (linking) properties as produced by
/// PropertyLinkBase::updateLabelReferences which is needed for undo/redo purposes.
std::vector<std::pair<Property*, std::unique_ptr<Property>>>
onProposedLabelChange(std::string& newLabel);
/*** Called to let object itself control relabeling
*
* @param newLabel: input as the new label, which can be modified by object itself
@@ -772,10 +765,10 @@ protected: // attributes
/// Old label; used for renaming expressions
std::string oldLabel;
private:
// pointer to the document name string (for performance)
const std::string* pcNameInDocument {nullptr};
private:
// accessed by App::Document to record and restore the correct view provider type
std::string _pcViewProviderName;

View File

@@ -70,13 +70,7 @@ void DynamicProperty::getPropertyNamedList(
}
}
void DynamicProperty::visitProperties(std::function<void(Property*)> visitor) const {
for (auto& v : props.get<0>()) {
visitor(v.property);
}
}
void DynamicProperty::getPropertyMap(std::map<std::string,Property*>& Map) const
void DynamicProperty::getPropertyMap(std::map<std::string, Property*>& Map) const
{
for (auto& v : props.get<0>()) {
Map[v.name] = v.property;
@@ -304,21 +298,25 @@ bool DynamicProperty::removeDynamicProperty(const char* name)
std::string DynamicProperty::getUniquePropertyName(PropertyContainer& pc, const char* Name) const
{
std::string cleanName = Base::Tools::getIdentifier(Name);
std::string CleanName = Base::Tools::getIdentifier(Name);
// We test if the property already exists by finding it, which is not much more expensive than
// having a separate propertyExists(name) method. This avoids building the UniqueNameManager
// (which could also tell if the name exists) except in the relatively rare condition of
// the name already existing.
if (pc.getPropertyByName(cleanName.c_str()) == nullptr) {
return cleanName;
// name in use?
std::map<std::string, Property*> objectProps;
pc.getPropertyMap(objectProps);
auto pos = objectProps.find(CleanName);
if (pos == objectProps.end()) {
// if not, name is OK
return CleanName;
}
else {
std::vector<std::string> names;
names.reserve(objectProps.size());
for (pos = objectProps.begin(); pos != objectProps.end(); ++pos) {
names.push_back(pos->first);
}
return Base::Tools::getUniqueName(CleanName, names);
}
Base::UniqueNameManager names;
// Build the index of existing names.
pc.visitProperties([&](Property* prop) {
names.addExactName(prop->getName());
});
return names.makeUniqueName(cleanName);
}
void DynamicProperty::save(const Property* prop, Base::Writer& writer) const

View File

@@ -87,8 +87,6 @@ public:
void getPropertyList(std::vector<Property*>& List) const;
/// get all properties with their names
void getPropertyNamedList(std::vector<std::pair<const char*, Property*>>& List) const;
/// See PropertyContainer::visitProperties for semantics
void visitProperties(std::function<void(Property*)> visitor) const;
/// Get all properties of the class (including parent)
void getPropertyMap(std::map<std::string, Property*>& Map) const;
/// Find a dynamic property by its name

View File

@@ -181,10 +181,6 @@ void Extension::extensionGetPropertyMap(std::map<std::string, Property*>& Map) c
extensionGetPropertyData().getPropertyMap(this, Map);
}
void Extension::extensionVisitProperties(std::function<void(Property*)> visitor) const
{
extensionGetPropertyData().visitProperties(this, visitor);
}
void Extension::initExtensionSubclass(Base::Type& toInit,
const char* ClassName,
const char* ParentName,

View File

@@ -254,8 +254,6 @@ public:
virtual const char* extensionGetPropertyName(const Property* prop) const;
/// get all properties of the class (including properties of the parent)
virtual void extensionGetPropertyMap(std::map<std::string,Property*> &Map) const;
/// See PropertyContainer::visitProperties for semantics
virtual void extensionVisitProperties(std::function<void(Property*)> visitor) const;
/// get all properties of the class (including properties of the parent)
virtual void extensionGetPropertyList(std::vector<Property*> &List) const;

View File

@@ -176,13 +176,6 @@ void ExtensionContainer::getPropertyMap(std::map<std::string, Property*>& Map) c
entry.second->extensionGetPropertyMap(Map);
}
}
void ExtensionContainer::visitProperties(std::function<void(Property*)> visitor) const
{
App::PropertyContainer::visitProperties(visitor);
for(const auto &entry : _extensions) {
entry.second->extensionVisitProperties(visitor);
};
}
Property* ExtensionContainer::getPropertyByName(const char* name) const
{

View File

@@ -182,8 +182,6 @@ public:
const char* getPropertyName(const Property* prop) const override;
/// get all properties of the class (including properties of the parent)
void getPropertyMap(std::map<std::string, Property*>& Map) const override;
/// See PropertyContainer::visitProperties for semantics
void visitProperties(std::function<void(Property*)> visitor) const override;
/// get all properties of the class (including properties of the parent)
void getPropertyList(std::vector<Property*>& List) const override;

View File

@@ -92,12 +92,6 @@ void PropertyContainer::getPropertyList(std::vector<Property*> &List) const
getPropertyData().getPropertyList(this,List);
}
void PropertyContainer::visitProperties(std::function<void(Property *)> visitor) const
{
dynamicProps.visitProperties(visitor);
getPropertyData().visitProperties(this, visitor);
}
void PropertyContainer::getPropertyNamedList(std::vector<std::pair<const char*, Property*> > &List) const
{
dynamicProps.getPropertyNamedList(List);
@@ -625,15 +619,7 @@ void PropertyData::getPropertyNamedList(OffsetBase offsetBase,
}
}
void PropertyData::visitProperties(OffsetBase offsetBase,
std::function<void(Property*)> visitor) const
{
merge();
char* offset = offsetBase.getOffset();
for (const auto& spec : propertyData.get<0>()) {
visitor(reinterpret_cast<Property*>(spec.Offset + offset));
};
}
/** \defgroup PropFrame Property framework
\ingroup APP

View File

@@ -134,8 +134,6 @@ struct AppExport PropertyData
void getPropertyMap(OffsetBase offsetBase,std::map<std::string,Property*> &Map) const;
void getPropertyList(OffsetBase offsetBase,std::vector<Property*> &List) const;
void getPropertyNamedList(OffsetBase offsetBase, std::vector<std::pair<const char*,Property*> > &List) const;
/// See PropertyContainer::visitProperties for semantics
void visitProperties(OffsetBase offsetBase, std::function<void(Property*)> visitor) const;
void merge(PropertyData *other=nullptr) const;
void split(PropertyData *other);
@@ -174,11 +172,6 @@ public:
virtual void getPropertyMap(std::map<std::string,Property*> &Map) const;
/// get all properties of the class (including properties of the parent)
virtual void getPropertyList(std::vector<Property*> &List) const;
/// Call the given visitor for each property. The visiting order is undefined.
/// This method is necessary because PropertyContainer has no begin and end methods
/// and it is not practical to implement these.
/// What gets visited is undefined if the collection of Properties is changed during this call.
virtual void visitProperties(std::function<void(Property*)> visitor) const;
/// get all properties with their names, may contain duplicates and aliases
virtual void getPropertyNamedList(std::vector<std::pair<const char*,Property*> > &List) const;
/// set the Status bit of all properties at once

View File

@@ -1385,13 +1385,101 @@ void PropertyString::setValue(const char* newLabel)
return;
}
std::string _newLabel;
std::vector<std::pair<Property*, std::unique_ptr<Property>>> propChanges;
std::string label = newLabel;
std::string label;
auto obj = dynamic_cast<DocumentObject*>(getContainer());
bool commit = false;
if (obj && this == &obj->Label) {
propChanges = obj->onProposedLabelChange(label);
if (obj && obj->isAttachedToDocument() && this == &obj->Label
&& (!obj->getDocument()->testStatus(App::Document::Restoring)
|| obj->getDocument()->testStatus(App::Document::Importing))
&& !obj->getDocument()->isPerformingTransaction()) {
// allow object to control label change
static ParameterGrp::handle _hPGrp;
if (!_hPGrp) {
_hPGrp = GetApplication().GetUserParameter().GetGroup("BaseApp");
_hPGrp = _hPGrp->GetGroup("Preferences")->GetGroup("Document");
}
App::Document* doc = obj->getDocument();
if (doc && !_hPGrp->GetBool("DuplicateLabels") && !obj->allowDuplicateLabel()) {
std::vector<std::string> objectLabels;
std::vector<App::DocumentObject*>::const_iterator it;
std::vector<App::DocumentObject*> objs = doc->getObjects();
bool match = false;
for (it = objs.begin(); it != objs.end(); ++it) {
if (*it == obj) {
continue; // don't compare object with itself
}
std::string objLabel = (*it)->Label.getValue();
if (!match && objLabel == newLabel) {
match = true;
}
objectLabels.push_back(objLabel);
}
// make sure that there is a name conflict otherwise we don't have to do anything
if (match && *newLabel) {
label = newLabel;
// remove number from end to avoid lengthy names
size_t lastpos = label.length() - 1;
while (label[lastpos] >= 48 && label[lastpos] <= 57) {
// if 'lastpos' becomes 0 then all characters are digits. In this case we use
// the complete label again
if (lastpos == 0) {
lastpos = label.length() - 1;
break;
}
lastpos--;
}
bool changed = false;
label = label.substr(0, lastpos + 1);
if (label != obj->getNameInDocument()
&& boost::starts_with(obj->getNameInDocument(), label)) {
// In case the label has the same base name as object's
// internal name, use it as the label instead.
const char* objName = obj->getNameInDocument();
const char* c = &objName[lastpos + 1];
for (; *c; ++c) {
if (*c < 48 || *c > 57) {
break;
}
}
if (*c == 0
&& std::find(objectLabels.begin(),
objectLabels.end(),
obj->getNameInDocument())
== objectLabels.end()) {
label = obj->getNameInDocument();
changed = true;
}
}
if (!changed) {
label = Base::Tools::getUniqueName(label, objectLabels, 3);
}
}
}
if (label.empty()) {
label = newLabel;
}
obj->onBeforeChangeLabel(label);
newLabel = label.c_str();
if (!obj->getDocument()->testStatus(App::Document::Restoring)) {
// Only update label reference if we are not restoring. When
// importing (which also counts as restoring), it is possible the
// new object changes its label. However, we cannot update label
// references here, because object restoring is not based on
// dependency order. It can only be done in afterRestore().
//
// See PropertyLinkBase::restoreLabelReference() for more details.
propChanges = PropertyLinkBase::updateLabelReferences(obj, newLabel);
}
if (!propChanges.empty() && !GetApplication().getActiveTransaction()) {
commit = true;
std::ostringstream str;
@@ -1401,7 +1489,7 @@ void PropertyString::setValue(const char* newLabel)
}
aboutToSetValue();
_cValue = label;
_cValue = newLabel;
hasSetValue();
for (auto& change : propChanges) {

View File

@@ -30,7 +30,6 @@
#include <App/DocumentObject.h>
#include <App/DocumentObserver.h>
#include <App/StringHasher.h>
#include <Base/Tools.h>
#include <CXX/Objects.hxx>
#include <boost/bimap.hpp>
#include <boost/graph/adjacency_list.hpp>
@@ -66,9 +65,6 @@ struct DocumentP
std::vector<DocumentObject*> objectArray;
std::unordered_set<App::DocumentObject*> touchedObjs;
std::unordered_map<std::string, DocumentObject*> objectMap;
Base::UniqueNameManager objectNameManager;
Base::UniqueNameManager objectLabelManager;
std::unordered_map<std::string, int> objectLabelCounts;
std::unordered_map<long, DocumentObject*> objectIdMap;
std::unordered_map<std::string, bool> partialLoadObjects;
std::vector<DocumentObjectT> pendingRemove;
@@ -133,8 +129,6 @@ struct DocumentP
void clearDocument()
{
objectLabelCounts.clear();
objectLabelManager.clear();
objectArray.clear();
for (auto& v : objectMap) {
v.second->setStatus(ObjectStatus::Destroy, true);
@@ -142,7 +136,6 @@ struct DocumentP
v.second = nullptr;
}
objectMap.clear();
objectNameManager.clear();
objectIdMap.clear();
}

View File

@@ -463,13 +463,14 @@ const char* Base::XMLReader::addFile(const char* Name, Base::Persistence* Object
temp.Object = Object;
FileList.push_back(temp);
FileNames.push_back(temp.FileName);
return Name;
}
bool Base::XMLReader::hasFilenames() const
const std::vector<std::string>& Base::XMLReader::getFilenames() const
{
return FileList.size() > 0;
return FileNames;
}
bool Base::XMLReader::hasReadFailed(const std::string& filename) const

View File

@@ -251,8 +251,8 @@ public:
const char* addFile(const char* Name, Base::Persistence* Object);
/// process the requested file writes
void readFiles(zipios::ZipInputStream& zipstream) const;
/// Returns whether reader has any registered filenames
bool hasFilenames() const;
/// get all registered file names
const std::vector<std::string>& getFilenames() const;
/// returns true if reading the file \a filename has failed
bool hasReadFailed(const std::string& filename) const;
bool isRegistered(Base::Persistence* Object) const;
@@ -364,6 +364,7 @@ public:
std::vector<FileEntry> FileList;
private:
std::vector<std::string> FileNames;
mutable std::vector<std::string> FailedFiles;
std::bitset<32> StatusBits;

View File

@@ -33,214 +33,130 @@
#include "Interpreter.h"
#include "Tools.h"
void Base::UniqueNameManager::PiecewiseSparseIntegerSet::Add(uint value)
namespace Base
{
etype newSpan(value, 1);
iterator above = Spans.lower_bound(newSpan);
if (above != Spans.end() && above->first <= value) {
// The found span includes value so there is nothing to do as it is already in the set.
return;
struct string_comp
{
// s1 and s2 must be numbers represented as string
bool operator()(const std::string& s1, const std::string& s2)
{
if (s1.size() < s2.size()) {
return true;
}
if (s1.size() > s2.size()) {
return false;
}
return s1 < s2;
}
static std::string increment(const std::string& s)
{
std::string n = s;
int addcarry = 1;
for (std::string::reverse_iterator it = n.rbegin(); it != n.rend(); ++it) {
if (addcarry == 0) {
break;
}
int d = *it - 48;
d = d + addcarry;
*it = ((d % 10) + 48);
addcarry = d / 10;
}
if (addcarry > 0) {
std::string b;
b.resize(1);
b[0] = addcarry + 48;
n = b + n;
}
return n;
}
};
class unique_name
{
public:
unique_name(std::string name, const std::vector<std::string>& names, int padding)
: base_name {std::move(name)}
, padding {padding}
{
removeDigitsFromEnd();
findHighestSuffix(names);
}
// Set below to the next span down, if any
iterator below;
if (above == Spans.begin()) {
below = Spans.end();
}
else {
below = above;
--below;
std::string get() const
{
return appendSuffix();
}
if (above != Spans.end() && below != Spans.end()
&& above->first - below->first + 1 == below->second) {
// below and above have a gap of exactly one between them, and this must be value
// so we coalesce the two spans (and the gap) into one.
newSpan = etype(below->first, below->second + above->second + 1);
Spans.erase(above);
above = Spans.erase(below);
private:
void removeDigitsFromEnd()
{
std::string::size_type pos = base_name.find_last_not_of("0123456789");
if (pos != std::string::npos && (pos + 1) < base_name.size()) {
num_suffix = base_name.substr(pos + 1);
base_name.erase(pos + 1);
}
}
if (below != Spans.end() && value - below->first == below->second) {
// value is adjacent to the end of below, so just expand below by one
newSpan = etype(below->first, below->second + 1);
above = Spans.erase(below);
void findHighestSuffix(const std::vector<std::string>& names)
{
for (const auto& name : names) {
if (name.substr(0, base_name.length()) == base_name) { // same prefix
std::string suffix(name.substr(base_name.length()));
if (!suffix.empty()) {
std::string::size_type pos = suffix.find_first_not_of("0123456789");
if (pos == std::string::npos) {
num_suffix = std::max<std::string>(num_suffix, suffix, Base::string_comp());
}
}
}
}
}
else if (above != Spans.end() && above->first - value == 1) {
// value is adjacent to the start of above, so juse expand above down by one
newSpan = etype(above->first - 1, above->second + 1);
above = Spans.erase(above);
std::string appendSuffix() const
{
std::stringstream str;
str << base_name;
if (padding > 0) {
str.fill('0');
str.width(padding);
}
str << Base::string_comp::increment(num_suffix);
return str.str();
}
// else value is not adjacent to any existing span, so just make anew span for it
Spans.insert(above, newSpan);
}
void Base::UniqueNameManager::PiecewiseSparseIntegerSet::Remove(uint value)
private:
std::string num_suffix;
std::string base_name;
int padding;
};
} // namespace Base
std::string
Base::Tools::getUniqueName(const std::string& name, const std::vector<std::string>& names, int pad)
{
etype newSpan(value, 1);
iterator at = Spans.lower_bound(newSpan);
if (at == Spans.end() || at->first > value) {
// The found span does not include value so there is nothing to do, as it is already not in
// the set.
return;
if (names.empty()) {
return name;
}
if (at->second == 1) {
// value is the only in this span, just remove the span
Spans.erase(at);
}
else if (at->first == value) {
// value is the first in this span, trim the lower end
etype replacement(at->first + 1, at->second - 1);
Spans.insert(Spans.erase(at), replacement);
}
else if (value - at->first == at->second - 1) {
// value is the last in this span, trim the upper end
etype replacement(at->first, at->second - 1);
Spans.insert(Spans.erase(at), replacement);
}
else {
// value is in the moddle of the span, so we must split it.
etype firstReplacement(at->first, value - at->first);
etype secondReplacement(value + 1, at->second - ((value + 1) - at->first));
// Because erase returns the iterator after the erased element, and insert returns the
// iterator for the inserted item, we want to insert secondReplacement first.
Spans.insert(Spans.insert(Spans.erase(at), secondReplacement), firstReplacement);
}
}
bool Base::UniqueNameManager::PiecewiseSparseIntegerSet::Contains(uint value) const
{
iterator at = Spans.lower_bound(etype(value, 1));
return at != Spans.end() && at->first <= value;
Base::unique_name unique(name, names, pad);
return unique.get();
}
std::tuple<uint, uint> Base::UniqueNameManager::decomposeName(const std::string& name,
std::string& baseNameOut,
std::string& nameSuffixOut) const
std::string Base::Tools::addNumber(const std::string& name, unsigned int num, int d)
{
auto suffixStart = std::make_reverse_iterator(GetNameSuffixStartPosition(name));
nameSuffixOut = name.substr(name.crend() - suffixStart);
auto digitsStart = std::find_if_not(suffixStart, name.crend(), [](char c) {
return std::isdigit(c);
});
baseNameOut = name.substr(0, name.crend() - digitsStart);
uint digitCount = digitsStart - suffixStart;
if (digitCount == 0) {
// No digits in name
return std::tuple<uint, uint> {0, 0};
std::stringstream str;
str << name;
if (d > 0) {
str.fill('0');
str.width(d);
}
else {
return std::tuple<uint, uint> {
digitCount,
std::stoul(name.substr(name.crend() - digitsStart, digitCount))};
}
}
void Base::UniqueNameManager::addExactName(const std::string& name)
{
std::string baseName;
std::string nameSuffix;
uint digitCount;
uint digitsValue;
std::tie(digitCount, digitsValue) = decomposeName(name, baseName, nameSuffix);
baseName += nameSuffix;
auto baseNameEntry = UniqueSeeds.find(baseName);
if (baseNameEntry == UniqueSeeds.end()) {
// First use of baseName
baseNameEntry =
UniqueSeeds.emplace(baseName, std::vector<PiecewiseSparseIntegerSet>()).first;
}
if (digitCount >= baseNameEntry->second.size()) {
// First use of this digitCount
baseNameEntry->second.resize(digitCount + 1);
}
PiecewiseSparseIntegerSet& baseNameAndDigitCountEntry = baseNameEntry->second[digitCount];
// Name should not already be there
assert(!baseNameAndDigitCountEntry.Contains(digitsValue));
baseNameAndDigitCountEntry.Add(digitsValue);
}
std::string Base::UniqueNameManager::makeUniqueName(const std::string& modelName,
std::size_t minDigits) const
{
std::string namePrefix;
std::string nameSuffix;
decomposeName(modelName, namePrefix, nameSuffix);
std::string baseName = namePrefix + nameSuffix;
auto baseNameEntry = UniqueSeeds.find(baseName);
if (baseNameEntry == UniqueSeeds.end()) {
// First use of baseName, just return it with no unique digits
return baseName;
}
// We don't care about the digit count of the suggested name, we always use at least the most
// digits ever used before.
std::size_t digitCount = baseNameEntry->second.size() - 1;
uint digitsValue;
if (digitCount < minDigits) {
// Caller is asking for more digits than we have in any registered name.
// We start the longer digit string at 000...0001 even though we might have shorter strings
// with larger numeric values.
digitCount = minDigits;
digitsValue = 1;
}
else {
digitsValue = baseNameEntry->second[digitCount].Next();
}
std::string digits = std::to_string(digitsValue);
if (digitCount > digits.size()) {
namePrefix += std::string(digitCount - digits.size(), '0');
}
return namePrefix + digits + nameSuffix;
str << num;
return str.str();
}
void Base::UniqueNameManager::removeExactName(const std::string& name)
{
std::string baseName;
std::string nameSuffix;
uint digitCount;
uint digitsValue;
std::tie(digitCount, digitsValue) = decomposeName(name, baseName, nameSuffix);
baseName += nameSuffix;
auto baseNameEntry = UniqueSeeds.find(baseName);
if (baseNameEntry == UniqueSeeds.end()) {
// name must not be registered, so nothing to do.
return;
}
auto& digitValueSets = baseNameEntry->second;
if (digitCount >= digitValueSets.size()) {
// First use of this digitCount, name must not be registered, so nothing to do.
return;
}
digitValueSets[digitCount].Remove(digitsValue);
// an element of digitValueSets may now be newly empty and so may other elements below it
// Prune off all such trailing empty entries.
auto lastNonemptyEntry =
std::find_if(digitValueSets.crbegin(), digitValueSets.crend(), [](auto& it) {
return it.Any();
});
if (lastNonemptyEntry == digitValueSets.crend()) {
// All entries are empty, so the entire baseName can be forgotten.
UniqueSeeds.erase(baseName);
}
else {
digitValueSets.resize(digitValueSets.crend() - lastNonemptyEntry);
}
}
bool Base::UniqueNameManager::containsName(const std::string& name) const
{
std::string baseName;
std::string nameSuffix;
uint digitCount;
uint digitsValue;
std::tie(digitCount, digitsValue) = decomposeName(name, baseName, nameSuffix);
baseName += nameSuffix;
auto baseNameEntry = UniqueSeeds.find(baseName);
if (baseNameEntry == UniqueSeeds.end()) {
// base name is not registered
return false;
}
if (digitCount >= baseNameEntry->second.size()) {
// First use of this digitCount, name must not be registered, so not in collection
return false;
}
return baseNameEntry->second[digitCount].Contains(digitsValue);
}
std::string Base::Tools::getIdentifier(const std::string& name)
{
if (name.empty()) {

View File

@@ -33,7 +33,6 @@
#include <iostream>
#include <vector>
#include <string>
#include <set>
#include <boost_signals2.hpp>
#include <QString>
@@ -265,100 +264,11 @@ public:
// ----------------------------------------------------------------------------
class BaseExport UniqueNameManager
{
protected:
// This method returns the position of the start of the suffix (or name.cend() if no
// suffix). It must return the same suffix lentgh (name.size() - returnValue) for both
// unique names (one containing digits) and the corresponding base name (with no digits).
virtual std::string::const_iterator GetNameSuffixStartPosition(const std::string& name) const
{
return name.cend();
}
private:
class PiecewiseSparseIntegerSet
{
public:
PiecewiseSparseIntegerSet()
{}
private:
// Each pair being <lowest, count> represents the span of integers from lowest to
// (lowest+count-1) inclusive
using etype = std::pair<uint, uint>;
// This span comparer class is analogous to std::less and treats overlapping spans as being
// neither greater nor less than each other
class comparer
{
public:
bool operator()(const etype& lhs, const etype& rhs) const
{
// The equality case here is when lhs is below and directly adjacent to rhs.
return rhs.first - lhs.first >= lhs.second;
}
};
// Spans is the set of spans. Adjacent spans are coalesced so there are always gaps between
// the entries.
std::set<etype, comparer> Spans;
using iterator = typename std::set<etype, comparer>::iterator;
using const_iterator = typename std::set<etype, comparer>::const_iterator;
public:
void Add(uint value);
void Remove(uint value);
bool Contains(uint value) const;
bool Any() const
{
return Spans.size() != 0;
}
void Clear()
{
Spans.clear();
}
uint Next() const
{
if (Spans.size() == 0) {
return 0;
}
iterator last = Spans.end();
--last;
return last->first + last->second;
}
};
// Keyed as UniqueSeeds[baseName][digitCount][digitValue] iff that seed is taken.
// We need the double-indexing so that Name01 and Name001 can both be indexed, although we only
// ever allocate off the longest for each name i.e. UniqueSeeds[baseName].size()-1 digits.
std::map<std::string, std::vector<PiecewiseSparseIntegerSet>> UniqueSeeds;
public:
std::tuple<uint, uint> decomposeName(const std::string& name,
std::string& baseNameOut,
std::string& nameSuffixOut) const;
UniqueNameManager()
{}
// Register a name in the collection. It is an error (detected only by assertions) to register a
// name more than once. The effect if undetected is that the second registration will have no
// effect
void addExactName(const std::string& name);
std::string makeUniqueName(const std::string& modelName, std::size_t minDigits = 0) const;
// Remove a registered name so it can be generated again.
// Nothing happens if you try to remove a non-registered name.
void removeExactName(const std::string& name);
bool containsName(const std::string& name) const;
void clear()
{
UniqueSeeds.clear();
}
};
struct BaseExport Tools
{
static std::string
getUniqueName(const std::string&, const std::vector<std::string>&, int d = 0);
static std::string addNumber(const std::string&, unsigned int, int d = 0);
static std::string getIdentifier(const std::string&);
static std::wstring widen(const std::string& str);
static std::string narrow(const std::wstring& str);

View File

@@ -247,19 +247,56 @@ std::string Writer::addFile(const char* Name, const Base::Persistence* Object)
assert(!isForceXML());
FileEntry temp;
temp.FileName = Name ? Name : "";
if (FileNameManager.containsName(temp.FileName)) {
temp.FileName = FileNameManager.makeUniqueName(temp.FileName);
}
temp.FileName = getUniqueFileName(Name);
temp.Object = Object;
FileList.push_back(temp);
FileNameManager.addExactName(temp.FileName);
FileNames.push_back(temp.FileName);
// return the unique file name
return temp.FileName;
}
std::string Writer::getUniqueFileName(const char* Name)
{
// name in use?
std::string CleanName = (Name ? Name : "");
std::vector<std::string>::const_iterator pos;
pos = find(FileNames.begin(), FileNames.end(), CleanName);
if (pos == FileNames.end()) {
// if not, name is OK
return CleanName;
}
std::vector<std::string> names;
names.reserve(FileNames.size());
FileInfo fi(CleanName);
CleanName = fi.fileNamePure();
std::string ext = fi.extension();
for (pos = FileNames.begin(); pos != FileNames.end(); ++pos) {
fi.setFile(*pos);
std::string FileName = fi.fileNamePure();
if (fi.extension() == ext) {
names.push_back(FileName);
}
}
std::stringstream str;
str << Base::Tools::getUniqueName(CleanName, names);
if (!ext.empty()) {
str << "." << ext;
}
return str.str();
}
const std::vector<std::string>& Writer::getFilenames() const
{
return FileNames;
}
void Writer::incInd()
{
if (indent < 1020) {

View File

@@ -39,8 +39,6 @@
#include <zipios++/zipoutputstream.h>
#include <zipios++/meta-iostreams.h>
#include <Base/Tools.h>
#include "FileInfo.h"
@@ -58,24 +56,6 @@ class Persistence;
*/
class BaseExport Writer
{
private:
// This overrides UniqueNameManager's suffix-locating function so thet the last '.' and
// everything after it is considered suffix.
class UniqueFileNameManager: public UniqueNameManager
{
protected:
virtual std::string::const_iterator
GetNameSuffixStartPosition(const std::string& name) const override
{
// This is an awkward way to do this, because the FileInfo class only yields pieces of
// the path, not delimiter positions. We can't just use fi.extension().size() because
// both "xyz" and "xyz." would yield three; we need the length of the extension
// *including its delimiter* so we use the length difference between the fileName and
// fileNamePure.
FileInfo fi(name);
return name.end() - (fi.fileName().size() - fi.fileNamePure().size());
}
};
public:
Writer();
@@ -104,6 +84,8 @@ public:
std::string addFile(const char* Name, const Base::Persistence* Object);
/// process the requested file storing
virtual void writeFiles() = 0;
/// get all registered file names
const std::vector<std::string>& getFilenames() const;
/// Set mode
void setMode(const std::string& mode);
/// Set modes
@@ -169,13 +151,14 @@ public:
std::string ObjectName;
protected:
std::string getUniqueFileName(const char* Name);
struct FileEntry
{
std::string FileName;
const Base::Persistence* Object;
};
std::vector<FileEntry> FileList;
UniqueFileNameManager FileNameManager;
std::vector<std::string> FileNames;
std::vector<std::string> Errors;
std::set<std::string> Modes;

View File

@@ -1938,7 +1938,7 @@ void Document::importObjects(const std::vector<App::DocumentObject*>& obj, Base:
localreader->readEndElement("Document");
// In the file GuiDocument.xml new data files might be added
if (localreader->hasFilenames())
if (!localreader->getFilenames().empty())
reader.initLocalReader(localreader);
}

View File

@@ -3408,16 +3408,7 @@ void ViewProviderLink::getPropertyMap(std::map<std::string,App::Property*> &Map)
}
}
void ViewProviderLink::visitProperties(std::function<void(App::Property*)> visitor) const
{
inherited::visitProperties(visitor);
if (childVp != nullptr) {
childVp->visitProperties(visitor);
}
}
void ViewProviderLink::getPropertyList(std::vector<App::Property*>& List) const
{
void ViewProviderLink::getPropertyList(std::vector<App::Property*> &List) const {
std::map<std::string,App::Property*> Map;
getPropertyMap(Map);
List.reserve(List.size()+Map.size());

View File

@@ -265,9 +265,7 @@ public:
App::Property *getPropertyByName(const char* name) const override;
void getPropertyMap(std::map<std::string,App::Property*> &Map) const override;
/// See PropertyContainer::visitProperties for semantics
void visitProperties(std::function<void(App::Property*)> visitor) const override;
void getPropertyList(std::vector<App::Property*>& List) const override;
void getPropertyList(std::vector<App::Property*> &List) const override;
ViewProviderDocumentObject *getLinkedViewProvider(
std::string *subname=nullptr, bool recursive=false) const override;

View File

@@ -872,17 +872,6 @@ void Sheet::getPropertyNamedList(std::vector<std::pair<const char*, Property*>>&
}
}
void Sheet::visitProperties(std::function<void(App::Property*)> visitor) const
{
DocumentObject::visitProperties(visitor);
for (const auto& v : cells.aliasProp) {
auto prop = getProperty(v.first);
if (prop != nullptr) {
visitor(prop);
}
};
}
void Sheet::touchCells(Range range)
{
do {
@@ -1145,7 +1134,7 @@ DocumentObjectExecReturn* Sheet::execute()
catch (std::exception&) { // TODO: evaluate using a more specific exception (not_a_dag)
// Cycle detected; flag all with errors
Base::Console().Error("Cyclic dependency detected in spreadsheet : %s\n",
getNameInDocument());
*pcNameInDocument);
std::ostringstream ss;
ss << "Cyclic dependency";
int count = 0;

View File

@@ -189,9 +189,6 @@ public:
void
getPropertyNamedList(std::vector<std::pair<const char*, App::Property*>>& List) const override;
/// See PropertyContainer::visitProperties for semantics
void visitProperties(std::function<void(App::Property*)> visitor) const override;
short mustExecute() const override;
App::DocumentObjectExecReturn* execute() override;

View File

@@ -5,58 +5,42 @@
// NOLINTBEGIN(cppcoreguidelines-*,readability-*)
TEST(BaseToolsSuite, TestUniqueName1)
{
EXPECT_EQ(Base::UniqueNameManager().makeUniqueName("Body"), "Body");
EXPECT_EQ(Base::Tools::getUniqueName("Body", {}), "Body");
}
TEST(BaseToolsSuite, TestUniqueName2)
{
Base::UniqueNameManager manager;
manager.addExactName("Body");
EXPECT_EQ(manager.makeUniqueName("Body", 1), "Body1");
EXPECT_EQ(Base::Tools::getUniqueName("Body", {"Body"}, 1), "Body1");
}
TEST(BaseToolsSuite, TestUniqueName3)
{
Base::UniqueNameManager manager;
manager.addExactName("Body");
EXPECT_EQ(manager.makeUniqueName("Body", 3), "Body001");
EXPECT_EQ(Base::Tools::getUniqueName("Body", {"Body"}, 3), "Body001");
}
TEST(BaseToolsSuite, TestUniqueName4)
{
Base::UniqueNameManager manager;
manager.addExactName("Body001");
EXPECT_EQ(manager.makeUniqueName("Body", 3), "Body002");
EXPECT_EQ(Base::Tools::getUniqueName("Body", {"Body001"}, 3), "Body002");
}
TEST(BaseToolsSuite, TestUniqueName5)
{
Base::UniqueNameManager manager;
manager.addExactName("Body");
manager.addExactName("Body001");
EXPECT_EQ(manager.makeUniqueName("Body", 3), "Body002");
EXPECT_EQ(Base::Tools::getUniqueName("Body", {"Body", "Body001"}, 3), "Body002");
}
TEST(BaseToolsSuite, TestUniqueName6)
{
Base::UniqueNameManager manager;
manager.addExactName("Body");
manager.addExactName("Body001");
EXPECT_EQ(manager.makeUniqueName("Body001", 3), "Body002");
EXPECT_EQ(Base::Tools::getUniqueName("Body001", {"Body", "Body001"}, 3), "Body002");
}
TEST(BaseToolsSuite, TestUniqueName7)
{
Base::UniqueNameManager manager;
manager.addExactName("Body");
EXPECT_EQ(manager.makeUniqueName("Body001", 3), "Body001");
EXPECT_EQ(Base::Tools::getUniqueName("Body001", {"Body"}, 3), "Body002");
}
TEST(BaseToolsSuite, TestUniqueName8)
{
Base::UniqueNameManager manager;
manager.addExactName("Body");
EXPECT_EQ(manager.makeUniqueName("Body12345", 3), "Body001");
EXPECT_EQ(Base::Tools::getUniqueName("Body12345", {"Body"}, 3), "Body12346");
}
TEST(Tools, TestIota)