mirror of
https://github.com/qelectrotech/qelectrotech-source-mirror.git
synced 2026-10-25 15:34:13 +01:00
Link a symbol built to its partners from the project database's rows
A symbol built after opening (lazy folios) links to the partners its link rows name, and to the symbols whose rows name it; its file's list only adds what the rows miss. The rows keep a partner on a folio not built until it is built, in its saved place, so a symbol no longer keeps a list of its own: the list read from the file is dropped once opening has filled the database. pendingLinks(), waitingLinks(), keepsSavedLinks(), linksToSave() and QETProject::waitingLinkTargets() go; the callers that asked a symbol for its waiting partners ask the database (waitingPartners()). When its links change, a symbol's rows are written from its built partners and the waiting partners its rows held, in their order (rowsToWrite()), so a full rewrite of the table keeps them too. A symbol built alone as a partner on a folio not built (Diagram::buildSymbols()) counts as built: its rows are kept, it is not waiting (waitingSymbols(), waitingPartners()), so saving does not build its folio, and removing it does not remove the rows naming it. A row naming a placed symbol that has not linked yet (its folio being built) is kept too. At opening the database holds no link until its fill (forgetLinks()); the symbols read their files' lists until then, and the fill takes their order: a symbol's own rows, its list, then the rows naming it. The symbols waiting on folios not built are cached and updated as folios are built. QET_CHECK_LINK_ROWS now checks the rows against the links: each built partner in its order, no other row but to a folio not built. tst_resaveunchanged::undoKeepsLinksToUnbuiltFolios: m_000 opened with lazy folios, a contact, its folio or its coil's folio (not built) removed, then undone: every link saved as before. QETZ-DB-FIRST stage 2a-3. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -522,6 +522,97 @@ private slots:
|
||||
QCOMPARE(dangling, 0);
|
||||
}
|
||||
|
||||
// Each placed symbol's saved links in the .qetz @p path, as "symbol
|
||||
// partner" lines, sorted: the link table of its database
|
||||
static QStringList qetzLinks(const QString &path)
|
||||
{
|
||||
QList<QetZip::Entry> entries;
|
||||
if (!QetZip::fromBytes(read(path), &entries)) return {QStringLiteral("not a zip")};
|
||||
QByteArray database;
|
||||
for (const QetZip::Entry &entry : std::as_const(entries))
|
||||
if (entry.name == QLatin1String("project.sqlite")) database = entry.data;
|
||||
sqlite3 *db = nullptr;
|
||||
sqlite3_open(":memory:", &db);
|
||||
unsigned char *copy = static_cast<unsigned char *>(sqlite3_malloc64(sqlite3_uint64(database.size())));
|
||||
memcpy(copy, database.constData(), size_t(database.size()));
|
||||
sqlite3_deserialize(db, "main", copy, database.size(), database.size(), SQLITE_DESERIALIZE_FREEONCLOSE);
|
||||
sqlite3_stmt *list = nullptr;
|
||||
QStringList links;
|
||||
sqlite3_prepare_v2(db, "SELECT element_uuid, linked_uuid FROM link", -1, &list, nullptr);
|
||||
while (list && sqlite3_step(list) == SQLITE_ROW)
|
||||
links << QString::fromUtf8(reinterpret_cast<const char *>(sqlite3_column_text(list, 0)))
|
||||
+ QLatin1Char(' ')
|
||||
+ QString::fromUtf8(reinterpret_cast<const char *>(sqlite3_column_text(list, 1)));
|
||||
sqlite3_finalize(list);
|
||||
sqlite3_close(db);
|
||||
links.sort();
|
||||
return links;
|
||||
}
|
||||
|
||||
// Removing a symbol or a folio and undoing it, with lazy folios, gives
|
||||
// back every link, also those to symbols on folios not built, which
|
||||
// only the project database's link rows hold (QETZ-DB-FIRST stage
|
||||
// 2a-3). m_000 as a .qetz saved with every folio built; a contact of
|
||||
// its middle folio, its folio, or its coil's folio (not built: the coil
|
||||
// is built alone) removed, then undone.
|
||||
void undoKeepsLinksToUnbuiltFolios_data()
|
||||
{
|
||||
QTest::addColumn<QString>("edit");
|
||||
QTest::newRow("symbol") << QStringLiteral("qet.deleteElement(f, u)");
|
||||
QTest::newRow("folio") << QStringLiteral("qet.removeFolio(f)");
|
||||
QTest::newRow("coil folio") << QStringLiteral("qet.removeFolio(folio_of[m])");
|
||||
}
|
||||
void undoKeepsLinksToUnbuiltFolios()
|
||||
{
|
||||
#ifndef QET_HAS_SCRIPTING
|
||||
QSKIP("needs --run: this QElectroTech is built without Qt Qml");
|
||||
#endif
|
||||
QFETCH(QString, edit);
|
||||
//Each symbol's folio, from the file: asking QElectroTech would
|
||||
//build the folios
|
||||
QFile source(QStringLiteral(QET_EXAMPLES_DIR "/m_000.qet"));
|
||||
QVERIFY(source.open(QIODevice::ReadOnly));
|
||||
const QStringList folios = QString::fromUtf8(source.readAll()).split(QStringLiteral("<diagram "));
|
||||
QStringList folio_of;
|
||||
static const QRegularExpression placed(QStringLiteral("<element [^>]*\\buuid=\"(\\{[0-9a-f-]+\\})\""));
|
||||
for (int i = 1 ; i < folios.size() ; ++i) {
|
||||
auto it = placed.globalMatch(folios.at(i));
|
||||
while (it.hasNext()) folio_of << QStringLiteral("'%1': %2").arg(it.next().captured(1)).arg(i - 1);
|
||||
}
|
||||
const QString qetz = m_dir.filePath(QStringLiteral("undo%1.qetz").arg(m_run));
|
||||
QVERIFY(!runChecked({QStringLiteral("--resave"),
|
||||
QStringLiteral(QET_EXAMPLES_DIR "/m_000.qet"), qetz}, true).isEmpty());
|
||||
auto save = [&](const QString &name, const QString &body) {
|
||||
const QString saved = m_dir.filePath(name + QString::number(m_run) + QStringLiteral(".qetz"));
|
||||
const QString js = m_dir.filePath(name + QString::number(m_run) + QStringLiteral(".js"));
|
||||
QFile script(js);
|
||||
if (!script.open(QIODevice::WriteOnly)) return std::pair<QString, QString>();
|
||||
script.write(QStringLiteral(
|
||||
"var folio_of = {%3};\n"
|
||||
"var n = qet.folioCount(), f = -1, u = '', m = '';\n"
|
||||
"for (var k = 0 ; k < n && !u ; ++k) {\n"
|
||||
" var g = (Math.floor(n / 2) + k) % n;\n"
|
||||
" var all = qet.elementUuids(g).slice().sort();\n"
|
||||
" for (var i = 0 ; i < all.length && !u ; ++i)\n"
|
||||
" if (qet.elementLinkType(g, all[i]) == 'slave' && qet.linkedElements(g, all[i]).length) { f = g; u = all[i]; m = qet.linkedElements(g, u)[0]; }\n"
|
||||
"}\n"
|
||||
"%1\n"
|
||||
"qet.save('%2');\n").arg(body, saved, folio_of.join(QStringLiteral(", "))).toUtf8());
|
||||
script.close();
|
||||
return std::pair<QString, QString>(saved, runChecked({QStringLiteral("--run"), js, qetz}, false,
|
||||
"QET_CHECK_LINK_ROWS"));
|
||||
};
|
||||
const auto plain = save(QStringLiteral("undo-plain"), QStringLiteral("qet.log('FOUND ' + !!u);"));
|
||||
QVERIFY2(plain.second.contains(QStringLiteral("FOUND true")), qPrintable(plain.second.right(600)));
|
||||
const auto undone = save(QStringLiteral("undo-undone"),
|
||||
QStringLiteral("qet.log('UNDONE ' + (%1) + ' ' + qet.undo());").arg(edit));
|
||||
QVERIFY2(undone.second.contains(QStringLiteral("UNDONE true true")), qPrintable(undone.second.right(600)));
|
||||
QVERIFY2(undone.second.contains(QStringLiteral("link rows: 0 differ")), qPrintable(undone.second.right(600)));
|
||||
const QStringList before = qetzLinks(plain.first);
|
||||
QVERIFY(before.size() > 10);
|
||||
QCOMPARE(qetzLinks(undone.first), before);
|
||||
}
|
||||
|
||||
// The stores filled from the document before the folios are built
|
||||
// hold what building them gives, for a
|
||||
// project saved by this version (every symbol, folio and wire carries
|
||||
|
||||
Reference in New Issue
Block a user