diff --git a/sources/TerminalStrip/terminalstrip.cpp b/sources/TerminalStrip/terminalstrip.cpp
index fe07d959b..337f56766 100644
--- a/sources/TerminalStrip/terminalstrip.cpp
+++ b/sources/TerminalStrip/terminalstrip.cpp
@@ -1011,6 +1011,15 @@ bool TerminalStrip::fromXml(QDomElement &xml_element)
auto xml_layout = xml_element.firstChildElement(QStringLiteral("layout"));
if (!xml_layout.isNull())
{
+ //The folios holding its terminals are built (QET_LAZY_FOLIOS):
+ //a strip is read with the project, before the database could
+ //tell where any symbol is
+ QSet terminals;
+ const QDomNodeList xml_reals = xml_layout.elementsByTagName(RealTerminal::xmlTagName());
+ for (int i = 0 ; i < xml_reals.size() ; ++i)
+ terminals.insert(QUuid(xml_reals.at(i).toElement().attribute(QStringLiteral("element_uuid"))));
+ m_project->buildFoliosHoldingSymbols(terminals);
+
//Get all free elements terminal of the project
const ElementProvider ep(m_project);
auto free_terminals = ep.freeTerminal();
diff --git a/sources/autoNum/autonumschemecommand.cpp b/sources/autoNum/autonumschemecommand.cpp
index 259eb9a3a..7eb96d3dd 100644
--- a/sources/autoNum/autonumschemecommand.cpp
+++ b/sources/autoNum/autonumschemecommand.cpp
@@ -105,7 +105,9 @@ QList AutoNumSchemeCommand::usersOf(const QETProject *project, Kind k
if (!project || title.isEmpty()) {
return users;
}
- for (Diagram *diagram : project->diagrams()) {
+ //The schemes a folio uses are its own data, read on opening:
+ //folios() builds nothing
+ for (Diagram *diagram : project->folios()) {
if (kind == Kind::Conductor) {
if (diagram->conductorsAutonumName() == title) {
users << diagram;
@@ -234,7 +236,8 @@ void AutoNumSchemeCommand::rename(const QString &from, const QString &to)
if (m_project->conductorCurrentAutoNum() == from) {
m_project->setCurrentConductorAutoNum(to);
}
- for (Diagram *d : m_project->diagrams()) {
+ //Folios' own data, read on opening: folios() builds nothing
+ for (Diagram *d : m_project->folios()) {
//The highest folio number given, kept by scheme title
for (auto *hash : {&d->m_cnd_unitfolio_max, &d->m_cnd_tenfolio_max, &d->m_cnd_hundredfolio_max}) {
if (hash->contains(from)) {
diff --git a/sources/autoNum/elementautonumschemecommand.cpp b/sources/autoNum/elementautonumschemecommand.cpp
index cd3684837..4fedb778e 100644
--- a/sources/autoNum/elementautonumschemecommand.cpp
+++ b/sources/autoNum/elementautonumschemecommand.cpp
@@ -369,10 +369,19 @@ ElementAutoNumSchemeCommand::NumberSupport ElementAutoNumSchemeCommand::numberSu
std::optional ElementAutoNumSchemeCommand::numberOf(const NumberSupport &support,
const Element *element)
{
- if (!support.supported || !element) {
+ if (!element) {
+ return std::nullopt;
+ }
+ return numberOf(support, element->sequenceStruct());
+}
+
+/// @return the number the sequential numbers @p seq give, if the scheme knows it
+std::optional ElementAutoNumSchemeCommand::numberOf(const NumberSupport &support,
+ const autonum::sequentialNumbers &seq)
+{
+ if (!support.supported) {
return std::nullopt;
}
- const autonum::sequentialNumbers seq = element->sequenceStruct();
const QStringList &list = support.type == QLatin1String("unit") ? seq.unit
: support.type == QLatin1String("ten") ? seq.ten
: seq.hundred;
@@ -385,14 +394,18 @@ std::optional ElementAutoNumSchemeCommand::numberOf(const NumberSupport &su
}
namespace {
-/// The elements of @p project which follow the scheme @p title and are not slaves or reports
-QVector numberedFollowers(const QETProject *project, const QString &title)
+/// The symbols of @p project which follow the scheme @p title and are not
+/// slaves or reports, built or not (QETProject::symbolNumbering())
+QList numberedFollowers(const QETProject *project, const QString &title)
{
- QVector list;
- const auto followers = project->elementsUsingElementAutoNum(title);
- for (Element *el : followers) {
- if (el->linkType() != Element::Slave && !(el->linkType() & Element::AllReport)) {
- list << el;
+ QList list;
+ const QUuid id = project->elementAutoNumId(title);
+ if (id.isNull()) {
+ return list;
+ }
+ for (const auto &symbol : project->symbolNumbering()) {
+ if (symbol.numbered && !symbol.formula.isEmpty() && symbol.scheme == id) {
+ list << symbol;
}
}
return list;
@@ -411,13 +424,10 @@ QSet ElementAutoNumSchemeCommand::labelsHeldBesides(const QETProject *p
if (!project) {
return labels;
}
- for (Diagram *d : project->diagrams()) {
- for (QGraphicsItem *item : d->items()) {
- if (auto *el = qgraphicsitem_cast(item)) {
- if (el != element) {
- labels << el->elementInformations().value(QETInformation::ELMT_LABEL).toString();
- }
- }
+ //Folios not built yet answer from what building them would give
+ for (const auto &symbol : project->symbolNumbering()) {
+ if (symbol.element != element) {
+ labels << symbol.label;
}
}
labels.remove(QString());
@@ -453,8 +463,8 @@ std::optional ElementAutoNumScheme
CounterConflict conflict;
conflict.counter = counter;
- for (const Element *el : numberedFollowers(project, title)) {
- if (const auto number = numberOf(support, el)) {
+ for (const auto &symbol : numberedFollowers(project, title)) {
+ if (const auto number = numberOf(support, symbol.sequence)) {
if (*number >= counter) {
++conflict.count;
conflict.highest = std::max(conflict.highest, *number);
@@ -486,8 +496,8 @@ QList ElementAutoNumSchemeCommand::gapRan
return ranges;
}
QSet used;
- for (const Element *el : numberedFollowers(project, title)) {
- if (const auto number = numberOf(support, el)) {
+ for (const auto &symbol : numberedFollowers(project, title)) {
+ if (const auto number = numberOf(support, symbol.sequence)) {
used << *number;
}
}
@@ -559,22 +569,19 @@ QList ElementAutoNumSchemeCommand::freeNumbers(const QETProject *project,
QSet used;
int highest = context.itemAt(support.partIndex).value(1).toInt();
- for (const Element *el : numberedFollowers(project, title)) {
- if (el == element) continue;
- if (const auto number = numberOf(support, el)) {
+ for (const auto &symbol : numberedFollowers(project, title)) {
+ if (symbol.element == element) continue;
+ if (const auto number = numberOf(support, symbol.sequence)) {
used << *number;
highest = std::max(highest, *number);
}
}
QSet labels;
- for (Diagram *d : project->diagrams()) {
- for (QGraphicsItem *item : d->items()) {
- if (auto *el = qgraphicsitem_cast(item)) {
- if (el != element) {
- labels << el->elementInformations().value(QETInformation::ELMT_LABEL).toString();
- }
- }
+ //Folios not built yet answer from what building them would give
+ for (const auto &symbol : project->symbolNumbering()) {
+ if (symbol.element != element) {
+ labels << symbol.label;
}
}
labels.remove(QString());
diff --git a/sources/autoNum/elementautonumschemecommand.h b/sources/autoNum/elementautonumschemecommand.h
index c0a69838c..5ed93e0aa 100644
--- a/sources/autoNum/elementautonumschemecommand.h
+++ b/sources/autoNum/elementautonumschemecommand.h
@@ -18,6 +18,7 @@
#ifndef ELEMENTAUTONUMSCHEMECOMMAND_H
#define ELEMENTAUTONUMSCHEMECOMMAND_H
+#include "assignvariables.h"
#include "numerotationcontext.h"
#include "renumberelementscommand.h"
@@ -201,6 +202,8 @@ class ElementAutoNumSchemeCommand : public QUndoCommand
static QSet labelsHeldBesides(const QETProject *project, const Element *element);
static NumberSupport numberSupport(const NumerotationContext &context);
static std::optional numberOf(const NumberSupport &support, const Element *element);
+ static std::optional numberOf(const NumberSupport &support,
+ const autonum::sequentialNumbers &seq);
static QList gapRanges(const QETProject *project, const QString &title);
static QList freeNumbers(const QETProject *project, const QString &title,
const Element *element, int limit = 500);
diff --git a/sources/autoNum/ui/autonumberingmanagementw.cpp b/sources/autoNum/ui/autonumberingmanagementw.cpp
index 93b09fdd2..81225b233 100644
--- a/sources/autoNum/ui/autonumberingmanagementw.cpp
+++ b/sources/autoNum/ui/autonumberingmanagementw.cpp
@@ -175,7 +175,8 @@ void AutoNumberingManagementW::on_m_apply_folios_rb_clicked()
if (ui->m_from_folios_cb->count()<=0) {
ui->m_from_folios_cb->clear();
ui->m_from_folios_cb->addItem("");
- foreach (Diagram *diagram, project_->diagrams()){
+ //Titles and positions only: folios() builds nothing
+ foreach (Diagram *diagram, project_->folios()){
if (diagram->title() != "")
ui->m_from_folios_cb->addItem(
diagram->title(),
@@ -206,16 +207,17 @@ void AutoNumberingManagementW::on_m_from_folios_cb_currentIndexChanged(
if (index > 0) {
ui->m_to_folios_cb->setEnabled(true);
ui->m_to_folios_cb->addItem("");
- for (int i=index;idiagrams().size();i++) {
- if (project_->diagrams().at(i)->title() != "") {
+ const QList folios = project_->folios();
+ for (int i=index;ititle() != "") {
ui->m_to_folios_cb->addItem(
- project_->diagrams().at(i)->title(),
- project_->diagrams().at(i)->folioIndex());
+ folios.at(i)->title(),
+ folios.at(i)->folioIndex());
}
else ui->m_to_folios_cb->addItem(
QString::number(
- project_->diagrams().at(i)->folioIndex()),
- project_->diagrams().at(i)->folioIndex());
+ folios.at(i)->folioIndex()),
+ folios.at(i)->folioIndex());
}
applyEnable(true);
ui->m_selected_folios_le->clear();
diff --git a/sources/autoNum/ui/folioautonumbering.cpp b/sources/autoNum/ui/folioautonumbering.cpp
index ab477f287..e5d1790bc 100644
--- a/sources/autoNum/ui/folioautonumbering.cpp
+++ b/sources/autoNum/ui/folioautonumbering.cpp
@@ -136,7 +136,8 @@ void FolioAutonumberingW::on_m_autonumber_tabs_rb_clicked()
if (ui->m_from_cb->count()<=0){
ui->m_from_cb->clear();
ui->m_from_cb->addItem("");
- foreach (Diagram *diagram, project_->diagrams()){
+ //Titles only: folios() builds nothing
+ foreach (Diagram *diagram, project_->folios()){
ui->m_from_cb->addItem(diagram->title());
}
}
@@ -164,9 +165,9 @@ void FolioAutonumberingW::on_m_from_cb_currentIndexChanged(int)
ui->m_to_cb->clear();
if (index > 0){
ui->m_to_cb->setEnabled(true);
- for (int i=index;idiagrams().size();i++)
- ui->m_to_cb->addItem(project_
- ->diagrams().at(i)->title());
+ const QList folios = project_->folios();
+ for (int i=index;im_to_cb->addItem(folios.at(i)->title());
applyEnable(true);
}
else{
diff --git a/sources/bordertitleblock.cpp b/sources/bordertitleblock.cpp
index 6ab3e0171..f0e6f65c1 100644
--- a/sources/bordertitleblock.cpp
+++ b/sources/bordertitleblock.cpp
@@ -29,6 +29,10 @@
#include "titleblocktemplaterenderer.h"
+#include "diagram.h"
+#include "qetproject.h"
+#include "dataBase/projectdatabase.h"
+
#include
#include
#include
@@ -282,28 +286,98 @@ void BorderTitleBlock::borderFromXml(const QDomElement &xml_elmt) {
@return the properties of the titleblock
\~French les proprietes du cartouches
*/
-TitleBlockProperties BorderTitleBlock::exportTitleBlock()
+TitleBlockProperties BorderTitleBlock::exportTitleBlock() const
+{
+ return current();
+}
+
+/**
+ @brief BorderTitleBlock::localTitleBlock
+ @return the properties of the titleblock this object holds itself,
+ whether or not its folio is placed in a project
+*/
+TitleBlockProperties BorderTitleBlock::localTitleBlock() const
{
TitleBlockProperties ip;
- ip.author = author();
- ip.date = date();
- ip.title = title();
- ip.filename = fileName();
- ip.plant = plant();
- ip.locmach = locmach();
- ip.indexrev = indexrev();
- ip.version = version();
- ip.folio = folio();
+ ip.author = btb_author_;
+ ip.date = btb_date_;
+ ip.title = btb_title_;
+ ip.filename = btb_filename_;
+ ip.plant = btb_plant_;
+ ip.locmach = btb_locmach_;
+ ip.indexrev = btb_indexrev_;
+ ip.version = btb_version_;
+ ip.folio = btb_folio_;
ip.template_name = titleBlockTemplateName();
ip.display_at = m_edge;
- ip.auto_page_num = autoPageNum();
+ ip.auto_page_num = btb_auto_page_num_;
ip.context = additional_fields_;
ip.collection = QET::QetCollection::Embedded;
return(ip);
}
+/**
+ @brief BorderTitleBlock::current
+ @return the properties of the titleblock: for a folio placed in a
+ project the project's store holds them (DB-ACCESSORS-PLAN.md stage
+ 4.4), for any other this object's own copy.
+*/
+TitleBlockProperties BorderTitleBlock::current() const
+{
+ if (const TitleBlockProperties *s = stored())
+ return *s;
+ return localTitleBlock();
+}
+
+/**
+ @brief BorderTitleBlock::stored
+ @return the properties the project's store holds for this titleblock's
+ folio, or nullptr if it is not placed. Read at once: the getters use it
+ field by field, as label formulas ask for the folio and title of every
+ symbol's folio, and copying the whole properties each time costs.
+*/
+const TitleBlockProperties *BorderTitleBlock::stored() const
+{
+ if (projectDataBase *store = placedStore())
+ return store->storedFolioTitleBlock(m_owner_folio->uuid());
+ return nullptr;
+}
+
+/**
+ @brief BorderTitleBlock::setOwnerFolio
+ @param folio : the folio this border and title block belongs to
+*/
+void BorderTitleBlock::setOwnerFolio(Diagram *folio)
+{
+ m_owner_folio = folio;
+}
+
+/**
+ @brief BorderTitleBlock::placedStore
+ @return the project database holding this titleblock's properties, or
+ nullptr if its folio is not placed in a project
+*/
+projectDataBase *BorderTitleBlock::placedStore() const
+{
+ QETProject *project = m_owner_folio ? m_owner_folio->project() : nullptr;
+ projectDataBase *store = project ? project->dataBase() : nullptr;
+ return store && store->placedFolio(m_owner_folio->uuid()) == m_owner_folio
+ ? store : nullptr;
+}
+
+/**
+ @brief BorderTitleBlock::storeTitleBlock
+ Write this titleblock's properties to its project's store, once its
+ folio is placed: every change of them comes through here.
+*/
+void BorderTitleBlock::storeTitleBlock()
+{
+ if (projectDataBase *store = placedStore())
+ store->storeFolioTitleBlock(m_owner_folio->uuid(), localTitleBlock());
+}
+
/**
@brief BorderTitleBlock::importTitleBlock
@param ip the new properties of titleblock
@@ -333,6 +407,7 @@ void BorderTitleBlock::importTitleBlock(const TitleBlockProperties &ip)
// through setFolioData(),
// which in turn calls updateDiagramContextForTitleBlock().
emit(needTitleBlockTemplate(ip.template_name));
+ storeTitleBlock();
emit informationChanged();
}
@@ -391,6 +466,7 @@ void BorderTitleBlock::setTitleBlockTemplate(
const TitleBlockTemplate *titleblock_template) {
m_titleblock_template_renderer -> setTitleBlockTemplate(
titleblock_template);
+ storeTitleBlock();
}
/**
@@ -889,9 +965,21 @@ DiagramPosition BorderTitleBlock::convertPosition(const QPointF &pos)
void BorderTitleBlock::setFolio(const QString &folio)
{
btb_folio_ = folio;
+ storeTitleBlock();
emit (titleBlockFolioChanged(folio));
}
+/**
+ @brief BorderTitleBlock::setAutoPageNum
+ @param title : the title of the folio numbering this folio follows,
+ nothing else changes
+*/
+void BorderTitleBlock::setAutoPageNum(const QString &title)
+{
+ btb_auto_page_num_ = title;
+ storeTitleBlock();
+}
+
/**
@brief BorderTitleBlock::updateDiagramContextForTitleBlock
Update the informations given to the titleblock template
@@ -915,9 +1003,10 @@ void BorderTitleBlock::updateDiagramContextForTitleBlock(
// So an empty page-level value is skipped only when a real project-level
// one is already there to show through; otherwise it still goes in
// empty, which is what makes the placeholder resolve to nothing.
+ const TitleBlockProperties tb = current();
DiagramContext context = initial_context;
- foreach (QString key, additional_fields_.keys()) {
- const QVariant value = additional_fields_[key];
+ foreach (QString key, tb.context.keys()) {
+ const QVariant value = tb.context[key];
if (!value.toString().isEmpty() || !context.contains(key))
context.addValue(key, value);
}
@@ -925,20 +1014,20 @@ void BorderTitleBlock::updateDiagramContextForTitleBlock(
// ... overridden by the historical and/or dynamically generated fields
QLocale var;
var.dateFormat(QLocale::ShortFormat);
- context.addValue("author", btb_author_);
+ context.addValue("author", tb.author);
context.addValue(
"date",
- QLocale::system().toString(btb_date_, QLocale::ShortFormat));
- context.addValue("title", btb_title_);
- context.addValue("filename", btb_filename_);
- context.addValue("plant", btb_plant_);
- context.addValue("locmach", btb_locmach_);
- context.addValue("indexrev", btb_indexrev_);
- context.addValue("version", btb_version_);
+ QLocale::system().toString(tb.date, QLocale::ShortFormat));
+ context.addValue("title", tb.title);
+ context.addValue("filename", tb.filename);
+ context.addValue("plant", tb.plant);
+ context.addValue("locmach", tb.locmach);
+ context.addValue("indexrev", tb.indexrev);
+ context.addValue("version", tb.version);
context.addValue("folio", btb_final_folio_);
context.addValue("folio-id", folio_index_);
context.addValue("folio-total", folio_total_);
- context.addValue("auto_page_num", btb_auto_page_num_);
+ context.addValue("auto_page_num", tb.auto_page_num);
context.addValue("previous-folio-num", m_previous_folio_num);
context.addValue("next-folio-num", m_next_folio_num);
@@ -1041,11 +1130,12 @@ void BorderTitleBlock::setFolioData(
// regenerate the content of the folio field
// regenere le contenu du champ folio
- btb_final_folio_ = btb_folio_;
+ btb_final_folio_ = folio();
if (btb_final_folio_.contains("%autonum")){
btb_final_folio_.replace("%autonum", autonum);
btb_folio_ = btb_final_folio_;
+ storeTitleBlock();
}
btb_final_folio_.replace("%id", QString::number(folio_index_));
btb_final_folio_.replace("%total", QString::number(folio_total_));
diff --git a/sources/bordertitleblock.h b/sources/bordertitleblock.h
index 8a57d3d9c..adc422752 100644
--- a/sources/bordertitleblock.h
+++ b/sources/bordertitleblock.h
@@ -29,6 +29,8 @@ class QPainter;
class DiagramPosition;
class TitleBlockTemplate;
class TitleBlockTemplateRenderer;
+class Diagram;
+class projectDataBase;
/**
@brief The BorderTitleBlock class
This class represents the border and the titleblock which frame a
@@ -107,34 +109,36 @@ class BorderTitleBlock : public QObject
QRectF outsideBorderRect() const;
QRectF insideBorderRect() const;
- // methods to get title block basic data
+ // methods to get title block basic data. A folio placed in a
+ // project reads them from the project's store
+ // (DB-ACCESSORS-PLAN.md stage 4.4), any other from its own copy.
/// @return the value of the title block "Author" field
- QString author() const { return(btb_author_); }
+ QString author() const { const TitleBlockProperties *s = stored(); return(s ? s->author : btb_author_); }
/// @return the value of the title block "Date" field
- QDate date() const { return(btb_date_); }
+ QDate date() const { const TitleBlockProperties *s = stored(); return(s ? s->date : btb_date_); }
/// @return the value of the title block "Title" field
- QString title() const { return(btb_title_); }
+ QString title() const { const TitleBlockProperties *s = stored(); return(s ? s->title : btb_title_); }
/// @return the value of the title block "Folio" field
- QString folio() const { return(btb_folio_); }
+ QString folio() const { const TitleBlockProperties *s = stored(); return(s ? s->folio : btb_folio_); }
/// @return the value of the title block "Folio" field as displayed
QString finalfolio() const { return(btb_final_folio_); }
/// @return the value of the title block "Plant" field
- QString plant() const { return(btb_plant_); }
+ QString plant() const { const TitleBlockProperties *s = stored(); return(s ? s->plant : btb_plant_); }
/// @return the value of the title block "Locmach" field
- QString locmach() const { return(btb_locmach_); }
+ QString locmach() const { const TitleBlockProperties *s = stored(); return(s ? s->locmach : btb_locmach_); }
/// @return the value of the revision index block "Folio" field
- QString indexrev() const { return(btb_indexrev_); }
+ QString indexrev() const { const TitleBlockProperties *s = stored(); return(s ? s->indexrev : btb_indexrev_); }
/// @return the value of the title block "File" field
- QString fileName() const { return(btb_filename_); }
+ QString fileName() const { const TitleBlockProperties *s = stored(); return(s ? s->filename : btb_filename_); }
/// @return the value of the title block Additional Fields
- QString version() const { return(btb_version_); }
+ QString version() const { const TitleBlockProperties *s = stored(); return(s ? s->version : btb_version_); }
/// @return the value of the title block Additional Fields
DiagramContext additionalFields() const {
- return (additional_fields_); }
+ const TitleBlockProperties *s = stored(); return(s ? s->context : additional_fields_); }
/// @return the value of the title block
- QString autoPageNum() const { return(btb_auto_page_num_); }
+ QString autoPageNum() const { const TitleBlockProperties *s = stored(); return(s ? s->auto_page_num : btb_auto_page_num_); }
/// The title of the folio numbering this folio follows, nothing else changes
- void setAutoPageNum(const QString &title) { btb_auto_page_num_ = title; }
+ void setAutoPageNum(const QString &title);
/// @return the value of the total number of folios
int folioTotal() const { return(folio_total_);}
@@ -175,7 +179,9 @@ class BorderTitleBlock : public QObject
void borderToXml(QDomElement &);
void borderFromXml(const QDomElement &);
- TitleBlockProperties exportTitleBlock();
+ TitleBlockProperties exportTitleBlock() const;
+ TitleBlockProperties localTitleBlock() const;
+ void setOwnerFolio(Diagram *folio);
void importTitleBlock(const TitleBlockProperties &);
BorderProperties exportBorder();
void importBorder(const BorderProperties &);
@@ -289,5 +295,10 @@ class BorderTitleBlock : public QObject
bool display_rows_ = false;
bool display_border_ = false;
TitleBlockTemplateRenderer *m_titleblock_template_renderer;
+ Diagram *m_owner_folio = nullptr;
+ TitleBlockProperties current() const;
+ const TitleBlockProperties *stored() const;
+ projectDataBase *placedStore() const;
+ void storeTitleBlock();
};
#endif
diff --git a/sources/cable/cablemanager.cpp b/sources/cable/cablemanager.cpp
index e3728d5af..6bb7854ea 100644
--- a/sources/cable/cablemanager.cpp
+++ b/sources/cable/cablemanager.cpp
@@ -335,6 +335,10 @@ void CableManager::refreshLabels(QETProject *project)
QString label;
};
+ //Without cables, the folios not built hold nothing this pass
+ //changes but stale references (QETProject::cableLabelFolios())
+ const QList folios = project->cableLabelFolios();
+
QHash bindings;
for (Cable *cable : project->cables())
{
@@ -361,7 +365,7 @@ void CableManager::refreshLabels(QETProject *project)
//the core or the link does.
QHash conductors_by_uuid;
QHash> endings_on_report;
- for (Diagram *diagram : project->diagrams())
+ for (Diagram *diagram : folios)
{
if (!diagram) continue;
for (Conductor *conductor : diagram->conductors())
@@ -440,7 +444,7 @@ void CableManager::refreshLabels(QETProject *project)
}
}
- for (Diagram *diagram : project->diagrams())
+ for (Diagram *diagram : folios)
{
if (!diagram) continue;
for (Conductor *conductor : diagram->conductors())
diff --git a/sources/cli_export.cpp b/sources/cli_export.cpp
index b8c2278ea..80e7f1aa4 100644
--- a/sources/cli_export.cpp
+++ b/sources/cli_export.cpp
@@ -869,8 +869,8 @@ int exportLinks(QETProject &project, const QString &output)
/// diff can reveal markup QET silently normalises (tolerated-but-invalid XML).
int resaveProject(QETProject &project, const QString &output)
{
- const QDomDocument doc = project.toXml();
if (QETProject::isContainerPath(output)) {
+ const QDomDocument doc = project.toXml();
QString error;
if (!QETProject::writeContainer(doc, output, &error)) {
err << "Cannot write '" << output << "': " << error << "\n";
@@ -879,13 +879,17 @@ int resaveProject(QETProject &project, const QString &output)
out << "Re-saved project -> " << output << "\n";
return 0;
}
+ //Folios not built (QET_LAZY_FOLIOS) written as read when that is
+ //what building them would write (QETProject::toXml())
+ QHash kept_folios;
+ const QDomDocument doc = project.toXml(&kept_folios);
QFile file(output);
if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
err << "Cannot open '" << output << "' for writing.\n";
return 1;
}
QTextStream fout(&file);
- fout << doc.toString(4);
+ fout << QETProject::spliceKeptFolios(doc.toString(4), kept_folios);
file.close();
out << "Re-saved project -> " << output << "\n";
return 0;
@@ -963,21 +967,25 @@ int setTitleBlock(QETProject &project, const QString &output,
++folios;
}
- const QDomDocument doc = project.toXml();
if (QETProject::isContainerPath(output)) {
+ const QDomDocument doc = project.toXml();
QString error;
if (!QETProject::writeContainer(doc, output, &error)) {
err << "Cannot write '" << output << "': " << error << "\n";
return 1;
}
} else {
+ //Folios not built (QET_LAZY_FOLIOS) written as read when that is
+ //what building them would write (QETProject::toXml())
+ QHash kept_folios;
+ const QDomDocument doc = project.toXml(&kept_folios);
QFile file(output);
if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
err << "Cannot open '" << output << "' for writing.\n";
return 1;
}
QTextStream fout(&file);
- fout << doc.toString(4);
+ fout << QETProject::spliceKeptFolios(doc.toString(4), kept_folios);
file.close();
}
out << "Stamped " << fields.size() << " field(s) on "
diff --git a/sources/conductorproperties.cpp b/sources/conductorproperties.cpp
index f65a2a340..d312941f3 100644
--- a/sources/conductorproperties.cpp
+++ b/sources/conductorproperties.cpp
@@ -217,10 +217,22 @@ void SingleLineProperties::drawPen(QPainter *painter,
*/
void SingleLineProperties::toXml(QDomElement &e) const
{
- e.setAttribute("ground", hasGround ? "true" : "false");
- e.setAttribute("neutral", hasNeutral ? "true" : "false");
- e.setAttribute("phase", phases);
- if (isPen()) e.setAttribute("pen", "true");
+ for (const auto &attribute : attributes())
+ e.setAttribute(attribute.first, attribute.second);
+}
+
+/**
+ @brief SingleLineProperties::attributes
+ @return the attributes toXml() writes, in its order
+*/
+QList> SingleLineProperties::attributes() const
+{
+ QList> a;
+ a << qMakePair(QStringLiteral("ground"), QString::fromLatin1(hasGround ? "true" : "false"));
+ a << qMakePair(QStringLiteral("neutral"), QString::fromLatin1(hasNeutral ? "true" : "false"));
+ a << qMakePair(QStringLiteral("phase"), QString::number(phases));
+ if (isPen()) a << qMakePair(QStringLiteral("pen"), QStringLiteral("true"));
+ return a;
}
/**
@@ -268,50 +280,66 @@ ConductorProperties::~ConductorProperties()
*/
void ConductorProperties::toXml(QDomElement &e) const
{
- e.setAttribute("type", typeToString(type));
+ for (const auto &attribute : attributes())
+ e.setAttribute(attribute.first, attribute.second);
+}
+
+/**
+ @brief ConductorProperties::attributes
+ @return the attributes toXml() writes, in the order it writes them: the
+ project database stores a placed wire's properties as these.
+*/
+QList> ConductorProperties::attributes() const
+{
+ QList> a;
+ auto add = [&a](const char *name, const QString &value) {
+ a << qMakePair(QString::fromLatin1(name), value);
+ };
+ add("type", typeToString(type));
if (color != QColor(Qt::black))
- e.setAttribute("color", color.name());
+ add("color", color.name());
- e.setAttribute("bicolor", m_bicolor? "true" : "false");
- e.setAttribute("color2", m_color_2.name());
- e.setAttribute("dash-size", QString::number(m_dash_size));
+ add("bicolor", m_bicolor? "true" : "false");
+ add("color2", m_color_2.name());
+ add("dash-size", QString::number(m_dash_size));
if (type == Single)
- singleLineProperties.toXml(e);
+ a << singleLineProperties.attributes();
- e.setAttribute("num", text);
- e.setAttribute("text_color", text_color.name());
- e.setAttribute("formula", m_formula);
- e.setAttribute("cable", m_cable);
+ add("num", text);
+ add("text_color", text_color.name());
+ add("formula", m_formula);
+ add("cable", m_cable);
if (!m_cable_uuid.isNull())
{
//Only written when there is a cable: a conductor which is no
//part of one keeps whatever cable text it had and no reference
//at all, so a hand written value is never mistaken for a
//generated one.
- e.setAttribute("cable_uuid", m_cable_uuid.toString());
- e.setAttribute("cable_slot", QString::number(m_cable_slot));
+ add("cable_uuid", m_cable_uuid.toString());
+ add("cable_slot", QString::number(m_cable_slot));
}
- e.setAttribute("bus", m_bus);
- e.setAttribute("function", m_function);
- e.setAttribute("tension_protocol", m_tension_protocol);
- e.setAttribute("conductor_color", m_wire_color);
- e.setAttribute("conductor_section", m_wire_section);
- e.setAttribute("numsize", QString::number(text_size));
- e.setAttribute("condsize", QString::number(cond_size));
- e.setAttribute("displaytext", m_show_text);
- e.setAttribute("onetextperfolio", m_one_text_per_folio);
- e.setAttribute("vertirotatetext", QString::number(verti_rotate_text));
- e.setAttribute("horizrotatetext", QString::number(horiz_rotate_text));
+ add("bus", m_bus);
+ add("function", m_function);
+ add("tension_protocol", m_tension_protocol);
+ add("conductor_color", m_wire_color);
+ add("conductor_section", m_wire_section);
+ add("numsize", QString::number(text_size));
+ add("condsize", QString::number(cond_size));
+ add("displaytext", QString::number(m_show_text));
+ add("onetextperfolio", QString::number(m_one_text_per_folio));
+ add("vertirotatetext", QString::number(verti_rotate_text));
+ add("horizrotatetext", QString::number(horiz_rotate_text));
QMetaEnum me = QMetaEnum::fromType();
- e.setAttribute("horizontal-alignment", me.valueToKey(m_horizontal_alignment));
- e.setAttribute("vertical-alignment", me.valueToKey(m_vertical_alignment));
+ add("horizontal-alignment", QString::fromLatin1(me.valueToKey(m_horizontal_alignment)));
+ add("vertical-alignment", QString::fromLatin1(me.valueToKey(m_vertical_alignment)));
QString conductor_style = writeStyle();
if (!conductor_style.isEmpty())
- e.setAttribute("style", conductor_style);
+ add("style", conductor_style);
+ return a;
}
diff --git a/sources/conductorproperties.h b/sources/conductorproperties.h
index 6bb0fd004..c9cb0df84 100644
--- a/sources/conductorproperties.h
+++ b/sources/conductorproperties.h
@@ -39,6 +39,7 @@ class SingleLineProperties {
bool isPen() const;
void draw(QPainter *, QET::ConductorSegmentType, const QRectF &);
void toXml(QDomElement &) const;
+ QList> attributes() const;
void fromXml(QDomElement &);
void toSettings(QSettings &, const QString & = QString()) const;
void fromSettings(QSettings &, const QString & = QString());
@@ -130,6 +131,7 @@ class ConductorProperties
// methods
void toXml(QDomElement &) const;
+ QList> attributes() const;
void fromXml(QDomElement &);
void toSettings(QSettings &, const QString & = QString()) const;
void fromSettings(QSettings &, const QString & = QString());
diff --git a/sources/dataBase/projectdatabase.cpp b/sources/dataBase/projectdatabase.cpp
index c348454db..d71bedfe4 100644
--- a/sources/dataBase/projectdatabase.cpp
+++ b/sources/dataBase/projectdatabase.cpp
@@ -16,6 +16,7 @@
along with QElectroTech. If not, see .
*/
#include "projectdatabase.h"
+#include "../qetversion.h"
#include "sqlreadonly.h"
@@ -112,10 +113,12 @@ void projectDataBase::updateDB()
flushDrawingItems();
flushLinks();
flushElementPositions();
+ flushConductorProperties();
emit dataBaseUpdated();
return;
}
+ keepUnbuiltFolioRows();
populateDiagramTable();
populateDiagramInfoTable();
populateElementTable();
@@ -123,11 +126,93 @@ void projectDataBase::updateDB()
populateConductorTable();
populateLinkTable();
populateDrawingItemTables();
+ dropKeptRows();
+ flushConductorProperties();
m_content_changed = false;
emit dataBaseUpdated();
}
+/**
+ @brief projectDataBase::keepUnbuiltFolioRows
+ Before the tables are rebuilt from the folios: keep the element,
+ element_info, terminal, conductor and link rows of the folios not built
+ yet (QET_LAZY_FOLIOS). They came from the document, nothing of those
+ folios can have changed since, and the rebuild puts them back where
+ each folio comes (restoreKeptRows()). Nothing to do when every folio
+ is built.
+*/
+void projectDataBase::keepUnbuiltFolioRows()
+{
+ m_kept_folios.clear();
+ if (!m_project->unloadedFolioCount()) {
+ return;
+ }
+ //Rows filled from the folios have none for a folio not built: it
+ //is built (diagrams()) and the tables come from it
+ if (!m_rows_from_document) {
+ m_project->diagrams();
+ return;
+ }
+ for (Diagram *folio : m_project->folios())
+ if (!folio->isLoaded()) m_kept_folios.insert(folio);
+ QSqlQuery query(m_data_base);
+ query.exec(QStringLiteral("CREATE TEMP TABLE kept_folio (uuid VARCHAR(50) PRIMARY KEY)"));
+ query.prepare(QStringLiteral("INSERT INTO kept_folio (uuid) VALUES (:uuid)"));
+ for (Diagram *folio : std::as_const(m_kept_folios)) {
+ query.bindValue(QStringLiteral(":uuid"), folio->uuid().toString());
+ query.exec();
+ }
+ for (const QString &sql : {
+ QStringLiteral("CREATE TEMP TABLE kept_element AS SELECT * FROM element "
+ "WHERE diagram_uuid IN (SELECT uuid FROM kept_folio) ORDER BY rowid"),
+ QStringLiteral("CREATE TEMP TABLE kept_element_info AS SELECT * FROM element_info "
+ "WHERE element_uuid IN (SELECT uuid FROM kept_element) ORDER BY rowid"),
+ QStringLiteral("CREATE TEMP TABLE kept_terminal AS SELECT * FROM terminal "
+ "WHERE element_uuid IN (SELECT uuid FROM kept_element) ORDER BY rowid"),
+ QStringLiteral("CREATE TEMP TABLE kept_conductor AS SELECT * FROM conductor "
+ "WHERE diagram_uuid IN (SELECT uuid FROM kept_folio) ORDER BY rowid"),
+ QStringLiteral("CREATE TEMP TABLE kept_link AS SELECT * FROM link "
+ "WHERE element_uuid IN (SELECT uuid FROM kept_element) ORDER BY rowid")}) {
+ if (!query.exec(sql)) {
+ qDebug() << "projectDataBase::keepUnbuiltFolioRows error : " << query.lastError();
+ }
+ }
+}
+
+/**
+ @brief projectDataBase::restoreKeptRows
+ Put back into @p table the rows kept for @p folio, not built
+ (keepUnbuiltFolioRows()), in the order they had. @p where selects them
+ in the kept table, with :folio for the folio's uuid.
+*/
+void projectDataBase::restoreKeptRows(const QString &table, Diagram *folio, const QString &where)
+{
+ QSqlQuery query(m_data_base);
+ query.prepare(QStringLiteral("INSERT INTO %1 SELECT * FROM kept_%1 WHERE %2 ORDER BY rowid")
+ .arg(table, where));
+ query.bindValue(QStringLiteral(":folio"), folio->uuid().toString());
+ if (!query.exec()) {
+ qDebug() << "projectDataBase::restoreKeptRows error : " << table << query.lastError();
+ }
+}
+
+/**
+ @brief projectDataBase::dropKeptRows
+ The rows kept by keepUnbuiltFolioRows() are back: drop their copies
+*/
+void projectDataBase::dropKeptRows()
+{
+ if (m_kept_folios.isEmpty()) {
+ return;
+ }
+ QSqlQuery query(m_data_base);
+ for (const char *table : {"kept_folio", "kept_element", "kept_element_info",
+ "kept_terminal", "kept_conductor", "kept_link"})
+ query.exec(QStringLiteral("DROP TABLE %1").arg(QLatin1String(table)));
+ m_kept_folios.clear();
+}
+
/**
@brief projectDataBase::updateDB
updateDB() for a project just read from @p document.
@@ -157,7 +242,9 @@ void projectDataBase::updateDB(const QDomDocument &document)
why = QStringLiteral("QET_DATABASE_FROM_FOLIOS is set");
} else if (populateFromDocument(document, &why)) {
qInfo() << "Project database filled from the document";
+ m_rows_from_document = true;
populateDrawingItemTables();
+ flushConductorProperties();
m_content_changed = false;
emit dataBaseUpdated();
return;
@@ -289,7 +376,9 @@ bool projectDataBase::populateFromDocument(const QDomDocument &document, QString
//The folios, in the order the project read them (readDiagramsXml()),
//each with its saved uuid -- the one the built folio has.
const QDomNodeList diagram_nodes = document.elementsByTagName(QStringLiteral("diagram"));
- const QList diagrams = m_project->diagrams();
+ //Built or not (QET_LAZY_FOLIOS): only their uuids are read, and the
+ //conductors of those built
+ const QList diagrams = m_project->folios();
if (diagram_nodes.size() != diagrams.size()) {
return refuse(QStringLiteral("the folios are not the document's"));
}
@@ -302,6 +391,7 @@ bool projectDataBase::populateFromDocument(const QDomDocument &document, QString
QList diagram_infos;
QList diagram_dates;
QList diagram_uuids;
+ QSet unjoined;
QList elements;
QList conductors;
QSet element_uuids;
@@ -522,6 +612,9 @@ bool projectDataBase::populateFromDocument(const QDomDocument &document, QString
const QUuid terminal(conductor_xml.attribute(QStringLiteral("terminal") + index));
const int e = on_this_folio.value(owner, -1);
if (e < 0) {
+ //Building the folio leaves this wire out and
+ //says so (Diagram::wiresNotReconnected())
+ unjoined.insert(diagram_uuid);
found = false;
break;
}
@@ -716,9 +809,68 @@ bool projectDataBase::populateFromDocument(const QDomDocument &document, QString
}
}
}
+ m_folios_with_unjoined_wires = unjoined;
return true;
}
+/**
+ @brief projectDataBase::foliosWithUnjoinedWires
+ @return the uuids of the folios whose document has a wire that building
+ them will leave out, an end of it not being found: those whose
+ Diagram::wiresNotReconnected() will not be empty. Known when the
+ database was last filled from the document, else empty.
+*/
+QSet projectDataBase::foliosWithUnjoinedWires() const
+{
+ return m_folios_with_unjoined_wires;
+}
+
+/**
+ @brief projectDataBase::elementTypesOnFolios
+ @return the kind (ElementData::typeToString()) of every symbol on the
+ folios @p folios, by uuid, read from the element table as it is: for
+ folios not built yet (QET_LAZY_FOLIOS) its rows came from the document
+ and nothing can have changed them. A symbol whose definition was not
+ found has no row.
+*/
+QHash projectDataBase::elementTypesOnFolios(const QSet &folios) const
+{
+ QHash types;
+ QSqlQuery query(m_data_base);
+ query.prepare(QStringLiteral("SELECT uuid, type FROM element WHERE diagram_uuid = :folio"));
+ for (const QUuid &folio : folios) {
+ query.bindValue(QStringLiteral(":folio"), folio.toString());
+ if (!query.exec()) {
+ continue;
+ }
+ while (query.next()) {
+ types.insert(QUuid(query.value(0).toString()), query.value(1).toString());
+ }
+ }
+ return types;
+}
+
+/**
+ @brief projectDataBase::folioUuidsOfElements
+ @return the uuids of the folios the symbols @p elements are on, read
+ from the element table as it is, without bringing it up to date: for a
+ symbol on a folio not built yet (QET_LAZY_FOLIOS) its row came from the
+ document and nothing can have changed it.
+*/
+QSet projectDataBase::folioUuidsOfElements(const QSet &elements) const
+{
+ QSet folios;
+ QSqlQuery query(m_data_base);
+ query.prepare(QStringLiteral("SELECT diagram_uuid FROM element WHERE uuid = :uuid"));
+ for (const QUuid &uuid : elements) {
+ query.bindValue(QStringLiteral(":uuid"), uuid.toString());
+ if (query.exec() && query.next()) {
+ folios.insert(QUuid(query.value(0).toString()));
+ }
+ }
+ return folios;
+}
+
/**
@brief projectDataBase::setUpdateBlocked
@param blocked : whether updateDB() should skip the full rebuild
@@ -728,6 +880,26 @@ void projectDataBase::setUpdateBlocked(bool blocked)
m_update_blocked = blocked;
}
+/**
+ @brief projectDataBase::setBuildingFolio
+ @see the declaration. The stores still take what building writes.
+*/
+void projectDataBase::setBuildingFolio(bool building)
+{
+ //A folio built while another is (a caller of diagrams() reached
+ //from building it) nests: only the outermost restores.
+ if (building) {
+ if (m_building_folios++ == 0) {
+ m_changed_before_folio = m_content_changed;
+ m_blocked_before_folio = m_update_blocked;
+ m_update_blocked = true;
+ }
+ } else if (m_building_folios > 0 && --m_building_folios == 0) {
+ m_update_blocked = m_blocked_before_folio;
+ m_content_changed = m_changed_before_folio;
+ }
+}
+
/**
@brief projectDataBase::project
@return the project of this database
@@ -815,11 +987,23 @@ QSqlQuery projectDataBase::newQuery(const QString &query, QString *error) {
//Drawing-item rows are rewritten lazily, see drawingItemChanged().
//Every read from outside comes through here, so this is the one
//place the queue has to be emptied for a reader to see current rows.
- //The same goes for link rows and elements' folio cells, see
- //linksChanged() and elementMoved().
+ //The same goes for link rows, elements' folio cells and wires'
+ //properties, see linksChanged(), elementMoved() and
+ //storeConductorProperties().
+ //The drawing tables hold what only built items know (a text's
+ //laid-out size, a picture's pixels): with folios not built yet
+ //(QET_LAZY_FOLIOS) a read of them builds those first. Every other
+ //table was filled from the document.
+ static const QRegularExpression drawing_tables(
+ QStringLiteral("\\b(shape|independent_text|image)\\b"),
+ QRegularExpression::CaseInsensitiveOption);
+ if (m_project && m_project->unloadedFolioCount() && drawing_tables.match(query).hasMatch()) {
+ m_project->loadFolios();
+ }
flushDrawingItems();
flushLinks();
flushElementPositions();
+ flushConductorProperties();
// First gate: which kind of statement is acceptable here at all. A
// textual check is the right tool for that and the wrong tool for
@@ -907,21 +1091,13 @@ void projectDataBase::addElement(Element *element)
return;
}
- bindElementValues(m_insert_elements_query, element, element->diagram());
- if (!m_insert_elements_query.exec()) {
- qDebug() << "projectDataBase::addElement insert element error : " << m_insert_elements_query.lastError();
- }
+ placeElement(element);
connect(element, &Element::linkedElementChanged,
this, &projectDataBase::linksChanged, Qt::UniqueConnection);
connect(element, &QGraphicsObject::xChanged, this, &projectDataBase::elementMoved, Qt::UniqueConnection);
connect(element, &QGraphicsObject::yChanged, this, &projectDataBase::elementMoved, Qt::UniqueConnection);
-
- bindElementInfoValues(m_insert_element_info_query, element);
- if (!m_insert_element_info_query.exec()) {
- qDebug() << "projectDataBase::addElement insert element info error : " << m_insert_element_info_query.lastError();
- } else {
- emit dataBaseUpdated();
- }
+ writeElementRows(element);
+ emit dataBaseUpdated();
}
/**
@@ -931,32 +1107,14 @@ void projectDataBase::addElement(Element *element)
void projectDataBase::removeElement(Element *element)
{
m_content_changed = true;
- bool changed = false;
-
- m_remove_element_query.bindValue(":uuid", element->uuid().toString());
- if (m_remove_element_query.exec()) {
- changed = true;
- } else {
- qDebug() << "projectDataBase::removeElement remove error : " << m_remove_element_query.lastError();
- }
-
- m_remove_element_info_query.bindValue(":uuid", element->uuid().toString());
- if (m_remove_element_info_query.exec()) {
- changed = true;
- } else {
- qDebug() << "projectDataBase::removeElement remove element_info error : " << m_remove_element_info_query.lastError();
- }
-
- QSqlQuery remove_links(m_data_base);
- remove_links.prepare(QStringLiteral("DELETE FROM link WHERE element_uuid = :uuid OR linked_uuid = :uuid"));
- remove_links.bindValue(QStringLiteral(":uuid"), element->uuid().toString());
- if (!remove_links.exec()) {
- qDebug() << "projectDataBase::removeElement remove link error : " << remove_links.lastError();
- }
-
- if (changed) {
- emit dataBaseUpdated();
+ const QUuid uuid = element->uuid();
+ removeElementRows(uuid);
+ unplaceElement(element, uuid);
+ //Another symbol still holds the uuid (F100): the rows are its own.
+ if (Element *other = otherHolder(uuid, element)) {
+ writeElementRows(other);
}
+ emit dataBaseUpdated();
}
/**
@@ -998,6 +1156,7 @@ void projectDataBase::elementInfoChanged(QList elements)
void projectDataBase::addDiagram(Diagram *diagram)
{
m_content_changed = true;
+ placeFolio(diagram);
m_insert_diagram_query.bindValue(":uuid", diagram->uuid().toString());
m_insert_diagram_query.bindValue(":pos", m_project->folioIndex(diagram)+1);
if(!m_insert_diagram_query.exec()) {
@@ -1016,6 +1175,24 @@ void projectDataBase::addDiagram(Diagram *diagram)
for (QGraphicsItem *item : items) {
addDrawingItem(item);
}
+ //So do its symbols, their wires and the links to and from them
+ //(F107).
+ const QList elements = diagram->elements();
+ const bool own_transaction = !elements.isEmpty() && m_data_base.transaction();
+ //One signal for the folio, not one per symbol and wire: each makes
+ //every folio table query the database again.
+ const bool blocked = blockSignals(true);
+ for (Element *element : elements) {
+ addElement(element);
+ queueLinks(element);
+ for (Element *linked : element->linkedElements())
+ queueLinks(linked);
+ }
+ for (Conductor *conductor : diagram->conductors())
+ addConductor(conductor);
+ blockSignals(blocked);
+ if (own_transaction)
+ m_data_base.commit();
//The folios after the new one moved down, and a folio number made
//from %id or %total changed on every folio.
@@ -1095,6 +1272,12 @@ void projectDataBase::removeDiagram(Diagram *diagram)
{
m_content_changed = true;
const QString uuid_str = diagram->uuid().toString();
+ unplaceFolio(diagram, diagram->uuid());
+ for (Conductor *conductor : diagram->conductors())
+ unplaceConductor(conductor, conductor->uuid());
+ //Its symbols are no longer placed (they live on in the undo stack)
+ for (Element *element : diagram->elements())
+ unplaceElement(element, element->uuid());
//Order matters: element_info and terminal are scoped through a
//subquery on element, so they must run before element itself is
@@ -1224,6 +1407,24 @@ void projectDataBase::addConductor(Conductor *conductor)
return;
}
+ placeConductor(conductor);
+ watchConductor(conductor);
+ writeConductorRow(conductor);
+ emit dataBaseUpdated();
+}
+
+/**
+ @brief projectDataBase::writeConductorRow
+ Write the conductor row of @p conductor, a placed wire, and the rows of
+ the terminals it ends on. A uuid that already has a row keeps it, as in
+ a full rebuild; nothing is written while updates are blocked (the
+ rebuild that ends them writes every row).
+*/
+void projectDataBase::writeConductorRow(Conductor *conductor)
+{
+ if (m_update_blocked) {
+ return;
+ }
//Both endpoints must belong to an element: the terminal table is keyed
//on (terminal, element) and a terminal with no parent has no identity
//to key on. Terminals whose *definition* predates terminal uuids are
@@ -1237,12 +1438,9 @@ void projectDataBase::addConductor(Conductor *conductor)
insertTerminal(conductor->terminal1);
insertTerminal(conductor->terminal2);
- watchConductor(conductor);
bindConductorValues(m_insert_conductor_query, conductor, conductor->diagram());
if (!m_insert_conductor_query.exec()) {
- qDebug() << "projectDataBase::addConductor insert error : " << m_insert_conductor_query.lastError();
- } else {
- emit dataBaseUpdated();
+ qDebug() << "projectDataBase::writeConductorRow insert error : " << m_insert_conductor_query.lastError();
}
}
@@ -1253,6 +1451,27 @@ void projectDataBase::addConductor(Conductor *conductor)
void projectDataBase::removeConductor(Conductor *conductor)
{
m_content_changed = true;
+ const QUuid uuid = conductor->uuid();
+ removeConductorRow(uuid);
+ unplaceConductor(conductor, uuid);
+ //Another wire still holds the uuid: the row is its own.
+ if (Conductor *other = otherConductorHolder(uuid, conductor)) {
+ writeConductorRow(other);
+ }
+ emit dataBaseUpdated();
+}
+
+/**
+ @brief projectDataBase::removeConductorRow
+ Remove the conductor row of @p uuid, and the rows of the terminals it
+ ended on unless another conductor ends there; nothing while updates are
+ blocked, as writeConductorRow().
+*/
+void projectDataBase::removeConductorRow(const QUuid &uuid)
+{
+ if (m_update_blocked) {
+ return;
+ }
//The terminal table lists the terminals a conductor ends on: the
//conductor's two ends go with it unless another conductor ends there.
QList> ends;
@@ -1260,15 +1479,15 @@ void projectDataBase::removeConductor(Conductor *conductor)
read_ends.prepare(QStringLiteral("SELECT terminal1_uuid, terminal1_element_uuid, "
"terminal2_uuid, terminal2_element_uuid "
"FROM conductor WHERE uuid = :uuid"));
- read_ends.bindValue(QStringLiteral(":uuid"), conductor->uuid().toString());
+ read_ends.bindValue(QStringLiteral(":uuid"), uuid.toString());
if (read_ends.exec() && read_ends.next()) {
ends << qMakePair(read_ends.value(0).toString(), read_ends.value(1).toString())
<< qMakePair(read_ends.value(2).toString(), read_ends.value(3).toString());
}
- m_remove_conductor_query.bindValue(":uuid", conductor->uuid().toString());
+ m_remove_conductor_query.bindValue(":uuid", uuid.toString());
if (!m_remove_conductor_query.exec()) {
- qDebug() << "projectDataBase::removeConductor delete error : " << m_remove_conductor_query.lastError();
+ qDebug() << "projectDataBase::removeConductorRow delete error : " << m_remove_conductor_query.lastError();
return;
}
@@ -1282,10 +1501,9 @@ void projectDataBase::removeConductor(Conductor *conductor)
remove_end.bindValue(QStringLiteral(":uuid"), end.first);
remove_end.bindValue(QStringLiteral(":element_uuid"), end.second);
if (!remove_end.exec()) {
- qDebug() << "projectDataBase::removeConductor terminal delete error : " << remove_end.lastError();
+ qDebug() << "projectDataBase::removeConductorRow terminal delete error : " << remove_end.lastError();
}
}
- emit dataBaseUpdated();
}
/**
@@ -1577,7 +1795,7 @@ bool projectDataBase::writeDrawingItem(QObject *object)
{
auto *item = dynamic_cast(object);
auto *diagram = item ? qobject_cast(item->scene()) : nullptr;
- if (!diagram || !m_project || !m_project->diagrams().contains(diagram)) {
+ if (!diagram || !m_project || !m_project->folios().contains(diagram)) {
//Not on a folio of this project (any more): nothing to write,
//and nothing to wait for.
return true;
@@ -1693,8 +1911,10 @@ void projectDataBase::populateDrawingItemTables()
//Queued directly, not through addDrawingItem(): every one of them
//came onto its folio through Diagram::addItem(), which connected it
- //already, and a full rebuild runs on every load.
- for (auto diagram : m_project->diagrams())
+ //already, and a full rebuild runs on every load. A folio not built
+ //yet (QET_LAZY_FOLIOS) has none: its items write their rows as it
+ //is built.
+ for (auto diagram : m_project->folios())
{
const QList items = diagram->items();
for (QGraphicsItem *item : items)
@@ -1787,6 +2007,50 @@ bool projectDataBase::createDataBase()
qDebug() << " element_info_table query : " << query_.lastError();
}
+ //Create the element_information table: the information of every
+ //symbol placed in the project, in full -- each key with its value
+ //and whether it is shown -- the store symbols write through
+ //(storeElementInformation()). Unlike element_info, which has one
+ //column per known key and is filled again on every rebuild, this
+ //is kept by the edits themselves.
+ if (!query_.exec(QStringLiteral(
+ "CREATE TABLE element_information ("
+ "element_uuid VARCHAR(50) NOT NULL, "
+ "ord INTEGER NOT NULL, "
+ "name TEXT NOT NULL, "
+ "value TEXT, "
+ "show INTEGER NOT NULL, "
+ "PRIMARY KEY (element_uuid, ord))"))) {
+ qDebug() << " element_information_table query : " << query_.lastError();
+ }
+ //Create the folio_titleblock table: the title block properties of
+ //every folio, as a folio saves them, the store folios write
+ //through (storeFolioTitleBlock()). One row per property, then one
+ //per additional field ("custom:" and its name).
+ if (!query_.exec(QStringLiteral(
+ "CREATE TABLE folio_titleblock ("
+ "diagram_uuid VARCHAR(50) NOT NULL, "
+ "ord INTEGER NOT NULL, "
+ "name TEXT NOT NULL, "
+ "value TEXT, "
+ "show INTEGER NOT NULL, "
+ "PRIMARY KEY (diagram_uuid, ord))"))) {
+ qDebug() << " folio_titleblock_table query : " << query_.lastError();
+ }
+
+ //Create the conductor_properties table: the properties of every
+ //wire placed in the project, as the attributes a saved wire carries,
+ //the store wires write through (storeConductorProperties()).
+ if (!query_.exec(QStringLiteral(
+ "CREATE TABLE conductor_properties ("
+ "conductor_uuid VARCHAR(50) NOT NULL, "
+ "name TEXT NOT NULL, "
+ "value TEXT, "
+ "PRIMARY KEY (conductor_uuid, name))"))) {
+ qDebug() << " conductor_properties_table query : " << query_.lastError();
+ }
+
+
//Create the link table: one row per element and element it is
//linked to -- a coil and its contacts, a pair of folio reports --
//from each side, with the contact group the element saved for it.
@@ -2114,7 +2378,8 @@ void projectDataBase::populateDiagramTable()
QSqlQuery query_(m_data_base);
query_.exec("DELETE FROM diagram");
- for (auto diagram : m_project->diagrams())
+ //The folios' own data: folios() builds nothing
+ for (auto diagram : m_project->folios())
{
m_insert_diagram_query.bindValue(":uuid", diagram->uuid().toString());
m_insert_diagram_query.bindValue(":pos", m_project->folioIndex(diagram)+1);
@@ -2158,8 +2423,12 @@ void projectDataBase::populateElementTable()
QSqlQuery query_(m_data_base);
query_.exec("DELETE FROM element");
- for (auto diagram : m_project->diagrams())
+ for (auto diagram : m_project->folios())
{
+ if (m_kept_folios.contains(diagram)) {
+ restoreKeptRows(QStringLiteral("element"), diagram, QStringLiteral("diagram_uuid = :folio"));
+ continue;
+ }
const ElementProvider ep(diagram);
const auto elmt_vector = ep.find(allElementTypes());
//Insert all values into the database
@@ -2175,20 +2444,73 @@ void projectDataBase::populateElementTable()
/**
@brief projectDataBase::populateElementInfoTable
- Populate the element info table
+ Populate the element info table from the symbol information store
+ (DB-ACCESSORS-PLAN.md stage 4.3), in the order of the element table.
+ The label column is the label a symbol shows, which its folio's
+ numbering decides: the symbol holding the uuid gives it.
+
+ A project where several symbols share a uuid (F100) is read from its
+ folios as before: the first symbol on them owns the row, and the store
+ does not know which one that is.
*/
void projectDataBase::populateElementInfoTable()
{
QSqlQuery query(m_data_base);
query.exec(QStringLiteral("DELETE FROM element_info"));
- for (const auto &diagram : m_project->diagrams())
- {
- const ElementProvider ep(diagram);
- const auto elmt_vector = ep.find(allElementTypes());
+ for (auto it = m_placed_elements.constBegin() ; it != m_placed_elements.constEnd() ; ++it) {
+ if (placedElementCount(it.key()) > 1) {
+ populateElementInfoTableFromFolios();
+ return;
+ }
+ }
- //Insert all values into the database
- for (const auto &elmt : elmt_vector)
+ QSqlQuery uuids(m_data_base);
+ if (!uuids.exec(QStringLiteral("SELECT uuid FROM element ORDER BY rowid"))) {
+ qDebug() << "projectDataBase::populateElementInfoTable select error : " << uuids.lastError();
+ return;
+ }
+ while (uuids.next())
+ {
+ const QString uuid_str = uuids.value(0).toString();
+ const QUuid uuid(uuid_str);
+ Element *holder = otherHolder(uuid, nullptr);
+ if (!holder) {
+ //A symbol on a folio not built: its row as it was
+ if (!m_kept_folios.isEmpty()) {
+ QSqlQuery kept(m_data_base);
+ kept.prepare(QStringLiteral("INSERT INTO element_info SELECT * FROM kept_element_info "
+ "WHERE element_uuid = :uuid"));
+ kept.bindValue(QStringLiteral(":uuid"), uuid_str);
+ kept.exec();
+ }
+ continue;
+ }
+ bindElementInfoValues(m_insert_element_info_query, uuid_str,
+ m_element_information.value(uuid), holder->actualLabel());
+ if (!m_insert_element_info_query.exec()) {
+ qDebug() << "projectDataBase::populateElementInfoTable insert error : " << m_insert_element_info_query.lastError();
+ }
+ }
+}
+
+/**
+ @brief projectDataBase::populateElementInfoTableFromFolios
+ populateElementInfoTable() from the symbols on the folios
+*/
+void projectDataBase::populateElementInfoTableFromFolios()
+{
+ for (const auto &diagram : m_project->folios())
+ {
+ //A folio not built: its rows as they were
+ if (m_kept_folios.contains(diagram)) {
+ restoreKeptRows(QStringLiteral("element_info"), diagram,
+ QStringLiteral("element_uuid IN (SELECT uuid FROM kept_element "
+ "WHERE diagram_uuid = :folio)"));
+ continue;
+ }
+ const ElementProvider ep(diagram);
+ for (const auto &elmt : ep.find(allElementTypes()))
{
bindElementInfoValues(m_insert_element_info_query, elmt);
if (!m_insert_element_info_query.exec()) {
@@ -2198,6 +2520,798 @@ void projectDataBase::populateElementInfoTable()
}
}
+/**
+ @brief projectDataBase::elementInformation
+ @return the stored information of the placed symbol @p element, empty
+ if it has none
+*/
+DiagramContext projectDataBase::elementInformation(const QUuid &element) const
+{
+ return m_element_information.value(element);
+}
+
+bool projectDataBase::hasElementInformation(const QUuid &element) const
+{
+ return m_element_information.contains(element);
+}
+
+int projectDataBase::placedElementCount(const QUuid &element) const
+{
+ //Without values(), which makes a list: this is asked on every read
+ //of a placed symbol's information.
+ int n = 0;
+ for (auto it = m_placed_elements.constFind(element) ;
+ it != m_placed_elements.constEnd() && it.key() == element ; ++it)
+ if (it.value()) ++n;
+ return n;
+}
+
+/**
+ @brief projectDataBase::storeElementInformation
+ Store @p information as the information of the placed symbol @p element,
+ in the element_information table and the cache in front of it.
+ @return true if that changed what was stored
+*/
+bool projectDataBase::storeElementInformation(const QUuid &element, const DiagramContext &information)
+{
+ if (element.isNull()) return false;
+ auto known = m_element_information.constFind(element);
+ if (known != m_element_information.constEnd() && *known == information) return false;
+ m_element_information.insert(element, information);
+
+ const QString uuid = element.toString();
+ m_store_remove_query.bindValue(QStringLiteral(":uuid"), uuid);
+ if (!m_store_remove_query.exec()) {
+ qDebug() << "projectDataBase::storeElementInformation remove error : " << m_store_remove_query.lastError();
+ }
+ QSqlQuery &insert = m_store_insert_query;
+ int ord = 0;
+ for (const QString &key : information.keys()) {
+ insert.bindValue(QStringLiteral(":uuid"), uuid);
+ insert.bindValue(QStringLiteral(":ord"), ord++);
+ insert.bindValue(QStringLiteral(":name"), key);
+ insert.bindValue(QStringLiteral(":value"), information.value(key).toString());
+ insert.bindValue(QStringLiteral(":show"), information.keyMustShow(key) ? 1 : 0);
+ if (!insert.exec()) {
+ qDebug() << "projectDataBase::storeElementInformation insert error : " << insert.lastError();
+ }
+ }
+ return true;
+}
+
+/**
+ @brief projectDataBase::elementInformationChanged
+ The placed symbol @p element now has @p information: the store keeps
+ it, and its element_info row follows (F106: a pasted symbol's label is
+ erased or numbered after its row is written, by
+ Element::setElementInformations(), which tells no table).
+ A uuid other symbols share (F100) keeps the row of the first of them.
+*/
+void projectDataBase::elementInformationChanged(Element *element, const DiagramContext &information)
+{
+ if (!storeElementInformation(element->uuid(), information)) {
+ return;
+ }
+ m_content_changed = true;
+ if (m_update_blocked || placedElementCount(element->uuid()) != 1) {
+ return;
+ }
+ const auto hash = elementInfoToString(element);
+ for (const auto &key : QETInformation::elementInfoKeys()) {
+ m_update_element_query.bindValue(QStringLiteral(":") + key, hash.value(key));
+ }
+ m_update_element_query.bindValue(QStringLiteral(":uuid"), element->uuid().toString());
+ if (!m_update_element_query.exec()) {
+ qDebug() << "projectDataBase::elementInformationChanged update error : " << m_update_element_query.lastError();
+ }
+}
+
+/**
+ @brief projectDataBase::forgetElementInformation
+ The symbol @p element is no longer placed: its stored information goes.
+*/
+void projectDataBase::forgetElementInformation(const QUuid &element)
+{
+ if (!m_element_information.remove(element)) return;
+ m_store_remove_query.bindValue(QStringLiteral(":uuid"), element.toString());
+ if (!m_store_remove_query.exec()) {
+ qDebug() << "projectDataBase::forgetElementInformation error : " << m_store_remove_query.lastError();
+ }
+}
+
+/**
+ @brief projectDataBase::placeElement
+ @p element is now placed: its information is stored under its uuid.
+*/
+void projectDataBase::placeElement(Element *element)
+{
+ if (!m_placed_elements.contains(element->uuid(), element))
+ m_placed_elements.insert(element->uuid(), element);
+ storeElementInformation(element->uuid(), element->ownInformations());
+}
+
+/**
+ @brief projectDataBase::unplaceElement
+ @p element no longer holds @p uuid. If another placed symbol still does
+ (the original of a copy), the row is written again from it; otherwise
+ it goes.
+*/
+void projectDataBase::unplaceElement(Element *element, const QUuid &uuid)
+{
+ m_placed_elements.remove(uuid, element);
+ for (const QPointer &other : m_placed_elements.values(uuid)) {
+ if (other) {
+ //Its own copy: the store still holds this symbol's
+ storeElementInformation(uuid, other->ownInformations());
+ return;
+ }
+ }
+ m_placed_elements.remove(uuid);
+ forgetElementInformation(uuid);
+}
+
+/**
+ @brief projectDataBase::elementUuidChanged
+ The uuid of the placed symbol @p element changed from @p old_uuid (a
+ paste or a folio copy renews it): its information and its element,
+ element_info and link rows follow it. A copy is placed while it still
+ carries its original's uuid, so its rows could not be written then
+ (the uuid was taken): they are written now. The old uuid keeps its rows
+ only if another symbol still holds it (F106).
+*/
+void projectDataBase::elementUuidChanged(Element *element, const QUuid &old_uuid)
+{
+ if (!m_placed_elements.contains(old_uuid, element) || old_uuid == element->uuid()) return;
+ m_content_changed = true;
+ Element *other = otherHolder(old_uuid, element);
+ unplaceElement(element, old_uuid);
+ removeElementRows(old_uuid);
+ if (other) {
+ //The old uuid's rows may have been written from this symbol
+ //while both held it: write them again from the one keeping it.
+ writeElementRows(other);
+ queueLinks(other);
+ for (Element *linked : other->linkedElements())
+ queueLinks(linked);
+ }
+ placeElement(element);
+ writeElementRows(element);
+ //Its wires' rows name their ends' symbols: write them again
+ //(a pasted wire is placed before its symbols get their uuids).
+ for (Conductor *conductor : element->conductors()) {
+ if (placedConductorCount(conductor->uuid()) == 1) {
+ removeConductorRow(conductor->uuid());
+ writeConductorRow(conductor);
+ }
+ }
+ queueLinks(element);
+ for (Element *linked : element->linkedElements())
+ queueLinks(linked);
+ emit dataBaseUpdated();
+}
+
+/**
+ @brief projectDataBase::otherHolder
+ @return a placed symbol other than @p element holding @p uuid, or
+ nullptr. Two hold one uuid while a copy is being placed, and for good
+ in files several symbols share a uuid in (F100).
+*/
+Element *projectDataBase::otherHolder(const QUuid &uuid, const Element *element) const
+{
+ for (const QPointer &other : m_placed_elements.values(uuid))
+ if (other && other != element) return other;
+ return nullptr;
+}
+
+/**
+ @brief projectDataBase::writeElementRows
+ Write the element and element_info rows of @p element, a placed symbol.
+ A uuid that already has rows keeps them: the first symbol to hold a
+ uuid owns its rows, as in a full rebuild.
+
+ Nothing is written while updates are blocked (a project being opened,
+ a paste being read): the rebuild that ends them writes every row.
+*/
+void projectDataBase::writeElementRows(Element *element)
+{
+ if (m_update_blocked) {
+ return;
+ }
+ bindElementValues(m_insert_elements_query, element, element->diagram());
+ if (!m_insert_elements_query.exec()) {
+ qDebug() << "projectDataBase::writeElementRows insert element error : " << m_insert_elements_query.lastError();
+ }
+ bindElementInfoValues(m_insert_element_info_query, element);
+ if (!m_insert_element_info_query.exec()) {
+ qDebug() << "projectDataBase::writeElementRows insert element info error : " << m_insert_element_info_query.lastError();
+ }
+}
+
+/**
+ @brief projectDataBase::removeElementRows
+ Remove the element, element_info and link rows of @p uuid; nothing
+ while updates are blocked, as writeElementRows().
+*/
+void projectDataBase::removeElementRows(const QUuid &uuid)
+{
+ if (m_update_blocked) {
+ return;
+ }
+ const QString uuid_str = uuid.toString();
+ m_remove_element_query.bindValue(QStringLiteral(":uuid"), uuid_str);
+ if (!m_remove_element_query.exec()) {
+ qDebug() << "projectDataBase::removeElementRows remove error : " << m_remove_element_query.lastError();
+ }
+ m_remove_element_info_query.bindValue(QStringLiteral(":uuid"), uuid_str);
+ if (!m_remove_element_info_query.exec()) {
+ qDebug() << "projectDataBase::removeElementRows remove element_info error : " << m_remove_element_info_query.lastError();
+ }
+ m_remove_links_query.bindValue(QStringLiteral(":uuid"), uuid_str);
+ if (!m_remove_links_query.exec()) {
+ qDebug() << "projectDataBase::removeElementRows remove link error : " << m_remove_links_query.lastError();
+ }
+}
+
+/**
+ @brief projectDataBase::elementInformationMismatches
+ Compare the store with every placed symbol, both ways: a symbol whose
+ information differs or is missing, and a stored row no symbol has.
+*/
+QStringList projectDataBase::elementInformationMismatches() const
+{
+ QStringList out;
+ QSet placed;
+ for (Diagram *diagram : m_project->diagrams()) {
+ for (Element *element : diagram->elements()) {
+ placed.insert(element->uuid());
+ //A uuid several symbols share has one row and cannot agree
+ //with them all; those symbols answer from their own copy.
+ if (placedElementCount(element->uuid()) > 1)
+ continue;
+ if (!m_element_information.contains(element->uuid()))
+ out << element->uuid().toString() + QStringLiteral(": not stored");
+ else if (m_element_information.value(element->uuid()) != element->elementInformations())
+ out << element->uuid().toString() + QStringLiteral(": differs");
+ }
+ }
+ for (auto it = m_element_information.constBegin() ; it != m_element_information.constEnd() ; ++it)
+ if (!placed.contains(it.key()))
+ out << it.key().toString() + QStringLiteral(": stored, not placed");
+ QSqlQuery rows(m_data_base);
+ if (rows.exec(QStringLiteral("SELECT count(*) FROM element_information")) && rows.next()) {
+ int expected = 0;
+ for (const DiagramContext &dc : m_element_information) expected += int(dc.keys().size());
+ if (rows.value(0).toInt() != expected)
+ out << QStringLiteral("table holds %1 rows, the cache %2").arg(rows.value(0).toInt()).arg(expected);
+ }
+ return out;
+}
+
+/**
+ @brief projectDataBase::folioTitleBlock
+ @return the stored title block properties of the folio @p folio
+*/
+TitleBlockProperties projectDataBase::folioTitleBlock(const QUuid &folio) const
+{
+ return m_folio_titleblocks.value(folio);
+}
+
+/**
+ @brief projectDataBase::storedFolioTitleBlock
+ @return the stored title block properties of the folio @p folio, or
+ nullptr; valid until the store next changes
+*/
+const TitleBlockProperties *projectDataBase::storedFolioTitleBlock(const QUuid &folio) const
+{
+ auto it = m_folio_titleblocks.constFind(folio);
+ return it == m_folio_titleblocks.constEnd() ? nullptr : &it.value();
+}
+
+/**
+ @brief projectDataBase::placedFolio
+ @return the folio of the project holding @p folio, or nullptr
+*/
+Diagram *projectDataBase::placedFolio(const QUuid &folio) const
+{
+ return m_placed_folios.value(folio);
+}
+
+/**
+ @brief projectDataBase::storeFolioTitleBlock
+ Store @p properties as the title block of the folio @p folio, in the
+ folio_titleblock table and the cache in front of it.
+*/
+void projectDataBase::storeFolioTitleBlock(const QUuid &folio, const TitleBlockProperties &properties)
+{
+ if (folio.isNull()) return;
+ auto known = m_folio_titleblocks.find(folio);
+ if (known != m_folio_titleblocks.end() && *known == properties) return;
+ m_folio_titleblocks.insert(folio, properties);
+
+ const QString uuid = folio.toString();
+ QSqlQuery remove(m_data_base);
+ remove.prepare(QStringLiteral("DELETE FROM folio_titleblock WHERE diagram_uuid = :uuid"));
+ remove.bindValue(QStringLiteral(":uuid"), uuid);
+ if (!remove.exec()) {
+ qDebug() << "projectDataBase::storeFolioTitleBlock remove error : " << remove.lastError();
+ }
+ QSqlQuery insert(m_data_base);
+ insert.prepare(QStringLiteral("INSERT INTO folio_titleblock (diagram_uuid, ord, name, value, show) "
+ "VALUES (:uuid, :ord, :name, :value, :show)"));
+ int ord = 0;
+ auto row = [&](const QString &name, const QString &value, bool show) {
+ insert.bindValue(QStringLiteral(":uuid"), uuid);
+ insert.bindValue(QStringLiteral(":ord"), ord++);
+ insert.bindValue(QStringLiteral(":name"), name);
+ insert.bindValue(QStringLiteral(":value"), value);
+ insert.bindValue(QStringLiteral(":show"), show ? 1 : 0);
+ if (!insert.exec()) {
+ qDebug() << "projectDataBase::storeFolioTitleBlock insert error : " << insert.lastError();
+ }
+ };
+ row(QStringLiteral("title"), properties.title, true);
+ row(QStringLiteral("author"), properties.author, true);
+ row(QStringLiteral("date"), properties.date.isValid()
+ ? properties.date.toString(Qt::ISODate) : QString(), true);
+ row(QStringLiteral("filename"), properties.filename, true);
+ row(QStringLiteral("plant"), properties.plant, true);
+ row(QStringLiteral("locmach"), properties.locmach, true);
+ row(QStringLiteral("indexrev"), properties.indexrev, true);
+ row(QStringLiteral("version"), properties.version, true);
+ row(QStringLiteral("folio"), properties.folio, true);
+ row(QStringLiteral("auto_page_num"), properties.auto_page_num, true);
+ row(QStringLiteral("template_name"), properties.template_name, true);
+ row(QStringLiteral("display_at"), properties.display_at == Qt::RightEdge
+ ? QStringLiteral("right") : QStringLiteral("bottom"), true);
+ for (const QString &key : properties.context.keys()) {
+ row(QStringLiteral("custom:") + key, properties.context.value(key).toString(),
+ properties.context.keyMustShow(key));
+ }
+}
+
+/**
+ @brief projectDataBase::placeFolio
+ @p folio is now a folio of the project: its title block is stored under
+ its uuid.
+*/
+void projectDataBase::placeFolio(Diagram *folio)
+{
+ m_placed_folios.insert(folio->uuid(), folio);
+ storeFolioTitleBlock(folio->uuid(), folio->border_and_titleblock.localTitleBlock());
+}
+
+/**
+ @brief projectDataBase::unplaceFolio
+ @p folio no longer holds @p uuid (it was removed, or its uuid changed):
+ the title block stored under it goes.
+*/
+void projectDataBase::unplaceFolio(Diagram *folio, const QUuid &uuid)
+{
+ if (m_placed_folios.value(uuid) != folio) return;
+ m_placed_folios.remove(uuid);
+ m_folio_titleblocks.remove(uuid);
+ QSqlQuery remove(m_data_base);
+ remove.prepare(QStringLiteral("DELETE FROM folio_titleblock WHERE diagram_uuid = :uuid"));
+ remove.bindValue(QStringLiteral(":uuid"), uuid.toString());
+ if (!remove.exec()) {
+ qDebug() << "projectDataBase::unplaceFolio error : " << remove.lastError();
+ }
+}
+
+/**
+ @brief projectDataBase::folioUuidChanged
+ The uuid of the placed folio @p folio changed from @p old_uuid: its
+ stored title block follows it.
+*/
+void projectDataBase::folioUuidChanged(Diagram *folio, const QUuid &old_uuid)
+{
+ if (old_uuid == folio->uuid() || m_placed_folios.value(old_uuid) != folio) return;
+ unplaceFolio(folio, old_uuid);
+ placeFolio(folio);
+}
+
+/**
+ @brief projectDataBase::folioTitleBlockMismatches
+ Compare the store with every folio's own title block, both ways.
+*/
+QStringList projectDataBase::folioTitleBlockMismatches() const
+{
+ QStringList out;
+ QSet folios;
+ for (Diagram *diagram : m_project->diagrams()) {
+ folios.insert(diagram->uuid());
+ if (m_placed_folios.value(diagram->uuid()) != diagram)
+ out << diagram->uuid().toString() + QStringLiteral(": not stored");
+ else if (TitleBlockProperties(m_folio_titleblocks.value(diagram->uuid()))
+ != diagram->border_and_titleblock.localTitleBlock())
+ out << diagram->uuid().toString() + QStringLiteral(": differs");
+ }
+ for (auto it = m_folio_titleblocks.constBegin() ; it != m_folio_titleblocks.constEnd() ; ++it)
+ if (!folios.contains(it.key()))
+ out << it.key().toString() + QStringLiteral(": stored, not a folio");
+ return out;
+}
+
+/**
+ @brief projectDataBase::conductorProperties
+ @return the stored properties of the placed wire @p conductor
+*/
+ConductorProperties projectDataBase::conductorProperties(const QUuid &conductor) const
+{
+ return m_conductor_properties.value(conductor);
+}
+
+bool projectDataBase::hasConductorProperties(const QUuid &conductor) const
+{
+ return m_conductor_properties.contains(conductor);
+}
+
+int projectDataBase::placedConductorCount(const QUuid &conductor) const
+{
+ //Without values(), as placedElementCount()
+ int n = 0;
+ for (auto it = m_placed_conductors.constFind(conductor) ;
+ it != m_placed_conductors.constEnd() && it.key() == conductor ; ++it)
+ if (it.value()) ++n;
+ return n;
+}
+
+/**
+ @brief projectDataBase::storeConductorProperties
+ Store @p properties as the properties of the placed wire @p conductor,
+ in the conductor_properties table -- the attributes a saved wire
+ carries -- and the cache in front of it.
+ @return true if that changed what was stored
+*/
+bool projectDataBase::storeConductorProperties(const QUuid &conductor, const ConductorProperties &properties)
+{
+ if (conductor.isNull()) return false;
+ auto known = m_conductor_properties.constFind(conductor);
+ if (known != m_conductor_properties.constEnd() && *known == properties) return false;
+ m_conductor_properties.insert(conductor, properties);
+ //The rows are written by the next flushConductorProperties(): a
+ //folio inserted or removed renumbers the wires of every folio after
+ //it, one change each, and nothing reads them in between.
+ m_dirty_conductor_properties.insert(conductor);
+ return true;
+}
+
+/**
+ @brief projectDataBase::flushConductorProperties
+ Write the conductor_properties rows of every wire changed since the last
+ flush; only the attributes that changed.
+*/
+void projectDataBase::flushConductorProperties()
+{
+ if (m_dirty_conductor_properties.isEmpty()) {
+ return;
+ }
+ const QSet dirty = m_dirty_conductor_properties;
+ m_dirty_conductor_properties.clear();
+ const bool own_transaction = m_data_base.transaction();
+ for (const QUuid &conductor : dirty)
+ {
+ const QString uuid = conductor.toString();
+ QHash attributes;
+ auto known = m_conductor_properties.constFind(conductor);
+ if (known != m_conductor_properties.constEnd()) {
+ for (const auto &attribute : known->attributes())
+ attributes.insert(attribute.first, attribute.second);
+ }
+ QHash &rows = m_conductor_attributes[conductor];
+ for (auto it = rows.constBegin() ; it != rows.constEnd() ; ++it) {
+ if (attributes.contains(it.key())) continue;
+ m_conductor_property_remove_query.bindValue(QStringLiteral(":uuid"), uuid);
+ m_conductor_property_remove_query.bindValue(QStringLiteral(":name"), it.key());
+ if (!m_conductor_property_remove_query.exec()) {
+ qDebug() << "projectDataBase::flushConductorProperties remove error : " << m_conductor_property_remove_query.lastError();
+ }
+ }
+ for (auto it = attributes.constBegin() ; it != attributes.constEnd() ; ++it) {
+ auto row = rows.constFind(it.key());
+ if (row != rows.constEnd() && *row == it.value()) continue;
+ m_conductor_properties_insert_query.bindValue(QStringLiteral(":uuid"), uuid);
+ m_conductor_properties_insert_query.bindValue(QStringLiteral(":name"), it.key());
+ m_conductor_properties_insert_query.bindValue(QStringLiteral(":value"), it.value());
+ if (!m_conductor_properties_insert_query.exec()) {
+ qDebug() << "projectDataBase::flushConductorProperties insert error : " << m_conductor_properties_insert_query.lastError();
+ }
+ }
+ if (attributes.isEmpty())
+ m_conductor_attributes.remove(conductor);
+ else
+ rows = attributes;
+
+ //The conductor row's text, if the row is this wire's alone
+ if (known != m_conductor_properties.constEnd() && !m_update_blocked
+ && placedConductorCount(conductor) == 1) {
+ m_update_conductor_query.bindValue(QStringLiteral(":uuid"), uuid);
+ m_update_conductor_query.bindValue(QStringLiteral(":text"), known->text);
+ if (!m_update_conductor_query.exec()) {
+ qDebug() << "projectDataBase::flushConductorProperties update error : " << m_update_conductor_query.lastError();
+ }
+ }
+ }
+ if (own_transaction) {
+ m_data_base.commit();
+ }
+}
+
+/**
+ @brief projectDataBase::conductorPropertiesStored
+ The placed wire @p conductor now has @p properties: the store keeps
+ them, and its conductor row's text follows at the next flush (a formula
+ is numbered by Conductor::refreshText(), which tells no table).
+*/
+void projectDataBase::conductorPropertiesStored(Conductor *conductor, const ConductorProperties &properties)
+{
+ if (storeConductorProperties(conductor->uuid(), properties)) {
+ m_content_changed = true;
+ }
+}
+
+/**
+ @brief projectDataBase::placeConductor
+ @p conductor is now placed: its properties are stored under its uuid.
+*/
+void projectDataBase::placeConductor(Conductor *conductor)
+{
+ if (!m_placed_conductors.contains(conductor->uuid(), conductor))
+ m_placed_conductors.insert(conductor->uuid(), conductor);
+ storeConductorProperties(conductor->uuid(), conductor->ownProperties());
+}
+
+/**
+ @brief projectDataBase::unplaceConductor
+ @p conductor no longer holds @p uuid. If another placed wire still does
+ (the original of a copy), its properties are stored again; otherwise
+ they go.
+*/
+void projectDataBase::unplaceConductor(Conductor *conductor, const QUuid &uuid)
+{
+ m_placed_conductors.remove(uuid, conductor);
+ if (Conductor *other = otherConductorHolder(uuid, nullptr)) {
+ storeConductorProperties(uuid, other->ownProperties());
+ return;
+ }
+ m_placed_conductors.remove(uuid);
+ if (m_conductor_properties.remove(uuid))
+ m_dirty_conductor_properties.insert(uuid);
+}
+
+/**
+ @brief projectDataBase::otherConductorHolder
+ @return a placed wire other than @p conductor holding @p uuid, or nullptr
+*/
+Conductor *projectDataBase::otherConductorHolder(const QUuid &uuid, const Conductor *conductor) const
+{
+ for (const QPointer &other : m_placed_conductors.values(uuid))
+ if (other && other != conductor) return other;
+ return nullptr;
+}
+
+/**
+ @brief projectDataBase::conductorUuidChanged
+ The uuid of the placed wire @p conductor changed from @p old_uuid (a
+ paste renews it, a file gives it): its properties and its row follow
+ it. A copy is placed while it still carries its original's uuid, so its
+ row could not be written then; the original's is written again.
+*/
+void projectDataBase::conductorUuidChanged(Conductor *conductor, const QUuid &old_uuid)
+{
+ if (!m_placed_conductors.contains(old_uuid, conductor) || old_uuid == conductor->uuid()) return;
+ m_content_changed = true;
+ Conductor *other = otherConductorHolder(old_uuid, conductor);
+ unplaceConductor(conductor, old_uuid);
+ removeConductorRow(old_uuid);
+ if (other) {
+ writeConductorRow(other);
+ }
+ placeConductor(conductor);
+ writeConductorRow(conductor);
+ emit dataBaseUpdated();
+}
+
+/**
+ @brief projectDataBase::conductorPropertiesMismatches
+ Compare the store with every placed wire, both ways.
+*/
+QStringList projectDataBase::conductorPropertiesMismatches() const
+{
+ QStringList out;
+ QSet placed;
+ for (Diagram *diagram : m_project->diagrams()) {
+ for (Conductor *conductor : diagram->conductors()) {
+ placed.insert(conductor->uuid());
+ if (placedConductorCount(conductor->uuid()) > 1)
+ continue;
+ if (!m_conductor_properties.contains(conductor->uuid()))
+ out << conductor->uuid().toString() + QStringLiteral(": not stored");
+ else if (m_conductor_properties.value(conductor->uuid()) != conductor->ownProperties())
+ out << conductor->uuid().toString() + QStringLiteral(": differs");
+ }
+ }
+ for (auto it = m_conductor_properties.constBegin() ; it != m_conductor_properties.constEnd() ; ++it)
+ if (!placed.contains(it.key()))
+ out << it.key().toString() + QStringLiteral(": stored, not placed");
+ return out;
+}
+
+/**
+ @brief projectDataBase::prefillFromDocument
+ Fill the stores of symbol information, folio title blocks and wire
+ properties from @p document, the project being opened, before its
+ folios are built: what step 5 (lazy folios) needs, a folio's data
+ without its scene. In a .qetz these blocks come from project.sqlite.
+
+ Only what the document names by a uuid it holds once: a symbol, wire
+ or folio without one gets its uuid while it is built, and a uuid
+ several symbols share (F100) names none of them. Building then writes
+ the same stores again; endPrefill() says how often it agreed.
+*/
+void projectDataBase::prefillFromDocument(const QDomDocument &document)
+{
+ m_prefilled_elements.clear();
+ m_prefilled_folios.clear();
+ m_prefilled_conductors.clear();
+ for (PrefillTally &tally : m_prefill_tally) tally = PrefillTally();
+
+ QHash seen;
+ QHash elements;
+ QHash conductors;
+ const QDomElement root = document.documentElement();
+ for (QDomElement folio = root.firstChildElement(QStringLiteral("diagram")) ; !folio.isNull() ;
+ folio = folio.nextSiblingElement(QStringLiteral("diagram")))
+ {
+ const QUuid folio_uuid(folio.attribute(QStringLiteral("uuid")));
+ if (!folio_uuid.isNull() && ++seen[folio_uuid] == 1) {
+ TitleBlockProperties properties;
+ properties.fromXml(folio);
+ //What BorderTitleBlock::importTitleBlock() makes of them
+ properties.version = QetVersion::displayedVersion();
+ properties.collection = QET::QetCollection::Embedded;
+ m_prefilled_folios.insert(folio_uuid, properties);
+ }
+ const QDomElement element_list = folio.firstChildElement(QStringLiteral("elements"));
+ for (QDomElement element = element_list.firstChildElement(QStringLiteral("element")) ;
+ !element.isNull() ; element = element.nextSiblingElement(QStringLiteral("element")))
+ {
+ const QUuid uuid(element.attribute(QStringLiteral("uuid")));
+ if (uuid.isNull() || ++seen[uuid] > 1) continue;
+ DiagramContext information;
+ information.fromXml(element.firstChildElement(QStringLiteral("elementInformations")),
+ QStringLiteral("elementInformation"));
+ elements.insert(uuid, information);
+ }
+ const QDomElement conductor_list = folio.firstChildElement(QStringLiteral("conductors"));
+ for (QDomElement conductor = conductor_list.firstChildElement(QStringLiteral("conductor")) ;
+ !conductor.isNull() ; conductor = conductor.nextSiblingElement(QStringLiteral("conductor")))
+ {
+ const QUuid uuid(conductor.attribute(QStringLiteral("uuid")));
+ if (uuid.isNull() || ++seen[uuid] > 1) continue;
+ ConductorProperties properties;
+ properties.fromXml(conductor);
+ conductors.insert(uuid, properties);
+ }
+ }
+ //A uuid held twice names nothing
+ for (auto it = elements.constBegin() ; it != elements.constEnd() ; ++it)
+ if (seen.value(it.key()) == 1) m_prefilled_elements.insert(it.key(), it.value());
+ for (auto it = conductors.constBegin() ; it != conductors.constEnd() ; ++it)
+ if (seen.value(it.key()) == 1) m_prefilled_conductors.insert(it.key(), it.value());
+ for (auto it = m_prefilled_folios.begin() ; it != m_prefilled_folios.end() ; )
+ it = seen.value(it.key()) == 1 ? std::next(it) : m_prefilled_folios.erase(it);
+
+ for (auto it = m_prefilled_elements.constBegin() ; it != m_prefilled_elements.constEnd() ; ++it)
+ storeElementInformation(it.key(), it.value());
+ for (auto it = m_prefilled_folios.constBegin() ; it != m_prefilled_folios.constEnd() ; ++it)
+ storeFolioTitleBlock(it.key(), it.value());
+ for (auto it = m_prefilled_conductors.constBegin() ; it != m_prefilled_conductors.constEnd() ; ++it)
+ storeConductorProperties(it.key(), it.value());
+}
+
+/**
+ @brief projectDataBase::endPrefill
+ The folios are built: compare what prefillFromDocument() stored with
+ what building stored, and forget what nothing was placed for -- unless
+ @p keep_unplaced, when some folios are not built yet (QET_LAZY_FOLIOS):
+ the store then answers for their items until they are.
+*/
+void projectDataBase::endPrefill(bool keep_unplaced)
+{
+ auto tally = [](PrefillTally &t, const QStringList &differing) {
+ if (differing.isEmpty()) { ++t.agree; return; }
+ ++t.differ;
+ for (const QString &field : differing) ++t.fields[field];
+ };
+ for (auto it = m_prefilled_elements.constBegin() ; it != m_prefilled_elements.constEnd() ; ++it) {
+ if (!placedElementCount(it.key())) {
+ ++m_prefill_tally[0].unplaced;
+ if (!keep_unplaced && !m_placed_elements.contains(it.key())) forgetElementInformation(it.key());
+ continue;
+ }
+ const DiagramContext built = m_element_information.value(it.key());
+ QStringList differing;
+ const QList built_keys = built.keys();
+ QSet names(built_keys.cbegin(), built_keys.cend());
+ const QList prefilled_keys = it.value().keys();
+ names.unite(QSet(prefilled_keys.cbegin(), prefilled_keys.cend()));
+ for (const QString &name : names)
+ if (built.value(name) != it.value().value(name)
+ || built.keyMustShow(name) != it.value().keyMustShow(name))
+ differing << name;
+ tally(m_prefill_tally[0], differing);
+ }
+ for (auto it = m_prefilled_folios.constBegin() ; it != m_prefilled_folios.constEnd() ; ++it) {
+ if (!m_placed_folios.value(it.key())) {
+ ++m_prefill_tally[1].unplaced;
+ if (!keep_unplaced) m_folio_titleblocks.remove(it.key());
+ continue;
+ }
+ const TitleBlockProperties built = m_folio_titleblocks.value(it.key());
+ const TitleBlockProperties &p = it.value();
+ QStringList differing;
+ if (built.title != p.title) differing << QStringLiteral("title");
+ if (built.author != p.author) differing << QStringLiteral("author");
+ if (built.date != p.date) differing << QStringLiteral("date");
+ if (built.filename != p.filename) differing << QStringLiteral("filename");
+ if (built.plant != p.plant) differing << QStringLiteral("plant");
+ if (built.locmach != p.locmach) differing << QStringLiteral("locmach");
+ if (built.indexrev != p.indexrev) differing << QStringLiteral("indexrev");
+ if (built.version != p.version) differing << QStringLiteral("version");
+ if (built.folio != p.folio) differing << QStringLiteral("folio");
+ if (built.auto_page_num != p.auto_page_num) differing << QStringLiteral("auto_page_num");
+ if (built.template_name != p.template_name) differing << QStringLiteral("template_name");
+ if (!(built.context == p.context)) differing << QStringLiteral("context");
+ if (built.display_at != p.display_at) differing << QStringLiteral("display_at");
+ if (built.collection != p.collection) differing << QStringLiteral("collection");
+ tally(m_prefill_tally[1], differing);
+ }
+ for (auto it = m_prefilled_conductors.constBegin() ; it != m_prefilled_conductors.constEnd() ; ++it) {
+ if (!placedConductorCount(it.key())) {
+ ++m_prefill_tally[2].unplaced;
+ if (!keep_unplaced && !m_placed_conductors.contains(it.key()) && m_conductor_properties.remove(it.key()))
+ m_dirty_conductor_properties.insert(it.key());
+ continue;
+ }
+ QHash built, prefilled;
+ for (const auto &a : m_conductor_properties.value(it.key()).attributes()) built.insert(a.first, a.second);
+ for (const auto &a : it.value().attributes()) prefilled.insert(a.first, a.second);
+ QStringList differing;
+ QSet names(built.keyBegin(), built.keyEnd());
+ names.unite(QSet(prefilled.keyBegin(), prefilled.keyEnd()));
+ for (const QString &name : names)
+ if (built.value(name) != prefilled.value(name)) differing << name;
+ tally(m_prefill_tally[2], differing);
+ }
+ m_prefilled_elements.clear();
+ m_prefilled_folios.clear();
+ m_prefilled_conductors.clear();
+}
+
+/**
+ @brief projectDataBase::prefillReport
+ @return how the last prefillFromDocument() compared with building, one
+ line: per store, values that agreed, differed (and in which fields),
+ and were not placed
+*/
+QString projectDataBase::prefillReport() const
+{
+ static const char *names[] = {"symbols", "folios", "wires"};
+ QStringList parts;
+ for (int i = 0 ; i < 3 ; ++i) {
+ const PrefillTally &t = m_prefill_tally[i];
+ QStringList fields;
+ for (auto it = t.fields.constBegin() ; it != t.fields.constEnd() ; ++it)
+ fields << QStringLiteral("%1 %2").arg(it.key()).arg(it.value());
+ parts << QStringLiteral("%1 %2 agree, %3 differ%4, %5 not placed")
+ .arg(QLatin1String(names[i])).arg(t.agree).arg(t.differ)
+ .arg(fields.isEmpty() ? QString() : QStringLiteral(" (") + fields.join(QStringLiteral(", ")) + QLatin1Char(')'))
+ .arg(t.unplaced);
+ }
+ return parts.join(QStringLiteral("; "));
+}
+
/**
@brief projectDataBase::populateLinkTable
Populate the link table from the built folios
@@ -2210,17 +3324,30 @@ void projectDataBase::populateLinkTable()
query.prepare(QStringLiteral("INSERT INTO link (element_uuid, linked_uuid, group_index) "
"VALUES (:element_uuid, :linked_uuid, :group_index)"));
- for (const auto &diagram : m_project->diagrams())
+ for (const auto &diagram : m_project->folios())
{
+ //A folio not built: its rows, but to a partner since removed
+ if (m_kept_folios.contains(diagram)) {
+ restoreKeptRows(QStringLiteral("link"), diagram,
+ QStringLiteral("element_uuid IN (SELECT uuid FROM kept_element "
+ "WHERE diagram_uuid = :folio) "
+ "AND linked_uuid IN (SELECT uuid FROM element)"));
+ continue;
+ }
const ElementProvider ep(diagram);
for (const auto &elmt : ep.find(allElementTypes()))
{
+ QList> rows;
for (Element *linked : elmt->linkedElements())
+ rows.append({linked->uuid().toString(), elmt->groupIndexForElement(linked)});
+ //Partners on a folio not built, which it will be linked to
+ for (const auto &waiting : elmt->waitingLinks())
+ rows.append({waiting.first.toString(), waiting.second});
+ for (const auto &row : std::as_const(rows))
{
- const int group = elmt->groupIndexForElement(linked);
query.bindValue(QStringLiteral(":element_uuid"), elmt->uuid().toString());
- query.bindValue(QStringLiteral(":linked_uuid"), linked->uuid().toString());
- query.bindValue(QStringLiteral(":group_index"), group >= 0 ? QVariant(group) : QVariant());
+ query.bindValue(QStringLiteral(":linked_uuid"), row.first);
+ query.bindValue(QStringLiteral(":group_index"), row.second >= 0 ? QVariant(row.second) : QVariant());
if (!query.exec()) {
qDebug() << "projectDataBase::populateLinkTable insert error : " << query.lastError();
}
@@ -2235,9 +3362,17 @@ void projectDataBase::populateLinkTable()
by the next flushLinks().
*/
void projectDataBase::linksChanged()
+{
+ queueLinks(qobject_cast(sender()));
+}
+
+/**
+ @brief projectDataBase::queueLinks
+ The link rows of @p element are written again by the next flushLinks().
+*/
+void projectDataBase::queueLinks(Element *element)
{
m_content_changed = true;
- auto *element = qobject_cast(sender());
if (element && !m_dirty_link_elements.contains(element)) {
m_dirty_link_elements << element;
}
@@ -2271,9 +3406,18 @@ void projectDataBase::flushLinks()
qDebug() << "projectDataBase::flushLinks remove error : " << remove.lastError();
}
//An element taken off its folio is unlinked first (Diagram::
- //removeItem()), so it writes no rows here.
+ //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)) {
+ continue;
+ }
for (Element *linked : element->linkedElements())
{
+ //A symbol on a removed folio stays linked, for the undo,
+ //but the project no longer has it
+ if (!m_placed_elements.contains(linked->uuid(), linked)) {
+ continue;
+ }
const int group = element->groupIndexForElement(linked);
insert.bindValue(QStringLiteral(":element_uuid"), element->uuid().toString());
insert.bindValue(QStringLiteral(":linked_uuid"), linked->uuid().toString());
@@ -2282,6 +3426,17 @@ void projectDataBase::flushLinks()
qDebug() << "projectDataBase::flushLinks insert error : " << insert.lastError();
}
}
+ //Partners on a folio not built (QET_LAZY_FOLIOS), which it will
+ //be linked to once that folio is
+ for (const auto &waiting : element->waitingLinks())
+ {
+ insert.bindValue(QStringLiteral(":element_uuid"), element->uuid().toString());
+ insert.bindValue(QStringLiteral(":linked_uuid"), waiting.first.toString());
+ insert.bindValue(QStringLiteral(":group_index"), waiting.second >= 0 ? QVariant(waiting.second) : QVariant());
+ if (!insert.exec()) {
+ qDebug() << "projectDataBase::flushLinks insert error : " << insert.lastError();
+ }
+ }
}
if (own_transaction) {
m_data_base.commit();
@@ -2293,7 +3448,8 @@ void projectDataBase::populateDiagramInfoTable()
QSqlQuery query(m_data_base);
query.exec("DELETE FROM diagram_info");
- for (auto *diagram : m_project->diagrams())
+ //The folios' own data: folios() builds nothing
+ for (auto *diagram : m_project->folios())
{
bindDiagramInfoValues(m_insert_diagram_info_query, diagram);
@@ -2315,8 +3471,15 @@ void projectDataBase::populateConductorTable()
query.exec(QStringLiteral("DELETE FROM conductor"));
query.exec(QStringLiteral("DELETE FROM terminal"));
- for (auto *diagram : m_project->diagrams())
+ for (auto *diagram : m_project->folios())
{
+ if (m_kept_folios.contains(diagram)) {
+ restoreKeptRows(QStringLiteral("terminal"), diagram,
+ QStringLiteral("element_uuid IN (SELECT uuid FROM kept_element "
+ "WHERE diagram_uuid = :folio)"));
+ restoreKeptRows(QStringLiteral("conductor"), diagram, QStringLiteral("diagram_uuid = :folio"));
+ continue;
+ }
const auto conductor_list = diagram->conductors();
for (auto *conductor : conductor_list)
{
@@ -2401,6 +3564,20 @@ void projectDataBase::prepareQuery()
m_cascade_remove_element_query.prepare(
"DELETE FROM element WHERE diagram_uuid = :uuid");
+ //The symbol information store and an element's link rows
+ m_store_remove_query = QSqlQuery(m_data_base);
+ m_store_remove_query.prepare(QStringLiteral("DELETE FROM element_information WHERE element_uuid = :uuid"));
+ m_store_insert_query = QSqlQuery(m_data_base);
+ m_store_insert_query.prepare(QStringLiteral("INSERT INTO element_information (element_uuid, ord, name, value, show) "
+ "VALUES (:uuid, :ord, :name, :value, :show)"));
+ m_conductor_properties_insert_query = QSqlQuery(m_data_base);
+ m_conductor_properties_insert_query.prepare(QStringLiteral("INSERT OR REPLACE INTO conductor_properties (conductor_uuid, name, value) "
+ "VALUES (:uuid, :name, :value)"));
+ m_conductor_property_remove_query = QSqlQuery(m_data_base);
+ m_conductor_property_remove_query.prepare(QStringLiteral("DELETE FROM conductor_properties WHERE conductor_uuid = :uuid AND name = :name"));
+ m_remove_links_query = QSqlQuery(m_data_base);
+ m_remove_links_query.prepare(QStringLiteral("DELETE FROM link WHERE element_uuid = :uuid OR linked_uuid = :uuid"));
+
m_remove_diagram_query = QSqlQuery(m_data_base);
m_remove_diagram_query.prepare("DELETE FROM diagram WHERE uuid=:uuid");
@@ -2689,9 +3866,13 @@ void projectDataBase::exportDb(projectDataBase *db,
// VACUUM INTO creates a standalone copy of the current database without
// requiring access to the SQLite driver's native connection handle.
const auto escaped_path = path_.replace("'", "''");
+ if (db->m_project) {
+ db->m_project->loadFolios(); //the drawing tables need built folios
+ }
db->flushDrawingItems();
db->flushLinks();
db->flushElementPositions();
+ db->flushConductorProperties();
QSqlQuery query(db->m_data_base);
if (!query.exec("VACUUM INTO '" % escaped_path % "'")) {
qWarning() << "Unable to export project database:" << query.lastError().text();
diff --git a/sources/dataBase/projectdatabase.h b/sources/dataBase/projectdatabase.h
index 9f3779c7c..0dd92aaa1 100644
--- a/sources/dataBase/projectdatabase.h
+++ b/sources/dataBase/projectdatabase.h
@@ -24,9 +24,15 @@
#include
#include
#include
+#include
+#include
#include
#include
+#include "../diagramcontext.h"
+#include "../titleblockproperties.h"
+#include "../conductorproperties.h"
+
class Element;
class QETProject;
class Diagram;
@@ -98,6 +104,62 @@ class projectDataBase : public QObject
QSqlDatabase database() const {return m_data_base;}
int excludedConductorCount() const;
+ //The information of every symbol placed in the project, by
+ //symbol uuid: the store each symbol's information is written
+ //through (DB-ACCESSORS-PLAN.md stage 4.1). A symbol not placed
+ //(a preview, the symbol editor) keeps its own.
+ DiagramContext elementInformation(const QUuid &element) const;
+ bool hasElementInformation(const QUuid &element) const;
+ //How many placed symbols hold @p element: more than one in a
+ //file whose symbols were copied by an old version (F100), or
+ //for a moment while a copy's uuid is renewed
+ int placedElementCount(const QUuid &element) const;
+ bool storeElementInformation(const QUuid &element, const DiagramContext &information);
+ void elementInformationChanged(Element *element, const DiagramContext &information);
+ void forgetElementInformation(const QUuid &element);
+ void elementUuidChanged(Element *element, const QUuid &old_uuid);
+ //Placed symbols whose information differs from the store's,
+ //each as "uuid: why"; empty when they all agree
+ QStringList elementInformationMismatches() const;
+
+ //The title block properties of every folio of the project, by
+ //folio uuid: the store each folio's title block is written
+ //through (DB-ACCESSORS-PLAN.md stage 4.4). A folio not in a
+ //project keeps its own.
+ TitleBlockProperties folioTitleBlock(const QUuid &folio) const;
+ const TitleBlockProperties *storedFolioTitleBlock(const QUuid &folio) const;
+ Diagram *placedFolio(const QUuid &folio) const;
+ void storeFolioTitleBlock(const QUuid &folio, const TitleBlockProperties &properties);
+ void placeFolio(Diagram *folio);
+ void folioUuidChanged(Diagram *folio, const QUuid &old_uuid);
+ QStringList folioTitleBlockMismatches() const;
+
+ //The properties of every wire placed in the project, by wire
+ //uuid: the store each wire's properties are written through
+ //(DB-ACCESSORS-PLAN.md stage 4.5). A wire not placed keeps its own.
+ ConductorProperties conductorProperties(const QUuid &conductor) const;
+ bool hasConductorProperties(const QUuid &conductor) const;
+ int placedConductorCount(const QUuid &conductor) const;
+ void conductorPropertiesStored(Conductor *conductor, const ConductorProperties &properties);
+ void conductorUuidChanged(Conductor *conductor, const QUuid &old_uuid);
+ QStringList conductorPropertiesMismatches() const;
+
+ //Fill the three stores from a project's document before its
+ //folios are built (DB-ACCESSORS-PLAN.md stage 4.6), then, once
+ //they are, say how many of those values building agreed with.
+ void prefillFromDocument(const QDomDocument &document);
+ void endPrefill(bool keep_unplaced = false);
+ //A folio of a project opened with QET_LAZY_FOLIOS is being built:
+ //its rows came from the document already, so none are written
+ //and the database is not marked changed
+ void setBuildingFolio(bool building);
+ bool isBuildingFolio() const {return m_building_folios > 0;}
+ bool rowsFromDocument() const {return m_rows_from_document;}
+ QString prefillReport() const;
+ QSet foliosWithUnjoinedWires() const;
+ QSet folioUuidsOfElements(const QSet &elements) const;
+ QHash elementTypesOnFolios(const QSet &folios) const;
+
void addElement (Element *element);
void removeElement (Element *element);
void elementInfoChanged (Element *element);
@@ -145,6 +207,7 @@ class projectDataBase : public QObject
void flushElementPositions();
void populateElementTable();
void populateElementInfoTable();
+ void populateElementInfoTableFromFolios();
void populateDiagramInfoTable();
void populateConductorTable();
void populateLinkTable();
@@ -203,7 +266,12 @@ class projectDataBase : public QObject
m_cascade_remove_element_query,
m_insert_shape_query,
m_insert_independent_text_query,
- m_insert_image_query;
+ m_insert_image_query,
+ m_store_remove_query,
+ m_store_insert_query,
+ m_remove_links_query,
+ m_conductor_properties_insert_query,
+ m_conductor_property_remove_query;
//Which uuid's row each drawing item last wrote, and which item
//wrote each row. A pasted copy is added to the folio still
@@ -222,6 +290,51 @@ class projectDataBase : public QObject
//sends xChanged and yChanged for every step of a drag, so the
//cell is written when next read, as drawing items are.
QList> m_moved_elements;
+ QHash m_element_information;
+ //The placed symbols holding each uuid: a paste or a folio copy
+ //briefly gives a copy its original's uuid, so one uuid can have
+ //two symbols until the copy's is renewed.
+ QMultiHash> m_placed_elements;
+ QHash m_folio_titleblocks;
+ QHash> m_placed_folios;
+ QHash m_conductor_properties;
+ //The rows of conductor_properties, to write only what changes
+ QHash> m_conductor_attributes;
+ QSet m_dirty_conductor_properties;
+ int m_building_folios = 0;
+ QSet m_folios_with_unjoined_wires;
+ //The folios whose rows a rebuild keeps (keepUnbuiltFolioRows())
+ QSet m_kept_folios;
+ //The tables' rows were filled from the document: those of a
+ //folio not built yet are right (QET_LAZY_FOLIOS)
+ bool m_rows_from_document = false;
+ void keepUnbuiltFolioRows();
+ void restoreKeptRows(const QString &table, Diagram *folio, const QString &where);
+ void dropKeptRows();
+ bool m_changed_before_folio = false, m_blocked_before_folio = false;
+ QHash m_prefilled_elements;
+ QHash m_prefilled_folios;
+ QHash m_prefilled_conductors;
+ struct PrefillTally {
+ int agree = 0, differ = 0, unplaced = 0;
+ QMap fields;
+ };
+ PrefillTally m_prefill_tally[3];
+ void flushConductorProperties();
+ QMultiHash> m_placed_conductors;
+ bool storeConductorProperties(const QUuid &conductor, const ConductorProperties &properties);
+ void placeConductor(Conductor *conductor);
+ void unplaceConductor(Conductor *conductor, const QUuid &uuid);
+ Conductor *otherConductorHolder(const QUuid &uuid, const Conductor *conductor) const;
+ void writeConductorRow(Conductor *conductor);
+ void removeConductorRow(const QUuid &uuid);
+ void unplaceFolio(Diagram *folio, const QUuid &uuid);
+ void placeElement(Element *element);
+ Element *otherHolder(const QUuid &uuid, const Element *element) const;
+ void writeElementRows(Element *element);
+ void queueLinks(Element *element);
+ void removeElementRows(const QUuid &uuid);
+ void unplaceElement(Element *element, const QUuid &uuid);
#ifdef QET_EXPORT_PROJECT_DB
public:
diff --git a/sources/diagram.cpp b/sources/diagram.cpp
index a04d4b433..9586b9555 100644
--- a/sources/diagram.cpp
+++ b/sources/diagram.cpp
@@ -16,6 +16,7 @@
along with QElectroTech. If not, see .
*/
#include "diagram.h"
+#include "dataBase/projectdatabase.h"
#include "autoNum/elementautonumschemecommand.h"
#include "ElementsCollection/elementcollectionhandler.h"
@@ -193,6 +194,7 @@ Diagram::Diagram(QETProject *project) :
* http://tech-artists.org/t/qt-properly-removing-qgraphicitems/3063
*/
+ border_and_titleblock.setOwnerFolio(this);
qgi_manager_ = new QGIManager(this);
setBackgroundBrush(Qt::white);
conductor_setter_ = new QGraphicsLineItem(nullptr);
@@ -767,6 +769,49 @@ void Diagram::keyReleaseEvent(QKeyEvent *e)
}
}
+/**
+ @brief Diagram::ensureLoaded
+ Build this folio's items from the XML kept when the project was
+ opened with QET_LAZY_FOLIOS, and do for them what opening does for
+ every folio's (QETProject::folioLoaded()). Nothing for a folio built
+ already.
+*/
+void Diagram::ensureLoaded()
+{
+ if (m_deferred.isNull()) {
+ return;
+ }
+ if (m_project) {
+ m_project->buildFolios({this});
+ } else {
+ buildDeferred();
+ }
+}
+
+/**
+ @brief Diagram::buildDeferred
+ Build this folio's items from the XML kept for later, and nothing else.
+ Their rows are in the project database already, filled from the same
+ XML: building writes none.
+ @return true if there was something to build
+*/
+bool Diagram::buildDeferred()
+{
+ if (m_deferred.isNull()) {
+ return false;
+ }
+ QDomElement xml = m_deferred;
+ m_deferred = QDomElement();
+ m_kept_text.clear();
+ projectDataBase *db = m_project ? m_project->dataBase() : nullptr;
+ if (db) db->setBuildingFolio(true);
+ m_loading_deferred = true;
+ fromXml(xml, QPointF(), true, nullptr);
+ m_loading_deferred = false;
+ if (db) db->setBuildingFolio(false);
+ return true;
+}
+
/**
@brief Diagram::uuid
@return the uuid of this diagram
@@ -801,7 +846,7 @@ bool Diagram::uuidUsedByOtherDiagram(const QUuid &uuid) const
if (!m_project) {
return false;
}
- const auto diagrams = m_project->diagrams();
+ const auto diagrams = m_project->folios(); //uuids only: no need to build them
for (const Diagram *diagram : diagrams) {
if (diagram != this && diagram->m_uuid == uuid) {
return true;
@@ -838,7 +883,7 @@ QUuid Diagram::derivedUuid(const QDomElement &root, const QString &reason) const
QStringLiteral("{d5951240-154d-44d6-8277-0092a31d1920}"));
const int index = m_project
- ? m_project->diagrams().indexOf(const_cast(this))
+ ? m_project->folios().indexOf(const_cast(this))
: -1;
const QString project_title = root.ownerDocument()
.documentElement()
@@ -1088,6 +1133,10 @@ QList < QSet > Diagram::potentials()
\~French Un Document XML (QDomDocument)
*/
QDomDocument Diagram::toXml(bool whole_content, bool is_copy_command) {
+ //A folio of a project opened with QET_LAZY_FOLIOS is written from
+ //its built items (QETProject::toXml() may write one not built as
+ //it was read instead)
+ ensureLoaded();
// document
QDomDocument document;
@@ -1098,120 +1147,7 @@ QDomDocument Diagram::toXml(bool whole_content, bool is_copy_command) {
// schema properties
// proprietes du schema
if (whole_content) {
- //Persist the folio identity, so that a folio keeps the same uuid
- //across save/load. Without it every load invents a new one, and
- //nothing outside the running instance (version control, a lock,
- //a diff tool...) can tell which folio is which.
- dom_root.setAttribute(QStringLiteral("uuid"), m_uuid.toString());
- border_and_titleblock.titleBlockToXml(dom_root);
- border_and_titleblock.borderToXml(dom_root);
-
- // Default conductor properties
- QDomElement default_conductor =
- document.createElement(QStringLiteral("defaultconductor"));
- defaultConductorProperties.toXml(default_conductor);
- dom_root.appendChild(default_conductor);
-
- // Conductor autonum
- if (!m_conductors_autonum_name.isEmpty()) {
- dom_root.setAttribute(QStringLiteral("conductorAutonum"),
- m_conductors_autonum_name);
- }
-
- //Default New Element
- dom_root.setAttribute(QStringLiteral("freezeNewElement"),
- m_freeze_new_elements ? QStringLiteral("true") : QStringLiteral("false"));
-
- //Default New Conductor
- dom_root.setAttribute(QStringLiteral("freezeNewConductor"),
- m_freeze_new_conductors_
- ? QStringLiteral("true") : QStringLiteral("false"));
-
- //Element Folio Sequential Variables
- if (!m_elmt_unitfolio_max.isEmpty()
- || !m_elmt_tenfolio_max.isEmpty()
- || !m_elmt_hundredfolio_max.isEmpty()) {
- QDomElement elmtfoliosequential =
- document.createElement(
- QStringLiteral("elementautonumfoliosequentials"));
- if (!m_elmt_unitfolio_max.isEmpty()) {
- QDomElement elmtfolioseq =
- document.createElement(
- QStringLiteral("elementunitfolioseq"));
- folioSequentialsToXml(&m_elmt_unitfolio_max,
- &elmtfolioseq,
- QStringLiteral("sequf_"),
- QStringLiteral("unitfolioseq"),
- &document);
- elmtfoliosequential.appendChild(elmtfolioseq);
- }
- if (!m_elmt_tenfolio_max.isEmpty()) {
- QDomElement elmtfolioseq =
- document.createElement(
- QStringLiteral("elementtenfolioseq"));
- folioSequentialsToXml(&m_elmt_tenfolio_max,
- &elmtfolioseq,
- QStringLiteral("seqtf_"),
- QStringLiteral("tenfolioseq"),
- &document);
- elmtfoliosequential.appendChild(elmtfolioseq);
- }
- if (!m_elmt_hundredfolio_max.isEmpty()) {
- QDomElement elmtfolioseq =
- document.createElement(
- QStringLiteral("elementhundredfolioseq"));
- folioSequentialsToXml(&m_elmt_hundredfolio_max,
- &elmtfolioseq,
- QStringLiteral("seqhf_"),
- QStringLiteral("hundredfolioseq"),
- &document);
- elmtfoliosequential.appendChild(elmtfolioseq);
- }
- dom_root.appendChild(elmtfoliosequential);
- }
- // Conductor Folio Sequential Variables
- if (!m_cnd_unitfolio_max.isEmpty()
- || !m_cnd_tenfolio_max.isEmpty() ||
- !m_cnd_hundredfolio_max.isEmpty()) {
- QDomElement cndfoliosequential =
- document.createElement(
- QStringLiteral("conductorautonumfoliosequentials"));
- QHash::iterator i;
- if (!m_cnd_unitfolio_max.isEmpty()) {
- QDomElement cndfolioseq =
- document.createElement(
- QStringLiteral("conductorunitfolioseq"));
- folioSequentialsToXml(&m_cnd_unitfolio_max,
- &cndfolioseq,
- QStringLiteral("sequf_"),
- QStringLiteral("unitfolioseq"),
- &document);
- cndfoliosequential.appendChild(cndfolioseq);
- }
- if (!m_cnd_tenfolio_max.isEmpty()) {
- QDomElement cndfolioseq =
- document.createElement(
- QStringLiteral("conductortenfolioseq"));
- folioSequentialsToXml(&m_cnd_tenfolio_max,
- &cndfolioseq,
- QStringLiteral("seqtf_"),
- QStringLiteral("tenfolioseq"),
- &document);
- cndfoliosequential.appendChild(cndfolioseq);
- }
- if (!m_cnd_hundredfolio_max.isEmpty()) {
- QDomElement cndfolioseq =
- document.createElement(
- QStringLiteral("conductorhundredfolioseq"));
- folioSequentialsToXml(&m_cnd_hundredfolio_max,
- &cndfolioseq,
- QStringLiteral("seqhf_"),
- QStringLiteral("hundredfolioseq"),
- &document);
- cndfoliosequential.appendChild(cndfolioseq);
- }
- dom_root.appendChild(cndfoliosequential);
- }
+ writeHeader(document, dom_root);
}
else {
//this method with whole_content to false,
@@ -1307,8 +1243,15 @@ QDomDocument Diagram::toXml(bool whole_content, bool is_copy_command) {
// since those ids are assigned sequentially in element order.
std::stable_sort(list_elements.begin(), list_elements.end(),
[](Element *a, Element *b) { return elementSortKey(a) < elementSortKey(b); });
+ //Wires that tie on their ends (common: several from one terminal)
+ //keep the order they were made in, which for a project's is its
+ //file's -- the order the scene gave them so far, except for a folio
+ //built late (QET_LAZY_FOLIOS), whose scene order differed
std::stable_sort(list_conductors.begin(), list_conductors.end(),
- [](Conductor *a, Conductor *b) { return conductorSortKey(a) < conductorSortKey(b); });
+ [](Conductor *a, Conductor *b) {
+ const QString ka = conductorSortKey(a), kb = conductorSortKey(b);
+ return ka != kb ? ka < kb : a->creationRank() < b->creationRank();
+ });
// A copy carries the numberings its elements follow: pasted into
// another project, which does not know them, it can offer to import
@@ -1375,6 +1318,143 @@ QDomDocument Diagram::toXml(bool whole_content, bool is_copy_command) {
return(document);
}
+/**
+ @brief Diagram::writeHeader
+ Write this folio's own data -- its uuid, title block, border, default
+ wire, numbering -- into @p dom_root, the folio's element of @p document:
+ what toXml() writes before the folio's items.
+*/
+void Diagram::writeHeader(QDomDocument &document, QDomElement &dom_root)
+{
+ //Persist the folio identity, so that a folio keeps the same uuid
+ //across save/load. Without it every load invents a new one, and
+ //nothing outside the running instance (version control, a lock,
+ //a diff tool...) can tell which folio is which.
+ dom_root.setAttribute(QStringLiteral("uuid"), m_uuid.toString());
+ border_and_titleblock.titleBlockToXml(dom_root);
+ border_and_titleblock.borderToXml(dom_root);
+
+ // Default conductor properties
+ QDomElement default_conductor =
+ document.createElement(QStringLiteral("defaultconductor"));
+ defaultConductorProperties.toXml(default_conductor);
+ dom_root.appendChild(default_conductor);
+
+ // Conductor autonum
+ if (!m_conductors_autonum_name.isEmpty()) {
+ dom_root.setAttribute(QStringLiteral("conductorAutonum"),
+ m_conductors_autonum_name);
+ }
+
+ //Default New Element
+ dom_root.setAttribute(QStringLiteral("freezeNewElement"),
+ m_freeze_new_elements ? QStringLiteral("true") : QStringLiteral("false"));
+
+ //Default New Conductor
+ dom_root.setAttribute(QStringLiteral("freezeNewConductor"),
+ m_freeze_new_conductors_
+ ? QStringLiteral("true") : QStringLiteral("false"));
+
+ //Element Folio Sequential Variables
+ if (!m_elmt_unitfolio_max.isEmpty()
+ || !m_elmt_tenfolio_max.isEmpty()
+ || !m_elmt_hundredfolio_max.isEmpty()) {
+ QDomElement elmtfoliosequential =
+ document.createElement(
+ QStringLiteral("elementautonumfoliosequentials"));
+ if (!m_elmt_unitfolio_max.isEmpty()) {
+ QDomElement elmtfolioseq =
+ document.createElement(
+ QStringLiteral("elementunitfolioseq"));
+ folioSequentialsToXml(&m_elmt_unitfolio_max,
+ &elmtfolioseq,
+ QStringLiteral("sequf_"),
+ QStringLiteral("unitfolioseq"),
+ &document);
+ elmtfoliosequential.appendChild(elmtfolioseq);
+ }
+ if (!m_elmt_tenfolio_max.isEmpty()) {
+ QDomElement elmtfolioseq =
+ document.createElement(
+ QStringLiteral("elementtenfolioseq"));
+ folioSequentialsToXml(&m_elmt_tenfolio_max,
+ &elmtfolioseq,
+ QStringLiteral("seqtf_"),
+ QStringLiteral("tenfolioseq"),
+ &document);
+ elmtfoliosequential.appendChild(elmtfolioseq);
+ }
+ if (!m_elmt_hundredfolio_max.isEmpty()) {
+ QDomElement elmtfolioseq =
+ document.createElement(
+ QStringLiteral("elementhundredfolioseq"));
+ folioSequentialsToXml(&m_elmt_hundredfolio_max,
+ &elmtfolioseq,
+ QStringLiteral("seqhf_"),
+ QStringLiteral("hundredfolioseq"),
+ &document);
+ elmtfoliosequential.appendChild(elmtfolioseq);
+ }
+ dom_root.appendChild(elmtfoliosequential);
+ }
+ // Conductor Folio Sequential Variables
+ if (!m_cnd_unitfolio_max.isEmpty()
+ || !m_cnd_tenfolio_max.isEmpty() ||
+ !m_cnd_hundredfolio_max.isEmpty()) {
+ QDomElement cndfoliosequential =
+ document.createElement(
+ QStringLiteral("conductorautonumfoliosequentials"));
+ QHash::iterator i;
+ if (!m_cnd_unitfolio_max.isEmpty()) {
+ QDomElement cndfolioseq =
+ document.createElement(
+ QStringLiteral("conductorunitfolioseq"));
+ folioSequentialsToXml(&m_cnd_unitfolio_max,
+ &cndfolioseq,
+ QStringLiteral("sequf_"),
+ QStringLiteral("unitfolioseq"),
+ &document);
+ cndfoliosequential.appendChild(cndfolioseq);
+ }
+ if (!m_cnd_tenfolio_max.isEmpty()) {
+ QDomElement cndfolioseq =
+ document.createElement(
+ QStringLiteral("conductortenfolioseq"));
+ folioSequentialsToXml(&m_cnd_tenfolio_max,
+ &cndfolioseq,
+ QStringLiteral("seqtf_"),
+ QStringLiteral("tenfolioseq"),
+ &document);
+ cndfoliosequential.appendChild(cndfolioseq);
+ }
+ if (!m_cnd_hundredfolio_max.isEmpty()) {
+ QDomElement cndfolioseq =
+ document.createElement(
+ QStringLiteral("conductorhundredfolioseq"));
+ folioSequentialsToXml(&m_cnd_hundredfolio_max,
+ &cndfolioseq,
+ QStringLiteral("seqhf_"),
+ QStringLiteral("hundredfolioseq"),
+ &document);
+ cndfoliosequential.appendChild(cndfolioseq);
+ }
+ dom_root.appendChild(cndfoliosequential);
+ }
+}
+
+/**
+ @brief Diagram::headerXml
+ @return the folio's own data as writeHeader() writes it, as text
+*/
+QString Diagram::headerXml()
+{
+ QDomDocument document;
+ auto dom_root = document.createElement(QStringLiteral("diagram"));
+ document.appendChild(dom_root);
+ writeHeader(document, dom_root);
+ return document.toString();
+}
+
/**
@brief Diagram::folioSequentialsToXml
Add folio sequential to QDomElement
@@ -1692,8 +1772,9 @@ bool Diagram::fromXml(QDomElement &document,
return(false);
}
- // Read attributes of this diagram
- if (consider_informations)
+ // Read attributes of this diagram (already read for a folio whose
+ // items are built later, see ensureLoaded())
+ if (consider_informations && !m_loading_deferred)
{
// Restore the persisted folio uuid. Done first, before any item is
// loaded, so that everything created below sees the final uuid.
@@ -1701,6 +1782,7 @@ bool Diagram::fromXml(QDomElement &document,
// persisted, or uuid already taken by another folio) gets a
// deterministic one instead, see derivedUuid().
const QUuid persisted_uuid(root.attribute(QStringLiteral("uuid")));
+ const QUuid old_uuid = m_uuid;
if (persisted_uuid.isNull()) {
m_uuid = derivedUuid(root, QStringLiteral("legacy"));
} else if (uuidUsedByOtherDiagram(persisted_uuid)) {
@@ -1709,6 +1791,9 @@ bool Diagram::fromXml(QDomElement &document,
} else {
m_uuid = persisted_uuid;
}
+ //A placed folio's stored title block follows its uuid
+ if (m_project && m_project->dataBase())
+ m_project->dataBase()->folioUuidChanged(this, old_uuid);
// Load border and titleblock
border_and_titleblock.titleBlockFromXml(root);
@@ -1776,6 +1861,16 @@ bool Diagram::fromXml(QDomElement &document,
return(true);
}
+ //A project opened with QET_LAZY_FOLIOS builds a folio's items when
+ //they are first needed: until then the folio has what it says
+ //about itself (uuid, title block, border, numbering) and keeps its
+ //XML, which belongs to the project's document.
+ if (consider_informations && !m_loading_deferred && m_project
+ && m_project->deferringFolios()) {
+ m_deferred = root;
+ return(true);
+ }
+
//If paste from another project
if (root.hasAttribute(QStringLiteral("projectId")))
{
diff --git a/sources/diagram.h b/sources/diagram.h
index c19cac53b..6b2e6c604 100644
--- a/sources/diagram.h
+++ b/sources/diagram.h
@@ -140,6 +140,11 @@ class Diagram : public QGraphicsScene
bool m_freeze_new_elements;
bool m_freeze_new_conductors_;
QUuid m_uuid = QUuid::createUuid();
+ /// The XML of this folio's items, while they are not built yet
+ QDomElement m_deferred;
+ QString m_opened_header;
+ QByteArray m_kept_text;
+ bool m_loading_deferred = false;
//Selection before the current click, see completeGroupSelection()
QList> m_previous_selection;
@@ -200,10 +205,26 @@ class Diagram : public QGraphicsScene
// methods related to XML import/export
QDomDocument toXml(bool wholeContent = true, bool is_copy_command = false);
+ void writeHeader(QDomDocument &document, QDomElement &dom_root);
bool initFromXml(QDomElement &,
QPointF = QPointF(),
bool = true,
DiagramContent * = nullptr);
+ /// false while this folio's items wait in its XML to be
+ /// built (QET_LAZY_FOLIOS, LAZY-FOLIO-PLAN.md)
+ bool isLoaded() const {return m_deferred.isNull();}
+ QDomElement deferredXml() const {return m_deferred;}
+ QString headerXml();
+ /// The folio's text in the file it was read from, while it is
+ /// not built (QETProject::toXml())
+ QByteArray keptText() const {return m_kept_text;}
+ void setKeptText(const QByteArray &text) {m_kept_text = text;}
+ /// Remember the folio's own data as it is now, for
+ /// headerChanged() (QETProject::toXml())
+ void rememberHeader() {m_opened_header = headerXml();}
+ bool headerChanged() {return headerXml() != m_opened_header;}
+ void ensureLoaded();
+ bool buildDeferred();
//The last argument is only for a copy fragment: when it is
//given, the cable lines that fragment carries are built as
//well and handed back through it -- they are not part of a
diff --git a/sources/diagramview.cpp b/sources/diagramview.cpp
index 548606aa0..5a1bc6590 100644
--- a/sources/diagramview.cpp
+++ b/sources/diagramview.cpp
@@ -1645,6 +1645,25 @@ void DiagramView::editDiagramProperties()
DiagramPropertiesDialog::diagramPropertiesDialog(m_diagram, diagramEditor());
}
+/**
+ @brief DiagramView::buildFolio
+ Build the items of the folio this view shows, and those of the folios
+ its cross-references need, if the project was opened without them
+ (QET_LAZY_FOLIOS); then fit the view to them.
+*/
+void DiagramView::buildFolio()
+{
+ if (QETProject *project = m_diagram->project()) {
+ if (!project->unloadedFolioCount()) {
+ return;
+ }
+ project->buildFolioToShow(m_diagram);
+ } else {
+ m_diagram->ensureLoaded();
+ }
+ adjustSceneRect();
+}
+
/**
@brief DiagramView::adjustSceneRect
Calcul and set the area of the scene visualized by this view
diff --git a/sources/diagramview.h b/sources/diagramview.h
index 4a3601ed7..2453e7b4b 100644
--- a/sources/diagramview.h
+++ b/sources/diagramview.h
@@ -207,6 +207,7 @@ class DiagramView : public PaletteGraphicsView
void paste(const QPointF & = QPointF(), QClipboard::Mode = QClipboard::Clipboard);
void pasteHere();
void duplicate(const QPoint &stepOffset);
+ void buildFolio();
void adjustSceneRect();
void updateWindowTitle();
void resetConductors();
diff --git a/sources/elementprovider.cpp b/sources/elementprovider.cpp
index 391bedc3a..a493fd7fe 100644
--- a/sources/elementprovider.cpp
+++ b/sources/elementprovider.cpp
@@ -30,11 +30,10 @@
@param prj the project where we must find element
@param diagram the diagram to exclude from the search
*/
-ElementProvider::ElementProvider(QETProject *prj, Diagram *diagram)
-{
- m_diagram_list = prj->diagrams();
- m_diagram_list.removeOne(diagram);
-}
+ElementProvider::ElementProvider(QETProject *prj, Diagram *diagram) :
+ m_project(prj),
+ m_excluded(diagram)
+{}
/**
@brief ElementProvider::ElementProvider Constructor
@@ -44,6 +43,46 @@ ElementProvider::ElementProvider(Diagram *diag) {
m_diagram_list << diag;
}
+/**
+ @brief ElementProvider::ElementProvider Constructor
+ @param diagrams the diagrams to search, e.g. QETProject::builtFolios()
+*/
+ElementProvider::ElementProvider(const QList &diagrams) :
+ m_diagram_list(diagrams)
+{}
+
+/**
+ @brief ElementProvider::folios
+ @return the folios to search for symbols of the kinds @p kinds (free
+ ones only if @p free_only). For a project, the folios not built yet
+ (QET_LAZY_FOLIOS) holding such a symbol are built first, and only them
+ (QETProject::buildFoliosHolding()).
+*/
+QList ElementProvider::folios(ElementData::Types kinds, bool free_only) const
+{
+ if (!m_project) {
+ return m_diagram_list;
+ }
+ m_project->buildFoliosHolding(kinds, free_only);
+ QList list = m_project->builtFolios();
+ list.removeOne(m_excluded);
+ return list;
+}
+
+/**
+ @brief ElementProvider::allFolios
+ @return the folios to search for anything else: every one, built
+*/
+QList ElementProvider::allFolios() const
+{
+ if (!m_project) {
+ return m_diagram_list;
+ }
+ QList list = m_project->diagrams();
+ list.removeOne(m_excluded);
+ return list;
+}
+
/**
@brief ElementProvider::FreeElement
Search and return the asked element corresponding with the given filter
@@ -57,16 +96,22 @@ QVector > ElementProvider::freeElement(ElementData::Types filt
{
QVector> free_elmt;
QList elmt_list;
+ const QList searched = folios(filter, true);
+ //A symbol whose saved partner is on a folio not built yet
+ //(QET_LAZY_FOLIOS) is not linked yet, but is not free: it will be
+ const QSet waiting = m_project ? m_project->symbolsOnUnbuiltFolios()
+ : QSet();
//search in all diagram
- for (const auto &diagram_ : std::as_const(m_diagram_list))
+ for (const auto &diagram_ : std::as_const(searched))
{
//get all element in diagram d
elmt_list = diagram_->elements();
for (const auto &elmt_ : std::as_const(elmt_list))
{
if (filter & elmt_->elementData().m_type &&
- elmt_->isFree())
+ elmt_->isFree() &&
+ !elmt_->pendingLinks().intersects(waiting))
{
free_elmt << elmt_;
}
@@ -86,7 +131,7 @@ QList ElementProvider::fromUuids(QList uuid_list) const
{
QList found_element;
- foreach (Diagram *d, m_diagram_list) {
+ foreach (Diagram *d, allFolios()) {
foreach(Element *elmt, d->elements()) {
if (uuid_list.contains(elmt->uuid())) {
found_element << elmt;
@@ -106,7 +151,8 @@ QList ElementProvider::fromUuids(QList uuid_list) const
QVector> ElementProvider::find(ElementData::Types elmt_type) const
{
QVector> returned_vector;
- for (const auto &diagram_ : std::as_const(m_diagram_list))
+ const QList searched = folios(elmt_type, false);
+ for (const auto &diagram_ : std::as_const(searched))
{
const auto elmt_list = diagram_->elements();
for (const auto &elmt_ : elmt_list)
@@ -153,7 +199,7 @@ QVector ElementProvider::table(
}
}
- for (auto d : m_diagram_list) {
+ for (auto d : allFolios()) {
for (auto item_ : d->items())
{
if(item_->type() == QetGraphicsTableItem::Type)
@@ -197,8 +243,9 @@ QetGraphicsTableItem *ElementProvider::tableFromUuid(const QUuid &uuid)
QVector ElementProvider::freeTerminal() const
{
QVector vector_;
+ const QList searched = folios(ElementData::Terminal, false);
- for (const auto &diagram : std::as_const(m_diagram_list))
+ for (const auto &diagram : std::as_const(searched))
{
const auto elmt_list{diagram->elements()};
diff --git a/sources/elementprovider.h b/sources/elementprovider.h
index 2660b1cde..5f839c38c 100644
--- a/sources/elementprovider.h
+++ b/sources/elementprovider.h
@@ -41,6 +41,7 @@ class ElementProvider
public:
ElementProvider(QETProject *prj, Diagram *diagram=nullptr);
ElementProvider(Diagram *diag);
+ explicit ElementProvider(const QList &diagrams);
QVector > freeElement(ElementData::Types filter) const;
QList fromUuids(QList ) const;
QVector > find(ElementData::Types elmt_type) const;
@@ -49,7 +50,15 @@ class ElementProvider
QVector freeTerminal() const;
private:
+ QList folios(ElementData::Types kinds, bool free_only) const;
+ QList allFolios() const;
+
QList m_diagram_list;
+ //Searching a project: its folios are asked for when searched,
+ //so that a project opened with QET_LAZY_FOLIOS builds only
+ //those a search can find something on
+ QETProject *m_project = nullptr;
+ Diagram *m_excluded = nullptr;
};
#endif // ELEMENTPROVIDER_H
diff --git a/sources/elementspanelwidget.cpp b/sources/elementspanelwidget.cpp
index 3b44d6380..fb5e5e7fa 100644
--- a/sources/elementspanelwidget.cpp
+++ b/sources/elementspanelwidget.cpp
@@ -473,14 +473,16 @@ void ElementsPanelWidget::updateButtons()
if (!selected_diagrams.isEmpty()) {
QETProject *selected_diagram_project = selected_diagrams.first() -> project();
bool is_writable = !(selected_diagram_project -> isReadOnly());
- int project_diagrams_count = selected_diagram_project -> diagrams().count();
+ //Positions only: folios() does not build the folios' items
+ const QList folios = selected_diagram_project -> folios();
+ int project_diagrams_count = folios.count();
// Find the highest (min) and lowest (max) index among the selection
int min_position = project_diagrams_count;
int max_position = -1;
for (Diagram *diagram : selected_diagrams) {
- int pos = selected_diagram_project -> diagrams().indexOf(diagram);
+ int pos = folios.indexOf(diagram);
if (pos < min_position) min_position = pos;
if (pos > max_position) max_position = pos;
}
diff --git a/sources/genericpanel.cpp b/sources/genericpanel.cpp
index fe4b44b6a..a11d43ac6 100644
--- a/sources/genericpanel.cpp
+++ b/sources/genericpanel.cpp
@@ -253,11 +253,13 @@ QTreeWidgetItem *GenericPanel::fillProjectItem(QTreeWidgetItem *project_qtwi,
this,&GenericPanel::projectDiagramsOrderChanged);
} else {
// remove diagrams unknown to the project (presumably removed)
- removeObsoleteItems(project -> diagrams(),
+ removeObsoleteItems(project -> folios(),
project_qtwi, QET::Diagram, false);
}
int index = 0;
- foreach (Diagram *diagram, project -> diagrams()) {
+ //A folio's entry shows its own data (title, folio label):
+ //folios(), which does not build the folios' items
+ foreach (Diagram *diagram, project -> folios()) {
QTreeWidgetItem *diagram_qtwi = addDiagram(diagram,
nullptr,
options);
diff --git a/sources/print/projectprintwindow.cpp b/sources/print/projectprintwindow.cpp
index 41912c368..e04ea1d44 100644
--- a/sources/print/projectprintwindow.cpp
+++ b/sources/print/projectprintwindow.cpp
@@ -208,7 +208,7 @@ void ProjectPrintWindow::requestPaint()
#endif
#endif
- if (!m_project->diagrams().count()) {
+ if (!m_project->folios().count()) {
return;
}
diff --git a/sources/projectview.cpp b/sources/projectview.cpp
index 81ad2a6f7..47bdc0719 100644
--- a/sources/projectview.cpp
+++ b/sources/projectview.cpp
@@ -959,7 +959,9 @@ void ProjectView::loadDiagrams()
{
if (!m_project) return;
- setDisplayFallbackWidget(m_project -> diagrams().isEmpty());
+ //The folios' items are built when their tab is first shown
+ //(tabChanged()), not here: a tab needs only the folio's own data
+ setDisplayFallbackWidget(m_project -> folios().isEmpty());
auto dialog = DialogWaiting::instance();
if(dialog)
@@ -968,7 +970,7 @@ void ProjectView::loadDiagrams()
"...
Creating sheet tabs :
"));
}
- for(auto diagram : m_project->diagrams())
+ for(auto diagram : m_project->folios())
{
if(dialog)
{
@@ -1202,6 +1204,12 @@ void ProjectView::rebuildDiagramsMap()
*/
void ProjectView::tabChanged(int tab_id)
{
+ //A folio opened without its items (QET_LAZY_FOLIOS) is built when
+ //its tab is first shown. Read the tab itself: the first tab is
+ //shown while loadDiagrams() is still filling m_diagram_ids
+ if (auto dv = qobject_cast(m_tab->widget(tab_id)))
+ dv->buildFolio();
+
if (tab_id == -1)
setDisplayFallbackWidget(true);
else if(m_tab->count() == 1)
diff --git a/sources/qet.cpp b/sources/qet.cpp
index daeef58b4..0f7a1c3af 100644
--- a/sources/qet.cpp
+++ b/sources/qet.cpp
@@ -770,6 +770,15 @@ bool QET::compareCanonicalFilePaths(const QString &first, const QString &second)
@return false if an error occurred, true otherwise
*/
bool QET::writeXmlFile(QDomDocument &xml_doc, const QString &filepath, QString *error_message)
+{
+ return writeXmlText(xml_doc.toString(4), filepath, error_message);
+}
+
+/**
+ @brief QET::writeXmlText
+ writeXmlFile() for a document already written as @p text
+*/
+bool QET::writeXmlText(const QString &text, const QString &filepath, QString *error_message)
{
QSaveFile file(filepath);
@@ -789,7 +798,7 @@ bool QET::writeXmlFile(QDomDocument &xml_doc, const QString &filepath, QString *
QTextStream out(&file);
out.setEncoding(QStringConverter::Utf8);
out.setGenerateByteOrderMark(false);
- out << xml_doc.toString(4);
+ out << text;
if (!file.commit())
{
if (error_message) {
@@ -895,6 +904,15 @@ QActionGroup *QET::depthActionGroup(QObject *parent)
}
bool QET::writeToFile(QDomDocument &xml_doc, QFile *file, QString *error_message)
+{
+ return writeTextToFile(xml_doc.toString(4), file, error_message);
+}
+
+/**
+ @brief QET::writeTextToFile
+ writeToFile() for a document already written as @p text
+*/
+bool QET::writeTextToFile(const QString &text, QFile *file, QString *error_message)
{
bool opened_here = file->isOpen() ? false : true;
@@ -920,7 +938,7 @@ bool QET::writeToFile(QDomDocument &xml_doc, QFile *file, QString *error_message
out.seek(0);
out.setEncoding(QStringConverter::Utf8);
out.setGenerateByteOrderMark(false);
- out << xml_doc.toString(4);
+ out << text;
if (opened_here) {
file->close();
}
diff --git a/sources/qet.h b/sources/qet.h
index 6901446a0..630ede6e8 100644
--- a/sources/qet.h
+++ b/sources/qet.h
@@ -202,6 +202,8 @@ namespace QET {
bool compareCanonicalFilePaths(const QString &, const QString &);
bool writeXmlFile(QDomDocument &xml_doc, const QString &filepath, QString * error_message= nullptr);
bool writeToFile (QDomDocument &xml_doc, QFile *file, QString *error_message = nullptr);
+ bool writeXmlText(const QString &text, const QString &filepath, QString *error_message = nullptr);
+ bool writeTextToFile(const QString &text, QFile *file, QString *error_message = nullptr);
bool eachStrIsEqual (const QStringList &qsl);
QActionGroup *depthActionGroup(QObject *parent = nullptr);
void saveCustomColors();
diff --git a/sources/qetdiagrameditor.cpp b/sources/qetdiagrameditor.cpp
index 907ae692b..8c9924606 100644
--- a/sources/qetdiagrameditor.cpp
+++ b/sources/qetdiagrameditor.cpp
@@ -562,8 +562,9 @@ void QETDiagramEditor::setUpActions()
m_draw_grid->setCheckable(true);
m_draw_grid->setChecked(settings.value("diagrameditor/grid_display_startup", true).toBool());
connect(m_draw_grid, &QAction::triggered, [this](bool checked) {
+ //The folio's own setting: folios() builds nothing
foreach (ProjectView *prjv, this->openedProjects())
- foreach (Diagram *d, prjv->project()->diagrams()) {
+ foreach (Diagram *d, prjv->project()->folios()) {
d->setDisplayGrid(checked);
d->update();
}
@@ -603,8 +604,9 @@ void QETDiagramEditor::setUpActions()
m_draw_guides->setCheckable(true);
m_draw_guides->setChecked(settings.value("diagrameditor/guides_display_startup", false).toBool());
connect(m_draw_guides, &QAction::triggered, [this](bool checked) {
+ //The folio's own setting: folios() builds nothing
foreach (ProjectView *prjv, this->openedProjects())
- foreach (Diagram *d, prjv->project()->diagrams()) {
+ foreach (Diagram *d, prjv->project()->folios()) {
d->setDisplayGuides(checked);
}
});
@@ -652,8 +654,10 @@ void QETDiagramEditor::setUpActions()
action->setData(int(kind.first));
connect(action, &QAction::triggered, this, [kind](bool checked) {
ShownKinds::setShown(kind.first, checked);
+ //Items built later are shown or hidden as they are made
+ //(ShownKinds::tag())
for (QETProject *project : QETApp::registeredProjects())
- for (Diagram *diagram : project->diagrams())
+ for (Diagram *diagram : project->builtFolios())
ShownKinds::apply(diagram, kind.first);
emit QETApp::instance()->shownKindsChanged();
});
@@ -2004,7 +2008,7 @@ bool QETDiagramEditor::openAndAddProject(
//was not found: they would otherwise vanish on the next save
//without the user ever being told.
QStringList lost_wires;
- for (Diagram *diagram : project->diagrams()) {
+ for (Diagram *diagram : project->foliosWithWiresNotReconnected()) {
for (const QString &wire : diagram->wiresNotReconnected()) {
lost_wires << tr("Sheet %1: %2").arg(diagram->folioIndex() + 1).arg(wire);
}
diff --git a/sources/qetgraphicsitem/ViewItem/projectdbmodel.cpp b/sources/qetgraphicsitem/ViewItem/projectdbmodel.cpp
index b8c83334b..cf9525cc2 100644
--- a/sources/qetgraphicsitem/ViewItem/projectdbmodel.cpp
+++ b/sources/qetgraphicsitem/ViewItem/projectdbmodel.cpp
@@ -37,7 +37,8 @@
*/
ProjectDBModel::ProjectDBModel(QETProject *project, QObject *parent) :
QAbstractTableModel(parent),
- m_project(project)
+ m_project(project),
+ m_fill_when_built(project->dataBase()->isBuildingFolio())
{
connect(m_project->dataBase(), &projectDataBase::dataBaseUpdated, this, &ProjectDBModel::dataBaseUpdated);
}
@@ -320,6 +321,21 @@ void ProjectDBModel::setIdentifier(const QString &identifier) {
m_identifier = identifier;
}
+/**
+ @brief ProjectDBModel::folioBuilt
+ The folio of a table made while it was being built (QET_LAZY_FOLIOS)
+ is built: read the database, as opening does once it has filled it.
+ Nothing for any other model.
+*/
+void ProjectDBModel::folioBuilt()
+{
+ if (!m_fill_when_built) {
+ return;
+ }
+ m_fill_when_built = false;
+ dataBaseUpdated();
+}
+
/**
@brief ProjectDBModel::dataBaseUpdated
slot called when the project database is updated
@@ -374,6 +390,13 @@ void ProjectDBModel::setHeaderString()
void ProjectDBModel::fillValue()
{
m_record.clear();
+ //Opening builds a table before the database is filled, and fills
+ //it after: a table on a folio built late (QET_LAZY_FOLIOS) does
+ //the same, so that it is laid out as it would have been on
+ //opening -- empty until its folio is built (folioBuilt())
+ if (m_fill_when_built) {
+ return;
+ }
auto query_ = m_project->dataBase()->newQuery(m_query);
if (!query_.exec()) {
diff --git a/sources/qetgraphicsitem/ViewItem/projectdbmodel.h b/sources/qetgraphicsitem/ViewItem/projectdbmodel.h
index add4db1da..37385765c 100644
--- a/sources/qetgraphicsitem/ViewItem/projectdbmodel.h
+++ b/sources/qetgraphicsitem/ViewItem/projectdbmodel.h
@@ -71,6 +71,7 @@ class ProjectDBModel : public QAbstractTableModel
void setIdentifier(const QString &identifier);
QString identifier() const {return m_identifier;}
static QString xmlTagName() {return QString("project_data_base_model");}
+ void folioBuilt();
private:
void dataBaseUpdated();
@@ -79,6 +80,7 @@ class ProjectDBModel : public QAbstractTableModel
private:
QPointer m_project;
+ bool m_fill_when_built = false;
QString m_query;
QVector m_record;
//First int = section, second int = Qt::role, QVariant = value
diff --git a/sources/qetgraphicsitem/ViewItem/qetgraphicstableitem.cpp b/sources/qetgraphicsitem/ViewItem/qetgraphicstableitem.cpp
index 7a4f50155..0b6922ebf 100644
--- a/sources/qetgraphicsitem/ViewItem/qetgraphicstableitem.cpp
+++ b/sources/qetgraphicsitem/ViewItem/qetgraphicstableitem.cpp
@@ -565,15 +565,34 @@ void QetGraphicsTableItem::initLink()
{
if (!m_pending_previous_table_uuid.isNull())
{
- ElementProvider provider_(this->diagram()->project());
- if (auto previous_table = provider_.tableFromUuid(m_pending_previous_table_uuid)) {
+ //The built folios only: a previous table on a folio not built
+ //yet (QET_LAZY_FOLIOS) is linked when that folio is
+ QETProject *project = this->diagram()->project();
+ ElementProvider provider_(project->builtFolios());
+ auto previous_table = provider_.tableFromUuid(m_pending_previous_table_uuid);
+ if (previous_table) {
setPreviousTable(previous_table);
}
- m_pending_previous_table_uuid = QUuid(); //Set to null in case initLink is called again
+ if (previous_table || !project->unloadedFolioCount()) {
+ m_pending_previous_table_uuid = QUuid(); //Set to null in case initLink is called again
+ }
}
setSize(m_pending_size);
}
+/**
+ @brief QetGraphicsTableItem::previousTableUuid
+ @return the uuid of the table before this one in its chain, linked or
+ still waiting for its folio to be built (QET_LAZY_FOLIOS); null if none
+*/
+QUuid QetGraphicsTableItem::previousTableUuid() const
+{
+ if (m_previous_table) {
+ return m_previous_table->uuid();
+ }
+ return m_pending_previous_table_uuid;
+}
+
/**
@brief QetGraphicsTableItem::minimumRowHeight
@return the minimum height of a row
@@ -610,7 +629,7 @@ QDomElement QetGraphicsTableItem::toXml(QDomDocument &dom_document) const
const auto project_ = diagram() ? diagram()->project() : nullptr;
auto in_project = [project_](const QetGraphicsTableItem *table) {
return project_ && table->diagram()
- && project_->diagrams().contains(table->diagram());
+ && project_->folios().contains(table->diagram());
};
QetGraphicsTableItem *previous_ = m_previous_table;
while (previous_ && !in_project(previous_)) {
diff --git a/sources/qetgraphicsitem/ViewItem/qetgraphicstableitem.h b/sources/qetgraphicsitem/ViewItem/qetgraphicstableitem.h
index b23fe3244..3b8cf7438 100644
--- a/sources/qetgraphicsitem/ViewItem/qetgraphicstableitem.h
+++ b/sources/qetgraphicsitem/ViewItem/qetgraphicstableitem.h
@@ -84,6 +84,8 @@ class QetGraphicsTableItem : public QetGraphicsItem
QPointer nextTable() const;
void setToMinimumHeight();
void initLink();
+ bool hasPendingLink() const {return !m_pending_previous_table_uuid.isNull();}
+ QUuid previousTableUuid() const;
QUuid uuid() const {return m_uuid;}
int minimumRowHeight() const;
diff --git a/sources/qetgraphicsitem/conductor.cpp b/sources/qetgraphicsitem/conductor.cpp
index dc2715139..720db396c 100644
--- a/sources/qetgraphicsitem/conductor.cpp
+++ b/sources/qetgraphicsitem/conductor.cpp
@@ -25,6 +25,7 @@
#include "../conductorsegment.h"
#include "../conductorsegmentprofile.h"
#include "../diagram.h"
+#include "../dataBase/projectdatabase.h"
#include "../diagramcommands.h"
#include "../qetdiagrameditor.h"
#include "../qetgraphicsitem/terminal.h"
@@ -118,6 +119,8 @@ Conductor::Conductor(Terminal *p1, Terminal* p2) :
setZValue(11);
m_previous_z_value = zValue();
m_uuid = QUuid::createUuid();
+ static quint64 created = 0;
+ m_creation_rank = ++created;
//Add this conductor to the list of conductors of each of the two terminals
bool ajout_p1 = terminal1 -> addConductor(this);
@@ -1114,6 +1117,7 @@ void Conductor::pointsToSegments(const QList& points_list) {
*/
bool Conductor::fromXml(QDomElement &dom_element)
{
+ const QUuid old_uuid = m_uuid;
//Older project files have no conductor uuid attribute at all --
//generate one on load, same treatment terminal uuids got when
//that field was introduced (see terminal1/terminal2 handling in
@@ -1129,6 +1133,7 @@ bool Conductor::fromXml(QDomElement &dom_element)
//single load-and-resave (see #754).
m_uuid = QUuid::createUuid();
}
+ uuidChanged(old_uuid);
//"nan" and "inf" parse as numbers; a non-finite position is kept at 0
const qreal x = dom_element.attribute("x", nullptr).toDouble();
@@ -1235,7 +1240,7 @@ QDomElement Conductor::toXml(QDomDocument &dom_document,
dom_element.appendChild(dom_seq);
// Export the properties and text
- m_properties. toXml(dom_element);
+ properties().toXml(dom_element);
if(m_text_item->wasMovedByUser())
{
dom_element.setAttribute("userx", QString::number(m_text_item->pos().x()));
@@ -1735,6 +1740,7 @@ void Conductor::refreshText()
m_properties.text = m_properties.m_formula;
m_text_item->setPlainText(m_properties.text);
}
+ storeProperties();
}
else
{
@@ -1833,6 +1839,7 @@ void Conductor::setProperties(const ConductorProperties &property)
calculateTextItemPosition();
update();
+ storeProperties();
emit propertiesChange();
}
@@ -1842,9 +1849,75 @@ void Conductor::setProperties(const ConductorProperties &property)
*/
ConductorProperties Conductor::properties() const
{
+ if (projectDataBase *store = placedStore())
+ return store->conductorProperties(m_uuid);
return(m_properties);
}
+/**
+ @brief Conductor::placedStore
+ @return the project database holding this wire's properties
+ (DB-ACCESSORS-PLAN.md stage 4.5), or nullptr if it is not placed in a
+ project, or shares its uuid with another wire for the moment a copy
+ takes to get its own
+*/
+projectDataBase *Conductor::placedStore() const
+{
+ Diagram *d = diagram();
+ QETProject *project = d ? d->project() : nullptr;
+ projectDataBase *store = project ? project->dataBase() : nullptr;
+ return store && store->hasConductorProperties(m_uuid)
+ && store->placedConductorCount(m_uuid) == 1 ? store : nullptr;
+}
+
+/**
+ @brief Conductor::storeProperties
+ Write this wire's properties to its project's store, once it is placed:
+ every change of a placed wire's properties comes through here.
+*/
+void Conductor::storeProperties()
+{
+ Diagram *d = diagram();
+ QETProject *project = d ? d->project() : nullptr;
+ if (project && project->dataBase() && project->dataBase()->hasConductorProperties(m_uuid))
+ project->dataBase()->conductorPropertiesStored(this, m_properties);
+}
+
+/**
+ @brief Conductor::newUuid
+ Make a new uuid for this conductor; its stored properties and its row
+ follow it.
+*/
+void Conductor::newUuid()
+{
+ setUuid(QUuid::createUuid());
+}
+
+/**
+ @brief Conductor::setUuid
+ @param uuid : the uuid of this conductor, saved from now on
+*/
+void Conductor::setUuid(const QUuid &uuid)
+{
+ const QUuid old_uuid = m_uuid;
+ m_uuid = uuid;
+ m_persist_uuid = true;
+ uuidChanged(old_uuid);
+}
+
+/**
+ @brief Conductor::uuidChanged
+ Tell the project's store this placed wire's uuid changed from
+ @p old_uuid (a paste renews it, a file gives it).
+*/
+void Conductor::uuidChanged(const QUuid &old_uuid)
+{
+ Diagram *d = diagram();
+ QETProject *project = d ? d->project() : nullptr;
+ if (project && project->dataBase())
+ project->dataBase()->conductorUuidChanged(this, old_uuid);
+}
+
/**
@brief Conductor::setCableReference
Record the cable core this conductor shows, together with the label
@@ -1868,6 +1941,7 @@ void Conductor::setCableReference(const QString &label,
m_properties.m_cable = label;
m_properties.m_cable_uuid = cable_uuid;
m_properties.m_cable_slot = slot;
+ storeProperties();
emit propertiesChange();
}
diff --git a/sources/qetgraphicsitem/conductor.h b/sources/qetgraphicsitem/conductor.h
index f75931064..844e97787 100644
--- a/sources/qetgraphicsitem/conductor.h
+++ b/sources/qetgraphicsitem/conductor.h
@@ -27,6 +27,7 @@
class ConductorProfile;
class ConductorSegmentProfile;
class Diagram;
+class projectDataBase;
class Terminal;
class ConductorSegment;
class ConductorTextItem;
@@ -80,8 +81,8 @@ class Conductor : public QGraphicsObject
Diagram *diagram() const;
ConductorTextItem *textItem() const;
QUuid uuid() const {return m_uuid;}
- void newUuid() {m_uuid = QUuid::createUuid(); m_persist_uuid = true;} //create new uuid for this conductor
- void setUuid(const QUuid &uuid) {m_uuid = uuid; m_persist_uuid = true;} //saved from now on
+ void newUuid(); //create new uuid for this conductor
+ void setUuid(const QUuid &uuid); //saved from now on
void updatePath(const QRectF & = QRectF());
//This method do nothing, it's only made to be used with Q_PROPERTY
@@ -125,6 +126,11 @@ class Conductor : public QGraphicsObject
bool only_text = false);
void setProperties(const ConductorProperties &property);
ConductorProperties properties() const;
+ /// This wire's own copy of its properties, whatever the store holds
+ ConductorProperties ownProperties() const {return m_properties;}
+ /// The order wires were made in this session: a project's are
+ /// made in the order its file lists them
+ quint64 creationRank() const {return m_creation_rank;}
/// Records which cable core this conductor shows and writes the
/// label that assignment generates (15W1:br) into its cable
/// field. Deliberately not routed through
@@ -233,6 +239,10 @@ class Conductor : public QGraphicsObject
/// file had none -- toXml() must not persist that value, or
/// every reload mints and saves a new random one (see #754).
bool m_persist_uuid = true;
+ quint64 m_creation_rank = 0;
+ void uuidChanged(const QUuid &old_uuid);
+ projectDataBase *placedStore() const;
+ void storeProperties();
/// QPen et QBrush objects used to draw conductors
static QPen conductor_pen;
diff --git a/sources/qetgraphicsitem/element.cpp b/sources/qetgraphicsitem/element.cpp
index 56c8bda2d..779c6f7c9 100644
--- a/sources/qetgraphicsitem/element.cpp
+++ b/sources/qetgraphicsitem/element.cpp
@@ -18,6 +18,7 @@
#include "element.h"
#include "../qetapp.h"
#include "../qetproject.h"
+#include "../dataBase/projectdatabase.h"
#include "../PropertiesEditor/propertieseditordialog.h"
#include "../autoNum/assignvariables.h"
#include "../autoNum/elementautonumschemecommand.h"
@@ -718,6 +719,7 @@ bool Element::buildFromXml(const QDomElement &xml_def_elmt, int *state)
m_data.m_informations.fromXml(
xml_def_elmt.firstChildElement(QStringLiteral("elementInformations")),
QStringLiteral("elementInformation"));
+ storeInformation();
//scroll of the Children of the Definition: Parts of the Drawing
int parsed_elements_count = 0;
@@ -1042,7 +1044,9 @@ bool Element::fromXml(QDomElement &e,
}
//uuid of this element
- m_uuid = QUuid(e.attribute(QStringLiteral("uuid"), QUuid::createUuid().toString()));
+ //Through setUuid(): a folio adds a symbol before reading it, so its
+ //stored information has to follow from the uuid it was born with.
+ setUuid(QUuid(e.attribute(QStringLiteral("uuid"), QUuid::createUuid().toString())));
//load prefix
m_prefix = e.attribute(QStringLiteral("prefix"));
@@ -1265,10 +1269,11 @@ QDomElement Element::toXml(
}
//save information of this element
- if (! m_data.m_informations.keys().isEmpty()) {
+ const DiagramContext informations = elementInformations();
+ if (! informations.keys().isEmpty()) {
QDomElement infos =
document.createElement(QStringLiteral("elementInformations"));
- m_data.m_informations.toXml(infos, QStringLiteral("elementInformation"));
+ informations.toXml(infos, QStringLiteral("elementInformation"));
//toXml() skips empty values: an element whose information is
//all empty would otherwise be written an empty block, which the
//next load reads as no information and the next save drops.
@@ -1625,7 +1630,10 @@ void Element::initLink(QETProject *prj)
// if nothing to link return now
if (tmp_uuids_link.isEmpty()) return;
- ElementProvider ep(prj);
+ //The built folios only: a partner on a folio not built yet
+ //(QET_LAZY_FOLIOS) is linked when its folio is, and the saved list
+ //is kept until then, for the order it gives
+ ElementProvider ep(prj->builtFolios());
QList uuids;
for (const auto &linkInfo : tmp_uuids_link) {
uuids.append(linkInfo.uuid);
@@ -1643,7 +1651,27 @@ void Element::initLink(QETProject *prj)
}
}
restoreSavedLinkOrder();
- tmp_uuids_link.clear();
+ if (pendingLinks().isEmpty() || !prj->unloadedFolioCount()) {
+ tmp_uuids_link.clear();
+ }
+}
+
+/**
+ @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 (QET_LAZY_FOLIOS),
+ until initLink() runs again once it is
+*/
+QSet Element::pendingLinks() const
+{
+ QSet pending;
+ for (const auto &link_info : tmp_uuids_link) {
+ pending.insert(link_info.uuid);
+ }
+ for (Element *elmt : connected_elements) {
+ pending.remove(elmt->uuid());
+ }
+ return pending;
}
/**
@@ -1674,6 +1702,23 @@ void Element::initLink(const QList &candidates)
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)
+*/
+QList> Element::waitingLinks() const
+{
+ const QSet pending = pendingLinks();
+ QList> 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.
@@ -1795,6 +1840,33 @@ void Element::setFormulaSchemeId(const QUuid &id)
m_data.m_informations.addValue(QETInformation::ELMT_FORMULA_ID,
id.toString(), false);
}
+ storeInformation();
+}
+
+/**
+ @brief Element::setUuid
+ A placed symbol's stored information and its database rows follow its
+ uuid (a paste or a folio copy renews it).
+*/
+void Element::setUuid(const QUuid &uuid)
+{
+ const QUuid old_uuid = m_uuid;
+ m_uuid = uuid;
+ if (diagram() && diagram()->project() && diagram()->project()->dataBase())
+ diagram()->project()->dataBase()->elementUuidChanged(this, old_uuid);
+}
+
+/**
+ @brief Element::storeInformation
+ Write this symbol's information to its project's store, once it is
+ placed (DB-ACCESSORS-PLAN.md stage 4.1): every change of a placed
+ symbol's information comes through here.
+*/
+void Element::storeInformation()
+{
+ if (diagram() && diagram()->project() && diagram()->project()->dataBase()
+ && diagram()->project()->dataBase()->hasElementInformation(m_uuid))
+ diagram()->project()->dataBase()->elementInformationChanged(this, m_data.m_informations);
}
void Element::setElementInformations(DiagramContext dc)
@@ -1811,6 +1883,7 @@ void Element::setElementInformations(DiagramContext dc)
if (!actual_label.isEmpty()) {
m_data.m_informations.addValue(QStringLiteral("label"), actual_label); //Update the label if there is a formula
}
+ storeInformation();
emit elementInfoChange(old_info, m_data.m_informations);
// Propagate label change to linked PLC slaves (label is changed via
@@ -1841,9 +1914,43 @@ void Element::setElementInformations(DiagramContext dc)
*/
ElementData Element::elementData() const
{
+ if (projectDataBase *store = placedStore()) {
+ ElementData data = m_data;
+ data.m_informations = store->elementInformation(m_uuid);
+ return data;
+ }
return m_data;
}
+/**
+ @brief Element::elementInformations
+ @return this symbol's information: for a placed symbol the project's
+ store holds it (DB-ACCESSORS-PLAN.md stage 4.2), for one not placed
+ (a preview, the symbol editor) its own copy.
+*/
+DiagramContext Element::elementInformations() const
+{
+ if (projectDataBase *store = placedStore())
+ return store->elementInformation(m_uuid);
+ return m_data.m_informations;
+}
+
+/**
+ @brief Element::placedStore
+ @return the project database holding this symbol's information, or
+ nullptr if it is not placed in a project
+*/
+projectDataBase *Element::placedStore() const
+{
+ Diagram *d = diagram();
+ QETProject *project = d ? d->project() : nullptr;
+ projectDataBase *store = project ? project->dataBase() : nullptr;
+ //A uuid other placed symbols share (F100) names no single symbol's
+ //information: this one answers from its own copy.
+ return store && store->hasElementInformation(m_uuid)
+ && store->placedElementCount(m_uuid) == 1 ? store : nullptr;
+}
+
/**
* @brief Element::setElementData
* Set new data for this element.
@@ -1860,6 +1967,7 @@ void Element::setElementData(ElementData data)
if (old_info != m_data.m_informations) {
m_data.m_informations.addValue(QStringLiteral("label"), actualLabel()); //Update the label if there is a formula
+ storeInformation();
if (diagram()) {
diagram()->project()->dataBase()->elementInfoChanged(this);
}
@@ -2078,6 +2186,7 @@ void Element::setUpFormula(bool code_letter, QUndoCommand *parent_undo)
->elementAutoNumCurrentFormula();
m_data.m_informations.addValue(QStringLiteral("formula"), formula);
+ storeInformation();
QString element_currentAutoNum = diagram()
->project()
@@ -2146,6 +2255,7 @@ void Element::setUpFormula(bool code_letter, QUndoCommand *parent_undo)
{
DiagramContext dc = m_data.m_informations;
m_data.m_informations.addValue(QStringLiteral("label"), actualLabel());
+ storeInformation();
emit elementInfoChange(dc, m_data.m_informations);
}
}
@@ -2199,6 +2309,8 @@ void Element::freezeNewAddedElement()
*/
QString Element::actualLabel()
{
+ //The symbol's own copy, not the store: this runs while the
+ //information is being changed, before the store has the change.
if (m_data.m_informations.value(QStringLiteral("formula")).toString().isEmpty()) {
return m_data.m_informations.value(QStringLiteral("label")).toString();
} else {
diff --git a/sources/qetgraphicsitem/element.h b/sources/qetgraphicsitem/element.h
index 31cddc5eb..6f5fc991d 100644
--- a/sources/qetgraphicsitem/element.h
+++ b/sources/qetgraphicsitem/element.h
@@ -40,6 +40,8 @@ class QUndoCommand;
/**
This is the base class for electrical elements.
*/
+class projectDataBase;
+
class Element : public QetGraphicsItem
{
friend class DiagramEventAddElement;
@@ -130,8 +132,9 @@ class Element : public QetGraphicsItem
}
//METHODS related to information
- DiagramContext elementInformations()const
- {return m_data.m_informations;}
+ DiagramContext elementInformations()const;
+ /// This symbol's own copy of its information, whatever the store holds
+ DiagramContext ownInformations() const {return m_data.m_informations;}
virtual void setElementInformations(DiagramContext dc);
ElementData elementData() const;
@@ -222,6 +225,9 @@ class Element : public QetGraphicsItem
virtual void unlinkAllElements() {}
virtual void unlinkElement(Element *) {}
virtual void initLink(QETProject *);
+ QSet pendingLinks() const;
+ bool keepsSavedLinks() const {return !tmp_uuids_link.isEmpty();}
+ QList> waitingLinks() const;
/**
Resolve tmp_uuids_link against a caller-supplied candidate
list instead of a project-wide search (bugtracker #607).
@@ -259,8 +265,8 @@ class Element : public QetGraphicsItem
*/
QString linkTypeToString() const;
- void newUuid() {m_uuid = QUuid::createUuid();} //create new uuid for this element
- void setUuid(const QUuid &uuid) {m_uuid = uuid;}
+ void newUuid() {setUuid(QUuid::createUuid());} //create new uuid for this element
+ void setUuid(const QUuid &uuid);
protected:
void drawAxes(QPainter *, const QStyleOptionGraphicsItem *);
@@ -329,6 +335,8 @@ class Element : public QetGraphicsItem
private:
void restoreSavedLinkOrder();
+ void storeInformation();
+ projectDataBase *placedStore() const;
bool m_must_highlight = false;
QSize dimensions;
QPoint hotspot_coord;
diff --git a/sources/qetgraphicsitem/terminal.cpp b/sources/qetgraphicsitem/terminal.cpp
index 68219f11b..0e3684cea 100644
--- a/sources/qetgraphicsitem/terminal.cpp
+++ b/sources/qetgraphicsitem/terminal.cpp
@@ -1090,6 +1090,17 @@ QList relatedPotentialTerminal (
// If terminal parent element is a folio report.
if (all_diagram && terminal -> parentElement() -> linkType() & Element::AllReport)
{
+ //The report's partner on a folio not built yet (QET_LAZY_FOLIOS)
+ //is built, so that a potential is followed as far as when every
+ //folio is; not while folios are being read or built, which
+ //would build whole report chains
+ Element *report = terminal->parentElement();
+ const QSet waiting = report->pendingLinks();
+ QETProject *project = report->diagram() ? report->diagram()->project() : nullptr;
+ if (!waiting.isEmpty() && project
+ && project->state() != QETProject::ProjectParsingRunning) {
+ project->buildFoliosHoldingSymbols(waiting);
+ }
QList elmt_list = terminal -> parentElement() -> linkedElements();
if (!elmt_list.isEmpty())
{
diff --git a/sources/qetproject.cpp b/sources/qetproject.cpp
index 70416ef25..913bc9b77 100644
--- a/sources/qetproject.cpp
+++ b/sources/qetproject.cpp
@@ -30,6 +30,8 @@
#include "autoNum/elementautonumschemecommand.h"
#include "diagram.h"
#include "qetgraphicsitem/element.h"
+#include "qetgraphicsitem/ViewItem/qetgraphicstableitem.h"
+#include "qetgraphicsitem/ViewItem/projectdbmodel.h"
#include "qetapp.h"
#include "qetmessagebox.h"
#include "qetresult.h"
@@ -320,7 +322,20 @@ void QETProject::init()
connect(&m_titleblocks_collection, &TitleBlockTemplatesCollection::changed, this, &QETProject::updateDiagramsTitleBlockTemplate);
connect(&m_titleblocks_collection, &TitleBlockTemplatesCollection::aboutToRemove, this, &QETProject::removeDiagramsTitleBlockTemplate);
+ //What changes the saved items of folios not built (QET_LAZY_FOLIOS):
+ //a folio's position, number or the folio total in their texts,
+ //the report and cross-reference settings, a symbol's definition.
+ //A folio not built is then built to be saved (writesKeptFolios())
+ connect(this, &QETProject::diagramAdded, this, &QETProject::markKeptFoliosStale);
+ connect(this, &QETProject::diagramRemoved, this, &QETProject::markKeptFoliosStale);
+ connect(this, &QETProject::projectDiagramsOrderChanged, this, &QETProject::markKeptFoliosStale);
+ connect(this, &QETProject::reportPropertiesChanged, this, &QETProject::markKeptFoliosStale);
+ connect(this, &QETProject::XRefPropertiesChanged, this, &QETProject::markKeptFoliosStale);
+
m_undo_stack = new QUndoStack(this);
+ //Folios not built are saved as read until something is edited
+ //(m_edited_since_open, toXml())
+ connect(m_undo_stack, &QUndoStack::indexChanged, this, [this]() {m_edited_since_open = true;});
connect(m_undo_stack, &QUndoStack::cleanChanged, this, &QETProject::undoStackChanged);
//What counts as a change for writeBackup(): the undo stack moving,
@@ -332,6 +347,7 @@ void QETProject::init()
connect(&m_titleblocks_collection, &TitleBlockTemplatesCollection::aboutToRemove, this, backup_needed);
connect(m_elements_collection, &XmlElementCollection::elementAdded, this, backup_needed);
connect(m_elements_collection, &XmlElementCollection::elementChanged, this, backup_needed);
+ connect(m_elements_collection, &XmlElementCollection::elementChanged, this, &QETProject::markKeptFoliosStale);
connect(m_elements_collection, &XmlElementCollection::elementRemoved, this, backup_needed);
connect(m_elements_collection, &XmlElementCollection::directorieAdded, this, backup_needed);
connect(m_elements_collection, &XmlElementCollection::directoryRemoved, this, backup_needed);
@@ -421,6 +437,8 @@ QETProject::ProjectState QETProject::openFile(QFile *file)
//Extract the content of the xml. The raw bytes are kept : a project
//file without a persisted uuid derives its uuid from them.
const QByteArray content = file->readAll();
+ //Each folio's text is kept while it is not built (readDiagramsXml())
+ m_opened_content = content;
QDomDocument xml_project;
// PreserveSpacingOnlyNodes: without it, a text node that is entirely
// whitespace -- e.g. a title-block custom variable deliberately set to
@@ -461,6 +479,7 @@ QETProject::ProjectState QETProject::openFile(QFile *file)
//Build the project from the xml
readProjectXml(xml_project);
+ m_opened_content.clear();
const qint64 total_ms = load_timer.elapsed();
qInfo().nospace().noquote()
@@ -494,8 +513,12 @@ void QETProject::refresh()
dlgWaiting->show();
}
- for(const auto &diagram : diagrams())
+ //A folio not built yet is refreshed when it is (folioLoaded())
+ for(const auto &diagram : folios())
{
+ if (!diagram->isLoaded()) {
+ continue;
+ }
if(dlgWaiting)
{
dlgWaiting->setProgressBar(dlgWaiting->progressBarValue()+1);
@@ -520,9 +543,409 @@ QETProject::ProjectState QETProject::state() const
*/
QList QETProject::diagrams() const
{
+ //Every caller of diagrams() expects every folio built: those that
+ //do not are moved to folios() one by one (LAZY-FOLIO-PLAN.md)
+ loadFolios();
return(m_diagrams_list);
}
+/**
+ @brief QETProject::folios
+ @return the folios of this project, whether their items are built yet
+ or not (a project opened with QET_LAZY_FOLIOS); see Diagram::isLoaded()
+*/
+QList QETProject::folios() const
+{
+ return(m_diagrams_list);
+}
+
+/**
+ @brief QETProject::cableLabelFolios
+ @return the folios CableManager::refreshLabels() works on, built: every
+ folio when the project has cables (a core is followed through folio
+ reports to any folio); without cables, the folios built and those not
+ built yet (QET_LAZY_FOLIOS) holding a wire that still refers to a
+ cable -- the only wires the pass changes then, taking the reference
+ away -- found in the XML kept for them
+*/
+QList QETProject::cableLabelFolios()
+{
+ if (!cables().isEmpty()) {
+ return diagrams();
+ }
+ QList referring;
+ for (Diagram *d : std::as_const(m_diagrams_list)) {
+ if (d->isLoaded()) continue;
+ for (const QDomElement &wire : QET::findInDomElement(d->deferredXml(), QStringLiteral("conductors"),
+ QStringLiteral("conductor")))
+ if (!QUuid(wire.attribute(QStringLiteral("cable_uuid"))).isNull()) {
+ referring << d;
+ break;
+ }
+ }
+ buildFolios(referring);
+ return builtFolios();
+}
+
+/**
+ @brief QETProject::builtFolios
+ @return the folios of this project whose items are built: all of them,
+ but for a project opened with QET_LAZY_FOLIOS
+*/
+QList QETProject::builtFolios() const
+{
+ QList built;
+ for (Diagram *d : m_diagrams_list)
+ if (d->isLoaded()) built << d;
+ return built;
+}
+
+/**
+ @brief QETProject::unloadedFolioCount
+ @return how many folios of this project wait to be built
+*/
+int QETProject::unloadedFolioCount() const
+{
+ int n = 0;
+ for (const Diagram *d : m_diagrams_list)
+ if (!d->isLoaded()) ++n;
+ return n;
+}
+
+/**
+ @brief QETProject::foliosWithWiresNotReconnected
+ @return the folios that left out a wire of the file, an end of it not
+ being found (Diagram::wiresNotReconnected()). A folio not built yet is
+ built only when the database, filled from the document, says it has
+ such a wire: the others are not built to find out.
+*/
+QList QETProject::foliosWithWiresNotReconnected()
+{
+ const QSet unjoined = m_data_base.foliosWithUnjoinedWires();
+ QList found;
+ for (Diagram *diagram : std::as_const(m_diagrams_list))
+ {
+ if (!diagram->isLoaded()) {
+ if (!unjoined.contains(diagram->uuid())) {
+ continue;
+ }
+ diagram->ensureLoaded();
+ }
+ if (!diagram->wiresNotReconnected().isEmpty()) {
+ found << diagram;
+ }
+ }
+ return found;
+}
+
+/**
+ @brief QETProject::loadFolios
+ Build every folio of this project still waiting to be built.
+ QET_LAZY_FOLIOS_TRACE=1 says when it happens, to find the callers that
+ still need every folio.
+*/
+void QETProject::loadFolios() const
+{
+ const int waiting = unloadedFolioCount();
+ if (!waiting) {
+ return;
+ }
+ if (qEnvironmentVariableIntValue("QET_LAZY_FOLIOS_TRACE") == 1) {
+ qWarning().noquote() << "lazy folios:" << waiting << "folios built for a caller of diagrams()";
+ }
+ buildFolios(m_diagrams_list);
+}
+
+/**
+ @brief QETProject::buildFolios
+ Build those of @p folios still waiting, as opening builds them: all
+ first, then what opening does after building (the cross-references of
+ each look for symbols on the others), with the project in the state
+ opening has, which keeps the texts where the file put them (a text
+ aligned right is not moved when its content is set).
+*/
+void QETProject::buildFolios(const QList &folios) const
+{
+ auto *self = const_cast(this);
+ const ProjectState state = m_state;
+ self->m_state = ProjectParsingRunning;
+ QList built;
+ for (Diagram *d : folios)
+ if (d->buildDeferred()) built << d;
+ for (Diagram *d : std::as_const(built))
+ self->folioLoaded(d);
+ //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);
+ //Likewise a table whose previous table was on a folio not built
+ for (Diagram *d : builtFolios())
+ for (QGraphicsItem *item : d->items())
+ if (item->type() == QetGraphicsTableItem::Type)
+ if (auto table = static_cast(item);
+ table->hasPendingLink())
+ table->initLink();
+ //The tables just built were filled with nothing, as opening builds
+ //them before the database is filled (ProjectDBModel::fillValue()):
+ //fill them now, as opening does then
+ QSet models;
+ for (Diagram *d : std::as_const(built))
+ for (QGraphicsItem *item : d->items())
+ if (item->type() == QetGraphicsTableItem::Type)
+ if (auto model = qobject_cast(
+ static_cast(item)->model()))
+ models.insert(model);
+ for (ProjectDBModel *model : std::as_const(models))
+ model->folioBuilt();
+ self->m_state = state;
+}
+
+/**
+ @brief QETProject::buildFoliosHolding
+ Build the folios not built yet (QET_LAZY_FOLIOS) holding a symbol of
+ one of the kinds @p kinds -- if @p free_only, one which may be linked
+ to nothing once built: none of the partners it was saved linked to is
+ in the project. What a search for such symbols must build to find
+ every one (ElementProvider), and no more.
+*/
+void QETProject::buildFoliosHolding(ElementData::Types kinds, bool free_only)
+{
+ if (!unloadedFolioCount()) {
+ return;
+ }
+ QSet unbuilt;
+ QSet placed;
+ for (Diagram *d : std::as_const(m_diagrams_list)) {
+ if (!d->isLoaded()) {
+ unbuilt.insert(d->uuid());
+ } else if (free_only) {
+ for (Element *element : d->elements())
+ placed.insert(element->uuid());
+ }
+ }
+ //A symbol the database has no row for is one building leaves out
+ const QHash types = m_data_base.elementTypesOnFolios(unbuilt);
+ if (free_only) {
+ for (auto it = types.cbegin() ; it != types.cend() ; ++it)
+ placed.insert(it.key());
+ }
+ QList to_build;
+ for (Diagram *d : std::as_const(m_diagrams_list))
+ {
+ if (d->isLoaded()) {
+ continue;
+ }
+ for (const QDomElement &element_xml :
+ QET::findInDomElement(d->deferredXml(),
+ QStringLiteral("elements"),
+ QStringLiteral("element")))
+ {
+ const auto type = types.constFind(QUuid(element_xml.attribute(QStringLiteral("uuid"))));
+ if (type == types.constEnd()
+ || !(kinds & ElementData::typeFromString(*type))) {
+ continue;
+ }
+ bool linked = false;
+ if (free_only) {
+ for (const QDomElement &link :
+ QET::findInDomElement(element_xml,
+ QStringLiteral("links_uuids"),
+ QStringLiteral("link_uuid"))) {
+ if (placed.contains(QUuid(link.attribute(QStringLiteral("uuid"))))) {
+ linked = true;
+ break;
+ }
+ }
+ }
+ if (!linked) {
+ to_build << d;
+ break;
+ }
+ }
+ }
+ if (to_build.isEmpty()) {
+ return;
+ }
+ if (qEnvironmentVariableIntValue("QET_LAZY_FOLIOS_TRACE") == 1) {
+ qWarning().noquote() << "lazy folios:" << to_build.size()
+ << "folios built for a search of"
+ << (free_only ? "free" : "") << "symbols of kinds" << int(kinds);
+ }
+ buildFolios(to_build);
+}
+
+/**
+ @brief QETProject::buildFoliosHoldingSymbols
+ Build the folios not built yet (QET_LAZY_FOLIOS) holding one of the
+ symbols @p symbols, found in the XML kept for them: what needs those
+ symbols while the project is being read, before the database knows
+ where they are (a terminal strip's terminals)
+*/
+void QETProject::buildFoliosHoldingSymbols(const QSet &symbols)
+{
+ if (symbols.isEmpty() || !unloadedFolioCount()) {
+ return;
+ }
+ QList to_build;
+ for (Diagram *d : std::as_const(m_diagrams_list))
+ {
+ if (d->isLoaded()) {
+ continue;
+ }
+ for (const QDomElement &element_xml :
+ QET::findInDomElement(d->deferredXml(),
+ QStringLiteral("elements"),
+ QStringLiteral("element"))) {
+ if (symbols.contains(QUuid(element_xml.attribute(QStringLiteral("uuid"))))) {
+ to_build << d;
+ break;
+ }
+ }
+ }
+ if (to_build.isEmpty()) {
+ return;
+ }
+ if (qEnvironmentVariableIntValue("QET_LAZY_FOLIOS_TRACE") == 1) {
+ qWarning().noquote() << "lazy folios:" << to_build.size()
+ << "folios built for the symbols asked for by uuid";
+ }
+ buildFolios(to_build);
+}
+
+/**
+ @brief QETProject::symbolsOnUnbuiltFolios
+ @return the uuids of the symbols on the folios not built yet
+ (QET_LAZY_FOLIOS) that building them places
+*/
+QSet QETProject::symbolsOnUnbuiltFolios() const
+{
+ QSet unbuilt;
+ for (Diagram *d : m_diagrams_list)
+ if (!d->isLoaded()) unbuilt.insert(d->uuid());
+ QSet symbols;
+ if (unbuilt.isEmpty()) {
+ return symbols;
+ }
+ const QHash types = m_data_base.elementTypesOnFolios(unbuilt);
+ for (auto it = types.cbegin() ; it != types.cend() ; ++it)
+ symbols.insert(it.key());
+ return symbols;
+}
+
+/**
+ @brief QETProject::tableChainFolios
+ @return the folios holding a table chained to one on @p folio, before
+ or after it, read from the folios built and the XML kept for the
+ others: a chain shares its model and its columns' widths
+*/
+QSet QETProject::tableChainFolios(Diagram *folio) const
+{
+ QHash folio_of;
+ QHash previous_of;
+ for (Diagram *d : m_diagrams_list)
+ {
+ if (d->isLoaded()) {
+ for (QGraphicsItem *item : d->items()) {
+ if (item->type() == QetGraphicsTableItem::Type) {
+ auto table = static_cast(item);
+ folio_of.insert(table->uuid(), d);
+ previous_of.insert(table->uuid(), table->previousTableUuid());
+ }
+ }
+ continue;
+ }
+ const QDomNodeList tables = d->deferredXml().elementsByTagName(
+ QetGraphicsTableItem::xmlTagName());
+ for (int i = 0 ; i < tables.size() ; ++i) {
+ const QDomElement table = tables.at(i).toElement();
+ const QUuid uuid(table.attribute(QStringLiteral("uuid")));
+ folio_of.insert(uuid, d);
+ const QDomElement previous = table.firstChildElement(QStringLiteral("previous_table"));
+ previous_of.insert(uuid, QUuid(previous.attribute(QStringLiteral("uuid"))));
+ }
+ }
+ //Every table's chain head, then the folios of the heads @p folio has
+ auto head = [&previous_of](QUuid uuid) {
+ QSet seen;
+ while (!previous_of.value(uuid).isNull() && !seen.contains(uuid)) {
+ seen.insert(uuid);
+ uuid = previous_of.value(uuid);
+ }
+ return uuid;
+ };
+ QSet heads;
+ for (auto it = folio_of.cbegin() ; it != folio_of.cend() ; ++it)
+ if (it.value() == folio)
+ heads.insert(head(it.key()));
+ QSet folios;
+ if (heads.isEmpty()) {
+ return folios;
+ }
+ for (auto it = folio_of.cbegin() ; it != folio_of.cend() ; ++it)
+ if (heads.contains(head(it.key())))
+ folios.insert(it.value());
+ return folios;
+}
+
+/**
+ @brief QETProject::buildFolioToShow
+ Build @p folio to show it, and the folios holding the partners its
+ symbols were saved linked to: a cross-reference or a folio report
+ draws what it knows of its partner, which must be built for that.
+ Their own partners on folios further away wait until those are built.
+*/
+void QETProject::buildFolioToShow(Diagram *folio)
+{
+ //A folio built earlier as another's partner may still have partners
+ //of its own waiting: it is looked at again
+ if (!folio || !unloadedFolioCount()) {
+ return;
+ }
+ //A table chain is built at once: its model is in its first table
+ QSet chained = tableChainFolios(folio);
+ chained.insert(folio);
+ QList first;
+ for (Diagram *d : std::as_const(m_diagrams_list))
+ if (chained.contains(d))
+ first << d;
+ buildFolios(first);
+ QSet partners;
+ for (Element *elmt : folio->elements())
+ partners.unite(elmt->pendingLinks());
+ const QSet folio_uuids = partners.isEmpty()
+ ? QSet()
+ : m_data_base.folioUuidsOfElements(partners);
+ QList to_build;
+ for (Diagram *d : std::as_const(m_diagrams_list))
+ if (!d->isLoaded() && folio_uuids.contains(d->uuid()))
+ to_build << d;
+ if (to_build.isEmpty()) {
+ return;
+ }
+ if (qEnvironmentVariableIntValue("QET_LAZY_FOLIOS_TRACE") == 1) {
+ qWarning().noquote() << "lazy folios:" << to_build.size()
+ << "folios built for the partners of folio"
+ << folio->folioIndex() + 1;
+ }
+ buildFolios(to_build);
+}
+
+/**
+ @brief QETProject::folioLoaded
+ @p folio's items were just built, after the project was opened: do for
+ them what opening does for every folio's.
+*/
+void QETProject::folioLoaded(Diagram *folio)
+{
+ linkElementsToElementAutoNums(folio);
+ folio->refreshContents();
+}
+
/**
@param diagram Pointer to a Diagram object
@return the folio number of the given diagram object within the project,
@@ -1240,6 +1663,7 @@ QString QETProject::elementAutoNumNameClash(const QString &name,
*/
QVector QETProject::elementsUsingElementAutoNum(const QString &title) const
{
+ loadFolios(); //walks the folios' symbols
QVector list;
const QUuid id = elementAutoNumId(title);
if (id.isNull()) {
@@ -1277,40 +1701,146 @@ QVector QETProject::elementsUsingElementAutoNum(const QString &title)
The label is not touched: nothing it is built from changes.
*/
void QETProject::linkElementsToElementAutoNums()
+{
+ for (Diagram *d : std::as_const(m_diagrams_list)) {
+ if (d) linkElementsToElementAutoNums(d);
+ }
+}
+
+/**
+ @brief QETProject::linkElementsToElementAutoNums
+ linkElementsToElementAutoNums() for the elements of @p folio alone
+*/
+void QETProject::linkElementsToElementAutoNums(Diagram *folio)
+{
+ const QHash titles_by_formula = elementAutoNumTitlesByFormula();
+ {
+ const auto items = folio->items();
+ for (QGraphicsItem *it : items) {
+ auto *el = qgraphicsitem_cast(it);
+ if (!el) continue;
+ if (const auto id = elementAutoNumIdToLink(el->elementInformations(),
+ titles_by_formula)) {
+ el->setFormulaSchemeId(*id);
+ }
+ }
+ }
+}
+
+/**
+ @brief QETProject::elementAutoNumTitlesByFormula
+ @return the titles of the element numbering schemes, by their formula
+*/
+QHash QETProject::elementAutoNumTitlesByFormula() const
{
QHash titles_by_formula;
for (auto it = m_element_autonum.constBegin();
it != m_element_autonum.constEnd(); ++it) {
titles_by_formula[autonum::numerotationContextToFormula(it.value())] << it.key();
}
+ return titles_by_formula;
+}
- for (Diagram *d : std::as_const(m_diagrams_list)) {
- if (!d) continue;
- const auto items = d->items();
- for (QGraphicsItem *it : items) {
- auto *el = qgraphicsitem_cast(it);
+/**
+ @brief QETProject::elementAutoNumIdToLink
+ The rule of linkElementsToElementAutoNums() for one element, whose
+ information is @p information.
+ @return the scheme id the element must be given, or nothing when the
+ one it has is kept
+*/
+std::optional QETProject::elementAutoNumIdToLink(
+ const DiagramContext &information,
+ const QHash &titles_by_formula) const
+{
+ const QString formula = information.value(QETInformation::ELMT_FORMULA).toString();
+ const bool has_id = information.contains(QETInformation::ELMT_FORMULA_ID);
+ const QUuid id(information.value(QETInformation::ELMT_FORMULA_ID).toString());
+
+ if (formula.isEmpty()) {
+ if (has_id) return QUuid();
+ return std::nullopt;
+ }
+ if (!elementAutoNumTitle(id).isEmpty()) {
+ return std::nullopt;
+ }
+ if (!has_id && !m_legacy_element_autonums) {
+ return std::nullopt; //A formula typed by hand
+ }
+ const QStringList matches = titles_by_formula.value(formula);
+ return matches.size() == 1 ? elementAutoNumId(matches.first()) : QUuid();
+}
+
+/**
+ @brief QETProject::symbolNumbering
+ @return the numbering of every symbol placed on the folios, read from
+ the symbol on a folio built and, on a folio not built yet
+ (QET_LAZY_FOLIOS), from what building it would give: its information
+ in the project database's store, the scheme it would be linked to,
+ its sequential numbers and its kind as the file has them.
+*/
+QList QETProject::symbolNumbering() const
+{
+ QList list;
+ QSet unbuilt;
+ for (Diagram *d : m_diagrams_list)
+ {
+ if (!d->isLoaded()) {
+ unbuilt.insert(d->uuid());
+ continue;
+ }
+ for (QGraphicsItem *item : d->items()) {
+ auto *el = qgraphicsitem_cast(item);
if (!el) continue;
const DiagramContext &info = el->elementInformations();
- const QString formula = info.value(QETInformation::ELMT_FORMULA).toString();
- const bool has_id = info.contains(QETInformation::ELMT_FORMULA_ID);
- const QUuid id(info.value(QETInformation::ELMT_FORMULA_ID).toString());
-
- if (formula.isEmpty()) {
- if (has_id) el->setFormulaSchemeId(QUuid());
- continue;
- }
- if (!elementAutoNumTitle(id).isEmpty()) {
- continue;
- }
- if (!has_id && !m_legacy_element_autonums) {
- continue; //A formula typed by hand
- }
- const QStringList matches = titles_by_formula.value(formula);
- el->setFormulaSchemeId(matches.size() == 1
- ? elementAutoNumId(matches.first())
- : QUuid());
+ SymbolNumbering symbol;
+ symbol.element = el;
+ symbol.label = info.value(QETInformation::ELMT_LABEL).toString();
+ symbol.formula = info.value(QETInformation::ELMT_FORMULA).toString();
+ symbol.scheme = QUuid(info.value(QETInformation::ELMT_FORMULA_ID).toString());
+ symbol.sequence = el->sequenceStruct();
+ symbol.numbered = el->linkType() != Element::Slave
+ && !(el->linkType() & Element::AllReport);
+ list << symbol;
}
}
+ if (unbuilt.isEmpty()) {
+ return list;
+ }
+
+ //A symbol the database has no row for is one building leaves out
+ //(its definition is missing)
+ const QHash types = m_data_base.elementTypesOnFolios(unbuilt);
+ const QHash titles_by_formula = elementAutoNumTitlesByFormula();
+ for (Diagram *d : m_diagrams_list)
+ {
+ if (d->isLoaded()) {
+ continue;
+ }
+ for (const QDomElement &element_xml :
+ QET::findInDomElement(d->deferredXml(),
+ QStringLiteral("elements"),
+ QStringLiteral("element")))
+ {
+ const QUuid uuid(element_xml.attribute(QStringLiteral("uuid")));
+ const auto type = types.constFind(uuid);
+ if (type == types.constEnd()) {
+ continue;
+ }
+ const DiagramContext info = m_data_base.elementInformation(uuid);
+ SymbolNumbering symbol;
+ symbol.label = info.value(QETInformation::ELMT_LABEL).toString();
+ symbol.formula = info.value(QETInformation::ELMT_FORMULA).toString();
+ const auto linked = elementAutoNumIdToLink(info, titles_by_formula);
+ symbol.scheme = linked ? *linked
+ : QUuid(info.value(QETInformation::ELMT_FORMULA_ID).toString());
+ symbol.sequence.fromXml(element_xml.firstChildElement(QStringLiteral("sequentialNumbers")));
+ const ElementData::Type kind = ElementData::typeFromString(*type);
+ symbol.numbered = kind != ElementData::Slave
+ && !(kind & ElementData::AllReport);
+ list << symbol;
+ }
+ }
+ return list;
}
/**
@@ -1451,6 +1981,7 @@ NumerotationContext QETProject::cableAutoNum(const QString &key) const
@param to - last folio index to apply freeze
*/
void QETProject::freezeExistentConductorLabel(bool freeze, int from, int to) {
+ loadFolios(); //walks the folios' wires
for (int i = from; i <= to; i++) {
m_diagrams_list.at(i)->freezeConductors(freeze);
}
@@ -1494,6 +2025,7 @@ void QETProject::setFreezeNewConductors(bool set) {
@param to - last folio index to apply freeze
*/
void QETProject::freezeExistentElementLabel(bool freeze, int from, int to) {
+ loadFolios(); //walks the folios' symbols
for (int i = from; i <= to; i++) {
m_diagrams_list.at(i)->freezeElements(freeze);
}
@@ -1571,7 +2103,8 @@ void QETProject::setWireHops(WireHops::Mode mode)
return;
}
m_wire_hops = mode;
- for (Diagram *diagram : diagrams()) {
+ //A folio not built yet reads the mode when its wires are drawn
+ for (Diagram *diagram : folios()) {
diagram->update();
}
}
@@ -1598,7 +2131,8 @@ void QETProject::setUprightSymbolTexts(bool upright)
return;
}
m_upright_symbol_texts = upright;
- for (Diagram *diagram : diagrams()) {
+ //A folio not built yet reads the setting when its symbols are drawn
+ for (Diagram *diagram : folios()) {
for (QGraphicsItem *item : diagram->items()) {
if (Element *element = qgraphicsitem_cast(item)) {
element->updateSymbolPictures();
@@ -1703,8 +2237,74 @@ void QETProject::autoFolioNumberingSelectedFolios(int from,
@brief QETProject::toXml
@return un document XML representant le projet
*/
-QDomDocument QETProject::toXml()
+QDomDocument QETProject::toXml(QHash *kept_folios)
{
+ //With @p kept_folios, a folio not built (QET_LAZY_FOLIOS) is
+ //written as a placeholder and its text
+ //as read from the file goes in kept_folios, for
+ //spliceKeptFolios() -- when building it would write that same
+ //text: the file is this version's and its database rows came from
+ //it (m_kept_folios_trusted), and nothing a folio's saved items are
+ //made from changed since: the folios' order and number, the
+ //project's properties, report and cross-reference settings, a
+ //symbol's definition (m_kept_folios_stale). Otherwise every folio is
+ //built and written from its items. A folio whose own data changed
+ //is built with the folios its links reach, as when it is shown:
+ //their saved texts can show its data (a report, its partner's
+ //folio label).
+ const bool keep = kept_folios && unloadedFolioCount()
+ && m_kept_folios_trusted && !m_kept_folios_stale;
+ if (keep) {
+ for (Diagram *diagram : std::as_const(m_diagrams_list))
+ if (diagram->headerChanged())
+ buildFolioToShow(diagram);
+ //A table filled from the database saves the size its rows
+ //give it, which any edit can change
+ if (m_edited_since_open)
+ for (Diagram *diagram : std::as_const(m_diagrams_list))
+ if (!diagram->isLoaded() && !diagram->deferredXml().elementsByTagName(
+ QetGraphicsTableItem::xmlTagName()).isEmpty())
+ buildFolioToShow(diagram);
+ //A built folio is written from its items, which need their
+ //partners: a symbol saves its links, a report the position of
+ //its partner. Build the folios of every partner still waiting,
+ //and of every table chain, until none is
+ for (int built = -1 ; built != unloadedFolioCount() ; )
+ {
+ built = unloadedFolioCount();
+ QSet waiting;
+ QSet chained;
+ for (Diagram *diagram : builtFolios()) {
+ for (Element *element : diagram->elements())
+ waiting.unite(element->pendingLinks());
+ for (Diagram *other : tableChainFolios(diagram))
+ if (!other->isLoaded()) chained.insert(other);
+ }
+ buildFoliosHoldingSymbols(waiting);
+ QList chain_list;
+ for (Diagram *diagram : std::as_const(m_diagrams_list))
+ if (chained.contains(diagram) && !diagram->isLoaded())
+ chain_list << diagram;
+ buildFolios(chain_list);
+ }
+ } else {
+ loadFolios();
+ }
+ //QET_CHECK_ELEMENT_INFO_STORE=1: say whether the stores of symbol
+ //information, folio title blocks and wire properties
+ //(DB-ACCESSORS-PLAN.md) agree with every placed symbol, folio and
+ //wire, at each save. A test and debugging aid; it changes nothing.
+ if (qEnvironmentVariableIntValue("QET_CHECK_ELEMENT_INFO_STORE") == 1) {
+ const QStringList mismatches = m_data_base.elementInformationMismatches();
+ qWarning().noquote() << "element information store:" << mismatches.size() << "differ"
+ << mismatches.mid(0, 5).join(QStringLiteral(", "));
+ const QStringList folios = m_data_base.folioTitleBlockMismatches();
+ qWarning().noquote() << "folio title block store:" << folios.size() << "differ"
+ << folios.mid(0, 5).join(QStringLiteral(", "));
+ const QStringList wires = m_data_base.conductorPropertiesMismatches();
+ qWarning().noquote() << "wire properties store:" << wires.size() << "differ"
+ << wires.mid(0, 5).join(QStringLiteral(", "));
+ }
// racine du projet
QDomDocument xml_doc;
QDomElement project_root = xml_doc.createElement("project");
@@ -1768,6 +2368,13 @@ QDomDocument QETProject::toXml()
<< "["
<< diagram
<< "]";
+ if (keep && !diagram->isLoaded()) {
+ auto placeholder = xml_doc.createElement(QStringLiteral("diagram"));
+ placeholder.setAttribute(QStringLiteral("qet_kept_folio"), order_num);
+ kept_folios->insert(order_num ++, diagram->keptText());
+ project_root.appendChild(placeholder);
+ continue;
+ }
QDomElement xml_diagram = diagram->toXml().documentElement();
QDomNode xml_node = xml_doc.importNode(xml_diagram, true);
@@ -1805,6 +2412,28 @@ QDomDocument QETProject::toXml()
return(xml_doc);
}
+/**
+ @brief QETProject::spliceKeptFolios
+ @return @p text, a project written by toXml() with kept folios, with
+ each placeholder line replaced by the folio's text as it was read
+*/
+QString QETProject::spliceKeptFolios(QString text, const QHash &kept_folios)
+{
+ for (auto it = kept_folios.cbegin() ; it != kept_folios.cend() ; ++it)
+ {
+ const QString placeholder = QStringLiteral("").arg(it.key());
+ const qsizetype at = text.indexOf(placeholder);
+ if (at < 0) {
+ continue;
+ }
+ const qsizetype line_start = text.lastIndexOf(QLatin1Char('\n'), at) + 1;
+ qsizetype line_end = text.indexOf(QLatin1Char('\n'), at);
+ line_end = line_end < 0 ? text.size() : line_end + 1;
+ text.replace(line_start, line_end - line_start, QString::fromUtf8(it.value()));
+ }
+ return text;
+}
+
/**
Ferme le projet
*/
@@ -1864,15 +2493,22 @@ QETResult QETProject::write()
return(QString("the file %1 was opened read-only and thus will not be written").arg(m_file_path));
}
- QDomDocument xml_project(toXml());
QString error_message;
if (isContainerPath(m_file_path))
{
+ // A container must contain complete folios, not lazy placeholders.
+ QDomDocument xml_project(toXml());
if (!writeContainer(xml_project, m_file_path, &error_message))
return(error_message);
}
- else if (!QET::writeXmlFile(xml_project, m_file_path, &error_message))
- return(error_message);
+ else
+ {
+ QHash kept_folios;
+ QDomDocument xml_project(toXml(&kept_folios));
+ if (!QET::writeXmlText(spliceKeptFolios(xml_project.toString(4), kept_folios),
+ m_file_path, &error_message))
+ return(error_message);
+ }
// The project has just been written to a writable file (e.g. saved to
// a new location with "Save As"), so it is no longer read-only.
@@ -1927,6 +2563,7 @@ void QETProject::setReadOnly(bool read_only)
*/
bool QETProject::isEmpty() const
{
+ loadFolios(); //asks the folios whether they hold anything
// si le projet a un titre, on considere qu'il n'est pas vide
if (!project_title_.isEmpty()) return(false);
@@ -2098,12 +2735,42 @@ QString QETProject::integrateTitleBlockTemplate(const TitleBlockTemplateLocation
*/
bool QETProject::usesElement(const ElementsLocation &location) const
{
- foreach(Diagram *diagram, diagrams()) {
+ foreach(Diagram *diagram, builtFolios()) {
if (diagram -> usesElement(location)) {
return(true);
}
}
- return(false);
+ return(unbuiltFolioElements().contains(location));
+}
+
+/**
+ @brief QETProject::unbuiltFolioElements
+ @return the locations of the symbols placed on the folios not built yet
+ (QET_LAZY_FOLIOS), each once, read from the XML kept for them as
+ building them reads it (Diagram::fromXml())
+*/
+QList QETProject::unbuiltFolioElements() const
+{
+ QSet types;
+ for (Diagram *diagram : m_diagrams_list) {
+ if (diagram->isLoaded()) {
+ continue;
+ }
+ for (const QDomElement &element_xml :
+ QET::findInDomElement(diagram->deferredXml(),
+ QStringLiteral("elements"),
+ QStringLiteral("element"))) {
+ types.insert(element_xml.attribute(QStringLiteral("type")));
+ }
+ }
+ QList locations;
+ auto *self = const_cast(this);
+ for (const QString &type : std::as_const(types)) {
+ locations << (type.startsWith(QStringLiteral("embed://"))
+ ? ElementsLocation(type, self)
+ : ElementsLocation(type));
+ }
+ return locations;
}
/**
@@ -2116,10 +2783,25 @@ bool QETProject::usesElement(const ElementsLocation &location) const
QList QETProject::unusedElements() const
{
QList unused_list;
+ const QList built = builtFolios();
+ const QList unbuilt = unbuiltFolioElements();
foreach(ElementsLocation location, m_elements_collection->elementsLocation())
- if (location.isElement() && !usesElement(location))
+ {
+ if (!location.isElement() || unbuilt.contains(location)) {
+ continue;
+ }
+ bool used = false;
+ for (Diagram *diagram : built) {
+ if (diagram->usesElement(location)) {
+ used = true;
+ break;
+ }
+ }
+ if (!used) {
unused_list << location;
+ }
+ }
return unused_list;
}
@@ -2133,7 +2815,8 @@ bool QETProject::usesTitleBlockTemplate(const TitleBlockTemplateLocation &locati
// a diagram can only use a title block template embedded within its parent project
if (location.parentProject() != this) return(false);
- foreach (Diagram *diagram, diagrams()) {
+ //The template is the folio's own data: folios() builds nothing
+ foreach (Diagram *diagram, folios()) {
if (diagram -> usesTitleBlockTemplate(location.name())) {
return(true);
}
@@ -2330,6 +3013,11 @@ void QETProject::readProjectXml(QDomDocument &xml_project)
//before the diagrams because the terminal strip items use it
m_project_properties_handler.terminalStripLayoutHandler().fromXml(xml_project.documentElement());
+ //The stores of symbol information, folio title blocks and wire
+ //properties are filled from the document before the folios are
+ //built (DB-ACCESSORS-PLAN.md stage 4.6)
+ m_data_base.prefillFromDocument(xml_project);
+
//Load the diagrams
readDiagramsXml(xml_project);
const qint64 diagrams_ms = phase_timer.restart();
@@ -2361,6 +3049,31 @@ void QETProject::readProjectXml(QDomDocument &xml_project)
m_data_base.blockSignals(false);
m_data_base.setUpdateBlocked(false);
m_data_base.updateDB(xml_project);
+ m_data_base.endPrefill(unloadedFolioCount() > 0);
+ //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
+ //not say which program wrote it, and one with this version's
+ //number can hold what a save rewrites (tst_freetextnonfinite,
+ //tst_foliofreezeflags): only with QET_LAZY_SAVE_KEPT=1, until a
+ //.qetz, whose manifest names its writer, does it
+ //(QETZ-DB-TRUTH-PLAN.md D4)
+ m_kept_folios_trusted = qEnvironmentVariableIntValue("QET_LAZY_SAVE_KEPT") == 1
+ && unloadedFolioCount() > 0
+ && m_project_qet_version == QetVersion::currentVersion()
+ && m_data_base.rowsFromDocument();
+ for (Diagram *diagram : std::as_const(m_diagrams_list))
+ if (!diagram->isLoaded() && diagram->keptText().isEmpty())
+ m_kept_folios_trusted = false;
+ m_kept_folios_stale = false;
+ m_edited_since_open = false;
+ if (m_kept_folios_trusted)
+ for (Diagram *diagram : std::as_const(m_diagrams_list))
+ diagram->rememberHeader();
+ if (qEnvironmentVariableIntValue("QET_LAZY_FOLIOS_TRACE") == 1)
+ qWarning().noquote() << "lazy folios: opened with" << unloadedFolioCount() << "of" << m_diagrams_list.size() << "folios not built";
+ if (qEnvironmentVariableIntValue("QET_CHECK_ELEMENT_INFO_STORE") == 1)
+ qWarning().noquote() << "filled before building:" << m_data_base.prefillReport();
const qint64 database_ms = phase_timer.elapsed();
qInfo().nospace()
@@ -2418,6 +3131,38 @@ void QETProject::readDiagramsXml(QDomDocument &xml_project)
if(dlgWaiting)
dlgWaiting->setProgressBarRange(0, diagram_nodes.length()*3);
+ //Where each line of the file starts, for a folio's text: lines
+ //[its start, the next element's start) (QETProject::toXml())
+ QList line_starts;
+ if (qEnvironmentVariableIntValue("QET_LAZY_FOLIOS") == 1 && !m_opened_content.isEmpty()) {
+ line_starts << 0;
+ for (qsizetype i = 0 ; i < m_opened_content.size() ; ++i)
+ if (m_opened_content.at(i) == '\n') line_starts << i + 1;
+ }
+ auto folio_text = [this, &line_starts](const QDomElement &folio) {
+ const int first = folio.lineNumber();
+ const QDomElement next = folio.nextSiblingElement();
+ int last = next.isNull() ? -1 : next.lineNumber();
+ if (last < 0) {
+ //The last folio: up to the project's closing tag
+ const qsizetype end = m_opened_content.lastIndexOf("");
+ last = end < 0 ? -1 : int(std::upper_bound(line_starts.cbegin(), line_starts.cend(), end)
+ - line_starts.cbegin());
+ }
+ if (first < 1 || last <= first || last > line_starts.size()) {
+ return QByteArray();
+ }
+ const QByteArray text = m_opened_content.mid(line_starts.at(first - 1),
+ line_starts.at(last - 1) - line_starts.at(first - 1));
+ //One folio, whole: else it is built to be saved
+ const QByteArray trimmed = text.trimmed();
+ if (!trimmed.startsWith("") || (trimmed.endsWith("/>") && !text.contains("")))) {
+ return QByteArray();
+ }
+ return text;
+ };
+
for (int i = 0 ; i < diagram_nodes.length() ; ++ i)
{
if(dlgWaiting)
@@ -2436,7 +3181,12 @@ void QETProject::readDiagramsXml(QDomDocument &xml_project)
connect(diagram, &Diagram::usedTitleBlockTemplateChanged,
this, &QETProject::usedTitleBlockTemplateChanged);
+ m_deferring_folios = qEnvironmentVariableIntValue("QET_LAZY_FOLIOS") == 1;
diagram->initFromXml(diagram_xml_element);
+ m_deferring_folios = false;
+ if (!diagram->isLoaded() && !line_starts.isEmpty())
+ diagram->setKeptText(folio_text(diagram_xml_element));
+ m_data_base.placeFolio(diagram);
if(dlgWaiting)
dlgWaiting->setDetail(diagram->title());
}
@@ -3072,11 +3822,13 @@ void QETProject::writeBackup()
//Capture the document by value (implicitly shared, so cheap): the
//Qt5-style QtConcurrent::run(function, reference-args) call did not
//survive the Qt6 API change, a lambda behaves identically on both.
- QDomDocument xml_project(toXml());
+ QHash kept_folios;
+ QDomDocument xml_project(toXml(&kept_folios));
KAutoSaveFile *target = &m_backup_files[m_next_backup_slot];
m_next_backup_slot = (m_next_backup_slot + 1) % BackupGenerations;
- m_backup_future = QtConcurrent::run([target, xml_project]() mutable {
- return QET::writeToFile(xml_project, target, nullptr);
+ m_backup_future = QtConcurrent::run([target, xml_project, kept_folios]() mutable {
+ return QET::writeTextToFile(spliceKeptFolios(xml_project.toString(4), kept_folios),
+ target, nullptr);
});
}
@@ -3104,6 +3856,8 @@ DiagramContext QETProject::projectProperties()
*/
void QETProject::setProjectProperties(const DiagramContext &context) {
m_project_properties = context;
+ //Folio and symbol texts are made from them
+ markKeptFoliosStale();
updateDiagramsFolioData();
emit projectInformationsChanged(this);
}
diff --git a/sources/qetproject.h b/sources/qetproject.h
index 58dd124c6..30da9d1a4 100644
--- a/sources/qetproject.h
+++ b/sources/qetproject.h
@@ -31,6 +31,7 @@
#include "wirehops.h"
#include "wiringrules.h"
#include "diagram.h"
+#include "autoNum/assignvariables.h"
#ifdef BUILD_WITHOUT_KF
# include "ui/nokde/kautosavefile.h"
#else
@@ -44,6 +45,7 @@
#include
#include
+#include
class Diagram;
class Element;
@@ -119,6 +121,22 @@ class QETProject : public QObject
QUuid derivedItemUuid(const QString &kind, const QString &key);
ProjectState state() const;
QList diagrams() const;
+ //The folios whether their items are built or not
+ //(QET_LAZY_FOLIOS, LAZY-FOLIO-PLAN.md); diagrams() builds them
+ QList folios() const;
+ bool deferringFolios() const {return m_deferring_folios;}
+ int unloadedFolioCount() const;
+ QList builtFolios() const;
+ QList cableLabelFolios();
+ QList foliosWithWiresNotReconnected();
+ void buildFolioToShow(Diagram *folio);
+ void buildFoliosHolding(ElementData::Types kinds, bool free_only);
+ void buildFoliosHoldingSymbols(const QSet &symbols);
+ QSet symbolsOnUnbuiltFolios() const;
+ QSet tableChainFolios(Diagram *folio) const;
+ void loadFolios() const;
+ void buildFolios(const QList &folios) const;
+ void folioLoaded(Diagram *folio);
int folioIndex(const Diagram *) const;
XmlElementCollection *embeddedElementCollection()const;
TitleBlockTemplatesProjectCollection *embeddedTitleBlockTemplatesCollection();
@@ -240,6 +258,18 @@ class QETProject : public QObject
const QString &ignored_title = QString()) const;
static QString normalizedAutoNumName(const QString &name);
QVector elementsUsingElementAutoNum(const QString &title) const;
+ /// A placed symbol's numbering, whether its folio is built or
+ /// not (QET_LAZY_FOLIOS): what is asked of every symbol of the
+ /// project when one is numbered
+ struct SymbolNumbering {
+ const Element *element = nullptr; ///< null on a folio not built
+ QString label;
+ QString formula;
+ QUuid scheme; ///< the element numbering scheme it follows
+ autonum::sequentialNumbers sequence;
+ bool numbered = false; ///< neither a slave nor a report
+ };
+ QList symbolNumbering() const;
/**
* @brief Renumber existing elements by element autonumbering scheme.
@@ -283,7 +313,8 @@ class QETProject : public QObject
void autoFolioNumberingNewFolios ();
void autoFolioNumberingSelectedFolios(int, int, const QString&);
- QDomDocument toXml();
+ QDomDocument toXml(QHash *kept_folios = nullptr);
+ static QString spliceKeptFolios(QString text, const QHash &kept_folios);
bool close();
QETResult write();
bool isReadOnly() const;
@@ -293,6 +324,7 @@ class QETProject : public QObject
QString integrateTitleBlockTemplate(const TitleBlockTemplateLocation &, MoveTitleBlockTemplatesHandler *handler);
bool usesElement(const ElementsLocation &) const;
QList unusedElements() const;
+ QList unbuiltFolioElements() const;
bool usesTitleBlockTemplate(const TitleBlockTemplateLocation &);
bool projectWasModified();
bool projectOptionsWereModified();
@@ -474,6 +506,24 @@ class QETProject : public QObject
/// saved without an id (written before ids existed)
bool m_legacy_element_autonums = false;
void linkElementsToElementAutoNums();
+ void linkElementsToElementAutoNums(Diagram *folio);
+ QHash elementAutoNumTitlesByFormula() const;
+ std::optional elementAutoNumIdToLink(
+ const DiagramContext &information,
+ const QHash &titles_by_formula) const;
+ bool m_deferring_folios = false;
+ /// Saving may write a folio not built as it was read
+ /// (writesKeptFolios()): the file is this version's, its
+ /// database rows came from it, and nothing project-wide that
+ /// a folio's saved items depend on has changed since
+ bool m_kept_folios_trusted = false;
+ bool m_kept_folios_stale = false;
+ /// Something was edited since the project was opened: a table
+ /// filled from the database may have changed
+ bool m_edited_since_open = false;
+ void markKeptFoliosStale() {m_kept_folios_stale = true;}
+ /// The file being read, for each folio's text (readDiagramsXml())
+ QByteArray m_opened_content;
bool m_auto_conductor = true;
WireHops::Mode m_wire_hops = WireHops::Mode::None;
/// Texts drawn in a turned symbol stay horizontal (on for a new
diff --git a/sources/ui/diagrambgcolorbutton.cpp b/sources/ui/diagrambgcolorbutton.cpp
index 037b8bc43..20097e037 100644
--- a/sources/ui/diagrambgcolorbutton.cpp
+++ b/sources/ui/diagrambgcolorbutton.cpp
@@ -150,8 +150,9 @@ void DiagramBgColorToolButton::applyColor(const QColor &color)
if (!editor) {
return;
}
+ //A folio not built yet takes the colour when it is drawn
for (ProjectView *pv : editor->openedProjects())
- for (Diagram *d : pv->project()->diagrams())
+ for (Diagram *d : pv->project()->folios())
d->update();
}
@@ -175,8 +176,9 @@ void DiagramBgColorToolButton::applySystemColor()
if (!editor) {
return;
}
+ //A folio not built yet takes the colour when it is drawn
for (ProjectView *pv : editor->openedProjects())
- for (Diagram *d : pv->project()->diagrams())
+ for (Diagram *d : pv->project()->folios())
d->update();
}
diff --git a/sources/ui/diagrameditorhandlersizewidget.cpp b/sources/ui/diagrameditorhandlersizewidget.cpp
index bb22cac90..a0726e4aa 100644
--- a/sources/ui/diagrameditorhandlersizewidget.cpp
+++ b/sources/ui/diagrameditorhandlersizewidget.cpp
@@ -60,7 +60,8 @@ void DiagramEditorHandlerSizeWidget::on_comboBox_currentIndexChanged(int index)
{
editor_->setProperty("graphics_handler_size", size);
for (auto project_view : editor_->openedProjects()) {
- for (auto diagram : project_view->project()->diagrams()) {
+ //Handles exist on built folios only
+ for (auto diagram : project_view->project()->builtFolios()) {
for (const auto item : diagram->items()) {
if (item->type() == QetGraphicsHandlerItem::Type) {
auto handler = qgraphicsitem_cast(item);
diff --git a/sources/ui/jumptoelementdialog.cpp b/sources/ui/jumptoelementdialog.cpp
index a2bf35089..eaaaaf2eb 100644
--- a/sources/ui/jumptoelementdialog.cpp
+++ b/sources/ui/jumptoelementdialog.cpp
@@ -169,7 +169,7 @@ void JumpToElementDialog::activateCurrentItem()
const int index = current->data(Qt::UserRole).toInt();
if (index == -1) {
const QList diagrams = m_diagram->project()
- ? m_diagram->project()->diagrams()
+ ? m_diagram->project()->folios()
: QList();
const int folio = current->data(Qt::UserRole + 2).toInt();
if (folio >= 0 && folio < diagrams.size()) {
@@ -208,7 +208,8 @@ QListWidgetItem *JumpToElementDialog::cellItem(const QString &needle) const
if (!m_diagram || !m_diagram->project()) {
return nullptr;
}
- const QList diagrams = m_diagram->project()->diagrams();
+ //Folios' cells only (their border): folios() builds nothing
+ const QList diagrams = m_diagram->project()->folios();
Diagram *diagram = m_diagram;
QString cell = needle;
diff --git a/tests/qttest/CMakeLists.txt b/tests/qttest/CMakeLists.txt
index faa763b3a..b5be0cf96 100644
--- a/tests/qttest/CMakeLists.txt
+++ b/tests/qttest/CMakeLists.txt
@@ -87,6 +87,13 @@ if(Python3_Interpreter_FOUND AND CMAKE_GENERATOR STREQUAL "Ninja")
--source ${CMAKE_CURRENT_SOURCE_DIR}/attached_plain_paste_probe.cpp
--fixture ${CMAKE_CURRENT_SOURCE_DIR}/fixtures/unlinked_contact_label.qet)
set_tests_properties(tst_attachedplainpaste PROPERTIES TIMEOUT 300 RUN_SERIAL TRUE)
+ add_test(NAME tst_lazyfolios
+ COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/run_lazy_folios_probe.py
+ --build ${CMAKE_BINARY_DIR} --binary $
+ --source ${CMAKE_CURRENT_SOURCE_DIR}/lazy_folios_probe.cpp
+ --fixture ${CMAKE_CURRENT_SOURCE_DIR}/../../examples/industrial.qet)
+ set_tests_properties(tst_lazyfolios PROPERTIES TIMEOUT 600 RUN_SERIAL TRUE
+ ENVIRONMENT "QT_QPA_PLATFORM=offscreen")
# Deleting a cable, undoing the delete twice and then editing anything
# has both cable commands tearing down the same cable. Runs under the
# same objects the application uses, so a second free is caught.
diff --git a/tests/qttest/lazy_folios_probe.cpp b/tests/qttest/lazy_folios_probe.cpp
new file mode 100644
index 000000000..daebbba50
--- /dev/null
+++ b/tests/qttest/lazy_folios_probe.cpp
@@ -0,0 +1,579 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+// Lazy folios (QET_LAZY_FOLIOS, LAZY-FOLIO-PLAN.md stages 5.2 and 5.3),
+// 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"
+
+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());
+ }
+ 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;
+}
diff --git a/tests/qttest/run_lazy_folios_probe.py b/tests/qttest/run_lazy_folios_probe.py
new file mode 100644
index 000000000..2c7f881cb
--- /dev/null
+++ b/tests/qttest/run_lazy_folios_probe.py
@@ -0,0 +1,60 @@
+#!/usr/bin/env python3
+"""Link the lazy folios integration probe using an existing Ninja app build.
+
+Reuses app objects without duplicating the app or introducing a core-library
+refactor. No application source or existing executable is replaced. The
+generated manifest, probe binary, reports and export fixtures stay in build/.
+"""
+import argparse
+import pathlib
+import re
+import shutil
+import subprocess
+import sys
+
+parser = argparse.ArgumentParser()
+parser.add_argument('--build', required=True)
+parser.add_argument('--binary', required=True)
+parser.add_argument('--source', required=True)
+parser.add_argument('--fixture', required=True)
+args = parser.parse_args()
+build = pathlib.Path(args.build).resolve()
+binary = pathlib.Path(args.binary).resolve()
+source = pathlib.Path(args.source).resolve()
+fixture = pathlib.Path(args.fixture).resolve()
+manifest = (build / 'build.ninja').read_text(encoding='utf-8')
+match = re.search(r'^build (\S*?/sources/main\.cpp\.(?:obj|o)):', manifest, re.MULTILINE)
+if not match:
+ sys.exit('Cannot find the application main object in the Ninja build.')
+main_object = match.group(1)
+probe_object = main_object.replace('main.cpp.', 'lazy_folios_probe.cpp.')
+probe_binary = binary.with_name('lazy_folios_probe' + binary.suffix)
+def escape(path):
+ return pathlib.Path(path).as_posix().replace('$', '$$').replace(':', '$:').replace(' ', '$ ')
+main_source = source.parents[2] / 'sources' / 'main.cpp'
+manifest = manifest.replace(main_object, probe_object)
+manifest = manifest.replace(escape(main_source), escape(source))
+if binary.suffix:
+ manifest = manifest.replace(binary.name, probe_binary.name)
+else:
+ old_target = escape(binary.name if binary.parent == build else binary)
+ new_target = escape(probe_binary.name if probe_binary.parent == build else probe_binary)
+ manifest = manifest.replace('build ' + old_target + ':', 'build ' + new_target + ':')
+ old_file = binary.name if binary.parent == build else binary.as_posix()
+ new_file = probe_binary.name if probe_binary.parent == build else probe_binary.as_posix()
+ manifest = re.sub(r'^ TARGET_FILE = ' + re.escape(old_file) + r'$',
+ ' TARGET_FILE = ' + new_file, manifest, flags=re.MULTILINE)
+probe_manifest = build / 'lazy-folios-probe.ninja'
+probe_manifest.write_text(manifest, encoding='utf-8')
+ninja = shutil.which('ninja')
+if not ninja:
+ sys.exit('Ninja is required for the integration probe.')
+target = probe_binary.name if probe_binary.parent == build else str(probe_binary)
+subprocess.run([ninja, '-C', str(build), '-f', probe_manifest.name, target], check=True)
+output = build / 'lazy-folios'
+output.mkdir(exist_ok=True)
+report = output / 'results.txt'
+result = subprocess.run([str(probe_binary), str(fixture), str(report), str(output)], timeout=500)
+if report.exists():
+ print(report.read_text(encoding='utf-8', errors='replace'))
+sys.exit(result.returncode)
diff --git a/tests/qttest/tst_resaveunchanged.cpp b/tests/qttest/tst_resaveunchanged.cpp
index 46178c692..1babd2c55 100644
--- a/tests/qttest/tst_resaveunchanged.cpp
+++ b/tests/qttest/tst_resaveunchanged.cpp
@@ -317,6 +317,169 @@ private slots:
QCOMPARE(without_save_stamps(read(back)), without_save_stamps(read(straight)));
}
+ // QElectroTech run in a sandbox of its own with @p args, the store
+ // check on (QET_CHECK_ELEMENT_INFO_STORE); its output
+ QString runChecked(const QStringList &args, bool eager = false)
+ {
+ const QString home = m_dir.filePath(QStringLiteral("home%1").arg(m_run++));
+ QDir().mkpath(home);
+ QProcessEnvironment env = QProcessEnvironment::systemEnvironment();
+ env.insert(QStringLiteral("QT_QPA_PLATFORM"), QStringLiteral("offscreen"));
+ env.insert(QStringLiteral("QET_ENABLE_SCRIPTING"), QStringLiteral("1"));
+ env.insert(QStringLiteral("QET_CHECK_ELEMENT_INFO_STORE"), QStringLiteral("1"));
+ if (eager) {
+ env.remove(QStringLiteral("QET_LAZY_FOLIOS")); //every folio built on opening
+ }
+ env.insert(QStringLiteral("HOME"), home);
+ env.insert(QStringLiteral("XDG_CONFIG_HOME"), home + QStringLiteral("/config"));
+ env.insert(QStringLiteral("XDG_DATA_HOME"), home + QStringLiteral("/data"));
+ QProcess proc;
+ proc.setProcessEnvironment(env);
+ proc.start(QStringLiteral(QET_TEST_BINARY_PATH), args);
+ if (!proc.waitForFinished(180000)) return {};
+ return QString::fromUtf8(proc.readAllStandardOutput() + proc.readAllStandardError());
+ }
+
+ // The stores of symbol information and folio title blocks
+ // (DB-ACCESSORS-PLAN.md stages 4.1, 4.4) agree with every placed symbol
+ // and folio once a project is open: every example.
+ void elementInfoStoreAgrees_data()
+ {
+ QTest::addColumn("project");
+ const QDir examples(QStringLiteral(QET_EXAMPLES_DIR));
+ for (const QString &f : examples.entryList({QStringLiteral("*.qet")}, QDir::Files, QDir::Name))
+ QTest::newRow(f.toUtf8().constData()) << examples.filePath(f);
+ }
+ void elementInfoStoreAgrees()
+ {
+ QFETCH(QString, project);
+ const QString out = runChecked({QStringLiteral("--resave"), project,
+ m_dir.filePath(QStringLiteral("store%1.qet").arg(m_run))});
+ QVERIFY2(out.contains(QStringLiteral("element information store: 0 differ")), qPrintable(out.right(600)));
+ QVERIFY2(out.contains(QStringLiteral("folio title block store: 0 differ")), qPrintable(out.right(600)));
+ QVERIFY2(out.contains(QStringLiteral("wire properties store: 0 differ")), qPrintable(out.right(600)));
+ }
+
+ // The stores filled from the document before the folios are built
+ // (DB-ACCESSORS-PLAN.md stage 4.6) hold what building them gives, for a
+ // project saved by this version (every symbol, folio and wire carries
+ // a uuid): nothing differs, nothing is left unplaced.
+ void storesFilledBeforeBuilding()
+ {
+ const QString saved = resave(QStringLiteral(QET_EXAMPLES_DIR "/industrial.qet"));
+ QVERIFY2(!saved.isEmpty(), "--resave failed");
+ const QString out = runChecked({QStringLiteral("--resave"), saved,
+ m_dir.filePath(QStringLiteral("prefilled.qet"))}, true);
+ const QRegularExpression line(QStringLiteral(
+ "filled before building: symbols (\\d+) agree, 0 differ, 0 not placed; "
+ "folios (\\d+) agree, 0 differ, 0 not placed; wires (\\d+) agree, 0 differ, 0 not placed"));
+ const QRegularExpressionMatch m = line.match(out);
+ QVERIFY2(m.hasMatch(), qPrintable(out.right(600)));
+ QVERIFY(m.captured(1).toInt() > 600 && m.captured(2).toInt() == 50 && m.captured(3).toInt() > 600);
+ }
+
+ // Title block fields stamped on every folio go through the store, and
+ // the saved folios carry them (a folio saves what the store holds).
+ void folioTitleBlockStoreAfterSetTitleBlock()
+ {
+ const QString saved = m_dir.filePath(QStringLiteral("stamped.qet"));
+ const QString out = runChecked({QStringLiteral("--set-titleblock"),
+ QStringLiteral(QET_EXAMPLES_DIR "/industrial.qet"), saved,
+ QStringLiteral("author=Ann"), QStringLiteral("extrafield=Hello")});
+ QVERIFY2(out.contains(QStringLiteral("folio title block store: 0 differ")), qPrintable(out.right(600)));
+ const QByteArray xml = read(saved);
+ QCOMPARE(xml.count(" wires ? ' with wires' : ''));\n"
+ "qet.log('ROWS ' + rows());\n"
+ "qet.log('SAVED ' + qet.save('%1'));\n").arg(saved)).toUtf8());
+ js.close();
+ QFile js2(dump);
+ QVERIFY(js2.open(QIODevice::WriteOnly));
+ js2.write((rows + QStringLiteral("qet.log('ROWS ' + rows());\n")).toUtf8());
+ js2.close();
+
+ const QString edited = runChecked({QStringLiteral("--run"), edit,
+ QStringLiteral(QET_EXAMPLES_DIR "/industrial.qet")});
+ QVERIFY2(edited.contains(QStringLiteral("COPIED 3")) && edited.contains(QStringLiteral("WIRED 6 with wires"))
+ && edited.contains(QStringLiteral("UNDONE true"))
+ && edited.contains(QStringLiteral("SAVED true")), qPrintable(edited.right(600)));
+ const QString reopened = runChecked({QStringLiteral("--run"), dump, saved});
+ auto rowsOf = [](const QString &log) {
+ const int at = log.indexOf(QStringLiteral("ROWS "));
+ return at < 0 ? QString() : log.mid(at + 5, log.indexOf(QLatin1Char('\n'), at) - at - 5);
+ };
+ QVERIFY(!rowsOf(reopened).isEmpty());
+ QCOMPARE(rowsOf(edited), rowsOf(reopened));
+ }
+
+
// A title-block value that is a single space is kept through two saves
// (#973), and a value with accents comes back as it went in.
void singleSpaceValueKept()