diff --git a/sources/diagram.cpp b/sources/diagram.cpp index 7dcbf2427..316593e78 100644 --- a/sources/diagram.cpp +++ b/sources/diagram.cpp @@ -830,6 +830,57 @@ QList Diagram::buildSymbols(const QSet &uuids) return built; } +namespace { + //The title block fields nothing on a folio is computed from: a formula + //reads the folio's label, plant, location, extra fields and grid + //(autonum::FormulaContext), none of these +const QStringList ShownOnlyFields = { + QStringLiteral("title"), QStringLiteral("author"), QStringLiteral("date"), + QStringLiteral("indexrev"), QStringLiteral("version"), QStringLiteral("filename"), + QStringLiteral("titleblocktemplate"), QStringLiteral("titleblocktemplateCollection")}; + +QString headerWithoutShownOnly(const QString &header) +{ + QDomDocument document; + document.setContent(header); + QDomElement root = document.documentElement(); + for (const QString &field : ShownOnlyFields) root.removeAttribute(field); + return document.toString(); +} +} + +/** + @brief Diagram::headerChangeShowsOnly + @return true if what changed in this folio's own data since it was read + (headerChanged()) is only title block fields nothing on the folio is + computed from (its title, author, date, revision, file name, template): + its items, as read, are what building it would write +*/ +bool Diagram::headerChangeShowsOnly() +{ + return headerWithoutShownOnly(headerXml()) == headerWithoutShownOnly(m_opened_header); +} + +/** + @brief Diagram::deferredXmlWithHeader + @return this folio, not built, as it saves: its own data as it is now + (writeHeader()) and its items as read (deferredXml()), in @p document -- + when its items are what building it would write (headerChangeShowsOnly()) +*/ +QDomElement Diagram::deferredXmlWithHeader(QDomDocument &document) +{ + QDomElement folio = document.createElement(QStringLiteral("diagram")); + writeHeader(document, folio); + QSet header_children; + for (QDomElement child = folio.firstChildElement() ; !child.isNull() ; child = child.nextSiblingElement()) + header_children.insert(child.tagName()); + const QDomElement items = deferredXml(); + for (QDomElement child = items.firstChildElement() ; !child.isNull() ; child = child.nextSiblingElement()) + if (!header_children.contains(child.tagName())) + folio.appendChild(document.importNode(child, true)); + return folio; +} + /** @brief Diagram::deferredXml @return the XML of this folio's items while they are not built: kept diff --git a/sources/diagram.h b/sources/diagram.h index b29730aa4..4117bd8e6 100644 --- a/sources/diagram.h +++ b/sources/diagram.h @@ -240,6 +240,8 @@ class Diagram : public QGraphicsScene /// headerChanged() (QETProject::toXml()) void rememberHeader() {m_opened_header = headerXml();} bool headerChanged() {return headerXml() != m_opened_header;} + bool headerChangeShowsOnly(); + QDomElement deferredXmlWithHeader(QDomDocument &document); void ensureLoaded(); bool buildDeferred(); //The last argument is only for a copy fragment: when it is diff --git a/sources/qetproject.cpp b/sources/qetproject.cpp index 63d6a1239..3a640c798 100644 --- a/sources/qetproject.cpp +++ b/sources/qetproject.cpp @@ -2374,9 +2374,17 @@ QDomDocument QETProject::toXml(QHash *kept_folios, QStringList changed << diagram; } buildFolios(changed); - for (Diagram *diagram : std::as_const(m_diagrams_list)) - if (diagram->headerChanged()) + //-- unless it changed only in what nothing is computed from (its + //title, date, revision...): written from its items as read + //(Diagram::deferredXmlWithHeader()) + for (Diagram *diagram : std::as_const(m_diagrams_list)) { + if (!diagram->headerChanged()) continue; + bool edits_need_build = false; + if (!diagram->isLoaded()) + m_data_base.unbuiltEditsOnFolio(diagram->uuid(), &edits_need_build); + if (diagram->isLoaded() || !diagram->headerChangeShowsOnly() || edits_need_build) buildFolioToShow(diagram); + } //A table filled from the database saves the size its rows //give it, which any edit can change if (m_edited_since_open) @@ -2490,6 +2498,24 @@ QDomDocument QETProject::toXml(QHash *kept_folios, QStringList << "[" << diagram << "]"; + if (keep && !diagram->isLoaded() && diagram->headerChanged()) { + QDomElement xml_diagram = diagram->deferredXmlWithHeader(xml_doc); + //Its symbols whose information changed while it was not built + const QHash edits = m_data_base.unbuiltEditsOnFolio(diagram->uuid()); + for (const QDomElement &list : QET::findInDomElement(xml_diagram, QStringLiteral("elements"))) + for (QDomElement element = list.firstChildElement(QStringLiteral("element")) ; !element.isNull() ; + element = element.nextSiblingElement(QStringLiteral("element"))) { + const auto edit = edits.constFind(QUuid(element.attribute(QStringLiteral("uuid")))); + if (edit == edits.constEnd()) continue; + QDomElement information = xml_doc.createElement(QStringLiteral("elementInformations")); + edit->toXml(information, QStringLiteral("elementInformation")); + const QDomElement old_information = element.firstChildElement(QStringLiteral("elementInformations")); + element.replaceChild(information, old_information); //it has one (unbuiltEditsOnFolio()) + } + xml_diagram.setAttribute("order", order_num ++); + project_root.appendChild(xml_diagram); + continue; + } if (keep && !diagram->isLoaded() && kept_parts) { auto placeholder = xml_doc.createElement(QStringLiteral("diagram")); placeholder.setAttribute(QStringLiteral("qetz-kept-part"), diagram->deferredPart()); diff --git a/tests/qttest/lazy_folios_probe.cpp b/tests/qttest/lazy_folios_probe.cpp index 0375b24f5..3389782b8 100644 --- a/tests/qttest/lazy_folios_probe.cpp +++ b/tests/qttest/lazy_folios_probe.cpp @@ -49,6 +49,73 @@ #include static void check(bool value, const char *message) { if (!value) throw std::runtime_error(message); } +static QStringList sortedLines(QString xml, const QString &path); + + //A new revision on every folio (its title block's revision index and + //author), saved lazily and with every folio built: what saving builds, + //and whether the two saves hold the same. A change to folio + //@p label_folio's label (what formulas read) when it is not -1 +static void revisionEveryFolio(const QString &source, const QString &output, const QString &name, + const QString &path, int label_folio = -1) +{ + QString error; + qputenv("QET_LAZY_FOLIOS", "1"); + QETProject revised(source); + const int unbuilt = revised.unloadedFolioCount(); + QetScriptApi api(&revised); + for (int i = 0 ; i < api.folioCount() ; ++i) { + check(api.setFolioProperty(i, "indexrev", "B"), "a folio's revision set"); + check(api.setFolioProperty(i, "author", "Revised"), "a folio's author set"); + } + if (label_folio >= 0) + check(api.setFolioProperty(label_folio, "folio", "X%id"), "a folio's label set"); + check(revised.unloadedFolioCount() == unbuilt, "setting the folios' title blocks builds none"); + int with_tables = 0; + for (Diagram *folio : revised.folios()) + if (!folio->isLoaded() && revised.unbuiltFolioHasTables(folio)) ++with_tables; + const bool qetz = QETProject::isContainerPath(source); + const QString lazy_path = output + "/" + name + (qetz ? ".qetz" : ".qet"); + if (qetz) check(revised.writeContainerFile(lazy_path, &error), "revised project saved"); + else { revised.setFilePath(lazy_path); check(revised.write().isOk(), "revised project saved"); } + const int built = unbuilt - revised.unloadedFolioCount(); + std::printf("%s: new revision on every folio, %d folios built by saving, %d hold a table%s\n", + qPrintable(name), built, with_tables, + label_folio >= 0 ? ", one label changed" : ""); + if (label_folio < 0) + check(built <= with_tables, "saving a new revision builds only the folios holding a table"); + else + check(revised.folios().at(label_folio)->isLoaded(), "a folio whose label changed is built to save"); + qunsetenv("QET_LAZY_FOLIOS"); + const QString usual_path = output + "/" + name + "-usual" + (qetz ? ".qetz" : ".qet"); + { + QETProject usual(source); + QetScriptApi usual_api(&usual); + for (int i = 0 ; i < usual_api.folioCount() ; ++i) { + usual_api.setFolioProperty(i, "indexrev", "B"); + usual_api.setFolioProperty(i, "author", "Revised"); + } + if (label_folio >= 0) usual_api.setFolioProperty(label_folio, "folio", "X%id"); + if (qetz) check(usual.writeContainerFile(usual_path, &error), "revision saved when built"); + else { usual.setFilePath(usual_path); check(usual.write().isOk(), "revision saved when built"); } + } + QETProject lazy_back(lazy_path), usual_back(usual_path); + qputenv("QET_LAZY_FOLIOS", "1"); + //What a save says of itself (file name, path, time) left out + auto lines = [&path](QETProject &project) { + QStringList list = sortedLines(project.toXml().toString(), path); + list.removeIf([](const QString &line) { return line.contains(QLatin1String("name=\"saved")); }); + return list; + }; + const QStringList lazy_lines = lines(lazy_back), usual_lines = lines(usual_back); + if (lazy_lines != usual_lines) { + int shown = 0; + for (const QString &line : usual_lines) + if (!lazy_lines.contains(line) && shown++ < 8) std::printf(" when built: %s\n", qPrintable(line.left(300))); + for (const QString &line : lazy_lines) + if (!usual_lines.contains(line) && shown++ < 16) std::printf(" lazily: %s\n", qPrintable(line.left(300))); + } + check(lazy_lines == usual_lines, "a new revision saved as when built"); +} //Every symbol's numbering, in a stable order static QStringList numbering(const QETProject &project) @@ -380,6 +447,7 @@ int main(int argc, char **argv) for (const QString &title : titles) check(gaps(numbered, title) == gaps(eager, title), "a scheme's gaps as when built"); check(numbered.unloadedFolioCount() == folios.size() - strip_folios.size(), "numbering read without building"); + revisionEveryFolio(path, output, "revised-qet", path); { //Wire numbers exported from the database filled from the //document, as when built (D5) @@ -880,6 +948,11 @@ int main(int argc, char **argv) "a symbol changed before its folio was built shows the change"); } + //A new revision on every folio: saved from the folios as read, + //not built; a label change builds its folio (D5) + revisionEveryFolio(zipped, output, "revised", path); + revisionEveryFolio(zipped, output, "relabelled", path, busiest); + //The wire numbers and wiring list exports, and the terminal //numbering dialog: as when built, building only the folios //holding a terminal (D5)