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 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019Wga9B92BNurDEtxxtCDxf
This commit is contained in:
ispyisail
2026-10-10 16:54:26 +13:00
parent 613f05a05e
commit 5441e2ae76
3 changed files with 72 additions and 1 deletions
+28
View File
@@ -1642,6 +1642,7 @@ void Element::initLink(QETProject *prj)
}
}
}
restoreSavedLinkOrder();
tmp_uuids_link.clear();
}
@@ -1669,9 +1670,36 @@ void Element::initLink(const QList<Element *> &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<Element *> 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
+1
View File
@@ -328,6 +328,7 @@ class Element : public QetGraphicsItem
QList<QPointF> plcTablePositions() const { return m_plc_table_positions; }
private:
void restoreSavedLinkOrder();
bool m_must_highlight = false;
QSize dimensions;
QPoint hotspot_coord;
+43 -1
View File
@@ -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(
"<element [^>]*uuid=\"\\{b02216df-0851-4732-893b-901fea80703e\\}\""));
// byte offset of the coil's <element> 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 = "<link_uuid uuid=\"{998190fa-5b4c-4be3-bc67-8a828eaab2b4}\"/>";
const QByteArray b = "<link_uuid uuid=\"{112aa911-",
b_end = "\"/>";
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()