diff --git a/sources/container/qetcontainerdb.cpp b/sources/container/qetcontainerdb.cpp index 340a22b47..85349e9fc 100644 --- a/sources/container/qetcontainerdb.cpp +++ b/sources/container/qetcontainerdb.cpp @@ -818,6 +818,23 @@ QByteArray QetContainerDb::extract(QDomElement project, QString *error, const QB ? QVariant(i.attribute(QStringLiteral("name"))) : QVariant(), value, attributesJson(i, QStringLiteral("name"), true)}); } + } + //Its wires whose properties changed since: their columns + for (const QDomElement &edited : children(d, QStringLiteral("qetz-edited-conductor"))) { + QStringList set; + QVariantList values; + for (const QString &column : ConductorData) { + if (column == QLatin1String("uuid") || column.startsWith(QLatin1String("element")) + || column.startsWith(QLatin1String("terminal")) || column == QLatin1String("freezeLabel") + || !edited.hasAttribute(column)) + continue; + set << QStringLiteral("\"%1\" = ?").arg(column); + values << edited.attribute(column); + } + values << id << edited.attribute(QStringLiteral("uuid")); + Statement update(db, QStringLiteral("UPDATE conductor SET %1 WHERE folio_id = ? AND uuid = ?") + .arg(set.join(QStringLiteral(", ")))); + ok = ok && !set.isEmpty() && update.run(values) && sqlite3_changes(db.m_db) == 1; } if (!ok) { setError(error, QStringLiteral("folio %1 kept as it was is not in its database").arg(id)); diff --git a/sources/dataBase/projectdatabase.cpp b/sources/dataBase/projectdatabase.cpp index c7c84c4a8..32085c9b3 100644 --- a/sources/dataBase/projectdatabase.cpp +++ b/sources/dataBase/projectdatabase.cpp @@ -1317,6 +1317,77 @@ QString projectDataBase::terminalName(const QUuid &terminal, const QUuid &elemen return query.exec() && query.next() ? query.value(0).toString() : QString(); } +/** + @brief projectDataBase::potentialWires + @return the wires at the potential of @p wire, @p wire included, read + from the conductor, element and link tables as they are (see + elementTypesOnFolios()), followed as Conductor::relatedPotentialConductors() + follows them over every folio: through a wire's ends, from a folio + report to the terminals of the first symbol it is linked to, and from a + terminal block's terminal to its others. Nothing when a terminal block + on the way isolates its terminals (their groups are in its definition, + not in the database) or @p wire has no row. +*/ +std::optional> projectDataBase::potentialWires(const QUuid &wire) const +{ + using Node = std::pair; //element uuid, terminal uuid + QHash> ends; + QHash wires_at; + QHash> nodes_of; + { + QSqlQuery query(m_data_base); + query.exec(QStringLiteral("SELECT uuid, terminal1_element_uuid, terminal1_uuid, " + "terminal2_element_uuid, terminal2_uuid FROM conductor")); + while (query.next()) { + const Node a{query.value(1).toString(), query.value(2).toString()}; + const Node b{query.value(3).toString(), query.value(4).toString()}; + const QString uuid = query.value(0).toString(); + ends.insert(uuid, {a, b}); + for (const Node &n : {a, b}) { + wires_at[n] << uuid; + if (!nodes_of.value(n.first).contains(n)) nodes_of[n.first] << n; + } + } + } + const auto start = ends.constFind(wire.toString()); + if (start == ends.constEnd()) { + return std::nullopt; + } + QHash kinds; + { + QSqlQuery query(m_data_base); + query.exec(QStringLiteral("SELECT uuid, type FROM element")); + while (query.next()) + kinds.insert(query.value(0).toString(), ElementData::typeFromString(query.value(1).toString())); + } + QSet wires; + QSet seen; + QList todo{start->first, start->second}; + while (!todo.isEmpty()) { + const Node node = todo.takeLast(); + if (seen.contains(node)) continue; + seen.insert(node); + for (const QString &w : wires_at.value(node)) { + if (wires.contains(QUuid(w))) continue; + wires.insert(QUuid(w)); + const auto e = ends.value(w); + todo << e.first << e.second; + } + const ElementData::Type kind = kinds.value(node.first, ElementData::Simple); + if (kind & ElementData::AllReport) { + const QList partners = linkedSymbols(QUuid(node.first)); + if (!partners.isEmpty()) + todo << nodes_of.value(partners.first().toString()); + } else if (kind & ElementData::Terminal) { + if (QET::infoFlagIsTrue(m_element_information.value(QUuid(node.first)) + .value(QStringLiteral("potential_isolating")).toString())) + return std::nullopt; + todo << nodes_of.value(node.first); + } + } + return wires; +} + /** @brief projectDataBase::linkedSymbols @return the symbols @p element is linked to, read from the link table @@ -1467,9 +1538,111 @@ QSet projectDataBase::foliosWithUnbuiltEdits() const { QSet folios; for (const auto &edit : m_unbuilt_edits) folios.insert(edit.folio); + for (const auto &edit : m_unbuilt_wire_edits) folios.insert(edit.folio); return folios; } +/** + @brief projectDataBase::unbuiltWire + @return true if the wire @p wire is on a folio not built (lazy folios): + it has a row and its properties are stored, and no wire built holds its + uuid. @p folio is set to its folio. +*/ +bool projectDataBase::unbuiltWire(const QUuid &wire, QUuid *folio) const +{ + if (placedConductorCount(wire) != 0 || !hasConductorProperties(wire)) { + return false; + } + QSqlQuery query(m_data_base); + query.prepare(QStringLiteral("SELECT diagram_uuid FROM conductor WHERE uuid = :uuid")); + query.bindValue(QStringLiteral(":uuid"), wire.toString()); + if (!query.exec() || !query.next()) { + return false; + } + if (folio) *folio = QUuid(query.value(0).toString()); + return !query.next(); +} + +/** + @brief projectDataBase::editUnbuiltConductorProperties + Give the wire @p wire, on a folio not built (unbuiltWire()), the + properties @p properties as Conductor::setProperties() would: the store, + its rows and its conductor row's text. Building it takes them + (takeUnbuiltWireEdit()); a .qetz saves them in its database (QETProject:: + toXml()), anything else builds its folio to save. +*/ +void projectDataBase::editUnbuiltConductorProperties(const QUuid &wire, const ConductorProperties &properties) +{ + QUuid folio; + if (!unbuiltWire(wire, &folio)) { + return; + } + m_unbuilt_wire_edits.insert(wire, {properties, folio}); + if (!storeConductorProperties(wire, properties)) { + return; + } + m_content_changed = true; + if (!m_update_blocked) { + m_update_conductor_query.bindValue(QStringLiteral(":uuid"), wire.toString()); + m_update_conductor_query.bindValue(QStringLiteral(":text"), properties.text); + if (!m_update_conductor_query.exec()) { + qDebug() << "projectDataBase::editUnbuiltConductorProperties update error : " << m_update_conductor_query.lastError(); + } + } + emit dataBaseUpdated(); +} + +/** + @brief projectDataBase::takeUnbuiltWireEdit + @return the properties @p wire was given while its folio was not built, + forgotten here: the wire being built takes them +*/ +std::optional projectDataBase::takeUnbuiltWireEdit(const QUuid &wire) +{ + const auto edit = m_unbuilt_wire_edits.constFind(wire); + if (edit == m_unbuilt_wire_edits.constEnd()) { + return std::nullopt; + } + const ConductorProperties properties = edit->properties; + m_unbuilt_wire_edits.erase(edit); + return properties; +} + +/** + @brief projectDataBase::unbuiltWireEditsOnFolio + @return the properties given to the wires of the folio @p folio while it + was not built, by wire +*/ +QHash projectDataBase::unbuiltWireEditsOnFolio(const QUuid &folio) const +{ + QHash edits; + for (auto it = m_unbuilt_wire_edits.cbegin() ; it != m_unbuilt_wire_edits.cend() ; ++it) + if (it->folio == folio) edits.insert(it.key(), it->properties); + return edits; +} + +/** + @brief projectDataBase::setPlacedConductorProperties + Give the wire @p wire its properties @p properties: through the wire when + it is built, through the store when it is not (unbuiltWire()). What + undoing and redoing a change of wires not built does, the wires having + perhaps been built since. + @return false if neither can +*/ +bool projectDataBase::setPlacedConductorProperties(const QUuid &wire, const ConductorProperties &properties) +{ + const QList> placed = m_placed_conductors.values(wire); + if (placed.size() == 1 && placed.first()) { + placed.first()->setProperties(properties); + return true; + } + if (placed.isEmpty() && unbuiltWire(wire)) { + editUnbuiltConductorProperties(wire, properties); + return true; + } + return false; +} + /** @brief projectDataBase::unbuiltEditsOnFolio @return the information changed through the store of the symbols on the diff --git a/sources/dataBase/projectdatabase.h b/sources/dataBase/projectdatabase.h index 8d95fe0c5..ceaeec512 100644 --- a/sources/dataBase/projectdatabase.h +++ b/sources/dataBase/projectdatabase.h @@ -186,6 +186,11 @@ class projectDataBase : public QObject QSet foliosWithUnbuiltEdits() const; QHash unbuiltEditsOnFolio(const QUuid &folio, bool *need_build = nullptr) const; bool setPlacedElementInformation(const QUuid &element, const DiagramContext &information); + bool unbuiltWire(const QUuid &wire, QUuid *folio = nullptr) const; + void editUnbuiltConductorProperties(const QUuid &wire, const ConductorProperties &properties); + std::optional takeUnbuiltWireEdit(const QUuid &wire); + QHash unbuiltWireEditsOnFolio(const QUuid &folio) const; + bool setPlacedConductorProperties(const QUuid &wire, const ConductorProperties &properties); QHash elementTypesOnFolios(const QSet &folios) const; /// A symbol placed on a folio, as the element table has it struct FolioSymbol { @@ -203,6 +208,7 @@ class projectDataBase : public QObject QList wiresOnFolios(const QList &folios) const; QString elementLabel(const QUuid &element) const; QString terminalName(const QUuid &terminal, const QUuid &element) const; + std::optional> potentialWires(const QUuid &wire) const; static QVariant sequenceValue(const autonum::sequentialNumbers &sequence); static autonum::sequentialNumbers sequenceFromValue(const QVariant &value); @@ -346,6 +352,9 @@ class projectDataBase : public QObject /// each is on: building the symbol takes it (takeUnbuiltEdit()) struct UnbuiltEdit { DiagramContext information; QUuid folio; bool had_information; }; QHash m_unbuilt_edits; + /// The same for wires (editUnbuiltConductorProperties()) + struct UnbuiltWireEdit { ConductorProperties properties; QUuid folio; }; + QHash m_unbuilt_wire_edits; QHash m_folio_titleblocks; QHash> m_placed_folios; QHash m_conductor_properties; diff --git a/sources/qetgraphicsitem/conductor.cpp b/sources/qetgraphicsitem/conductor.cpp index 339c4a243..f6d3b8ae4 100644 --- a/sources/qetgraphicsitem/conductor.cpp +++ b/sources/qetgraphicsitem/conductor.cpp @@ -1154,6 +1154,11 @@ bool Conductor::fromXml(QDomElement &dom_element) m_freeze_label = dom_element.attribute("freezeLabel") == "true" ? true : false; + //Changed through the project database while its folio was not + //built (lazy folios): the change, not what the file says + if (diagram() && diagram()->project()) + if (const auto edited = diagram()->project()->dataBase()->takeUnbuiltWireEdit(m_uuid)) + pr = *edited; setProperties(pr); return retval; diff --git a/sources/qetproject.cpp b/sources/qetproject.cpp index 1bf67e8a9..d03f15633 100644 --- a/sources/qetproject.cpp +++ b/sources/qetproject.cpp @@ -2512,6 +2512,17 @@ QDomDocument QETProject::toXml(QHash *kept_folios, QStringList const QDomElement old_information = element.firstChildElement(QStringLiteral("elementInformations")); element.replaceChild(information, old_information); //it has one (unbuiltEditsOnFolio()) } + //Its wires whose properties changed + const QHash wire_edits = m_data_base.unbuiltWireEditsOnFolio(diagram->uuid()); + if (!wire_edits.isEmpty()) + for (const QDomElement &list : QET::findInDomElement(xml_diagram, QStringLiteral("conductors"))) + for (QDomElement wire = list.firstChildElement(QStringLiteral("conductor")) ; !wire.isNull() ; + wire = wire.nextSiblingElement(QStringLiteral("conductor"))) { + const auto edit = wire_edits.constFind(QUuid(wire.attribute(QStringLiteral("uuid")))); + if (edit == wire_edits.constEnd()) continue; + for (const auto &attribute : edit->attributes()) + wire.setAttribute(attribute.first, attribute.second); + } xml_diagram.setAttribute("order", order_num ++); project_root.appendChild(xml_diagram); continue; @@ -2530,6 +2541,16 @@ QDomDocument QETProject::toXml(QHash *kept_folios, QStringList it.value().toXml(information, QStringLiteral("elementInformation")); symbol.appendChild(information); placeholder.appendChild(symbol); + } + //Its wires whose properties changed: what the .qetz database + //holds of them (only that can change, QetScriptApi) + const QHash wire_edits = m_data_base.unbuiltWireEditsOnFolio(diagram->uuid()); + for (auto it = wire_edits.cbegin() ; it != wire_edits.cend() ; ++it) { + auto wire = xml_doc.createElement(QStringLiteral("qetz-edited-conductor")); + wire.setAttribute(QStringLiteral("uuid"), it.key().toString()); + for (const auto &attribute : it.value().attributes()) + wire.setAttribute(attribute.first, attribute.second); + placeholder.appendChild(wire); } kept_parts->append(diagram->deferredPart()); project_root.appendChild(placeholder); diff --git a/sources/scripting/qetscriptapi.cpp b/sources/scripting/qetscriptapi.cpp index 3f372bdfb..e8b7f70e5 100644 --- a/sources/scripting/qetscriptapi.cpp +++ b/sources/scripting/qetscriptapi.cpp @@ -1265,6 +1265,16 @@ bool QetScriptApi::setConductorProperty(int folioIndex, const QString &elementUu .arg(caller, value, property)); return false; } + return applyConductorProperties(conductor, properties); +} + +/** + @brief QetScriptApi::applyConductorProperties + Give @p conductor and every wire at its potential the properties + @p properties, one undo step +*/ +bool QetScriptApi::applyConductorProperties(Conductor *conductor, const ConductorProperties &properties) +{ if (properties == conductor->properties()) return true; // already so QSet potential = conductor->relatedPotentialConductors(true); @@ -1282,6 +1292,174 @@ bool QetScriptApi::setConductorProperty(int folioIndex, const QString &elementUu return true; } +namespace { + //The wire properties a .qetz's database holds in its own columns, which + //nothing on a folio computes from but a wire's number formula +const QStringList &storedConductorPropertyNames() +{ + static const QStringList names { + QStringLiteral("num"), QStringLiteral("function"), QStringLiteral("bus"), + QStringLiteral("cable"), QStringLiteral("tension_protocol"), + QStringLiteral("conductor_color"), QStringLiteral("conductor_section")}; + return names; +} + + //true if @p a and @p b differ only in storedConductorPropertyNames() +bool differOnlyInStored(const ConductorProperties &a, const ConductorProperties &b) +{ + QHash left, right; + for (const auto &attribute : a.attributes()) left.insert(attribute.first, attribute.second); + for (const auto &attribute : b.attributes()) right.insert(attribute.first, attribute.second); + QSet names(left.keyBegin(), left.keyEnd()); + for (const QString &name : right.keys()) names.insert(name); + for (const QString &name : std::as_const(names)) + if (left.value(name) != right.value(name) && !storedConductorPropertyNames().contains(name)) + return false; + return true; +} + + //Wires given their properties by uuid, built or not (lazy folios): + //projectDataBase::setPlacedConductorProperties() +class ChangeStoredConductorPropertiesCommand : public QUndoCommand +{ + public: + ChangeStoredConductorPropertiesCommand(QETProject *project, + const QHash> &changes) : + m_project(project), m_changes(changes) + { setText(QObject::tr("Edit the conductor properties")); } + void undo() override { apply(false); } + void redo() override { apply(true); } + private: + void apply(bool forward) { + if (!m_project) return; + for (auto it = m_changes.cbegin() ; it != m_changes.cend() ; ++it) + m_project->dataBase()->setPlacedConductorProperties( + it.key(), forward ? it->second : it->first); + } + QPointer m_project; + QHash> m_changes; +}; +} + +/** + @brief QetScriptApi::findConductorByUuid + @return the wire @p conductorUuid of the folio @p folioIndex, the folio + built, or nullptr (said in the log) +*/ +Conductor *QetScriptApi::findConductorByUuid(int folioIndex, const QString &conductorUuid, + const QString &caller) +{ + if (!m_project || folioIndex < 0 || folioIndex >= m_project->folios().count()) { + log(QStringLiteral("qet.%1: no folio at index %2").arg(caller).arg(folioIndex)); + return nullptr; + } + const QUuid wanted(conductorUuid); + for (Conductor *c : built(m_project->folios().at(folioIndex))->conductors()) + if (c->uuid() == wanted) return c; + log(QStringLiteral("qet.%1: no conductor %2 on folio %3").arg(caller, conductorUuid).arg(folioIndex)); + return nullptr; +} + +/** + @brief QetScriptApi::unbuiltConductorChange + Change the property @p property of the wire @p wire, on the folio + @p folioIndex not built (lazy folios), and of every wire at its potential, + through the project database, as applyConductorProperties() would: when + they are all on folios not built, none is numbered by a formula, and what + changes in each is only what the .qetz database holds + (storedConductorPropertyNames()) -- nothing then is computed from it. + @return true if done; false for the folio to be built, @p refused set when + the value itself is not one +*/ +bool QetScriptApi::unbuiltConductorChange(int folioIndex, const QUuid &wire, const QString &property, + const QString &value, bool *refused) +{ + *refused = false; + projectDataBase *database = m_project->dataBase(); + Diagram *folio = m_project->folios().at(folioIndex); + QUuid on; + if (folio->isLoaded() || !database->rowsFromDocument() || !storedConductorPropertyNames().contains(property) + || !database->unbuiltWire(wire, &on) || on != folio->uuid()) { + return false; + } + ConductorProperties properties = database->conductorProperties(wire); + if (!setConductorPropertyValue(properties, property, value)) { + *refused = true; + return false; + } + if (!properties.m_formula.isEmpty()) { + return false; + } + const auto potential = database->potentialWires(wire); + if (!potential) { + return false; + } + QHash> changes; + for (const QUuid &other : *potential) { + if (!database->unbuiltWire(other)) return false; + const ConductorProperties old = database->conductorProperties(other); + if (!differOnlyInStored(old, properties)) return false; + if (!(old == properties)) changes.insert(other, {old, properties}); + } + if (!changes.isEmpty()) + m_project->undoStack()->push(new ChangeStoredConductorPropertiesCommand(m_project, changes)); + return true; +} + +QString QetScriptApi::conductorPropertyByUuid(int folioIndex, const QString &conductorUuid, + const QString &property) const +{ + if (!m_project || folioIndex < 0 || folioIndex >= m_project->folios().count()) return QString(); + Diagram *folio = m_project->folios().at(folioIndex); + projectDataBase *database = m_project->dataBase(); + QUuid on; + if (!folio->isLoaded() && database->rowsFromDocument() + && database->unbuiltWire(QUuid(conductorUuid), &on) && on == folio->uuid()) + return conductorPropertyValue(database->conductorProperties(QUuid(conductorUuid)), property); + auto *self = const_cast(this); + Conductor *conductor = self->findConductorByUuid(folioIndex, conductorUuid, + QStringLiteral("conductorPropertyByUuid")); + return conductor ? conductorPropertyValue(conductor->properties(), property) : QString(); +} + +bool QetScriptApi::setConductorPropertyByUuid(int folioIndex, const QString &conductorUuid, + const QString &property, const QString &value) +{ + if (!m_project) return false; + const QString caller = QStringLiteral("setConductorPropertyByUuid"); + if (m_project->isReadOnly()) { + log(QStringLiteral("qet.%1: project is read-only").arg(caller)); + return false; + } + if (!conductorPropertyNames().contains(property)) { + log(QStringLiteral("qet.%1: unknown property '%2'; expected one of %3") + .arg(caller, property, conductorPropertyNames().join(QStringLiteral(", ")))); + return false; + } + if (folioIndex < 0 || folioIndex >= m_project->folios().count()) { + log(QStringLiteral("qet.%1: no folio at index %2").arg(caller).arg(folioIndex)); + return false; + } + //The folio left unbuilt when the change can go through the project + //database (stage D5) + bool refused = false; + if (unbuiltConductorChange(folioIndex, QUuid(conductorUuid), property, value, &refused)) + return true; + if (refused) { + log(QStringLiteral("qet.%1: '%2' is not a valid value for %3").arg(caller, value, property)); + return false; + } + Conductor *conductor = findConductorByUuid(folioIndex, conductorUuid, caller); + if (!conductor) return false; + ConductorProperties properties = conductor->properties(); + if (!setConductorPropertyValue(properties, property, value)) { + log(QStringLiteral("qet.%1: '%2' is not a valid value for %3") + .arg(caller, value, property)); + return false; + } + return applyConductorProperties(conductor, properties); +} + /** @brief QetScriptApi::conductorSegments List the drawn path of the conductor on a terminal, one line per diff --git a/sources/scripting/qetscriptapi.h b/sources/scripting/qetscriptapi.h index 6a954d5a6..9d0c9c7b7 100644 --- a/sources/scripting/qetscriptapi.h +++ b/sources/scripting/qetscriptapi.h @@ -34,6 +34,7 @@ class DiagramView; class Element; class Terminal; class Conductor; +class ConductorProperties; class IndependentTextItem; class QetShapeItem; class DiagramImageItem; @@ -440,6 +441,11 @@ class QetScriptApi : public QObject Q_INVOKABLE bool setConductorProperty(int folioIndex, const QString &elementUuid, int terminalIndex, const QString &property, const QString &value); + // The same, the wire named by its uuid (conductorUuids()) + Q_INVOKABLE QString conductorPropertyByUuid(int folioIndex, const QString &conductorUuid, + const QString &property) const; + Q_INVOKABLE bool setConductorPropertyByUuid(int folioIndex, const QString &conductorUuid, + const QString &property, const QString &value); // -- a conductor's own drawn path, not the whole potential's // properties above -- one conductor only, addressed the same way -- @@ -648,6 +654,10 @@ class QetScriptApi : public QObject private: bool runFlag(const QString &flag, const QStringList &args); Diagram *built(Diagram *folio) const; + Conductor *findConductorByUuid(int folioIndex, const QString &conductorUuid, const QString &caller); + bool applyConductorProperties(Conductor *conductor, const ConductorProperties &properties); + bool unbuiltConductorChange(int folioIndex, const QUuid &wire, const QString &property, + const QString &value, bool *refused); std::optional unbuiltInformation(int folioIndex, const QString &elementUuid) const; Element *findElement(int folioIndex, const QString &elementUuid) const; Terminal *findTerminal(int folioIndex, const QString &elementUuid, int terminalIndex, diff --git a/tests/qttest/CMakeLists.txt b/tests/qttest/CMakeLists.txt index b5be0cf96..2418a9ef0 100644 --- a/tests/qttest/CMakeLists.txt +++ b/tests/qttest/CMakeLists.txt @@ -92,7 +92,7 @@ if(Python3_Interpreter_FOUND AND CMAKE_GENERATOR STREQUAL "Ninja") --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 + set_tests_properties(tst_lazyfolios PROPERTIES TIMEOUT 1800 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 diff --git a/tests/qttest/lazy_folios_probe.cpp b/tests/qttest/lazy_folios_probe.cpp index 2bc143b93..630dca6db 100644 --- a/tests/qttest/lazy_folios_probe.cpp +++ b/tests/qttest/lazy_folios_probe.cpp @@ -777,6 +777,28 @@ int main(int argc, char **argv) std::printf(".qetz drawn items through their view: %d lazily, %d when built\n", lazily, built); check(built > 0, "the project has drawn items"); check(lazily == built, "the drawn items' view as when built"); + } + //Every wire's potential from the database as QElectroTech + //follows it with every folio built + { + QETProject indexed_potentials(zipped); + int same = 0, differ = 0, unknown = 0; + for (Diagram *folio : eager.folios()) + for (Conductor *wire : folio->conductors()) { + QSet live{wire->uuid()}; + for (Conductor *c : wire->relatedPotentialConductors(true)) live.insert(c->uuid()); + for (QETProject *project : {&eager, &indexed_potentials}) { + const auto stored = project->dataBase()->potentialWires(wire->uuid()); + if (!stored) { ++unknown; continue; } + if (*stored == live) { ++same; continue; } + if (differ++ < 3) + std::printf(" potential of %s: %d wires followed, %d from the database\n", + qPrintable(wire->uuid().toString()), int(live.size()), int(stored->size())); + } + } + std::printf("potentials from the database: %d same, %d differ, %d not answered\n", same, differ, unknown); + check(differ == 0 && same > 0, "every wire's potential from the database as followed"); + check(indexed_potentials.unloadedFolioCount() > 0, "potentials read without building"); } //The folios' rows as saved, not as reading their headers one //by one numbers them ("1/1" for the first of three, D7) @@ -997,6 +1019,76 @@ int main(int argc, char **argv) revisionEveryFolio(zipped, output, "revised", path); revisionEveryFolio(zipped, output, "relabelled", path, busiest); + //A script changes a wire's cable by its uuid, on folios not + //built, the wire and its potential numbered by hand: through + //the project database, no folio built; saved as when built; + //undone; the folio built afterwards shows it (D5) + { + QETProject cabled(zipped); + const int unbuilt = cabled.unloadedFolioCount(); + int folio_index = -1; + QString uuid; + QSet potential; + for (int i = 0 ; i < folios.size() && uuid.isEmpty() ; ++i) { + if (cabled.folios().at(i)->isLoaded()) continue; + for (Conductor *wire : folios.at(i)->conductors()) { + QSet wires = wire->relatedPotentialConductors(true); + wires << wire; + bool usable = true; + for (Conductor *c : std::as_const(wires)) + if (!c->properties().m_formula.isEmpty() + || cabled.folios().at(eager.folioIndex(c->diagram()))->isLoaded()) + usable = false; + if (!usable || wires.size() < 2) continue; + folio_index = i; + uuid = wire->uuid().toString(); + for (Conductor *c : std::as_const(wires)) potential.insert(c->uuid()); + break; + } + } + check(!uuid.isEmpty(), "a wire numbered by hand, its potential on folios not built"); + QetScriptApi api(&cabled); + const QString before = api.conductorPropertyByUuid(folio_index, uuid, "cable"); + const QString value = QStringLiteral("W-42"); + check(api.setConductorPropertyByUuid(folio_index, uuid, "cable", value), "a script changes a wire not built"); + check(api.conductorPropertyByUuid(folio_index, uuid, "cable") == value, "the wire's change read back"); + for (const QUuid &wire : std::as_const(potential)) + check(cabled.dataBase()->conductorProperties(wire).m_cable == value, "every wire of the potential changed"); + check(cabled.unloadedFolioCount() == unbuilt, "changing wires not built builds no folio"); + const QString cabled_path = output + "/cabled.qetz"; + check(cabled.writeContainerFile(cabled_path, &error), "saved after the wire change"); + std::printf(".qetz wire on folio %d, potential of %d wires, cable changed by uuid: 0 folios built, %d by saving\n", + folio_index + 1, int(potential.size()), unbuilt - cabled.unloadedFolioCount()); + qputenv("QET_EAGER_FOLIOS", "1"); + const QString usual_path = output + "/cabled-usual.qetz"; + { + QETProject usual_cabled(zipped); + check(QetScriptApi(&usual_cabled).setConductorPropertyByUuid(folio_index, uuid, "cable", value), + "the same wire change when built"); + check(usual_cabled.writeContainerFile(usual_path, &error), "saved after the wire change when built"); + } + QETProject cabled_back(cabled_path), usual_back(usual_path); + qunsetenv("QET_EAGER_FOLIOS"); + auto lines = [&path](QETProject &project) { + QStringList list = sortedLines(project.toXml().toString(), path); + list.removeIf([](const QString &line) { return line.contains(QLatin1String("name=\"saved")); }); + return list; + }; + check(lines(cabled_back) == lines(usual_back), "a wire changed through the database saved as when built"); + cabled.undoStack()->undo(); + check(api.conductorPropertyByUuid(folio_index, uuid, "cable") == before, "the wire change undone"); + cabled.undoStack()->redo(); + Conductor *built_wire = nullptr; + for (Conductor *wire : cabled.folios().at(folio_index)->conductors()) + if (wire->uuid().toString() == uuid) built_wire = wire; + check(!built_wire, "the folio not built yet"); + cabled.buildFolioToShow(cabled.folios().at(folio_index)); + for (Conductor *wire : cabled.folios().at(folio_index)->conductors()) + if (wire->uuid().toString() == uuid) built_wire = wire; + check(built_wire && built_wire->properties().m_cable == value, + "a wire changed before its folio was built shows the change"); + } + //The wire numbers and wiring list exports, and the terminal //numbering dialog: as when built, building only the folios //holding a terminal (D5) diff --git a/tests/qttest/run_lazy_folios_probe.py b/tests/qttest/run_lazy_folios_probe.py index 2c7f881cb..af41a0fcc 100644 --- a/tests/qttest/run_lazy_folios_probe.py +++ b/tests/qttest/run_lazy_folios_probe.py @@ -54,7 +54,7 @@ subprocess.run([ninja, '-C', str(build), '-f', probe_manifest.name, target], che 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) +result = subprocess.run([str(probe_binary), str(fixture), str(report), str(output)], timeout=1500) if report.exists(): print(report.read_text(encoding='utf-8', errors='replace')) sys.exit(result.returncode)