mirror of
https://github.com/qelectrotech/qelectrotech-source-mirror.git
synced 2026-10-19 20:44:16 +02:00
Link a symbol built to its partners from the project database's rows
A symbol built after opening (lazy folios) links to the partners its link rows name, and to the symbols whose rows name it; its file's list only adds what the rows miss. The rows keep a partner on a folio not built until it is built, in its saved place, so a symbol no longer keeps a list of its own: the list read from the file is dropped once opening has filled the database. pendingLinks(), waitingLinks(), keepsSavedLinks(), linksToSave() and QETProject::waitingLinkTargets() go; the callers that asked a symbol for its waiting partners ask the database (waitingPartners()). When its links change, a symbol's rows are written from its built partners and the waiting partners its rows held, in their order (rowsToWrite()), so a full rewrite of the table keeps them too. A symbol built alone as a partner on a folio not built (Diagram::buildSymbols()) counts as built: its rows are kept, it is not waiting (waitingSymbols(), waitingPartners()), so saving does not build its folio, and removing it does not remove the rows naming it. A row naming a placed symbol that has not linked yet (its folio being built) is kept too. At opening the database holds no link until its fill (forgetLinks()); the symbols read their files' lists until then, and the fill takes their order: a symbol's own rows, its list, then the rows naming it. The symbols waiting on folios not built are cached and updated as folios are built. QET_CHECK_LINK_ROWS now checks the rows against the links: each built partner in its order, no other row but to a folio not built. tst_resaveunchanged::undoKeepsLinksToUnbuiltFolios: m_000 opened with lazy folios, a contact, its folio or its coil's folio (not built) removed, then undone: every link saved as before. QETZ-DB-FIRST stage 2a-3. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -17,7 +17,6 @@
|
||||
*/
|
||||
#include "projectdatabase.h"
|
||||
#include "../qetversion.h"
|
||||
|
||||
#include "sqlreadonly.h"
|
||||
|
||||
#include "../autoNum/assignvariables.h"
|
||||
@@ -54,6 +53,7 @@
|
||||
#include <QRegularExpression>
|
||||
#include <QSqlDriver>
|
||||
#include <QSqlError>
|
||||
#include <optional>
|
||||
|
||||
|
||||
|
||||
@@ -861,6 +861,7 @@ bool projectDataBase::populateFromDocument(const QDomDocument &document, QString
|
||||
}
|
||||
}
|
||||
}
|
||||
m_holds_links = true;
|
||||
|
||||
for (const DocumentConductor &conductor : std::as_const(conductors))
|
||||
{
|
||||
@@ -1647,8 +1648,13 @@ void projectDataBase::addElement(Element *element)
|
||||
*/
|
||||
void projectDataBase::removeElement(Element *element)
|
||||
{
|
||||
m_content_changed = true;
|
||||
const QUuid uuid = element->uuid();
|
||||
//A symbol built alone as a partner (Diagram::buildSymbols()) was
|
||||
//never placed: its rows are those of the symbol its folio builds
|
||||
if (!m_placed_elements.contains(uuid, element)) {
|
||||
return;
|
||||
}
|
||||
m_content_changed = true;
|
||||
removeElementRows(uuid);
|
||||
unplaceElement(element, uuid);
|
||||
//Another symbol still holds the uuid (F100): the rows are its own.
|
||||
@@ -3872,8 +3878,16 @@ QString projectDataBase::prefillReport() const
|
||||
void projectDataBase::populateLinkTable()
|
||||
{
|
||||
m_dirty_link_elements.clear();
|
||||
const QSet<QUuid> waiting = waitingSymbols();
|
||||
const QSet<QUuid> waiting_symbols = waitingSymbols();
|
||||
const auto waiting = [&waiting_symbols](const QUuid &uuid) {return waiting_symbols.contains(uuid);};
|
||||
QSqlQuery query(m_data_base);
|
||||
//The rows as they are: the order they give, and the partners on
|
||||
//folios not built, which no built symbol knows
|
||||
QHash<QUuid, QList<std::pair<QUuid, int>>> old_rows;
|
||||
if (query.exec(QStringLiteral("SELECT element_uuid, linked_uuid, group_index FROM link ORDER BY ord, rowid")))
|
||||
while (query.next())
|
||||
old_rows[QUuid(query.value(0).toString())].append(
|
||||
{QUuid(query.value(1).toString()), query.value(2).isNull() ? -1 : query.value(2).toInt()});
|
||||
query.exec(QStringLiteral("DELETE FROM link"));
|
||||
query.prepare(QStringLiteral("INSERT INTO link (element_uuid, linked_uuid, group_index, ord) "
|
||||
"VALUES (:element_uuid, :linked_uuid, :group_index, :ord)"));
|
||||
@@ -3890,8 +3904,11 @@ void projectDataBase::populateLinkTable()
|
||||
}
|
||||
const ElementProvider ep(diagram);
|
||||
for (const auto &elmt : ep.find(allElementTypes()))
|
||||
insertLinkRows(query, elmt, waiting);
|
||||
insertLinkRows(query, elmt, waiting,
|
||||
old_rows.contains(elmt->uuid()) ? old_rows.value(elmt->uuid())
|
||||
: elmt->savedLinkList());
|
||||
}
|
||||
m_holds_links = true;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -3919,8 +3936,9 @@ void projectDataBase::queueLinks(Element *element)
|
||||
/**
|
||||
@brief projectDataBase::linkRowMismatches
|
||||
A test and debugging aid (QET_CHECK_LINK_ROWS): the placed
|
||||
symbols whose link rows are not the links they save, in order
|
||||
(Element::linksToSave(), insertLinkRows())
|
||||
symbols whose link rows are not their links: each built partner, in
|
||||
the order the symbol has them, and no other, but partners on folios
|
||||
not built (QETZ-DB-FIRST stage 2a-3: the rows are what is saved)
|
||||
@return one entry per such symbol: its uuid
|
||||
*/
|
||||
QStringList projectDataBase::linkRowMismatches()
|
||||
@@ -3928,26 +3946,29 @@ QStringList projectDataBase::linkRowMismatches()
|
||||
flushLinks();
|
||||
const QSet<QUuid> waiting = waitingSymbols();
|
||||
QStringList mismatches;
|
||||
QSqlQuery query(m_data_base);
|
||||
query.prepare(QStringLiteral("SELECT linked_uuid, group_index FROM link "
|
||||
"WHERE element_uuid = :uuid ORDER BY ord, rowid"));
|
||||
for (auto it = m_placed_elements.cbegin() ; it != m_placed_elements.cend() ; ++it)
|
||||
{
|
||||
Element *element = it.value();
|
||||
if (!element) {
|
||||
continue;
|
||||
}
|
||||
QList<std::pair<QString, int>> expected;
|
||||
for (const Element::SavedLink &link : element->linksToSave(waiting))
|
||||
if (!link.built || m_placed_elements.contains(link.uuid, link.built))
|
||||
expected.append({link.uuid.toString(), link.group_index});
|
||||
QList<std::pair<QString, int>> rows;
|
||||
query.bindValue(QStringLiteral(":uuid"), element->uuid().toString());
|
||||
if (query.exec())
|
||||
while (query.next())
|
||||
rows.append({query.value(0).toString(),
|
||||
query.value(1).isNull() ? -1 : query.value(1).toInt()});
|
||||
if (rows != expected) {
|
||||
QList<std::pair<QUuid, int>> expected;
|
||||
for (Element *linked : element->linkedElements())
|
||||
if (m_placed_elements.contains(linked->uuid(), linked) || builtAlone(linked))
|
||||
expected.append({linked->uuid(), element->groupIndexForElement(linked)});
|
||||
QList<std::pair<QUuid, int>> rows;
|
||||
bool stray = false;
|
||||
for (const auto &row : linkRowsOf(element->uuid())) {
|
||||
bool built = false;
|
||||
for (const auto &link : std::as_const(expected))
|
||||
built |= link.first == row.first;
|
||||
if (built) {
|
||||
rows.append(row);
|
||||
} else {
|
||||
stray |= !waiting.contains(row.first);
|
||||
}
|
||||
}
|
||||
if (stray || rows != expected) {
|
||||
mismatches << element->uuid().toString();
|
||||
}
|
||||
}
|
||||
@@ -3989,36 +4010,133 @@ bool projectDataBase::savedLinks(const Element *element, QList<std::pair<QUuid,
|
||||
|
||||
/**
|
||||
@brief projectDataBase::waitingSymbols
|
||||
@return the symbols on folios not built yet (lazy folios): partners a
|
||||
symbol keeps its saved links to until they are built
|
||||
@return the symbols on folios not built yet (lazy folios) and not
|
||||
built alone as partners either (Diagram::buildSymbols()): partners a
|
||||
symbol keeps its rows to until they are built
|
||||
*/
|
||||
QSet<QUuid> projectDataBase::waitingSymbols() const
|
||||
{
|
||||
return m_project && m_project->unloadedFolioCount() ? m_project->symbolsOnUnbuiltFolios()
|
||||
: QSet<QUuid>();
|
||||
if (!m_project || !m_project->unloadedFolioCount()) {
|
||||
return QSet<QUuid>();
|
||||
}
|
||||
//Kept while the same folios are not built and as many symbols are
|
||||
//built alone: finding them reads every such folio's rows
|
||||
QList<Diagram *> unbuilt;
|
||||
int alone = 0;
|
||||
for (Diagram *diagram : m_project->folios())
|
||||
if (!diagram->isLoaded()) {
|
||||
unbuilt << diagram;
|
||||
alone += int(diagram->partialElements().size());
|
||||
}
|
||||
if (unbuilt == m_waiting_folios && alone == m_waiting_alone) {
|
||||
return m_waiting_symbols;
|
||||
}
|
||||
//Folios only built since: their symbols are not waiting any more
|
||||
bool only_built = true;
|
||||
for (Diagram *diagram : std::as_const(unbuilt))
|
||||
only_built &= m_waiting_folios.contains(diagram);
|
||||
for (Diagram *diagram : std::as_const(m_waiting_folios))
|
||||
if (!unbuilt.contains(diagram))
|
||||
only_built &= m_project->folios().contains(diagram) && diagram->isLoaded();
|
||||
QSet<QUuid> waiting;
|
||||
if (only_built && !m_waiting_folios.isEmpty()) {
|
||||
waiting = m_waiting_symbols;
|
||||
for (Diagram *diagram : std::as_const(m_waiting_folios))
|
||||
if (!unbuilt.contains(diagram))
|
||||
for (Element *element : diagram->elements())
|
||||
waiting.remove(element->uuid());
|
||||
} else {
|
||||
waiting = m_project->symbolsOnUnbuiltFolios();
|
||||
}
|
||||
for (Diagram *diagram : std::as_const(unbuilt))
|
||||
for (const QUuid &built : diagram->partialElements().keys())
|
||||
waiting.remove(built);
|
||||
m_waiting_folios = unbuilt;
|
||||
m_waiting_alone = alone;
|
||||
m_waiting_symbols = waiting;
|
||||
return waiting;
|
||||
}
|
||||
|
||||
/**
|
||||
@brief projectDataBase::builtAlone
|
||||
@return true if @p element is a symbol built alone, as a partner, on a
|
||||
folio of the project not built (Diagram::buildSymbols())
|
||||
*/
|
||||
bool projectDataBase::builtAlone(Element *element) const
|
||||
{
|
||||
Diagram *diagram = element->diagram();
|
||||
return m_project && diagram && !diagram->isLoaded()
|
||||
&& m_project->folios().contains(diagram)
|
||||
&& diagram->partialElements().value(element->uuid()) == element;
|
||||
}
|
||||
|
||||
/**
|
||||
@brief projectDataBase::linksLater
|
||||
@return true if a symbol of uuid @p uuid is placed but not linked from
|
||||
its rows yet (Element::initLink(), as its folio is being built): it
|
||||
links to the partners its rows and theirs name, so these are kept
|
||||
*/
|
||||
bool projectDataBase::linksLater(const QUuid &uuid) const
|
||||
{
|
||||
for (const QPointer<Element> &element : m_placed_elements.values(uuid))
|
||||
if (element && !element->savedLinkList().isEmpty())
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
@brief projectDataBase::rowsToWrite
|
||||
@return the link rows of @p element: its built partners, and the
|
||||
partners on folios not built (@p waiting) that @p old_rows, its rows as
|
||||
they were, name, and those placed but not linked yet (linksLater()).
|
||||
The rows keep the order of @p old_rows, the order the file gave, and
|
||||
partners linked since come after them: the order the symbol has its
|
||||
partners in changes with the order symbols are built.
|
||||
A built partner the project no longer has (on a removed folio, linked
|
||||
still for the undo) gets no row.
|
||||
*/
|
||||
QList<std::pair<QUuid, int>> projectDataBase::rowsToWrite(Element *element, const std::function<bool(const QUuid &)> &waiting,
|
||||
const QList<std::pair<QUuid, int>> &old_rows) const
|
||||
{
|
||||
QList<Element *> built;
|
||||
for (Element *linked : element->linkedElements())
|
||||
if (m_placed_elements.contains(linked->uuid(), linked) || builtAlone(linked))
|
||||
built << linked;
|
||||
QList<std::pair<QUuid, int>> rows;
|
||||
QSet<Element *> written;
|
||||
for (const auto &row : old_rows) {
|
||||
Element *linked = nullptr;
|
||||
for (Element *partner : std::as_const(built))
|
||||
if (partner->uuid() == row.first && !written.contains(partner)) { linked = partner; break; }
|
||||
if (linked) {
|
||||
written.insert(linked);
|
||||
rows.append({linked->uuid(), element->groupIndexForElement(linked)});
|
||||
} else if (waiting(row.first) || linksLater(row.first)) {
|
||||
rows.append(row);
|
||||
}
|
||||
}
|
||||
for (Element *partner : std::as_const(built))
|
||||
if (!written.contains(partner))
|
||||
rows.append({partner->uuid(), element->groupIndexForElement(partner)});
|
||||
return rows;
|
||||
}
|
||||
|
||||
/**
|
||||
@brief projectDataBase::insertLinkRows
|
||||
Write @p element's link rows with @p insert (prepared with :element_uuid,
|
||||
:linked_uuid, :group_index and :ord): its partners as it saves them
|
||||
(Element::linksToSave()), in that order, so the rows and the file say
|
||||
the same: @p waiting is the symbols on folios not built, as a save
|
||||
takes them (waitingSymbols()). A built partner the project no longer has (on a removed
|
||||
folio, linked still for the undo) gets no row.
|
||||
:linked_uuid, :group_index and :ord), as rowsToWrite() gives them, in
|
||||
that order: @p waiting is the symbols on folios not built
|
||||
(waitingSymbols()) tells which, @p old_rows its rows before.
|
||||
*/
|
||||
void projectDataBase::insertLinkRows(QSqlQuery &insert, Element *element,
|
||||
const QSet<QUuid> &waiting)
|
||||
void projectDataBase::insertLinkRows(QSqlQuery &insert, Element *element, const std::function<bool(const QUuid &)> &waiting,
|
||||
const QList<std::pair<QUuid, int>> &old_rows)
|
||||
{
|
||||
int ord = 0;
|
||||
for (const Element::SavedLink &link : element->linksToSave(waiting))
|
||||
for (const auto &link : rowsToWrite(element, waiting, old_rows))
|
||||
{
|
||||
if (link.built && !m_placed_elements.contains(link.uuid, link.built)) {
|
||||
continue;
|
||||
}
|
||||
insert.bindValue(QStringLiteral(":element_uuid"), element->uuid().toString());
|
||||
insert.bindValue(QStringLiteral(":linked_uuid"), link.uuid.toString());
|
||||
insert.bindValue(QStringLiteral(":group_index"), link.group_index >= 0 ? QVariant(link.group_index) : QVariant());
|
||||
insert.bindValue(QStringLiteral(":linked_uuid"), link.first.toString());
|
||||
insert.bindValue(QStringLiteral(":group_index"), link.second >= 0 ? QVariant(link.second) : QVariant());
|
||||
insert.bindValue(QStringLiteral(":ord"), ord++);
|
||||
if (!insert.exec()) {
|
||||
qDebug() << "projectDataBase::insertLinkRows insert error : " << insert.lastError();
|
||||
@@ -4026,6 +4144,65 @@ void projectDataBase::insertLinkRows(QSqlQuery &insert, Element *element,
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@brief projectDataBase::linkRowsOf
|
||||
@return the link rows of @p element: each partner and its contact group
|
||||
(-1 if none), in their order
|
||||
*/
|
||||
QList<std::pair<QUuid, int>> projectDataBase::linkRowsOf(const QUuid &element) const
|
||||
{
|
||||
QList<std::pair<QUuid, int>> rows;
|
||||
QSqlQuery query(m_data_base);
|
||||
query.prepare(QStringLiteral("SELECT linked_uuid, group_index FROM link "
|
||||
"WHERE element_uuid = :uuid ORDER BY ord, rowid"));
|
||||
query.bindValue(QStringLiteral(":uuid"), element.toString());
|
||||
if (query.exec())
|
||||
while (query.next())
|
||||
rows.append({QUuid(query.value(0).toString()),
|
||||
query.value(1).isNull() ? -1 : query.value(1).toInt()});
|
||||
return rows;
|
||||
}
|
||||
|
||||
/**
|
||||
@brief projectDataBase::partnersToLink
|
||||
@return the partners a symbol built links to (Element::initLink()): its
|
||||
link rows, in their order, then the symbols whose rows name it (no
|
||||
contact group: the group is the row owner's)
|
||||
*/
|
||||
QList<std::pair<QUuid, int>> projectDataBase::partnersToLink(const QUuid &element)
|
||||
{
|
||||
flushLinks();
|
||||
QList<std::pair<QUuid, int>> partners = linkRowsOf(element);
|
||||
QSqlQuery query(m_data_base);
|
||||
query.prepare(QStringLiteral("SELECT element_uuid FROM link WHERE linked_uuid = :uuid ORDER BY rowid"));
|
||||
query.bindValue(QStringLiteral(":uuid"), element.toString());
|
||||
if (query.exec())
|
||||
while (query.next())
|
||||
partners.append({QUuid(query.value(0).toString()), -1});
|
||||
return partners;
|
||||
}
|
||||
|
||||
/**
|
||||
@brief projectDataBase::waitingPartners
|
||||
@return the partners of @p element its link rows name among @p waiting,
|
||||
the symbols on folios not built (waitingSymbols()): those it is not
|
||||
linked to yet
|
||||
*/
|
||||
QSet<QUuid> projectDataBase::waitingPartners(const QUuid &element, const QSet<QUuid> &waiting)
|
||||
{
|
||||
QSet<QUuid> partners;
|
||||
if (waiting.isEmpty()) {
|
||||
return partners;
|
||||
}
|
||||
flushLinks();
|
||||
//A partner built alone (Diagram::buildSymbols()) is built: its
|
||||
//folio, not built, still holds it
|
||||
for (const auto &row : linkRowsOf(element))
|
||||
if (waiting.contains(row.first) && waitingSymbols().contains(row.first))
|
||||
partners.insert(row.first);
|
||||
return partners;
|
||||
}
|
||||
|
||||
/**
|
||||
@brief projectDataBase::flushLinks
|
||||
Write the link rows of every element queued by linksChanged()
|
||||
@@ -4038,18 +4215,42 @@ void projectDataBase::flushLinks()
|
||||
|
||||
const auto dirty = m_dirty_link_elements;
|
||||
m_dirty_link_elements.clear();
|
||||
const QSet<QUuid> waiting = waitingSymbols();
|
||||
//The symbols on folios not built, looked up only when a row names
|
||||
//a partner not linked: finding them reads every such folio's rows
|
||||
std::optional<QSet<QUuid>> waiting_symbols;
|
||||
const auto waiting = [this, &waiting_symbols](const QUuid &uuid) {
|
||||
if (!waiting_symbols) {
|
||||
waiting_symbols = waitingSymbols();
|
||||
}
|
||||
return waiting_symbols->contains(uuid);
|
||||
};
|
||||
const bool own_transaction = m_data_base.transaction();
|
||||
QSqlQuery remove(m_data_base);
|
||||
remove.prepare(QStringLiteral("DELETE FROM link WHERE element_uuid = :uuid"));
|
||||
QSqlQuery insert(m_data_base);
|
||||
insert.prepare(QStringLiteral("INSERT INTO link (element_uuid, linked_uuid, group_index, ord) "
|
||||
"VALUES (:element_uuid, :linked_uuid, :group_index, :ord)"));
|
||||
QList<QPointer<Element>> not_linked;
|
||||
for (const QPointer<Element> &element : dirty)
|
||||
{
|
||||
if (!element) {
|
||||
continue;
|
||||
}
|
||||
const bool placed = m_placed_elements.contains(element->uuid(), element);
|
||||
//A symbol not linked from its rows yet (Element::initLink())
|
||||
//keeps them as they are: a partner linking to it first has not
|
||||
//given it its contact groups. Written once it is linked.
|
||||
if (placed && m_holds_links && !element->savedLinkList().isEmpty()) {
|
||||
not_linked << element;
|
||||
continue;
|
||||
}
|
||||
QList<std::pair<QUuid, int>> old_rows;
|
||||
if (placed) {
|
||||
old_rows = linkRowsOf(element->uuid());
|
||||
if (old_rows.isEmpty()) {
|
||||
old_rows = element->savedLinkList();
|
||||
}
|
||||
}
|
||||
remove.bindValue(QStringLiteral(":uuid"), element->uuid().toString());
|
||||
if (!remove.exec()) {
|
||||
qDebug() << "projectDataBase::flushLinks remove error : " << remove.lastError();
|
||||
@@ -4057,14 +4258,15 @@ void projectDataBase::flushLinks()
|
||||
//An element taken off its folio is unlinked first (Diagram::
|
||||
//removeItem()), so it writes no rows here; one on a removed
|
||||
//folio is not, and writes none either.
|
||||
if (!m_placed_elements.contains(element->uuid(), element)) {
|
||||
if (!placed) {
|
||||
continue;
|
||||
}
|
||||
insertLinkRows(insert, element, waiting);
|
||||
insertLinkRows(insert, element, waiting, old_rows);
|
||||
}
|
||||
if (own_transaction) {
|
||||
m_data_base.commit();
|
||||
}
|
||||
m_dirty_link_elements << not_linked;
|
||||
}
|
||||
|
||||
void projectDataBase::populateDiagramInfoTable()
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
#ifndef PROJECTDATABASE_H
|
||||
#define PROJECTDATABASE_H
|
||||
|
||||
#include <functional>
|
||||
#include <QObject>
|
||||
#include <QSqlDatabase>
|
||||
#include <QSqlQuery>
|
||||
@@ -156,6 +157,14 @@ class projectDataBase : public QObject
|
||||
QStringList conductorPropertiesMismatches() const;
|
||||
QStringList linkRowMismatches();
|
||||
bool savedLinks(const Element *element, QList<std::pair<QUuid, int>> *links);
|
||||
//The link rows are written (opening filled them): a symbol
|
||||
//built links from them (Element::initLink())
|
||||
bool holdsLinks() const {return m_holds_links;}
|
||||
void forgetLinks() {m_holds_links = false;}
|
||||
QList<std::pair<QUuid, int>> partnersToLink(const QUuid &element);
|
||||
QList<std::pair<QUuid, int>> linkRowsOf(const QUuid &element) const;
|
||||
QSet<QUuid> waitingPartners(const QUuid &element, const QSet<QUuid> &waiting);
|
||||
QSet<QUuid> waitingSymbols() const;
|
||||
|
||||
//Fill the three stores from a project's document before its
|
||||
//folios are built, then, once
|
||||
@@ -255,8 +264,12 @@ class projectDataBase : public QObject
|
||||
void populateConductorTable();
|
||||
void populateLinkTable();
|
||||
void flushLinks();
|
||||
void insertLinkRows(QSqlQuery &insert, Element *element, const QSet<QUuid> &waiting);
|
||||
QSet<QUuid> waitingSymbols() const;
|
||||
void insertLinkRows(QSqlQuery &insert, Element *element, const std::function<bool(const QUuid &)> &waiting,
|
||||
const QList<std::pair<QUuid, int>> &old_rows);
|
||||
bool builtAlone(Element *element) const;
|
||||
bool linksLater(const QUuid &uuid) const;
|
||||
QList<std::pair<QUuid, int>> rowsToWrite(Element *element, const std::function<bool(const QUuid &)> &waiting,
|
||||
const QList<std::pair<QUuid, int>> &old_rows) const;
|
||||
void populateDrawingItemTables();
|
||||
bool populateFromDocument(const QDomDocument &document, QString *why = nullptr);
|
||||
bool writeDrawingItem(QObject *object);
|
||||
@@ -332,6 +345,12 @@ class projectDataBase : public QObject
|
||||
//so the rows are written when next read, as drawing items are.
|
||||
QList<QPointer<Element>> m_dirty_link_elements;
|
||||
QSqlQuery m_saved_links_query;
|
||||
bool m_holds_links = false;
|
||||
/// waitingSymbols() as it was last, for these folios not built
|
||||
/// and this count of symbols built alone on them
|
||||
mutable QList<Diagram *> m_waiting_folios;
|
||||
mutable int m_waiting_alone = 0;
|
||||
mutable QSet<QUuid> m_waiting_symbols;
|
||||
//Elements moved since their folio cell was last written: a move
|
||||
//sends xChanged and yChanged for every step of a drag, so the
|
||||
//cell is written when next read, as drawing items are.
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
#include "qetgraphicsitem/element.h"
|
||||
#include "qetgraphicsitem/terminalelement.h"
|
||||
#include "qetproject.h"
|
||||
#include "dataBase/projectdatabase.h"
|
||||
|
||||
#include <QAbstractItemModel>
|
||||
|
||||
@@ -111,7 +112,8 @@ QVector <QPointer<Element>> ElementProvider::freeElement(ElementData::Types filt
|
||||
{
|
||||
if (filter & elmt_->elementData().m_type &&
|
||||
elmt_->isFree() &&
|
||||
!elmt_->pendingLinks().intersects(waiting))
|
||||
(waiting.isEmpty()
|
||||
|| m_project->dataBase()->waitingPartners(elmt_->uuid(), waiting).isEmpty()))
|
||||
{
|
||||
free_elmt << elmt_;
|
||||
}
|
||||
|
||||
@@ -1438,16 +1438,14 @@ QDomElement Element::toXml(
|
||||
|
||||
//if this element is linked to other elements, save the uuid of each:
|
||||
//as the project database's link rows list them (QETZ-DB-FIRST stage
|
||||
//2a), or, for a symbol the database does not hold, as it lists them
|
||||
//itself (linksToSave())
|
||||
//2a), or, for a symbol the database does not hold, its partners
|
||||
QList<std::pair<QUuid, int>> links;
|
||||
projectDataBase *database = diagram() && diagram()->project()
|
||||
? diagram()->project()->dataBase() : nullptr;
|
||||
if (!database || !database->savedLinks(this, &links)) {
|
||||
links.clear();
|
||||
for (const SavedLink &link : linksToSave(diagram() && diagram()->project()
|
||||
? diagram()->project()->waitingLinkTargets() : QSet<QUuid>()))
|
||||
links.append({link.uuid, link.group_index});
|
||||
for (Element *elmt : std::as_const(connected_elements))
|
||||
links.append({elmt->uuid(), m_group_index_map.value(elmt, -1)});
|
||||
}
|
||||
if (!links.isEmpty()) {
|
||||
QDomElement links_uuids = document.createElement(QStringLiteral("links_uuids"));
|
||||
@@ -1815,10 +1813,14 @@ QList <QPair <Terminal *, Terminal *> > Element::AlignedFreeTerminals() const
|
||||
|
||||
/**
|
||||
@brief Element::initLink
|
||||
Initialise the link between this element and other elements.
|
||||
This method can be call once because init the link according to
|
||||
uuid store in a private list, after link, the list is clear, so
|
||||
call another time do nothing.
|
||||
Link this symbol, just read, to its partners already built. Once the
|
||||
project database holds the link rows, the partners are those its rows
|
||||
name (QETZ-DB-FIRST stage 2a-3): its own rows, in their order, then the
|
||||
symbols whose rows name it, then any its file names that the rows miss.
|
||||
A partner not built yet (lazy folios) is linked when it is, from the
|
||||
rows, which keep it until then. While a project opens, before the
|
||||
database is filled, the partners are those its file names, and the list
|
||||
is kept for the fill.
|
||||
|
||||
@param prj :
|
||||
ownership project of this element and other element to be linked
|
||||
@@ -1828,88 +1830,84 @@ void Element::initLink(QETProject *prj)
|
||||
// if nothing to link return now
|
||||
if (tmp_uuids_link.isEmpty()) return;
|
||||
|
||||
projectDataBase *database = prj->dataBase();
|
||||
const bool from_rows = database->holdsLinks();
|
||||
QList<LinkInfo> links;
|
||||
QSet<QUuid> listed;
|
||||
const QList<std::pair<QUuid, int>> rows = from_rows ? database->partnersToLink(uuid())
|
||||
: QList<std::pair<QUuid, int>>();
|
||||
const QList<std::pair<QUuid, int>> own_rows = from_rows ? database->linkRowsOf(uuid())
|
||||
: QList<std::pair<QUuid, int>>();
|
||||
//Its own rows first, then its file's list, then the symbols whose
|
||||
//rows name it: a partner's row gives no contact group, so it
|
||||
//comes after the lists that do
|
||||
for (const auto &row : own_rows) {
|
||||
if (!listed.contains(row.first)) {
|
||||
listed.insert(row.first);
|
||||
links << LinkInfo(row.first, row.second);
|
||||
}
|
||||
}
|
||||
for (const auto &link_info : std::as_const(tmp_uuids_link)) {
|
||||
if (!listed.contains(link_info.uuid)) {
|
||||
listed.insert(link_info.uuid);
|
||||
links << link_info;
|
||||
}
|
||||
}
|
||||
for (const auto &row : rows) {
|
||||
if (!listed.contains(row.first)) {
|
||||
listed.insert(row.first);
|
||||
links << LinkInfo(row.first, row.second);
|
||||
}
|
||||
}
|
||||
|
||||
//The symbols built: on the folios built, and those built alone as
|
||||
//partners on folios not built (Diagram::buildSymbols()). A partner
|
||||
//not built yet (lazy folios) is linked when it is, and the
|
||||
//saved list is kept until then, for the order it gives
|
||||
//partners on folios not built (Diagram::buildSymbols())
|
||||
ElementProvider ep(prj->folios());
|
||||
QList<QUuid> uuids;
|
||||
for (const auto &linkInfo : tmp_uuids_link) {
|
||||
for (const auto &linkInfo : std::as_const(links)) {
|
||||
uuids.append(linkInfo.uuid);
|
||||
}
|
||||
QList<Element *> elements = ep.fromUuids(uuids);
|
||||
for (int i = 0; i < tmp_uuids_link.size(); ++i) {
|
||||
for (const auto &link_info : std::as_const(links)) {
|
||||
for (Element *elmt : elements) {
|
||||
if (elmt->uuid() == tmp_uuids_link[i].uuid) {
|
||||
if (elmt->uuid() == link_info.uuid) {
|
||||
elmt->linkToElement(this);
|
||||
if (tmp_uuids_link[i].group_index >= 0) {
|
||||
m_group_index_map[elmt] = tmp_uuids_link[i].group_index;
|
||||
if (link_info.group_index >= 0) {
|
||||
m_group_index_map[elmt] = link_info.group_index;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
restoreSavedLinkOrder();
|
||||
if (pendingLinks().isEmpty() || !prj->unloadedFolioCount()) {
|
||||
restoreLinkOrder(uuids);
|
||||
if (from_rows) {
|
||||
//Each partner puts its partners back in the order of its rows:
|
||||
//this one was linked last
|
||||
for (Element *partner : std::as_const(connected_elements)) {
|
||||
QList<QUuid> order;
|
||||
for (const auto &row : database->partnersToLink(partner->uuid()))
|
||||
order << row.first;
|
||||
partner->restoreLinkOrder(order);
|
||||
}
|
||||
tmp_uuids_link.clear();
|
||||
}
|
||||
//Otherwise the project opens: the list is kept for the database's
|
||||
//fill, which takes its order and its partners not built from it
|
||||
//(QETProject::readProjectXml() drops it after)
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Element::linksToSave
|
||||
The partners this symbol saves, in the order it saves them: the order
|
||||
it was saved with (tmp_uuids_link), then partners linked since. A
|
||||
partner on a folio not built yet (lazy folios), which this symbol will
|
||||
be linked to, is in the list too: saving needs no partner built (stage
|
||||
D5a). toXml() writes this list, and the project database's link rows
|
||||
are written from it, so the two are the same (QETZ-DB-FIRST stage 2a).
|
||||
@param waiting the symbols on folios not built: a saved partner among
|
||||
them is kept (QETProject::symbolsOnUnbuiltFolios())
|
||||
@return each partner's uuid, its contact group (-1 if none), and the
|
||||
partner itself when it is built (nullptr for one waiting)
|
||||
@brief Element::savedLinkList
|
||||
@return the links this symbol was read with and keeps (initLink()):
|
||||
each partner's uuid and contact group (-1 if none), in its file's order
|
||||
*/
|
||||
QList<Element::SavedLink> Element::linksToSave(const QSet<QUuid> &waiting) const
|
||||
QList<std::pair<QUuid, int>> Element::savedLinkList() const
|
||||
{
|
||||
QList<SavedLink> links;
|
||||
if (keepsSavedLinks() && diagram() && diagram()->project()) {
|
||||
QSet<Element *> written;
|
||||
for (const auto &link_info : std::as_const(tmp_uuids_link)) {
|
||||
Element *linked = nullptr;
|
||||
for (Element *elmt : std::as_const(connected_elements))
|
||||
if (elmt->uuid() == link_info.uuid && !written.contains(elmt)) { linked = elmt; break; }
|
||||
if (linked) {
|
||||
written.insert(linked);
|
||||
links.append({linked->uuid(), m_group_index_map.value(linked, -1), linked});
|
||||
} else if (waiting.contains(link_info.uuid)) {
|
||||
links.append({link_info.uuid, link_info.group_index, nullptr});
|
||||
}
|
||||
}
|
||||
for (Element *elmt : std::as_const(connected_elements))
|
||||
if (!written.contains(elmt))
|
||||
links.append({elmt->uuid(), m_group_index_map.value(elmt, -1), elmt});
|
||||
} else {
|
||||
for (Element *elmt : std::as_const(connected_elements))
|
||||
links.append({elmt->uuid(), m_group_index_map.value(elmt, -1), elmt});
|
||||
}
|
||||
return links;
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Element::pendingLinks
|
||||
@return the uuids of the partners this symbol was saved linked to and
|
||||
is not linked to yet: those on a folio not built yet (lazy folios),
|
||||
until initLink() runs again once it is
|
||||
*/
|
||||
QSet<QUuid> Element::pendingLinks() const
|
||||
{
|
||||
QSet<QUuid> pending;
|
||||
QList<std::pair<QUuid, int>> list;
|
||||
for (const auto &link_info : tmp_uuids_link) {
|
||||
pending.insert(link_info.uuid);
|
||||
list.append({link_info.uuid, link_info.group_index});
|
||||
}
|
||||
for (Element *elmt : connected_elements) {
|
||||
pending.remove(elmt->uuid());
|
||||
}
|
||||
return pending;
|
||||
return list;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1936,41 +1934,27 @@ void Element::initLink(const QList<Element *> &candidates)
|
||||
}
|
||||
}
|
||||
}
|
||||
restoreSavedLinkOrder();
|
||||
QList<QUuid> order;
|
||||
for (const auto &link_info : std::as_const(tmp_uuids_link))
|
||||
order << link_info.uuid;
|
||||
restoreLinkOrder(order);
|
||||
tmp_uuids_link.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Element::waitingLinks
|
||||
@return pendingLinks() in the order this symbol was saved with them,
|
||||
each with its group index (-1 for none)
|
||||
@brief Element::restoreLinkOrder
|
||||
Put connected_elements back in the order @p order gives (the order this
|
||||
symbol saves its partners in). Partners link in the order the folios'
|
||||
items are visited, and for items at the same height that order changes
|
||||
from run to run, so a master's contacts were saved in a different order
|
||||
each time. Linked elements missing from @p order keep their place after it.
|
||||
*/
|
||||
QList<std::pair<QUuid, int>> Element::waitingLinks() const
|
||||
{
|
||||
const QSet<QUuid> pending = pendingLinks();
|
||||
QList<std::pair<QUuid, int>> list;
|
||||
for (const auto &link_info : tmp_uuids_link) {
|
||||
if (pending.contains(link_info.uuid)) {
|
||||
list.append({link_info.uuid, link_info.group_index});
|
||||
}
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Element::restoreSavedLinkOrder
|
||||
Put connected_elements back in the order tmp_uuids_link saved them.
|
||||
Partners link in the order the folios' items are visited, and for
|
||||
items at the same height that order changes from run to run, so a
|
||||
master's contacts were saved in a different order each time.
|
||||
Linked elements missing from the saved list keep their place after it.
|
||||
*/
|
||||
void Element::restoreSavedLinkOrder()
|
||||
void Element::restoreLinkOrder(const QList<QUuid> &order)
|
||||
{
|
||||
QList<Element *> ordered;
|
||||
for (const auto &link_info : std::as_const(tmp_uuids_link)) {
|
||||
for (const QUuid &uuid : order) {
|
||||
for (Element *elmt : std::as_const(connected_elements)) {
|
||||
if (elmt->uuid() == link_info.uuid && !ordered.contains(elmt)) {
|
||||
if (elmt->uuid() == uuid && !ordered.contains(elmt)) {
|
||||
ordered << elmt;
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -226,16 +226,12 @@ class Element : public QetGraphicsItem
|
||||
virtual void unlinkAllElements() {}
|
||||
virtual void unlinkElement(Element *) {}
|
||||
virtual void initLink(QETProject *);
|
||||
QSet<QUuid> pendingLinks() const;
|
||||
/// A partner as the symbol saves it (linksToSave())
|
||||
struct SavedLink {
|
||||
QUuid uuid;
|
||||
int group_index = -1;
|
||||
Element *built = nullptr; ///< nullptr: on a folio not built yet
|
||||
};
|
||||
QList<SavedLink> linksToSave(const QSet<QUuid> &waiting) const;
|
||||
bool keepsSavedLinks() const {return !tmp_uuids_link.isEmpty();}
|
||||
QList<std::pair<QUuid, int>> waitingLinks() const;
|
||||
/// The links this symbol was read with (its file's links_uuids),
|
||||
/// until it is linked: the project database writes its link rows
|
||||
/// from them while it holds none for it (opening)
|
||||
QList<std::pair<QUuid, int>> savedLinkList() const;
|
||||
void dropSavedLinks() {tmp_uuids_link.clear();}
|
||||
void restoreLinkOrder(const QList<QUuid> &order);
|
||||
/**
|
||||
Resolve tmp_uuids_link against a caller-supplied candidate
|
||||
list instead of a project-wide search (bugtracker #607).
|
||||
@@ -359,7 +355,6 @@ class Element : public QetGraphicsItem
|
||||
QList<QPointF> plcTablePositions() const { return m_plc_table_positions; }
|
||||
|
||||
private:
|
||||
void restoreSavedLinkOrder();
|
||||
void storeInformation();
|
||||
projectDataBase *placedStore() const;
|
||||
bool m_must_highlight = false;
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
#include "../ElementsCollection/terminaluuids.h"
|
||||
#include "../qet.h"
|
||||
#include "../qetproject.h"
|
||||
#include "../dataBase/projectdatabase.h"
|
||||
#include "../conductorautonumerotation.h"
|
||||
#include "../diagram.h"
|
||||
#include "../undocommand/addgraphicsobjectcommand.h"
|
||||
@@ -1104,8 +1105,10 @@ QList<Terminal *> relatedPotentialTerminal (
|
||||
//folio is; not while folios are being read or built, which
|
||||
//would build whole report chains
|
||||
Element *report = terminal->parentElement();
|
||||
const QSet<QUuid> waiting = report->pendingLinks();
|
||||
QETProject *project = report->diagram() ? report->diagram()->project() : nullptr;
|
||||
const QSet<QUuid> waiting = project && project->unloadedFolioCount()
|
||||
? project->dataBase()->waitingPartners(report->uuid(), project->symbolsOnUnbuiltFolios())
|
||||
: QSet<QUuid>();
|
||||
if (!waiting.isEmpty() && project
|
||||
&& project->state() != QETProject::ProjectParsingRunning) {
|
||||
project->buildFoliosHoldingSymbols(waiting);
|
||||
|
||||
+23
-26
@@ -793,15 +793,6 @@ void QETProject::buildFolios(const QList<Diagram *> &folios) const
|
||||
if (!signals_were_blocked && !built.isEmpty()) {
|
||||
emit self->m_data_base.dataBaseUpdated();
|
||||
}
|
||||
//A symbol built before still keeping the links it was saved with
|
||||
//(a partner on a folio not built): its initLink() links those just
|
||||
//built, puts its partners back in their saved order and drops what
|
||||
//is done
|
||||
for (Diagram *d : builtFolios())
|
||||
if (!built.contains(d))
|
||||
for (Element *elmt : d->elements())
|
||||
if (elmt->keepsSavedLinks())
|
||||
elmt->initLink(self);
|
||||
//A linked folio report's texts show a wire's properties: the
|
||||
//report's own wire, else its partner's. A report built alone
|
||||
//first, as a partner (buildPartnerSymbols()), linked before it had
|
||||
@@ -1051,9 +1042,17 @@ void QETProject::buildFolioToShow(Diagram *folio)
|
||||
if (chained.contains(d))
|
||||
first << d;
|
||||
buildFolios(first);
|
||||
//The partners its symbols' rows name, not linked: those on a
|
||||
//folio not built are built alone below (the others are built)
|
||||
QSet<QUuid> partners;
|
||||
for (Element *elmt : folio->elements())
|
||||
partners.unite(elmt->pendingLinks());
|
||||
for (Element *elmt : folio->elements()) {
|
||||
QSet<QUuid> linked;
|
||||
for (Element *partner : elmt->linkedElements())
|
||||
linked.insert(partner->uuid());
|
||||
for (const auto &row : m_data_base.linkRowsOf(elmt->uuid()))
|
||||
if (!linked.contains(row.first))
|
||||
partners.insert(row.first);
|
||||
}
|
||||
const QSet<QUuid> folio_uuids = partners.isEmpty()
|
||||
? QSet<QUuid>()
|
||||
: m_data_base.folioUuidsOfElements(partners);
|
||||
@@ -1095,12 +1094,6 @@ void QETProject::buildPartnerSymbols(const QList<Diagram *> &folios, const QSet<
|
||||
for (auto &text : element->dynamicTextItems())
|
||||
text->refreshLabelConnection();
|
||||
}
|
||||
//A built symbol linked to one of them puts its partners back in
|
||||
//their saved order
|
||||
for (Diagram *d : builtFolios())
|
||||
for (Element *element : d->elements())
|
||||
if (element->keepsSavedLinks())
|
||||
element->initLink(this);
|
||||
m_state = state;
|
||||
}
|
||||
|
||||
@@ -2456,10 +2449,11 @@ QDomDocument QETProject::toXml(QHash<int, QByteArray> *kept_folios, QStringList
|
||||
built = unloadedFolioCount();
|
||||
QSet<QUuid> waiting;
|
||||
QSet<Diagram *> chained;
|
||||
const QSet<QUuid> unbuilt = kept_folios ? symbolsOnUnbuiltFolios() : QSet<QUuid>();
|
||||
for (Diagram *diagram : builtFolios()) {
|
||||
if (kept_folios)
|
||||
for (Element *element : diagram->elements())
|
||||
waiting.unite(element->pendingLinks());
|
||||
waiting.unite(m_data_base.waitingPartners(element->uuid(), unbuilt));
|
||||
for (Diagram *other : tableChainFolios(diagram))
|
||||
if (!other->isLoaded()) chained.insert(other);
|
||||
}
|
||||
@@ -2473,7 +2467,6 @@ QDomDocument QETProject::toXml(QHash<int, QByteArray> *kept_folios, QStringList
|
||||
} else {
|
||||
loadFolios();
|
||||
}
|
||||
m_waiting_link_targets = unloadedFolioCount() ? symbolsOnUnbuiltFolios() : QSet<QUuid>();
|
||||
//QET_CHECK_ELEMENT_INFO_STORE=1: say whether the stores of symbol
|
||||
//information, folio title blocks and wire properties agree with every placed symbol, folio and
|
||||
//wire, at each save. A test and debugging aid; it changes nothing.
|
||||
@@ -2661,7 +2654,6 @@ QDomDocument QETProject::toXml(QHash<int, QByteArray> *kept_folios, QStringList
|
||||
<< links.mid(0, 5).join(QStringLiteral(", "));
|
||||
}
|
||||
|
||||
m_waiting_link_targets.clear();
|
||||
return(xml_doc);
|
||||
}
|
||||
|
||||
@@ -3215,15 +3207,12 @@ bool QETProject::usesElement(const ElementsLocation &location) const
|
||||
/**
|
||||
@brief QETProject::folioXml
|
||||
@return the built folio @p folio written as saving writes it: its
|
||||
symbols keep the links to partners on folios not built yet
|
||||
(lazy folios), which Diagram::toXml() alone leaves out
|
||||
symbols' links are their link rows, partners on folios not built yet
|
||||
(lazy folios) included (Element::toXml())
|
||||
*/
|
||||
QDomDocument QETProject::folioXml(Diagram *folio)
|
||||
{
|
||||
m_waiting_link_targets = unloadedFolioCount() ? symbolsOnUnbuiltFolios() : QSet<QUuid>();
|
||||
const QDomDocument xml = folio->toXml();
|
||||
m_waiting_link_targets.clear();
|
||||
return xml;
|
||||
return folio->toXml();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -3519,6 +3508,9 @@ void QETProject::readProjectXml(QDomDocument &xml_project)
|
||||
//complete rebuild of it. The content being loaded is the same for all
|
||||
//of them, so a single rebuild once everything is in place is enough.
|
||||
m_data_base.setUpdateBlocked(true);
|
||||
//The symbols read link from their files' lists until the fill
|
||||
//below writes the link rows (Element::initLink())
|
||||
m_data_base.forgetLinks();
|
||||
|
||||
//Load the project-wide properties
|
||||
readProjectPropertiesXml(xml_project);
|
||||
@@ -3602,6 +3594,11 @@ void QETProject::readProjectXml(QDomDocument &xml_project)
|
||||
m_data_base.setUpdateBlocked(false);
|
||||
m_data_base.updateDB(xml_project);
|
||||
m_data_base.endPrefill(unloadedFolioCount() > 0);
|
||||
//The link rows now hold the partners on folios not built: a
|
||||
//symbol keeps no list of its own (QETZ-DB-FIRST stage 2a-3)
|
||||
for (Diagram *diagram : builtFolios())
|
||||
for (Element *element : diagram->elements())
|
||||
element->dropSavedLinks();
|
||||
//Saving may write the folios not built as they were read, when
|
||||
//building them would write the same: a file written by this
|
||||
//version, whose database rows came from it (toXml()). A .qet does
|
||||
|
||||
@@ -143,9 +143,6 @@ class QETProject : public QObject
|
||||
void buildFoliosHoldingSymbols(const QSet<QUuid> &symbols);
|
||||
void buildFoliosHoldingTables();
|
||||
QSet<QUuid> symbolsOnUnbuiltFolios() const;
|
||||
/// While saving: the symbols on folios not built a built symbol
|
||||
/// may be saved linked to (Element::toXml())
|
||||
QSet<QUuid> waitingLinkTargets() const {return m_waiting_link_targets;}
|
||||
QDomDocument folioXml(Diagram *folio);
|
||||
QSet<Diagram *> tableChainFolios(Diagram *folio) const;
|
||||
void loadFolios() const;
|
||||
@@ -585,7 +582,6 @@ class QETProject : public QObject
|
||||
/// Saving a .qetz may copy the part of a folio not built
|
||||
/// (toXml(), stage D4)
|
||||
bool m_kept_parts_trusted = false;
|
||||
QSet<QUuid> m_waiting_link_targets;
|
||||
/// The entries of the .qetz opened, and the part each folio
|
||||
/// was read from, in order
|
||||
QHash<QString, QByteArray> m_container_files;
|
||||
|
||||
@@ -90,9 +90,11 @@ QDomDocument WiringListExport::projectDocument() const
|
||||
//Conductor::toXml(): the label of the last of the symbol's partners.
|
||||
//A symbol still waiting for a partner not built has its links as
|
||||
//saved, in the database
|
||||
auto linked = [&built, database, &label](const QUuid &uuid) -> std::optional<QString> {
|
||||
const QSet<QUuid> waiting = m_project->unloadedFolioCount() ? m_project->symbolsOnUnbuiltFolios()
|
||||
: QSet<QUuid>();
|
||||
auto linked = [&built, database, &label, &waiting](const QUuid &uuid) -> std::optional<QString> {
|
||||
Element *element = built.value(uuid);
|
||||
if (element && element->pendingLinks().isEmpty()) {
|
||||
if (element && database->waitingPartners(uuid, waiting).isEmpty()) {
|
||||
if (element->linkedElements().isEmpty()) return std::nullopt;
|
||||
return element->linkedElements().last()->actualLabel();
|
||||
}
|
||||
|
||||
@@ -522,6 +522,97 @@ private slots:
|
||||
QCOMPARE(dangling, 0);
|
||||
}
|
||||
|
||||
// Each placed symbol's saved links in the .qetz @p path, as "symbol
|
||||
// partner" lines, sorted: the link table of its database
|
||||
static QStringList qetzLinks(const QString &path)
|
||||
{
|
||||
QList<QetZip::Entry> entries;
|
||||
if (!QetZip::fromBytes(read(path), &entries)) return {QStringLiteral("not a zip")};
|
||||
QByteArray database;
|
||||
for (const QetZip::Entry &entry : std::as_const(entries))
|
||||
if (entry.name == QLatin1String("project.sqlite")) database = entry.data;
|
||||
sqlite3 *db = nullptr;
|
||||
sqlite3_open(":memory:", &db);
|
||||
unsigned char *copy = static_cast<unsigned char *>(sqlite3_malloc64(sqlite3_uint64(database.size())));
|
||||
memcpy(copy, database.constData(), size_t(database.size()));
|
||||
sqlite3_deserialize(db, "main", copy, database.size(), database.size(), SQLITE_DESERIALIZE_FREEONCLOSE);
|
||||
sqlite3_stmt *list = nullptr;
|
||||
QStringList links;
|
||||
sqlite3_prepare_v2(db, "SELECT element_uuid, linked_uuid FROM link", -1, &list, nullptr);
|
||||
while (list && sqlite3_step(list) == SQLITE_ROW)
|
||||
links << QString::fromUtf8(reinterpret_cast<const char *>(sqlite3_column_text(list, 0)))
|
||||
+ QLatin1Char(' ')
|
||||
+ QString::fromUtf8(reinterpret_cast<const char *>(sqlite3_column_text(list, 1)));
|
||||
sqlite3_finalize(list);
|
||||
sqlite3_close(db);
|
||||
links.sort();
|
||||
return links;
|
||||
}
|
||||
|
||||
// Removing a symbol or a folio and undoing it, with lazy folios, gives
|
||||
// back every link, also those to symbols on folios not built, which
|
||||
// only the project database's link rows hold (QETZ-DB-FIRST stage
|
||||
// 2a-3). m_000 as a .qetz saved with every folio built; a contact of
|
||||
// its middle folio, its folio, or its coil's folio (not built: the coil
|
||||
// is built alone) removed, then undone.
|
||||
void undoKeepsLinksToUnbuiltFolios_data()
|
||||
{
|
||||
QTest::addColumn<QString>("edit");
|
||||
QTest::newRow("symbol") << QStringLiteral("qet.deleteElement(f, u)");
|
||||
QTest::newRow("folio") << QStringLiteral("qet.removeFolio(f)");
|
||||
QTest::newRow("coil folio") << QStringLiteral("qet.removeFolio(folio_of[m])");
|
||||
}
|
||||
void undoKeepsLinksToUnbuiltFolios()
|
||||
{
|
||||
#ifndef QET_HAS_SCRIPTING
|
||||
QSKIP("needs --run: this QElectroTech is built without Qt Qml");
|
||||
#endif
|
||||
QFETCH(QString, edit);
|
||||
//Each symbol's folio, from the file: asking QElectroTech would
|
||||
//build the folios
|
||||
QFile source(QStringLiteral(QET_EXAMPLES_DIR "/m_000.qet"));
|
||||
QVERIFY(source.open(QIODevice::ReadOnly));
|
||||
const QStringList folios = QString::fromUtf8(source.readAll()).split(QStringLiteral("<diagram "));
|
||||
QStringList folio_of;
|
||||
static const QRegularExpression placed(QStringLiteral("<element [^>]*\\buuid=\"(\\{[0-9a-f-]+\\})\""));
|
||||
for (int i = 1 ; i < folios.size() ; ++i) {
|
||||
auto it = placed.globalMatch(folios.at(i));
|
||||
while (it.hasNext()) folio_of << QStringLiteral("'%1': %2").arg(it.next().captured(1)).arg(i - 1);
|
||||
}
|
||||
const QString qetz = m_dir.filePath(QStringLiteral("undo%1.qetz").arg(m_run));
|
||||
QVERIFY(!runChecked({QStringLiteral("--resave"),
|
||||
QStringLiteral(QET_EXAMPLES_DIR "/m_000.qet"), qetz}, true).isEmpty());
|
||||
auto save = [&](const QString &name, const QString &body) {
|
||||
const QString saved = m_dir.filePath(name + QString::number(m_run) + QStringLiteral(".qetz"));
|
||||
const QString js = m_dir.filePath(name + QString::number(m_run) + QStringLiteral(".js"));
|
||||
QFile script(js);
|
||||
if (!script.open(QIODevice::WriteOnly)) return std::pair<QString, QString>();
|
||||
script.write(QStringLiteral(
|
||||
"var folio_of = {%3};\n"
|
||||
"var n = qet.folioCount(), f = -1, u = '', m = '';\n"
|
||||
"for (var k = 0 ; k < n && !u ; ++k) {\n"
|
||||
" var g = (Math.floor(n / 2) + k) % n;\n"
|
||||
" var all = qet.elementUuids(g).slice().sort();\n"
|
||||
" for (var i = 0 ; i < all.length && !u ; ++i)\n"
|
||||
" if (qet.elementLinkType(g, all[i]) == 'slave' && qet.linkedElements(g, all[i]).length) { f = g; u = all[i]; m = qet.linkedElements(g, u)[0]; }\n"
|
||||
"}\n"
|
||||
"%1\n"
|
||||
"qet.save('%2');\n").arg(body, saved, folio_of.join(QStringLiteral(", "))).toUtf8());
|
||||
script.close();
|
||||
return std::pair<QString, QString>(saved, runChecked({QStringLiteral("--run"), js, qetz}, false,
|
||||
"QET_CHECK_LINK_ROWS"));
|
||||
};
|
||||
const auto plain = save(QStringLiteral("undo-plain"), QStringLiteral("qet.log('FOUND ' + !!u);"));
|
||||
QVERIFY2(plain.second.contains(QStringLiteral("FOUND true")), qPrintable(plain.second.right(600)));
|
||||
const auto undone = save(QStringLiteral("undo-undone"),
|
||||
QStringLiteral("qet.log('UNDONE ' + (%1) + ' ' + qet.undo());").arg(edit));
|
||||
QVERIFY2(undone.second.contains(QStringLiteral("UNDONE true true")), qPrintable(undone.second.right(600)));
|
||||
QVERIFY2(undone.second.contains(QStringLiteral("link rows: 0 differ")), qPrintable(undone.second.right(600)));
|
||||
const QStringList before = qetzLinks(plain.first);
|
||||
QVERIFY(before.size() > 10);
|
||||
QCOMPARE(qetzLinks(undone.first), before);
|
||||
}
|
||||
|
||||
// The stores filled from the document before the folios are built
|
||||
// hold what building them gives, for a
|
||||
// project saved by this version (every symbol, folio and wire carries
|
||||
|
||||
Reference in New Issue
Block a user