Merge pull request #1522 from ispyisail/fix/shared-symbol-uuid-renew

Fix symbols sharing a uuid: give each copy its own when a project opens
This commit is contained in:
ispyisail
2026-10-18 19:52:54 +13:00
committed by GitHub
4 changed files with 142 additions and 8 deletions
+17
View File
@@ -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<QUuid, int> 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()
+58 -1
View File
@@ -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<int> build_now;
{
QHash<QUuid, QList<std::tuple<int, int, bool>>> copies;
for (int i = 0 ; i < diagram_nodes.length() ; ++i) {
const QDomElement elements = diagram_nodes.at(i).toElement()
.firstChildElement(QStringLiteral("elements"));
QHash<QUuid, int> 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 &copy : 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 &copy : 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
+7 -1
View File
@@ -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<QString, int> m_derived_uuid_keys;
QSet<QUuid> m_saved_item_uuids; //symbol and wire uuids the file carries, see derivedItemUuid()
QSet<QUuid> 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<QUuid, std::pair<int, int>> m_shared_uuid_keepers; //symbol and wire uuids the file carries, see derivedItemUuid()
projectDataBase m_data_base;
QVector<TerminalStrip *> m_terminal_strip_vector;
QVector<Cable *> m_cables;
+60 -6
View File
@@ -1,5 +1,6 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#include <QtTest>
#include <QSet>
#include <QDomDocument>
#include <QFile>
@@ -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<QString> 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<QDomElement> 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<QString, QDomElement> by_uuid;
QList<std::pair<QString, QString>> 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()