diff --git a/sources/dataBase/projectdatabase.cpp b/sources/dataBase/projectdatabase.cpp index e8c7b92b2..b2b9d3da3 100644 --- a/sources/dataBase/projectdatabase.cpp +++ b/sources/dataBase/projectdatabase.cpp @@ -4119,9 +4119,24 @@ bool projectDataBase::savedLinks(const Element *element, QList projectDataBase::waitingSymbols() const +{ + QSet kept; + if (m_project) + for (Diagram *diagram : m_project->folios()) + if (diagram->isLoaded()) kept.unite(diagram->keptSymbolUuids()); + return unbuiltSymbols().unite(kept); +} + +/** + @brief projectDataBase::unbuiltSymbols + @return the symbols on folios not built yet and not built alone + (waitingSymbols()) +*/ +QSet projectDataBase::unbuiltSymbols() const { if (!m_project || !m_project->unloadedFolioCount()) { return QSet(); diff --git a/sources/dataBase/projectdatabase.h b/sources/dataBase/projectdatabase.h index da8d4103b..2c1118a58 100644 --- a/sources/dataBase/projectdatabase.h +++ b/sources/dataBase/projectdatabase.h @@ -165,6 +165,7 @@ class projectDataBase : public QObject QList> linkRowsOf(const QUuid &element) const; QSet waitingPartners(const QUuid &element, const QSet &waiting); QSet waitingSymbols() const; + QSet unbuiltSymbols() const; //Fill the three stores from a project's document before its //folios are built, then, once diff --git a/sources/diagram.cpp b/sources/diagram.cpp index 9940ced53..8d08da012 100644 --- a/sources/diagram.cpp +++ b/sources/diagram.cpp @@ -944,6 +944,132 @@ QStringList Diagram::wiresNotReconnected() const return m_wires_not_reconnected; } +/** + @brief Diagram::symbolsNotLoaded + @return one line per symbol of the loaded file that could not be built, + its definition missing from the project and the collections: kept as + read and saved back (F127). Empty for a folio that built every symbol. +*/ +QStringList Diagram::symbolsNotLoaded() const +{ + return m_symbols_not_loaded; +} + +/** + @brief Diagram::keptSymbolUuids + @return the uuids of the symbols this folio could not build and keeps + as read: a partner linked to one of them keeps its link +*/ +QSet Diagram::keptSymbolUuids() const +{ + QSet uuids; + for (const QDomElement &symbol : m_kept_symbols) + uuids.insert(QUuid(symbol.attribute(QStringLiteral("uuid")))); + return uuids; +} + +/** + @brief Diagram::keepWire + Keep @p wire, which could not be joined to its terminals, to save it + back (F127, F128). An end found (@p found1, @p found2) is written from + that terminal at save; an end not found keeps the symbol and terminal + it names, as uuids: for a wire saved by legacy terminal ids, the + symbol among @p symbols (uuid and XML of each symbol read) holding that + id and the uuid derived for that terminal where it was saved + (Element::savedTerminalUuid()), so the wire joins it again once the + symbol's definition has it there. An end naming no terminal of the + file at all pointed nowhere when it was saved: written as the null + uuid, so it names no symbol and is read back as a wire whose ends + cannot be read, kept unchanged and joining nothing (never a legacy id, + which the next save gives to another terminal). +*/ +void Diagram::keepWire(const QDomElement &wire, Terminal *found1, Terminal *found2, + const QList> &symbols) +{ + KeptWire kept; + Terminal *found[2] = {found1, found2}; + for (int i = 0 ; i < 2 ; ++i) { + const QString index = QString::number(i + 1); + if (found[i]) { + kept.end[i] = found[i]; + kept.found[i] = true; + continue; + } + if (wire.hasAttribute(QStringLiteral("element") + index)) { + kept.element[i] = QUuid(wire.attribute(QStringLiteral("element") + index)); + kept.terminal[i] = QUuid(wire.attribute(QStringLiteral("terminal") + index)); + } else { + const QString id = wire.attribute(QStringLiteral("terminal") + index); + for (const auto &symbol : symbols) { + const QList records = QET::findInDomElement( + symbol.second, QStringLiteral("terminals"), QStringLiteral("terminal")); + for (int r = 0 ; r < records.size() && kept.element[i].isNull() ; ++r) + if (records.at(r).attribute(QStringLiteral("id")) == id) { + kept.element[i] = symbol.first; + kept.terminal[i] = Element::savedTerminalUuid(records, r); + } + if (!kept.element[i].isNull()) break; + } + } + } + kept.xml = m_kept_document.importNode(wire, true).toElement(); + if (QUuid(kept.xml.attribute(QStringLiteral("uuid"))).isNull() && m_project) { + QStringList ends; + for (int i = 0 ; i < 2 ; ++i) + ends << (kept.found[i] + ? kept.end[i]->parentElement()->uuid().toString() + QLatin1Char('/') + + kept.end[i]->stableUuid().toString() + : kept.element[i].toString() + QLatin1Char('/') + kept.terminal[i].toString()); + ends.sort(); + kept.xml.setAttribute(QStringLiteral("uuid"), + m_project->derivedItemUuid(QStringLiteral("conductor"), + ends.join(QLatin1Char('\n'))).toString()); + } + m_kept_wires << kept; +} + +/** + @brief Diagram::writeKept + Write what this folio could not load, as it was read, into @p elements + and @p conductors of @p document: the symbols, their terminals given ids + after those of @p table_adr_id so none is read twice, and the wires, an + end found written from its terminal as Conductor::toXml() does. A wire + whose found end was deleted since goes with it. +*/ +void Diagram::writeKept(QDomDocument &document, QDomElement &elements, + QDomElement &conductors, const QHash &table_adr_id) const +{ + int next_id = 0; + for (int id : table_adr_id) next_id = qMax(next_id, id + 1); + for (const QDomElement &symbol : m_kept_symbols) { + QDomElement copy = document.importNode(symbol, true).toElement(); + for (QDomElement record : QET::findInDomElement(copy, QStringLiteral("terminals"), + QStringLiteral("terminal"))) + record.setAttribute(QStringLiteral("id"), next_id++); + elements.appendChild(copy); + } + for (const KeptWire &kept : m_kept_wires) { + if ((kept.found[0] && !kept.end[0]) || (kept.found[1] && !kept.end[1])) continue; + QDomElement copy = document.importNode(kept.xml, true).toElement(); + for (int i = 0 ; i < 2 && !kept.as_read ; ++i) { + const QString index = QString::number(i + 1); + const QString element = QStringLiteral("element") + index; + const QString terminal = QStringLiteral("terminal") + index; + if (!kept.found[i]) { + copy.setAttribute(element, kept.element[i].toString()); + copy.setAttribute(terminal, kept.terminal[i].toString()); + } else if (kept.end[i]->uuid().isNull()) { + copy.removeAttribute(element); + copy.setAttribute(terminal, table_adr_id.value(kept.end[i])); + } else { + copy.setAttribute(element, kept.end[i]->parentElement()->uuid().toString()); + copy.setAttribute(terminal, kept.end[i]->uuid().toString()); + } + } + conductors.appendChild(copy); + } +} + /** @brief Diagram::uuidUsedByOtherDiagram A hand-edited or merged project file can contain two folios with the same @@ -1268,7 +1394,10 @@ QDomDocument Diagram::toXml(bool whole_content, bool is_copy_command) { } document.appendChild(dom_root); - if (items().isEmpty()) + //What the folio could not load is written back with a whole folio + const bool write_kept = whole_content && !is_copy_command + && (!m_kept_symbols.isEmpty() || !m_kept_wires.isEmpty()); + if (items().isEmpty() && !write_kept) return(document); QVector list_elements; @@ -1375,8 +1504,8 @@ QDomDocument Diagram::toXml(bool whole_content, bool is_copy_command) { // table de correspondance entre les adresses des bornes et leurs ids QHash table_adr_id; - if (!list_elements.isEmpty()) { - auto dom_elements = document.createElement(QStringLiteral("elements")); + auto dom_elements = document.createElement(QStringLiteral("elements")); + if (!list_elements.isEmpty() || write_kept) { for (auto elmt : list_elements) { QDomElement dom_element = elmt->toXml(document, table_adr_id); ItemGroups::write(dom_element, elmt); @@ -1388,6 +1517,7 @@ QDomDocument Diagram::toXml(bool whole_content, bool is_copy_command) { dom_root.appendChild(dom_elements); } + auto dom_conductors = document.createElement(QStringLiteral("conductors")); if (!list_conductors.isEmpty()) { //Symbols copied in old versions can share one uuid. A wire //saved by that uuid would reopen on the first of them (#1408). @@ -1397,7 +1527,6 @@ QDomDocument Diagram::toXml(bool whole_content, bool is_copy_command) { shared_uuids.insert(elmt->uuid()); seen_uuids.insert(elmt->uuid()); } - auto dom_conductors = document.createElement(QStringLiteral("conductors")); for (auto cond : list_conductors) { dom_conductors.appendChild(cond->toXml(document, table_adr_id, @@ -1405,6 +1534,11 @@ QDomDocument Diagram::toXml(bool whole_content, bool is_copy_command) { } dom_root.appendChild(dom_conductors); } + if (write_kept) { + writeKept(document, dom_elements, dom_conductors, table_adr_id); + if (dom_conductors.hasChildNodes() && dom_conductors.parentNode().isNull()) + dom_root.insertAfter(dom_conductors, dom_elements); + } // A rect query, unlike items(), returns true stacking order (z, then // insertion order) even with NoIndex. @@ -1603,6 +1737,23 @@ void Diagram::folioSequentialsToXml(QHashderivedItemUuid( + QStringLiteral("element"), + QStringList{element_xml.attribute(QStringLiteral("type")), + element_xml.attribute(QStringLiteral("x")), + element_xml.attribute(QStringLiteral("y")), + element_xml.attribute(QStringLiteral("orientation"))} + .join(QLatin1Char('\n'))); +} + Element *Diagram::buildElement(QDomElement element_xml, QHash &table_adr_id, bool consider_informations) { @@ -1650,13 +1801,7 @@ Element *Diagram::buildElement(QDomElement element_xml, QHash & //uuids anyway. if (consider_informations && m_project && QUuid(element_xml.attribute(QStringLiteral("uuid"))).isNull()) { - nvel_elmt->setUuid(m_project->derivedItemUuid( - QStringLiteral("element"), - QStringList{type_id, - element_xml.attribute(QStringLiteral("x")), - element_xml.attribute(QStringLiteral("y")), - element_xml.attribute(QStringLiteral("orientation"))} - .join(QLatin1Char('\n')))); + nvel_elmt->setUuid(derivedSymbolUuid(element_xml)); } ItemGroups::setGroup(nvel_elmt, ItemGroups::read(element_xml)); return nvel_elmt; @@ -2092,6 +2237,14 @@ bool Diagram::fromXml(QDomElement &document, QHash table_adr_id; if (m_loading_deferred) { table_adr_id = m_partial_table; //the terminals of symbols built already + } + //Each symbol read, by uuid: the ends of a wire kept (keepWire()) + QList> read_symbols; + if (consider_informations) { + m_kept_document = QDomDocument(); + m_kept_symbols.clear(); + m_kept_wires.clear(); + m_symbols_not_loaded.clear(); } for (auto element_xml : QET::findInDomElement(root, QStringLiteral("elements"), QStringLiteral("element"))) @@ -2102,11 +2255,30 @@ bool Diagram::fromXml(QDomElement &document, Element *built = m_partial_elements.value(QUuid(element_xml.attribute(QStringLiteral("uuid")))); if (built) { added_elements << built; + read_symbols.append({built->uuid(), element_xml}); continue; } } - if (Element *nvel_elmt = buildElement(element_xml, table_adr_id, consider_informations)) + if (Element *nvel_elmt = buildElement(element_xml, table_adr_id, consider_informations)) { added_elements << nvel_elmt; + if (consider_informations) read_symbols.append({nvel_elmt->uuid(), element_xml}); + } else if (consider_informations) { + //Its definition is missing: kept as read, saved back, so a + //save loses nothing (F127) + QDomElement kept = m_kept_document.importNode(element_xml, true).toElement(); + if (QUuid(kept.attribute(QStringLiteral("uuid"))).isNull() && m_project) + kept.setAttribute(QStringLiteral("uuid"), derivedSymbolUuid(element_xml).toString()); + m_kept_symbols << kept; + read_symbols.append({QUuid(kept.attribute(QStringLiteral("uuid"))), element_xml}); + QString label; + for (const QDomElement &info : QET::findInDomElement( + element_xml, QStringLiteral("elementInformations"), QStringLiteral("elementInformation"))) + if (info.attribute(QStringLiteral("name")) == QLatin1String("label")) label = info.text(); + const QString type = element_xml.attribute(QStringLiteral("type")); + m_symbols_not_loaded << (label.isEmpty() + ? type.section(QLatin1Char('/'), -1) + : QStringLiteral("%1 (%2)").arg(label, type.section(QLatin1Char('/'), -1))); + } } //Texts, images and shapes written before they carried a uuid (or @@ -2262,7 +2434,17 @@ bool Diagram::fromXml(QDomElement &document, QStringLiteral("conductors"), QStringLiteral("conductor"))) { - if (!Conductor::valideXml(f)) continue; + if (!Conductor::valideXml(f)) { + //Its ends cannot be read: kept and written back unchanged + //(F128), it can join nothing + if (consider_informations) { + KeptWire kept; + kept.xml = m_kept_document.importNode(f, true).toElement(); + kept.as_read = true; + m_kept_wires << kept; + } + continue; + } //Check if terminal that conductor must be linked is know @@ -2297,6 +2479,8 @@ bool Diagram::fromXml(QDomElement &document, if (!num.isEmpty() && num != QLatin1String("_")) wire += QStringLiteral(" (%1)").arg(num); m_wires_not_reconnected << wire; + //Kept as read, saved back (F128) + keepWire(f, p1, p2, read_symbols); } if (p1 && p2 && p1 != p2) diff --git a/sources/diagram.h b/sources/diagram.h index cddb4d77e..614e5a184 100644 --- a/sources/diagram.h +++ b/sources/diagram.h @@ -39,6 +39,7 @@ class DiagramContent; class DiagramPosition; class DiagramTextItem; class Element; +class Terminal; class ElementsLocation; class DiagramImageItem; class DiagramEventInterface; @@ -165,6 +166,28 @@ class Diagram : public QGraphicsScene //Wires of the loaded file whose ends could not be found QStringList m_wires_not_reconnected; + //Symbols of the loaded file that could not be built + QStringList m_symbols_not_loaded; + + //What the folio could not load, written back as it was read + //(F127, F128): symbols whose definition is missing, and wires + //to a terminal not found + struct KeptWire { + QDomElement xml; + QPointer end[2]; ///< an end found, null if not + bool found[2] = {false, false}; + QUuid element[2]; ///< an end not found: its symbol + QUuid terminal[2]; ///< and its terminal + bool as_read = false; ///< no end can be read: written unchanged + }; + QDomDocument m_kept_document; + QList m_kept_symbols; + QList m_kept_wires; + QUuid derivedSymbolUuid(const QDomElement &element_xml) const; + void keepWire(const QDomElement &wire, Terminal *found1, Terminal *found2, + const QList> &symbols); + void writeKept(QDomDocument &document, QDomElement &elements, + QDomElement &conductors, const QHash &table_adr_id) const; // METHODS protected: @@ -189,6 +212,8 @@ class Diagram : public QGraphicsScene void restoreText(Element* elmt); QUuid uuid(); QStringList wiresNotReconnected() const; + QStringList symbolsNotLoaded() const; + QSet keptSymbolUuids() const; void setEventInterface (DiagramEventInterface *event_interface); void clearEventInterface(); diff --git a/sources/qetdiagrameditor.cpp b/sources/qetdiagrameditor.cpp index ed35d1085..c3a3016c6 100644 --- a/sources/qetdiagrameditor.cpp +++ b/sources/qetdiagrameditor.cpp @@ -2262,15 +2262,38 @@ bool QETDiagramEditor::openAndAddProject( ); } - //Report wires left out of the load because a terminal they join - //was not found: they would otherwise vanish on the next save - //without the user ever being told. - QStringList lost_wires; + //Report the elements and wires left out of the load: an element + //whose definition is missing, a wire whose terminal was not found. + //They are kept in the file as they were (F127, F128), but are not + //shown. + QStringList lost_elements, lost_wires; + for (Diagram *diagram : project->folios()) { + if (!diagram->isLoaded()) continue; + for (const QString &element : diagram->symbolsNotLoaded()) + lost_elements << tr("Sheet %1: %2").arg(diagram->folioIndex() + 1).arg(element); + } for (Diagram *diagram : project->foliosWithWiresNotReconnected()) { for (const QString &wire : diagram->wiresNotReconnected()) { lost_wires << tr("Sheet %1: %2").arg(diagram->folioIndex() + 1).arg(wire); } } + if (interactive && !lost_elements.isEmpty()) + { + QMessageBox box(QMessageBox::Warning, + tr("Elements not loaded", "message box title"), + tr("%n elements could not be loaded: their definition is " + "missing from the project and from the collections.\n" + "\n" + "They are not shown, but they are kept in the file as they " + "are, with their conductors, and come back once their " + "definition is available again.", + "message box content", + lost_elements.size()), + QMessageBox::Ok, + this); + box.setDetailedText(lost_elements.join(QLatin1Char('\n'))); + box.exec(); + } if (interactive && !lost_wires.isEmpty()) { QMessageBox box(QMessageBox::Warning, @@ -2280,9 +2303,9 @@ bool QETDiagramEditor::openAndAddProject( "was probably replaced by another one with different " "terminals.\n" "\n" - "If you save the project, these conductors will disappear " - "from the file. Close it without saving to keep the file as " - "it is.", + "They are not shown, but they are kept in the file as they " + "are, and join their terminals again once the definition " + "has them where they were.", "message box content", lost_wires.size()), QMessageBox::Ok, diff --git a/sources/qetgraphicsitem/element.cpp b/sources/qetgraphicsitem/element.cpp index e9eacba5f..2d487a1aa 100644 --- a/sources/qetgraphicsitem/element.cpp +++ b/sources/qetgraphicsitem/element.cpp @@ -975,6 +975,41 @@ bool Element::valideXml(QDomElement &e) return(true); } +/** + @brief Element::savedTerminalUuid + @return the uuid TerminalUuids derives for the terminal saved as + @p records [@p index] (a symbol's saved terminals, in file order) at the + place it was saved: its docking point less the stub Terminal::init() + adds, and its rank among the records at that place, as the project + counted it when it gave the terminals their uuids +*/ +QUuid Element::savedTerminalUuid(const QList &records, int index) +{ + const auto dock_of = [](const QDomElement &r) { + return QPointF(r.attribute(QStringLiteral("x")).toDouble(), + r.attribute(QStringLiteral("y")).toDouble()); + }; + const QDomElement &record = records.at(index); + const int orientation = record.attribute(QStringLiteral("orientation")).toInt(); + const QPointF dock = dock_of(record); + QPointF old_pos = dock; + switch (orientation) { + case Qet::North: old_pos -= QPointF(0, Terminal::terminalSize); break; + case Qet::East : old_pos -= QPointF(-Terminal::terminalSize, 0); break; + case Qet::West : old_pos -= QPointF(Terminal::terminalSize, 0); break; + case Qet::South: old_pos -= QPointF(0, -Terminal::terminalSize); break; + } + int rank = 0; + for (int j = 0; j < index; ++j) { + const QPointF other = dock_of(records.at(j)); + if (records.at(j).attribute(QStringLiteral("orientation")).toInt() == orientation + && qAbs(other.x() - dock.x()) < 1e-6 + && qAbs(other.y() - dock.y()) < 1e-6) + ++rank; + } + return TerminalUuids::derived(old_pos.x(), old_pos.y(), orientation, rank); +} + /** @brief Element::matchMovedTerminals Pair the saved terminals that no live terminal matched exactly with the @@ -1033,32 +1068,8 @@ void Element::matchMovedTerminals(const QList &records, //Pair a record with a live terminal: register its legacy id, and the //uuid derived for the terminal at its old place const auto pair_up = [&](int index, Terminal *t) { - const QDomElement &record = records.at(index); - const QPointF dock = dock_of(record); - - //The old place of the terminal: its docking point less the - //stub that Terminal::init() added - QPointF old_pos = dock; - switch (orientation_of(record)) { - case Qet::North: old_pos -= QPointF(0, Terminal::terminalSize); break; - case Qet::East : old_pos -= QPointF(-Terminal::terminalSize, 0); break; - case Qet::West : old_pos -= QPointF(Terminal::terminalSize, 0); break; - case Qet::South: old_pos -= QPointF(0, -Terminal::terminalSize); break; - } - //The rank among the records at the same place, as the project - //counted it when it gave the terminals their uuids - int rank = 0; - for (int j = 0; j < index; ++j) { - const QPointF other = dock_of(records.at(j)); - if (orientation_of(records.at(j)) == orientation_of(record) - && qAbs(other.x() - dock.x()) < 1e-6 - && qAbs(other.y() - dock.y()) < 1e-6) - ++rank; - } - - id_adr.insert(record.attribute(QStringLiteral("id")).toInt(), t); - m_old_place_terminals.insert(TerminalUuids::derived(old_pos.x(), old_pos.y(), - orientation_of(record), rank), t); + id_adr.insert(records.at(index).attribute(QStringLiteral("id")).toInt(), t); + m_old_place_terminals.insert(savedTerminalUuid(records, index), t); m_moved_terminals << t; }; const auto same_place = [](const QPointF &a, const QPointF &b) { diff --git a/sources/qetgraphicsitem/element.h b/sources/qetgraphicsitem/element.h index 22384b9d9..b5f6764f5 100644 --- a/sources/qetgraphicsitem/element.h +++ b/sources/qetgraphicsitem/element.h @@ -183,6 +183,7 @@ class Element : public QetGraphicsItem QPoint hotspot() const; void editProperty() override; static bool valideXml(QDomElement &); + static QUuid savedTerminalUuid(const QList &records, int index); virtual bool fromXml( QDomElement &, QHash + + 10/2/26 + 02-10-2026 + 2026-10-02 + free_text_width + 18:39 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + K1 + + + + A2 + + + A1 + + + K1 + label + + + + + + + + + + + + + + + + K1 + label + + + + + + + + + + + + + + + + + + + + + + + Imported elements + + + + كهربائي + Elèctric + Elektrotechnika + Elektrisk + Elektrik + Ηλεκτρικά + Electric + Eléctrica + Sähkö + Electrique + Električno + Elektromos + Elettrica + 電気 + 전기 + Цахилгаан + Elektrisk + Elektrotechniek + Elektrisch + Elektrotechnika + Elétrico + Elétrica + Electric + Электротехника + Elektrické + Elektrika + Електрика + El + Elektrik + Електрика + 10_电气 + 电气工程 + + + + متعدد الأقطاب + Multifilar + Vícežilový + Flere ledere + Allpolig + Πολυγραμμικό + All-pole + Multifilar + Yleisnapa + Multifilaire + Univerzalni + Többvonalas szimbólumok + Multifilare + 複線 + 다-극 + Бүх туйл + Alle poler + Veel polig + All-pole + Schematy wieloliniowe + Multipolar + Multifilar + Bipolar + Многополюсные + Všepólové + Vsi poli + Свe-полни + Allpolig + Çok kutuplu + Багатополюсні + 10_全极 + + + + المرحّلات والموصّلات وجهات الاتصال + Relés, contactors i contactes + Relé, stykače a kontakty + Relæer, kontaktorer og kontakter + Relais, Schütze und Kontakte + Ηλεκτρονόμοι και επαφές + Relays, contactors and contacts + Relevadores, contactores y contactos + Releet, kontaktorit ja koskettimet + Relais, contacteurs et contacts + Releji, kontaktori i kontakti + Relék, kontaktorok és érintkezők + Relè, contattori e contatti + 継電器、接触器、接点 + 릴레이, 커넥터및 접점 + Реле, контактор болон контакт + Reléer, kontaktorere og kontakter + Relais, contactoren en contacten + Relais, contacteurs en contacten + Przekaźniki i styczniki + Relés, contatores e contactos + Relés, Contatores e contatos + Relee, contactoare și contacte + Реле, пускатели и контакты + Relé, stykače a kontakty + Releji, kontaktorji in kontakti + Релеји, контактори и контакти + Reläer, kontaktorer och kontakter + Röleler, kontaktörler ve kontaklar + Реле, контактори та контакти + 继电器、接触器与触点 + + + + لفه + Bobines + cívka + spole + Spule + σπείρα + coil + bobina + Käämi + bobine + zavojnica + tekercs + bobina + コイル + 코일 + ороомог + Spole + spoel + Spoel + cewka + bobina + bobina + bobina + катушка + Cievka + tuljava + калем + spole + bobin + котушка + 线圈 + + + + + + ملف + Bobina + Cívka relé + Relæ + Spule + Πηνίο + Coil + Bobina + Käämi + Bobine + Zavojnica + Tekercs + Bobina + コイル + 코일 + Ороомог + Spole + Spoel + Relais spoel (algemeen) + Cewka + Bobina + Bobina + Bobina + Обмотка + Cievka + Tuljava + Каlem + Spole + Bobin + Котушка + 三线圈 + + + coil + + Author: The QElectroTech team +License: see http://qelectrotech.org/wiki/doc/elements_license + + + + + A2 + + + A1 + + + + + label + + + + + + + + + + تماسات مع إحالات + Contactes amb referències creuades + Kontakty s křížovými odkazy + Kontaker med krydsreference + Kontakte mit Querverweis + Επαφές με παραπομπές + Contacts with cross referencing + Contactos con referencia cruzada + Kosketimet lehtiviittauksella + Contacts avec référence croisée + Kontakti s unakrsnim referenciranjem + Érintkezők kereszthivatkozásokkal + Contatti con riferimenti incrociati + 相互参照付き接点 + 교차 참조 접점 + Харилцан холбоос бүхий контакт + Kontakter med kryssreferanse + Contacten met kruisreferentie + Contacten met kruisverwijzing + Zestyki (automatyczne oznaczenia) + Contactos com renvoi de folio + Contatos com referencia cruzada + Contacte cu renvoi de folio + Контакты с перекрестными ссылками + Kontakty s krížovým odkazom + Kontakti s sklici + Контакти са унакрсним референцирањем + Kontakter med bladhänvisning + Çapraz referanslı kontaklar + Контакти з перехресними посиланнями + 触点交叉引用 + + + + مساعد + Auxiliars + Pomocné kontakty + Hjælpe kontakter + Hilfsglieder + Βοηθητικές επαφές + Auxiliary + Contactos auxiliares + Apupiiri + Auxilliaires + Pomoćni + Segéd + Contatti ausiliari + 補助 + 보조 접점 + Туслах + Hjelpe + Hulpcontacten + Hulpcontacten + Zestyki pomocnicze + Auxiliar + Auxiliares + Auxiliare + Внешние + Pomocné + Pomožni + Помоћни + Hjälp + Yardımcı + Допоміжні + 辅助触点 + + + + + + مُلامس بسيط (NO) + Contacte senzill + Spínací kontakt + Enkel kontakt + Schliesser + Απλή επαφή, ανοικτή + Simple contact + Contactor simple + Yksinkertainen kosketin + Contact simple + Jednostavni kontakt + Egyszerű érintkező + Contatto semplice (NO) + 単純接点 + 단순 접점 + Энгийн контакт + Enkel kontakt + contact enkel + Eenvoudig contact + Zestyk zwierny + Contacto simples (NO) + Contato simples + Contact simplu + Проктой контакт (НР) + Jednoduchý kontakt + Enostavni kontakt + Прости контакт + Enkelkontakt + Basit kontak + Простий контакт + 简单连接器 + + + simple + NO + 1 + + Author: The QElectroTech team +License: see http://qelectrotech.org/wiki/doc/elements_license + + + + + label + + + + + + + + + + + + + + + diff --git a/tests/qttest/tst_resaveunchanged.cpp b/tests/qttest/tst_resaveunchanged.cpp index fe6a90a00..fe428c90b 100644 --- a/tests/qttest/tst_resaveunchanged.cpp +++ b/tests/qttest/tst_resaveunchanged.cpp @@ -802,6 +802,101 @@ private slots: QStringLiteral("]*name=\"accents\"[^>]*>Armoire façade été"))), "the accented value changed"); } + + // What a folio cannot load is saved back as it was read (F127, F128). + // fixtures/coil_contact_wired.qet: a coil K1 and its contact, linked, + // two wires between them. Each row damages it as an uploaded project + // can be: the coil's definition missing from the file, or the + // contact's definition redrawn (terminals moved, other uuids). A save + // keeps both symbols, both wires and both links, a second save changes + // nothing, and once the coil's definition is back the save is the + // undamaged project's again. + void unloadableItemsKept_data() + { + QTest::addColumn("damage"); + QTest::newRow("definition missing") << QStringLiteral("coil"); + QTest::newRow("terminals redrawn") << QStringLiteral("contact"); + } + void unloadableItemsKept() + { + QFETCH(QString, damage); + const QString fixture = QFINDTESTDATA("fixtures/coil_contact_wired.qet"); + const QString whole = resave(fixture); + QVERIFY2(!whole.isEmpty(), "--resave of the fixture failed"); + const QString xml = QString::fromUtf8(read(whole)); + static const QRegularExpression coil_definition(QStringLiteral( + ".*?\\s*"), + QRegularExpression::DotMatchesEverythingOption); + const QString coil = coil_definition.match(xml).captured(0); + QVERIFY(!coil.isEmpty()); + QString damaged = xml; + if (damage == QLatin1String("coil")) { + damaged.remove(coil); + } else { + //The contact's terminals 40 further right, with new uuids + static const QRegularExpression contact_definition(QStringLiteral( + ".*?"), + QRegularExpression::DotMatchesEverythingOption); + QString contact = contact_definition.match(xml).captured(0); + QVERIFY(!contact.isEmpty()); + const QString before = contact; + static const QRegularExpression terminal(QStringLiteral( + "(]*uuid=\"\\{)[0-9a-f]{8}([^>]*\\bx=\")(-?[0-9.]+)")); + int n = 0; + for (auto it = terminal.globalMatch(before); it.hasNext() ; ++n) { + const auto m = it.next(); + contact.replace(m.captured(0), m.captured(1) + QStringLiteral("%1").arg(n, 8, 10, QLatin1Char('0')) + + m.captured(2) + QString::number(m.captured(3).toDouble() + 40)); + } + QCOMPARE(n, 2); + damaged.replace(before, contact); + } + const QString damaged_path = m_dir.filePath(QStringLiteral("damaged%1.qet").arg(m_run)); + QFile out(damaged_path); + QVERIFY(out.open(QIODevice::WriteOnly)); + out.write(damaged.toUtf8()); + out.close(); + + auto count = [](const QString &text, const char *pattern) { + return int(text.count(QRegularExpression(QString::fromLatin1(pattern)))); + }; + const QString first = resave(damaged_path); + QVERIFY2(!first.isEmpty(), "--resave of the damaged project failed"); + const QString saved = QString::fromUtf8(read(first)); + QCOMPARE(count(saved, "]*\\btype="), 2); + QCOMPARE(count(saved, "\\s*.*?\\s*)"), + QRegularExpression::DotMatchesEverythingOption); + QString mended = saved; + QVERIFY(collection_category.match(mended).hasMatch()); + mended.replace(collection_category, QStringLiteral("\\1") + coil); + const QString mended_path = m_dir.filePath(QStringLiteral("mended%1.qet").arg(m_run)); + QFile mend(mended_path); + QVERIFY(mend.open(QIODevice::WriteOnly)); + mend.write(mended.toUtf8()); + mend.close(); + const QString back = resave(mended_path); + QVERIFY2(!back.isEmpty(), "--resave of the mended project failed"); + auto items = [](const QString &text, const char *tag) { + QStringList found; + static const QRegularExpression noise(QStringLiteral(" (element[12]_(label|linked|name)|terminalname[12])=\"[^\"]*\"")); + auto it = QRegularExpression(QStringLiteral("<%1 [^>]*>").arg(QLatin1String(tag))).globalMatch(text); + while (it.hasNext()) found << it.next().captured(0).remove(noise); + found.sort(); + return found; + }; + const QString again = QString::fromUtf8(read(back)); + QCOMPARE(items(again, "element"), items(xml, "element")); + QCOMPARE(items(again, "conductor"), items(xml, "conductor")); + } }; QTEST_APPLESS_MAIN(tst_resaveunchanged)