mirror of
https://github.com/qelectrotech/qelectrotech-source-mirror.git
synced 2026-10-10 22:24:13 +02:00
Fix a split table losing its data and crashing after a folio is deleted
A summary or parts-list table split over several folios saves its data with its first part only; each later part names the part before it. Deleting a folio keeps its tables in memory for undo, still in the chain, so saving then named the deleted part as the previous table of the next one. On reopening, every part after the gap had no data, and closing the project crashed: the first orphaned part, destroyed, handed its missing data on to the next part through a null model. toXml() now names the nearest previous part still in the project, or saves the data if there is none. setPreviousTable() no longer copies a null model, so files already saved this way open and close. Tests (tst_resaveunchanged): missingPreviousTableOpens, deletedFolioKeepsTableChain; both fail without the fix. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019Wga9B92BNurDEtxxtCDxf
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -160,6 +160,70 @@ 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()
|
||||
{
|
||||
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()
|
||||
|
||||
Reference in New Issue
Block a user