Give each copy of a symbol sharing a uuid its own uuid when a project opens

Older versions' copy and paste could leave two symbols with the same
uuid. Wires, links and the project database name a symbol by its uuid,
so a copy was ambiguous: #1417 worked around it for wires, but the
project database keeps one row per uuid, and a link to the uuid could
mean either copy.

When a project opens, every symbol carrying a uuid another symbol also
carries gets a uuid of its own, derived from the shared one and its
folio's uuid, so the same file gives the same result on every open. The
copy that keeps the shared uuid is the first one linked to another
symbol (its partner's link names that uuid), else the first. It is done
once the folio's wires are connected, so #1417's choice of which copy a
wire meets is kept. Folios holding such a copy are built when the
project opens, also with lazy folios.

Only files with shared uuids change, once: 3 of the 341 corpus projects.

tst_sharedsymboluuid: the lamps given one uuid now come back with three;
a linked contact and an unlinked symbol sharing its uuid keep every link
and wire, the linked one keeping the uuid. Both fail without the fix.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XCdg2Er2vYhB8Vwa5G7fSR
This commit is contained in:
ispyisail
2026-10-18 15:01:26 +13:00
parent abd1c8df2f
commit 1c0deb2bc8
4 changed files with 142 additions and 8 deletions
+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