From 5c2726d34b0eed41b95c044c0e8800d778749463 Mon Sep 17 00:00:00 2001 From: ispyisail Date: Fri, 23 Oct 2026 13:29:07 +1300 Subject: [PATCH] Move a symbol's kind information through the project database QETZ-DB-FIRST 2d-2a: the first half of 2d-2 (QETZ-DB-FIRST-2D-SCOPE.md). A symbol's kind information (NO/NC/SW/power and the like, baked into its own definition and never edited after it is built -- the data CrossRefItem::NOElements()/NCElements() read, through the already @deprecated Element::kindInformations(), to classify a linked contact) had no database-backed form at all: nothing could query it without walking a live Element*. Add an element_kind_information table, written through from projectDataBase::placeElement()/unplaceElement() (the same hooks that already write element_information), with Element::kindInformations() now reading the store first -- same shape as elementInformations()/ ownInformations(). Added Element::ownKindInformations() as the raw member accessor, mirroring ownInformations(), so storeElementKindInformation() is called with the member value rather than the store-reading getter (the same stale-echo trap storeBorder()/storeFolioNumber() avoid). No automated test existed for cross-reference text or kind information at all; added tst_elementkindinformation (a hand-written embedded-symbol project, checked through the database via qet.query()) before touching anything cross-reference-shaped, per the re-scoping in QETZ-DB-FIRST-2D-SCOPE.md. crossrefitem.cpp itself is untouched here: switching NOElements()/NCElements() to read through this store is 2d-2b, once this lands. Co-Authored-By: Claude Sonnet 5 --- sources/dataBase/projectdatabase.cpp | 83 +++++++++++ sources/dataBase/projectdatabase.h | 10 ++ sources/qetgraphicsitem/element.cpp | 14 ++ sources/qetgraphicsitem/element.h | 5 +- tests/qttest/CMakeLists.txt | 12 ++ tests/qttest/tst_elementkindinformation.cpp | 145 ++++++++++++++++++++ 6 files changed, 267 insertions(+), 2 deletions(-) create mode 100644 tests/qttest/tst_elementkindinformation.cpp diff --git a/sources/dataBase/projectdatabase.cpp b/sources/dataBase/projectdatabase.cpp index 827b0df06..a5e00d5e1 100644 --- a/sources/dataBase/projectdatabase.cpp +++ b/sources/dataBase/projectdatabase.cpp @@ -2656,6 +2656,21 @@ bool projectDataBase::createDataBase() "PRIMARY KEY (element_uuid, ord))"))) { qDebug() << " element_information_table query : " << query_.lastError(); } + + //Create the element_kind_information table: a symbol's kind + //information (NO/NC/SW/power and the like), baked into its own + //definition and never edited after it is built, the store symbols + //write through (storeElementKindInformation()). + if (!query_.exec(QStringLiteral( + "CREATE TABLE element_kind_information (" + "element_uuid VARCHAR(50) NOT NULL, " + "ord INTEGER NOT NULL, " + "name TEXT NOT NULL, " + "value TEXT, " + "show INTEGER NOT NULL, " + "PRIMARY KEY (element_uuid, ord))"))) { + qDebug() << " element_kind_information_table query : " << query_.lastError(); + } //Create the folio_titleblock table: the title block properties of //every folio, as a folio saves them, the store folios write //through (storeFolioTitleBlock()). One row per property, then one @@ -3365,6 +3380,71 @@ void projectDataBase::forgetElementInformation(const QUuid &element) } } +/** + @brief projectDataBase::elementKindInformation + @return the stored kind information of the symbol @p element, or an + empty context if it holds none +*/ +DiagramContext projectDataBase::elementKindInformation(const QUuid &element) const +{ + return m_element_kind_information.value(element); +} + +/** + @brief projectDataBase::storeElementKindInformation + Store @p information as the kind information of the placed symbol + @p element, in the element_kind_information table and the cache in + front of it. + @return true if that changed what was stored +*/ +bool projectDataBase::storeElementKindInformation(const QUuid &element, const DiagramContext &information) +{ + if (element.isNull()) return false; + auto known = m_element_kind_information.constFind(element); + if (known != m_element_kind_information.constEnd() && *known == information) return false; + m_element_kind_information.insert(element, information); + + const QString uuid = element.toString(); + QSqlQuery remove(m_data_base); + remove.prepare(QStringLiteral("DELETE FROM element_kind_information WHERE element_uuid = :uuid")); + remove.bindValue(QStringLiteral(":uuid"), uuid); + if (!remove.exec()) { + qDebug() << "projectDataBase::storeElementKindInformation remove error : " << remove.lastError(); + } + QSqlQuery insert(m_data_base); + insert.prepare(QStringLiteral( + "INSERT INTO element_kind_information (element_uuid, ord, name, value, show) " + "VALUES (:uuid, :ord, :name, :value, :show)")); + int ord = 0; + for (const QString &key : information.keys()) { + insert.bindValue(QStringLiteral(":uuid"), uuid); + insert.bindValue(QStringLiteral(":ord"), ord++); + insert.bindValue(QStringLiteral(":name"), key); + insert.bindValue(QStringLiteral(":value"), information.value(key).toString()); + insert.bindValue(QStringLiteral(":show"), information.keyMustShow(key) ? 1 : 0); + if (!insert.exec()) { + qDebug() << "projectDataBase::storeElementKindInformation insert error : " << insert.lastError(); + } + } + return true; +} + +/** + @brief projectDataBase::forgetElementKindInformation + The symbol @p element is no longer placed: its stored kind information + goes. +*/ +void projectDataBase::forgetElementKindInformation(const QUuid &element) +{ + if (!m_element_kind_information.remove(element)) return; + QSqlQuery remove(m_data_base); + remove.prepare(QStringLiteral("DELETE FROM element_kind_information WHERE element_uuid = :uuid")); + remove.bindValue(QStringLiteral(":uuid"), element.toString()); + if (!remove.exec()) { + qDebug() << "projectDataBase::forgetElementKindInformation error : " << remove.lastError(); + } +} + /** @brief projectDataBase::placeElement @p element is now placed: its information is stored under its uuid. @@ -3374,6 +3454,7 @@ void projectDataBase::placeElement(Element *element) if (!m_placed_elements.contains(element->uuid(), element)) m_placed_elements.insert(element->uuid(), element); storeElementInformation(element->uuid(), element->ownInformations()); + storeElementKindInformation(element->uuid(), element->ownKindInformations()); } /** @@ -3389,11 +3470,13 @@ void projectDataBase::unplaceElement(Element *element, const QUuid &uuid) if (other) { //Its own copy: the store still holds this symbol's storeElementInformation(uuid, other->ownInformations()); + storeElementKindInformation(uuid, other->ownKindInformations()); return; } } m_placed_elements.remove(uuid); forgetElementInformation(uuid); + forgetElementKindInformation(uuid); } /** diff --git a/sources/dataBase/projectdatabase.h b/sources/dataBase/projectdatabase.h index 5ec7cace0..0a8b30a63 100644 --- a/sources/dataBase/projectdatabase.h +++ b/sources/dataBase/projectdatabase.h @@ -136,6 +136,15 @@ class projectDataBase : public QObject //each as "uuid: why"; empty when they all agree QStringList elementInformationMismatches() const; + //The kind information of every symbol placed in the project, by + //symbol uuid (NO/NC/SW/power and the like, baked into the + //symbol's own definition, never edited after it is built): the + //store each symbol's kind information is written through. A + //symbol not placed keeps its own. + DiagramContext elementKindInformation(const QUuid &element) const; + bool storeElementKindInformation(const QUuid &element, const DiagramContext &information); + void forgetElementKindInformation(const QUuid &element); + //The title block properties of every folio of the project, by //folio uuid: the store each folio's title block is written //through. A folio not in a @@ -423,6 +432,7 @@ class projectDataBase : public QObject //cell is written when next read, as drawing items are. QList> m_moved_elements; QHash m_element_information; + QHash m_element_kind_information; //The placed symbols holding each uuid: a paste or a folio copy //briefly gives a copy its original's uuid, so one uuid can have //two symbols until the copy's is renewed. diff --git a/sources/qetgraphicsitem/element.cpp b/sources/qetgraphicsitem/element.cpp index 8c4676e79..a6c4d769e 100644 --- a/sources/qetgraphicsitem/element.cpp +++ b/sources/qetgraphicsitem/element.cpp @@ -2297,6 +2297,20 @@ DiagramContext Element::elementInformations() const return m_data.m_informations; } +/** + @brief Element::kindInformations + @return this symbol's kind information (NO/NC/SW/power and the like, + baked into its own definition, never edited after it is built): the + project's store holds it once this symbol is placed, for one not + placed (a preview, the symbol editor) its own copy. +*/ +DiagramContext Element::kindInformations() const +{ + if (projectDataBase *store = placedStore()) + return store->elementKindInformation(m_uuid); + return m_kind_informations; +} + /** @brief Element::placedStore @return the project database holding this symbol's information, or diff --git a/sources/qetgraphicsitem/element.h b/sources/qetgraphicsitem/element.h index 29c29bb1f..2cb097b9a 100644 --- a/sources/qetgraphicsitem/element.h +++ b/sources/qetgraphicsitem/element.h @@ -147,8 +147,9 @@ class Element : public QetGraphicsItem * use elementData function instead * @return */ - DiagramContext kindInformations() const - {return m_kind_informations;} + DiagramContext kindInformations() const; + /// This symbol's own copy of its kind information, whatever the store holds + DiagramContext ownKindInformations() const {return m_kind_informations;} //@kind_information_ is used to store more information //about the herited class like contactelement for know // kind of contact (simple tempo) or number of contact show by the element. diff --git a/tests/qttest/CMakeLists.txt b/tests/qttest/CMakeLists.txt index 4e8d6c930..64ab7f836 100644 --- a/tests/qttest/CMakeLists.txt +++ b/tests/qttest/CMakeLists.txt @@ -1211,6 +1211,18 @@ target_link_libraries(tst_movedterminals PRIVATE Qt::Test Qt::Xml) target_compile_definitions(tst_movedterminals PRIVATE "QET_TEST_BINARY_PATH=\"$\"") +# A symbol's kind information (NO/NC/SW/power) is queryable from the +# project database once it is placed, not only from a live Element*. +# QETZ-DB-FIRST 2d-2a. +add_executable( + tst_elementkindinformation + tst_elementkindinformation.cpp) +add_test(NAME tst_elementkindinformation COMMAND tst_elementkindinformation) +add_dependencies(tst_elementkindinformation qelectrotech) +target_link_libraries(tst_elementkindinformation PRIVATE Qt::Test Qt::Xml) +target_compile_definitions(tst_elementkindinformation PRIVATE + "QET_TEST_BINARY_PATH=\"$\"") + # Symbols sharing one uuid on a folio keep their wires through save and # reopen (#1408): nets of examples/Habitat-Schemas_developpes.qet. add_executable( diff --git a/tests/qttest/tst_elementkindinformation.cpp b/tests/qttest/tst_elementkindinformation.cpp new file mode 100644 index 000000000..7d6a947a4 --- /dev/null +++ b/tests/qttest/tst_elementkindinformation.cpp @@ -0,0 +1,145 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// A symbol's kind information (NO/NC/SW/power and the like, baked into its +// own definition and never edited after it is built -- the data +// CrossRefItem::NOElements()/NCElements() read to classify a linked contact) +// is queryable from the project database (element_kind_information), not +// only from a live Element*. QETZ-DB-FIRST 2d-2a. + +namespace { + +// One embedded symbol, carrying the given kind-information rows, placed once. +QString project(const QString &kind_informations_xml) +{ + return QStringLiteral( + "" + "" + "" + "" + "" + "" + "" + "" + "" + "box" + "%1" + "" + "" + "").arg(kind_informations_xml); +} + +QString kindInformation(const QString &name, const QString &value, bool show) +{ + return QStringLiteral("%3") + .arg(name).arg(show ? QStringLiteral("1") : QStringLiteral("0"), value); +} + +} // namespace + +class tst_elementkindinformation : public QObject +{ + Q_OBJECT + + QTemporaryDir m_dir; + int m_run = 0; + + // Runs the real binary's --run on a project written here with @p xml, + // executing @p script; returns its stdout+stderr. + QString run(const QString &xml, const QString &script) + { + const QString in = m_dir.filePath(QStringLiteral("in%1.qet").arg(m_run)); + const QString js = m_dir.filePath(QStringLiteral("script%1.js").arg(m_run)); + const QString home = m_dir.filePath(QStringLiteral("home%1").arg(m_run++)); + QDir().mkpath(home); + QFile f(in); + if (!f.open(QIODevice::WriteOnly)) return {}; + f.write(xml.toUtf8()); + f.close(); + QFile s(js); + if (!s.open(QIODevice::WriteOnly)) return {}; + s.write(script.toUtf8()); + s.close(); + + QProcessEnvironment env = QProcessEnvironment::systemEnvironment(); + env.insert(QStringLiteral("QT_QPA_PLATFORM"), QStringLiteral("offscreen")); + env.insert(QStringLiteral("QET_ENABLE_SCRIPTING"), QStringLiteral("1")); + env.insert(QStringLiteral("HOME"), home); + env.insert(QStringLiteral("XDG_CONFIG_HOME"), home + QStringLiteral("/config")); + env.insert(QStringLiteral("XDG_DATA_HOME"), home + QStringLiteral("/data")); + QProcess proc; + proc.setProcessEnvironment(env); + proc.start(QStringLiteral(QET_TEST_BINARY_PATH), {QStringLiteral("--run"), js, in}); + if (!proc.waitForFinished(60000)) return {}; + return QString::fromUtf8(proc.readAllStandardOutput() + proc.readAllStandardError()); + } + + // The ROWS line of a --run output, as a sorted list of its rows. + static QJsonArray rowsIn(const QString &out) + { + for (const QString &line : out.split(QLatin1Char('\n'))) { + const int i = line.indexOf(QStringLiteral("ROWS ")); + if (i < 0) continue; + QJsonArray rows = QJsonDocument::fromJson(line.mid(i + 5).toUtf8()).array(); + return rows; + } + return {}; + } + +private slots: + void initTestCase() + { + QVERIFY(m_dir.isValid()); + QVERIFY(QFile::exists(QStringLiteral(QET_TEST_BINARY_PATH))); + } + + // A placed symbol's kind information is in the database once it is + // placed, in the order its definition gives it, each key with its + // value and whether it is shown. + void kindInformationIsStored() + { + const QString xml = project(kindInformation(QStringLiteral("state"), QStringLiteral("NO"), false) + + kindInformation(QStringLiteral("type"), QStringLiteral("contact"), true)); + const QString script = QStringLiteral( + "qet.log('ROWS ' + JSON.stringify(qet.query(" + "'SELECT element_uuid, ord, name, value, show FROM element_kind_information ORDER BY ord')));"); + const QJsonArray rows = rowsIn(run(xml, script)); + QCOMPARE(rows.size(), 2); + const QJsonObject r0 = rows.at(0).toObject(), r1 = rows.at(1).toObject(); + QCOMPARE(r0.value(QStringLiteral("element_uuid")).toString(), + QStringLiteral("{11111111-1111-4111-8111-111111111111}")); + QCOMPARE(r0.value(QStringLiteral("name")).toString(), QStringLiteral("state")); + QCOMPARE(r0.value(QStringLiteral("value")).toString(), QStringLiteral("NO")); + QCOMPARE(r0.value(QStringLiteral("show")).toInt(), 0); + QCOMPARE(r1.value(QStringLiteral("name")).toString(), QStringLiteral("type")); + QCOMPARE(r1.value(QStringLiteral("value")).toString(), QStringLiteral("contact")); + QCOMPARE(r1.value(QStringLiteral("show")).toInt(), 1); + } + + // A symbol with no kind information at all stores no rows (not a + // crash, not a row of empty strings). + void noKindInformationMeansNoRows() + { + const QString xml = project(QString()); + const QString script = QStringLiteral( + "qet.log('ROWS ' + JSON.stringify(qet.query(" + "'SELECT * FROM element_kind_information')));"); + QCOMPARE(rowsIn(run(xml, script)).size(), 0); + } +}; + +QTEST_GUILESS_MAIN(tst_elementkindinformation) +#include "tst_elementkindinformation.moc"