// SPDX-License-Identifier: GPL-2.0-or-later // Lazy folios (QET_LAZY_FOLIOS), // linked against the application's objects: a folio shown in a project // opened without building its folios draws and links as in the project // opened as usual, building only it and the folios its links and tables // reach, and the project saves the same. #include #include #include #include #include #include #include #include #include #include #include #include #include "../../sources/diagram.h" #include "../../sources/qetproject.h" #include "../../sources/qetresult.h" #include "../../sources/qetmessagebox.h" #include "../../sources/qetgraphicsitem/element.h" #include "../../sources/autoNum/elementautonumschemecommand.h" #include "../../sources/elementprovider.h" #include "../../sources/TerminalStrip/terminalstrip.h" #include "../../sources/ui/elementpropertieswidget.h" #include "../../sources/conductorautonumerotation.h" #include "../../sources/diagramcontent.h" #include "../../sources/diagramview.h" #include "../../sources/diagramevent/diagrameventaddpaste.h" #include #include "../../sources/qetgraphicsitem/conductor.h" #include "../../sources/qetgraphicsitem/terminal.h" #include "../../sources/undocommand/changeelementinformationcommand.h" #include "../../sources/undocommand/changetitleblockcommand.h" #include "../../sources/undocommand/deleteqgraphicsitemcommand.h" #include "../../sources/factory/elementfactory.h" #include "../../sources/undocommand/addgraphicsobjectcommand.h" #include "../../sources/dataBase/projectdatabase.h" #include static void check(bool value, const char *message) { if (!value) throw std::runtime_error(message); } //Every symbol's numbering, in a stable order static QStringList numbering(const QETProject &project) { QStringList list; for (const auto &symbol : project.symbolNumbering()) list << QStringList{symbol.label, symbol.formula, symbol.scheme.toString(), symbol.sequence.unit.join(';'), symbol.sequence.ten.join(';'), symbol.sequence.hundred.join(';'), QString::number(symbol.numbered)}.join('|'); list.sort(); return list; } //What the scheme @p title tells of the numbers its symbols carry static QString gaps(const QETProject &project, const QString &title) { QStringList list; for (const auto &gap : ElementAutoNumSchemeCommand::gapRanges(&project, title)) list << QString("%1-%2").arg(gap.from).arg(gap.to); return list.join(','); } //A symbol of @p location placed on folio @p i as the folio's editor //places one (DiagramEventAddElement::addElement()), numbered with the //scheme @p title: its label, and the numbers then free for it static QString place(QETProject &project, int i, const QString &location, const QString &title) { Diagram *folio = project.folios().at(i); project.buildFolioToShow(folio); project.setCurrrentElementAutonum(title); int state = 0; Element *element = ElementFactory::Instance()->createElement( ElementsLocation(location, &project), nullptr, &state); check(!state, "symbol made"); element->setPos(QPointF(-500, -500)); folio->addItem(element); auto *undo = new QUndoCommand("insert"); new AddGraphicsObjectCommand(element, folio, element->pos(), undo); element->setUpFormula(true, undo); folio->undoStack().push(undo); element->freezeNewAddedElement(); QStringList free; for (int n : ElementAutoNumSchemeCommand::freeNumbers(&project, title, element)) free << QString::number(n); return element->actualLabel() + " free " + free.join(','); } //The symbols a search of a project finds, by uuid static QStringList found(const QVector> &elements) { QStringList list; for (const auto &element : elements) list << element->uuid().toString(); list.sort(); return list; } //Everyday edits on folio @p i, as its editor makes them: a symbol's //information, a wire, the folio's title block, a linked symbol //deleted, then one undo and one redo static void edit(QETProject &project, int i) { Diagram *folio = project.folios().at(i); project.buildFolioToShow(folio); QList elements = folio->elements(); std::sort(elements.begin(), elements.end(), [](Element *a, Element *b) { return a->uuid() < b->uuid(); }); check(elements.size() > 2, "a folio with symbols to edit"); const DiagramContext old_info = elements.first()->elementInformations(); DiagramContext new_info = old_info; new_info.addValue(QStringLiteral("comment"), QStringLiteral("edited lazily")); folio->undoStack().push(new ChangeElementInformationCommand(elements.first(), old_info, new_info)); Terminal *ends[2] = {nullptr, nullptr}; for (Element *element : elements) { for (Terminal *terminal : element->terminals()) { if (terminal->conductors().isEmpty() && (!ends[0] || ends[0]->parentElement() != element)) { ends[ends[0] ? 1 : 0] = terminal; break; } } if (ends[1]) break; } if (ends[1]) { auto *undo = new QUndoCommand(QStringLiteral("wire")); auto *wire = new Conductor(ends[0], ends[1]); new AddGraphicsObjectCommand(wire, folio, QPointF(), undo); ConductorAutoNumerotation numbering(wire, folio, undo); numbering.numerate(); folio->undoStack().push(undo); } const TitleBlockProperties old_block = folio->border_and_titleblock.exportTitleBlock(); TitleBlockProperties new_block = old_block; new_block.title += QStringLiteral(" (edited)"); folio->undoStack().push(new ChangeTitleBlockCommand(folio, old_block, new_block)); //Copy three symbols and paste them, as Ctrl+C then Ctrl+Shift+V //and Return do { DiagramView view(folio); folio->clearSelection(); for (int n = 0 ; n < 3 ; ++n) elements.at(n)->setSelected(true); view.copy(); auto *paste = new DiagramEventAddPaste(folio, QPointF(), DiagramEventAddPaste::AtOrigin); folio->setEventInterface(paste); QKeyEvent drop(QEvent::KeyPress, Qt::Key_Return, Qt::NoModifier); paste->keyPressEvent(&drop); folio->clearEventInterface(); } Element *linked = nullptr; for (Element *element : elements) if (!element->linkedElements().isEmpty()) { linked = element; break; } folio->clearSelection(); (linked ? linked : elements.last())->setSelected(true); folio->undoStack().push(new DeleteQGraphicsItemCommand(folio, DiagramContent(folio, true))); project.undoStack()->undo(); project.undoStack()->redo(); } //The project as saving writes it: folios not built written as read //when building them would write the same (QETProject::toXml()) static QString saved(QETProject &project) { QHash kept; const QDomDocument document = project.toXml(&kept); return QETProject::spliceKeptFolios(document.toString(4), kept); } //The rows of the project database's tables, in their order, read as //every caller reads them (newQuery(): brought up to date first) static QStringList tables(QETProject &project) { QStringList rows; for (const char *table : {"diagram", "diagram_info", "element", "element_info", "terminal", "conductor", "link"}) { QSqlQuery query = project.dataBase()->newQuery( QStringLiteral("SELECT * FROM %1 ORDER BY rowid").arg(QLatin1String(table))); check(query.exec(), "a table read"); QStringList table_rows; while (query.next()) { QStringList row; for (int i = 0 ; i < query.record().count() ; ++i) row << query.value(i).toString(); table_rows << QLatin1String(table) + ": " + row.join('|'); } //Rows are written in the order the scene gives a folio's items, //which is not the same from one opening to the next: two usual //opens given the same edits differ in it (637 rows of 3 394 on //industrial.qet). What a table holds is compared, not its order table_rows.sort(); rows << table_rows; } return rows; } static QImage render(Diagram *folio) { const QRectF rect = folio->border_and_titleblock.borderAndTitleBlockRect().adjusted(-5, -5, 5, 5); QImage image(rect.size().toSize(), QImage::Format_RGB32); image.fill(Qt::white); QPainter painter(&image); folio->render(&painter, QRectF(QPointF(0, 0), rect.size()), rect); return image; } //Each symbol's partners, in their order static QStringList links(Diagram *folio) { QStringList list; for (Element *element : folio->elements()) { QStringList partners; for (Element *partner : element->linkedElements()) partners << partner->uuid().toString(); list << element->uuid().toString() + ' ' + partners.join(','); } list.sort(); return list; } int main(int argc, char **argv) { QApplication app(argc, argv); std::freopen(argv[2], "w", stdout); QTemporaryDir settings; QSettings::setDefaultFormat(QSettings::IniFormat); QSettings::setPath(QSettings::IniFormat, QSettings::UserScope, settings.path()); QCoreApplication::setOrganizationName("QETLazyFoliosRegression"); QETProject::setBackupEnabled(false); QET::QetMessageBox::setNonInteractive(true); QFontDatabase::addApplicationFont(":/fonts/LiberationSans-Regular.ttf"); //Folios not built saved as read (QETProject::toXml()): this //fixture is written by this version's own save qputenv("QET_LAZY_SAVE_KEPT", "1"); try { const QString output = QString::fromLocal8Bit(argv[3]); qunsetenv("QET_LAZY_FOLIOS"); //A file saved by this version: its folios carry their uuids, //which opening without building them needs const QString path = output + "/lazy_folios.qet"; QSet strip_folios; { QETProject source(QString::fromLocal8Bit(argv[1])); check(source.state() == QETProject::Ok, "fixture opens"); //A terminal strip of terminals on several folios: it is //read with the project, before any folio is built TerminalStrip *strip = source.newTerminalStrip(QStringLiteral("=A1"), QStringLiteral("+B1"), QStringLiteral("X99")); int added = 0; for (Diagram *folio : source.folios()) for (Element *element : folio->elements()) if (element->elementData().m_type == ElementData::Terminal && added < 12 && strip->addTerminal(element)) { ++added; strip_folios.insert(source.folioIndex(folio)); } check(added > 3, "a terminal strip with terminals"); source.setFilePath(path); check(source.write().isOk(), "fixture saved by this version"); } QETProject eager(path); check(eager.state() == QETProject::Ok, "project opens"); { qputenv("QET_LAZY_FOLIOS", "1"); QETProject lazy_strips(path); qunsetenv("QET_LAZY_FOLIOS"); auto strip_of = [](const QETProject &project) { for (TerminalStrip *strip : project.terminalStrip()) if (strip->name() == QStringLiteral("X99")) return strip->physicalTerminalCount(); return -1; }; check(strip_of(lazy_strips) > 3 && strip_of(lazy_strips) == strip_of(eager), "a terminal strip keeps its terminals when the folios are not built"); } const QList folios = eager.folios(); check(folios.size() > 3, "several folios"); QList images; QList partners; int linked = 0; for (Diagram *folio : folios) { images << render(folio); partners << links(folio); for (Element *element : folio->elements()) linked += !element->linkedElements().isEmpty(); } check(linked > 0, "the project has linked symbols"); qputenv("QET_LAZY_FOLIOS", "1"); auto same = [&](QETProject &lazy, int i, const char *what) { Diagram *folio = lazy.folios().at(i); lazy.buildFolioToShow(folio); if (render(folio) != images.at(i) || links(folio) != partners.at(i)) { std::printf("folio %d, %s: differs\n", i + 1, what); throw std::runtime_error("a folio shown lazily draws or links as opened as usual"); } }; //Each folio first after opening for (int i = 0; i < folios.size(); ++i) { QETProject lazy(path); check(lazy.unloadedFolioCount() == folios.size() - strip_folios.size(), "opened building only the folios of the terminal strip's terminals"); same(lazy, i, "shown first"); check(lazy.unloadedFolioCount() > 0, "showing a folio does not build them all"); } //Every folio, one after the other, then the save QETProject lazy(path); for (int i = 0; i < folios.size(); ++i) same(lazy, i, "shown in turn"); check(lazy.toXml().toString() == eager.toXml().toString(), "saves as opened as usual"); check(lazy.undoStack()->isClean(), "showing folios changes nothing"); //Numbering asks every symbol of the project: it is answered for //the folios not built without building them, as when built QETProject numbered(path); check(numbering(numbered) == numbering(eager), "symbols' numbering as when built"); const QStringList titles = eager.elementAutoNum().keys(); check(!titles.isEmpty(), "the project has element numbering schemes"); 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"); //A symbol placed on a folio, numbered by a scheme others follow QString location, title; int folio = -1; for (const auto &symbol : eager.symbolNumbering()) { if (symbol.numbered && symbol.element && !eager.elementAutoNumTitle(symbol.scheme).isEmpty()) { location = symbol.element->location().toString(); title = eager.elementAutoNumTitle(symbol.scheme); folio = eager.folios().indexOf(symbol.element->diagram()); break; } } check(folio >= 0, "a symbol following a scheme"); const QString placed = place(numbered, folio, location, title); std::printf("placed on folio %d with %s: %s\n", folio + 1, qPrintable(title), qPrintable(placed)); check(numbered.unloadedFolioCount() > 0, "numbering a placed symbol does not build every folio"); QETProject usual(path); check(placed == place(usual, folio, location, title), "a placed symbol numbered as when built"); //The candidates a link tab lists (ElementProvider): found as //when built, building only the folios which hold some const ElementProvider usual_search(&eager); for (const ElementData::Types kinds : {ElementData::Types(ElementData::Slave), ElementData::Types(ElementData::Master), ElementData::Types(ElementData::AllReport), ElementData::Types(ElementData::Terminal)}) { QETProject searched(path); const ElementProvider search(&searched); const QStringList lazy_free = found(search.freeElement(kinds)), usual_free = found(usual_search.freeElement(kinds)); if (lazy_free != usual_free) { for (const QString &u : usual_free) if (!lazy_free.contains(u)) std::printf(" only when built: %s\n", qPrintable(u)); for (const QString &u : lazy_free) if (!usual_free.contains(u)) std::printf(" only lazily: %s\n", qPrintable(u)); } check(lazy_free == usual_free, "free symbols found as when built"); std::printf("free symbols of kinds %d: %d folios built\n", int(kinds), int(folios.size() - searched.unloadedFolioCount())); check(found(search.find(kinds)) == found(usual_search.find(kinds)), "symbols found as when built"); } { QETProject searched(path); ElementProvider(&searched).freeElement(ElementData::Slave); check(searched.unloadedFolioCount() > 0, "a search for free contacts does not build every folio"); } //Selecting a symbol shows its properties, a link tab for a //coil, a contact or a report: the folio and few others built for (const Element::kind kind : {Element::Master, Element::Slave, Element::NextReport, Element::PreviousReport}) { QETProject selected(path); Element *element = nullptr; for (int i = 0 ; !element && i < folios.size() ; ++i) { for (Element *candidate : eager.folios().at(i)->elements()) { if (candidate->linkType() == kind) { selected.buildFolioToShow(selected.folios().at(i)); for (Element *e : selected.folios().at(i)->elements()) if (e->uuid() == candidate->uuid()) element = e; break; } } } check(element, "a symbol of each kind with a link tab"); const int before = selected.unloadedFolioCount(); delete new ElementPropertiesWidget(element); std::printf("properties of a symbol of kind %d: %d more folios built, %d not built\n", int(kind), before - selected.unloadedFolioCount(), selected.unloadedFolioCount()); check(selected.unloadedFolioCount() > 0, "selecting a symbol does not build every folio"); } //Everyday edits on a folio linked to others: the others stay //not built, and the project saves as when they are int busiest = 0, most = -1; for (int i = 0 ; i < folios.size() ; ++i) { int partners = 0; for (Element *element : folios.at(i)->elements()) for (Element *partner : element->linkedElements()) partners += partner->diagram() != folios.at(i); if (partners > most) { most = partners; busiest = i; } } { QETProject edited(path), usual(path); qunsetenv("QET_LAZY_FOLIOS"); QETProject usual_edited(path); qputenv("QET_LAZY_FOLIOS", "1"); edit(edited, busiest); std::printf("edits on folio %d (%d links to other folios): %d folios not built\n", busiest + 1, most, edited.unloadedFolioCount()); check(edited.unloadedFolioCount() > 0, "editing a folio does not build every folio"); edit(usual_edited, busiest); //Made by the edits, a new wire's uuid is random: any uuid the //file had not is "new" before comparing QFile saved(path); saved.open(QIODevice::ReadOnly); const QString before = QString::fromUtf8(saved.readAll()); static const QRegularExpression uuid_re(QStringLiteral("uuid=\"(\\{[0-9a-f-]{36}\\})\"")); auto known = [&before](QString xml) { QString out; qsizetype at = 0; for (auto it = uuid_re.globalMatch(xml) ; it.hasNext() ; ) { const auto match = it.next(); out += xml.mid(at, match.capturedStart() - at); out += before.contains(match.captured(1)) ? match.captured(0) : QStringLiteral("uuid=\"new\""); at = match.capturedEnd(); } return out + xml.mid(at); }; const QStringList lazy_rows = tables(edited), usual_rows = tables(usual_edited); static const QRegularExpression bare_uuid_re(QStringLiteral("\\{[0-9a-f-]{36}\\}")); auto known_row = [&before](QString row) { for (auto it = bare_uuid_re.globalMatch(QString(row)) ; it.hasNext() ; ) { const QString uuid = it.next().captured(0); if (!before.contains(uuid)) row.replace(uuid, QStringLiteral("new")); } return row; }; check(edited.unloadedFolioCount() > 0, "reading the database does not build every folio"); QStringList lazy_known, usual_known; for (const QString &row : lazy_rows) lazy_known << known_row(row); for (const QString &row : usual_rows) usual_known << known_row(row); lazy_known.sort(); usual_known.sort(); int differing = 0; for (int i = 0 ; i < std::max(lazy_known.size(), usual_known.size()) ; ++i) if (lazy_known.value(i) != usual_known.value(i) && differing++ < 5) std::printf(" row %d: lazy %s\n usual %s\n", i, qPrintable(lazy_known.value(i).left(160)), qPrintable(usual_known.value(i).left(160))); std::printf("database after the edits: %d rows, %d differ\n", int(usual_rows.size()), differing); check(!differing, "the database after the edits as when every folio is built"); //A pasted copy has a random uuid, and a folio's symbols are //saved in uuid order: the lines are compared, not their order auto lines = [&known](const QString &xml) { QStringList list = known(xml).split(QLatin1Char('\n')); list.sort(); return list.join(QLatin1Char('\n')); }; const QString a = lines(edited.toXml().toString()), b = lines(usual_edited.toXml().toString()); if (a != b) { QFile fa(output + "/edited-lazy.xml"); fa.open(QIODevice::WriteOnly); fa.write(a.toUtf8()); QFile fb(output + "/edited-usual.xml"); fb.open(QIODevice::WriteOnly); fb.write(b.toUtf8()); } check(a == b, "edits save as when every folio is built"); } //Saving writes the folios not built as they were read, the same //bytes as building them would write const QString usual_text = saved(eager); { QETProject untouched(path); const int unbuilt = untouched.unloadedFolioCount(); const QString untouched_text = saved(untouched); if (untouched_text != usual_text) { QFile fa(output + "/untouched-lazy.xml"); fa.open(QIODevice::WriteOnly); fa.write(untouched_text.toUtf8()); QFile fb(output + "/untouched-usual.xml"); fb.open(QIODevice::WriteOnly); fb.write(usual_text.toUtf8()); } check(untouched_text == usual_text, "a project saved without building, as when built"); //Only the partners of the folios the terminal strip built std::printf("saved untouched: %d of %d folios not built built to save\n", unbuilt - untouched.unloadedFolioCount(), unbuilt); check(untouched.unloadedFolioCount() > 0, "saving does not build every folio"); } //After everyday edits on a folio: as when built, others not built { QETProject edited(path), usual(path); qunsetenv("QET_LAZY_FOLIOS"); QETProject usual_edited(path); qputenv("QET_LAZY_FOLIOS", "1"); edit(edited, busiest); edit(usual_edited, busiest); QFile file(path); file.open(QIODevice::ReadOnly); const QString before = QString::fromUtf8(file.readAll()); static const QRegularExpression uuid_re(QStringLiteral("\\{[0-9a-f-]{36}\\}")); auto lines = [&before](QString text) { for (auto it = uuid_re.globalMatch(QString(text)) ; it.hasNext() ; ) { const QString uuid = it.next().captured(0); if (!before.contains(uuid)) text.replace(uuid, QStringLiteral("new")); } QStringList list = text.split(QLatin1Char('\n')); list.sort(); return list; }; const QStringList la = lines(saved(edited)), lb = lines(saved(usual_edited)); if (la != lb) { int shown = 0; for (const QString &l : lb) if (!la.contains(l) && shown++ < 6) std::printf(" only usual: %s\n", qPrintable(l.trimmed().left(150))); shown = 0; for (const QString &l : la) if (!lb.contains(l) && shown++ < 6) std::printf(" only lazy: %s\n", qPrintable(l.trimmed().left(150))); } check(la == lb, "edits saved without building, as when built"); std::printf("saved after the edits: %d folios not built\n", edited.unloadedFolioCount()); check(edited.unloadedFolioCount() > 0, "saving after edits does not build every folio"); } //A folio not built given a new title: it and the folios its //links reach are built to be saved, the others not { QETProject renamed(path); qunsetenv("QET_LAZY_FOLIOS"); QETProject usual_renamed(path); qputenv("QET_LAZY_FOLIOS", "1"); for (QETProject *project : {&renamed, &usual_renamed}) { Diagram *folio = project->folios().at(busiest); TitleBlockProperties block = folio->border_and_titleblock.exportTitleBlock(); block.title += QStringLiteral(" (renamed)"); folio->border_and_titleblock.importTitleBlock(block); } check(saved(renamed) == saved(usual_renamed), "a renamed folio saved as when built"); std::printf("saved after a folio not built was renamed: %d folios not built\n", renamed.unloadedFolioCount()); check(renamed.unloadedFolioCount() > 0, "renaming a folio does not build every folio to save"); } //Project properties, which any folio's texts can use: every //folio is built to be saved { QETProject changed(path); qunsetenv("QET_LAZY_FOLIOS"); QETProject usual_changed(path); qputenv("QET_LAZY_FOLIOS", "1"); for (QETProject *project : {&changed, &usual_changed}) { DiagramContext properties = project->projectProperties(); properties.addValue(QStringLiteral("lazy_test"), QStringLiteral("1")); project->setProjectProperties(properties); } check(saved(changed) == saved(usual_changed), "changed project properties saved as when built"); check(changed.unloadedFolioCount() == 0, "changed project properties build every folio to save"); } //A potential through folio reports is followed as far as when //every folio is built { Conductor *usual_wire = nullptr; for (Diagram *folio : eager.folios()) for (Element *element : folio->elements()) if ((element->linkType() & Element::AllReport) && !element->conductors().isEmpty() && !usual_wire) usual_wire = element->conductors().first(); check(usual_wire, "a wire on a folio report"); auto potential = [](Conductor *wire) { QStringList list; for (Conductor *c : wire->relatedPotentialConductors(true)) list << c->uuid().toString(); list.sort(); return list; }; QETProject followed(path); Diagram *folio = followed.folios().at(eager.folioIndex(usual_wire->diagram())); followed.buildFolioToShow(folio); Conductor *wire = nullptr; for (Conductor *c : folio->conductors()) if (c->uuid() == usual_wire->uuid()) wire = c; check(wire, "the same wire"); check(potential(wire) == potential(usual_wire), "a potential followed as when built"); std::printf("potential through reports: %d wires, %d folios not built\n", int(potential(wire).size()), followed.unloadedFolioCount()); } //Two symbols sharing a uuid: the database is filled from the //folios, which builds every one of them, so the project opens //with every folio built rather than building them all later { QFile file(path); check(file.open(QIODevice::ReadOnly), "fixture read"); QDomDocument document; check(bool(document.setContent(&file)), "fixture parsed"); file.close(); check(projectDataBase::documentRefusal(document).isEmpty(), "the fixture fills the database itself"); QDomNodeList elements = document.elementsByTagName(QStringLiteral("element")); QList placed; for (int i = 0; i < elements.size(); ++i) { QDomElement e = elements.at(i).toElement(); if (Element::valideXml(e)) placed << e; } check(placed.size() > 1, "two placed symbols"); placed.last().setAttribute(QStringLiteral("uuid"), placed.first().attribute(QStringLiteral("uuid"))); check(!projectDataBase::documentRefusal(document).isEmpty(), "a shared uuid is seen before the folios are read"); const QString shared = output + "/lazy_folios_shared_uuid.qet"; QFile out(shared); check(out.open(QIODevice::WriteOnly), "copy written"); out.write(document.toByteArray(4)); out.close(); qputenv("QET_LAZY_FOLIOS", "1"); qputenv("QET_LAZY_FOLIOS_TRACE", "1"); static int built_late; built_late = 0; const QtMessageHandler previous = qInstallMessageHandler( [](QtMsgType, const QMessageLogContext &, const QString &message) { if (message.contains(QStringLiteral("built for a caller of diagrams()"))) ++built_late; }); QETProject opened(shared); qInstallMessageHandler(previous); qunsetenv("QET_LAZY_FOLIOS_TRACE"); check(opened.state() == QETProject::Ok, "a project with a shared uuid opens"); check(opened.unloadedFolioCount() == 0, "a project with a shared uuid opens with every folio built"); check(built_late == 0, "a project with a shared uuid builds its folios as it reads them, not later"); } //A .qetz opened from its index (stage D2): no //folio read, each read from its part when shown, drawn and //linked as when built, saved as the .qet is { const QString zipped = output + "/lazy_folios.qetz"; QString error; check(eager.writeContainerFile(zipped, &error), "saved as a .qetz"); QETProject indexed(zipped); check(indexed.state() == QETProject::Ok, "the .qetz opens"); check(indexed.openedFromIndex(), "the .qetz opens from its index"); std::printf(".qetz opened from its index: %d of %d folios not built (strip folios %d)\n", indexed.unloadedFolioCount(), int(folios.size()), int(strip_folios.size())); check(indexed.unloadedFolioCount() == folios.size() - strip_folios.size(), "the .qetz opens building only the folios of the terminal strip's terminals"); check(numbering(indexed) == numbering(eager), "symbols' numbering from the index as when built"); for (int i = 0; i < folios.size(); ++i) same(indexed, i, "shown from the .qetz"); check(indexed.toXml().toString() == eager.toXml().toString(), "the .qetz saves as when built"); } std::printf("PASS: %d folios\n", int(folios.size())); } catch (const std::exception &error) { std::printf("FAIL: %s\n", error.what()); std::fflush(stdout); return 1; } std::fflush(stdout); return 0; }