mirror of
https://github.com/qelectrotech/qelectrotech-source-mirror.git
synced 2026-10-02 16:44:16 +02:00
Merge pull request #1142 from ispyisail/feat/database-from-document
Fill the project database from the file, not from the built folios
This commit is contained in:
@@ -220,12 +220,56 @@ namespace autonum
|
||||
Diagram *diagram,
|
||||
const Element *elmt,
|
||||
const Conductor *cndr)
|
||||
{
|
||||
if (!diagram) {
|
||||
return formula;
|
||||
}
|
||||
|
||||
FormulaContext context;
|
||||
const BorderTitleBlock &border = diagram->border_and_titleblock;
|
||||
context.folio = border.folio();
|
||||
context.folio_index = diagram->folioIndex();
|
||||
context.folio_total = border.folioTotal();
|
||||
context.plant = border.plant();
|
||||
context.locmach = border.locmach();
|
||||
context.title_block_fields = border.additionalFields();
|
||||
context.project_properties = diagram->project()->projectProperties();
|
||||
if (elmt)
|
||||
{
|
||||
context.has_element = true;
|
||||
context.element_position = diagram->convertPosition(elmt->scenePos());
|
||||
context.element_prefix = elmt->getPrefix();
|
||||
}
|
||||
if (cndr)
|
||||
{
|
||||
context.has_conductor = true;
|
||||
context.wire_function = cndr->properties().m_function;
|
||||
context.wire_tension_protocol = cndr->properties().m_tension_protocol;
|
||||
context.wire_color = cndr->properties().m_wire_color;
|
||||
context.wire_section = cndr->properties().m_wire_section;
|
||||
}
|
||||
return formulaToLabel(std::move(formula), seqStruct, context);
|
||||
}
|
||||
|
||||
/**
|
||||
@brief AssignVariables::formulaToLabel
|
||||
Return the formula with its variables assigned from @p context,
|
||||
which describes a folio and the element or conductor the formula
|
||||
belongs to without needing either to be built.
|
||||
@param formula - the formula to work
|
||||
@param seqStruct - struct where is stocked int values
|
||||
(struct is passed as a reference
|
||||
and modified by this static method)
|
||||
@param context - what the variables are read from
|
||||
@return the string with variable assigned.
|
||||
*/
|
||||
QString AssignVariables::formulaToLabel(QString formula,
|
||||
sequentialNumbers &seqStruct,
|
||||
const FormulaContext &context)
|
||||
{
|
||||
AssignVariables av(std::move(formula),
|
||||
seqStruct,
|
||||
diagram,
|
||||
elmt,
|
||||
cndr);
|
||||
context);
|
||||
seqStruct = av.m_seq_struct;
|
||||
return av.m_assigned_label;
|
||||
}
|
||||
@@ -347,76 +391,54 @@ namespace autonum
|
||||
|
||||
AssignVariables::AssignVariables(const QString& formula,
|
||||
const sequentialNumbers& seqStruct,
|
||||
Diagram *diagram,
|
||||
const Element *elmt,
|
||||
const Conductor *cndr):
|
||||
m_diagram(diagram),
|
||||
const FormulaContext &context):
|
||||
m_context(context),
|
||||
m_arg_formula(formula),
|
||||
m_assigned_label(formula),
|
||||
m_seq_struct(seqStruct),
|
||||
m_element(elmt),
|
||||
m_conductor(cndr)
|
||||
m_seq_struct(seqStruct)
|
||||
{
|
||||
if (m_diagram)
|
||||
m_assigned_label.replace("%F", m_context.folio);
|
||||
m_assigned_label.replace("%f",
|
||||
QString::number(m_context.folio_index+1));
|
||||
m_assigned_label.replace("%id",
|
||||
QString::number(m_context.folio_index+1));
|
||||
m_assigned_label.replace("%total",
|
||||
QString::number(m_context.folio_total));
|
||||
m_assigned_label.replace("%M", m_context.plant);
|
||||
m_assigned_label.replace("%LM", m_context.locmach);
|
||||
|
||||
QSettings settings;
|
||||
if (m_context.has_element)
|
||||
{
|
||||
m_assigned_label.replace("%F",
|
||||
m_diagram
|
||||
-> border_and_titleblock
|
||||
.folio());
|
||||
m_assigned_label.replace("%f",
|
||||
QString::number(
|
||||
m_diagram
|
||||
->folioIndex()+1));
|
||||
m_assigned_label.replace("%id",
|
||||
QString::number(
|
||||
m_diagram
|
||||
->folioIndex()+1));
|
||||
m_assigned_label.replace("%total",
|
||||
QString::number(
|
||||
m_diagram
|
||||
->border_and_titleblock
|
||||
.folioTotal()));
|
||||
m_assigned_label.replace("%M",
|
||||
m_diagram
|
||||
-> border_and_titleblock
|
||||
.plant());
|
||||
m_assigned_label.replace("%LM",
|
||||
m_diagram
|
||||
-> border_and_titleblock
|
||||
.locmach());
|
||||
|
||||
QSettings settings;
|
||||
if (m_element)
|
||||
{
|
||||
if (settings.value("border-columns_0", true).toBool()){
|
||||
m_assigned_label.replace("%c", QString::number(m_diagram->convertPosition(m_element->scenePos()).number() - 1));
|
||||
}else{
|
||||
m_assigned_label.replace("%c", QString::number(m_diagram->convertPosition(m_element->scenePos()).number()));
|
||||
}
|
||||
m_assigned_label.replace("%l", m_diagram->convertPosition(m_element->scenePos()).letter());
|
||||
m_assigned_label.replace("%prefix", m_element->getPrefix());
|
||||
if (settings.value("border-columns_0", true).toBool()){
|
||||
m_assigned_label.replace("%c", QString::number(m_context.element_position.number() - 1));
|
||||
}else{
|
||||
m_assigned_label.replace("%c", QString::number(m_context.element_position.number()));
|
||||
}
|
||||
|
||||
if (m_conductor)
|
||||
{
|
||||
m_assigned_label.replace("%wf", cndr->properties().m_function);
|
||||
m_assigned_label.replace("%wv", cndr->properties().m_tension_protocol);
|
||||
m_assigned_label.replace("%wc", cndr->properties().m_wire_color);
|
||||
m_assigned_label.replace("%ws", cndr->properties().m_wire_section);
|
||||
}
|
||||
|
||||
assignTitleBlockVar();
|
||||
assignProjectVar();
|
||||
assignSequence();
|
||||
m_assigned_label.replace("%l", m_context.element_position.letter());
|
||||
m_assigned_label.replace("%prefix", m_context.element_prefix);
|
||||
}
|
||||
|
||||
if (m_context.has_conductor)
|
||||
{
|
||||
m_assigned_label.replace("%wf", m_context.wire_function);
|
||||
m_assigned_label.replace("%wv", m_context.wire_tension_protocol);
|
||||
m_assigned_label.replace("%wc", m_context.wire_color);
|
||||
m_assigned_label.replace("%ws", m_context.wire_section);
|
||||
}
|
||||
|
||||
assignTitleBlockVar();
|
||||
assignProjectVar();
|
||||
assignSequence();
|
||||
}
|
||||
|
||||
void AssignVariables::assignTitleBlockVar()
|
||||
{
|
||||
for (int i = 0; i < m_diagram->border_and_titleblock.additionalFields().count(); i++)
|
||||
DiagramContext fields = m_context.title_block_fields;
|
||||
for (int i = 0; i < fields.count(); i++)
|
||||
{
|
||||
QString folio_variable = m_diagram->border_and_titleblock.additionalFields().keys().at(i);
|
||||
QVariant folio_value = m_diagram->border_and_titleblock.additionalFields().operator [](folio_variable);
|
||||
QString folio_variable = fields.keys().at(i);
|
||||
QVariant folio_value = fields[folio_variable];
|
||||
|
||||
if (m_assigned_label.contains(folio_variable)) {
|
||||
m_assigned_label.replace("%{" + folio_variable + "}", folio_value.toString());
|
||||
@@ -427,10 +449,11 @@ namespace autonum
|
||||
|
||||
void AssignVariables::assignProjectVar()
|
||||
{
|
||||
for (int i = 0; i < m_diagram->project()->projectProperties().count(); i++)
|
||||
DiagramContext properties = m_context.project_properties;
|
||||
for (int i = 0; i < properties.count(); i++)
|
||||
{
|
||||
QString folio_variable = m_diagram->project()->projectProperties().keys().at(i);
|
||||
QVariant folio_value = m_diagram->project()->projectProperties().operator [](folio_variable);
|
||||
QString folio_variable = properties.keys().at(i);
|
||||
QVariant folio_value = properties[folio_variable];
|
||||
|
||||
if (m_assigned_label.contains(folio_variable)) {
|
||||
m_assigned_label.replace("%{" + folio_variable + "}", folio_value.toString());
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
#ifndef ASSIGNVARIABLES_H
|
||||
#define ASSIGNVARIABLES_H
|
||||
#include "../diagramcontext.h"
|
||||
#include "../diagramposition.h"
|
||||
#include "numerotationcontext.h"
|
||||
|
||||
#include <QPointF>
|
||||
@@ -57,6 +58,36 @@ namespace autonum
|
||||
QStringList alpha;
|
||||
};
|
||||
|
||||
/**
|
||||
@brief The FormulaContext struct
|
||||
Everything a formula's variables are read from: the folio it is on
|
||||
and, for an element's or a conductor's formula, that item.
|
||||
AssignVariables::formulaToLabel() fills one from a built folio; the
|
||||
project database fills one from the project's file.
|
||||
*/
|
||||
struct FormulaContext
|
||||
{
|
||||
/// border_and_titleblock.folio(), folioIndex(), folioTotal()...
|
||||
QString folio;
|
||||
int folio_index = 0;
|
||||
int folio_total = 0;
|
||||
QString plant;
|
||||
QString locmach;
|
||||
/// the title block's additional fields and the project's properties
|
||||
DiagramContext title_block_fields;
|
||||
DiagramContext project_properties;
|
||||
/// an element's grid cell (%c, %l) and prefix
|
||||
bool has_element = false;
|
||||
DiagramPosition element_position;
|
||||
QString element_prefix;
|
||||
/// a conductor's %wf, %wv, %wc and %ws
|
||||
bool has_conductor = false;
|
||||
QString wire_function;
|
||||
QString wire_tension_protocol;
|
||||
QString wire_color;
|
||||
QString wire_section;
|
||||
};
|
||||
|
||||
/**
|
||||
@brief The AssignVariables class
|
||||
This class assign variable of a formula string.
|
||||
@@ -66,21 +97,20 @@ namespace autonum
|
||||
{
|
||||
public:
|
||||
static QString formulaToLabel (QString formula, sequentialNumbers &seqStruct, Diagram *diagram, const Element *elmt = nullptr, const Conductor *cndr = nullptr);
|
||||
static QString formulaToLabel (QString formula, sequentialNumbers &seqStruct, const FormulaContext &context);
|
||||
static QString replaceVariable (const QString &formula, const DiagramContext &dc);
|
||||
static QString genericXref (const Element *element);
|
||||
|
||||
private:
|
||||
AssignVariables(const QString& formula, const sequentialNumbers& seqStruct , Diagram *diagram, const Element *elmt = nullptr, const Conductor *cndr = nullptr);
|
||||
AssignVariables(const QString& formula, const sequentialNumbers& seqStruct, const FormulaContext &context);
|
||||
void assignTitleBlockVar();
|
||||
void assignProjectVar();
|
||||
void assignSequence();
|
||||
|
||||
Diagram *m_diagram = nullptr;
|
||||
const FormulaContext &m_context;
|
||||
QString m_arg_formula;
|
||||
QString m_assigned_label;
|
||||
sequentialNumbers m_seq_struct;
|
||||
const Element *m_element = nullptr;
|
||||
const Conductor *m_conductor = nullptr;
|
||||
};
|
||||
|
||||
void setSequentialToList(QStringList &list, NumerotationContext &nc, const QString& type);
|
||||
|
||||
@@ -19,6 +19,9 @@
|
||||
|
||||
#include "sqlreadonly.h"
|
||||
|
||||
#include "../autoNum/assignvariables.h"
|
||||
#include "../borderproperties.h"
|
||||
#include "../bordertitleblock.h"
|
||||
#include "../diagram.h"
|
||||
#include "../diagramposition.h"
|
||||
#include "../elementprovider.h"
|
||||
@@ -32,8 +35,16 @@
|
||||
#include "../qetgraphicsitem/terminal.h"
|
||||
#include "../qetinformation.h"
|
||||
#include "../qetproject.h"
|
||||
#include "../qet.h"
|
||||
#include "../titleblockproperties.h"
|
||||
#include "../ElementsCollection/xmlelementcollection.h"
|
||||
#include "../properties/elementdata.h"
|
||||
|
||||
#include <QLocale>
|
||||
#include <QDate>
|
||||
#include <QDomDocument>
|
||||
|
||||
#include <functional>
|
||||
#include <QMetaEnum>
|
||||
#include <QTextDocument>
|
||||
#include <QFile>
|
||||
@@ -125,6 +136,480 @@ void projectDataBase::updateDB()
|
||||
emit dataBaseUpdated();
|
||||
}
|
||||
|
||||
/**
|
||||
@brief projectDataBase::updateDB
|
||||
updateDB() for a project just read from @p document.
|
||||
|
||||
The diagram, diagram_info, element, element_info, terminal and conductor
|
||||
tables are filled from the document itself when it carries everything
|
||||
they need -- see populateFromDocument() -- and from the built folios
|
||||
otherwise, as updateDB() does. Shapes, texts and pictures always come from
|
||||
the built folios: their boxes need the fonts and pens a folio renders with.
|
||||
|
||||
The two fills give the same tables (tst_databasefromdocument checks it on
|
||||
the shipped examples). Reading the document instead of the folios is what
|
||||
the database needs before a project can be opened without building every
|
||||
folio; see DB-FROM-XML-SCOPE.md in qelectrotech-docker.
|
||||
*/
|
||||
void projectDataBase::updateDB(const QDomDocument &document)
|
||||
{
|
||||
if (m_update_blocked || !m_content_changed) {
|
||||
updateDB();
|
||||
return;
|
||||
}
|
||||
//QET_DATABASE_FROM_FOLIOS=1 keeps the fill from the built folios:
|
||||
//a way back should the two ever disagree, and what the test that
|
||||
//says they do not compares against.
|
||||
QString why;
|
||||
if (qEnvironmentVariableIntValue("QET_DATABASE_FROM_FOLIOS") == 1) {
|
||||
why = QStringLiteral("QET_DATABASE_FROM_FOLIOS is set");
|
||||
} else if (populateFromDocument(document, &why)) {
|
||||
qInfo() << "Project database filled from the document";
|
||||
populateDrawingItemTables();
|
||||
m_content_changed = false;
|
||||
emit dataBaseUpdated();
|
||||
return;
|
||||
}
|
||||
qInfo().noquote() << "Project database filled from the folios:" << why;
|
||||
updateDB();
|
||||
}
|
||||
|
||||
namespace {
|
||||
struct DocumentTerminal
|
||||
{
|
||||
QString uuid;
|
||||
QString name;
|
||||
bool master_label = false;
|
||||
};
|
||||
|
||||
struct DocumentDefinition
|
||||
{
|
||||
QString type;
|
||||
QString sub_type;
|
||||
QHash<QUuid, DocumentTerminal> terminals;
|
||||
};
|
||||
|
||||
struct DocumentElement
|
||||
{
|
||||
QString uuid;
|
||||
QString diagram_uuid;
|
||||
QString pos;
|
||||
QString type;
|
||||
QString sub_type;
|
||||
QVariant group;
|
||||
DiagramContext informations;
|
||||
QString label;
|
||||
QHash<QUuid, DocumentTerminal> terminals;
|
||||
};
|
||||
|
||||
//The sequential values an element or a conductor was saved with, as
|
||||
//Element::fromXml() and Conductor::fromXml() read them -- false for
|
||||
//the attributes files written before <sequentialNumbers> carry.
|
||||
bool readSequence(const QDomElement &item, autonum::sequentialNumbers *sequence)
|
||||
{
|
||||
for (const char *name : {"sequ_1", "sequf_1", "seqt_1", "seqtf_1", "seqh_1"}) {
|
||||
if (item.hasAttribute(QLatin1String(name))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
sequence->fromXml(item.firstChildElement(QStringLiteral("sequentialNumbers")));
|
||||
return true;
|
||||
}
|
||||
|
||||
struct DocumentConductor
|
||||
{
|
||||
QString uuid;
|
||||
QString diagram_uuid;
|
||||
QString element1, terminal1, element2, terminal2;
|
||||
QString text;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
@brief projectDataBase::populateFromDocument
|
||||
Fill the diagram, diagram_info, element, element_info, terminal and
|
||||
conductor tables from @p document, the project as read from its file,
|
||||
with the same values the built folios give -- using the same code:
|
||||
a BorderTitleBlock read from each folio's XML gives the title-block
|
||||
values and each element's grid cell, the project's embedded collection
|
||||
gives each element's definition, and the binders are shared.
|
||||
|
||||
Nothing is written unless the whole document can be read this way. It
|
||||
cannot -- and false is returned, for the caller to fill from the folios
|
||||
instead -- when a folio, element or conductor carries no saved uuid (the
|
||||
folios derive one on load), a conductor names its ends the older way,
|
||||
a folio number uses %autonum, a conductor ends on a terminal that shows
|
||||
its master's contact label, an element's definition is missing or
|
||||
not one the folios could build, two elements on a folio number their
|
||||
terminals alike, or sequential numbers are saved as the attributes
|
||||
older files carry. A file saved by a current QElectroTech carries
|
||||
everything else.
|
||||
|
||||
A label or a conductor text made from a formula is worked out again,
|
||||
as the folios do, with the same AssignVariables code: the one saved
|
||||
in the file is what the formula gave when it was saved, and a folio
|
||||
added or moved since changes it. A frozen conductor text is left to the
|
||||
folios, which work it out part-way through loading.
|
||||
@return true if the tables were filled
|
||||
*/
|
||||
bool projectDataBase::populateFromDocument(const QDomDocument &document, QString *why)
|
||||
{
|
||||
auto refuse = [why](const QString &reason) {
|
||||
if (why) *why = reason;
|
||||
return false;
|
||||
};
|
||||
if (!m_project) {
|
||||
return refuse(QStringLiteral("no project"));
|
||||
}
|
||||
|
||||
//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<Diagram *> diagrams = m_project->diagrams();
|
||||
if (diagram_nodes.size() != diagrams.size()) {
|
||||
return refuse(QStringLiteral("the folios are not the document's"));
|
||||
}
|
||||
|
||||
const DiagramContext project_wide = m_project->projectWideProperties();
|
||||
const DiagramContext project_properties = m_project->projectProperties();
|
||||
//One border and title block read from each folio's XML in turn:
|
||||
//only what it gives is kept.
|
||||
BorderTitleBlock reader;
|
||||
QList<DiagramContext> diagram_infos;
|
||||
QList<QDate> diagram_dates;
|
||||
QList<QUuid> diagram_uuids;
|
||||
QList<DocumentElement> elements;
|
||||
QList<DocumentConductor> conductors;
|
||||
QSet<QUuid> element_uuids;
|
||||
QHash<QString, DocumentDefinition> definitions; //by element type
|
||||
|
||||
//The project's own definitions by "embed://" path, which is how an
|
||||
//element's type names them: one walk of the embedded collection
|
||||
//rather than resolving an ElementsLocation per type.
|
||||
QHash<QString, QDomElement> stored;
|
||||
std::function<void (const QDomElement &, const QString &)> walk =
|
||||
[&stored, &walk](const QDomElement &category, const QString &path) {
|
||||
for (QDomElement child = category.firstChildElement() ;
|
||||
!child.isNull() ; child = child.nextSiblingElement()) {
|
||||
const QString name = path + child.attribute(QStringLiteral("name"));
|
||||
if (child.tagName() == QLatin1String("category")) {
|
||||
walk(child, name + QLatin1Char('/'));
|
||||
} else if (child.tagName() == QLatin1String("element")) {
|
||||
stored.insert(name, child.firstChildElement(QStringLiteral("definition")));
|
||||
}
|
||||
}
|
||||
};
|
||||
if (auto collection = m_project->embeddedElementCollection()) {
|
||||
walk(collection->root(), QStringLiteral("embed://"));
|
||||
}
|
||||
|
||||
for (int i = 0 ; i < diagram_nodes.size() ; ++i)
|
||||
{
|
||||
const QDomElement diagram_xml = diagram_nodes.at(i).toElement();
|
||||
const QUuid diagram_uuid(diagram_xml.attribute(QStringLiteral("uuid")));
|
||||
if (diagram_uuid.isNull() || diagram_uuid != diagrams.at(i)->uuid()) {
|
||||
return refuse(QStringLiteral("a folio has no saved uuid"));
|
||||
}
|
||||
|
||||
BorderTitleBlock *border = &reader;
|
||||
//As a new Diagram's border starts, before initFromXml() reads it
|
||||
border->importBorder(BorderProperties());
|
||||
border->importTitleBlock(TitleBlockProperties());
|
||||
border->titleBlockFromXml(diagram_xml);
|
||||
border->borderFromXml(diagram_xml);
|
||||
if (border->folio().contains(QStringLiteral("%autonum"))) {
|
||||
return refuse(QStringLiteral("a folio number uses %autonum"));
|
||||
}
|
||||
border->setFolioData(i + 1, int(diagram_nodes.size()), QString(), project_wide);
|
||||
|
||||
//What a formula on this folio is worked out from, as
|
||||
//AssignVariables::formulaToLabel() reads it off a built folio.
|
||||
autonum::FormulaContext folio_context;
|
||||
folio_context.folio = border->folio();
|
||||
folio_context.folio_index = i;
|
||||
folio_context.folio_total = border->folioTotal();
|
||||
folio_context.plant = border->plant();
|
||||
folio_context.locmach = border->locmach();
|
||||
folio_context.title_block_fields = border->additionalFields();
|
||||
folio_context.project_properties = project_properties;
|
||||
|
||||
QHash<QUuid, int> on_this_folio; //element uuid -> index in elements
|
||||
QSet<int> terminal_ids; //the older terminal ids used so far
|
||||
for (QDomElement element_xml : QET::findInDomElement(
|
||||
diagram_xml, QStringLiteral("elements"), QStringLiteral("element")))
|
||||
{
|
||||
//Skipped by Diagram::fromXml() as well
|
||||
if (!Element::valideXml(element_xml)) {
|
||||
continue;
|
||||
}
|
||||
const QUuid uuid(element_xml.attribute(QStringLiteral("uuid")));
|
||||
if (uuid.isNull() || element_uuids.contains(uuid)) {
|
||||
return refuse(QStringLiteral("an element has no saved uuid, or shares one"));
|
||||
}
|
||||
element_uuids.insert(uuid);
|
||||
|
||||
//Element::fromXml() refuses an element whose terminals are
|
||||
//numbered like those of one read before it on the folio;
|
||||
//which of them it matches needs their geometry, so leave any
|
||||
//such folio to the folios. A current file numbers them
|
||||
//across the folio.
|
||||
QSet<int> ids;
|
||||
for (QDomElement t : QET::findInDomElement(
|
||||
element_xml, QStringLiteral("terminals"), QStringLiteral("terminal"))) {
|
||||
if (Terminal::valideXml(t)) {
|
||||
ids.insert(t.attribute(QStringLiteral("id")).toInt());
|
||||
}
|
||||
}
|
||||
if (terminal_ids.intersects(ids)) {
|
||||
return refuse(QStringLiteral("two elements on a folio number their terminals alike"));
|
||||
}
|
||||
terminal_ids.unite(ids);
|
||||
|
||||
//The definition the folio builds the element from, with the
|
||||
//checks Element::buildFromXml() refuses an element on --
|
||||
//read once per type, as many elements share one.
|
||||
const QString type = element_xml.attribute(QStringLiteral("type"));
|
||||
auto known = definitions.constFind(type);
|
||||
if (known == definitions.constEnd())
|
||||
{
|
||||
const QDomElement definition = stored.value(type);
|
||||
if (definition.isNull()) {
|
||||
return refuse(QStringLiteral("an element's definition is not in the project"));
|
||||
}
|
||||
int number;
|
||||
if (definition.tagName() != QLatin1String("definition")
|
||||
|| definition.attribute(QStringLiteral("type")) != QLatin1String("element")
|
||||
|| !QET::attributeIsAnInteger(definition, QStringLiteral("width"), &number)
|
||||
|| !QET::attributeIsAnInteger(definition, QStringLiteral("height"), &number)
|
||||
|| !QET::attributeIsAnInteger(definition, QStringLiteral("hotspot_x"), &number)
|
||||
|| !QET::attributeIsAnInteger(definition, QStringLiteral("hotspot_y"), &number)
|
||||
|| definition.firstChild().isNull()) {
|
||||
return refuse(QStringLiteral("an element's definition cannot be built"));
|
||||
}
|
||||
DocumentDefinition read;
|
||||
ElementData data;
|
||||
data.fromXml(definition);
|
||||
read.type = data.typeToString();
|
||||
read.sub_type = data.masterTypeToString();
|
||||
const QDomElement description = definition.firstChildElement(QStringLiteral("description"));
|
||||
for (QDomElement t = description.firstChildElement(QStringLiteral("terminal")) ;
|
||||
!t.isNull() ; t = t.nextSiblingElement(QStringLiteral("terminal")))
|
||||
{
|
||||
const QUuid terminal_uuid(t.attribute(QStringLiteral("uuid")));
|
||||
if (terminal_uuid.isNull()) {
|
||||
continue;
|
||||
}
|
||||
DocumentTerminal terminal;
|
||||
terminal.uuid = terminal_uuid.toString();
|
||||
terminal.name = t.attribute(QStringLiteral("name"));
|
||||
terminal.master_label = t.attribute(QStringLiteral("use_master_label")) == QLatin1String("true");
|
||||
read.terminals.insert(terminal_uuid, terminal);
|
||||
}
|
||||
known = definitions.insert(type, read);
|
||||
}
|
||||
|
||||
DocumentElement element;
|
||||
element.uuid = uuid.toString();
|
||||
element.diagram_uuid = diagram_uuid.toString();
|
||||
DiagramPosition position = border->convertPosition(
|
||||
QPointF(element_xml.attribute(QStringLiteral("x")).toDouble(),
|
||||
element_xml.attribute(QStringLiteral("y")).toDouble()));
|
||||
element.pos = position.toString();
|
||||
element.type = known->type;
|
||||
element.sub_type = known->sub_type;
|
||||
element.terminals = known->terminals;
|
||||
const QUuid group = ItemGroups::read(element_xml);
|
||||
element.group = group.isNull() ? QVariant() : QVariant(group.toString());
|
||||
element.informations.fromXml(
|
||||
element_xml.firstChildElement(QStringLiteral("elementInformations")),
|
||||
QStringLiteral("elementInformation"));
|
||||
//Element::actualLabel()
|
||||
const QString formula = element.informations.value(QStringLiteral("formula")).toString();
|
||||
if (formula.isEmpty()) {
|
||||
element.label = element.informations.value(QStringLiteral("label")).toString();
|
||||
} else {
|
||||
autonum::sequentialNumbers sequence;
|
||||
if (!readSequence(element_xml, &sequence)) {
|
||||
return refuse(QStringLiteral("an element's sequential numbers are saved the older way"));
|
||||
}
|
||||
autonum::FormulaContext context = folio_context;
|
||||
context.has_element = true;
|
||||
context.element_position = position;
|
||||
context.element_prefix = element_xml.attribute(QStringLiteral("prefix"));
|
||||
element.label = autonum::AssignVariables::formulaToLabel(formula, sequence, context);
|
||||
}
|
||||
|
||||
on_this_folio.insert(uuid, int(elements.size()));
|
||||
elements << element;
|
||||
}
|
||||
|
||||
//The conductors the folio builds, and only those: an end that is
|
||||
//not found, a conductor from a terminal to itself and a second
|
||||
//conductor between the same two terminals are all dropped on load.
|
||||
QSet<QString> joined;
|
||||
for (QDomElement conductor_xml : QET::findInDomElement(
|
||||
diagram_xml, QStringLiteral("conductors"), QStringLiteral("conductor")))
|
||||
{
|
||||
if (!Conductor::valideXml(conductor_xml)) {
|
||||
continue;
|
||||
}
|
||||
const QUuid uuid(conductor_xml.attribute(QStringLiteral("uuid")));
|
||||
if (uuid.isNull()
|
||||
|| !conductor_xml.hasAttribute(QStringLiteral("element1"))
|
||||
|| !conductor_xml.hasAttribute(QStringLiteral("element2"))) {
|
||||
return refuse(QStringLiteral("a conductor has no saved uuid, or names its ends the older way"));
|
||||
}
|
||||
QString ends[2][2];
|
||||
bool found = true;
|
||||
for (int n = 0 ; n < 2 ; ++n)
|
||||
{
|
||||
const QString index = QString::number(n + 1);
|
||||
const QUuid owner(conductor_xml.attribute(QStringLiteral("element") + index));
|
||||
const QUuid terminal(conductor_xml.attribute(QStringLiteral("terminal") + index));
|
||||
const int e = on_this_folio.value(owner, -1);
|
||||
if (e < 0) {
|
||||
found = false;
|
||||
break;
|
||||
}
|
||||
if (!elements.at(e).terminals.contains(terminal)) {
|
||||
//The folio would try the terminal's derived uuid;
|
||||
//leave that to it.
|
||||
return refuse(QStringLiteral("a conductor ends on a terminal not in its element's definition"));
|
||||
}
|
||||
const DocumentTerminal &t = elements.at(e).terminals.value(terminal);
|
||||
if (t.master_label) {
|
||||
return refuse(QStringLiteral("a conductor ends on a terminal showing its master's label"));
|
||||
}
|
||||
ends[n][0] = elements.at(e).uuid;
|
||||
ends[n][1] = t.uuid;
|
||||
}
|
||||
if (!found) {
|
||||
continue;
|
||||
}
|
||||
const QString a = ends[0][0] + ends[0][1], b = ends[1][0] + ends[1][1];
|
||||
if (a == b) {
|
||||
continue;
|
||||
}
|
||||
const QString pair = a < b ? a + b : b + a;
|
||||
if (joined.contains(pair)) {
|
||||
continue;
|
||||
}
|
||||
joined.insert(pair);
|
||||
|
||||
DocumentConductor conductor;
|
||||
conductor.uuid = uuid.toString();
|
||||
conductor.diagram_uuid = diagram_uuid.toString();
|
||||
conductor.element1 = ends[0][0];
|
||||
conductor.terminal1 = ends[0][1];
|
||||
conductor.element2 = ends[1][0];
|
||||
conductor.terminal2 = ends[1][1];
|
||||
//ConductorProperties::fromXml()'s text, without reading the
|
||||
//rest of the properties -- or, as Conductor::refreshText()
|
||||
//makes it, what its formula gives.
|
||||
const QString formula = conductor_xml.attribute(QStringLiteral("formula"));
|
||||
if (formula.isEmpty()) {
|
||||
conductor.text = conductor_xml.attribute(QStringLiteral("num"));
|
||||
} else {
|
||||
autonum::sequentialNumbers sequence;
|
||||
if (conductor_xml.attribute(QStringLiteral("freezeLabel")) == QLatin1String("true")) {
|
||||
return refuse(QStringLiteral("a conductor's text made from a formula is frozen"));
|
||||
}
|
||||
if (!readSequence(conductor_xml, &sequence)) {
|
||||
return refuse(QStringLiteral("a conductor's sequential numbers are saved the older way"));
|
||||
}
|
||||
autonum::FormulaContext context = folio_context;
|
||||
context.has_conductor = true;
|
||||
context.wire_function = conductor_xml.attribute(QStringLiteral("function"));
|
||||
context.wire_tension_protocol = conductor_xml.attribute(QStringLiteral("tension_protocol"));
|
||||
context.wire_color = conductor_xml.attribute(QStringLiteral("conductor_color"));
|
||||
context.wire_section = conductor_xml.attribute(QStringLiteral("conductor_section"));
|
||||
conductor.text = autonum::AssignVariables::formulaToLabel(formula, sequence, context);
|
||||
}
|
||||
conductors << conductor;
|
||||
}
|
||||
|
||||
diagram_uuids << diagram_uuid;
|
||||
diagram_infos << border->titleblockInformation();
|
||||
diagram_dates << border->date();
|
||||
}
|
||||
|
||||
//Everything could be read: write it.
|
||||
QSqlQuery query(m_data_base);
|
||||
for (const QString &table : {QStringLiteral("diagram"), QStringLiteral("diagram_info"),
|
||||
QStringLiteral("element"), QStringLiteral("element_info"),
|
||||
QStringLiteral("conductor"), QStringLiteral("terminal")}) {
|
||||
query.exec(QStringLiteral("DELETE FROM ") + table);
|
||||
}
|
||||
|
||||
for (int i = 0 ; i < diagram_uuids.size() ; ++i)
|
||||
{
|
||||
m_insert_diagram_query.bindValue(":uuid", diagram_uuids.at(i).toString());
|
||||
m_insert_diagram_query.bindValue(":pos", i + 1);
|
||||
if (!m_insert_diagram_query.exec()) {
|
||||
qDebug() << "projectDataBase::populateFromDocument diagram insert error : " << m_insert_diagram_query.lastError();
|
||||
}
|
||||
bindDiagramInfoValues(m_insert_diagram_info_query, diagram_uuids.at(i),
|
||||
diagram_infos.at(i), diagram_dates.at(i));
|
||||
if (!m_insert_diagram_info_query.exec()) {
|
||||
qDebug() << "projectDataBase::populateFromDocument diagram_info insert error : " << m_insert_diagram_info_query.lastError();
|
||||
}
|
||||
}
|
||||
|
||||
for (const DocumentElement &element : std::as_const(elements))
|
||||
{
|
||||
m_insert_elements_query.bindValue(QStringLiteral(":uuid"), element.uuid);
|
||||
m_insert_elements_query.bindValue(QStringLiteral(":diagram_uuid"), element.diagram_uuid);
|
||||
m_insert_elements_query.bindValue(QStringLiteral(":pos"), element.pos);
|
||||
m_insert_elements_query.bindValue(QStringLiteral(":type"), element.type);
|
||||
m_insert_elements_query.bindValue(QStringLiteral(":sub_type"), element.sub_type);
|
||||
m_insert_elements_query.bindValue(QStringLiteral(":group_uuid"), element.group);
|
||||
if (!m_insert_elements_query.exec()) {
|
||||
qDebug() << "projectDataBase::populateFromDocument element insert error : " << m_insert_elements_query.lastError();
|
||||
}
|
||||
bindElementInfoValues(m_insert_element_info_query, element.uuid, element.informations,
|
||||
element.label);
|
||||
if (!m_insert_element_info_query.exec()) {
|
||||
qDebug() << "projectDataBase::populateFromDocument element_info insert error : " << m_insert_element_info_query.lastError();
|
||||
}
|
||||
}
|
||||
|
||||
QHash<QString, int> element_index;
|
||||
for (int i = 0 ; i < elements.size() ; ++i) {
|
||||
element_index.insert(elements.at(i).uuid, i);
|
||||
}
|
||||
for (const DocumentConductor &conductor : std::as_const(conductors))
|
||||
{
|
||||
for (const auto &end : {std::make_pair(conductor.element1, conductor.terminal1),
|
||||
std::make_pair(conductor.element2, conductor.terminal2)}) {
|
||||
const DocumentElement &owner = elements.at(element_index.value(end.first));
|
||||
insertTerminal(end.second, end.first,
|
||||
owner.terminals.value(QUuid(end.second)).name);
|
||||
}
|
||||
m_insert_conductor_query.bindValue(QStringLiteral(":uuid"), conductor.uuid);
|
||||
m_insert_conductor_query.bindValue(QStringLiteral(":diagram_uuid"), conductor.diagram_uuid);
|
||||
m_insert_conductor_query.bindValue(QStringLiteral(":terminal1_uuid"), conductor.terminal1);
|
||||
m_insert_conductor_query.bindValue(QStringLiteral(":terminal1_element_uuid"), conductor.element1);
|
||||
m_insert_conductor_query.bindValue(QStringLiteral(":terminal2_uuid"), conductor.terminal2);
|
||||
m_insert_conductor_query.bindValue(QStringLiteral(":terminal2_element_uuid"), conductor.element2);
|
||||
m_insert_conductor_query.bindValue(QStringLiteral(":text"), conductor.text);
|
||||
if (!m_insert_conductor_query.exec()) {
|
||||
qDebug() << "projectDataBase::populateFromDocument conductor insert error : " << m_insert_conductor_query.lastError();
|
||||
}
|
||||
}
|
||||
|
||||
//While every folio is still built, their conductors are watched for
|
||||
//property changes as populateConductorTable() does -- the one walk
|
||||
//over the built folios left here.
|
||||
for (Diagram *diagram : diagrams) {
|
||||
for (Conductor *conductor : diagram->conductors()) {
|
||||
if (conductor->terminal1->parentElement() && conductor->terminal2->parentElement()) {
|
||||
watchConductor(conductor);
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
@brief projectDataBase::setUpdateBlocked
|
||||
@param blocked : whether updateDB() should skip the full rebuild
|
||||
@@ -1514,9 +1999,21 @@ void projectDataBase::populateConductorTable()
|
||||
*/
|
||||
void projectDataBase::insertTerminal(Terminal *terminal)
|
||||
{
|
||||
m_insert_terminal_query.bindValue(":uuid", terminal->stableUuid().toString());
|
||||
m_insert_terminal_query.bindValue(":element_uuid", terminal->parentElement()->uuid().toString());
|
||||
m_insert_terminal_query.bindValue(":name", terminal->name());
|
||||
insertTerminal(terminal->stableUuid().toString(),
|
||||
terminal->parentElement()->uuid().toString(),
|
||||
terminal->name());
|
||||
}
|
||||
|
||||
/**
|
||||
@brief projectDataBase::insertTerminal
|
||||
insertTerminal(Terminal *) from values rather than a live terminal.
|
||||
*/
|
||||
void projectDataBase::insertTerminal(const QString &uuid, const QString &element_uuid,
|
||||
const QString &name)
|
||||
{
|
||||
m_insert_terminal_query.bindValue(":uuid", uuid);
|
||||
m_insert_terminal_query.bindValue(":element_uuid", element_uuid);
|
||||
m_insert_terminal_query.bindValue(":name", name);
|
||||
if (!m_insert_terminal_query.exec()) {
|
||||
qDebug() << "projectDataBase::insertTerminal insert error : " << m_insert_terminal_query.lastError();
|
||||
}
|
||||
@@ -1733,11 +2230,48 @@ void projectDataBase::bindElementInfoValues(QSqlQuery &query, Element *element)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@brief projectDataBase::bindElementInfoValues
|
||||
bindElementInfoValues(QSqlQuery &, Element *) from values: the element's
|
||||
information and the label to store for it.
|
||||
*/
|
||||
void projectDataBase::bindElementInfoValues(QSqlQuery &query, const QString &element_uuid,
|
||||
const DiagramContext &informations,
|
||||
const QString &label)
|
||||
{
|
||||
query.bindValue(QStringLiteral(":uuid"), element_uuid);
|
||||
for (const auto &key : QETInformation::elementInfoKeys()) {
|
||||
query.bindValue(QStringLiteral(":") + key,
|
||||
key == QLatin1String("label") ? label
|
||||
: informations[key].toString());
|
||||
}
|
||||
}
|
||||
|
||||
void projectDataBase::bindDiagramInfoValues(QSqlQuery &query, Diagram *diagram)
|
||||
{
|
||||
query.bindValue(":uuid", diagram->uuid());
|
||||
bindDiagramInfoValues(query, diagram->uuid(), diagram->border_and_titleblock);
|
||||
}
|
||||
|
||||
/**
|
||||
@brief projectDataBase::bindDiagramInfoValues
|
||||
bindDiagramInfoValues(QSqlQuery &, Diagram *) from a folio's uuid and its
|
||||
border and title block, which need not belong to a built folio.
|
||||
*/
|
||||
void projectDataBase::bindDiagramInfoValues(QSqlQuery &query, const QUuid &diagram_uuid,
|
||||
const BorderTitleBlock &border)
|
||||
{
|
||||
bindDiagramInfoValues(query, diagram_uuid, border.titleblockInformation(), border.date());
|
||||
}
|
||||
|
||||
/**
|
||||
@brief projectDataBase::bindDiagramInfoValues
|
||||
The same from a title block's information and date.
|
||||
*/
|
||||
void projectDataBase::bindDiagramInfoValues(QSqlQuery &query, const QUuid &diagram_uuid,
|
||||
const DiagramContext &infos, const QDate &date)
|
||||
{
|
||||
query.bindValue(":uuid", diagram_uuid);
|
||||
|
||||
auto infos = diagram->border_and_titleblock.titleblockInformation();
|
||||
for (auto key : QETInformation::diagramInfoKeys())
|
||||
{
|
||||
if (key == "date") {
|
||||
@@ -1745,8 +2279,7 @@ void projectDataBase::bindDiagramInfoValues(QSqlQuery &query, Diagram *diagram)
|
||||
//back: that text is the locale's short format, and where
|
||||
//it has a two-digit year (en_US "M/d/yy") toDate() reads
|
||||
//2010 back as 1910.
|
||||
query.bindValue(QStringLiteral(":date"),
|
||||
diagram->border_and_titleblock.date());
|
||||
query.bindValue(QStringLiteral(":date"), date);
|
||||
} else {
|
||||
auto value = infos.value(key);
|
||||
auto bind = key.prepend(":");
|
||||
|
||||
@@ -33,6 +33,10 @@ class Diagram;
|
||||
class Conductor;
|
||||
class Terminal;
|
||||
class QGraphicsItem;
|
||||
class QDomDocument;
|
||||
class BorderTitleBlock;
|
||||
class DiagramContext;
|
||||
class QDate;
|
||||
|
||||
/**
|
||||
@brief The projectDataBase class
|
||||
@@ -50,6 +54,13 @@ class projectDataBase : public QObject
|
||||
virtual ~projectDataBase() override;
|
||||
|
||||
void updateDB();
|
||||
/**
|
||||
updateDB() for a project just read from @p document: the
|
||||
diagram, element, terminal and conductor tables are filled
|
||||
from the document when it carries everything they need (see
|
||||
populateFromDocument()), from the built folios otherwise.
|
||||
*/
|
||||
void updateDB(const QDomDocument &document);
|
||||
/**
|
||||
Suppress the full rebuild performed by updateDB().
|
||||
|
||||
@@ -131,18 +142,28 @@ class projectDataBase : public QObject
|
||||
void populateDiagramInfoTable();
|
||||
void populateConductorTable();
|
||||
void populateDrawingItemTables();
|
||||
bool populateFromDocument(const QDomDocument &document, QString *why = nullptr);
|
||||
bool writeDrawingItem(QObject *object);
|
||||
void flushDrawingItems();
|
||||
void forgetDrawingItem(QObject *object);
|
||||
void bindConductorValues(QSqlQuery &query, Conductor *conductor, Diagram *diagram);
|
||||
void watchConductor(Conductor *conductor);
|
||||
void insertTerminal(Terminal *terminal);
|
||||
void insertTerminal(const QString &uuid, const QString &element_uuid,
|
||||
const QString &name);
|
||||
void prepareQuery();
|
||||
static QHash<QString, QString> elementInfoToString(
|
||||
Element *elmt);
|
||||
void bindDiagramInfoValues(QSqlQuery &query, Diagram *diagram);
|
||||
static void bindDiagramInfoValues(QSqlQuery &query, const QUuid &diagram_uuid,
|
||||
const BorderTitleBlock &border);
|
||||
static void bindDiagramInfoValues(QSqlQuery &query, const QUuid &diagram_uuid,
|
||||
const DiagramContext &infos, const QDate &date);
|
||||
static void bindElementValues(QSqlQuery &query, Element *element, Diagram *diagram);
|
||||
static void bindElementInfoValues(QSqlQuery &query, Element *element);
|
||||
static void bindElementInfoValues(QSqlQuery &query, const QString &element_uuid,
|
||||
const DiagramContext &informations,
|
||||
const QString &label);
|
||||
|
||||
private:
|
||||
QPointer<QETProject> m_project;
|
||||
|
||||
+15
-4
@@ -1880,7 +1880,7 @@ void QETProject::readProjectXml(QDomDocument &xml_project)
|
||||
|
||||
m_data_base.blockSignals(false);
|
||||
m_data_base.setUpdateBlocked(false);
|
||||
m_data_base.updateDB();
|
||||
m_data_base.updateDB(xml_project);
|
||||
const qint64 database_ms = phase_timer.elapsed();
|
||||
|
||||
qInfo().nospace()
|
||||
@@ -2473,14 +2473,25 @@ bool QETProject::projectWasModified()
|
||||
Indique a chaque schema du projet quel est son numero de folio et combien de
|
||||
folio le projet contient.
|
||||
*/
|
||||
void QETProject::updateDiagramsFolioData()
|
||||
/**
|
||||
@brief QETProject::projectWideProperties
|
||||
@return the project's properties as every folio's title block sees them:
|
||||
the user's project properties plus the project's title, path and file name.
|
||||
*/
|
||||
DiagramContext QETProject::projectWideProperties()
|
||||
{
|
||||
int total_folio = m_diagrams_list.count();
|
||||
|
||||
DiagramContext project_wide_properties = m_project_properties;
|
||||
project_wide_properties.addValue("projecttitle", title());
|
||||
project_wide_properties.addValue("projectpath", filePath());
|
||||
project_wide_properties.addValue("projectfilename", QFileInfo(filePath()).completeBaseName());
|
||||
return project_wide_properties;
|
||||
}
|
||||
|
||||
void QETProject::updateDiagramsFolioData()
|
||||
{
|
||||
int total_folio = m_diagrams_list.count();
|
||||
|
||||
const DiagramContext project_wide_properties = projectWideProperties();
|
||||
|
||||
for (int i = 0 ; i < total_folio ; ++ i)
|
||||
{
|
||||
|
||||
@@ -221,6 +221,7 @@ class QETProject : public QObject
|
||||
bool projectWasModified();
|
||||
bool projectOptionsWereModified();
|
||||
DiagramContext projectProperties();
|
||||
DiagramContext projectWideProperties();
|
||||
void setProjectProperties(const DiagramContext &);
|
||||
QUndoStack* undoStack() {return m_undo_stack;}
|
||||
|
||||
|
||||
@@ -354,6 +354,19 @@ if(QET_HAS_SCRIPTING)
|
||||
target_compile_definitions(tst_scriptconductoruuid PRIVATE
|
||||
"QET_TEST_BINARY_PATH=\"$<TARGET_FILE:qelectrotech>\"")
|
||||
|
||||
# The project database filled from the file as it is read holds exactly
|
||||
# what it holds filled from the built folios (QET_DATABASE_FROM_FOLIOS=1),
|
||||
# on every example saved once; an older file falls back, saying why.
|
||||
add_executable(
|
||||
tst_databasefromdocument
|
||||
tst_databasefromdocument.cpp)
|
||||
add_test(NAME tst_databasefromdocument COMMAND tst_databasefromdocument)
|
||||
add_dependencies(tst_databasefromdocument qelectrotech)
|
||||
target_link_libraries(tst_databasefromdocument PRIVATE Qt::Test Qt::Xml)
|
||||
target_compile_definitions(tst_databasefromdocument PRIVATE
|
||||
"QET_TEST_BINARY_PATH=\"$<TARGET_FILE:qelectrotech>\""
|
||||
"QET_EXAMPLES_DIR=\"${QET_DIR}/examples\"")
|
||||
|
||||
# The project database keeps a folio's date with its century: 741.qet's
|
||||
# 2010-09-21, read through project_summary_view under LC_ALL=en_US.UTF-8.
|
||||
add_executable(
|
||||
|
||||
@@ -0,0 +1,295 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
#include <QtTest>
|
||||
|
||||
#include <QDir>
|
||||
#include <QDomDocument>
|
||||
#include <QFile>
|
||||
#include <QJsonArray>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QProcess>
|
||||
#include <QProcessEnvironment>
|
||||
#include <QTemporaryDir>
|
||||
|
||||
// A project's database, filled from the file as it is read, holds exactly
|
||||
// what the same database filled from the built folios holds. Every example
|
||||
// is saved once (so that it carries the uuids a current QElectroTech
|
||||
// writes), then opened twice through the real binary's --run: once as is,
|
||||
// once with QET_DATABASE_FROM_FOLIOS=1, and the six tables compared.
|
||||
class tst_databasefromdocument : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
QTemporaryDir m_dir;
|
||||
int m_run = 0;
|
||||
|
||||
QProcessEnvironment env()
|
||||
{
|
||||
const QString home = m_dir.filePath(QStringLiteral("home%1").arg(m_run++));
|
||||
QDir().mkpath(home);
|
||||
QProcessEnvironment e = QProcessEnvironment::systemEnvironment();
|
||||
e.insert(QStringLiteral("QT_QPA_PLATFORM"), QStringLiteral("offscreen"));
|
||||
e.insert(QStringLiteral("QET_ENABLE_SCRIPTING"), QStringLiteral("1"));
|
||||
e.insert(QStringLiteral("HOME"), home);
|
||||
e.insert(QStringLiteral("XDG_CONFIG_HOME"), home + QStringLiteral("/config"));
|
||||
e.insert(QStringLiteral("XDG_DATA_HOME"), home + QStringLiteral("/data"));
|
||||
e.insert(QStringLiteral("TMPDIR"), m_dir.path());
|
||||
e.remove(QStringLiteral("QET_DATABASE_FROM_FOLIOS"));
|
||||
return e;
|
||||
}
|
||||
|
||||
QString run(const QStringList &args, bool from_folios = false)
|
||||
{
|
||||
QProcessEnvironment e = env();
|
||||
if (from_folios)
|
||||
e.insert(QStringLiteral("QET_DATABASE_FROM_FOLIOS"), QStringLiteral("1"));
|
||||
QProcess proc;
|
||||
proc.setProcessEnvironment(e);
|
||||
proc.start(QStringLiteral(QET_TEST_BINARY_PATH), args);
|
||||
if (!proc.waitForFinished(180000)) return {};
|
||||
return QString::fromUtf8(proc.readAllStandardOutput() + proc.readAllStandardError());
|
||||
}
|
||||
|
||||
// The six tables, each as a sorted list of its rows, and which way the
|
||||
// database was filled.
|
||||
QJsonObject dump(const QString &project, bool from_folios, QString *how)
|
||||
{
|
||||
const QString out = run({QStringLiteral("--run"), m_dir.filePath(QStringLiteral("dump.js")),
|
||||
project}, from_folios);
|
||||
QJsonObject tables;
|
||||
for (const QString &line : out.split(QLatin1Char('\n'))) {
|
||||
if (line.contains(QStringLiteral("Project database filled")))
|
||||
*how = line.mid(line.indexOf(QStringLiteral("Project database filled")));
|
||||
const int i = line.indexOf(QStringLiteral("DUMP "));
|
||||
if (i >= 0) {
|
||||
const QJsonObject raw = QJsonDocument::fromJson(line.mid(i + 5).toUtf8()).object();
|
||||
for (auto it = raw.begin(); it != raw.end(); ++it) {
|
||||
QStringList rows;
|
||||
for (const QJsonValue &row : it.value().toArray())
|
||||
rows << QString::fromUtf8(QJsonDocument(row.toObject()).toJson(QJsonDocument::Compact));
|
||||
rows.sort();
|
||||
tables.insert(it.key(), QJsonArray::fromStringList(rows));
|
||||
}
|
||||
}
|
||||
}
|
||||
return tables;
|
||||
}
|
||||
|
||||
// tremie_vibrante.qet saved once, as a document to change
|
||||
QDomDocument resaved(const QString &saved)
|
||||
{
|
||||
run({QStringLiteral("--resave"), QStringLiteral(QET_EXAMPLES_DIR "/tremie_vibrante.qet"), saved});
|
||||
QFile file(saved);
|
||||
QDomDocument document;
|
||||
if (file.open(QIODevice::ReadOnly))
|
||||
document.setContent(&file);
|
||||
return document;
|
||||
}
|
||||
|
||||
static bool write(const QString &path, const QDomDocument &document)
|
||||
{
|
||||
QFile file(path);
|
||||
return file.open(QIODevice::WriteOnly | QIODevice::Truncate)
|
||||
&& file.write(document.toByteArray()) > 0;
|
||||
}
|
||||
|
||||
// Both fills of @p saved, which must come from the document, agree.
|
||||
void compareBothWays(const QString &saved)
|
||||
{
|
||||
QString how_document, how_folios;
|
||||
const QJsonObject document = dump(saved, false, &how_document);
|
||||
const QJsonObject folios = dump(saved, true, &how_folios);
|
||||
QVERIFY2(how_document == QLatin1String("Project database filled from the document"),
|
||||
qPrintable(how_document));
|
||||
QVERIFY2(how_folios.contains(QStringLiteral("QET_DATABASE_FROM_FOLIOS")), qPrintable(how_folios));
|
||||
QCOMPARE(document.keys().size(), 6);
|
||||
for (const QString &table : folios.keys()) {
|
||||
const QJsonArray a = document.value(table).toArray(), b = folios.value(table).toArray();
|
||||
QVERIFY2(a == b, qPrintable(QStringLiteral("%1: %2 rows from the document, %3 from the folios")
|
||||
.arg(table).arg(a.size()).arg(b.size())));
|
||||
}
|
||||
m_last = document;
|
||||
}
|
||||
|
||||
QJsonObject m_last; // the document's tables, from compareBothWays()
|
||||
|
||||
private slots:
|
||||
void initTestCase()
|
||||
{
|
||||
QVERIFY(m_dir.isValid());
|
||||
QVERIFY(QFile::exists(QStringLiteral(QET_TEST_BINARY_PATH)));
|
||||
QFile js(m_dir.filePath(QStringLiteral("dump.js")));
|
||||
QVERIFY(js.open(QIODevice::WriteOnly));
|
||||
js.write("var out = {};\n"
|
||||
"['diagram', 'diagram_info', 'element', 'element_info', 'terminal', 'conductor']"
|
||||
".forEach(function (t) { out[t] = qet.query('SELECT * FROM ' + t); });\n"
|
||||
"qet.log('DUMP ' + JSON.stringify(out));\n");
|
||||
}
|
||||
|
||||
void sameTablesBothWays_data()
|
||||
{
|
||||
QTest::addColumn<QString>("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 sameTablesBothWays()
|
||||
{
|
||||
QFETCH(QString, project);
|
||||
const QString saved = m_dir.filePath(QStringLiteral("saved%1.qet").arg(m_run));
|
||||
run({QStringLiteral("--resave"), project, saved});
|
||||
QVERIFY2(QFile::exists(saved), "--resave failed");
|
||||
compareBothWays(saved);
|
||||
}
|
||||
|
||||
// A label or a conductor text made from a formula is what the formula
|
||||
// gives now, not what it gave when the file was saved: every folio's
|
||||
// first element and first conductor is given a formula using each kind
|
||||
// of variable, and a saved label no formula gives.
|
||||
void formulasAreWorkedOut()
|
||||
{
|
||||
const QString saved = m_dir.filePath(QStringLiteral("formulas-saved.qet"));
|
||||
run({QStringLiteral("--resave"), QStringLiteral(QET_EXAMPLES_DIR "/tremie_vibrante.qet"), saved});
|
||||
QFile file(saved);
|
||||
QVERIFY(file.open(QIODevice::ReadOnly));
|
||||
QDomDocument document;
|
||||
QVERIFY(document.setContent(&file));
|
||||
file.close();
|
||||
|
||||
auto property = [&document](QDomElement parent, const QString &name, const QString &value) {
|
||||
QDomElement properties = parent.firstChildElement(QStringLiteral("properties"));
|
||||
if (properties.isNull())
|
||||
properties = parent.appendChild(document.createElement(QStringLiteral("properties"))).toElement();
|
||||
QDomElement p = document.createElement(QStringLiteral("property"));
|
||||
p.setAttribute(QStringLiteral("name"), name);
|
||||
p.appendChild(document.createTextNode(value));
|
||||
properties.appendChild(p);
|
||||
};
|
||||
auto sequence = [&document](QDomElement item, const QString &unit) {
|
||||
item.removeChild(item.firstChildElement(QStringLiteral("sequentialNumbers")));
|
||||
QDomElement s = document.createElement(QStringLiteral("sequentialNumbers"));
|
||||
QDomElement u = document.createElement(QStringLiteral("unit"));
|
||||
u.appendChild(document.createTextNode(unit));
|
||||
s.appendChild(u);
|
||||
item.appendChild(s);
|
||||
};
|
||||
|
||||
property(document.documentElement(), QStringLiteral("site"), QStringLiteral("S"));
|
||||
const QDomNodeList diagrams = document.elementsByTagName(QStringLiteral("diagram"));
|
||||
QCOMPARE(diagrams.size(), 3);
|
||||
for (int i = 0 ; i < diagrams.size() ; ++i)
|
||||
{
|
||||
QDomElement diagram = diagrams.at(i).toElement();
|
||||
diagram.setAttribute(QStringLiteral("folio"), QStringLiteral("F%id"));
|
||||
diagram.setAttribute(QStringLiteral("plant"), QStringLiteral("P"));
|
||||
diagram.setAttribute(QStringLiteral("locmach"), QStringLiteral("L"));
|
||||
property(diagram, QStringLiteral("zone"), QStringLiteral("Z%1").arg(i));
|
||||
|
||||
QDomElement element = diagram.firstChildElement(QStringLiteral("elements"))
|
||||
.firstChildElement(QStringLiteral("element"));
|
||||
QVERIFY(!element.isNull());
|
||||
element.setAttribute(QStringLiteral("prefix"), QStringLiteral("X"));
|
||||
sequence(element, QStringLiteral("7"));
|
||||
QDomElement informations = element.firstChildElement(QStringLiteral("elementInformations"));
|
||||
if (informations.isNull())
|
||||
informations = element.appendChild(document.createElement(QStringLiteral("elementInformations"))).toElement();
|
||||
while (!informations.firstChild().isNull())
|
||||
informations.removeChild(informations.firstChild());
|
||||
for (const auto &info : {std::make_pair(QStringLiteral("formula"),
|
||||
QStringLiteral("K%total-%f-%F-%M-%LM-%c%l-%prefix-%{zone}-%{site}-%sequ_1")),
|
||||
std::make_pair(QStringLiteral("label"), QStringLiteral("OLD"))}) {
|
||||
QDomElement e = document.createElement(QStringLiteral("elementInformation"));
|
||||
e.setAttribute(QStringLiteral("name"), info.first);
|
||||
e.setAttribute(QStringLiteral("show"), QStringLiteral("1"));
|
||||
e.appendChild(document.createTextNode(info.second));
|
||||
informations.appendChild(e);
|
||||
}
|
||||
|
||||
QDomElement conductor = diagram.firstChildElement(QStringLiteral("conductors"))
|
||||
.firstChildElement(QStringLiteral("conductor"));
|
||||
QVERIFY(!conductor.isNull());
|
||||
conductor.setAttribute(QStringLiteral("formula"), QStringLiteral("W%total-%id-%wf-%{zone}-%sequ_1"));
|
||||
conductor.setAttribute(QStringLiteral("function"), QStringLiteral("N"));
|
||||
conductor.setAttribute(QStringLiteral("num"), QStringLiteral("OLD"));
|
||||
sequence(conductor, QStringLiteral("3"));
|
||||
}
|
||||
QVERIFY(file.open(QIODevice::WriteOnly | QIODevice::Truncate));
|
||||
file.write(document.toByteArray());
|
||||
file.close();
|
||||
|
||||
compareBothWays(saved);
|
||||
// ...and they were worked out, not left as saved
|
||||
const QString info = QString::fromUtf8(QJsonDocument(m_last.value(QStringLiteral("element_info")).toArray())
|
||||
.toJson(QJsonDocument::Compact));
|
||||
const QString wires = QString::fromUtf8(QJsonDocument(m_last.value(QStringLiteral("conductor")).toArray())
|
||||
.toJson(QJsonDocument::Compact));
|
||||
QVERIFY2(info.contains(QStringLiteral("K3-1-F1-P-L-")), qPrintable(info.left(400)));
|
||||
QVERIFY2(info.contains(QStringLiteral("-X-Z0-S-7")), qPrintable(info.left(400)));
|
||||
QVERIFY2(wires.contains(QStringLiteral("W3-3-N-Z2-3")), qPrintable(wires.left(400)));
|
||||
QVERIFY(!info.contains(QStringLiteral("OLD")));
|
||||
QVERIFY(!wires.contains(QStringLiteral("OLD")));
|
||||
}
|
||||
|
||||
// An element the folio does not build, and so the conductors ending on
|
||||
// it, are left out the same way.
|
||||
void unbuiltElementIsLeftOut()
|
||||
{
|
||||
const QString saved = m_dir.filePath(QStringLiteral("unbuilt.qet"));
|
||||
QDomDocument document = resaved(saved);
|
||||
QDomElement conductor = document.elementsByTagName(QStringLiteral("conductor")).at(0).toElement();
|
||||
QVERIFY(!conductor.isNull());
|
||||
const QString uuid = conductor.attribute(QStringLiteral("element1"));
|
||||
const QDomNodeList elements = document.elementsByTagName(QStringLiteral("element"));
|
||||
bool found = false;
|
||||
for (int i = 0 ; i < elements.size() ; ++i) {
|
||||
QDomElement e = elements.at(i).toElement();
|
||||
if (e.attribute(QStringLiteral("uuid")) == uuid) {
|
||||
e.setAttribute(QStringLiteral("x"), QStringLiteral("nan"));
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
QVERIFY(found);
|
||||
QVERIFY(write(saved, document));
|
||||
compareBothWays(saved);
|
||||
const QString wires = QString::fromUtf8(QJsonDocument(m_last.value(QStringLiteral("conductor")).toArray())
|
||||
.toJson(QJsonDocument::Compact));
|
||||
QVERIFY(!wires.contains(conductor.attribute(QStringLiteral("uuid"))));
|
||||
}
|
||||
|
||||
// Two elements on a folio numbering their terminals alike: which one the
|
||||
// folio then refuses depends on the terminals' geometry, so the folios
|
||||
// fill the database.
|
||||
void clashingTerminalIdsFallBack()
|
||||
{
|
||||
const QString saved = m_dir.filePath(QStringLiteral("clash.qet"));
|
||||
QDomDocument document = resaved(saved);
|
||||
const QDomElement diagram = document.elementsByTagName(QStringLiteral("diagram")).at(0).toElement();
|
||||
QDomElement first = diagram.firstChildElement(QStringLiteral("elements")).firstChildElement(QStringLiteral("element"));
|
||||
QDomElement second = first.nextSiblingElement(QStringLiteral("element"));
|
||||
const QString id = first.firstChildElement(QStringLiteral("terminals"))
|
||||
.firstChildElement(QStringLiteral("terminal")).attribute(QStringLiteral("id"));
|
||||
QDomElement terminal = second.firstChildElement(QStringLiteral("terminals"))
|
||||
.firstChildElement(QStringLiteral("terminal"));
|
||||
QVERIFY(!id.isEmpty() && !terminal.isNull());
|
||||
terminal.setAttribute(QStringLiteral("id"), id);
|
||||
QVERIFY(write(saved, document));
|
||||
QString how;
|
||||
dump(saved, false, &how);
|
||||
QCOMPARE(how, QStringLiteral("Project database filled from the folios: "
|
||||
"two elements on a folio number their terminals alike"));
|
||||
}
|
||||
|
||||
// A file whose items carry no saved uuid is filled from the folios,
|
||||
// which work those uuids out as they are built, and says why.
|
||||
void olderFileFallsBack()
|
||||
{
|
||||
QString how;
|
||||
dump(QStringLiteral(QET_EXAMPLES_DIR "/tremie_vibrante.qet"), false, &how);
|
||||
QCOMPARE(how, QStringLiteral("Project database filled from the folios: a folio has no saved uuid"));
|
||||
}
|
||||
};
|
||||
|
||||
QTEST_APPLESS_MAIN(tst_databasefromdocument)
|
||||
|
||||
#include "tst_databasefromdocument.moc"
|
||||
Reference in New Issue
Block a user