Merge pull request #1590 from ispyisail/feature/element-state-db

Store each symbol's contact type in the project database
This commit is contained in:
ispyisail
2026-10-23 18:04:55 +13:00
committed by GitHub
6 changed files with 267 additions and 2 deletions
+83
View File
@@ -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);
}
/**
+10
View File
@@ -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<QPointer<Element>> m_moved_elements;
QHash<QUuid, DiagramContext> m_element_information;
QHash<QUuid, DiagramContext> 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.
+14
View File
@@ -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
+3 -2
View File
@@ -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.
+12
View File
@@ -1211,6 +1211,18 @@ target_link_libraries(tst_movedterminals PRIVATE Qt::Test Qt::Xml)
target_compile_definitions(tst_movedterminals PRIVATE
"QET_TEST_BINARY_PATH=\"$<TARGET_FILE:qelectrotech>\"")
# 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=\"$<TARGET_FILE:qelectrotech>\"")
# Symbols sharing one uuid on a folio keep their wires through save and
# reopen (#1408): nets of examples/Habitat-Schemas_developpes.qet.
add_executable(
+145
View File
@@ -0,0 +1,145 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#include <QtTest>
#include <QDir>
#include <QFile>
#include <QJsonArray>
#include <QJsonDocument>
#include <QJsonObject>
#include <QJsonValue>
#include <QProcess>
#include <QProcessEnvironment>
#include <QTemporaryDir>
// 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(
"<project title=\"kind\" version=\"0.100.1\">"
"<diagram title=\"f\" cols=\"8\" rows=\"4\" colsize=\"60\" rowsize=\"80\">"
"<elements>"
"<element orientation=\"0\" type=\"embed://import/t/box.elmt\" z=\"10\""
" uuid=\"{11111111-1111-4111-8111-111111111111}\" x=\"100\" y=\"200\"/>"
"</elements><conductors/></diagram>"
"<collection><category name=\"import\"><category name=\"t\">"
"<element name=\"box.elmt\">"
"<definition width=\"30\" hotspot_y=\"21\" version=\"0.100.0\" type=\"element\""
" link_type=\"simple\" hotspot_x=\"15\" height=\"42\">"
"<uuid uuid=\"{33333333-3333-4333-8333-333333333333}\"/>"
"<names><name lang=\"en\">box</name></names>"
"<kindInformations>%1</kindInformations><informations/>"
"<description><rect x=\"-5\" y=\"-6\" width=\"10\" height=\"12\""
" style=\"line-style:normal;line-weight:normal;filling:none;color:black\"/></description>"
"</definition></element></category></category></collection>"
"</project>").arg(kind_informations_xml);
}
QString kindInformation(const QString &name, const QString &value, bool show)
{
return QStringLiteral("<kindInformation name=\"%1\" show=\"%2\">%3</kindInformation>")
.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"