Merge pull request #1514 from ispyisail/fix/lazy-table-chain-folio-removed

Fix a table losing its query when a folio is removed with lazy folios
This commit is contained in:
ispyisail
2026-10-18 05:59:34 +13:00
committed by GitHub
3 changed files with 104 additions and 0 deletions
+44
View File
@@ -752,11 +752,24 @@ void QETProject::buildFolios(const QList<Diagram *> &folios) const
auto *self = const_cast<QETProject *>(this);
const ProjectState state = m_state;
self->m_state = ProjectParsingRunning;
//Batch the database's signals as a paste does
//(DiagramEventAddPaste): every symbol and wire built would emit
//dataBaseUpdated(), and every table already built would measure all
//its rows again each time -- minutes for a save building 40 folios
//with 10 parts-list folios built. The rows are still written as each
//item is built: only the signal waits, and is sent once at the end
//(updateDB() would fill the whole database again, every time).
const bool signals_were_blocked = self->m_data_base.signalsBlocked();
self->m_data_base.blockSignals(true);
QList<Diagram *> built;
for (Diagram *d : folios)
if (d->buildDeferred()) built << d;
for (Diagram *d : std::as_const(built))
self->folioLoaded(d);
self->m_data_base.blockSignals(signals_were_blocked);
if (!signals_were_blocked && !built.isEmpty()) {
emit self->m_data_base.dataBaseUpdated();
}
//A symbol built before still keeping the links it was saved with
//(a partner on a folio not built): its initLink() links those just
//built, puts its partners back in their saved order and drops what
@@ -3175,6 +3188,23 @@ void QETProject::buildFoliosHoldingTables()
buildFolios(to_build);
}
/**
@brief QETProject::folioHoldsTablesOrSymbols
@return true if @p folio, built or not, has a drawn table or a symbol
*/
bool QETProject::folioHoldsTablesOrSymbols(Diagram *folio) const
{
if (!folio->isLoaded()) {
const QDomElement xml = folio->deferredXml();
return !xml.elementsByTagName(QetGraphicsTableItem::xmlTagName()).isEmpty()
|| !xml.elementsByTagName(QStringLiteral("element")).isEmpty();
}
for (QGraphicsItem *item : folio->items())
if (item->type() == QetGraphicsTableItem::Type || item->type() == Element::Type)
return true;
return false;
}
/**
@brief QETProject::unbuiltFolioHasTables
@return true if @p folio, not built yet (lazy folios), has a drawn
@@ -4274,6 +4304,20 @@ void QETProject::detachDiagram(Diagram *diagram)
disconnect(diagram, &Diagram::usedTitleBlockTemplateChanged,
this, &QETProject::usedTitleBlockTemplateChanged);
//A folio that holds a table or a symbol is removed: tables on folios
//not built yet (lazy folios) are built while it is still in the
//project, as they are with every folio built (F121). A table split
//over several folios keeps its query in its first part, and the next
//part takes it over only if that part was built
//(QetGraphicsTableItem::toXml()); built later, from the file, it
//names a part that is gone and saves no query. A built table also
//follows the project's database, so a parts list that showed this
//folio's symbols is resized like every other. A folio with neither
//builds no other.
if (folioHoldsTablesOrSymbols(diagram)) {
buildFoliosHoldingTables();
}
m_diagrams_list.removeAll(diagram);
updateDiagramsFolioData();
emit diagramRemoved(this, diagram);
+1
View File
@@ -354,6 +354,7 @@ class QETProject : public QObject
QList <ElementsLocation> unusedElements() const;
QList<ElementsLocation> unbuiltFolioElements() const;
bool unbuiltFolioHasTables(Diagram *folio) const;
bool folioHoldsTablesOrSymbols(Diagram *folio) const;
bool usesTitleBlockTemplate(const TitleBlockTemplateLocation &);
bool projectWasModified();
bool projectOptionsWereModified();
+59
View File
@@ -234,6 +234,65 @@ private slots:
QVERIFY(references > 0);
}
// Deleting the folio that holds the first part of a split table, with
// folios opened lazily (the default), then saving: the next part keeps
// the table's query (F121). The first part holds it; the next part takes
// it over only if it was built when the folio went, and it used to be
// built later, from the file, naming a part that was gone and saving no
// query. industrial.qet's folio 41 (index 40) holds the first part of its
// 10-part parts list. It is resaved first: its folios have no saved uuid,
// and such a project opens with every folio built. Every table in the
// file must name a table that is in it, or have a query.
void deletedFirstPartKeepsTableQuery()
{
#ifndef QET_HAS_SCRIPTING
QSKIP("needs --run: this QElectroTech is built without Qt Qml");
#endif
const QString in = resave(QStringLiteral(QET_EXAMPLES_DIR "/industrial.qet"));
QVERIFY(!in.isEmpty());
const QString out = m_dir.filePath(QStringLiteral("deleted_first_part.qet"));
const QString script = m_dir.filePath(QStringLiteral("deleted_first_part.js"));
QFile js(script);
QVERIFY(js.open(QIODevice::WriteOnly));
js.write(QStringLiteral("qet.log('REMOVED ' + qet.removeFolio(40));\n"
"qet.log('SAVED ' + qet.save('%1'));\n").arg(out).toUtf8());
js.close();
const QString home = m_dir.filePath(QStringLiteral("home%1").arg(m_run++));
QProcessEnvironment env = QProcessEnvironment::systemEnvironment();
env.insert(QStringLiteral("QT_QPA_PLATFORM"), QStringLiteral("offscreen"));
env.insert(QStringLiteral("QET_ENABLE_SCRIPTING"), QStringLiteral("1"));
env.remove(QStringLiteral("QET_EAGER_FOLIOS"));
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"), script, in});
QVERIFY(proc.waitForFinished(180000));
const QString log = QString::fromUtf8(proc.readAllStandardOutput() + proc.readAllStandardError());
QVERIFY2(log.contains(QStringLiteral("REMOVED true")) && log.contains(QStringLiteral("SAVED true")),
qPrintable(log.right(400)));
const QString saved = QString::fromUtf8(read(out));
const QRegularExpression table_re(
QStringLiteral("<graphics_table [^>]*uuid=\"([^\"]+)\"[^>]*>(.*?)</graphics_table>"),
QRegularExpression::DotMatchesEverythingOption);
QHash<QString, QString> tables; // uuid -> what is inside the table
for (const auto &m : table_re.globalMatch(saved))
tables.insert(m.captured(1), m.captured(2));
QVERIFY(tables.size() >= 9);
const QRegularExpression previous_re(QStringLiteral("<previous_table uuid=\"([^\"]+)\""));
const QRegularExpression query_re(QStringLiteral("<query>\\s*\\S"));
for (auto it = tables.cbegin() ; it != tables.cend() ; ++it) {
const auto previous = previous_re.match(it.value());
QVERIFY2(query_re.match(it.value()).hasMatch()
|| (previous.hasMatch() && tables.contains(previous.captured(1))),
qPrintable(QStringLiteral("table %1 has no query and names no table in the file")
.arg(it.key())));
}
}
// A folio report arrow linked to one that has no terminal -- such as
// ref_voyant_2_h.elmt from the collection -- crashed QElectroTech on
// opening: the arrow's text looked for a wire on the other arrow's