From 122678a7971acaafb6ea4ed675186539c1e2e817 Mon Sep 17 00:00:00 2001 From: ispyisail Date: Tue, 20 Oct 2026 06:02:39 +1300 Subject: [PATCH 1/3] Keep the project's numbering in the project database only QETProject no longer holds its numbering schemes and their settings in hashes and members of its own: every accessor reads and writes the project database's numbering tables (QETZ-DB-FIRST stage 2b-3), which the save already writes from. projectDataBase gains per-scheme calls (numberingSchemes, numberingScheme, setNumberingScheme, removeNumberingScheme, renameNumberingScheme, the scheme uuid and title, and the settings); setNumbering() is what reading the file writes, in one go once it is all read. QET_CHECK_NUMBERING_ROWS goes: there is one copy left to compare. The test now checks the scheme list after each change through the script API, that no part row is left without its scheme, and the saved file. Co-Authored-By: Claude Opus 5.5 --- sources/dataBase/projectdatabase.cpp | 312 ++++++++++++++++++++----- sources/dataBase/projectdatabase.h | 15 ++ sources/qetproject.cpp | 299 ++++++++++-------------- sources/qetproject.h | 25 -- tests/qttest/tst_elementautonumids.cpp | 58 +++-- 5 files changed, 426 insertions(+), 283 deletions(-) diff --git a/sources/dataBase/projectdatabase.cpp b/sources/dataBase/projectdatabase.cpp index 5e463cbc3..2cf59498c 100644 --- a/sources/dataBase/projectdatabase.cpp +++ b/sources/dataBase/projectdatabase.cpp @@ -4113,68 +4113,28 @@ QStringList projectDataBase::linkRowMismatches() /** @brief projectDataBase::setNumbering - The numbering tables hold @p numbering from now on, and nothing else - (QETZ-DB-FIRST stage 2b-1: QETProject writes them after reading its - file and after each change of its numbering) + The numbering tables hold @p numbering from now on, and nothing else: + what QETProject reads from its file (QETZ-DB-FIRST stage 2b) */ void projectDataBase::setNumbering(const Numbering &numbering) { QSqlQuery query(m_data_base); - bool ok = true; - for (const char *table : {"numbering_part", "numbering_scheme", "numbering_setting"}) - ok = ok && query.exec(QStringLiteral("DELETE FROM %1").arg(QLatin1String(table))); - - QSqlQuery scheme(m_data_base); - scheme.prepare(QStringLiteral("INSERT INTO numbering_scheme (kind, title, uuid) " - "VALUES (:kind, :title, :uuid)")); - QSqlQuery part(m_data_base); - part.prepare(QStringLiteral("INSERT INTO numbering_part (kind, title, ord, type, value, " - "increase, initialvalue, modulus, format) VALUES (:kind, :title, " - ":ord, :type, :value, :increase, :initialvalue, :modulus, :format)")); - for (const NumberingScheme &item : numbering.schemes) - { - scheme.bindValue(QStringLiteral(":kind"), item.kind); - scheme.bindValue(QStringLiteral(":title"), item.title); - scheme.bindValue(QStringLiteral(":uuid"), item.uuid.isNull() ? QVariant() - : QVariant(item.uuid.toString())); - ok = ok && scheme.exec(); - for (int i = 0 ; i < item.context.size() ; ++i) - { - const QStringList fields = item.context.itemAt(i); - part.bindValue(QStringLiteral(":kind"), item.kind); - part.bindValue(QStringLiteral(":title"), item.title); - part.bindValue(QStringLiteral(":ord"), i); - int field = 0; - for (const char *name : {":type", ":value", ":increase", ":initialvalue", ":modulus", ":format"}) { - part.bindValue(QLatin1String(name), field < fields.size() ? QVariant(fields.at(field)) - : QVariant()); - ++field; - } - ok = ok && part.exec(); + for (const char *table : {"numbering_part", "numbering_scheme", "numbering_setting"}) { + if (!query.exec(QStringLiteral("DELETE FROM %1").arg(QLatin1String(table)))) { + qDebug() << "projectDataBase::setNumbering error : " << query.lastError(); } } - - QSqlQuery setting(m_data_base); - setting.prepare(QStringLiteral("INSERT INTO numbering_setting (name, value) VALUES (:name, :value)")); - auto set = [&setting, &ok](const QString &name, const QString &value) { - setting.bindValue(QStringLiteral(":name"), name); - setting.bindValue(QStringLiteral(":value"), value); - ok = ok && setting.exec(); - }; - auto flag = [](bool value) { return value ? QStringLiteral("true") : QStringLiteral("false"); }; - set(QStringLiteral("conductor_current"), numbering.current_conductor); - set(QStringLiteral("element_current"), numbering.current_element); - set(QStringLiteral("cable_current"), numbering.current_cable); - set(QStringLiteral("freeze_new_conductors"), flag(numbering.freeze_new_conductors)); - set(QStringLiteral("freeze_new_elements"), flag(numbering.freeze_new_elements)); - set(QStringLiteral("auto_break_conductors"), flag(numbering.auto_break_conductors)); - set(QStringLiteral("cable_x_axis_first"), flag(numbering.cable_x_axis_first)); - set(QStringLiteral("cable_ask_numbering"), flag(numbering.cable_ask_numbering)); - - if (!ok) { - qDebug() << "projectDataBase::setNumbering error : " << query.lastError() - << scheme.lastError() << part.lastError() << setting.lastError(); + for (const NumberingScheme &item : numbering.schemes) { + setNumberingScheme(item.kind, item.title, item.context, item.uuid); } + setNumberingSetting(QStringLiteral("conductor_current"), numbering.current_conductor); + setNumberingSetting(QStringLiteral("element_current"), numbering.current_element); + setNumberingSetting(QStringLiteral("cable_current"), numbering.current_cable); + setNumberingFlag(QStringLiteral("freeze_new_conductors"), numbering.freeze_new_conductors); + setNumberingFlag(QStringLiteral("freeze_new_elements"), numbering.freeze_new_elements); + setNumberingFlag(QStringLiteral("auto_break_conductors"), numbering.auto_break_conductors); + setNumberingFlag(QStringLiteral("cable_x_axis_first"), numbering.cable_x_axis_first); + setNumberingFlag(QStringLiteral("cable_ask_numbering"), numbering.cable_ask_numbering); } /** @@ -4225,6 +4185,248 @@ projectDataBase::Numbering projectDataBase::numbering() const return numbering; } +/** + @brief projectDataBase::numberingSchemes + @return the numbering schemes of @p kind (conductor, element, folio or + cable) by title +*/ +QHash projectDataBase::numberingSchemes(const QString &kind) const +{ + QHash schemes; + QSqlQuery query(m_data_base); + query.prepare(QStringLiteral("SELECT title FROM numbering_scheme WHERE kind = :kind")); + query.bindValue(QStringLiteral(":kind"), kind); + query.exec(); + while (query.next()) { + schemes.insert(query.value(0).toString(), NumerotationContext()); + } + query.prepare(QStringLiteral("SELECT title, type, value, increase, initialvalue, modulus, format " + "FROM numbering_part WHERE kind = :kind ORDER BY title, ord")); + query.bindValue(QStringLiteral(":kind"), kind); + query.exec(); + while (query.next()) { + const QString title = query.value(0).toString(); + if (!schemes.contains(title)) { + continue; + } + schemes[title].addValue(query.value(1).toString(), query.value(2).toString(), + query.value(3).toInt(), query.value(4).toInt(), + query.value(5).toInt(), query.value(6).toString()); + } + return schemes; +} + +/** + @brief projectDataBase::hasNumberingScheme + @return true if there is a numbering scheme of @p kind titled @p title +*/ +bool projectDataBase::hasNumberingScheme(const QString &kind, const QString &title) const +{ + QSqlQuery query(m_data_base); + query.prepare(QStringLiteral("SELECT 1 FROM numbering_scheme WHERE kind = :kind AND title = :title")); + query.bindValue(QStringLiteral(":kind"), kind); + query.bindValue(QStringLiteral(":title"), title); + return query.exec() && query.next(); +} + +/** + @brief projectDataBase::numberingScheme + @return the numbering scheme of @p kind titled @p title, empty if there + is none +*/ +NumerotationContext projectDataBase::numberingScheme(const QString &kind, const QString &title) const +{ + NumerotationContext context; + QSqlQuery query(m_data_base); + query.prepare(QStringLiteral("SELECT type, value, increase, initialvalue, modulus, format " + "FROM numbering_part WHERE kind = :kind AND title = :title ORDER BY ord")); + query.bindValue(QStringLiteral(":kind"), kind); + query.bindValue(QStringLiteral(":title"), title); + query.exec(); + while (query.next()) { + context.addValue(query.value(0).toString(), query.value(1).toString(), + query.value(2).toInt(), query.value(3).toInt(), + query.value(4).toInt(), query.value(5).toString()); + } + return context; +} + +/** + @brief projectDataBase::numberingSchemeUuid + @return the uuid of the numbering scheme of @p kind titled @p title, + null if there is no such scheme or it has none (only element schemes + have one) +*/ +QUuid projectDataBase::numberingSchemeUuid(const QString &kind, const QString &title) const +{ + QSqlQuery query(m_data_base); + query.prepare(QStringLiteral("SELECT uuid FROM numbering_scheme WHERE kind = :kind AND title = :title")); + query.bindValue(QStringLiteral(":kind"), kind); + query.bindValue(QStringLiteral(":title"), title); + if (query.exec() && query.next()) { + return QUuid(query.value(0).toString()); + } + return QUuid(); +} + +/** + @brief projectDataBase::numberingSchemeTitle + @return the title of the numbering scheme of @p kind whose uuid is + @p uuid, empty if there is none +*/ +QString projectDataBase::numberingSchemeTitle(const QString &kind, const QUuid &uuid) const +{ + if (uuid.isNull()) { + return QString(); + } + QSqlQuery query(m_data_base); + query.prepare(QStringLiteral("SELECT title FROM numbering_scheme WHERE kind = :kind AND uuid = :uuid")); + query.bindValue(QStringLiteral(":kind"), kind); + query.bindValue(QStringLiteral(":uuid"), uuid.toString()); + if (query.exec() && query.next()) { + return query.value(0).toString(); + } + return QString(); +} + +/** + @brief projectDataBase::setNumberingScheme + Add the numbering scheme of @p kind titled @p title, or replace it: + its parts are those of @p context, its uuid @p uuid +*/ +void projectDataBase::setNumberingScheme(const QString &kind, const QString &title, + const NumerotationContext &context, const QUuid &uuid) +{ + QSqlQuery query(m_data_base); + bool ok = true; + query.prepare(QStringLiteral("DELETE FROM numbering_part WHERE kind = :kind AND title = :title")); + query.bindValue(QStringLiteral(":kind"), kind); + query.bindValue(QStringLiteral(":title"), title); + ok = ok && query.exec(); + query.prepare(QStringLiteral("INSERT OR REPLACE INTO numbering_scheme (kind, title, uuid) " + "VALUES (:kind, :title, :uuid)")); + query.bindValue(QStringLiteral(":kind"), kind); + query.bindValue(QStringLiteral(":title"), title); + query.bindValue(QStringLiteral(":uuid"), uuid.isNull() ? QVariant() : QVariant(uuid.toString())); + ok = ok && query.exec(); + + query.prepare(QStringLiteral("INSERT INTO numbering_part (kind, title, ord, type, value, " + "increase, initialvalue, modulus, format) VALUES (:kind, :title, " + ":ord, :type, :value, :increase, :initialvalue, :modulus, :format)")); + for (int i = 0 ; i < context.size() ; ++i) + { + const QStringList fields = context.itemAt(i); + query.bindValue(QStringLiteral(":kind"), kind); + query.bindValue(QStringLiteral(":title"), title); + query.bindValue(QStringLiteral(":ord"), i); + int field = 0; + for (const char *name : {":type", ":value", ":increase", ":initialvalue", ":modulus", ":format"}) { + query.bindValue(QLatin1String(name), field < fields.size() ? QVariant(fields.at(field)) + : QVariant()); + ++field; + } + ok = ok && query.exec(); + } + if (!ok) { + qDebug() << "projectDataBase::setNumberingScheme error : " << query.lastError(); + } +} + +/** + @brief projectDataBase::removeNumberingScheme + Remove the numbering scheme of @p kind titled @p title +*/ +void projectDataBase::removeNumberingScheme(const QString &kind, const QString &title) +{ + QSqlQuery query(m_data_base); + for (const char *table : {"numbering_part", "numbering_scheme"}) + { + query.prepare(QStringLiteral("DELETE FROM %1 WHERE kind = :kind AND title = :title") + .arg(QLatin1String(table))); + query.bindValue(QStringLiteral(":kind"), kind); + query.bindValue(QStringLiteral(":title"), title); + if (!query.exec()) { + qDebug() << "projectDataBase::removeNumberingScheme error : " << query.lastError(); + } + } +} + +/** + @brief projectDataBase::renameNumberingScheme + The numbering scheme of @p kind titled @p old_title is titled + @p new_title from now on, with the same parts and uuid. The caller + makes sure no scheme of @p kind is titled @p new_title. +*/ +void projectDataBase::renameNumberingScheme(const QString &kind, const QString &old_title, + const QString &new_title) +{ + QSqlQuery query(m_data_base); + for (const char *table : {"numbering_scheme", "numbering_part"}) + { + query.prepare(QStringLiteral("UPDATE %1 SET title = :new_title " + "WHERE kind = :kind AND title = :old_title") + .arg(QLatin1String(table))); + query.bindValue(QStringLiteral(":new_title"), new_title); + query.bindValue(QStringLiteral(":kind"), kind); + query.bindValue(QStringLiteral(":old_title"), old_title); + if (!query.exec()) { + qDebug() << "projectDataBase::renameNumberingScheme error : " << query.lastError(); + } + } +} + +/** + @brief projectDataBase::numberingSetting + @return the numbering setting @p name (setNumbering() lists them), + @p default_value if it was never set +*/ +QString projectDataBase::numberingSetting(const QString &name, const QString &default_value) const +{ + QSqlQuery query(m_data_base); + query.prepare(QStringLiteral("SELECT value FROM numbering_setting WHERE name = :name")); + query.bindValue(QStringLiteral(":name"), name); + if (query.exec() && query.next()) { + return query.value(0).toString(); + } + return default_value; +} + +/** + @brief projectDataBase::numberingFlag + @return numberingSetting() @p name as a flag, @p default_value if it + was never set +*/ +bool projectDataBase::numberingFlag(const QString &name, bool default_value) const +{ + return numberingSetting(name, default_value ? QStringLiteral("true") : QStringLiteral("false")) + == QLatin1String("true"); +} + +/** + @brief projectDataBase::setNumberingSetting + The numbering setting @p name is @p value from now on +*/ +void projectDataBase::setNumberingSetting(const QString &name, const QString &value) +{ + QSqlQuery query(m_data_base); + query.prepare(QStringLiteral("INSERT OR REPLACE INTO numbering_setting (name, value) " + "VALUES (:name, :value)")); + query.bindValue(QStringLiteral(":name"), name); + query.bindValue(QStringLiteral(":value"), value); + if (!query.exec()) { + qDebug() << "projectDataBase::setNumberingSetting error : " << query.lastError(); + } +} + +/** + @brief projectDataBase::setNumberingFlag + setNumberingSetting() for a flag +*/ +void projectDataBase::setNumberingFlag(const QString &name, bool value) +{ + setNumberingSetting(name, value ? QStringLiteral("true") : QStringLiteral("false")); +} + /** @brief projectDataBase::savedLinks The links @p element saves, read from its link rows, in their order diff --git a/sources/dataBase/projectdatabase.h b/sources/dataBase/projectdatabase.h index 8d75dc585..516800a1e 100644 --- a/sources/dataBase/projectdatabase.h +++ b/sources/dataBase/projectdatabase.h @@ -179,6 +179,21 @@ class projectDataBase : public QObject }; void setNumbering(const Numbering &numbering); Numbering numbering() const; + QHash numberingSchemes(const QString &kind) const; + bool hasNumberingScheme(const QString &kind, const QString &title) const; + NumerotationContext numberingScheme(const QString &kind, const QString &title) const; + QUuid numberingSchemeUuid(const QString &kind, const QString &title) const; + QString numberingSchemeTitle(const QString &kind, const QUuid &uuid) const; + void setNumberingScheme(const QString &kind, const QString &title, + const NumerotationContext &context, + const QUuid &uuid = QUuid()); + void removeNumberingScheme(const QString &kind, const QString &title); + void renameNumberingScheme(const QString &kind, const QString &old_title, + const QString &new_title); + QString numberingSetting(const QString &name, const QString &default_value = QString()) const; + bool numberingFlag(const QString &name, bool default_value) const; + void setNumberingSetting(const QString &name, const QString &value); + void setNumberingFlag(const QString &name, bool value); bool savedLinks(const Element *element, QList> *links); //The link rows are written (opening filled them): a symbol //built links from them (Element::initLink()) diff --git a/sources/qetproject.cpp b/sources/qetproject.cpp index 8b5ec0b61..91ce3d1e5 100644 --- a/sources/qetproject.cpp +++ b/sources/qetproject.cpp @@ -93,7 +93,7 @@ m_project_properties_handler{this} QSettings settings; //Read auto break conductor default from global settings - m_auto_break_conductor = settings.value(QStringLiteral("diagrameditor/auto_break_conductor"), false).toBool(); + setAutoBreakConductor(settings.value(QStringLiteral("diagrameditor/auto_break_conductor"), false).toBool()); int size = settings.beginReadArray(QStringLiteral("diagrameditor/defaultguides")); for (int i = 0; i < size; ++i) { @@ -140,8 +140,6 @@ m_project_properties_handler{this} setCurrentCableAutoNum(cableData.second); } } - //m_auto_break_conductor above is read from the settings directly - writeNumberingRows(); } ProjectPropertiesHandler &QETProject::projectPropertiesHandler() @@ -1446,7 +1444,7 @@ void QETProject::setDefaultXRefProperties(QHash hash) */ QHash QETProject::conductorAutoNum() const { - return m_conductor_autonum; + return m_data_base.numberingSchemes(QStringLiteral("conductor")); } /** @@ -1455,7 +1453,7 @@ QHash QETProject::conductorAutoNum() const */ QHash QETProject::elementAutoNum() const { - return m_element_autonum; + return m_data_base.numberingSchemes(QStringLiteral("element")); } /** @@ -1465,7 +1463,7 @@ QHash QETProject::elementAutoNum() const */ QHash QETProject::cableAutoNum() const { - return m_cable_autonum; + return m_data_base.numberingSchemes(QStringLiteral("cable")); } /** @@ -1475,8 +1473,8 @@ QHash QETProject::cableAutoNum() const */ QString QETProject::elementAutoNumFormula (const QString& key) const { - if (m_element_autonum.contains(key)) { - return autonum::numerotationContextToFormula(m_element_autonum[key]); + if (m_data_base.hasNumberingScheme(QStringLiteral("element"), key)) { + return autonum::numerotationContextToFormula(m_data_base.numberingScheme(QStringLiteral("element"), key)); } return QString(); @@ -1488,7 +1486,7 @@ QString QETProject::elementAutoNumFormula (const QString& key) const */ QString QETProject::elementAutoNumCurrentFormula() const { - return elementAutoNumFormula(m_current_element_autonum); + return elementAutoNumFormula(elementCurrentAutoNum()); } /** @@ -1497,7 +1495,7 @@ QString QETProject::elementAutoNumCurrentFormula() const */ QString QETProject::elementCurrentAutoNum () const { - return m_current_element_autonum; + return m_data_base.numberingSetting(QStringLiteral("element_current")); } /** @@ -1505,8 +1503,7 @@ QString QETProject::elementCurrentAutoNum () const @param autoNum : set the current element autonum to autonum */ void QETProject::setCurrrentElementAutonum(QString autoNum) { - m_current_element_autonum = std::move(autoNum); - writeNumberingRows(); + m_data_base.setNumberingSetting(QStringLiteral("element_current"), autoNum); } /** @@ -1516,8 +1513,8 @@ void QETProject::setCurrrentElementAutonum(QString autoNum) { */ QString QETProject::cableAutoNumFormula(const QString& key) const { - if (m_cable_autonum.contains(key)) { - return autonum::numerotationContextToFormula(m_cable_autonum.value(key)); + if (m_data_base.hasNumberingScheme(QStringLiteral("cable"), key)) { + return autonum::numerotationContextToFormula(m_data_base.numberingScheme(QStringLiteral("cable"), key)); } return QString(); @@ -1532,8 +1529,9 @@ QString QETProject::cableAutoNumFormula(const QString& key) const */ bool QETProject::hasCableAutoNum() const { - if (m_current_cable_autonum.isEmpty()) return false; - return !cableAutoNum(m_current_cable_autonum).isEmpty(); + const QString current = cableCurrentAutoNum(); + if (current.isEmpty()) return false; + return !cableAutoNum(current).isEmpty(); } /** @@ -1542,7 +1540,7 @@ bool QETProject::hasCableAutoNum() const */ QString QETProject::cableCurrentAutoNum() const { - return m_current_cable_autonum; + return m_data_base.numberingSetting(QStringLiteral("cable_current")); } /** @@ -1551,8 +1549,7 @@ QString QETProject::cableCurrentAutoNum() const */ void QETProject::setCurrentCableAutoNum(QString autoNum) { - m_current_cable_autonum = std::move(autoNum); - writeNumberingRows(); + m_data_base.setNumberingSetting(QStringLiteral("cable_current"), autoNum); } /** @@ -1572,7 +1569,7 @@ QString QETProject::cableAutoNumRuleName() */ bool QETProject::cableXAxisFirst() const { - return m_cable_axis_x_first; + return m_data_base.numberingFlag(QStringLiteral("cable_x_axis_first"), true); } /** @@ -1584,8 +1581,7 @@ bool QETProject::cableXAxisFirst() const */ void QETProject::setCableXAxisFirst(bool x_axis_first) { - m_cable_axis_x_first = x_axis_first; - writeNumberingRows(); + m_data_base.setNumberingFlag(QStringLiteral("cable_x_axis_first"), x_axis_first); } /** @@ -1598,7 +1594,7 @@ void QETProject::setCableXAxisFirst(bool x_axis_first) */ bool QETProject::cableAskNumbering() const { - return m_cable_ask_numbering; + return m_data_base.numberingFlag(QStringLiteral("cable_ask_numbering"), true); } /** @@ -1611,8 +1607,7 @@ bool QETProject::cableAskNumbering() const */ void QETProject::setCableAskNumbering(bool ask) { - m_cable_ask_numbering = ask; - writeNumberingRows(); + m_data_base.setNumberingFlag(QStringLiteral("cable_ask_numbering"), ask); } /** @@ -1645,8 +1640,8 @@ void QETProject::renumberElementsBySchemeTitle(const QString &scheme_title) */ QString QETProject::conductorAutoNumFormula (const QString& key) const { - if (m_conductor_autonum.contains(key)) - return autonum::numerotationContextToFormula(m_conductor_autonum.value(key)); + if (m_data_base.hasNumberingScheme(QStringLiteral("conductor"), key)) + return autonum::numerotationContextToFormula(m_data_base.numberingScheme(QStringLiteral("conductor"), key)); else return QString(); } @@ -1657,7 +1652,7 @@ QString QETProject::conductorAutoNumFormula (const QString& key) const */ QString QETProject::conductorCurrentAutoNum () const { - return m_current_conductor_autonum; + return m_data_base.numberingSetting(QStringLiteral("conductor_current")); } /** @@ -1665,8 +1660,7 @@ QString QETProject::conductorCurrentAutoNum () const @param autoNum set the current conductor autonum to autonum */ void QETProject::setCurrentConductorAutoNum(QString autoNum) { - m_current_conductor_autonum = std::move(autoNum); - writeNumberingRows(); + m_data_base.setNumberingSetting(QStringLiteral("conductor_current"), autoNum); } /** @@ -1675,7 +1669,7 @@ void QETProject::setCurrentConductorAutoNum(QString autoNum) { */ QHash QETProject::folioAutoNum() const { - return m_folio_autonum; + return m_data_base.numberingSchemes(QStringLiteral("folio")); } /** @@ -1686,8 +1680,7 @@ QHash QETProject::folioAutoNum() const @param context */ void QETProject::addConductorAutoNum(const QString& key, const NumerotationContext& context) { - m_conductor_autonum.insert(key, context); - writeNumberingRows(); + m_data_base.setNumberingScheme(QStringLiteral("conductor"), key, context); emit autoNumContextUpdated(); } @@ -1725,14 +1718,12 @@ void QETProject::addElementAutoNum(const QString &key, } } if (scheme_id.isNull()) { - scheme_id = m_element_autonum_id.value(key); + scheme_id = elementAutoNumId(key); } if (scheme_id.isNull()) { scheme_id = QUuid::createUuid(); } - m_element_autonum_id.insert(key, scheme_id); - m_element_autonum.insert(key, context); - writeNumberingRows(); + m_data_base.setNumberingScheme(QStringLiteral("element"), key, context, scheme_id); emit elementAutoNumAdded(key); emit autoNumContextUpdated(); } @@ -1744,7 +1735,7 @@ void QETProject::addElementAutoNum(const QString &key, */ QUuid QETProject::elementAutoNumId(const QString &title) const { - return m_element_autonum_id.value(title); + return m_data_base.numberingSchemeUuid(QStringLiteral("element"), title); } /** @@ -1754,16 +1745,7 @@ QUuid QETProject::elementAutoNumId(const QString &title) const */ QString QETProject::elementAutoNumTitle(const QUuid &id) const { - if (id.isNull()) { - return QString(); - } - for (auto it = m_element_autonum_id.constBegin(); - it != m_element_autonum_id.constEnd(); ++it) { - if (it.value() == id) { - return it.key(); - } - } - return QString(); + return m_data_base.numberingSchemeTitle(QStringLiteral("element"), id); } /** @@ -1779,18 +1761,17 @@ QString QETProject::elementAutoNumTitle(const QUuid &id) const bool QETProject::renameElementAutoNum(const QString &old_title, const QString &new_title) { if (old_title == new_title) { - return m_element_autonum.contains(old_title); + return m_data_base.hasNumberingScheme(QStringLiteral("element"), old_title); } - if (!m_element_autonum.contains(old_title) - || m_element_autonum.contains(new_title) + if (!m_data_base.hasNumberingScheme(QStringLiteral("element"), old_title) + || m_data_base.hasNumberingScheme(QStringLiteral("element"), new_title) || new_title.isEmpty()) { return false; } - m_element_autonum.insert(new_title, m_element_autonum.take(old_title)); - m_element_autonum_id.insert(new_title, m_element_autonum_id.take(old_title)); - if (m_current_element_autonum == old_title) { - m_current_element_autonum = new_title; + m_data_base.renameNumberingScheme(QStringLiteral("element"), old_title, new_title); + if (elementCurrentAutoNum() == old_title) { + m_data_base.setNumberingSetting(QStringLiteral("element_current"), new_title); } for (Diagram *d : std::as_const(m_diagrams_list)) { @@ -1804,7 +1785,6 @@ bool QETProject::renameElementAutoNum(const QString &old_title, const QString &n } } - writeNumberingRows(); emit elementAutoNumRemoved(old_title); emit elementAutoNumAdded(new_title); emit autoNumContextUpdated(); @@ -1835,8 +1815,8 @@ QString QETProject::elementAutoNumNameClash(const QString &name, const QString &ignored_title) const { const QString wanted = normalizedAutoNumName(name); - for (auto it = m_element_autonum.constBegin(); - it != m_element_autonum.constEnd(); ++it) { + const QHash schemes = m_data_base.numberingSchemes(QStringLiteral("element")); + for (auto it = schemes.constBegin(); it != schemes.constEnd(); ++it) { if (it.key() == ignored_title) { continue; } @@ -1926,8 +1906,8 @@ void QETProject::linkElementsToElementAutoNums(Diagram *folio) QHash QETProject::elementAutoNumTitlesByFormula() const { QHash titles_by_formula; - for (auto it = m_element_autonum.constBegin(); - it != m_element_autonum.constEnd(); ++it) { + const QHash schemes = m_data_base.numberingSchemes(QStringLiteral("element")); + for (auto it = schemes.constBegin(); it != schemes.constEnd(); ++it) { titles_by_formula[autonum::numerotationContextToFormula(it.value())] << it.key(); } return titles_by_formula; @@ -2032,8 +2012,7 @@ QList QETProject::symbolNumbering() const @param context */ void QETProject::addFolioAutoNum(const QString& key, const NumerotationContext& context) { - m_folio_autonum.insert(key, context); - writeNumberingRows(); + m_data_base.setNumberingScheme(QStringLiteral("folio"), key, context); emit autoNumContextUpdated(); } @@ -2048,19 +2027,19 @@ void QETProject::addFolioAutoNum(const QString& key, const NumerotationContext& void QETProject::addCableAutoNum(const QString& key, const NumerotationContext& context) { if (!key.isEmpty()) { - const QStringList others = m_cable_autonum.keys(); + const QStringList others = cableAutoNum().keys(); for (const QString &other : others) { - if (other != key) m_cable_autonum.remove(other); + if (other != key) m_data_base.removeNumberingScheme(QStringLiteral("cable"), other); } } - m_cable_autonum.insert(key, context); + m_data_base.setNumberingScheme(QStringLiteral("cable"), key, context); //The rule in use must always be one which exists: after a save //under another name the old entry is gone, so follow the new one. - if (!key.isEmpty() && (m_current_cable_autonum.isEmpty() - || !m_cable_autonum.contains(m_current_cable_autonum))) { - m_current_cable_autonum = key; + const QString current = cableCurrentAutoNum(); + if (!key.isEmpty() && (current.isEmpty() + || !m_data_base.hasNumberingScheme(QStringLiteral("cable"), current))) { + m_data_base.setNumberingSetting(QStringLiteral("cable_current"), key); } - writeNumberingRows(); emit autoNumContextUpdated(); } @@ -2070,8 +2049,7 @@ void QETProject::addCableAutoNum(const QString& key, const NumerotationContext& @param key */ void QETProject::removeConductorAutoNum(const QString& key) { - m_conductor_autonum.remove(key); - writeNumberingRows(); + m_data_base.removeNumberingScheme(QStringLiteral("conductor"), key); } /** @@ -2081,9 +2059,7 @@ void QETProject::removeConductorAutoNum(const QString& key) { */ void QETProject::removeElementAutoNum(const QString& key) { - m_element_autonum.remove(key); - m_element_autonum_id.remove(key); - writeNumberingRows(); + m_data_base.removeNumberingScheme(QStringLiteral("element"), key); emit elementAutoNumRemoved(key); } @@ -2093,8 +2069,7 @@ void QETProject::removeElementAutoNum(const QString& key) @param key */ void QETProject::removeFolioAutoNum(const QString& key) { - m_folio_autonum.remove(key); - writeNumberingRows(); + m_data_base.removeNumberingScheme(QStringLiteral("folio"), key); } /** @@ -2104,12 +2079,12 @@ void QETProject::removeFolioAutoNum(const QString& key) { */ void QETProject::removeCableAutoNum(const QString& key) { - m_cable_autonum.remove(key); - if (m_current_cable_autonum == key) { - m_current_cable_autonum = m_cable_autonum.isEmpty() - ? QString() : m_cable_autonum.keys().first(); + m_data_base.removeNumberingScheme(QStringLiteral("cable"), key); + if (cableCurrentAutoNum() == key) { + const QStringList left = cableAutoNum().keys(); + m_data_base.setNumberingSetting(QStringLiteral("cable_current"), + left.isEmpty() ? QString() : left.first()); } - writeNumberingRows(); emit autoNumContextUpdated(); } @@ -2121,8 +2096,7 @@ void QETProject::removeCableAutoNum(const QString& key) */ NumerotationContext QETProject::conductorAutoNum (const QString &key) const { - if (m_conductor_autonum.contains(key)) return m_conductor_autonum[key]; - else return NumerotationContext(); + return m_data_base.numberingScheme(QStringLiteral("conductor"), key); } /** @@ -2132,8 +2106,7 @@ NumerotationContext QETProject::conductorAutoNum (const QString &key) const @param key */ NumerotationContext QETProject::elementAutoNum (const QString &key) { - if (m_element_autonum.contains(key)) return m_element_autonum[key]; - else return NumerotationContext(); + return m_data_base.numberingScheme(QStringLiteral("element"), key); } /** @@ -2144,8 +2117,7 @@ NumerotationContext QETProject::elementAutoNum (const QString &key) { */ NumerotationContext QETProject::folioAutoNum (const QString &key) const { - if (m_folio_autonum.contains(key)) return m_folio_autonum[key]; - else return NumerotationContext(); + return m_data_base.numberingScheme(QStringLiteral("folio"), key); } /** @@ -2156,8 +2128,7 @@ NumerotationContext QETProject::folioAutoNum (const QString &key) const */ NumerotationContext QETProject::cableAutoNum(const QString &key) const { - if (m_cable_autonum.contains(key)) return m_cable_autonum.value(key); - else return NumerotationContext(); + return m_data_base.numberingScheme(QStringLiteral("cable"), key); } /** @@ -2193,7 +2164,7 @@ void QETProject::freezeNewConductorLabel(bool freeze, int from, int to) { */ bool QETProject::isFreezeNewConductors() { - return m_freeze_new_conductors; + return m_data_base.numberingFlag(QStringLiteral("freeze_new_conductors"), false); } /** @@ -2201,8 +2172,7 @@ bool QETProject::isFreezeNewConductors() Set Project Wide freeze new conductors */ void QETProject::setFreezeNewConductors(bool set) { - m_freeze_new_conductors = set; - writeNumberingRows(); + m_data_base.setNumberingFlag(QStringLiteral("freeze_new_conductors"), set); } /** @@ -2238,7 +2208,7 @@ void QETProject::freezeNewElementLabel(bool freeze, int from, int to) { */ bool QETProject::isFreezeNewElements() { - return m_freeze_new_elements; + return m_data_base.numberingFlag(QStringLiteral("freeze_new_elements"), false); } /** @@ -2246,8 +2216,7 @@ bool QETProject::isFreezeNewElements() Set Project Wide freeze new elements */ void QETProject::setFreezeNewElements(bool set) { - m_freeze_new_elements = set; - writeNumberingRows(); + m_data_base.setNumberingFlag(QStringLiteral("freeze_new_elements"), set); } /** @@ -2368,7 +2337,7 @@ void QETProject::setWiringRules(const WiringRules::Settings &rules) */ bool QETProject::autoBreakConductor() const { - return m_auto_break_conductor; + return m_data_base.numberingFlag(QStringLiteral("auto_break_conductors"), false); } /** @@ -2379,9 +2348,7 @@ bool QETProject::autoBreakConductor() const */ void QETProject::setAutoBreakConductor(bool abc) { - if (abc != m_auto_break_conductor) - m_auto_break_conductor = abc; - writeNumberingRows(); + m_data_base.setNumberingFlag(QStringLiteral("auto_break_conductors"), abc); } /** @@ -3970,16 +3937,39 @@ void QETProject::readDefaultPropertiesXml(QDomDocument &xml_project) m_default_xref_properties.insert(elmt.attribute(QStringLiteral("type")), xrp); } } + //The numbering read here goes to the project database, its only + //home (QETZ-DB-FIRST stage 2b-3), in one go once it is all read + QHash conductor_autonum = conductorAutoNum(); + QHash folio_autonum = folioAutoNum(); + QHash element_autonum = elementAutoNum(); + QHash element_autonum_id; + for (auto it = element_autonum.cbegin() ; it != element_autonum.cend() ; ++it) { + element_autonum_id.insert(it.key(), elementAutoNumId(it.key())); + } + QHash cable_autonum = cableAutoNum(); + projectDataBase::Numbering numbering; + numbering.current_conductor = conductorCurrentAutoNum(); + numbering.current_element = elementCurrentAutoNum(); + numbering.current_cable = cableCurrentAutoNum(); + numbering.freeze_new_conductors = isFreezeNewConductors(); + numbering.freeze_new_elements = isFreezeNewElements(); + numbering.auto_break_conductors = autoBreakConductor(); + numbering.cable_x_axis_first = cableXAxisFirst(); + numbering.cable_ask_numbering = cableAskNumbering(); + auto element_title = [&element_autonum_id](const QUuid &id) { + return id.isNull() ? QString() : element_autonum_id.key(id); + }; + if (!conds_autonums.isNull()) { - m_current_conductor_autonum = conds_autonums.attribute(QStringLiteral("current_autonum")); - m_freeze_new_conductors = conds_autonums.attribute(QStringLiteral("freeze_new_conductors")) == QLatin1String("true"); - m_auto_break_conductor = conds_autonums.attribute(QStringLiteral("auto_break_conductors")) == QLatin1String("true"); + numbering.current_conductor = conds_autonums.attribute(QStringLiteral("current_autonum")); + numbering.freeze_new_conductors = conds_autonums.attribute(QStringLiteral("freeze_new_conductors")) == QLatin1String("true"); + numbering.auto_break_conductors = conds_autonums.attribute(QStringLiteral("auto_break_conductors")) == QLatin1String("true"); for (auto elmt : QET::findInDomElement(conds_autonums, QStringLiteral("conductor_autonum"))) { NumerotationContext nc; nc.fromXml(elmt); - m_conductor_autonum.insert(elmt.attribute("title"), nc); + conductor_autonum.insert(elmt.attribute("title"), nc); } } if (!folio_autonums.isNull()) @@ -3988,20 +3978,20 @@ void QETProject::readDefaultPropertiesXml(QDomDocument &xml_project) { NumerotationContext nc; nc.fromXml(elmt); - m_folio_autonum.insert(elmt.attribute(QStringLiteral("title")), nc); + folio_autonum.insert(elmt.attribute(QStringLiteral("title")), nc); } } if (!element_autonums.isNull()) { - m_current_element_autonum = element_autonums.attribute(QStringLiteral("current_autonum")); - m_freeze_new_elements = element_autonums.attribute(QStringLiteral("freeze_new_elements")) == QLatin1String("true"); + numbering.current_element = element_autonums.attribute(QStringLiteral("current_autonum")); + numbering.freeze_new_elements = element_autonums.attribute(QStringLiteral("freeze_new_elements")) == QLatin1String("true"); for (auto elmt : QET::findInDomElement(element_autonums, QStringLiteral("element_autonum"))) { NumerotationContext nc; nc.fromXml(elmt); const QString title = elmt.attribute(QStringLiteral("title")); QUuid id(elmt.attribute(QStringLiteral("id"))); - if (id.isNull() || !elementAutoNumTitle(id).isEmpty()) { + if (id.isNull() || !element_title(id).isEmpty()) { //Saved before schemes had an id: derive one, the //same on every load of the file. m_legacy_element_autonums = true; @@ -4010,30 +4000,30 @@ void QETProject::readDefaultPropertiesXml(QDomDocument &xml_project) id = derivedItemUuid(QStringLiteral("element_autonum"), m_uuid.toString() + QLatin1Char('\n') + title); } - m_element_autonum.insert(title, nc); - m_element_autonum_id.insert(title, id); + element_autonum.insert(title, nc); + element_autonum_id.insert(title, id); } //The id is authoritative, the title is kept for older versions - const QString current_title = elementAutoNumTitle( + const QString current_title = element_title( QUuid(element_autonums.attribute(QStringLiteral("current_autonum_id")))); if (!current_title.isEmpty()) { - m_current_element_autonum = current_title; + numbering.current_element = current_title; } } if (!cable_autonums.isNull()) { - m_current_cable_autonum = cable_autonums.attribute(QStringLiteral("current_autonum")); + numbering.current_cable = cable_autonums.attribute(QStringLiteral("current_autonum")); //Which axis the cables are numbered along. A file written //before this existed has no say in it and stays with the //axis the terminals have always started from. - m_cable_axis_x_first = cable_autonums.attribute( + numbering.cable_x_axis_first = cable_autonums.attribute( QStringLiteral("axis_priority"), QStringLiteral("x")) != QLatin1String("y"); //Whether the numbering question was already answered with //no in this project. A file which does not hold the answer //yet asks again rather than staying silent: it is his own //answer per project which is kept, not a default saying no. - m_cable_ask_numbering = cable_autonums.attribute( + numbering.cable_ask_numbering = cable_autonums.attribute( QStringLiteral("ask_numbering_rule"), QStringLiteral("true")) != QLatin1String("false"); for (auto elmt : QET::findInDomElement(cable_autonums, QStringLiteral("cable_autonum"))) @@ -4041,16 +4031,27 @@ void QETProject::readDefaultPropertiesXml(QDomDocument &xml_project) NumerotationContext nc; nc.fromXml(elmt); if (elmt.attribute(QStringLiteral("title")).isEmpty()) continue; - m_cable_autonum.insert(elmt.attribute(QStringLiteral("title")), nc); + cable_autonum.insert(elmt.attribute(QStringLiteral("title")), nc); } //A file written by hand, or written before this rule existed, //may name a rule which is not in the list: the numbering must //never be left pointing at something which is not there. - if (!m_cable_autonum.contains(m_current_cable_autonum)) { - m_current_cable_autonum = m_cable_autonum.isEmpty() - ? QString() : m_cable_autonum.keys().first(); + if (!cable_autonum.contains(numbering.current_cable)) { + numbering.current_cable = cable_autonum.isEmpty() + ? QString() : cable_autonum.keys().first(); } } + auto add = [&numbering](const QString &kind, const QHash &schemes, + const QHash &ids) { + for (auto it = schemes.cbegin() ; it != schemes.cend() ; ++it) + numbering.schemes << projectDataBase::NumberingScheme{kind, it.key(), ids.value(it.key()), it.value()}; + }; + add(QStringLiteral("conductor"), conductor_autonum, {}); + add(QStringLiteral("folio"), folio_autonum, {}); + add(QStringLiteral("element"), element_autonum, element_autonum_id); + add(QStringLiteral("cable"), cable_autonum, {}); + m_data_base.setNumbering(numbering); + // Read guides from XML (if missing, e.g. in old projects, list stays empty) m_default_guides.clear(); @@ -4063,7 +4064,6 @@ void QETProject::readDefaultPropertiesXml(QDomDocument &xml_project) m_default_guides.append(g); } } - writeNumberingRows(); } /** @@ -4247,24 +4247,9 @@ void QETProject::writeDefaultPropertiesXml(QDomElement &xml_element) } xml_element.appendChild(xrefs_elmt); - //The numbering is written from the project database's rows - //(QETZ-DB-FIRST stage 2b-2), which the project keeps in step - //(writeNumberingRows()) + //The numbering is written from the project database, its only + //home (QETZ-DB-FIRST stage 2b) writeNumberingXml(m_data_base.numbering(), xml_element); - //QET_CHECK_NUMBERING_ROWS=1: say whether the project database's - //numbering rows write what the project holds. A test and - //debugging aid; it changes nothing. - if (qEnvironmentVariableIntValue("QET_CHECK_NUMBERING_ROWS") == 1) { - QDomDocument from_project, from_rows; - QDomElement project_root = from_project.createElement(QStringLiteral("numbering")); - QDomElement rows_root = from_rows.createElement(QStringLiteral("numbering")); - from_project.appendChild(project_root); - from_rows.appendChild(rows_root); - writeNumberingXml(numberingOfProject(), project_root); - writeNumberingXml(m_data_base.numbering(), rows_root); - const bool same = from_project.toString() == from_rows.toString(); - qWarning().noquote() << "numbering rows:" << (same ? 0 : 1) << "differ"; - } // Export default guides QDomElement guides_elmt = xml_document.createElement("guides"); @@ -4278,48 +4263,6 @@ void QETProject::writeDefaultPropertiesXml(QDomElement &xml_element) xml_element.appendChild(guides_elmt); } -/** - @brief QETProject::numberingOfProject - @return the project's numbering schemes and their settings, as the - project holds them, for the project database's numbering tables - (QETZ-DB-FIRST stage 2b) and for the save -*/ -projectDataBase::Numbering QETProject::numberingOfProject() const -{ - projectDataBase::Numbering numbering; - auto add = [&numbering](const QString &kind, const QHash &schemes, - const QHash &ids) { - for (auto it = schemes.cbegin() ; it != schemes.cend() ; ++it) - numbering.schemes << projectDataBase::NumberingScheme{kind, it.key(), ids.value(it.key()), it.value()}; - }; - add(QStringLiteral("conductor"), m_conductor_autonum, {}); - add(QStringLiteral("folio"), m_folio_autonum, {}); - add(QStringLiteral("element"), m_element_autonum, m_element_autonum_id); - add(QStringLiteral("cable"), m_cable_autonum, {}); - numbering.current_conductor = m_current_conductor_autonum; - numbering.current_element = m_current_element_autonum; - numbering.current_cable = m_current_cable_autonum; - numbering.freeze_new_conductors = m_freeze_new_conductors; - numbering.freeze_new_elements = m_freeze_new_elements; - numbering.auto_break_conductors = m_auto_break_conductor; - numbering.cable_x_axis_first = m_cable_axis_x_first; - numbering.cable_ask_numbering = m_cable_ask_numbering; - return numbering; -} - -/** - @brief QETProject::writeNumberingRows - Write the project's numbering into the project database's numbering - tables (QETZ-DB-FIRST stage 2b-1): after reading the file and after - each change of the numbering. The save writes the numbering from - them (2b-2); QET_CHECK_NUMBERING_ROWS says whether they hold what - the project does -*/ -void QETProject::writeNumberingRows() -{ - m_data_base.setNumbering(numberingOfProject()); -} - /** @brief QETProject::writeNumberingXml Export @p numbering as the , , diff --git a/sources/qetproject.h b/sources/qetproject.h index a7b9e7613..73c27415f 100644 --- a/sources/qetproject.h +++ b/sources/qetproject.h @@ -457,8 +457,6 @@ class QETProject : public QObject void writeProjectPropertiesXml(QDomElement &); void writeDefaultPropertiesXml(QDomElement &); - projectDataBase::Numbering numberingOfProject() const; - void writeNumberingRows(); static void writeNumberingXml(const projectDataBase::Numbering &numbering, QDomElement &xml_element); void writeUsageXml(QDomElement &); @@ -531,26 +529,6 @@ class QETProject : public QObject DiagramContext m_project_properties; /// undo stack for this project QUndoStack *m_undo_stack; - /// Conductor auto numerotation - QHash m_conductor_autonum;//Title and NumContext hash - QString m_current_conductor_autonum; - /// Folio auto numbering - QHash m_folio_autonum; - /// Element Auto Numbering - QHash m_element_autonum; //Title and NumContext hash - /// Title -> uuid of each element numbering scheme - QHash m_element_autonum_id; - QString m_current_element_autonum; - /// Cable auto numbering: exactly one rule, kept the same way - /// the others are so it stores and travels with the project - /// alike, but never a list to choose from. - QHash m_cable_autonum; - QString m_current_cable_autonum; - ///< which axis the cables are numbered along (see cableXAxisFirst) - bool m_cable_axis_x_first = true; - ///< whether the cable tool still asks about a numbering rule - ///< for this project (see cableAskNumbering) - bool m_cable_ask_numbering = true; /// True when the loaded file had element numbering schemes /// saved without an id (written before ids existed) bool m_legacy_element_autonums = false; @@ -598,10 +576,7 @@ class QETProject : public QObject /// project, off for one saved without it), see uprightSymbolTexts() bool m_upright_symbol_texts = false; WiringRules::Settings m_wiring_rules; - bool m_auto_break_conductor = false; XmlElementCollection *m_elements_collection = nullptr; - bool m_freeze_new_elements = false; - bool m_freeze_new_conductors = false; QTimer m_save_backup_timer, m_autosave_timer; QFuture m_backup_future; diff --git a/tests/qttest/tst_elementautonumids.cpp b/tests/qttest/tst_elementautonumids.cpp index 565a25111..b6c7646ba 100644 --- a/tests/qttest/tst_elementautonumids.cpp +++ b/tests/qttest/tst_elementautonumids.cpp @@ -42,7 +42,6 @@ class tst_elementautonumids : public QObject int m_run = 0; QString m_preferences; // contents of the settings file of the next run - bool m_check_numbering = false; // QET_CHECK_NUMBERING_ROWS for the runs QProcessEnvironment environment() { @@ -62,9 +61,6 @@ class tst_elementautonumids : public QObject env.insert(QStringLiteral("QET_SETTINGS_DIR"), settings_dir); env.insert(QStringLiteral("QT_QPA_PLATFORM"), QStringLiteral("offscreen")); env.insert(QStringLiteral("QET_ENABLE_SCRIPTING"), QStringLiteral("1")); - if (m_check_numbering) { - env.insert(QStringLiteral("QET_CHECK_NUMBERING_ROWS"), 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")); @@ -729,29 +725,45 @@ qet.log('PROBE ' + JSON.stringify(r)); // the folios which read the numbering with it (26 folios of // industrial.qet read "Wire"), a numbering which folios read cannot be // removed, an unused one can, and every step is one undo. - // The project database's numbering rows (QETZ-DB-FIRST stage 2b-1) - // follow every change of the numbering: each save below writes the - // numbering from the project and from the rows, and the two must be - // the same (QET_CHECK_NUMBERING_ROWS). A scheme of each kind added, - // renamed, removed, undone and redone, then made current; a - // renumbering and its undo. On a one-folio project: a --run script - // is stopped after 30 s, and a sanitizer build is slow. + // The project database is the only home of the numbering (QETZ-DB-FIRST + // stage 2b): every change of it is read back, and saved, from there. + // A scheme of each kind added, renamed, removed, undone and redone, + // then made current; a renumbering and its undo; the scheme list is + // checked after each step and the project saved. On a one-folio + // project: a --run script is stopped after 30 s, and a sanitizer + // build is slow. void numberingRowsFollowEveryChange() { const QString saved = m_dir.filePath(QStringLiteral("numbering-rows.qet")); const QString script = QStringLiteral(R"JS( -var saves = 0; +var saves = 0, wrong = []; function save() { if (qet.save(%1)) saves++; } +function has(k, title) { + return qet.autoNums(k).some(function (n) { return n.split(': formula=')[0] === title; }); +} +function formula(k, title) { + var found = qet.autoNums(k).filter(function (n) { return n.split(': formula=')[0] === title; }); + return found.length ? found[0].split(': formula=')[1].replace(/^'|'$/g, '') : null; +} +function expect(step, k, present, absent) { + if (present && !has(k, present)) wrong.push(step + ' ' + k + ': no ' + present); + if (present && !formula(k, present)) wrong.push(step + ' ' + k + ': ' + present + ' has no parts'); + if (absent && has(k, absent)) wrong.push(step + ' ' + k + ': still ' + absent); + var left = qet.query('SELECT COUNT(*) AS n FROM numbering_part p WHERE NOT EXISTS (SELECT 1 ' + + 'FROM numbering_scheme s WHERE s.kind = p.kind AND s.title = p.title)'); + if (!left.length || left[0].n !== 0) wrong.push(step + ' ' + k + ': parts of no scheme'); +} save(); ['conductor', 'element', 'folio'].forEach(function (k) { - qet.addAutoNum(k, 'Rows ' + k, ['string:R-', 'unit:7:2']); save(); - qet.renameAutoNum(k, 'Rows ' + k, 'Rows renamed ' + k); save(); - qet.undo(); save(); - qet.redo(); save(); + var first = 'Rows ' + k, renamed = 'Rows renamed ' + k; + qet.addAutoNum(k, first, ['string:R-', 'unit:7:2']); expect('add', k, first); save(); + qet.renameAutoNum(k, first, renamed); expect('rename', k, renamed, first); save(); + qet.undo(); expect('undo', k, first, renamed); save(); + qet.redo(); expect('redo', k, renamed, first); save(); }); ['conductor', 'element', 'folio'].forEach(function (k) { - qet.removeAutoNum(k, 'Rows renamed ' + k); save(); - qet.undo(); save(); + qet.removeAutoNum(k, 'Rows renamed ' + k); expect('remove', k, null, 'Rows renamed ' + k); save(); + qet.undo(); expect('undo remove', k, 'Rows renamed ' + k); save(); }); qet.useElementAutoNum('Rows renamed element'); save(); qet.useConductorAutoNum(0, 'Rows renamed conductor'); save(); @@ -760,10 +772,9 @@ var renumbered = qet.renumberElementAutoNum(schemes[0].split(': formula=')[0]) > save(); if (renumbered) qet.undo(); save(); -qet.log('PROBE ' + JSON.stringify({saves: saves})); +qet.log('PROBE ' + JSON.stringify({saves: saves, wrong: wrong})); )JS").arg(QStringLiteral("'") + saved + QStringLiteral("'")); - m_check_numbering = true; const QString path = m_dir.filePath(QStringLiteral("probe%1.js").arg(m_run)); QFile f(path); QVERIFY(f.open(QIODevice::WriteOnly)); @@ -771,22 +782,19 @@ qet.log('PROBE ' + JSON.stringify({saves: saves})); f.close(); QProcess proc; proc.setProcessEnvironment(environment()); - m_check_numbering = false; proc.start(QStringLiteral(QET_TEST_BINARY_PATH), {QStringLiteral("--run"), path, QStringLiteral(QET_EXAMPLES_DIR "/741.qet")}); QVERIFY(proc.waitForFinished(180000)); const QString out = QString::fromUtf8(proc.readAllStandardOutput() + proc.readAllStandardError()); - QVERIFY2(out.contains(QLatin1String("PROBE {\"saves\":23}")), qPrintable(out.right(2000))); - QCOMPARE(out.count(QLatin1String("numbering rows: 0 differ")), 23); - QCOMPARE(out.count(QLatin1String("numbering rows:")), 23); + QVERIFY2(out.contains(QLatin1String("PROBE {\"saves\":23,\"wrong\":[]}")), qPrintable(out.right(2000))); // the changes are in the file: the folio numbering's removal was // undone, the wires of the first folio follow the new numbering const QByteArray xml = read(saved); QVERIFY(xml.contains("title=\"Rows renamed folio\"")); QCOMPARE(diagramAttributes(xml, QStringLiteral("conductorAutonum")).value(0), QStringLiteral("Rows renamed conductor")); - // and the save, which writes the numbering from the rows (2b-2), + // and the save, which writes the numbering from the database, // has every scheme and the ones made current const Saved numbering = parse(xml); QCOMPARE(numbering.current, QStringLiteral("Rows renamed element")); From e7d644003fca8001c7234d548ba270450beceba9 Mon Sep 17 00:00:00 2001 From: ispyisail Date: Wed, 21 Oct 2026 10:46:34 +1300 Subject: [PATCH 2/3] qetproject: memoize repeated numbering title/id lookups per call elementAutoNumIdToLink() queried numbering_scheme per symbol even when many symbols on unbuilt folios share the same scheme; setUpFormula() fetched the same scheme's rows twice (elementAutoNumCurrentFormula() then elementAutoNum() separately). Both are real but didn't resolve the tst_lazyfolios ASan timeout on their own -- see QETZ-DB-FIRST-PLAN.md's 2b-3 row for the remaining open question. Co-Authored-By: Claude Sonnet 5 --- sources/qetgraphicsitem/element.cpp | 19 +++++++------ sources/qetproject.cpp | 42 +++++++++++++++++++++++++---- sources/qetproject.h | 4 ++- 3 files changed, 49 insertions(+), 16 deletions(-) diff --git a/sources/qetgraphicsitem/element.cpp b/sources/qetgraphicsitem/element.cpp index 125c069a0..565688059 100644 --- a/sources/qetgraphicsitem/element.cpp +++ b/sources/qetgraphicsitem/element.cpp @@ -2568,21 +2568,20 @@ void Element::setUpFormula(bool code_letter, QUndoCommand *parent_undo) if (diagram()) { - QString formula = diagram() - ->project() - ->elementAutoNumCurrentFormula(); + QETProject *autonum_project = diagram()->project(); + QString element_currentAutoNum = autonum_project->elementCurrentAutoNum(); + //One query for the scheme's rows, read once and reused below -- + //elementAutoNumCurrentFormula()+elementAutoNum() each re-fetched + //the same rows separately (2b-3: every numbering read is now a + //database query, so a second fetch is a second round trip). + NumerotationContext nc = autonum_project->elementAutoNum(element_currentAutoNum); + QString formula = autonum::numerotationContextToFormula(nc); m_data.m_informations.addValue(QStringLiteral("formula"), formula); storeInformation(); - QString element_currentAutoNum = diagram() - ->project() - ->elementCurrentAutoNum(); - const QUuid scheme_id = diagram()->project()->elementAutoNumId(element_currentAutoNum); + const QUuid scheme_id = autonum_project->elementAutoNumId(element_currentAutoNum); setFormulaSchemeId(formula.isEmpty() ? QUuid() : scheme_id); - NumerotationContext nc = diagram() - ->project() - ->elementAutoNum(element_currentAutoNum); //The number given is the first whose label no other element of //the project carries: a counter set back, or an element which diff --git a/sources/qetproject.cpp b/sources/qetproject.cpp index 91ce3d1e5..c8dd66df0 100644 --- a/sources/qetproject.cpp +++ b/sources/qetproject.cpp @@ -1886,13 +1886,16 @@ void QETProject::linkElementsToElementAutoNums() void QETProject::linkElementsToElementAutoNums(Diagram *folio) { const QHash titles_by_formula = elementAutoNumTitlesByFormula(); + QHash title_cache; + QHash id_cache; { const auto items = folio->items(); for (QGraphicsItem *it : items) { auto *el = qgraphicsitem_cast(it); if (!el) continue; if (const auto id = elementAutoNumIdToLink(el->elementInformations(), - titles_by_formula)) { + titles_by_formula, + &title_cache, &id_cache)) { el->setFormulaSchemeId(*id); } } @@ -1922,7 +1925,9 @@ QHash QETProject::elementAutoNumTitlesByFormula() const */ std::optional QETProject::elementAutoNumIdToLink( const DiagramContext &information, - const QHash &titles_by_formula) const + const QHash &titles_by_formula, + QHash *title_cache, + QHash *id_cache) const { const QString formula = information.value(QETInformation::ELMT_FORMULA).toString(); const bool has_id = information.contains(QETInformation::ELMT_FORMULA_ID); @@ -1932,14 +1937,36 @@ std::optional QETProject::elementAutoNumIdToLink( if (has_id) return QUuid(); return std::nullopt; } - if (!elementAutoNumTitle(id).isEmpty()) { + //Every symbol following the same scheme repeats the same id/title + //lookup; a project database query per symbol is the ASan-visible + //cost (F134-adjacent, 2b-3), so a caller walking many symbols may + //pass caches that live only for that one walk -- never persisted. + QString title; + if (title_cache && title_cache->contains(id)) { + title = title_cache->value(id); + } else { + title = elementAutoNumTitle(id); + if (title_cache) title_cache->insert(id, title); + } + if (!title.isEmpty()) { return std::nullopt; } if (!has_id && !m_legacy_element_autonums) { return std::nullopt; //A formula typed by hand } const QStringList matches = titles_by_formula.value(formula); - return matches.size() == 1 ? elementAutoNumId(matches.first()) : QUuid(); + if (matches.size() != 1) { + return QUuid(); + } + const QString &match = matches.first(); + if (id_cache) { + auto it = id_cache->find(match); + if (it == id_cache->end()) { + it = id_cache->insert(match, elementAutoNumId(match)); + } + return it.value(); + } + return elementAutoNumId(match); } /** @@ -1986,13 +2013,18 @@ QList QETProject::symbolNumbering() const for (Diagram *d : m_diagrams_list) if (!d->isLoaded()) unbuilt_in_order << d->uuid(); const QHash titles_by_formula = elementAutoNumTitlesByFormula(); + //Every symbol on an unbuilt folio following the same scheme repeats + //the same title/id lookup; cache both directions for this one walk + //(never persisted -- a scheme rename between calls must see it). + QHash title_cache; + QHash id_cache; for (const projectDataBase::FolioSymbol &row : m_data_base.symbolsOnFolios(unbuilt_in_order)) { const DiagramContext info = m_data_base.elementInformation(row.uuid); SymbolNumbering symbol; symbol.label = info.value(QETInformation::ELMT_LABEL).toString(); symbol.formula = info.value(QETInformation::ELMT_FORMULA).toString(); - const auto linked = elementAutoNumIdToLink(info, titles_by_formula); + const auto linked = elementAutoNumIdToLink(info, titles_by_formula, &title_cache, &id_cache); symbol.scheme = linked ? *linked : QUuid(info.value(QETInformation::ELMT_FORMULA_ID).toString()); symbol.sequence = row.sequence; diff --git a/sources/qetproject.h b/sources/qetproject.h index 73c27415f..1675f2627 100644 --- a/sources/qetproject.h +++ b/sources/qetproject.h @@ -537,7 +537,9 @@ class QETProject : public QObject QHash elementAutoNumTitlesByFormula() const; std::optional elementAutoNumIdToLink( const DiagramContext &information, - const QHash &titles_by_formula) const; + const QHash &titles_by_formula, + QHash *title_cache = nullptr, + QHash *id_cache = nullptr) const; bool m_deferring_folios = false; /// Opening with folios built only when needed: QET_LAZY_FOLIOS /// is set and the database can be filled from the document From 7d75c72f97c0adc9363a71b6114bd40ed183ed6d Mon Sep 17 00:00:00 2001 From: ispyisail Date: Wed, 21 Oct 2026 21:43:26 +1300 Subject: [PATCH 3/3] tests: give tst_lazyfolios room for real ASan runtime Rows-only numbering reads cost roughly 2x master's wall-clock under ASan (~2065s measured solo on an idle machine, vs master's ~1038-1071s). Two timeouts were tight enough to fail on that legitimate cost alone: the script's own subprocess timeout and ctest's TIMEOUT property for the test. Raised both with real margin. Co-Authored-By: Claude Sonnet 5 --- tests/qttest/CMakeLists.txt | 2 +- tests/qttest/run_lazy_folios_probe.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/qttest/CMakeLists.txt b/tests/qttest/CMakeLists.txt index 4ca44ded7..5cab5a5dd 100644 --- a/tests/qttest/CMakeLists.txt +++ b/tests/qttest/CMakeLists.txt @@ -92,7 +92,7 @@ if(Python3_Interpreter_FOUND AND CMAKE_GENERATOR STREQUAL "Ninja") --build ${CMAKE_BINARY_DIR} --binary $ --source ${CMAKE_CURRENT_SOURCE_DIR}/lazy_folios_probe.cpp --fixture ${CMAKE_CURRENT_SOURCE_DIR}/../../examples/industrial.qet) - set_tests_properties(tst_lazyfolios PROPERTIES TIMEOUT 1800 RUN_SERIAL TRUE + set_tests_properties(tst_lazyfolios PROPERTIES TIMEOUT 3000 RUN_SERIAL TRUE ENVIRONMENT "QT_QPA_PLATFORM=offscreen") # Deleting a cable, undoing the delete twice and then editing anything # has both cable commands tearing down the same cable. Runs under the diff --git a/tests/qttest/run_lazy_folios_probe.py b/tests/qttest/run_lazy_folios_probe.py index af41a0fcc..41a2503bc 100644 --- a/tests/qttest/run_lazy_folios_probe.py +++ b/tests/qttest/run_lazy_folios_probe.py @@ -54,7 +54,7 @@ subprocess.run([ninja, '-C', str(build), '-f', probe_manifest.name, target], che output = build / 'lazy-folios' output.mkdir(exist_ok=True) report = output / 'results.txt' -result = subprocess.run([str(probe_binary), str(fixture), str(report), str(output)], timeout=1500) +result = subprocess.run([str(probe_binary), str(fixture), str(report), str(output)], timeout=2400) if report.exists(): print(report.read_text(encoding='utf-8', errors='replace')) sys.exit(result.returncode)