Merge pull request #1552 from ispyisail/fix/keep-unloadable-items
Linux build and tests / Build and test (Qt 6, Debug) (push) Failing after 1m43s

Fix symbols and wires lost when their definition cannot be found
This commit is contained in:
ispyisail
2026-10-19 20:58:13 +13:00
committed by GitHub
13 changed files with 898 additions and 62 deletions
+46 -2
View File
@@ -19,6 +19,7 @@
#include "qetcontainerdb.h"
#include <QCryptographicHash>
#include <functional>
#include <QHash>
#include <QMap>
#include <QSet>
@@ -242,6 +243,28 @@ bool QetContainer::ownUuids(const QDomElement &project)
return true;
}
/**
@brief QetContainer::embeddedDefinitions
@return the paths of the symbol definitions the project @p project
holds in its collection, as a symbol's type names them after embed://
*/
QSet<QString> QetContainer::embeddedDefinitions(const QDomElement &project)
{
QSet<QString> paths;
std::function<void(const QDomElement &, const QString &)> 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")) paths.insert(name);
}
};
for (const QDomElement &collection : childElements(project, QStringLiteral("collection")))
walk(collection, QString());
return paths;
}
/**
@brief QetContainer::dropComputed
Take out of the folio @p diagram what QElectroTech computes when it
@@ -253,19 +276,39 @@ bool QetContainer::ownUuids(const QDomElement &project)
list of its terminals. Checked on 342 projects: opened and saved, each
comes back as it was, but the terminal lists of a file whose symbols
share a uuid (F100), where wires find their ends by them.
Nothing is taken from a symbol whose definition is not among
@p defined (embeddedDefinitions()), nor from a wire ending on one:
QElectroTech cannot build that symbol, keeps it as read and saves it
back (F127), so nothing of it would be computed again.
*/
void QetContainer::dropComputed(QDomElement diagram, bool terminal_lists)
void QetContainer::dropComputed(QDomElement diagram, bool terminal_lists,
const QSet<QString> &defined)
{
QSet<QString> undefined;
for (const QDomElement &list : childElements(diagram, QStringLiteral("elements")))
for (const QDomElement &element : childElements(list, QStringLiteral("element"))) {
const QString type = element.attribute(QStringLiteral("type"));
if (!type.startsWith(QLatin1String("embed://"))
|| !defined.contains(type.mid(int(qstrlen("embed://")))))
undefined.insert(element.attribute(QStringLiteral("uuid")));
}
for (QDomElement list : childElements(diagram, QStringLiteral("conductors")))
for (QDomElement wire : childElements(list, QStringLiteral("conductor")))
{
if (undefined.contains(wire.attribute(QStringLiteral("element1")))
|| undefined.contains(wire.attribute(QStringLiteral("element2"))))
continue;
for (const char *end : {"1", "2"})
for (const char *name : {"element%1_label", "element%1_name", "element%1_linked",
"terminalname%1"})
wire.removeAttribute(QString::fromLatin1(name).arg(QLatin1String(end)));
}
for (QDomElement list : childElements(diagram, QStringLiteral("elements")))
{
for (QDomElement element : childElements(list, QStringLiteral("element")))
{
if (undefined.contains(element.attribute(QStringLiteral("uuid"))))
continue;
if (terminal_lists)
for (QDomElement terminals : childElements(element, QStringLiteral("terminals")))
removeElement(terminals);
@@ -341,8 +384,9 @@ QList<QetZip::Entry> QetContainer::split(const QDomDocument &project, const QStr
int folio = 0;
//Nothing QElectroTech computes is kept (format 4)
const bool own_uuids = ownUuids(root);
const QSet<QString> defined = embeddedDefinitions(root);
for (QDomElement diagram : childElements(root, QStringLiteral("diagram")))
dropComputed(diagram, own_uuids);
dropComputed(diagram, own_uuids, defined);
//The parts of the folios kept as they were, and their pictures
QMap<QString, QByteArray> pictures; //name -> png, once each
+3 -1
View File
@@ -89,7 +89,9 @@ namespace QetContainer
void inlinePicture(QDomElement image, const QByteArray &bytes);
/// Take what QElectroTech computes out of a folio (format 4)
void dropComputed(QDomElement diagram, bool terminal_lists);
void dropComputed(QDomElement diagram, bool terminal_lists, const QSet<QString> &defined);
/// The symbol definitions a project holds, by path after embed://
QSet<QString> embeddedDefinitions(const QDomElement &project);
/// true if every folio, symbol and wire has a uuid of its own
bool ownUuids(const QDomElement &project);
/// Parse @p xml as a .qet is read (QETProject::openFile)
+16 -1
View File
@@ -4119,9 +4119,24 @@ bool projectDataBase::savedLinks(const Element *element, QList<std::pair<QUuid,
@brief projectDataBase::waitingSymbols
@return the symbols on folios not built yet (lazy folios) and not
built alone as partners either (Diagram::buildSymbols()): partners a
symbol keeps its rows to until they are built
symbol keeps its rows to until they are built; and the symbols a built
folio could not build and keeps as read (Diagram::keptSymbolUuids())
*/
QSet<QUuid> projectDataBase::waitingSymbols() const
{
QSet<QUuid> 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<QUuid> projectDataBase::unbuiltSymbols() const
{
if (!m_project || !m_project->unloadedFolioCount()) {
return QSet<QUuid>();
+1
View File
@@ -165,6 +165,7 @@ class projectDataBase : public QObject
QList<std::pair<QUuid, int>> linkRowsOf(const QUuid &element) const;
QSet<QUuid> waitingPartners(const QUuid &element, const QSet<QUuid> &waiting);
QSet<QUuid> waitingSymbols() const;
QSet<QUuid> unbuiltSymbols() const;
//Fill the three stores from a project's document before its
//folios are built, then, once
+197 -13
View File
@@ -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<QUuid> Diagram::keptSymbolUuids() const
{
QSet<QUuid> 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<QPair<QUuid, QDomElement>> &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<QDomElement> 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<Terminal *, int> &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<Element *> 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<Terminal *, int> 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(QHash<QString,
fromXml() does for each symbol it reads.
@return the symbol, or nullptr if it could not be made (said in the log)
*/
/**
@brief Diagram::derivedSymbolUuid
@return the uuid of the symbol @p element_xml, saved without one:
derived from what it is and where it sits on its folio, never from the
folio's index (see buildElement())
*/
QUuid Diagram::derivedSymbolUuid(const QDomElement &element_xml) const
{
return m_project->derivedItemUuid(
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<int, Terminal *> &table_adr_id,
bool consider_informations)
{
@@ -1650,13 +1801,7 @@ Element *Diagram::buildElement(QDomElement element_xml, QHash<int, Terminal *> &
//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<int, Terminal *> 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<QPair<QUuid, QDomElement>> 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)
+25
View File
@@ -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<Terminal> 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<QDomElement> m_kept_symbols;
QList<KeptWire> m_kept_wires;
QUuid derivedSymbolUuid(const QDomElement &element_xml) const;
void keepWire(const QDomElement &wire, Terminal *found1, Terminal *found2,
const QList<QPair<QUuid, QDomElement>> &symbols);
void writeKept(QDomDocument &document, QDomElement &elements,
QDomElement &conductors, const QHash<Terminal *, int> &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<QUuid> keptSymbolUuids() const;
void setEventInterface (DiagramEventInterface *event_interface);
void clearEventInterface();
+32 -10
View File
@@ -2262,27 +2262,49 @@ 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,
tr("Conductors not loaded", "message box title"),
tr("%n conductors could not be joined to their terminals and "
"were not loaded. The element's definition in the project "
"was probably replaced by another one with different "
"terminals.\n"
"were not loaded. The element's definition is missing, or "
"was 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,
+37 -26
View File
@@ -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<QDomElement> &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<QDomElement> &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) {
+1
View File
@@ -183,6 +183,7 @@ class Element : public QetGraphicsItem
QPoint hotspot() const;
void editProperty() override;
static bool valideXml(QDomElement &);
static QUuid savedTerminalUuid(const QList<QDomElement> &records, int index);
virtual bool fromXml(
QDomElement &,
QHash<int,
@@ -0,0 +1,421 @@
<project title="Free text widths" uuid="{5b0f5e1c-8a43-4c1e-9e57-3f0d0f0a7a00}" version="0.200.1">
<properties>
<property name="saveddate" show="1">10/2/26</property>
<property name="saveddate-eu" show="1">02-10-2026</property>
<property name="saveddate-us" show="1">2026-10-02</property>
<property name="savedfilename" show="1">free_text_width</property>
<property name="savedtime" show="1">18:39</property>
</properties>
<usage enabled="true" time_spent="0"/>
<newdiagrams>
<border cols="17" colsize="60" displaycols="true" displayrows="true" rows="8" rowsize="80"/>
<inset author="" auto_page_num="" date="null" displayAt="bottom" filename="" folio="" indexrev="" locmach="" plant="" title="" version=""/>
<conductors bicolor="false" bus="" cable="" color2="#000000" condsize="1" conductor_color="" conductor_section="" dash-size="1" displaytext="1" formula="" function="" horizontal-alignment="AlignBottom" horizrotatetext="0" num="_" numsize="7" onetextperfolio="0" tension_protocol="" text_color="#000000" type="multi" vertical-alignment="AlignRight" vertirotatetext="270"/>
<report label="%f-%l%c"/>
<xrefs>
<xref delayprefix="" displayhas="cross" master_label="%f-%l%c" offset="0" powerprefix="" showallconfiguredslaves="false" showpowerctc="true" showterminalname="true" slave_label="(%f-%l%c)" slave_offset="0" snapto="label" stackoverlapping="true" switchprefix="" type="cable" xrefpos="AlignBottom"/>
<xref delayprefix="" displayhas="cross" master_label="%f-%l%c" offset="0" powerprefix="" showallconfiguredslaves="false" showpowerctc="true" showterminalname="true" slave_label="(%f-%l%c)" slave_offset="0" snapto="label" stackoverlapping="true" switchprefix="" type="coil" xrefpos="AlignBottom"/>
<xref delayprefix="" displayhas="cross" master_label="%f-%l%c" offset="0" powerprefix="" showallconfiguredslaves="false" showpowerctc="true" showterminalname="true" slave_label="(%f-%l%c)" slave_offset="0" snapto="label" stackoverlapping="true" switchprefix="" type="commutator" xrefpos="AlignBottom"/>
<xref delayprefix="" displayhas="cross" master_label="%f-%l%c" offset="0" powerprefix="" showallconfiguredslaves="false" showpowerctc="true" showterminalname="true" slave_label="(%f-%l%c)" slave_offset="0" snapto="label" stackoverlapping="true" switchprefix="" type="plc" xrefpos="AlignBottom"/>
<xref delayprefix="" displayhas="cross" master_label="%f-%l%c" offset="0" powerprefix="" showallconfiguredslaves="false" showpowerctc="true" showterminalname="true" slave_label="(%f-%l%c)" slave_offset="0" snapto="label" stackoverlapping="true" switchprefix="" type="protection" xrefpos="AlignBottom"/>
</xrefs>
<conductors_autonums auto_break_conductors="false" current_autonum="" freeze_new_conductors="false"/>
<folio_autonums/>
<element_autonums current_autonum="" freeze_new_elements="false"/>
<guides/>
</newdiagrams>
<diagram author="" auto_page_num="" cols="17" colsize="60" date="null" displayAt="bottom" displaycols="true" displayrows="true" filename="" folio="" freezeNewConductor="false" freezeNewElement="false" height="660" indexrev="" locmach="" order="1" plant="" rows="8" rowsize="80" title="" uuid="{5b0f5e1c-8a43-4c1e-9e57-3f0d0f0a7a01}" version="0.200.1-dev">
<defaultconductor bicolor="false" bus="" cable="" color2="#000000" condsize="1" conductor_color="" conductor_section="" dash-size="1" displaytext="1" formula="" function="" horizontal-alignment="AlignBottom" horizrotatetext="0" num="_" numsize="7" onetextperfolio="0" tension_protocol="" text_color="#000000" type="multi" vertical-alignment="AlignRight" vertirotatetext="270"/>
<elements>
<element freezeLabel="false" is_movable="1" orientation="0" prefix="K" type="embed://import/10_electric/10_allpole/310_relays_contactors_contacts/01_coils/bobine3.elmt" uuid="{ee860e7c-eaf3-404a-9c71-5ae4b296bdde}" x="200" y="200" z="10">
<terminals>
<terminal id="0" orientation="0" x="0" y="-16"/>
<terminal id="1" orientation="2" x="0" y="16"/>
</terminals>
<inputs/>
<links_uuids>
<link_uuid uuid="{8f8831dc-ebbe-48e0-8916-c014c9dd31eb}"/>
</links_uuids>
<elementInformations>
<elementInformation name="label" show="1">K1</elementInformation>
</elementInformations>
<dynamic_texts>
<dynamic_elmt_text Halignment="AlignLeft" Valignment="AlignTop" font="Liberation Sans,4,-1,5,25,0,0,0,0,0,Regular" frame="false" keep_visual_rotation="false" rotation="0" rotation_point_center="false" text_from="UserText" text_width="-1" uuid="{28faae8b-3f47-4578-9708-eee8500d3507}" x="2" y="11.33">
<text>A2</text>
</dynamic_elmt_text>
<dynamic_elmt_text Halignment="AlignLeft" Valignment="AlignTop" font="Liberation Sans,4,-1,5,25,0,0,0,0,0,Regular" frame="false" keep_visual_rotation="false" rotation="0" rotation_point_center="false" text_from="UserText" text_width="-1" uuid="{bcabc653-1e9a-43d2-addd-722e2b0bff35}" x="2" y="-24.67">
<text>A1</text>
</dynamic_elmt_text>
<dynamic_elmt_text Halignment="AlignLeft" Valignment="AlignTop" font="Liberation Sans,9,-1,5,50,0,0,0,0,0,Regular" frame="false" keep_visual_rotation="false" rotation="0" rotation_point_center="false" text_from="ElementInfo" text_width="-1" uuid="{93524f29-abab-4d7a-950c-fe6bfa385fa9}" x="25" y="-9.17">
<text>K1</text>
<info_name>label</info_name>
</dynamic_elmt_text>
</dynamic_texts>
<texts_groups/>
</element>
<element freezeLabel="false" is_movable="1" orientation="0" prefix="S" type="embed://import/10_electric/10_allpole/310_relays_contactors_contacts/02_contacts_cross_referencing/01_auxiliary_contacts/con_simple.elmt" uuid="{8f8831dc-ebbe-48e0-8916-c014c9dd31eb}" x="400" y="200" z="10">
<terminals>
<terminal id="2" orientation="0" x="0" y="-16"/>
<terminal id="3" orientation="2" x="0" y="16"/>
</terminals>
<inputs/>
<links_uuids>
<link_uuid uuid="{ee860e7c-eaf3-404a-9c71-5ae4b296bdde}"/>
</links_uuids>
<dynamic_texts>
<dynamic_elmt_text Halignment="AlignLeft" Valignment="AlignTop" font="Liberation Sans,9,-1,5,50,0,0,0,0,0,Regular" frame="false" keep_visual_rotation="false" rotation="0" rotation_point_center="false" text_from="ElementInfo" text_width="-1" uuid="{e6230b67-df36-4e2b-96f9-5e1b6ff44cf9}" x="2.75" y="-9.5">
<text>K1</text>
<info_name>label</info_name>
</dynamic_elmt_text>
</dynamic_texts>
<texts_groups/>
</element>
</elements>
<conductors>
<conductor bicolor="false" bus="" cable="" color2="#000000" condsize="1" conductor_color="" conductor_section="" dash-size="1" displaytext="1" element1="{ee860e7c-eaf3-404a-9c71-5ae4b296bdde}" element1_label="K1" element1_linked="" element1_name="Coil" element2="{8f8831dc-ebbe-48e0-8916-c014c9dd31eb}" element2_label="" element2_linked="K1" element2_name="Simple contact" formula="" freezeLabel="false" function="" horizontal-alignment="AlignBottom" horizrotatetext="0" num="_" numsize="7" onetextperfolio="0" tension_protocol="" terminal1="{8d0fa333-2d98-4a75-8a4e-21c81cce7ec3}" terminal2="{b3b3dc63-1c98-409b-ab79-3dbaea3556cb}" terminalname1="A1" terminalname2="" text_color="#000000" type="multi" uuid="{965e3155-9b9d-41f3-af7d-03ecf52ddf9a}" vertical-alignment="AlignRight" vertirotatetext="270" x="0" y="0">
<sequentialNumbers/>
</conductor>
<conductor bicolor="false" bus="" cable="" color2="#000000" condsize="1" conductor_color="" conductor_section="" dash-size="1" displaytext="1" element1="{ee860e7c-eaf3-404a-9c71-5ae4b296bdde}" element1_label="K1" element1_linked="" element1_name="Coil" element2="{8f8831dc-ebbe-48e0-8916-c014c9dd31eb}" element2_label="" element2_linked="K1" element2_name="Simple contact" formula="" freezeLabel="false" function="" horizontal-alignment="AlignBottom" horizrotatetext="0" num="_" numsize="7" onetextperfolio="0" tension_protocol="" terminal1="{c5376fd7-bdf1-4c10-985a-0d7d5f52c8f9}" terminal2="{b9683546-f0a2-4386-b114-ef1d46b2df3d}" terminalname1="A2" terminalname2="" text_color="#000000" type="multi" uuid="{5ccf65d5-ed6a-45f3-b0bc-d041c36cebca}" vertical-alignment="AlignRight" vertirotatetext="270" x="0" y="0">
<sequentialNumbers/>
</conductor>
</conductors>
<inputs>
<input font="Liberation Sans,9,-1,5,50,0,0,0,0,0,Regular" rotation="0" text="&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;>&#xa;&lt;html>&lt;head>&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; />&lt;meta charset=&quot;utf-8&quot; />&lt;style type=&quot;text/css&quot;>&#xa;p, li { white-space: pre-wrap; }&#xa;hr { height: 1px; border-width: 0; }&#xa;li.unchecked::marker { content: &quot;\2610&quot;; }&#xa;li.checked::marker { content: &quot;\2612&quot;; }&#xa;&lt;/style>&lt;/head>&lt;body style=&quot; font-family:'Liberation Sans'; font-size:9pt; font-weight:400; font-style:normal;&quot;>&#xa;&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;>FreeAlpha FreeBeta FreeGamma FreeDelta FreeEpsilon&lt;/p>&lt;/body>&lt;/html>" text_width="70" uuid="{5b0f5e1c-8a43-4c1e-9e57-3f0d0f0a7a03}" x="100" y="100"/>
<input font="Liberation Sans,9,-1,5,50,0,0,0,0,0,Regular" rotation="0" text="&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;>&#xa;&lt;html>&lt;head>&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; />&lt;meta charset=&quot;utf-8&quot; />&lt;style type=&quot;text/css&quot;>&#xa;p, li { white-space: pre-wrap; }&#xa;hr { height: 1px; border-width: 0; }&#xa;li.unchecked::marker { content: &quot;\2610&quot;; }&#xa;li.checked::marker { content: &quot;\2612&quot;; }&#xa;&lt;/style>&lt;/head>&lt;body style=&quot; font-family:'Liberation Sans'; font-size:9pt; font-weight:400; font-style:normal;&quot;>&#xa;&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;>OpenAlpha OpenBeta OpenGamma OpenDelta OpenEpsilon&lt;/p>&lt;/body>&lt;/html>" uuid="{5b0f5e1c-8a43-4c1e-9e57-3f0d0f0a7a04}" x="100" y="400"/>
<input font="Liberation Sans,9,-1,5,50,0,0,0,0,0,Regular" rotation="0" text="&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;>&#xa;&lt;html>&lt;head>&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; />&lt;meta charset=&quot;utf-8&quot; />&lt;style type=&quot;text/css&quot;>&#xa;p, li { white-space: pre-wrap; }&#xa;hr { height: 1px; border-width: 0; }&#xa;li.unchecked::marker { content: &quot;\2610&quot;; }&#xa;li.checked::marker { content: &quot;\2612&quot;; }&#xa;&lt;/style>&lt;/head>&lt;body style=&quot; font-family:'Liberation Sans'; font-size:9pt; font-weight:400; font-style:normal;&quot;>&#xa;&lt;p align=&quot;center&quot; style=&quot; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;>CentAlpha CentBeta CentGamma&lt;/p>&lt;/body>&lt;/html>" text_width="120" uuid="{5b0f5e1c-8a43-4c1e-9e57-3f0d0f0a7a08}" x="400" y="100"/>
</inputs>
</diagram>
<collection>
<category name="import">
<names>
<name lang="en">Imported elements</name>
</names>
<category name="10_electric">
<names>
<name lang="ar">كهربائي</name>
<name lang="ca">Elèctric</name>
<name lang="cs">Elektrotechnika</name>
<name lang="da">Elektrisk</name>
<name lang="de">Elektrik</name>
<name lang="el">Ηλεκτρικά</name>
<name lang="en">Electric</name>
<name lang="es">Eléctrica</name>
<name lang="fi">Sähkö</name>
<name lang="fr">Electrique</name>
<name lang="hr">Električno</name>
<name lang="hu">Elektromos</name>
<name lang="it">Elettrica</name>
<name lang="ja">電気</name>
<name lang="ko">전기</name>
<name lang="mn">Цахилгаан</name>
<name lang="nb">Elektrisk</name>
<name lang="nl">Elektrotechniek</name>
<name lang="nl_BE">Elektrisch</name>
<name lang="pl">Elektrotechnika</name>
<name lang="pt">Elétrico</name>
<name lang="pt_BR">Elétrica</name>
<name lang="ro">Electric</name>
<name lang="ru">Электротехника</name>
<name lang="sk">Elektrické</name>
<name lang="sl">Elektrika</name>
<name lang="sr">Електрика</name>
<name lang="sv">El</name>
<name lang="tr">Elektrik</name>
<name lang="uk">Електрика</name>
<name lang="zh">10_电气</name>
<name lang="zn">电气工程</name>
</names>
<category name="10_allpole">
<names>
<name lang="ar">متعدد الأقطاب</name>
<name lang="ca">Multifilar</name>
<name lang="cs">Vícežilový</name>
<name lang="da">Flere ledere</name>
<name lang="de">Allpolig</name>
<name lang="el">Πολυγραμμικό</name>
<name lang="en">All-pole</name>
<name lang="es">Multifilar</name>
<name lang="fi">Yleisnapa</name>
<name lang="fr">Multifilaire</name>
<name lang="hr">Univerzalni</name>
<name lang="hu">Többvonalas szimbólumok</name>
<name lang="it">Multifilare</name>
<name lang="ja">複線</name>
<name lang="ko">다-극</name>
<name lang="mn">Бүх туйл</name>
<name lang="nb">Alle poler</name>
<name lang="nl">Veel polig</name>
<name lang="nl_BE">All-pole</name>
<name lang="pl">Schematy wieloliniowe</name>
<name lang="pt">Multipolar</name>
<name lang="pt_BR">Multifilar</name>
<name lang="ro">Bipolar</name>
<name lang="ru">Многополюсные</name>
<name lang="sk">Všepólové</name>
<name lang="sl">Vsi poli</name>
<name lang="sr">Свe-полни</name>
<name lang="sv">Allpolig</name>
<name lang="tr">Çok kutuplu</name>
<name lang="uk">Багатополюсні</name>
<name lang="zh">10_全极</name>
</names>
<category name="310_relays_contactors_contacts">
<names>
<name lang="ar">المرحّلات والموصّلات وجهات الاتصال</name>
<name lang="ca">Relés, contactors i contactes</name>
<name lang="cs">Relé, stykače a kontakty</name>
<name lang="da">Relæer, kontaktorer og kontakter</name>
<name lang="de">Relais, Schütze und Kontakte</name>
<name lang="el">Ηλεκτρονόμοι και επαφές</name>
<name lang="en">Relays, contactors and contacts</name>
<name lang="es">Relevadores, contactores y contactos</name>
<name lang="fi">Releet, kontaktorit ja koskettimet</name>
<name lang="fr">Relais, contacteurs et contacts</name>
<name lang="hr">Releji, kontaktori i kontakti</name>
<name lang="hu">Relék, kontaktorok és érintkezők</name>
<name lang="it">Relè, contattori e contatti</name>
<name lang="ja">継電器、接触器、接点</name>
<name lang="ko">릴레이, 커넥터및 접점</name>
<name lang="mn">Реле, контактор болон контакт</name>
<name lang="nb">Reléer, kontaktorere og kontakter</name>
<name lang="nl">Relais, contactoren en contacten</name>
<name lang="nl_BE">Relais, contacteurs en contacten</name>
<name lang="pl">Przekaźniki i styczniki</name>
<name lang="pt">Relés, contatores e contactos</name>
<name lang="pt_BR">Relés, Contatores e contatos</name>
<name lang="ro">Relee, contactoare și contacte</name>
<name lang="ru">Реле, пускатели и контакты</name>
<name lang="sk">Relé, stykače a kontakty</name>
<name lang="sl">Releji, kontaktorji in kontakti</name>
<name lang="sr">Релеји, контактори и контакти</name>
<name lang="sv">Reläer, kontaktorer och kontakter</name>
<name lang="tr">Röleler, kontaktörler ve kontaklar</name>
<name lang="uk">Реле, контактори та контакти</name>
<name lang="zh">继电器、接触器与触点</name>
</names>
<category name="01_coils">
<names>
<name lang="ar">لفه</name>
<name lang="ca">Bobines</name>
<name lang="cs">cívka</name>
<name lang="da">spole</name>
<name lang="de">Spule</name>
<name lang="el">σπείρα</name>
<name lang="en">coil</name>
<name lang="es">bobina</name>
<name lang="fi">Käämi</name>
<name lang="fr">bobine</name>
<name lang="hr">zavojnica</name>
<name lang="hu">tekercs</name>
<name lang="it">bobina</name>
<name lang="ja">コイル</name>
<name lang="ko">코일</name>
<name lang="mn">ороомог</name>
<name lang="nb">Spole</name>
<name lang="nl">spoel</name>
<name lang="nl_BE">Spoel</name>
<name lang="pl">cewka</name>
<name lang="pt">bobina</name>
<name lang="pt_BR">bobina</name>
<name lang="ro">bobina</name>
<name lang="ru">катушка</name>
<name lang="sk">Cievka</name>
<name lang="sl">tuljava</name>
<name lang="sr">калем</name>
<name lang="sv">spole</name>
<name lang="tr">bobin</name>
<name lang="uk">котушка</name>
<name lang="zh">线圈</name>
</names>
<element name="bobine3.elmt">
<definition height="60" hotspot_x="20" hotspot_y="32" link_type="master" type="element" version="0.100.0" width="40">
<uuid uuid="{793302b1-e96a-f7f8-70bc-dec53eeaab5b}"/>
<names>
<name lang="ar">ملف</name>
<name lang="ca">Bobina</name>
<name lang="cs">Cívka relé</name>
<name lang="da">Relæ</name>
<name lang="de">Spule</name>
<name lang="el">Πηνίο</name>
<name lang="en">Coil</name>
<name lang="es">Bobina</name>
<name lang="fi">Käämi</name>
<name lang="fr">Bobine</name>
<name lang="hr">Zavojnica</name>
<name lang="hu">Tekercs</name>
<name lang="it">Bobina</name>
<name lang="ja">コイル</name>
<name lang="ko">코일</name>
<name lang="mn">Ороомог</name>
<name lang="nb">Spole</name>
<name lang="nl">Spoel</name>
<name lang="nl_BE">Relais spoel (algemeen)</name>
<name lang="pl">Cewka</name>
<name lang="pt">Bobina</name>
<name lang="pt_BR">Bobina</name>
<name lang="ro">Bobina</name>
<name lang="ru">Обмотка</name>
<name lang="sk">Cievka</name>
<name lang="sl">Tuljava</name>
<name lang="sr">Каlem</name>
<name lang="sv">Spole</name>
<name lang="tr">Bobin</name>
<name lang="uk">Котушка</name>
<name lang="zh">三线圈</name>
</names>
<kindInformations>
<kindInformation name="type">coil</kindInformation>
</kindInformations>
<informations>Author: The QElectroTech team
License: see http://qelectrotech.org/wiki/doc/elements_license</informations>
<description>
<rect antialias="false" height="16" rx="0" ry="0" style="line-style:normal;line-weight:normal;filling:none;color:black" width="28" x="-14" y="-8"/>
<line antialias="false" end1="none" end2="none" length1="1.5" length2="1.5" style="line-style:normal;line-weight:normal;filling:none;color:black" x1="0" x2="0" y1="-20" y2="-8"/>
<dynamic_text Halignment="AlignLeft" Valignment="AlignTop" font="Liberation Sans,4,-1,5,25,0,0,0,0,0,Regular" frame="false" keep_visual_rotation="false" rotation="0" text_from="UserText" text_width="-1" uuid="{0a58bdda-9173-48ba-acaa-35f2dc00c4bb}" x="2" y="11.33" z="3">
<text>A2</text>
</dynamic_text>
<dynamic_text Halignment="AlignLeft" Valignment="AlignTop" font="Liberation Sans,4,-1,5,25,0,0,0,0,0,Regular" frame="false" keep_visual_rotation="false" rotation="0" text_from="UserText" text_width="-1" uuid="{7e98ddfe-fde0-4715-a926-9f60cf750078}" x="2" y="-24.67" z="4">
<text>A1</text>
</dynamic_text>
<line antialias="false" end1="none" end2="none" length1="1.5" length2="1.5" style="line-style:normal;line-weight:normal;filling:none;color:black" x1="0" x2="0" y1="8" y2="20"/>
<dynamic_text Halignment="AlignLeft" Valignment="AlignTop" font="Liberation Sans,9,-1,5,50,0,0,0,0,0,Regular" frame="false" keep_visual_rotation="false" rotation="0" text_from="ElementInfo" text_width="-1" uuid="{3ef105d2-add6-4c4b-a4cd-120c40096202}" x="25" y="-9.17" z="6">
<text/>
<info_name>label</info_name>
</dynamic_text>
<terminal name="A1" orientation="n" type="Generic" uuid="{8d0fa333-2d98-4a75-8a4e-21c81cce7ec3}" x="0" y="-20"/>
<terminal name="A2" orientation="s" type="Generic" uuid="{c5376fd7-bdf1-4c10-985a-0d7d5f52c8f9}" x="0" y="20"/>
</description>
</definition>
</element>
</category>
<category name="02_contacts_cross_referencing">
<names>
<name lang="ar">تماسات مع إحالات</name>
<name lang="ca">Contactes amb referències creuades</name>
<name lang="cs">Kontakty s křížovými odkazy</name>
<name lang="da">Kontaker med krydsreference</name>
<name lang="de">Kontakte mit Querverweis</name>
<name lang="el">Επαφές με παραπομπές</name>
<name lang="en">Contacts with cross referencing</name>
<name lang="es">Contactos con referencia cruzada</name>
<name lang="fi">Kosketimet lehtiviittauksella</name>
<name lang="fr">Contacts avec référence croisée</name>
<name lang="hr">Kontakti s unakrsnim referenciranjem</name>
<name lang="hu">Érintkezők kereszthivatkozásokkal</name>
<name lang="it">Contatti con riferimenti incrociati</name>
<name lang="ja">相互参照付き接点</name>
<name lang="ko">교차 참조 접점</name>
<name lang="mn">Харилцан холбоос бүхий контакт</name>
<name lang="nb">Kontakter med kryssreferanse</name>
<name lang="nl">Contacten met kruisreferentie</name>
<name lang="nl_BE">Contacten met kruisverwijzing</name>
<name lang="pl">Zestyki (automatyczne oznaczenia)</name>
<name lang="pt">Contactos com renvoi de folio</name>
<name lang="pt_BR">Contatos com referencia cruzada</name>
<name lang="ro">Contacte cu renvoi de folio</name>
<name lang="ru">Контакты с перекрестными ссылками</name>
<name lang="sk">Kontakty s krížovým odkazom</name>
<name lang="sl">Kontakti s sklici</name>
<name lang="sr">Контакти са унакрсним референцирањем</name>
<name lang="sv">Kontakter med bladhänvisning</name>
<name lang="tr">Çapraz referanslı kontaklar</name>
<name lang="uk">Контакти з перехресними посиланнями</name>
<name lang="zh">触点交叉引用</name>
</names>
<category name="01_auxiliary_contacts">
<names>
<name lang="ar">مساعد</name>
<name lang="ca">Auxiliars</name>
<name lang="cs">Pomocné kontakty</name>
<name lang="da">Hjælpe kontakter</name>
<name lang="de">Hilfsglieder</name>
<name lang="el">Βοηθητικές επαφές</name>
<name lang="en">Auxiliary</name>
<name lang="es">Contactos auxiliares</name>
<name lang="fi">Apupiiri</name>
<name lang="fr">Auxilliaires</name>
<name lang="hr">Pomoćni</name>
<name lang="hu">Segéd</name>
<name lang="it">Contatti ausiliari</name>
<name lang="ja">補助</name>
<name lang="ko">보조 접점</name>
<name lang="mn">Туслах</name>
<name lang="nb">Hjelpe</name>
<name lang="nl">Hulpcontacten</name>
<name lang="nl_BE">Hulpcontacten</name>
<name lang="pl">Zestyki pomocnicze</name>
<name lang="pt">Auxiliar</name>
<name lang="pt_BR">Auxiliares</name>
<name lang="ro">Auxiliare</name>
<name lang="ru">Внешние</name>
<name lang="sk">Pomocné</name>
<name lang="sl">Pomožni</name>
<name lang="sr">Помоћни</name>
<name lang="sv">Hjälp</name>
<name lang="tr">Yardımcı</name>
<name lang="uk">Допоміжні</name>
<name lang="zh">辅助触点</name>
</names>
<element name="con_simple.elmt">
<definition height="60" hotspot_x="10" hotspot_y="30" link_type="slave" type="element" version="0.100.0" width="20">
<uuid uuid="{9d34fad7-8a48-2a3e-df68-74d26a8f1012}"/>
<names>
<name lang="ar">مُلامس بسيط (NO)</name>
<name lang="ca">Contacte senzill</name>
<name lang="cs">Spínací kontakt</name>
<name lang="da">Enkel kontakt</name>
<name lang="de">Schliesser</name>
<name lang="el">Απλή επαφή, ανοικτή</name>
<name lang="en">Simple contact</name>
<name lang="es">Contactor simple</name>
<name lang="fi">Yksinkertainen kosketin</name>
<name lang="fr">Contact simple</name>
<name lang="hr">Jednostavni kontakt</name>
<name lang="hu">Egyszerű érintkező</name>
<name lang="it">Contatto semplice (NO)</name>
<name lang="ja">単純接点</name>
<name lang="ko">단순 접점</name>
<name lang="mn">Энгийн контакт</name>
<name lang="nb">Enkel kontakt</name>
<name lang="nl">contact enkel</name>
<name lang="nl_BE">Eenvoudig contact</name>
<name lang="pl">Zestyk zwierny</name>
<name lang="pt">Contacto simples (NO)</name>
<name lang="pt_BR">Contato simples</name>
<name lang="ro">Contact simplu</name>
<name lang="ru">Проктой контакт (НР)</name>
<name lang="sk">Jednoduchý kontakt</name>
<name lang="sl">Enostavni kontakt</name>
<name lang="sr">Прости контакт</name>
<name lang="sv">Enkelkontakt</name>
<name lang="tr">Basit kontak</name>
<name lang="uk">Простий контакт</name>
<name lang="zh">简单连接器</name>
</names>
<kindInformations>
<kindInformation name="type">simple</kindInformation>
<kindInformation name="state">NO</kindInformation>
<kindInformation name="number">1</kindInformation>
</kindInformations>
<informations>Author: The QElectroTech team
License: see http://qelectrotech.org/wiki/doc/elements_license</informations>
<description>
<polygon antialias="true" closed="false" style="line-style:normal;line-weight:normal;filling:none;color:black" x1="-5" x2="0" x3="0" y1="-10" y2="10" y3="20"/>
<dynamic_text Halignment="AlignLeft" Valignment="AlignTop" font="Liberation Sans,9,-1,5,50,0,0,0,0,0,Regular" frame="false" keep_visual_rotation="false" rotation="0" text_from="ElementInfo" text_width="-1" uuid="{a4a5addc-2580-413c-828c-6e93cfe494ef}" x="2.75" y="-9.5" z="2">
<text/>
<info_name>label</info_name>
</dynamic_text>
<line antialias="false" end1="none" end2="none" length1="1.5" length2="1.5" style="line-style:normal;line-weight:normal;filling:none;color:black" x1="0" x2="0" y1="-20" y2="-10"/>
<terminal name="" orientation="n" type="Generic" uuid="{b3b3dc63-1c98-409b-ab79-3dbaea3556cb}" x="0" y="-20"/>
<terminal name="" orientation="s" type="Generic" uuid="{b9683546-f0a2-4386-b114-ef1d46b2df3d}" x="0" y="20"/>
</description>
</definition>
</element>
</category>
</category>
</category>
</category>
</category>
</category>
</collection>
</project>
+22 -8
View File
@@ -155,10 +155,12 @@ class tst_movedterminals : public QObject
QTemporaryDir m_dir;
int m_run = 0;
struct Result { int wires = -1; QString log; QStringList pairs; };
// wires: those joined to their terminals; saved: those in the saved
// file, which keeps a wire it could not join as it was read (F128)
struct Result { int wires = -1; int saved = -1; QString log; QStringList pairs; };
// Open and save the project in a sandbox of its own (so a running
// QElectroTech cannot answer instead): the wires that survive.
// QElectroTech cannot answer instead): the wires that are joined.
Result resave(const QString &xml)
{
const QString in = m_dir.filePath(QStringLiteral("in%1.qet").arg(m_run));
@@ -184,8 +186,13 @@ class tst_movedterminals : public QObject
QDomDocument doc;
QFile saved(out);
if (!saved.open(QIODevice::ReadOnly) || !doc.setContent(&saved)) return {};
r.wires = doc.elementsByTagName(QStringLiteral("conductor")).size();
for (int i = 0; i < r.wires; ++i) {
r.saved = doc.elementsByTagName(QStringLiteral("conductor")).size();
int not_joined = 0;
static const QRegularExpression not_loaded(QStringLiteral("(\\d+) wire\\(s\\) not loaded"));
for (auto it = not_loaded.globalMatch(r.log); it.hasNext() ; )
not_joined += it.next().captured(1).toInt();
r.wires = r.saved - not_joined;
for (int i = 0; i < r.saved; ++i) {
const QDomElement c = doc.elementsByTagName(QStringLiteral("conductor")).at(i).toElement();
r.pairs << c.attribute(QStringLiteral("terminal1")) + QLatin1Char('>') + c.attribute(QStringLiteral("terminal2"));
}
@@ -236,8 +243,12 @@ private slots:
void movedSixUnitsDoesNot()
{
QCOMPARE(resave(project({16}, byId())).wires, 0);
QCOMPARE(resave(project({16}, byUuid(10))).wires, 0);
const Result legacy = resave(project({16}, byId()));
QCOMPARE(legacy.wires, 0);
QCOMPARE(legacy.saved, 1);
const Result uuid = resave(project({16}, byUuid(10)));
QCOMPARE(uuid.wires, 0);
QCOMPARE(uuid.saved, 1);
}
// Two live terminals in reach of one saved one: no way to tell which.
@@ -245,6 +256,7 @@ private slots:
{
const Result legacy = resave(project({13, 14}, byId()));
QCOMPARE(legacy.wires, 0);
QCOMPARE(legacy.saved, 1);
QVERIFY(!said(legacy));
QCOMPARE(resave(project({13, 14}, byUuid(10))).wires, 0);
}
@@ -296,8 +308,10 @@ private slots:
// one, and no single shift maps both.
void ambiguousStillRefuses()
{
QCOMPARE(resave(multiProject({{'n', 0, -10}, {'n', 4, -10}},
{{'n', 2, -10}, {'n', 6, -10}, {'n', -2, -10}})).wires, 0);
const Result r = resave(multiProject({{'n', 0, -10}, {'n', 4, -10}},
{{'n', 2, -10}, {'n', 6, -10}, {'n', -2, -10}}));
QCOMPARE(r.wires, 0);
QCOMPARE(r.saved, 2);
}
};
+2 -1
View File
@@ -77,9 +77,10 @@ class tst_qetcontainer : public QObject
{
QDomDocument copy = document.cloneNode(true).toDocument();
const bool own_uuids = QetContainer::ownUuids(copy.documentElement());
const QSet<QString> defined = QetContainer::embeddedDefinitions(copy.documentElement());
for (QDomElement diagram = copy.documentElement().firstChildElement(QStringLiteral("diagram"));
!diagram.isNull() ; diagram = diagram.nextSiblingElement(QStringLiteral("diagram")))
QetContainer::dropComputed(diagram, own_uuids);
QetContainer::dropComputed(diagram, own_uuids, defined);
return copy;
}
+95
View File
@@ -802,6 +802,101 @@ private slots:
QStringLiteral("<property [^>]*name=\"accents\"[^>]*>Armoire façade été</property>"))),
"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<QString>("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(
"<element name=\"bobine3\\.elmt\">.*?</element>\\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(
"<element name=\"con_simple\\.elmt\">.*?</element>"),
QRegularExpression::DotMatchesEverythingOption);
QString contact = contact_definition.match(xml).captured(0);
QVERIFY(!contact.isEmpty());
const QString before = contact;
static const QRegularExpression terminal(QStringLiteral(
"(<terminal [^>]*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, "<element [^>]*\\btype="), 2);
QCOMPARE(count(saved, "<conductor "), 2);
QCOMPARE(count(saved, "<link_uuid "), 2);
const QString second = resave(first);
QVERIFY2(!second.isEmpty(), "second --resave failed");
QVERIFY2(read(second) == read(first), "the second save changed the file");
if (damage != QLatin1String("coil")) return;
//The definition back: the coil and its wires are built again
static const QRegularExpression collection_category(QStringLiteral(
"(<category name=\"01_coils\">\\s*<names>.*?</names>\\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)