mirror of
https://github.com/qelectrotech/qelectrotech-source-mirror.git
synced 2026-10-14 17:44:13 +02:00
e89ccf2a40
A .qetz now carries index.sqlite: the project database as QElectroTech keeps it -- every table but the drawing items, which exist for built folios only -- with each folio's own data (Diagram::headerXml()) and a stamp of the schema it was written with (projectDataBase::indexFile(), QETProject::writeContainerFile(): Save, --resave, --set-titleblock, a script's and live mode's save). Written when every folio, symbol and wire has a uuid of its own; a reader that does not know the entry skips it. With QET_LAZY_FOLIOS=1, a .qetz with an index opens from it: the project document is joined without its folios (QetContainer::join() with folio_files), the database and the stores of symbol information and wire properties are filled from the index (fillFromIndex()), each folio is made from its own data and keeps the name of its part, read when the folio is built (Diagram::deferredXml(), QETProject::readFolioPart(), QetContainer::readFolio() -- pictures and engineering data back from project.sqlite, QetContainerDb::restoreFolio()). A terminal strip finds its terminals' folios in the database. Any other .qetz, or one whose index cannot be used, is read whole as before. The index is untrusted: refused unless written with this database's schema, every table read a plain table (not a view), nothing in its schema run (trusted_schema off); refused, the project is read whole. Checked: an index of garbage, a wrong stamp, a table made a view -- each refused and the project saved as a plain save. The 136-folio f2099 as .qetz opens in 1.8 s (3.65 s read whole, 29 s building every folio); what is left is mostly its 7 MB of symbol definitions (0.8 s). The file grows from 2.6 to 5.2 MB, the index overlapping project.sqlite until D3. tst_lazyfolios: a .qetz opened from its index builds only the terminal strip's folios, numbering read as when built, every folio shown in turn draws and links as when built, the save as when built; fails when a folio part's engineering data is not put back. Corpus, 342 projects: 340 .qetz carry an index and open from it (2 have symbols sharing a uuid); saved back as .qet = eager = master on all 342. Probe on industrial and f2099 .qetz: every folio as eager but the 3 render-noise folios of f2099. In a real window the .qetz opens and pages building only shown folios and partners. ctest 96/96 with and without QET_LAZY_FOLIOS=1. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
639 lines
28 KiB
C++
639 lines
28 KiB
C++
// 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 <QApplication>
|
|
#include <QFile>
|
|
#include <QFontDatabase>
|
|
#include <QImage>
|
|
#include <QSqlRecord>
|
|
#include <QSqlQuery>
|
|
#include <QRegularExpression>
|
|
#include <QPainter>
|
|
#include <QSettings>
|
|
#include <QTemporaryDir>
|
|
#include <cstdio>
|
|
#include <stdexcept>
|
|
#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 <QKeyEvent>
|
|
#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 <QDomDocument>
|
|
|
|
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<QPointer<Element>> &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<Element *> 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<int, QByteArray> 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<int> 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<Diagram *> folios = eager.folios();
|
|
check(folios.size() > 3, "several folios");
|
|
QList<QImage> images; QList<QStringList> 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<QDomElement> 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;
|
|
}
|