Merge pull request #1444 from ispyisail/fix/table-chain-deleted-folio

Fix a split table going blank and crashing after a folio is deleted
This commit is contained in:
ispyisail
2026-10-10 21:12:06 +13:00
committed by GitHub
2 changed files with 83 additions and 4 deletions
@@ -448,7 +448,7 @@ void QetGraphicsTableItem::setPreviousTable(QetGraphicsTableItem *table)
m_previous_table->setNextTable(this);
setModel(m_previous_table->m_model);
}
else //Copie the model of old previous table
else if (old_previous_table->model()) //Copie the model of old previous table
{
setModel(new ProjectDBModel(*static_cast<ProjectDBModel *>(old_previous_table->model())));
}
@@ -596,11 +596,23 @@ QDomElement QetGraphicsTableItem::toXml(QDomDocument &dom_document) const
//Add the header xml
dom_table.appendChild(m_header_item->toXml(dom_document));
//Add previous table, the model is save by the previous table
if (m_previous_table)
//Add previous table, the model is save by the previous table.
//A folio deleted with undo still possible keeps its tables in the
//chain, but it is not saved: name the nearest previous table that
//is, or save the model if none is.
const auto project_ = diagram() ? diagram()->project() : nullptr;
auto in_project = [project_](const QetGraphicsTableItem *table) {
return project_ && table->diagram()
&& project_->diagrams().contains(table->diagram());
};
QetGraphicsTableItem *previous_ = m_previous_table;
while (previous_ && !in_project(previous_)) {
previous_ = previous_->previousTable();
}
if (previous_)
{
auto dom_previous_table = dom_document.createElement("previous_table");
dom_previous_table.setAttribute("uuid", m_previous_table->m_uuid.toString());
dom_previous_table.setAttribute("uuid", previous_->m_uuid.toString());
dom_table.appendChild(dom_previous_table);
}
else if (m_model) //There is not a previous table, we need to save the model
+67
View File
@@ -160,6 +160,73 @@ private slots:
QVERIFY2(sb < sa, "the coil's contacts were saved in another order than the file's");
}
// A table split over several folios whose part names a previous part
// that is not in the file: opening and closing it crashed (the part
// has no data of its own and handed its missing data on when it was
// destroyed). industrial.qet's third <previous_table> is changed.
void missingPreviousTableOpens()
{
QByteArray xml = read(QStringLiteral(QET_EXAMPLES_DIR "/industrial.qet"));
int at = -1;
for (int n = 0 ; n < 3 ; ++n) {
at = xml.indexOf("<previous_table uuid=\"", at + 1);
QVERIFY(at > 0);
}
at += int(qstrlen("<previous_table uuid=\""));
xml.replace(at, 38, "{00000000-0000-4000-8000-000000000000}");
const QString in = m_dir.filePath(QStringLiteral("missing_previous.qet"));
QFile f(in);
QVERIFY(f.open(QIODevice::WriteOnly));
f.write(xml);
f.close();
QVERIFY2(!resave(in).isEmpty(), "--resave failed: QElectroTech crashed closing the project");
}
// Deleting the folio that holds a middle part of a split table, then
// saving: every <previous_table> in the file names a table that is in
// it. It used to name the deleted part, so every part after it opened
// with no data. industrial.qet's folio 46 (index 45) holds such a part.
void deletedFolioKeepsTableChain()
{
#ifndef QET_HAS_SCRIPTING
QSKIP("needs --run: this QElectroTech is built without Qt Qml");
#endif
const QString out = m_dir.filePath(QStringLiteral("deleted_folio.qet"));
const QString script = m_dir.filePath(QStringLiteral("deleted_folio.js"));
QFile js(script);
QVERIFY(js.open(QIODevice::WriteOnly));
js.write(QStringLiteral("qet.log('REMOVED ' + qet.removeFolio(45));\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.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, QStringLiteral(QET_EXAMPLES_DIR "/industrial.qet")});
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));
QSet<QString> tables;
for (const auto &m : QRegularExpression(QStringLiteral("<graphics_table [^>]*uuid=\"([^\"]+)\"")).globalMatch(saved))
tables.insert(m.captured(1));
int references = 0;
for (const auto &m : QRegularExpression(QStringLiteral("<previous_table uuid=\"([^\"]+)\"")).globalMatch(saved)) {
++references;
QVERIFY2(tables.contains(m.captured(1)),
qPrintable(QStringLiteral("a table names %1, which is not in the file").arg(m.captured(1))));
}
QVERIFY(references > 0);
}
// A title-block value that is a single space is kept through two saves
// (#973), and a value with accents comes back as it went in.
void singleSpaceValueKept()