From 5441e2ae76605da80ee8c7c65ea400716dc444d8 Mon Sep 17 00:00:00 2001 From: ispyisail Date: Sat, 10 Oct 2026 16:54:26 +1300 Subject: [PATCH] Keep a coil's contacts in the order the file saved them On opening a project, a coil and its contacts are linked in the order the folios' items are visited. When contacts are on an earlier folio than the coil they link first, so the coil's list followed the folio order instead of the file's, and contacts on one folio swapped places from run to run. Saving the same project twice could then give two different files. After linking, Element::initLink() now puts the linked elements back in the order the file listed them. Elements linked but not in that list keep their place after it. Test: tst_resaveunchanged::savedLinkOrderKept reverses a coil's two contacts in m_000.qet and checks a resave keeps that order; it fails without the fix. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_019Wga9B92BNurDEtxxtCDxf --- sources/qetgraphicsitem/element.cpp | 28 ++++++++++++++++++ sources/qetgraphicsitem/element.h | 1 + tests/qttest/tst_resaveunchanged.cpp | 44 +++++++++++++++++++++++++++- 3 files changed, 72 insertions(+), 1 deletion(-) diff --git a/sources/qetgraphicsitem/element.cpp b/sources/qetgraphicsitem/element.cpp index 494f9941b..56c8bda2d 100644 --- a/sources/qetgraphicsitem/element.cpp +++ b/sources/qetgraphicsitem/element.cpp @@ -1642,6 +1642,7 @@ void Element::initLink(QETProject *prj) } } } + restoreSavedLinkOrder(); tmp_uuids_link.clear(); } @@ -1669,9 +1670,36 @@ void Element::initLink(const QList &candidates) } } } + restoreSavedLinkOrder(); tmp_uuids_link.clear(); } +/** + @brief Element::restoreSavedLinkOrder + Put connected_elements back in the order tmp_uuids_link saved them. + Partners link in the order the folios' items are visited, and for + items at the same height that order changes from run to run, so a + master's contacts were saved in a different order each time. + Linked elements missing from the saved list keep their place after it. +*/ +void Element::restoreSavedLinkOrder() +{ + QList ordered; + for (const auto &link_info : std::as_const(tmp_uuids_link)) { + for (Element *elmt : std::as_const(connected_elements)) { + if (elmt->uuid() == link_info.uuid && !ordered.contains(elmt)) { + ordered << elmt; + break; + } + } + } + for (Element *elmt : std::as_const(connected_elements)) { + if (!ordered.contains(elmt)) + ordered << elmt; + } + connected_elements = ordered; +} + /** * @brief Element::linkTypeToString * \deprecated use instead ElementData::typeToString diff --git a/sources/qetgraphicsitem/element.h b/sources/qetgraphicsitem/element.h index 6c2a4b352..02c3ea600 100644 --- a/sources/qetgraphicsitem/element.h +++ b/sources/qetgraphicsitem/element.h @@ -328,6 +328,7 @@ class Element : public QetGraphicsItem QList plcTablePositions() const { return m_plc_table_positions; } private: + void restoreSavedLinkOrder(); bool m_must_highlight = false; QSize dimensions; QPoint hotspot_coord; diff --git a/tests/qttest/tst_resaveunchanged.cpp b/tests/qttest/tst_resaveunchanged.cpp index bc333ea44..e80b4cb50 100644 --- a/tests/qttest/tst_resaveunchanged.cpp +++ b/tests/qttest/tst_resaveunchanged.cpp @@ -19,7 +19,8 @@ // was opened again (m_000.qet). // Runs the real binary's --resave twice on every example, on a project // whose title block holds a value that is a single space (#973), and on -// one that used to crash on opening (bugtracker #345). +// one that used to crash on opening (bugtracker #345); and that a coil's +// contacts keep the order the file saved them in. class tst_resaveunchanged : public QObject { Q_OBJECT @@ -118,6 +119,47 @@ private slots: "a machine manufacturer reference variable was left unresolved"); } + // A coil's contacts are saved in the order the file listed them. + // They used to come back in the order the folios were visited, so a + // list that disagreed with it changed on every save, and contacts on + // one folio swapped places from run to run. m_000.qet's coil b02216df + // (folio 13) lists its contacts on folios 7 and 12; the test lists + // them the other way round. + void savedLinkOrderKept() + { + QByteArray xml = read(QStringLiteral(QET_EXAMPLES_DIR "/m_000.qet")); + const QRegularExpression coil(QStringLiteral( + "]*uuid=\"\\{b02216df-0851-4732-893b-901fea80703e\\}\"")); + // byte offset of the coil's tag in @p file, or -1 + const auto coilAt = [&coil](const QByteArray &file) { + const QRegularExpressionMatch m = coil.match(QString::fromLatin1(file)); + return m.hasMatch() ? int(m.capturedStart()) : -1; + }; + const QByteArray a = ""; + const QByteArray b = ""; + const int start = coilAt(xml); + QVERIFY(start > 0); + const int ia = xml.indexOf(a, start), ib = xml.indexOf(b, start); + QVERIFY2(ia > 0 && ib > ia, "m_000.qet no longer lists the coil's contacts as expected"); + const QByteArray link_b = xml.mid(ib, xml.indexOf(b_end, ib) + b_end.size() - ib); + xml.replace(ib, link_b.size(), a); + xml.replace(ia, a.size(), link_b); + const QString in = m_dir.filePath(QStringLiteral("links.qet")); + QFile f(in); + QVERIFY(f.open(QIODevice::WriteOnly)); + f.write(xml); + f.close(); + + const QByteArray saved = read(resave(in)); + QVERIFY2(!saved.isEmpty(), "--resave failed"); + const int s = coilAt(saved); + QVERIFY(s > 0); + const int sa = saved.indexOf(a, s), sb = saved.indexOf(b, s); + QVERIFY2(sa > 0 && sb > 0, "a link was lost"); + QVERIFY2(sb < sa, "the coil's contacts were saved in another order than the file's"); + } + // A title-block value that is a single space is kept through two saves // (#973), and a value with accents comes back as it went in. void singleSpaceValueKept()