diff --git a/sources/diagram.cpp b/sources/diagram.cpp index bc0cd7c9e..ee3b6540b 100644 --- a/sources/diagram.cpp +++ b/sources/diagram.cpp @@ -1685,7 +1685,7 @@ bool Diagram::fromXml(QDomElement &document, //uuids anyway. if (consider_informations && m_project && QUuid(element_xml.attribute(QStringLiteral("uuid"))).isNull()) { - nvel_elmt->setUuid(m_project->derivedUuid( + nvel_elmt->setUuid(m_project->derivedItemUuid( QStringLiteral("element"), QStringList{type_id, element_xml.attribute(QStringLiteral("x")), diff --git a/sources/qetproject.cpp b/sources/qetproject.cpp index e66714577..b89acc626 100644 --- a/sources/qetproject.cpp +++ b/sources/qetproject.cpp @@ -276,7 +276,7 @@ QUuid QETProject::uuid() const } /** - @brief QETProject::derivedUuid + @brief QETProject::derivedItemUuid A uuid for an item of this project that was saved without one, the same on every load of the same file. @p key describes the item by what it is, never by its place in the file @@ -284,16 +284,31 @@ QUuid QETProject::uuid() const Items with the same @p kind and @p key anywhere in the project (a copied folio, two identical symbols stacked on one spot) are told apart by a counter, in load order among those items alone. + + A derived uuid is never one the file already carries: an item saved with + a derived uuid and then moved or re-connected keeps it, so a newcomer + later taking its old place or ends would otherwise derive the same one. + The file's saved uuids are collected before any folio loads + (readDiagramsXml()), so the result still depends on the file alone. + + uuids are unique within one project; copies of a project share them, as + they share every saved uuid. Anything bringing items in from another + project must renew them, as paste does. @return a UUID v5, which cannot collide with the v4 uuids given to new items */ -QUuid QETProject::derivedUuid(const QString &kind, const QString &key) +QUuid QETProject::derivedItemUuid(const QString &kind, const QString &key) { static const QUuid derived_ns(QStringLiteral("{7d1e9c3a-5b2f-4e8a-9c61-2f4b8d0e6a17}")); const QString full = kind + QLatin1Char('\n') + key; - const int n = m_derived_uuid_keys[full]++; - return QUuid::createUuidV5(derived_ns, - n ? full + QLatin1Char('\n') + QString::number(n) : full); + int &n = m_derived_uuid_keys[full]; + QUuid uuid; + do { + uuid = QUuid::createUuidV5(derived_ns, + n ? full + QLatin1Char('\n') + QString::number(n) : full); + ++n; + } while (m_saved_item_uuids.contains(uuid)); + return uuid; } /** @@ -1909,6 +1924,18 @@ void QETProject::readDiagramsXml(QDomDocument &xml_project) //Search the diagrams in the project QDomNodeList diagram_nodes = xml_project.elementsByTagName(QStringLiteral("diagram")); + //Every symbol and wire uuid the file already carries, on any folio, + //before a folio derives one for an item saved without: see + //derivedItemUuid(). + for (const QString &tag : {QStringLiteral("element"), QStringLiteral("conductor")}) { + const QDomNodeList nodes = xml_project.elementsByTagName(tag); + for (int i = 0; i < nodes.size(); ++i) { + const QUuid saved(nodes.at(i).toElement().attribute(QStringLiteral("uuid"))); + if (!saved.isNull()) + m_saved_item_uuids.insert(saved); + } + } + if(dlgWaiting) dlgWaiting->setProgressBarRange(0, diagram_nodes.length()*3); diff --git a/sources/qetproject.h b/sources/qetproject.h index 991b9f1a9..35830912c 100644 --- a/sources/qetproject.h +++ b/sources/qetproject.h @@ -36,6 +36,7 @@ #endif #include +#include #include class Diagram; @@ -107,7 +108,7 @@ class QETProject : public QObject ProjectPropertiesHandler& projectPropertiesHandler(); projectDataBase *dataBase(); QUuid uuid() const; - QUuid derivedUuid(const QString &kind, const QString &key); + QUuid derivedItemUuid(const QString &kind, const QString &key); ProjectState state() const; QList diagrams() const; int folioIndex(const Diagram *) const; @@ -367,6 +368,7 @@ class QETProject : public QObject KAutoSaveFile m_backup_file; QUuid m_uuid = QUuid::createUuid(); QHash m_derived_uuid_keys; + QSet m_saved_item_uuids; //symbol and wire uuids the file carries, see derivedItemUuid() projectDataBase m_data_base; QVector m_terminal_strip_vector; diff --git a/tests/qttest/tst_derivedsymboluuid.cpp b/tests/qttest/tst_derivedsymboluuid.cpp index d3fa5861a..e5a0e67f8 100644 --- a/tests/qttest/tst_derivedsymboluuid.cpp +++ b/tests/qttest/tst_derivedsymboluuid.cpp @@ -156,6 +156,28 @@ private slots: QCOMPARE(symbolUuids(resave(doc)), symbolUuids(doc)); } + // A symbol keeps its derived uuid once saved, even when moved. A symbol + // saved without a uuid that later turns up on the spot it left (a hand + // edit, an older version, another tool) would derive the same uuid: it + // must get another one instead. + void newcomerOnAMovedSymbolsSpotGetsAnotherUuid() + { + QDomDocument doc = resave(fixture()); + QDomElement moved = symbols(diagrams(doc).first()).first(); + QVERIFY(!QUuid(moved.attribute(QStringLiteral("uuid"))).isNull()); + QDomElement newcomer = moved.cloneNode(true).toElement(); + newcomer.removeAttribute(QStringLiteral("uuid")); + const QDomNodeList terminals = newcomer.elementsByTagName(QStringLiteral("terminal")); + for (int i = 0; i < terminals.size(); ++i) + terminals.at(i).toElement().setAttribute(QStringLiteral("id"), 90000 + i); + moved.setAttribute(QStringLiteral("x"), moved.attribute(QStringLiteral("x")).toInt() + 500); + moved.parentNode().appendChild(newcomer); + + const QMultiHash after = symbolUuids(resave(doc)); + QCOMPARE(after.size(), 10); + QCOMPARE(QSet(after.begin(), after.end()).size(), 10); + } + void stackedIdenticalSymbolsDiffer() { QDomDocument doc = fixture();