diff --git a/sources/diagram.cpp b/sources/diagram.cpp index 5dc44f471..9940ced53 100644 --- a/sources/diagram.cpp +++ b/sources/diagram.cpp @@ -2342,6 +2342,23 @@ bool Diagram::fromXml(QDomElement &document, << "wire end(s) on a terminal that moved since the project was saved," << "matched to it by place"; } + //A copy of a symbol sharing its uuid with another (older versions' + //copies, F100) gets its own, now that its wires are connected to + //it: wires and links then name one symbol each + //(QETProject::renewedSharedUuid(), only while the project opens) + if (consider_informations && m_project) { + const int folio_index = m_project->folioIndex(this); + QHash ordinal; + for (Element *element : std::as_const(added_elements)) { + const QUuid shared = element->uuid(); + const QUuid renewed = m_project->renewedSharedUuid(folio_index, ordinal[shared]++, + shared, uuid()); + if (!renewed.isNull()) { + element->setUuid(renewed); + } + } + } + if (consider_informations && !m_wires_not_reconnected.isEmpty()) { qWarning().noquote() << "Diagram::fromXml():" << m_wires_not_reconnected.size() diff --git a/sources/qetproject.cpp b/sources/qetproject.cpp index eaa39afc9..c32f1f8b8 100644 --- a/sources/qetproject.cpp +++ b/sources/qetproject.cpp @@ -299,6 +299,27 @@ QByteArray QETProject::containerPicture(const QString &file) const return m_container_pictures.value(file); } +/** + @brief QETProject::renewedSharedUuid + While the project opens: the uuid of the @p ordinal -th symbol carrying + @p uuid on the folio at @p folio_index (in the file's order), when + other symbols of the project carry the same uuid and this copy is not + the one keeping it (readDiagramsXml()). Derived from the uuid and the + folio's uuid, so the same file gives the same uuid on every open. + @return a null uuid when this symbol keeps its uuid +*/ +QUuid QETProject::renewedSharedUuid(int folio_index, int ordinal, const QUuid &uuid, + const QUuid &folio_uuid) +{ + const auto keeper = m_shared_uuid_keepers.constFind(uuid); + if (keeper == m_shared_uuid_keepers.constEnd() + || (keeper->first == folio_index && keeper->second == ordinal)) { + return QUuid(); + } + return derivedItemUuid(QStringLiteral("element-copy"), + uuid.toString() + QLatin1Char('\n') + folio_uuid.toString()); +} + /** @brief QETProject::derivedItemUuid A uuid for an item of this project that was saved without one, the same @@ -3664,6 +3685,41 @@ void QETProject::readDiagramsXml(QDomDocument &xml_project) } } + //Symbols sharing a uuid (copies made by older versions, F100): the + //project database, links and wires name a symbol by its uuid, so + //each copy but one gets its own (renewedSharedUuid()). The copy + //that keeps it is the first one linked to another symbol (a + //partner's link names the uuid), else the first. The folios with + //a copy to renew are built now, also with lazy folios, so the + //whole project has one uuid per symbol from the start. + m_shared_uuid_keepers.clear(); + QSet build_now; + { + QHash>> copies; + for (int i = 0 ; i < diagram_nodes.length() ; ++i) { + const QDomElement elements = diagram_nodes.at(i).toElement() + .firstChildElement(QStringLiteral("elements")); + QHash ordinal; + for (QDomElement e = elements.firstChildElement(QStringLiteral("element")); + !e.isNull() ; e = e.nextSiblingElement(QStringLiteral("element"))) { + const QUuid uuid(e.attribute(QStringLiteral("uuid"))); + if (uuid.isNull()) continue; + const bool linked = !e.firstChildElement(QStringLiteral("links_uuids")) + .firstChildElement(QStringLiteral("link_uuid")).isNull(); + copies[uuid].append({i, ordinal[uuid]++, linked}); + } + } + for (auto it = copies.cbegin() ; it != copies.cend() ; ++it) { + if (it.value().size() < 2) continue; + auto keeper = it.value().first(); + for (const auto © : it.value()) + if (std::get<2>(copy)) { keeper = copy; break; } + m_shared_uuid_keepers.insert(it.key(), {std::get<0>(keeper), std::get<1>(keeper)}); + for (const auto © : it.value()) + if (copy != keeper) build_now.insert(std::get<0>(copy)); + } + } + if(dlgWaiting) dlgWaiting->setProgressBarRange(0, diagram_nodes.length()*3); @@ -3717,7 +3773,7 @@ void QETProject::readDiagramsXml(QDomDocument &xml_project) connect(diagram, &Diagram::usedTitleBlockTemplateChanged, this, &QETProject::usedTitleBlockTemplateChanged); - m_deferring_folios = m_lazy_open; + m_deferring_folios = m_lazy_open && !build_now.contains(i); diagram->initFromXml(diagram_xml_element); m_deferring_folios = false; if (!diagram->isLoaded() && !line_starts.isEmpty()) @@ -3730,6 +3786,7 @@ void QETProject::readDiagramsXml(QDomDocument &xml_project) } } + m_shared_uuid_keepers.clear(); updateDiagramsFolioData(); //Initialise links between elements in this project diff --git a/sources/qetproject.h b/sources/qetproject.h index 80a634407..4b3dc7986 100644 --- a/sources/qetproject.h +++ b/sources/qetproject.h @@ -123,6 +123,8 @@ class QETProject : public QObject projectDataBase *dataBase(); QUuid uuid() const; QUuid derivedItemUuid(const QString &kind, const QString &key); + QUuid renewedSharedUuid(int folio_index, int ordinal, const QUuid &uuid, + const QUuid &folio_uuid); QByteArray containerPicture(const QString &file) const; ProjectState state() const; QString openError() const; @@ -615,7 +617,11 @@ class QETProject : public QObject int m_next_backup_slot = 0; QUuid m_uuid = QUuid::createUuid(); QHash m_derived_uuid_keys; - QSet m_saved_item_uuids; //symbol and wire uuids the file carries, see derivedItemUuid() + QSet m_saved_item_uuids; + /// While opening: per uuid carried by several symbols, the copy + /// keeping it (folio index, ordinal on that folio), see + /// renewedSharedUuid() + QHash> m_shared_uuid_keepers; //symbol and wire uuids the file carries, see derivedItemUuid() projectDataBase m_data_base; QVector m_terminal_strip_vector; QVector m_cables; diff --git a/tests/qttest/tst_sharedsymboluuid.cpp b/tests/qttest/tst_sharedsymboluuid.cpp index 85ba9bf95..f2c2671e5 100644 --- a/tests/qttest/tst_sharedsymboluuid.cpp +++ b/tests/qttest/tst_sharedsymboluuid.cpp @@ -1,5 +1,6 @@ // SPDX-License-Identifier: GPL-2.0-or-later #include +#include #include #include @@ -150,13 +151,66 @@ private slots: QVERIFY(!twice.isEmpty()); QCOMPARE(nets(twice), expected); - //No wire names a lamp by the uuid they share - for (const QDomElement &c : childElements(firstDiagram(load(twice)), - QStringLiteral("conductors"), - QStringLiteral("conductor"))) { - QVERIFY(c.attribute(QStringLiteral("element1")) != shared); - QVERIFY(c.attribute(QStringLiteral("element2")) != shared); + //Each lamp has a uuid of its own after a save: one keeps the + //shared uuid, the others get one derived from it + QSet uuids; + for (const QDomElement &e : lamps(load(twice))) + uuids.insert(e.attribute(QStringLiteral("uuid"))); + QCOMPARE(uuids.size(), 3); + QVERIFY(uuids.contains(shared)); + } + + // A linked contact and an unlinked symbol carrying the same uuid (a copy + // made by an older version): the linked one keeps the uuid, the other + // gets its own, and every link and wire stays where it was. + void linkedCopyKeepsUuid() + { + const QString source = QStringLiteral(QET_EXAMPLES_DIR "/affuteuse_250h.qet"); + QDomDocument doc = load(source); + QList all; + const QDomNodeList folios = doc.documentElement().elementsByTagName(QStringLiteral("diagram")); + for (int i = 0; i < folios.size(); ++i) + all << childElements(folios.at(i).toElement(), QStringLiteral("elements"), + QStringLiteral("element")); + auto linked = [](const QDomElement &e) { + return !e.firstChildElement(QStringLiteral("links_uuids")) + .firstChildElement(QStringLiteral("link_uuid")).isNull(); + }; + QDomElement keeper, copy; + for (const QDomElement &e : std::as_const(all)) { + if (keeper.isNull() && linked(e)) keeper = e; + else if (copy.isNull() && !linked(e)) copy = e; } + QVERIFY(!keeper.isNull() && !copy.isNull()); + const QString shared = keeper.attribute(QStringLiteral("uuid")); + copy.setAttribute(QStringLiteral("uuid"), shared); + const int wires = doc.elementsByTagName(QStringLiteral("conductor")).size(); + + const QString saved = resave(write(doc)); + QVERIFY(!saved.isEmpty()); + const QDomDocument out = load(saved); + QHash by_uuid; + QList> links; + const QDomNodeList saved_folios = out.documentElement().elementsByTagName(QStringLiteral("diagram")); + for (int i = 0; i < saved_folios.size(); ++i) + for (const QDomElement &e : childElements(saved_folios.at(i).toElement(), + QStringLiteral("elements"), + QStringLiteral("element"))) { + const QString u = e.attribute(QStringLiteral("uuid")); + QVERIFY2(!by_uuid.contains(u), qPrintable(QStringLiteral("uuid still shared: ") + u)); + by_uuid.insert(u, e); + for (QDomElement l = e.firstChildElement(QStringLiteral("links_uuids")) + .firstChildElement(QStringLiteral("link_uuid")); + !l.isNull(); l = l.nextSiblingElement(QStringLiteral("link_uuid"))) + links.append({u, l.attribute(QStringLiteral("uuid"))}); + } + QVERIFY(by_uuid.contains(shared)); + QVERIFY(linked(by_uuid.value(shared))); + for (const auto &link : std::as_const(links)) { + QVERIFY2(by_uuid.contains(link.second), "a link names a symbol not in the project"); + QVERIFY2(links.contains(std::make_pair(link.second, link.first)), "a link only one side has"); + } + QCOMPARE(out.elementsByTagName(QStringLiteral("conductor")).size(), wires); } void savedByUuidReopensRight()